Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Bug report
description: A command behaves differently from how it is documented or intended.
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Every issue carries three things: a **reason** it matters, a **scope**
it is bounded to, and a **plan** for what happens next. All three are
required. The plan is your proposal - triage may replace it, and will
say so in a comment before accepting.

Wait for the `accepted` label before writing a fix. See
[ISSUES.md](https://github.com/KeeperHub/cli/blob/main/ISSUES.md).

- type: checkboxes
id: preflight
attributes:
label: Before filing
options:
- label: I searched open and closed issues for this behaviour.
required: true
- label: I am on the latest release, or I checked that `main` still has it.
required: true
- label: This is one problem, not several. (Several means several issues.)
required: true
- label: This is not a security vulnerability (those go through private reporting).
required: true

- type: textarea
id: reproduction
attributes:
label: "Reason: command and output"
description: >-
The exact command and its full output. A pasted terminal session settles
a report faster than any description of one. Redact keys and addresses
you do not want public, but keep the shape of what was redacted.
render: shell
placeholder: |
$ kh execute status exec_123 --watch --timeout 5s
...
validations:
required: true

- type: textarea
id: expected
attributes:
label: "Reason: what you expected, and what told you to expect it"
description: >-
Name the source - `--help` output, a docs page, a flag name, an exit
code convention. If the source says something different from the code,
that immediately tells us which of the two is wrong.
validations:
required: true

- type: textarea
id: cost
attributes:
label: "Reason: what it costs"
description: >-
What goes wrong for someone who hits this. A wrong exit code that breaks
a CI gate, a command that hangs forever, output a script cannot parse.
This is what orders the queue.
validations:
required: true

- type: input
id: version
attributes:
label: kh version
description: Output of `kh version`.
validations:
required: true

- type: input
id: platform
attributes:
label: OS and architecture
placeholder: macOS 15 arm64 / Ubuntu 24.04 amd64 / Windows 11 amd64
validations:
required: true

- type: textarea
id: scope
attributes:
label: "Scope: what this covers, and what it does not"
description: >-
The commands affected, and the ones you checked and found fine. If a
sibling command plausibly shares the fault, name it - a fix applied to
one command and not its siblings is a recurring failure. Then confirm
this is one problem.
placeholder: >-
Affects `kh execute status --watch`. Checked `kh execute transfer --wait`
- same shape, likely the same fault. `kh workflow list` unaffected.
validations:
required: true

- type: textarea
id: plan
attributes:
label: "Plan: what should happen next"
description: >-
Your proposal, which triage may replace. Say whether it changes anything
a caller depends on - output format, an exit code, a flag's meaning. If
you do not know the fix, say what you would need to determine to choose
one. Blank is not a valid answer.
validations:
required: true
96 changes: 96 additions & 0 deletions .github/ISSUE_TEMPLATE/change_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Behaviour change or new command
description: Propose something kh should do that it does not do today.
labels: ["enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Every issue carries three things: a **reason** it matters, a **scope**
it is bounded to, and a **plan** for what happens next. All three are
required. The plan is your proposal - triage may replace it, and will
say so in a comment before accepting.

Lead with what you are trying to do, not with what to build.

Wait for the `accepted` label before writing code. See
[ISSUES.md](https://github.com/KeeperHub/cli/blob/main/ISSUES.md).

- type: checkboxes
id: preflight
attributes:
label: Before filing
options:
- label: I searched open and closed issues for this proposal.
required: true
- label: I checked `kh <command> --help` and the command reference.
required: true
- label: This is one change, not several. (Several means several issues.)
required: true

- type: textarea
id: problem
attributes:
label: "Reason: what you cannot do today"
description: >-
The concrete task that is blocked or awkward, and what you do instead.
If a flag nearly does it, say what it falls short on.
validations:
required: true

- type: textarea
id: cost
attributes:
label: "Reason: what the workaround costs"
description: >-
Time, reliability, an invariant you cannot hold, a script you cannot
write. That cost is the size of the problem. If there is no workaround,
say so.
validations:
required: true

- type: textarea
id: scope
attributes:
label: "Scope: what this touches, and what it does not"
description: >-
The commands this changes and the ones it deliberately leaves alone.
Then confirm it is one change: if any part could ship and be correct with
another reverted, those are separate issues.
validations:
required: true

- type: textarea
id: plan
attributes:
label: "Plan: what you propose"
description: >-
The command or flag as you would build it, including output shape and
exit codes, and what happens to callers of the current behaviour. Triage
may replace this and will say so before accepting.
validations:
required: true

- type: textarea
id: alternatives
attributes:
label: "Plan: alternatives you considered"
description: >-
Including doing nothing, and approaches you rejected with the reason. A
rejected alternative with a reason stops triage re-deriving it.
validations:
required: false

- type: checkboxes
id: breaking
attributes:
label: "Scope: compatibility"
description: Tick anything the plan would touch. Each one turns this into a decision rather than a fix.
options:
- label: Changes existing output, an exit code, or a flag's meaning.
required: false
- label: Changes a default for callers who pass no flags.
required: false
- label: Adds or upgrades a Go module dependency.
required: false
- label: Touches authentication or credential storage.
required: false
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/KeeperHub/cli/security
about: Report privately. Never in a public issue or pull request.
- name: Command reference
url: https://docs.keeperhub.com/cli
about: Check the docs before filing - the behaviour may be intentional and described.
- name: Contribution policy
url: https://github.com/KeeperHub/cli/blob/main/ISSUES.md
about: What needs an issue, what does not, and what happens after you file one.
44 changes: 44 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
Title format: <type>: #<issue> <description>
feat: #97 add --require-verified to execute status
fix(execute): #98 bound a hung request under --watch --timeout

The issue must carry the `accepted` label before you open this. See ISSUES.md.
Exempt: docs / chore / style changes from the "no issue required" list.
The type prefix drives release-please, so keep it accurate.
-->

## Issue

Closes #

<!-- If this needs no issue, say which exemption applies and delete the line above. -->

## What this changes

<!--
What the diff does and why. Name anything a reader would not predict from the
title: a changed default, a new dependency, altered credential handling.
-->

## Scope

<!--
Confirm this is one change: could any part of it ship and be correct with the
rest reverted? If yes, split it.
-->

## How it was verified

<!--
Tests added and what they would catch. For a bug fix, the test that fails
without it. For a timeout or deadline, a test against a handler that blocks
past it - a promptly-responding server does not cover the case.
-->

---

- [ ] Targets `main`
- [ ] Title carries the issue number, or an exemption applies
- [ ] `make lint` and `make test` pass
- [ ] `go generate ./docs/` run and committed, if a command or flag changed
104 changes: 104 additions & 0 deletions .github/workflows/pr-issue-link.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: PR Issue Link

# Runs on pull_request_target so it also runs on fork pull requests, which are
# the ones this gate exists for. Fork runs of `pull_request` need per-run
# maintainer approval, which would leave the gate silent exactly where it is
# needed.
#
# SAFETY: pull_request_target runs with the base repository's token. This job
# therefore never checks out, builds, or executes pull request code. It reads
# the title, the labels, and the referenced issue through the API and nothing
# else. Do not add a checkout step to this file.

on:
pull_request_target:
types: [opened, edited, reopened, labeled, unlabeled]
branches:
- main

permissions:
contents: read
issues: read
pull-requests: read

jobs:
check-issue-link:
runs-on: ubuntu-latest

steps:
- name: Require an accepted issue in the PR title
env:
# Untrusted input. Passed through the environment and never
# interpolated into the script body.
PR_TITLE: ${{ github.event.pull_request.title }}
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
EXEMPT_LABEL: no-issue-required
ACCEPTED_LABEL: accepted
# Types that never require an issue. Keep in step with the
# "not required" list in ISSUES.md.
EXEMPT_TYPES: docs chore style
run: |
set -euo pipefail

fail() {
echo "----------------------------------------------"
echo " ERROR: $1"
echo "----------------------------------------------"
echo ""
echo " Got title: $PR_TITLE"
echo ""
echo " KeeperHub takes issues before pull requests. Open an issue,"
echo " wait for a maintainer to apply the '$ACCEPTED_LABEL' label,"
echo " then reference it in this PR's title:"
echo ""
echo " feat: #97 add --require-verified to execute status"
echo " fix(execute): #98 bound a hung request under --watch"
echo ""
echo " No issue needed for typos, help-text wording, or docs that"
echo " match existing behaviour. Retitle as one of:"
echo " $EXEMPT_TYPES"
echo ""
echo " Full policy: https://github.com/$REPO/blob/main/ISSUES.md"
echo ""
echo " Already labelled '$ACCEPTED_LABEL'? This check does not rerun"
echo " by itself when the issue changes - re-run the job, or edit"
echo " the PR title to retrigger it."
exit 1
}

if printf '%s' "$PR_LABELS" | grep -qF "\"$EXEMPT_LABEL\""; then
echo "Exempt: pull request carries the '$EXEMPT_LABEL' label."
exit 0
fi

pr_type=$(printf '%s' "$PR_TITLE" | sed -nE 's/^([a-zA-Z]+)(\([^)]*\))?!?:.*/\1/p' | tr '[:upper:]' '[:lower:]')
for exempt in $EXEMPT_TYPES; do
if [ "$pr_type" = "$exempt" ]; then
echo "Exempt: '$pr_type' changes do not require an issue."
exit 0
fi
done

issue_number=$(printf '%s' "$PR_TITLE" | grep -oE '#[0-9]+' | head -n1 | tr -d '#')
if [ -z "$issue_number" ]; then
fail "PR title carries no issue reference."
fi

if ! issue_json=$(gh api "repos/$REPO/issues/$issue_number" 2>/dev/null); then
fail "#$issue_number does not resolve to an issue in $REPO."
fi

if printf '%s' "$issue_json" | jq -e '.pull_request' >/dev/null 2>&1; then
fail "#$issue_number is a pull request, not an issue."
fi

if ! printf '%s' "$issue_json" | jq -e --arg l "$ACCEPTED_LABEL" \
'.labels | map(.name) | index($l)' >/dev/null 2>&1; then
state=$(printf '%s' "$issue_json" | jq -r '.state')
labels=$(printf '%s' "$issue_json" | jq -r '[.labels[].name] | join(", ")')
fail "#$issue_number is not marked '$ACCEPTED_LABEL' (state: $state; labels: ${labels:-none})."
fi

echo "PR title references accepted issue #$issue_number."
Loading