@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.
- package/generated/GENERATED-NOTES.md +16 -0
- package/generated/agents/toolu--architect.md +52 -0
- package/generated/agents/toolu--deep-explore.md +65 -0
- package/generated/agents/toolu--implementer.md +50 -0
- package/generated/agents/toolu--quick-task.md +54 -0
- package/generated/agents/toolu--research-agent.md +101 -0
- package/generated/commands/toolu--commit.md +8 -0
- package/generated/commands/toolu--review-and-commit.md +8 -0
- package/generated/opencode.toolu.json +116 -0
- package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
- package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
- package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
- package/generated/skills/toolu--debug/SKILL.md +54 -0
- package/generated/skills/toolu--deep-research/SKILL.md +51 -0
- package/generated/skills/toolu--execution/SKILL.md +77 -0
- package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
- package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
- package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
- package/generated/skills/toolu--plan/SKILL.md +56 -0
- package/generated/skills/toolu--plan/references/ledger.md +18 -0
- package/generated/skills/toolu--plan-review/SKILL.md +48 -0
- package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
- package/generated/skills/toolu--setup/SKILL.md +24 -0
- package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
- package/generated/skills/toolu--spec/SKILL.md +67 -0
- package/generated/skills/toolu--spec-review/SKILL.md +50 -0
- package/generated/skills/toolu--test/SKILL.md +49 -0
- package/package.json +51 -0
- package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
- package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
- package/plugins/agent-browser/README.md +18 -0
- package/plugins/agent-browser/hooks/hooks.json +15 -0
- package/plugins/agent-browser/hooks/session-start.sh +42 -0
- package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
- package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
- package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
- package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
- package/plugins/ast-grep/README.md +21 -0
- package/plugins/ast-grep/hooks/hooks.json +15 -0
- package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
- package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
- package/plugins/ast-grep/hooks/register.sh +68 -0
- package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
- package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
- package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
- package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
- package/plugins/context7/.claude-plugin/plugin.json +18 -0
- package/plugins/context7/.codex-plugin/plugin.json +7 -0
- package/plugins/context7/README.md +19 -0
- package/plugins/context7/hooks/hooks.json +15 -0
- package/plugins/context7/hooks/session-start.sh +42 -0
- package/plugins/context7/skills/context7/SKILL.md +64 -0
- package/plugins/context7/skills/context7/scripts/search.sh +151 -0
- package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
- package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
- package/plugins/exa-search/README.md +19 -0
- package/plugins/exa-search/hooks/hooks.json +15 -0
- package/plugins/exa-search/hooks/session-start.sh +34 -0
- package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
- package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
- package/plugins/jev/.claude-plugin/plugin.json +19 -0
- package/plugins/jev/.codex-plugin/plugin.json +7 -0
- package/plugins/jev/README.md +61 -0
- package/plugins/jev/hooks/hooks.json +25 -0
- package/plugins/jev/hooks/lib/common.sh +49 -0
- package/plugins/jev/hooks/session-start.sh +45 -0
- package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
- package/plugins/jev/skills/jev/SKILL.md +81 -0
- package/plugins/jev/skills/jev/evals/README.md +98 -0
- package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
- package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
- package/plugins/jira/.claude-plugin/plugin.json +18 -0
- package/plugins/jira/.codex-plugin/plugin.json +7 -0
- package/plugins/jira/README.md +42 -0
- package/plugins/jira/hooks/hooks.json +15 -0
- package/plugins/jira/hooks/session-start.sh +34 -0
- package/plugins/jira/skills/jira/SKILL.md +123 -0
- package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
- package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
- package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
- package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
- package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
- package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
- package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
- package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
- package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
- package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
- package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
- package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
- package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
- package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
- package/plugins/pr-babysit/README.md +38 -0
- package/plugins/pr-babysit/commands/babysit.md +13 -0
- package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
- package/plugins/pr-babysit/hooks/hooks.json +15 -0
- package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
- package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
- package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
- package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
- package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
- package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
- package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
- package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
- package/plugins/pr-babysit/scripts/record.sh +69 -0
- package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
- package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
- package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
- package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
- package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
- package/plugins/pr-babysit/workflows/babysit.md +722 -0
- package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/python-quality/README.md +23 -0
- package/plugins/python-quality/hooks/check-toolu.sh +20 -0
- package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
- package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
- package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
- package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
- package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
- package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
- package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
- package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/python-quality/hooks/hooks.json +19 -0
- package/plugins/python-quality/hooks/register.sh +65 -0
- package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/rust-quality/README.md +24 -0
- package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
- package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
- package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
- package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
- package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
- package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
- package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
- package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
- package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
- package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
- package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
- package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/rust-quality/hooks/hooks.json +19 -0
- package/plugins/rust-quality/hooks/register.sh +65 -0
- package/plugins/statusline/.claude-plugin/plugin.json +19 -0
- package/plugins/statusline/.codex-plugin/plugin.json +7 -0
- package/plugins/statusline/README.md +108 -0
- package/plugins/statusline/commands/setup.md +30 -0
- package/plugins/statusline/hooks/hooks.json +15 -0
- package/plugins/statusline/hooks/session-start.sh +45 -0
- package/plugins/statusline/scripts/collect-status.sh +147 -0
- package/plugins/statusline/scripts/setup.sh +93 -0
- package/plugins/statusline/scripts/status.sh +55 -0
- package/plugins/statusline/skills/status/SKILL.md +13 -0
- package/plugins/statusline/statusline.sh +177 -0
- package/plugins/toolu/.claude-plugin/plugin.json +19 -0
- package/plugins/toolu/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu/README.md +35 -0
- package/plugins/toolu/agents/.gitkeep +0 -0
- package/plugins/toolu/agents/architect.md +52 -0
- package/plugins/toolu/agents/deep-explore.md +65 -0
- package/plugins/toolu/agents/implementer.md +50 -0
- package/plugins/toolu/agents/quick-task.md +54 -0
- package/plugins/toolu/agents/research-agent.md +107 -0
- package/plugins/toolu/assets/agents/architect.toml +9 -0
- package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
- package/plugins/toolu/assets/agents/implementer.toml +9 -0
- package/plugins/toolu/assets/agents/quick-task.toml +9 -0
- package/plugins/toolu/assets/agents/research-agent.toml +9 -0
- package/plugins/toolu/commands/.gitkeep +0 -0
- package/plugins/toolu/commands/commit.md +5 -0
- package/plugins/toolu/commands/review-and-commit.md +5 -0
- package/plugins/toolu/hooks/docs/gates.md +157 -0
- package/plugins/toolu/hooks/docs/model-routing.md +14 -0
- package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
- package/plugins/toolu/hooks/docs/push-review.md +123 -0
- package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
- package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
- package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
- package/plugins/toolu/hooks/docs/session-start.md +16 -0
- package/plugins/toolu/hooks/hooks.json +76 -0
- package/plugins/toolu/hooks/lib/config.sh +355 -0
- package/plugins/toolu/hooks/lib/detect.sh +929 -0
- package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
- package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
- package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
- package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
- package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
- package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
- package/plugins/toolu/hooks/lib/host.sh +205 -0
- package/plugins/toolu/hooks/lib/permissions.sh +125 -0
- package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
- package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
- package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
- package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
- package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
- package/plugins/toolu/hooks/lib/registry.sh +72 -0
- package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
- package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
- package/plugins/toolu/hooks/lib/verdict.sh +477 -0
- package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
- package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
- package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
- package/plugins/toolu/hooks/pre-compact.sh +18 -0
- package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
- package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
- package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
- package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
- package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
- package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
- package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
- package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
- package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
- package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
- package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
- package/plugins/toolu/hooks/session-start.sh +379 -0
- package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
- package/plugins/toolu/scripts/context-budget.sh +111 -0
- package/plugins/toolu/scripts/debug-log.sh +98 -0
- package/plugins/toolu/scripts/debug-stack.sh +108 -0
- package/plugins/toolu/scripts/debug-testfail.sh +97 -0
- package/plugins/toolu/settings/.gitkeep +0 -0
- package/plugins/toolu/settings/README.md +127 -0
- package/plugins/toolu/settings/bash-allowlist.txt +12 -0
- package/plugins/toolu/settings/bash-denylist.txt +25 -0
- package/plugins/toolu/settings/code-edit-rules.json +67 -0
- package/plugins/toolu/settings/commit-prefixes.txt +12 -0
- package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
- package/plugins/toolu/settings/permissions.fragment.json +36 -0
- package/plugins/toolu/settings/protected-files.txt +30 -0
- package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
- package/plugins/toolu/settings/toolu.config.example.json +130 -0
- package/plugins/toolu/skills/.gitkeep +0 -0
- package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
- package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
- package/plugins/toolu/skills/commit/SKILL.md +11 -0
- package/plugins/toolu/skills/debug/SKILL.md +54 -0
- package/plugins/toolu/skills/debug/evals/README.md +17 -0
- package/plugins/toolu/skills/debug/evals/cases.json +24 -0
- package/plugins/toolu/skills/debug/evals/results.json +34 -0
- package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
- package/plugins/toolu/skills/execution/SKILL.md +77 -0
- package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
- package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
- package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
- package/plugins/toolu/skills/plan/SKILL.md +56 -0
- package/plugins/toolu/skills/plan/references/ledger.md +18 -0
- package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
- package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
- package/plugins/toolu/skills/setup/SKILL.md +24 -0
- package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
- package/plugins/toolu/skills/spec/SKILL.md +67 -0
- package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
- package/plugins/toolu/skills/test/SKILL.md +49 -0
- package/plugins/toolu/workflows/commit.md +21 -0
- package/plugins/toolu/workflows/host-mapping.md +22 -0
- package/plugins/toolu/workflows/review-and-commit.md +54 -0
- package/plugins/toolu/workflows/semantic-judgments.md +35 -0
- package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
- package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu-review/README.md +17 -0
- package/plugins/toolu-review/hooks/hooks.json +15 -0
- package/plugins/toolu-review/hooks/session-start.sh +34 -0
- package/plugins/toolu-review/skills/review/SKILL.md +90 -0
- package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
- package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/ts-quality/README.md +24 -0
- package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
- package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
- package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
- package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
- package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
- package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
- package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
- package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
- package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
- package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
- package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
- package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
- package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
- package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
- package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
- package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
- package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
- package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
- package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
- package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
- package/plugins/ts-quality/hooks/hooks.json +19 -0
- package/plugins/ts-quality/hooks/register.sh +65 -0
- package/src/adapter/__tests__/evaluate.test.ts +82 -0
- package/src/adapter/__tests__/permission-map.test.ts +91 -0
- package/src/adapter/evaluate.ts +54 -0
- package/src/adapter/permission-map.ts +177 -0
- package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
- package/src/bootstrap/entrypoint.ts +18 -0
- package/src/bootstrap/index.ts +5 -0
- package/src/bootstrap/readiness.ts +59 -0
- package/src/bootstrap/result.ts +21 -0
- package/src/bootstrap/runtime.ts +99 -0
- package/src/bootstrap/test-helpers.ts +25 -0
- package/src/host/__tests__/host.test.ts +37 -0
- package/src/host/detect.ts +46 -0
- package/src/host/index.ts +10 -0
- package/src/host/roots.ts +59 -0
- package/src/host/types.ts +14 -0
- package/src/inventory/__tests__/inventory.test.ts +69 -0
- package/src/inventory/index.ts +9 -0
- package/src/inventory/manifest.ts +46 -0
- package/src/inventory/scan.ts +66 -0
- package/src/inventory/selection.ts +92 -0
- package/src/inventory/types.ts +22 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
- package/src/lifecycle/index.ts +2 -0
- package/src/lifecycle/table.ts +42 -0
- package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
- package/src/plugin/plugin-stub.ts +7 -0
- package/src/plugin/toolu.ts +142 -0
- package/src/preflight/__tests__/preflight.test.ts +20 -0
- package/src/preflight/check.ts +75 -0
- package/src/preflight/index.ts +2 -0
- package/src/select/__tests__/select.test.ts +52 -0
- package/src/select/index.ts +2 -0
- 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
|
+
}
|