From 62ed13753691ddbe058b8337711d8ba6fead2016 Mon Sep 17 00:00:00 2001 From: Bill Seremetis Date: Wed, 23 Sep 2026 21:15:27 +0300 Subject: [PATCH 1/6] Prefix Teamwork card titles with the GitLab project name in package-checker-and-card-maker Assisted-by: Claude:claude-sonnet-5 --- commands/host/package-checker-and-card-maker | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) mode change 100755 => 100644 commands/host/package-checker-and-card-maker diff --git a/commands/host/package-checker-and-card-maker b/commands/host/package-checker-and-card-maker old mode 100755 new mode 100644 index a843b9f..40cdd54 --- a/commands/host/package-checker-and-card-maker +++ b/commands/host/package-checker-and-card-maker @@ -79,7 +79,7 @@ if ! $DRY_RUN && [[ -z "$TW_API_KEY" || -z "$DOMAIN" ]]; then exit 1 fi -CARD_TITLE="Update Dependencies" +BASE_TITLE="Update Dependencies" GROUP="${GITLAB_GROUP:-clients}" MANIFEST="composer-manifest.yaml" @@ -205,8 +205,8 @@ is_insecure() { # # Create the Teamwork card on a project's board. Uses the "Cards" tasklist # (case-insensitive) or falls back to the first one, like tw-batch-card-maker. -create_card() { # [comma-separated assignee ids] - local tw_id="$1" desc="$2" assignees="${3:-}" tl_response tl_status tasklist_id data response http_code body +create_card() { # <description> [comma-separated assignee ids] + local tw_id="$1" title="$2" desc="$3" assignees="${4:-}" tl_response tl_status tasklist_id data response http_code body tl_response=$(tw_curl -s "https://${DOMAIN}/projects/${tw_id}/tasklists.json") [ -n "$tl_response" ] || { echo "failed to fetch tasklists"; return 1; } @@ -220,7 +220,7 @@ create_card() { # <teamwork-project-id> <description> [comma-separated assignee .id // empty' <<<"$tl_response") [ -n "$tasklist_id" ] || { echo "no tasklist found"; return 1; } - data=$(jq -n --arg name "$CARD_TITLE" --arg desc "$desc" --arg assignees "$assignees" \ + data=$(jq -n --arg name "$title" --arg desc "$desc" --arg assignees "$assignees" \ '{"todo-item": ({"content": $name, "description": $desc} + (if $assignees != "" then {"responsible-party-id": $assignees} else {} end))}') @@ -298,6 +298,7 @@ while IFS=$'\t' read -r id name branch web_url; do fi # Repos onboarded to Renovate get a link to their dashboard issue + card_title="${name##*/}: $BASE_TITLE" card_desc="Please update $pkg_list" renovate_url="" if [ -n "$renovate_id" ] && [ -n "$web_url" ]; then @@ -312,14 +313,14 @@ while IFS=$'\t' read -r id name branch web_url; do echo -e "${GREEN}$name${NC} ($pkg_list): would make card on Teamwork project $tw_id" fi # Show the card exactly as it would be created, indented under the repo. - echo " title: $CARD_TITLE" + echo " title: $card_title" echo " description:" sed 's/^/ /' <<<"$card_desc" made=$((made+1)) continue fi - if result=$(create_card "$tw_id" "$card_desc" "$assignees"); then + if result=$(create_card "$tw_id" "$card_title" "$card_desc" "$assignees"); then echo -e "${GREEN}$name${NC} ($pkg_list): card made - $result" made_urls+=("$name"$'\t'"$result") made=$((made+1)) From 33739de4d57a1097aabc6e1132c28d8328b4b755 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:16:53 +0000 Subject: [PATCH 2/6] docs: sync with PR #180 changes Co-Authored-By: Claude <noreply@anthropic.com> --- docs/host-commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/host-commands.md b/docs/host-commands.md index fccf0ef..6bcfe1f 100644 --- a/docs/host-commands.md +++ b/docs/host-commands.md @@ -31,7 +31,7 @@ These commands run on the developer's host machine (via `ddev <command>`), from - `mr` — Pushes the current branch and creates a draft GitLab MR via `glab mr create`, prefilling the title from the branch's `T-<id>` and description suffix. - `open-board` — Opens the Teamwork board for the project, building the URL from `TEAMWORK_PROJECT_ID`/`TEAMWORK_DOMAIN` in `.ddev/.env.anner`. Aliases: `tw-board`. - `open-issue` — Opens the Teamwork task URL for the `T-<id>` in the current branch name and copies it to the clipboard; falls back to the previous branch if needed. Pass `-c` to copy (and print) the URL only, without opening a browser. Aliases: `tw-open`. -- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates an "Update Dependencies" card on each matching project's Teamwork board. Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. +- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates a card titled `<project-name>: Update Dependencies` on each matching project's Teamwork board. Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. - `package-checker` — Scans every project in a GitLab group for usage (and version) of one or more Composer packages (`vendor/package` or `vendor/package:max-insecure-version`, OR-matched) by reading each project's `composer-manifest.yaml`, optionally flagging insecure versions; a project is reported as a single aggregated line listing every matching package, in red if any match is insecure. Aliases: `wtfiow`. - `protect` — Enables/disables/resets HTTP basic-auth protection on a nixOS-hosted dev project via nginx config and htpasswd; no-ops unless `ANNERTECH_LOCAL_DEV` is set. - `remote-db` — Pulls the latest database (skipping files) from the configured upstream provider (`DDEV_UPSTREAM_PROVIDER` in `.ddev/.env.anner`) via `ddev pull <provider> --skip-files -y`. From 77ef718e6a99c90c6a93ef2bdfaa02826a135c4d Mon Sep 17 00:00:00 2001 From: Bill Seremetis <bill@seremetis.net> Date: Wed, 23 Sep 2026 21:18:57 +0300 Subject: [PATCH 3/6] Use the GitLab project title (not the path slug) for the card title Assisted-by: Claude:claude-sonnet-5 --- commands/host/package-checker-and-card-maker | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/host/package-checker-and-card-maker b/commands/host/package-checker-and-card-maker index 40cdd54..0992707 100644 --- a/commands/host/package-checker-and-card-maker +++ b/commands/host/package-checker-and-card-maker @@ -100,7 +100,7 @@ if [ "$BACKEND" = "glab" ]; then list_projects() { "${api[@]}" --paginate \ "groups/$GID/projects?include_subgroups=true&archived=false&per_page=100" \ - | jq -rs '.[][] | select(.archived != true) | "\(.id)\t\(.path_with_namespace)\t\(.default_branch // "main")\t\(.web_url // "")"' + | jq -rs '.[][] | select(.archived != true) | "\(.id)\t\(.path_with_namespace)\t\(.default_branch // "main")\t\(.web_url // "")\t\(.name)"' } get_file() { # <id> <branch> <url-encoded path> "${api[@]}" "projects/$1/repository/files/$3/raw?ref=$2" 2>/dev/null @@ -128,7 +128,7 @@ else resp=$(curl -sf "${hdr[@]}" \ "$GITLAB_URL/api/v4/groups/$GID/projects?include_subgroups=true&archived=false&per_page=100&page=$page") [ "$(jq length <<<"$resp")" -eq 0 ] && break - jq -r '.[] | select(.archived != true) | "\(.id)\t\(.path_with_namespace)\t\(.default_branch // "main")\t\(.web_url // "")"' <<<"$resp" + jq -r '.[] | select(.archived != true) | "\(.id)\t\(.path_with_namespace)\t\(.default_branch // "main")\t\(.web_url // "")\t\(.name)"' <<<"$resp" page=$((page+1)) done } @@ -249,7 +249,7 @@ matched_repos=0 made_urls=() # "repo<TAB>task-url" for the summary echo "------------------------------------------------" -while IFS=$'\t' read -r id name branch web_url; do +while IFS=$'\t' read -r id name branch web_url title; do scanned=$((scanned+1)) [ -n "${DEBUG:-}" ] && echo " scanning: $name ($branch)" >&2 @@ -298,7 +298,7 @@ while IFS=$'\t' read -r id name branch web_url; do fi # Repos onboarded to Renovate get a link to their dashboard issue - card_title="${name##*/}: $BASE_TITLE" + card_title="$title: $BASE_TITLE" card_desc="Please update $pkg_list" renovate_url="" if [ -n "$renovate_id" ] && [ -n "$web_url" ]; then From fb6e2329043b0066442b44481a67b8971afa847d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:20:44 +0000 Subject: [PATCH 4/6] docs: sync with PR #180 changes Co-Authored-By: Claude <noreply@anthropic.com> --- docs/host-commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/host-commands.md b/docs/host-commands.md index 6bcfe1f..6f1edde 100644 --- a/docs/host-commands.md +++ b/docs/host-commands.md @@ -31,7 +31,7 @@ These commands run on the developer's host machine (via `ddev <command>`), from - `mr` — Pushes the current branch and creates a draft GitLab MR via `glab mr create`, prefilling the title from the branch's `T-<id>` and description suffix. - `open-board` — Opens the Teamwork board for the project, building the URL from `TEAMWORK_PROJECT_ID`/`TEAMWORK_DOMAIN` in `.ddev/.env.anner`. Aliases: `tw-board`. - `open-issue` — Opens the Teamwork task URL for the `T-<id>` in the current branch name and copies it to the clipboard; falls back to the previous branch if needed. Pass `-c` to copy (and print) the URL only, without opening a browser. Aliases: `tw-open`. -- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates a card titled `<project-name>: Update Dependencies` on each matching project's Teamwork board. Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. +- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates a card titled `<GitLab project title>: Update Dependencies` on each matching project's Teamwork board (the project's display title, not its path slug). Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. - `package-checker` — Scans every project in a GitLab group for usage (and version) of one or more Composer packages (`vendor/package` or `vendor/package:max-insecure-version`, OR-matched) by reading each project's `composer-manifest.yaml`, optionally flagging insecure versions; a project is reported as a single aggregated line listing every matching package, in red if any match is insecure. Aliases: `wtfiow`. - `protect` — Enables/disables/resets HTTP basic-auth protection on a nixOS-hosted dev project via nginx config and htpasswd; no-ops unless `ANNERTECH_LOCAL_DEV` is set. - `remote-db` — Pulls the latest database (skipping files) from the configured upstream provider (`DDEV_UPSTREAM_PROVIDER` in `.ddev/.env.anner`) via `ddev pull <provider> --skip-files -y`. From 725d6d6077979ecce021addbefcd235e7cbc2ccf Mon Sep 17 00:00:00 2001 From: Bill Seremetis <bill@seremetis.net> Date: Thu, 24 Sep 2026 14:55:20 +0300 Subject: [PATCH 5/6] Keep card title fixed; move GitLab project title into a description heading Assisted-by: Claude:claude-sonnet-5 --- commands/host/package-checker-and-card-maker | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/commands/host/package-checker-and-card-maker b/commands/host/package-checker-and-card-maker index 0992707..92a5d67 100644 --- a/commands/host/package-checker-and-card-maker +++ b/commands/host/package-checker-and-card-maker @@ -79,7 +79,7 @@ if ! $DRY_RUN && [[ -z "$TW_API_KEY" || -z "$DOMAIN" ]]; then exit 1 fi -BASE_TITLE="Update Dependencies" +CARD_TITLE="Update Dependencies" GROUP="${GITLAB_GROUP:-clients}" MANIFEST="composer-manifest.yaml" @@ -205,8 +205,8 @@ is_insecure() { # <version> <spec> # Create the Teamwork card on a project's board. Uses the "Cards" tasklist # (case-insensitive) or falls back to the first one, like tw-batch-card-maker. -create_card() { # <teamwork-project-id> <title> <description> [comma-separated assignee ids] - local tw_id="$1" title="$2" desc="$3" assignees="${4:-}" tl_response tl_status tasklist_id data response http_code body +create_card() { # <teamwork-project-id> <description> [comma-separated assignee ids] + local tw_id="$1" desc="$2" assignees="${3:-}" tl_response tl_status tasklist_id data response http_code body tl_response=$(tw_curl -s "https://${DOMAIN}/projects/${tw_id}/tasklists.json") [ -n "$tl_response" ] || { echo "failed to fetch tasklists"; return 1; } @@ -220,7 +220,7 @@ create_card() { # <teamwork-project-id> <title> <description> [comma-separated a .id // empty' <<<"$tl_response") [ -n "$tasklist_id" ] || { echo "no tasklist found"; return 1; } - data=$(jq -n --arg name "$title" --arg desc "$desc" --arg assignees "$assignees" \ + data=$(jq -n --arg name "$CARD_TITLE" --arg desc "$desc" --arg assignees "$assignees" \ '{"todo-item": ({"content": $name, "description": $desc} + (if $assignees != "" then {"responsible-party-id": $assignees} else {} end))}') @@ -297,13 +297,16 @@ while IFS=$'\t' read -r id name branch web_url title; do continue fi + # Heading names the GitLab project; affected packages become a checklist so + # each one can be ticked off as it's updated. + checklist=$(printf -- '- [ ] %s\n' "${repo_pkgs[@]}") + card_desc="## $title"$'\n\n'"Please update:"$'\n\n'"${checklist%$'\n'}" + # Repos onboarded to Renovate get a link to their dashboard issue - card_title="$title: $BASE_TITLE" - card_desc="Please update $pkg_list" renovate_url="" if [ -n "$renovate_id" ] && [ -n "$web_url" ]; then renovate_url="${web_url%/}/-/work_items/${renovate_id}" - card_desc="$card_desc"$'\n\n'"Renovate dashboard: $renovate_url" + card_desc="$card_desc"$'\n\n'"**Renovate dashboard:** $renovate_url" fi if $DRY_RUN; then @@ -313,14 +316,14 @@ while IFS=$'\t' read -r id name branch web_url title; do echo -e "${GREEN}$name${NC} ($pkg_list): would make card on Teamwork project $tw_id" fi # Show the card exactly as it would be created, indented under the repo. - echo " title: $card_title" + echo " title: $CARD_TITLE" echo " description:" sed 's/^/ /' <<<"$card_desc" made=$((made+1)) continue fi - if result=$(create_card "$tw_id" "$card_title" "$card_desc" "$assignees"); then + if result=$(create_card "$tw_id" "$card_desc" "$assignees"); then echo -e "${GREEN}$name${NC} ($pkg_list): card made - $result" made_urls+=("$name"$'\t'"$result") made=$((made+1)) From d10a888fd985c64a94e1e7483020e74be98ee9bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:56:31 +0000 Subject: [PATCH 6/6] docs: sync with PR #180 changes Co-Authored-By: Claude <noreply@anthropic.com> --- docs/host-commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/host-commands.md b/docs/host-commands.md index 6f1edde..f12f990 100644 --- a/docs/host-commands.md +++ b/docs/host-commands.md @@ -31,7 +31,7 @@ These commands run on the developer's host machine (via `ddev <command>`), from - `mr` — Pushes the current branch and creates a draft GitLab MR via `glab mr create`, prefilling the title from the branch's `T-<id>` and description suffix. - `open-board` — Opens the Teamwork board for the project, building the URL from `TEAMWORK_PROJECT_ID`/`TEAMWORK_DOMAIN` in `.ddev/.env.anner`. Aliases: `tw-board`. - `open-issue` — Opens the Teamwork task URL for the `T-<id>` in the current branch name and copies it to the clipboard; falls back to the previous branch if needed. Pass `-c` to copy (and print) the URL only, without opening a browser. Aliases: `tw-open`. -- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates a card titled `<GitLab project title>: Update Dependencies` on each matching project's Teamwork board (the project's display title, not its path slug). Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. +- `package-checker-and-card-maker` — Combines `package-checker` with automatic Teamwork card creation: scans every project in a GitLab group for one or more Composer packages and creates an "Update Dependencies" card on each matching project's Teamwork board. The description names the GitLab project (its display title, not its path slug) as a heading, followed by a checklist of the affected packages. Reads `TEAMWORK_PROJECT_ID` from the repo's `.ddev/.env.anner`. Supports `--dry-run` (show what would happen without creating cards) and `--auto-assign` (assigns the card to the people whose IDs are stored as `TEAMWORK_DEPUTY_ID`, `TEAMWORK_GUARDIAN_ID`, and `TEAMWORK_DM_ID` in the same `.env.anner`). Uses the same GitLab backend (glab or curl) and package/version-range syntax as `package-checker`, and additionally accepts a full drupal.org "Affected versions" constraint as the version suffix, e.g. `ddev paccma 'drupal/core:>=11.4.0 <11.4.7'`. Requires `TEAMWORK_API_KEY` and `TEAMWORK_DOMAIN`. Aliases: `paccma`, `paccman`. - `package-checker` — Scans every project in a GitLab group for usage (and version) of one or more Composer packages (`vendor/package` or `vendor/package:max-insecure-version`, OR-matched) by reading each project's `composer-manifest.yaml`, optionally flagging insecure versions; a project is reported as a single aggregated line listing every matching package, in red if any match is insecure. Aliases: `wtfiow`. - `protect` — Enables/disables/resets HTTP basic-auth protection on a nixOS-hosted dev project via nginx config and htpasswd; no-ops unless `ANNERTECH_LOCAL_DEV` is set. - `remote-db` — Pulls the latest database (skipping files) from the configured upstream provider (`DDEV_UPSTREAM_PROVIDER` in `.ddev/.env.anner`) via `ddev pull <provider> --skip-files -y`.