Skip to content
Open
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
173 changes: 161 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help setup generate-input generate-input-custom generate-custom-input-file fetch_changes_between_tags_from_input clean test-linear monthly-release monthly-release-no-ticket
.PHONY: help setup generate-input generate-input-custom generate-custom-input-file fetch_changes_between_tags_from_input clean test-linear monthly-release monthly-release-no-ticket create-release-ticket create-release-ticket-only

# Configuration
PYTHON := python3
Expand All @@ -7,10 +7,20 @@ VERSION_URL := https://cloud.stackgen.com/version.json
# New versions: raw .env from appcd-dist at STACKGEN_TAG (required for generate-input), e.g.
# https://raw.githubusercontent.com/appcd-dev/appcd-dist/v2026.3.12/.env
APPCD_DIST_RAW_ENV = https://raw.githubusercontent.com/appcd-dev/appcd-dist/$(STACKGEN_TAG)/.env
INPUT_FILE := generated_files/input_file/input.json
OUTPUT_FILE := generated_files/final_tag_differences.json

# Artifact root. Default for legacy targets: generated_files/
# create-release-ticket overrides this to $(FROM_REF)-$(TO_REF) (e.g. v2026.7.3-v2026.7.7)
GENERATED_DIR ?= generated_files
INPUT_FILE := $(GENERATED_DIR)/input_file/input.json
OUTPUT_FILE := $(GENERATED_DIR)/final_tag_differences.json
COMMIT_DIFF_FILE := $(GENERATED_DIR)/commit_differences_with_messages.txt
ENV_FILE := .env

# create-release-ticket defaults (override on the command line)
RELEASE_KIND ?= weekly
ASSIGNEE_QUERY ?= gaurav@stackgen.com
STATE_NAME ?= Todo

# Default target
help:
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
Expand All @@ -22,18 +32,41 @@ help:
@echo " make setup - Set up environment and test Linear API"
@echo " make generate-input STACKGEN_TAG=<tag> - input.json (prod version.json + raw appcd-dist .env at tag)"
@echo " make generate-input-custom STACKGEN_TAG=<tag> - Generate input.json (STACKGEN_TAG required)"
@echo " make generate-custom-input-file - input.json from appcd-dist .env between FROM_REF and TO_REF"
@echo " make generate-custom-input-file FROM_REF=<ref> TO_REF=<ref>"
@echo " - input.json from appcd-dist .env between two refs"
@echo " make fetch_changes_between_tags_from_input - Extract ticket changes between versions"
@echo " make monthly-release STACKGEN_TAG=<tag> - Full pipeline: clean → prod input + .env → tickets → Linear"
@echo " make monthly-release-no-ticket STACKGEN_TAG=<tag> - Steps 1–3 only (no Linear issue)"
@echo " make create-release-ticket FROM_REF=<ref> TO_REF=<ref>"
@echo " - clean → custom input → fetch → Linear ticket"
@echo " - artifacts under <FROM_REF>-<TO_REF>/"
@echo " make create-release-ticket-only FROM_REF=<ref> TO_REF=<ref>"
@echo " - Create Linear ticket from <FROM_REF>-<TO_REF>/ artifacts"
@echo " make full-workflow - Run complete workflow (generate + process)"
@echo " make test-linear - Test Linear API connection"
@echo " make clean - Remove generated files"
@echo " make clean - Remove generated_files/ (or GENERATED_DIR=…)"
@echo ""
@echo "Configuration:"
@echo " VERSION_URL = $(VERSION_URL)"
@echo " VERSION_URL = $(VERSION_URL)"
@echo " GENERATED_DIR = $(GENERATED_DIR) (default: generated_files)"
@echo " (generate-input) STACKGEN_TAG required — .env = appcd-dist raw at that tag"
@echo ""
@echo "create-release-ticket parameters:"
@echo " FROM_REF required — appcd-dist base ref/tag (current)"
@echo " TO_REF required — appcd-dist candidate ref/tag (new); also used as STACKGEN_TAG"
@echo " STACKGEN_TAG optional — overrides title tag (default: TO_REF)"
@echo " RELEASE_KIND optional — weekly|monthly (default: weekly)"
@echo " MONTH_LABEL optional — e.g. \"July 2026\""
@echo " ASSIGNEE_QUERY optional — default gaurav@stackgen.com"
@echo " STATE_NAME optional — default Todo"
@echo " OUT_DIR optional — artifact dir (default: <FROM_REF>-<TO_REF>)"
@echo " DRY_RUN=1 optional — preview Linear body only (skip issueCreate)"
@echo ""
@echo "Example:"
@echo " make create-release-ticket FROM_REF=v2026.7.3 TO_REF=v2026.7.7 MONTH_LABEL=\"July 2026\""
@echo " # writes to v2026.7.3-v2026.7.7/"
@echo " make create-release-ticket FROM_REF=v2026.7.3 TO_REF=v2026.7.7 DRY_RUN=1"
@echo ""
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# Setup environment and test Linear API
Expand Down Expand Up @@ -125,6 +158,7 @@ generate-input-custom:
--pretty

# Generate input.json by comparing appcd-dist .env between two refs/tags/branches
# Required: FROM_REF TO_REF (interactive prompt only if either is missing)
generate-custom-input-file:
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo "Generating custom input.json from appcd-dist .env refs..."
Expand All @@ -145,6 +179,7 @@ generate-custom-input-file:
fi; \
echo ""; \
echo "Comparing appcd-dist refs: $$from_ref → $$to_ref"; \
echo "Artifact dir: $(GENERATED_DIR)"; \
echo "Output: $(INPUT_FILE)"; \
echo ""; \
$(PYTHON) generate_custom_input_file.py \
Expand All @@ -154,22 +189,26 @@ generate-custom-input-file:
--pretty

# Process all repos and extract ticket changes
# Override artifact root: make fetch_changes_between_tags_from_input GENERATED_DIR=v2026.7.3-v2026.7.7
fetch_changes_between_tags_from_input:
@if [ ! -f "$(INPUT_FILE)" ]; then \
echo "❌ Error: $(INPUT_FILE) not found. Run 'make generate-input' first."; \
echo "❌ Error: $(INPUT_FILE) not found. Run 'make generate-input' or 'make generate-custom-input-file' first."; \
exit 1; \
fi
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo "Processing all repositories and extracting ticket changes..."
@echo "Artifact dir: $(GENERATED_DIR)"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo ""
@$(PYTHON) process_all_repos.py \
--input "$(INPUT_FILE)" \
--output "$(OUTPUT_FILE)" \
--commit-diff-log "$(COMMIT_DIFF_FILE)" \
--verbose \
--pretty
@echo ""
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo "✅ Processing complete! Check the output file above."
@echo "✅ Processing complete! Output: $(OUTPUT_FILE)"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# Run complete workflow: generate input + process changes
Expand Down Expand Up @@ -224,20 +263,130 @@ monthly-release-no-ticket:
if [ -n "$(VERSION_JSON_URL)" ]; then EXTRA="$$EXTRA --version-json-url $(VERSION_JSON_URL)"; fi; \
$(PYTHON) run_monthly_release.py "$(STACKGEN_TAG)" $$EXTRA

# Clean generated files
# Full weekly/monthly release ticket pipeline:
# clean OUT_DIR → generate-custom-input-file → fetch_changes → Linear ticket
# Artifacts written to OUT_DIR (default: <FROM_REF>-<TO_REF>/), e.g. v2026.7.3-v2026.7.7/
#
# Required: FROM_REF TO_REF
# Optional: OUT_DIR, STACKGEN_TAG, RELEASE_KIND, MONTH_LABEL, ASSIGNEE_QUERY, STATE_NAME, DRY_RUN=1
create-release-ticket:
@if [ -z "$(FROM_REF)" ] || [ -z "$(TO_REF)" ]; then \
echo "❌ FROM_REF and TO_REF are required."; \
echo ""; \
echo "Usage:"; \
echo " make create-release-ticket FROM_REF=<base-tag> TO_REF=<candidate-tag> [options]"; \
echo ""; \
echo "Options:"; \
echo " OUT_DIR=<dir> artifact dir (default: <FROM_REF>-<TO_REF>)"; \
echo " STACKGEN_TAG=<tag> title tag (default: TO_REF)"; \
echo " RELEASE_KIND=weekly|monthly (default: weekly)"; \
echo " MONTH_LABEL=\"July 2026\""; \
echo " ASSIGNEE_QUERY=gaurav@stackgen.com"; \
echo " STATE_NAME=Todo"; \
echo " DRY_RUN=1 preview only"; \
echo ""; \
echo "Example:"; \
echo " make create-release-ticket FROM_REF=v2026.7.3 TO_REF=v2026.7.7 MONTH_LABEL=\"July 2026\""; \
exit 1; \
fi
@from_safe=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
to_safe=$$(printf '%s' "$(TO_REF)" | sed 's|[/ :]|-|g'); \
out_dir="$(OUT_DIR)"; \
if [ -z "$$out_dir" ]; then out_dir="$${from_safe}-$${to_safe}"; fi; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "create-release-ticket pipeline"; \
echo " FROM_REF=$(FROM_REF) → TO_REF=$(TO_REF)"; \
echo " OUT_DIR=$$out_dir"; \
echo " STACKGEN_TAG=$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))"; \
echo " RELEASE_KIND=$(RELEASE_KIND) DRY_RUN=$(if $(DRY_RUN),$(DRY_RUN),0)"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo ""; \
echo "▶ Step 1/4 — clean $$out_dir"; \
rm -rf "$$out_dir"; \
echo "✅ Removed $$out_dir (if it existed)"; \
echo ""; \
echo "▶ Step 2/4 — generate-custom-input-file → $$out_dir/"; \
$(MAKE) generate-custom-input-file \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 3/4 — fetch_changes_between_tags_from_input → $$out_dir/"; \
$(MAKE) fetch_changes_between_tags_from_input GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 4/4 — create Linear release ticket"; \
$(MAKE) create-release-ticket-only \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir" \
STACKGEN_TAG="$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))" \
RELEASE_KIND="$(RELEASE_KIND)" \
MONTH_LABEL="$(MONTH_LABEL)" \
ASSIGNEE_QUERY="$(ASSIGNEE_QUERY)" \
STATE_NAME="$(STATE_NAME)" \
DRY_RUN="$(DRY_RUN)"; \
echo ""; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "✅ create-release-ticket pipeline finished"; \
echo " Artifacts: $$out_dir/"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
Comment on lines +292 to +332

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Pipeline steps are chained with ;, so failures are swallowed.

The whole recipe is one shell invocation without set -e; if generate-custom-input-file, fetch_changes_between_tags_from_input, or create-release-ticket-only fails, execution continues and the recipe exits with the status of the final echo — make prints "✅ pipeline finished" and returns 0.

🐛 Proposed fix
-	`@from_safe`=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
+	`@set` -e; \
+	from_safe=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
 	to_safe=$$(printf '%s' "$(TO_REF)" | sed 's|[/ :]|-|g'); \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@from_safe=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
to_safe=$$(printf '%s' "$(TO_REF)" | sed 's|[/ :]|-|g'); \
out_dir="$(OUT_DIR)"; \
if [ -z "$$out_dir" ]; then out_dir="$${from_safe}-$${to_safe}"; fi; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "create-release-ticket pipeline"; \
echo " FROM_REF=$(FROM_REF) → TO_REF=$(TO_REF)"; \
echo " OUT_DIR=$$out_dir"; \
echo " STACKGEN_TAG=$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))"; \
echo " RELEASE_KIND=$(RELEASE_KIND) DRY_RUN=$(if $(DRY_RUN),$(DRY_RUN),0)"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo ""; \
echo "▶ Step 1/4 — clean $$out_dir"; \
rm -rf "$$out_dir"; \
echo "✅ Removed $$out_dir (if it existed)"; \
echo ""; \
echo "▶ Step 2/4 — generate-custom-input-file → $$out_dir/"; \
$(MAKE) generate-custom-input-file \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 3/4 — fetch_changes_between_tags_from_input → $$out_dir/"; \
$(MAKE) fetch_changes_between_tags_from_input GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 4/4 — create Linear release ticket"; \
$(MAKE) create-release-ticket-only \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir" \
STACKGEN_TAG="$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))" \
RELEASE_KIND="$(RELEASE_KIND)" \
MONTH_LABEL="$(MONTH_LABEL)" \
ASSIGNEE_QUERY="$(ASSIGNEE_QUERY)" \
STATE_NAME="$(STATE_NAME)" \
DRY_RUN="$(DRY_RUN)"; \
echo ""; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "✅ create-release-ticket pipeline finished"; \
echo " Artifacts: $$out_dir/"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
`@set` -e; \
from_safe=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
to_safe=$$(printf '%s' "$(TO_REF)" | sed 's|[/ :]|-|g'); \
out_dir="$(OUT_DIR)"; \
if [ -z "$$out_dir" ]; then out_dir="$${from_safe}-$${to_safe}"; fi; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "create-release-ticket pipeline"; \
echo " FROM_REF=$(FROM_REF) → TO_REF=$(TO_REF)"; \
echo " OUT_DIR=$$out_dir"; \
echo " STACKGEN_TAG=$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))"; \
echo " RELEASE_KIND=$(RELEASE_KIND) DRY_RUN=$(if $(DRY_RUN),$(DRY_RUN),0)"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo ""; \
echo "▶ Step 1/4 — clean $$out_dir"; \
rm -rf "$$out_dir"; \
echo "✅ Removed $$out_dir (if it existed)"; \
echo ""; \
echo "▶ Step 2/4 — generate-custom-input-file → $$out_dir/"; \
$(MAKE) generate-custom-input-file \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 3/4 — fetch_changes_between_tags_from_input → $$out_dir/"; \
$(MAKE) fetch_changes_between_tags_from_input GENERATED_DIR="$$out_dir"; \
echo ""; \
echo "▶ Step 4/4 — create Linear release ticket"; \
$(MAKE) create-release-ticket-only \
FROM_REF="$(FROM_REF)" \
TO_REF="$(TO_REF)" \
GENERATED_DIR="$$out_dir" \
STACKGEN_TAG="$(if $(STACKGEN_TAG),$(STACKGEN_TAG),$(TO_REF))" \
RELEASE_KIND="$(RELEASE_KIND)" \
MONTH_LABEL="$(MONTH_LABEL)" \
ASSIGNEE_QUERY="$(ASSIGNEE_QUERY)" \
STATE_NAME="$(STATE_NAME)" \
DRY_RUN="$(DRY_RUN)"; \
echo ""; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "✅ create-release-ticket pipeline finished"; \
echo " Artifacts: $$out_dir/"; \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 292 - 332, Update the create-release-ticket pipeline
recipe to fail immediately when any cleanup, generation, fetch, or
ticket-creation command fails. Add shell fail-fast behavior at the start of the
recipe or explicitly chain each command with failure propagation, ensuring the
final success message and zero exit status occur only after all steps complete
successfully; preserve the existing Step 1–4 flow and logging.


# Create HZ Linear release ticket from existing tag-diff artifacts only.
#
# Prefer: FROM_REF + TO_REF → reads <FROM_REF>-<TO_REF>/final_tag_differences.json
# Or set: GENERATED_DIR=... / OUT_DIR=...
# Required: TO_REF or STACKGEN_TAG (for title)
create-release-ticket-only:
@tag="$(STACKGEN_TAG)"; \
if [ -z "$$tag" ]; then tag="$(TO_REF)"; fi; \
if [ -z "$$tag" ]; then \
echo "❌ STACKGEN_TAG or TO_REF is required."; \
echo "Usage: make create-release-ticket-only FROM_REF=v2026.7.3 TO_REF=v2026.7.7 [DRY_RUN=1]"; \
exit 1; \
fi; \
out_dir="$(GENERATED_DIR)"; \
if [ -n "$(OUT_DIR)" ]; then out_dir="$(OUT_DIR)"; fi; \
if [ "$$out_dir" = "generated_files" ] && [ -n "$(FROM_REF)" ] && [ -n "$(TO_REF)" ]; then \
from_safe=$$(printf '%s' "$(FROM_REF)" | sed 's|[/ :]|-|g'); \
to_safe=$$(printf '%s' "$(TO_REF)" | sed 's|[/ :]|-|g'); \
out_dir="$${from_safe}-$${to_safe}"; \
fi; \
output_file="$$out_dir/final_tag_differences.json"; \
services_input="$$out_dir/input_file/input.json"; \
if [ ! -f "$$output_file" ]; then \
echo "❌ Error: $$output_file not found."; \
echo " Run: make create-release-ticket FROM_REF=… TO_REF=…"; \
exit 1; \
fi; \
EXTRA="--release-kind $(RELEASE_KIND) --stackgen-tag $$tag --assignee-query $(ASSIGNEE_QUERY) --state-name $(STATE_NAME)"; \
if [ -n "$(MONTH_LABEL)" ]; then EXTRA="$$EXTRA --month-label \"$(MONTH_LABEL)\""; fi; \
if [ "$(DRY_RUN)" = "1" ]; then EXTRA="$$EXTRA --dry-run"; fi; \
if [ -f "$$services_input" ]; then EXTRA="$$EXTRA --services-input \"$$services_input\""; fi; \
echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
eval $(PYTHON) create_monthly_release_ticket.py --input "$$output_file" $$EXTRA
Comment on lines +361 to +366

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: EXTRA is executed through eval, so values with spaces such as ASSIGNEE_QUERY="Jane Doe" break argparse and can misexecute. Pass arguments positionally without eval.

Suggested change
EXTRA="--release-kind $(RELEASE_KIND) --stackgen-tag $$tag --assignee-query $(ASSIGNEE_QUERY) --state-name $(STATE_NAME)"; \
if [ -n "$(MONTH_LABEL)" ]; then EXTRA="$$EXTRA --month-label \"$(MONTH_LABEL)\""; fi; \
if [ "$(DRY_RUN)" = "1" ]; then EXTRA="$$EXTRA --dry-run"; fi; \
if [ -f "$$services_input" ]; then EXTRA="$$EXTRA --services-input \"$$services_input\""; fi; \
echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
eval $(PYTHON) create_monthly_release_ticket.py --input "$$output_file" $$EXTRA
set -- --release-kind "$(RELEASE_KIND)" --stackgen-tag "$$tag" --assignee-query "$(ASSIGNEE_QUERY)" --state-name "$(STATE_NAME)"; \
if [ -n "$(MONTH_LABEL)" ]; then set -- "$$@" --month-label "$(MONTH_LABEL)"; fi; \
if [ "$(DRY_RUN)" = "1" ]; then set -- "$$@" --dry-run; fi; \
if [ -f "$$services_input" ]; then set -- "$$@" --services-input "$$services_input"; fi; \
echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
$(PYTHON) create_monthly_release_ticket.py --input "$$output_file" "$$@"

Comment on lines +361 to +366

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unquoted variables passed through eval — injection and breakage on values with spaces.

RELEASE_KIND, $$tag, ASSIGNEE_QUERY, and STATE_NAME are interpolated unquoted into EXTRA and then re-parsed by eval. A STATE_NAME="In Progress" silently splits into two args, and any shell metacharacter in these values is executed (e.g. TO_REF='v1; …').

🛡️ Proposed fix — quote each value and drop `eval`
-	EXTRA="--release-kind $(RELEASE_KIND) --stackgen-tag $$tag --assignee-query $(ASSIGNEE_QUERY) --state-name $(STATE_NAME)"; \
-	if [ -n "$(MONTH_LABEL)" ]; then EXTRA="$$EXTRA --month-label \"$(MONTH_LABEL)\""; fi; \
-	if [ "$(DRY_RUN)" = "1" ]; then EXTRA="$$EXTRA --dry-run"; fi; \
-	if [ -f "$$services_input" ]; then EXTRA="$$EXTRA --services-input \"$$services_input\""; fi; \
-	echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
-	eval $(PYTHON) create_monthly_release_ticket.py --input "$$output_file" $$EXTRA
+	set -- --release-kind "$(RELEASE_KIND)" --stackgen-tag "$$tag" \
+		--assignee-query "$(ASSIGNEE_QUERY)" --state-name "$(STATE_NAME)"; \
+	if [ -n "$(MONTH_LABEL)" ]; then set -- "$$@" --month-label "$(MONTH_LABEL)"; fi; \
+	if [ "$(DRY_RUN)" = "1" ]; then set -- "$$@" --dry-run; fi; \
+	if [ -f "$$services_input" ]; then set -- "$$@" --services-input "$$services_input"; fi; \
+	echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
+	$(PYTHON) create_monthly_release_ticket.py --input "$$output_file" "$$@"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EXTRA="--release-kind $(RELEASE_KIND) --stackgen-tag $$tag --assignee-query $(ASSIGNEE_QUERY) --state-name $(STATE_NAME)"; \
if [ -n "$(MONTH_LABEL)" ]; then EXTRA="$$EXTRA --month-label \"$(MONTH_LABEL)\""; fi; \
if [ "$(DRY_RUN)" = "1" ]; then EXTRA="$$EXTRA --dry-run"; fi; \
if [ -f "$$services_input" ]; then EXTRA="$$EXTRA --services-input \"$$services_input\""; fi; \
echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
eval $(PYTHON) create_monthly_release_ticket.py --input "$$output_file" $$EXTRA
set -- --release-kind "$(RELEASE_KIND)" --stackgen-tag "$$tag" \
--assignee-query "$(ASSIGNEE_QUERY)" --state-name "$(STATE_NAME)"; \
if [ -n "$(MONTH_LABEL)" ]; then set -- "$$@" --month-label "$(MONTH_LABEL)"; fi; \
if [ "$(DRY_RUN)" = "1" ]; then set -- "$$@" --dry-run; fi; \
if [ -f "$$services_input" ]; then set -- "$$@" --services-input "$$services_input"; fi; \
echo "Creating Linear ticket from $$output_file (tag=$$tag)…"; \
$(PYTHON) create_monthly_release_ticket.py --input "$$output_file" "$$@"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 361 - 366, Update the release-ticket command around
EXTRA and the create_monthly_release_ticket.py invocation to remove eval and
pass every option as a separately quoted argument, including RELEASE_KIND, tag,
ASSIGNEE_QUERY, STATE_NAME, MONTH_LABEL, and services_input. Preserve
conditional inclusion of the optional arguments and ensure values containing
spaces or shell metacharacters remain single literal arguments.


# Clean generated artifacts
# make clean → removes generated_files/
# make clean GENERATED_DIR=v2026.7.3-v2026.7.7 → removes that dir only
clean:
@echo "🧹 Cleaning generated files..."
@rm -rf generated_files/
@echo "🧹 Cleaning $(GENERATED_DIR)..."
@rm -rf "$(GENERATED_DIR)"
@rm -rf __pycache__ release_pipeline/__pycache__
@echo "✅ Cleaned!"
@echo "✅ Cleaned $(GENERATED_DIR)/"

# Show current configuration
config:
@echo "Current Configuration:"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo "VERSION_URL = $(VERSION_URL)"
@echo "ENV_URL = $(ENV_URL)"
@echo "GENERATED_DIR = $(GENERATED_DIR)"
@echo "INPUT_FILE = $(INPUT_FILE)"
@echo "OUTPUT_FILE = $(OUTPUT_FILE)"
@echo "FROM_REF = $(FROM_REF)"
@echo "TO_REF = $(TO_REF)"
@echo "STACKGEN_TAG = $(STACKGEN_TAG)"
@echo "RELEASE_KIND = $(RELEASE_KIND)"
@echo "LINEAR_API_KEY = $${LINEAR_API_KEY:+Set (hidden)}$${LINEAR_API_KEY:-Not set}"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

Loading