@toolu/opencode 6.8.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 (331) hide show
  1. package/generated/GENERATED-NOTES.md +16 -0
  2. package/generated/agents/toolu--architect.md +52 -0
  3. package/generated/agents/toolu--deep-explore.md +65 -0
  4. package/generated/agents/toolu--implementer.md +50 -0
  5. package/generated/agents/toolu--quick-task.md +54 -0
  6. package/generated/agents/toolu--research-agent.md +101 -0
  7. package/generated/commands/toolu--commit.md +8 -0
  8. package/generated/commands/toolu--review-and-commit.md +8 -0
  9. package/generated/opencode.toolu.json +116 -0
  10. package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
  11. package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
  12. package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
  13. package/generated/skills/toolu--debug/SKILL.md +54 -0
  14. package/generated/skills/toolu--deep-research/SKILL.md +51 -0
  15. package/generated/skills/toolu--execution/SKILL.md +77 -0
  16. package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
  17. package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
  18. package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
  19. package/generated/skills/toolu--plan/SKILL.md +56 -0
  20. package/generated/skills/toolu--plan/references/ledger.md +18 -0
  21. package/generated/skills/toolu--plan-review/SKILL.md +48 -0
  22. package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
  23. package/generated/skills/toolu--setup/SKILL.md +24 -0
  24. package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
  25. package/generated/skills/toolu--spec/SKILL.md +67 -0
  26. package/generated/skills/toolu--spec-review/SKILL.md +50 -0
  27. package/generated/skills/toolu--test/SKILL.md +49 -0
  28. package/package.json +51 -0
  29. package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
  30. package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
  31. package/plugins/agent-browser/README.md +18 -0
  32. package/plugins/agent-browser/hooks/hooks.json +15 -0
  33. package/plugins/agent-browser/hooks/session-start.sh +42 -0
  34. package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
  35. package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
  36. package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
  37. package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
  38. package/plugins/ast-grep/README.md +21 -0
  39. package/plugins/ast-grep/hooks/hooks.json +15 -0
  40. package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
  41. package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
  42. package/plugins/ast-grep/hooks/register.sh +68 -0
  43. package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
  44. package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
  45. package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
  46. package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
  47. package/plugins/context7/.claude-plugin/plugin.json +18 -0
  48. package/plugins/context7/.codex-plugin/plugin.json +7 -0
  49. package/plugins/context7/README.md +19 -0
  50. package/plugins/context7/hooks/hooks.json +15 -0
  51. package/plugins/context7/hooks/session-start.sh +42 -0
  52. package/plugins/context7/skills/context7/SKILL.md +64 -0
  53. package/plugins/context7/skills/context7/scripts/search.sh +151 -0
  54. package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
  55. package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
  56. package/plugins/exa-search/README.md +19 -0
  57. package/plugins/exa-search/hooks/hooks.json +15 -0
  58. package/plugins/exa-search/hooks/session-start.sh +34 -0
  59. package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
  60. package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
  61. package/plugins/jev/.claude-plugin/plugin.json +19 -0
  62. package/plugins/jev/.codex-plugin/plugin.json +7 -0
  63. package/plugins/jev/README.md +61 -0
  64. package/plugins/jev/hooks/hooks.json +25 -0
  65. package/plugins/jev/hooks/lib/common.sh +49 -0
  66. package/plugins/jev/hooks/session-start.sh +45 -0
  67. package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
  68. package/plugins/jev/skills/jev/SKILL.md +81 -0
  69. package/plugins/jev/skills/jev/evals/README.md +98 -0
  70. package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
  71. package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
  72. package/plugins/jira/.claude-plugin/plugin.json +18 -0
  73. package/plugins/jira/.codex-plugin/plugin.json +7 -0
  74. package/plugins/jira/README.md +42 -0
  75. package/plugins/jira/hooks/hooks.json +15 -0
  76. package/plugins/jira/hooks/session-start.sh +34 -0
  77. package/plugins/jira/skills/jira/SKILL.md +123 -0
  78. package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
  79. package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
  80. package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
  81. package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
  82. package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
  83. package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
  84. package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
  85. package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
  86. package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
  87. package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
  88. package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
  89. package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
  90. package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
  91. package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
  92. package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
  93. package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
  94. package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
  95. package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
  96. package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
  97. package/plugins/pr-babysit/README.md +38 -0
  98. package/plugins/pr-babysit/commands/babysit.md +13 -0
  99. package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
  100. package/plugins/pr-babysit/hooks/hooks.json +15 -0
  101. package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
  102. package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
  103. package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
  104. package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
  105. package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
  106. package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
  107. package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
  108. package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
  109. package/plugins/pr-babysit/scripts/record.sh +69 -0
  110. package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
  111. package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
  112. package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
  113. package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
  114. package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
  115. package/plugins/pr-babysit/workflows/babysit.md +722 -0
  116. package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
  117. package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
  118. package/plugins/python-quality/README.md +23 -0
  119. package/plugins/python-quality/hooks/check-toolu.sh +20 -0
  120. package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
  121. package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
  122. package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
  123. package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
  124. package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
  125. package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
  126. package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
  127. package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
  128. package/plugins/python-quality/hooks/hooks.json +19 -0
  129. package/plugins/python-quality/hooks/register.sh +65 -0
  130. package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
  131. package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
  132. package/plugins/rust-quality/README.md +24 -0
  133. package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
  134. package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
  135. package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
  136. package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
  137. package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
  138. package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
  139. package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
  140. package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
  141. package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
  142. package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
  143. package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
  144. package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
  145. package/plugins/rust-quality/hooks/hooks.json +19 -0
  146. package/plugins/rust-quality/hooks/register.sh +65 -0
  147. package/plugins/statusline/.claude-plugin/plugin.json +19 -0
  148. package/plugins/statusline/.codex-plugin/plugin.json +7 -0
  149. package/plugins/statusline/README.md +108 -0
  150. package/plugins/statusline/commands/setup.md +30 -0
  151. package/plugins/statusline/hooks/hooks.json +15 -0
  152. package/plugins/statusline/hooks/session-start.sh +45 -0
  153. package/plugins/statusline/scripts/collect-status.sh +147 -0
  154. package/plugins/statusline/scripts/setup.sh +93 -0
  155. package/plugins/statusline/scripts/status.sh +55 -0
  156. package/plugins/statusline/skills/status/SKILL.md +13 -0
  157. package/plugins/statusline/statusline.sh +177 -0
  158. package/plugins/toolu/.claude-plugin/plugin.json +19 -0
  159. package/plugins/toolu/.codex-plugin/plugin.json +7 -0
  160. package/plugins/toolu/README.md +35 -0
  161. package/plugins/toolu/agents/.gitkeep +0 -0
  162. package/plugins/toolu/agents/architect.md +52 -0
  163. package/plugins/toolu/agents/deep-explore.md +65 -0
  164. package/plugins/toolu/agents/implementer.md +50 -0
  165. package/plugins/toolu/agents/quick-task.md +54 -0
  166. package/plugins/toolu/agents/research-agent.md +107 -0
  167. package/plugins/toolu/assets/agents/architect.toml +9 -0
  168. package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
  169. package/plugins/toolu/assets/agents/implementer.toml +9 -0
  170. package/plugins/toolu/assets/agents/quick-task.toml +9 -0
  171. package/plugins/toolu/assets/agents/research-agent.toml +9 -0
  172. package/plugins/toolu/commands/.gitkeep +0 -0
  173. package/plugins/toolu/commands/commit.md +5 -0
  174. package/plugins/toolu/commands/review-and-commit.md +5 -0
  175. package/plugins/toolu/hooks/docs/gates.md +157 -0
  176. package/plugins/toolu/hooks/docs/model-routing.md +14 -0
  177. package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
  178. package/plugins/toolu/hooks/docs/push-review.md +123 -0
  179. package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
  180. package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
  181. package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
  182. package/plugins/toolu/hooks/docs/session-start.md +16 -0
  183. package/plugins/toolu/hooks/hooks.json +76 -0
  184. package/plugins/toolu/hooks/lib/config.sh +355 -0
  185. package/plugins/toolu/hooks/lib/detect.sh +929 -0
  186. package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
  187. package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
  188. package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
  189. package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
  190. package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
  191. package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
  192. package/plugins/toolu/hooks/lib/host.sh +205 -0
  193. package/plugins/toolu/hooks/lib/permissions.sh +125 -0
  194. package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
  195. package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
  196. package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
  197. package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
  198. package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
  199. package/plugins/toolu/hooks/lib/registry.sh +72 -0
  200. package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
  201. package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
  202. package/plugins/toolu/hooks/lib/verdict.sh +477 -0
  203. package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
  204. package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
  205. package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
  206. package/plugins/toolu/hooks/pre-compact.sh +18 -0
  207. package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
  208. package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
  209. package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
  210. package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
  211. package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
  212. package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
  213. package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
  214. package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
  215. package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
  216. package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
  217. package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
  218. package/plugins/toolu/hooks/session-start.sh +379 -0
  219. package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
  220. package/plugins/toolu/scripts/context-budget.sh +111 -0
  221. package/plugins/toolu/scripts/debug-log.sh +98 -0
  222. package/plugins/toolu/scripts/debug-stack.sh +108 -0
  223. package/plugins/toolu/scripts/debug-testfail.sh +97 -0
  224. package/plugins/toolu/settings/.gitkeep +0 -0
  225. package/plugins/toolu/settings/README.md +127 -0
  226. package/plugins/toolu/settings/bash-allowlist.txt +12 -0
  227. package/plugins/toolu/settings/bash-denylist.txt +25 -0
  228. package/plugins/toolu/settings/code-edit-rules.json +67 -0
  229. package/plugins/toolu/settings/commit-prefixes.txt +12 -0
  230. package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
  231. package/plugins/toolu/settings/permissions.fragment.json +36 -0
  232. package/plugins/toolu/settings/protected-files.txt +30 -0
  233. package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
  234. package/plugins/toolu/settings/toolu.config.example.json +130 -0
  235. package/plugins/toolu/skills/.gitkeep +0 -0
  236. package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
  237. package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
  238. package/plugins/toolu/skills/commit/SKILL.md +11 -0
  239. package/plugins/toolu/skills/debug/SKILL.md +54 -0
  240. package/plugins/toolu/skills/debug/evals/README.md +17 -0
  241. package/plugins/toolu/skills/debug/evals/cases.json +24 -0
  242. package/plugins/toolu/skills/debug/evals/results.json +34 -0
  243. package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
  244. package/plugins/toolu/skills/execution/SKILL.md +77 -0
  245. package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
  246. package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
  247. package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
  248. package/plugins/toolu/skills/plan/SKILL.md +56 -0
  249. package/plugins/toolu/skills/plan/references/ledger.md +18 -0
  250. package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
  251. package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
  252. package/plugins/toolu/skills/setup/SKILL.md +24 -0
  253. package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
  254. package/plugins/toolu/skills/spec/SKILL.md +67 -0
  255. package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
  256. package/plugins/toolu/skills/test/SKILL.md +49 -0
  257. package/plugins/toolu/workflows/commit.md +21 -0
  258. package/plugins/toolu/workflows/host-mapping.md +22 -0
  259. package/plugins/toolu/workflows/review-and-commit.md +54 -0
  260. package/plugins/toolu/workflows/semantic-judgments.md +35 -0
  261. package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
  262. package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
  263. package/plugins/toolu-review/README.md +17 -0
  264. package/plugins/toolu-review/hooks/hooks.json +15 -0
  265. package/plugins/toolu-review/hooks/session-start.sh +34 -0
  266. package/plugins/toolu-review/skills/review/SKILL.md +90 -0
  267. package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
  268. package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
  269. package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
  270. package/plugins/ts-quality/README.md +24 -0
  271. package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
  272. package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
  273. package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
  274. package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
  275. package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
  276. package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
  277. package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
  278. package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
  279. package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
  280. package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
  281. package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
  282. package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
  283. package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
  284. package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
  285. package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
  286. package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
  287. package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
  288. package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
  289. package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
  290. package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
  291. package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
  292. package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
  293. package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
  294. package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
  295. package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
  296. package/plugins/ts-quality/hooks/hooks.json +19 -0
  297. package/plugins/ts-quality/hooks/register.sh +65 -0
  298. package/src/adapter/__tests__/evaluate.test.ts +82 -0
  299. package/src/adapter/__tests__/permission-map.test.ts +91 -0
  300. package/src/adapter/evaluate.ts +54 -0
  301. package/src/adapter/permission-map.ts +177 -0
  302. package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
  303. package/src/bootstrap/entrypoint.ts +18 -0
  304. package/src/bootstrap/index.ts +5 -0
  305. package/src/bootstrap/readiness.ts +59 -0
  306. package/src/bootstrap/result.ts +21 -0
  307. package/src/bootstrap/runtime.ts +99 -0
  308. package/src/bootstrap/test-helpers.ts +25 -0
  309. package/src/host/__tests__/host.test.ts +37 -0
  310. package/src/host/detect.ts +46 -0
  311. package/src/host/index.ts +10 -0
  312. package/src/host/roots.ts +59 -0
  313. package/src/host/types.ts +14 -0
  314. package/src/inventory/__tests__/inventory.test.ts +69 -0
  315. package/src/inventory/index.ts +9 -0
  316. package/src/inventory/manifest.ts +46 -0
  317. package/src/inventory/scan.ts +66 -0
  318. package/src/inventory/selection.ts +92 -0
  319. package/src/inventory/types.ts +22 -0
  320. package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
  321. package/src/lifecycle/index.ts +2 -0
  322. package/src/lifecycle/table.ts +42 -0
  323. package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
  324. package/src/plugin/plugin-stub.ts +7 -0
  325. package/src/plugin/toolu.ts +142 -0
  326. package/src/preflight/__tests__/preflight.test.ts +20 -0
  327. package/src/preflight/check.ts +75 -0
  328. package/src/preflight/index.ts +2 -0
  329. package/src/select/__tests__/select.test.ts +52 -0
  330. package/src/select/index.ts +2 -0
  331. package/src/select/resolve.ts +87 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # issue.sh — Jira issue operations: get, create, update, delete, comment,
3
+ # transition(s), assign. Sourced; depends on http.sh (jira_curl/jira_lean) and
4
+ # adf.sh (jira_text_to_adf). Comment/description bodies are version-gated (ADF
5
+ # for v3, plain text for v2); assign/transition mutate live tickets.
6
+
7
+ _jira_issue_path() { printf '/rest/api/%s/issue' "${_JIRA_VER:-3}"; }
8
+
9
+ # Lean projection for one issue: keep the useful fields, drop null/empty ones.
10
+ _JIRA_ISSUE_LEAN='{key,
11
+ summary: .fields.summary, status: .fields.status.name,
12
+ assignee: .fields.assignee.displayName, reporter: .fields.reporter.displayName,
13
+ priority: .fields.priority.name, type: .fields.issuetype.name,
14
+ project: .fields.project.key, created: .fields.created, updated: .fields.updated,
15
+ labels: .fields.labels}
16
+ | with_entries(select(.value != null and .value != [] and .value != ""))'
17
+
18
+ # _jira_assignee_obj ID: compact JSON for an assignee value; "-" unassigns.
19
+ # v3 uses accountId, v2 uses name.
20
+ _jira_assignee_obj() {
21
+ local id="$1" field="accountId"
22
+ [[ "${_JIRA_VER:-3}" == "2" ]] && field="name"
23
+ if [[ "$id" == "-" ]]; then
24
+ jq -nc --arg f "$field" '{($f): null}'
25
+ else
26
+ jq -nc --arg f "$field" --arg i "$id" '{($f): $i}'
27
+ fi
28
+ }
29
+
30
+ jira_issue() {
31
+ local action="${1:-}"; [[ $# -gt 0 ]] && shift
32
+ case "$action" in
33
+ get) _jira_issue_get "$@";;
34
+ create) _jira_issue_create "$@";;
35
+ update) _jira_issue_update "$@";;
36
+ delete) _jira_issue_delete "$@";;
37
+ comment) _jira_issue_comment "$@";;
38
+ transitions) _jira_issue_transitions "$@";;
39
+ transition) _jira_issue_transition "$@";;
40
+ assign) _jira_issue_assign "$@";;
41
+ *) echo "Usage: jira issue <get|create|update|delete|comment|transition|transitions|assign> ..." >&2; return 1;;
42
+ esac
43
+ }
44
+
45
+ _jira_issue_get() {
46
+ local key="${1:-}"
47
+ [[ -z "$key" ]] && { echo "Usage: jira issue get <KEY>" >&2; return 1; }
48
+ jira_curl GET "$(_jira_issue_path)/$key" | jira_lean "$_JIRA_ISSUE_LEAN"
49
+ }
50
+
51
+ _jira_issue_create() {
52
+ local project="" itype="" summary="" desc="" assignee=""
53
+ local fields=()
54
+ while [[ $# -gt 0 ]]; do
55
+ case "$1" in
56
+ -p|--project) project="$2"; shift 2;;
57
+ -t|--type) itype="$2"; shift 2;;
58
+ -s|--summary) summary="$2"; shift 2;;
59
+ -d|--desc) desc="$2"; shift 2;;
60
+ --assignee) assignee="$2"; shift 2;;
61
+ -f|--field) fields+=("$2"); shift 2;;
62
+ *) echo "issue create: unknown option '$1'" >&2; return 1;;
63
+ esac
64
+ done
65
+ [[ -z "$project" || -z "$itype" || -z "$summary" ]] && {
66
+ echo "Usage: jira issue create -p <PROJECT> -t <TYPE> -s <SUMMARY> [-d DESC] [--assignee ID] [-f field=val]..." >&2
67
+ return 1
68
+ }
69
+ local body
70
+ body=$(jq -nc --arg p "$project" --arg t "$itype" --arg s "$summary" \
71
+ '{fields:{project:{key:$p}, issuetype:{name:$t}, summary:$s}}')
72
+ if [[ -n "$desc" ]]; then
73
+ body=$(jq -c --argjson d "$(jira_text_to_adf "$desc")" '.fields.description=$d' <<<"$body")
74
+ fi
75
+ if [[ -n "$assignee" ]]; then
76
+ body=$(jq -c --argjson a "$(_jira_assignee_obj "$assignee")" '.fields.assignee=$a' <<<"$body")
77
+ fi
78
+ local kv k v
79
+ for kv in ${fields[@]+"${fields[@]}"}; do
80
+ k="${kv%%=*}"; v="${kv#*=}"
81
+ body=$(jq -c --arg k "$k" --arg v "$v" '.fields[$k]=$v' <<<"$body")
82
+ done
83
+ jira_curl POST "$(_jira_issue_path)" "$body" | jira_lean '.'
84
+ }
85
+
86
+ _jira_issue_update() {
87
+ local key="${1:-}"; [[ $# -gt 0 ]] && shift
88
+ [[ -z "$key" ]] && { echo "Usage: jira issue update <KEY> [-s SUMMARY] [-d DESC] [-f field=val]..." >&2; return 1; }
89
+ local summary="" desc="" fields=() body='{"fields":{}}'
90
+ while [[ $# -gt 0 ]]; do
91
+ case "$1" in
92
+ -s|--summary) summary="$2"; shift 2;;
93
+ -d|--desc) desc="$2"; shift 2;;
94
+ -f|--field) fields+=("$2"); shift 2;;
95
+ *) echo "issue update: unknown option '$1'" >&2; return 1;;
96
+ esac
97
+ done
98
+ [[ -n "$summary" ]] && body=$(jq -c --arg s "$summary" '.fields.summary=$s' <<<"$body")
99
+ [[ -n "$desc" ]] && body=$(jq -c --argjson d "$(jira_text_to_adf "$desc")" '.fields.description=$d' <<<"$body")
100
+ local kv k v
101
+ for kv in ${fields[@]+"${fields[@]}"}; do
102
+ k="${kv%%=*}"; v="${kv#*=}"
103
+ body=$(jq -c --arg k "$k" --arg v "$v" '.fields[$k]=$v' <<<"$body")
104
+ done
105
+ jira_curl PUT "$(_jira_issue_path)/$key" "$body"
106
+ echo "updated $key"
107
+ }
108
+
109
+ _jira_issue_delete() {
110
+ local key="${1:-}"
111
+ [[ -z "$key" ]] && { echo "Usage: jira issue delete <KEY>" >&2; return 1; }
112
+ jira_curl DELETE "$(_jira_issue_path)/$key"
113
+ echo "deleted $key"
114
+ }
115
+
116
+ _jira_issue_comment() {
117
+ local key="${1:-}" text="${2:-}"
118
+ [[ -z "$key" || -z "$text" ]] && { echo "Usage: jira issue comment <KEY> <TEXT>" >&2; return 1; }
119
+ local body
120
+ body=$(jq -nc --argjson b "$(jira_text_to_adf "$text")" '{body:$b}')
121
+ jira_curl POST "$(_jira_issue_path)/$key/comment" "$body" | jira_lean '.'
122
+ }
123
+
124
+ _jira_issue_transitions() {
125
+ local key="${1:-}"
126
+ [[ -z "$key" ]] && { echo "Usage: jira issue transitions <KEY>" >&2; return 1; }
127
+ jira_curl GET "$(_jira_issue_path)/$key/transitions" | jira_lean '{transitions: [.transitions[] | {id, name}]}'
128
+ }
129
+
130
+ _jira_issue_transition() {
131
+ local key="${1:-}" want="${2:-}"
132
+ [[ -z "$key" || -z "$want" ]] && { echo "Usage: jira issue transition <KEY> <NAME|ID>" >&2; return 1; }
133
+ local id
134
+ if [[ "$want" =~ ^[0-9]+$ ]]; then
135
+ id="$want"
136
+ else
137
+ local list matches n
138
+ list=$(jira_curl GET "$(_jira_issue_path)/$key/transitions") || return 1
139
+ matches=$(jq -c --arg w "$want" '[.transitions[] | select((.name|ascii_downcase)==($w|ascii_downcase)) | .id]' <<<"$list")
140
+ n=$(jq 'length' <<<"$matches")
141
+ if [[ "$n" -eq 0 ]]; then
142
+ echo "jira: no transition matches '$want'. Available: $(jq -r '[.transitions[].name]|join(", ")' <<<"$list")" >&2
143
+ return 1
144
+ elif [[ "$n" -gt 1 ]]; then
145
+ echo "jira: transition '$want' is ambiguous (matched ids: $(jq -r 'join(", ")' <<<"$matches"))" >&2
146
+ return 1
147
+ fi
148
+ id=$(jq -r '.[0]' <<<"$matches")
149
+ fi
150
+ jira_curl POST "$(_jira_issue_path)/$key/transitions" "$(jq -nc --arg i "$id" '{transition:{id:$i}}')"
151
+ echo "transitioned $key -> $id"
152
+ }
153
+
154
+ _jira_issue_assign() {
155
+ local key="${1:-}" who="${2:-}"
156
+ [[ -z "$key" || -z "$who" ]] && { echo "Usage: jira issue assign <KEY> <ACCOUNT_ID|->" >&2; return 1; }
157
+ jira_curl PUT "$(_jira_issue_path)/$key/assignee" "$(_jira_assignee_obj "$who")"
158
+ echo "assigned $key"
159
+ }
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ # paginate.sh — follow Jira's two pagination models and concatenate items.
3
+ # token : v3 /search/jql — nextPageToken, no total; POST, carry the token.
4
+ # offset : v2 /search and all Agile endpoints — startAt/maxResults/total.
5
+ # Emits a JSON array of every item under ITEMS_KEY across all pages. Sourced;
6
+ # depends on jira_curl from http.sh.
7
+
8
+ # jira_paginate MODE METHOD PATH ITEMS_KEY [BODY]
9
+ # MODE = token | offset ; ITEMS_KEY = issues | values ; BODY = JSON (default {})
10
+ jira_paginate() {
11
+ local mode="$1" method="$2" path="$3" items="$4" body="${5:-}"
12
+ [[ -z "$body" ]] && body='{}'
13
+ local acc='[]' page req url max token="" start=0 total got last guard=0
14
+ max=$(jq -r '.maxResults // 50' <<<"$body")
15
+
16
+ while (( guard++ < 1000 )); do
17
+ req="$body"; url="$path"
18
+ if [[ "$mode" == "token" ]]; then
19
+ [[ -n "$token" ]] && req=$(jq -c --arg t "$token" '. + {nextPageToken:$t}' <<<"$body")
20
+ page=$(jira_curl "$method" "$url" "$req") || return 1
21
+ elif [[ "$method" == "GET" ]]; then
22
+ local sep="?"; [[ "$url" == *"?"* ]] && sep="&"
23
+ url="${url}${sep}startAt=${start}&maxResults=${max}"
24
+ page=$(jira_curl GET "$url") || return 1
25
+ else
26
+ req=$(jq -c --argjson s "$start" --argjson m "$max" '. + {startAt:$s, maxResults:$m}' <<<"$body")
27
+ page=$(jira_curl "$method" "$url" "$req") || return 1
28
+ fi
29
+
30
+ acc=$(jq -c --argjson acc "$acc" --arg k "$items" '$acc + (.[$k] // [])' <<<"$page")
31
+
32
+ if [[ "$mode" == "token" ]]; then
33
+ token=$(jq -r '.nextPageToken // empty' <<<"$page")
34
+ last=$(jq -r '.isLast // false' <<<"$page")
35
+ [[ "$last" == "true" || -z "$token" ]] && break
36
+ else
37
+ total=$(jq -r '.total // 0' <<<"$page")
38
+ got=$(jq -r --arg k "$items" '(.[$k] // []) | length' <<<"$page")
39
+ (( got == 0 )) && break
40
+ start=$(( start + got ))
41
+ (( start >= total )) && break
42
+ fi
43
+ done
44
+ printf '%s\n' "$acc"
45
+ }
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # Plan-doc parsing for the `plan` family: pull the machine-readable steps block
3
+ # and the `**Issue:**` header key out of a jira plan doc. Pure functions, jq-only,
4
+ # no command execution and no `set` (this is a sourced library).
5
+ # Source via: . "${BASH_SOURCE[0]%/*}/plan-parse.sh"
6
+ #
7
+ # The steps-block grammar mirrors toolu's plan-ledger-parse.sh so a jira plan doc
8
+ # stays readable by the same tooling: an exact `## Steps (machine-readable)`
9
+ # heading followed by the first ```json fence.
10
+
11
+ # jira_plan_parse_steps PLAN_DOC
12
+ # Print the normalized steps JSON array (jq-compacted) on stdout. Validates the
13
+ # block is a non-empty array whose every element has non-empty string
14
+ # id/title/check. Backfills the optional authored fields (activity -> null).
15
+ # Missing heading/block, empty array, or malformed json -> stderr + non-zero.
16
+ jira_plan_parse_steps() {
17
+ local doc="$1" block
18
+ if [ ! -f "$doc" ]; then
19
+ echo "jira plan: plan doc not found: $doc" >&2
20
+ return 1
21
+ fi
22
+ block=$(awk '
23
+ /^## Steps \(machine-readable\)[[:space:]]*$/ { in_steps = 1; next }
24
+ in_steps && !in_block && /^```json[[:space:]]*$/ { in_block = 1; next }
25
+ in_block && /^```[[:space:]]*$/ { exit }
26
+ in_block { print }
27
+ ' "$doc")
28
+ if [ -z "$block" ]; then
29
+ echo "jira plan: no '## Steps (machine-readable)' json block in $doc" >&2
30
+ return 1
31
+ fi
32
+ if ! jq -e '
33
+ type == "array"
34
+ and length > 0
35
+ and all(.[];
36
+ (.id | type == "string" and length > 0) and
37
+ (.title | type == "string" and length > 0) and
38
+ (.check | type == "string" and length > 0))
39
+ ' <<< "$block" >/dev/null 2>&1; then
40
+ echo "jira plan: steps block in $doc is not a non-empty array of {id,title,check} strings" >&2
41
+ return 1
42
+ fi
43
+ jq -c 'map(.activity = (.activity // null))' <<< "$block"
44
+ }
45
+
46
+ # jira_plan_doc_field DOC FIELD
47
+ # Print the trimmed value of an inline-bold `**<FIELD>:**` from a packed doc
48
+ # header line, or empty string when absent. Never errors — a missing field is a
49
+ # valid signal, not a failure. Same extraction as toolu's pl_doc_field.
50
+ jira_plan_doc_field() {
51
+ local doc="$1" field="$2"
52
+ [ -n "$doc" ] && [ -f "$doc" ] || return 0
53
+ [ -n "$field" ] || return 0
54
+ grep -m1 -F "**${field}:**" "$doc" 2>/dev/null \
55
+ | sed -E "s/.*\\*\\*${field}:\\*\\*[[:space:]]*//; s/[[:space:]]+\\*\\*[^*]+:\\*\\*.*$//; s/^[[:space:]]+//; s/[[:space:]]+$//"
56
+ }
57
+
58
+ # jira_plan_issue_key DOC
59
+ # Print the issue key declared by the doc's `**Issue:** <KEY>` header. A key must
60
+ # look like a Jira key (LETTERS-DIGITS); anything else is rejected so a malformed
61
+ # header can never steer the ledger filename.
62
+ jira_plan_issue_key() {
63
+ local doc="$1" key
64
+ key=$(jira_plan_doc_field "$doc" "Issue")
65
+ if [[ ! "$key" =~ ^[A-Za-z][A-Za-z0-9_]*-[0-9]+$ ]]; then
66
+ echo "jira plan: doc is missing a valid '**Issue:** <KEY>' header: $doc" >&2
67
+ return 1
68
+ fi
69
+ printf '%s\n' "$key"
70
+ }
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # Check runner for the `plan` family: probe Jira once, then execute each step's
3
+ # `check` and record the verdict in the ledger. No `set` (sourced library).
4
+ # Source via: . "${BASH_SOURCE[0]%/*}/plan-run.sh"
5
+ # Requires plan-parse.sh + plan-store.sh to be sourced first.
6
+ #
7
+ # A step is green iff its check exits 0. Because every check is a live Jira REST
8
+ # assertion, green means "Jira agrees", not "the agent says so".
9
+ #
10
+ # Infra failure is NOT a Jira disagreement. A missing credential, an unreachable
11
+ # host, or a broken jq pipe would each exit non-zero and land every step `red` --
12
+ # flipping dashboard cards to Blocked for a reason that has nothing to do with
13
+ # the ticket. So we probe once with `user whoami` and, on failure, abort before
14
+ # writing anything: statuses are left exactly as they were.
15
+
16
+ # jira_plan_cli
17
+ # Resolve the jira CLI a check invokes as "$JIRA". JIRA_CLI overrides it, which
18
+ # is how the bats sandbox points checks at its stubbed copy instead of the host's.
19
+ jira_plan_cli() {
20
+ if [ -n "${JIRA_CLI:-}" ]; then
21
+ printf '%s\n' "$JIRA_CLI"
22
+ elif [ -n "${TOOLU_CONFIG_DIR:-}" ]; then
23
+ printf '%s/jira/jira.sh\n' "$TOOLU_CONFIG_DIR"
24
+ elif [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || {
25
+ [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ];
26
+ }; then
27
+ printf '%s/jira/jira.sh\n' "${CODEX_HOME:-$HOME/.codex}"
28
+ else
29
+ printf '%s/jira/jira.sh\n' "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
30
+ fi
31
+ }
32
+
33
+ # jira_plan_probe CLI
34
+ # Confirm Jira is reachable and the credentials work. Zero on success.
35
+ jira_plan_probe() {
36
+ "$1" user whoami >/dev/null 2>&1
37
+ }
38
+
39
+ # jira_plan_exec_check CLI CHECK ROOT
40
+ # Run CHECK with $JIRA bound and cwd at ROOT. Prints combined output; returns the
41
+ # check's exit code. Never aborts the caller under `set -e`.
42
+ jira_plan_exec_check() {
43
+ local cli="$1" check="$2" root="$3" out code
44
+ if out=$(cd "$root" && JIRA="$cli" bash -c "$check" 2>&1); then code=0; else code=$?; fi
45
+ printf '%s' "$out"
46
+ return "$code"
47
+ }
48
+
49
+ # jira_plan_run KEY DOC [--step ID] [--activity LABEL]
50
+ # Merge the doc's steps over the persisted ledger, probe Jira, then run either
51
+ # one step (--step) or all of them, writing the ledger after each transition so
52
+ # the dashboard sees `running` while a check is in flight.
53
+ # Returns non-zero if any executed step ended red.
54
+ jira_plan_run() {
55
+ local key="$1" doc="$2"; shift 2
56
+ local only="" activity="" cli root state steps prev ledger targets id check out code failed=0
57
+
58
+ while [ $# -gt 0 ]; do
59
+ case "$1" in
60
+ --step) only="${2:-}"; shift 2 ;;
61
+ --activity) activity="${2:-}"; shift 2 ;;
62
+ *) echo "jira plan run: unknown option '$1'" >&2; return 1 ;;
63
+ esac
64
+ done
65
+
66
+ steps=$(jira_plan_parse_steps "$doc") || return 1
67
+ root=$(jira_plan_repo_root)
68
+ state=$(jira_plan_ledger_path "$key") || return 1
69
+ prev=$(jira_plan_read "$state" 2>/dev/null) || prev="null"
70
+ ledger=$(jira_plan_build "$key" "$doc" "$steps" "$prev") || return 1
71
+
72
+ if [ -n "$only" ] && ! jq -e --arg id "$only" 'any(.steps[]; .id == $id)' <<< "$ledger" >/dev/null; then
73
+ echo "jira plan run: no step '$only' in $doc" >&2
74
+ return 1
75
+ fi
76
+
77
+ cli=$(jira_plan_cli)
78
+ if [ ! -x "$cli" ] && [ ! -f "$cli" ]; then
79
+ echo "jira plan run: jira CLI not found at $cli (set JIRA_CLI)" >&2
80
+ return 1
81
+ fi
82
+ # Probe BEFORE the first write: an unreachable Jira must not manufacture reds.
83
+ if ! jira_plan_probe "$cli"; then
84
+ echo "jira plan run: cannot reach Jira (user whoami failed) — no step statuses were written" >&2
85
+ return 1
86
+ fi
87
+
88
+ if [ -n "$only" ]; then targets="$only"; else targets=$(jq -r '.steps[].id' <<< "$ledger"); fi
89
+
90
+ while IFS= read -r id; do
91
+ [ -n "$id" ] || continue
92
+ check=$(jq -r --arg id "$id" '.steps[] | select(.id==$id) | .check' <<< "$ledger")
93
+
94
+ # First write: running + started_at (+ activity), so the dashboard shows it in flight.
95
+ ledger=$(jira_plan_set_step "$ledger" "$id" "$(jq -n --arg now "$(jira_plan_now)" --arg a "$activity" \
96
+ '{status:"running", started_at:$now, exit_code:null, evidence_tail:null}
97
+ + (if $a == "" then {} else {activity:$a} end)')") || return 1
98
+ jira_plan_write "$state" "$ledger" || return 1
99
+
100
+ if out=$(jira_plan_exec_check "$cli" "$check" "$root"); then code=0; else code=$?; fi
101
+
102
+ # Second write: the verdict.
103
+ ledger=$(jira_plan_set_step "$ledger" "$id" "$(jq -n \
104
+ --arg st "$([ "$code" -eq 0 ] && echo green || echo red)" \
105
+ --argjson ec "$code" \
106
+ --arg now "$(jira_plan_now)" \
107
+ --argjson ev "$(jira_plan_evidence "$out")" \
108
+ '{status:$st, exit_code:$ec, last_run:$now, evidence_tail:$ev, started_at:null}')") || return 1
109
+ jira_plan_write "$state" "$ledger" || return 1
110
+
111
+ if [ "$code" -eq 0 ]; then echo "green $id"; else echo "red $id (exit $code)"; failed=1; fi
112
+ done <<< "$targets"
113
+
114
+ return "$failed"
115
+ }
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env bash
2
+ # Ledger store for the `plan` family: resolve the ledger path, merge authored
3
+ # steps over persisted status, recompute the summary, and write atomically.
4
+ # Pure functions, jq-only, no command execution and no `set` (sourced library).
5
+ # Source via: . "${BASH_SOURCE[0]%/*}/plan-store.sh"
6
+ #
7
+ # Emits schema version 1, the same document plugins/toolu/dashboard/state.ts
8
+ # reads. Two fields are deliberate and load-bearing:
9
+ # base_branch: "" -> dashboard currentDiffSha() returns null, so no Jira step
10
+ # is ever marked `stale` when unrelated code commits move
11
+ # the repo diff (state.ts: `if (!base) return null`).
12
+ # diff_sha: null -> a Jira step's freshness is not a function of the code diff.
13
+ # The file is named jira-<KEY>.json, never <branch-slug>.json, so toolu's push
14
+ # gate (which stats only "$ledger_dir/<branch-slug>.json") can never block a push
15
+ # on Jira state.
16
+
17
+ # jira_plan_repo_root
18
+ # Print the repo root, or the cwd when not inside a git work tree.
19
+ jira_plan_repo_root() {
20
+ git rev-parse --show-toplevel 2>/dev/null || pwd
21
+ }
22
+
23
+ # This skill is self-contained and cannot source the toolu plugin's host
24
+ # adapter. Mirror only its host-native project directory decision here.
25
+ jira_plan_project_dirname() {
26
+ if [ -n "${TOOLU_PROJECT_CONFIG_DIRNAME:-}" ]; then
27
+ printf '%s\n' "$TOOLU_PROJECT_CONFIG_DIRNAME"
28
+ elif [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || { [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ]; }; then
29
+ printf '.codex\n'
30
+ else
31
+ printf '.claude\n'
32
+ fi
33
+ }
34
+
35
+ # jira_plan_require_key KEY
36
+ # Accept only a well-formed Jira key (LETTERS-DIGITS). Both path builders below
37
+ # interpolate the key into a filename, so anything else -- a slash, a `..`, an
38
+ # absolute path -- must be rejected here rather than escaping the ledger dir.
39
+ jira_plan_require_key() {
40
+ local key="${1:-}"
41
+ if [[ ! "$key" =~ ^[A-Za-z][A-Za-z0-9_]*-[0-9]+$ ]]; then
42
+ echo "jira plan: not a valid issue key: '${key}'" >&2
43
+ return 1
44
+ fi
45
+ }
46
+
47
+ # jira_plan_ledger_path KEY
48
+ # Print <repo_root>/<host-dir>/tmp/plan-ledger/jira-<KEY>.json.
49
+ jira_plan_ledger_path() {
50
+ local key="$1" project_dir
51
+ jira_plan_require_key "$key" || return 1
52
+ project_dir=$(jira_plan_project_dirname)
53
+ printf '%s\n' "$(jira_plan_repo_root)/${project_dir}/tmp/plan-ledger/jira-${key}.json"
54
+ }
55
+
56
+ # jira_plan_doc_path KEY
57
+ # Print <repo_root>/<host-dir>/tmp/jira/plans/<KEY>.md.
58
+ jira_plan_doc_path() {
59
+ local key="$1" project_dir
60
+ jira_plan_require_key "$key" || return 1
61
+ project_dir=$(jira_plan_project_dirname)
62
+ printf '%s\n' "$(jira_plan_repo_root)/${project_dir}/tmp/jira/plans/${key}.md"
63
+ }
64
+
65
+ # jira_plan_now — ISO-8601 UTC, second precision.
66
+ jira_plan_now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
67
+
68
+ # jira_plan_evidence COMBINED_OUTPUT
69
+ # JSON-encode the last 10 lines of a check's combined stdout+stderr, capped to
70
+ # ~2000 bytes. Prints a quoted JSON string. Mirrors toolu's pl_evidence.
71
+ jira_plan_evidence() {
72
+ printf '%s' "$1" | tail -n 10 | head -c 2000 | jq -Rs .
73
+ }
74
+
75
+ # jira_plan_read STATE_FILE
76
+ # Print the ledger json. Absent, empty, or unparseable -> non-zero, no output.
77
+ jira_plan_read() {
78
+ local f="$1" content
79
+ [ -s "$f" ] || return 1
80
+ content=$(cat "$f" 2>/dev/null) || return 1
81
+ jq -e . <<< "$content" >/dev/null 2>&1 || return 1
82
+ printf '%s\n' "$content"
83
+ }
84
+
85
+ # jira_plan_write STATE_FILE JSON
86
+ # Validate, then write atomically (temp + mv), creating the parent dir.
87
+ jira_plan_write() {
88
+ local f="$1" json="$2" dir tmp
89
+ if ! jq -e . <<< "$json" >/dev/null 2>&1; then
90
+ echo "jira plan: refusing to write invalid ledger json to $f" >&2
91
+ return 1
92
+ fi
93
+ dir=$(dirname "$f")
94
+ mkdir -p "$dir" || { echo "jira plan: cannot create ledger dir: $dir" >&2; return 1; }
95
+ tmp="$f.tmp.$$"
96
+ if ! jq . <<< "$json" > "$tmp" 2>/dev/null; then
97
+ rm -f "$tmp"; echo "jira plan: failed to stage ledger to $tmp" >&2; return 1
98
+ fi
99
+ if ! mv "$tmp" "$f"; then
100
+ rm -f "$tmp"; echo "jira plan: atomic mv failed for $f" >&2; return 1
101
+ fi
102
+ }
103
+
104
+ # jira_plan_recompute LEDGER_JSON
105
+ # Recompute summary{} and next from steps[]. `stale` is always 0 and
106
+ # `fresh_green` equals `green`: Jira freshness does not depend on the code diff.
107
+ # `next` is the first step that is not green. Note the dashboard derives its
108
+ # status dot and progress from steps[] (aggregate.ts countSteps), not from this
109
+ # summary — summary must simply agree with steps[].
110
+ jira_plan_recompute() {
111
+ jq '
112
+ (.steps | length) as $total
113
+ | (.steps | map(select(.status=="green")) | length) as $green
114
+ | (.steps | map(select(.status=="red")) | length) as $red
115
+ | (.steps | map(select(.status=="pending"))| length) as $pending
116
+ | (.steps | map(select(.status=="running"))| length) as $running
117
+ | .summary = { total: $total, green: $green, red: $red, pending: $pending,
118
+ running: $running, stale: 0, fresh_green: $green, retried: 0 }
119
+ | .next = ((.steps | map(select(.status != "green")) | first | .id) // null)
120
+ '
121
+ }
122
+
123
+ # jira_plan_build KEY PLAN_DOC STEPS_JSON [PREV_LEDGER_JSON]
124
+ # Build the full ledger document. Authored fields (title/check/activity) come
125
+ # from the doc; execution state (status/started_at/exit_code/last_run/
126
+ # evidence_tail) is carried over from PREV by step id, defaulting to pending.
127
+ # A step dropped from the doc disappears; a new one arrives pending.
128
+ jira_plan_build() {
129
+ local key="$1" doc="$2" steps="$3" prev="${4:-null}" now
130
+ now=$(jira_plan_now)
131
+ jq -n \
132
+ --argjson steps "$steps" \
133
+ --argjson prev "$prev" \
134
+ --arg branch "jira-${key}" \
135
+ --arg doc "$doc" \
136
+ --arg now "$now" '
137
+ ((($prev.steps // []) | map({ (.id): . }) | add) // {}) as $old
138
+ | {
139
+ version: 1,
140
+ branch: $branch,
141
+ base_branch: "",
142
+ plan_doc: $doc,
143
+ updated_at: $now,
144
+ summary: {},
145
+ next: null,
146
+ steps: ($steps | map(
147
+ ($old[.id] // {}) as $p | {
148
+ id, title, check,
149
+ status: ($p.status // "pending"),
150
+ started_at: ($p.started_at // null),
151
+ activity: (.activity // $p.activity // null),
152
+ exit_code: ($p.exit_code // null),
153
+ diff_sha: null,
154
+ last_run: ($p.last_run // null),
155
+ evidence_tail: ($p.evidence_tail // null)
156
+ }))
157
+ }' | jira_plan_recompute
158
+ }
159
+
160
+ # jira_plan_set_step LEDGER_JSON ID FIELDS_JSON
161
+ # Merge FIELDS_JSON into the step with the given id, then recompute + restamp.
162
+ jira_plan_set_step() {
163
+ local ledger="$1" id="$2" fields="$3"
164
+ jq --arg id "$id" --argjson f "$fields" --arg now "$(jira_plan_now)" '
165
+ .steps = (.steps | map(if .id == $id then . + $f else . end))
166
+ | .updated_at = $now
167
+ ' <<< "$ledger" | jira_plan_recompute
168
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bash
2
+ # `plan` family: scaffold a plan doc for a ticket, run its checks, and keep a
3
+ # dashboard-readable ledger at <repo>/.claude/tmp/plan-ledger/jira-<KEY>.json.
4
+ #
5
+ # jira.sh sources only lib/<family>.sh, so this file pulls in its own deps —
6
+ # including issue.sh, because `plan init` titles the doc from a live `issue get`.
7
+ # Sourcing is relative to THIS file, not $LIB: $LIB is set by jira.sh alone, so a
8
+ # test that sources plan.sh directly would otherwise expand to "/plan-parse.sh".
9
+ _jira_plan_lib="${BASH_SOURCE[0]%/*}"
10
+ # shellcheck source=/dev/null
11
+ . "$_jira_plan_lib/plan-parse.sh"
12
+ # shellcheck source=/dev/null
13
+ . "$_jira_plan_lib/plan-store.sh"
14
+ # shellcheck source=/dev/null
15
+ . "$_jira_plan_lib/plan-run.sh"
16
+ # shellcheck source=/dev/null
17
+ . "$_jira_plan_lib/issue.sh"
18
+
19
+ _jira_plan_usage() {
20
+ cat >&2 <<'EOF'
21
+ jira plan — decompose ticket work into verifiable steps the dashboard renders
22
+
23
+ plan init <KEY> scaffold the plan doc for a ticket
24
+ plan run <DOC> [--step <id>] [--activity <s>] run checks, write the ledger
25
+ plan status <KEY> print the ledger summary
26
+ plan path <KEY> print the ledger path
27
+
28
+ Every step's `check` must be a live Jira assertion that exits 0 only when Jira
29
+ itself reflects the change.
30
+ EOF
31
+ return 1
32
+ }
33
+
34
+ # jira_plan_init KEY
35
+ # Write <repo>/.claude/tmp/jira/plans/<KEY>.md, titled from a live `issue get`.
36
+ # Refuses to clobber an existing doc.
37
+ jira_plan_init() {
38
+ local key="${1:-}" doc summary
39
+ [ -n "$key" ] || { echo "jira plan init: needs an issue key" >&2; return 1; }
40
+ doc=$(jira_plan_doc_path "$key") || return 1
41
+ if [ -e "$doc" ]; then
42
+ echo "jira plan init: $doc already exists" >&2
43
+ return 1
44
+ fi
45
+ summary=$(jira_issue get "$key" | jq -r '.fields.summary // empty') || return 1
46
+ [ -n "$summary" ] || summary="$key"
47
+ mkdir -p "$(dirname "$doc")" || return 1
48
+ cat > "$doc" <<EOF
49
+ # $key — $summary
50
+
51
+ **Date:** $(date -u +%Y-%m-%d) **Issue:** $key **Topic:** $summary
52
+
53
+ ## Steps (machine-readable)
54
+
55
+ \`\`\`json
56
+ []
57
+ \`\`\`
58
+
59
+ Fill the array with {"id","title","check"} objects. A \`check\` is a shell command
60
+ that exits 0 **only when Jira itself reflects the change** — assert against a live
61
+ read, e.g.
62
+
63
+ "\$JIRA" issue get $key --lean | jq -e '.status=="Done"' >/dev/null
64
+
65
+ \`\$JIRA\` is bound to the jira CLI when the check runs. Then: jira.sh plan run $doc
66
+ EOF
67
+ printf '%s\n' "$doc"
68
+ }
69
+
70
+ # jira_plan_status KEY — print the ledger summary and each step's status.
71
+ jira_plan_status() {
72
+ local key="${1:-}" state ledger
73
+ [ -n "$key" ] || { echo "jira plan status: needs an issue key" >&2; return 1; }
74
+ state=$(jira_plan_ledger_path "$key") || return 1
75
+ if ! ledger=$(jira_plan_read "$state"); then
76
+ echo "jira plan status: no ledger for $key (run: jira.sh plan run <doc>)" >&2
77
+ return 1
78
+ fi
79
+ jq -r '
80
+ "\(.branch) \(.summary.green)/\(.summary.total) green next: \(.next // "-")",
81
+ (.steps[] | " \(.status)\t\(.id)\t\(.title)")
82
+ ' <<< "$ledger"
83
+ }
84
+
85
+ # jira_plan_path KEY — print the ledger path. Needs no credentials.
86
+ jira_plan_path() {
87
+ local key="${1:-}"
88
+ [ -n "$key" ] || { echo "jira plan path: needs an issue key" >&2; return 1; }
89
+ jira_plan_ledger_path "$key"
90
+ }
91
+
92
+ jira_plan() {
93
+ local action="${1:-}" doc key
94
+ [ $# -gt 0 ] && shift
95
+ case "$action" in
96
+ init) jira_plan_init "$@" ;;
97
+ status) jira_plan_status "$@" ;;
98
+ path) jira_plan_path "$@" ;;
99
+ run)
100
+ doc="${1:-}"
101
+ [ -n "$doc" ] || { echo "jira plan run: needs a plan doc path" >&2; return 1; }
102
+ shift
103
+ key=$(jira_plan_issue_key "$doc") || return 1
104
+ jira_plan_run "$key" "$doc" "$@"
105
+ ;;
106
+ *) _jira_plan_usage ;;
107
+ esac
108
+ }