Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b398f3e
ci: remove milestone check from promotion eligibility to allow promot…
marc-romu Mar 24, 2026
b6838c8
ci: fix version grouping to use minor version and standardize PowerSh…
marc-romu Mar 24, 2026
ae28638
ci: skip promotion for versions with closed target milestones and fix…
marc-romu Mar 24, 2026
eb59bb5
ci: standardize PowerShell output commands and improve PR URL parsing…
marc-romu Mar 24, 2026
98fda0e
ci: prevent older versions from being promoted when target milestone …
marc-romu Mar 24, 2026
9550ead
ci: new stabilization-aware milestone workflow
marc-romu Mar 25, 2026
4c61ce1
ci: add workflows write permission to stabilization init workflow
marc-romu Mar 25, 2026
f37899d
ci: remove unnecessary workflows write permission from stabilization …
marc-romu Mar 25, 2026
75e4a6d
ci: replace git commands with GitHub API calls for branch creation in…
marc-romu Mar 25, 2026
d93232d
ci: use PAT_TOKEN for branch creation in stabilization init workflow …
marc-romu Mar 25, 2026
4bad881
chore: add provider hash manifest for version 1.4.2-beta (dual platform)
github-actions[bot] Apr 15, 2026
b02687f
ci: remove automatic branch deletion from hash manifest PR merge in r…
marc-romu Apr 15, 2026
f7cc8da
ci: simplify GitHub Pages deployment trigger to run on all main branc…
marc-romu Apr 15, 2026
fb76c62
ci: add validation to prevent editing stable releases in release work…
marc-romu Apr 15, 2026
faca31d
ci: checkout specific version tag in build jobs for release workflow
marc-romu Apr 15, 2026
95b0e45
chore: add provider hash manifest for version 1.4.2-beta (dual platform)
github-actions[bot] Apr 15, 2026
71fd802
ci: add optional version input parameter to milestone assignment action
marc-romu Apr 15, 2026
d1e38e5
Merge branch 'main' of https://github.com/architects-toolkit/SmartHopper
marc-romu Apr 15, 2026
306289b
ci: extract manifest text update logic into reusable action and integ…
marc-romu Apr 15, 2026
4fda03e
ci: add reusable cherry-pick action and patch propagation workflow fo…
marc-romu Apr 25, 2026
061a4f7
ci: apply PR labels best-effort after creation to prevent unknown lab…
marc-romu Apr 25, 2026
201f4b0
ci: apply PR labels best-effort after creation to prevent unknown lab…
marc-romu Apr 25, 2026
83c39a5
Merge branch 'main' of https://github.com/architects-toolkit/SmartHopper
marc-romu Apr 25, 2026
bd7e3c8
build: replace GhJSON project references with NuGet package references
marc-romu Apr 25, 2026
77b6a5e
ci: add patch/ prefix to auto-delete branch patterns in PR cleanup wo…
marc-romu Apr 25, 2026
e95ec0a
ci: automate release notes generation with Mistral AI
devin-ai-integration[bot] May 1, 2026
0091ae3
ci: add concurrency controls, promotion freeze, auto-discover branche…
devin-ai-integration[bot] May 1, 2026
84658a4
ci: revert hash PR target branch to always use main
devin-ai-integration[bot] May 1, 2026
7a9d5bd
docs(rules): clarify Windsurf guidance
devin-ai-integration[bot] May 3, 2026
d9759e0
Specify zeroed GUID for new components
marc-romu May 3, 2026
a897928
docs(rules): clarify component GUID placeholder
devin-ai-integration[bot] May 3, 2026
2b9a86f
Revise unit test guidelines for Rhino/Grasshopper
marc-romu May 3, 2026
9e82b63
docs(rules): align testing guidance
devin-ai-integration[bot] May 3, 2026
08f4f03
docs(rules): align tool envelope guidance
devin-ai-integration[bot] May 3, 2026
2456728
ci: remove redundant JSON output from PR creation in contributors wor…
marc-romu May 3, 2026
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
129 changes: 129 additions & 0 deletions .github/actions/ai/mistral-chat/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Generic, reusable Mistral AI Chat Completions API wrapper.
# This action is a raw API client with no domain-specific logic.
# It can be used by any workflow that needs AI text generation capabilities.

name: 'Mistral AI Chat'
description: 'Generic wrapper around the Mistral AI Chat Completions API'

inputs:
api-key:
description: 'Mistral AI API key'
required: true
model:
description: 'Mistral model name'
required: false
default: 'mistral-medium-latest'
system-prompt:
description: 'System message content'
required: false
default: ''
user-prompt:
description: 'User message content'
required: true
temperature:
description: 'Sampling temperature'
required: false
default: '0.7'
max-tokens:
description: 'Maximum tokens in response'
required: false
default: '4096'

outputs:
response:
description: 'Raw text content from the API response'
value: ${{ steps.call_api.outputs.response }}
usage-prompt-tokens:
description: 'Prompt tokens used'
value: ${{ steps.call_api.outputs.usage-prompt-tokens }}
usage-completion-tokens:
description: 'Completion tokens used'
value: ${{ steps.call_api.outputs.usage-completion-tokens }}
success:
description: 'Whether the API call succeeded'
value: ${{ steps.call_api.outputs.success }}
error:
description: 'Error message if the call failed'
value: ${{ steps.call_api.outputs.error }}

runs:
using: 'composite'
steps:
- name: Call Mistral AI API
id: call_api
shell: bash
env:
API_KEY: ${{ inputs.api-key }}
MODEL: ${{ inputs.model }}
SYSTEM_PROMPT: ${{ inputs.system-prompt }}
USER_PROMPT: ${{ inputs.user-prompt }}
TEMPERATURE: ${{ inputs.temperature }}
MAX_TOKENS: ${{ inputs.max-tokens }}
run: |
# Build JSON payload using jq to safely handle special characters
PAYLOAD=$(jq -n \
--arg model "$MODEL" \
--arg system "$SYSTEM_PROMPT" \
--arg user "$USER_PROMPT" \
--argjson temperature "$TEMPERATURE" \
--argjson max_tokens "$MAX_TOKENS" \
'{
model: $model,
messages: (
if ($system | length) > 0
then [{role: "system", content: $system}, {role: "user", content: $user}]
else [{role: "user", content: $user}]
end
),
temperature: $temperature,
max_tokens: $max_tokens
}')

# Call the API
HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "https://api.mistral.ai/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d "$PAYLOAD")

# Split body and status code
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -n 1)

if [ "$HTTP_STATUS" -eq 200 ]; then
# Extract response content
CONTENT=$(echo "$HTTP_BODY" | jq -r '.choices[0].message.content // empty')
PROMPT_TOKENS=$(echo "$HTTP_BODY" | jq -r '.usage.prompt_tokens // 0')
COMPLETION_TOKENS=$(echo "$HTTP_BODY" | jq -r '.usage.completion_tokens // 0')

if [ -z "$CONTENT" ]; then
echo "::warning::Mistral API call succeeded but returned empty content"
echo "success=false" >> "$GITHUB_OUTPUT"
echo "error=API returned empty content" >> "$GITHUB_OUTPUT"
echo "response=" >> "$GITHUB_OUTPUT"
echo "usage-prompt-tokens=0" >> "$GITHUB_OUTPUT"
echo "usage-completion-tokens=0" >> "$GITHUB_OUTPUT"
else
echo "success=true" >> "$GITHUB_OUTPUT"
echo "error=" >> "$GITHUB_OUTPUT"
{
echo "response<<EOF_MISTRAL_RESPONSE"
echo "$CONTENT"
echo "EOF_MISTRAL_RESPONSE"
} >> "$GITHUB_OUTPUT"
echo "usage-prompt-tokens=$PROMPT_TOKENS" >> "$GITHUB_OUTPUT"
echo "usage-completion-tokens=$COMPLETION_TOKENS" >> "$GITHUB_OUTPUT"
fi
else
# Error handling
ERROR_MSG=$(echo "$HTTP_BODY" | head -c 500)
echo "::warning::Mistral API call failed: HTTP $HTTP_STATUS - $ERROR_MSG"
echo "success=false" >> "$GITHUB_OUTPUT"
{
echo "error<<EOF_MISTRAL_ERROR"
echo "HTTP $HTTP_STATUS: $ERROR_MSG"
echo "EOF_MISTRAL_ERROR"
} >> "$GITHUB_OUTPUT"
echo "response=" >> "$GITHUB_OUTPUT"
echo "usage-prompt-tokens=0" >> "$GITHUB_OUTPUT"
echo "usage-completion-tokens=0" >> "$GITHUB_OUTPUT"
fi
21 changes: 21 additions & 0 deletions .github/actions/cherry-pick-to-branch/PR_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Auto-generated by patch-propagate workflow -->

## 🍒 Patch Propagation

This PR cherry-picks the following commit(s) onto **`{{TARGET_BRANCH}}`**:

{{COMMIT_LIST}}

**Source branch (reference):** `{{SOURCE_BRANCH}}`

{{CONFLICT_SECTION}}

### Validation checklist

- [ ] CI passes (build, tests, code style)
- [ ] CHANGELOG updated if user-facing
- [ ] Conflicts (if any) resolved correctly
- [ ] Behavior verified on target branch

---
_Opened automatically by `patch-propagate.yml`._
239 changes: 239 additions & 0 deletions .github/actions/cherry-pick-to-branch/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
name: 'Cherry-pick to Branch'
description: 'Cherry-pick one or more commits onto a target branch and open a PR. Never pushes directly to the target.'
inputs:
source-shas:
description: 'Comma- or space-separated list of commit SHAs to cherry-pick (in chronological order).'
required: true
source-branch:
description: 'Source branch name (informational, used in PR body).'
required: false
default: ''
target-branch:
description: 'Target branch to receive the patch via PR.'
required: true
pr-title-prefix:
description: 'Prefix for the PR title.'
required: false
default: '[patch]'
pr-body-extra:
description: 'Optional extra markdown appended to the PR body.'
required: false
default: ''
labels:
description: 'Comma-separated list of labels to apply to the PR. Missing labels are skipped (not auto-created).'
required: false
default: ''
draft-always:
description: 'If true, force the PR to be opened as draft regardless of conflicts.'
required: false
default: 'false'
mainline:
description: 'If set (e.g., "1"), passes -m <mainline> to git cherry-pick to support merge commits.'
required: false
default: ''
token:
description: 'GitHub token with contents:write and pull-requests:write.'
required: true
default: ${{ github.token }}

outputs:
pr-number:
description: 'Number of the PR created (empty if skipped).'
value: ${{ steps.open-pr.outputs.pr-number }}
pr-url:
description: 'URL of the PR created (empty if skipped).'
value: ${{ steps.open-pr.outputs.pr-url }}
branch-name:
description: 'Name of the patch branch pushed.'
value: ${{ steps.cherry-pick.outputs.branch-name }}
has-conflicts:
description: 'true if any cherry-pick required conflict markers to be committed.'
value: ${{ steps.cherry-pick.outputs.has-conflicts }}
status:
description: 'One of: created, skipped-noop, failed.'
value: ${{ steps.cherry-pick.outputs.status }}

runs:
using: 'composite'
steps:
- name: Validate inputs and prepare
id: prep
shell: bash
run: |
set -euo pipefail
target="${{ inputs.target-branch }}"
if [ -z "$target" ]; then
echo "::error::target-branch is required"
exit 1
fi
# Normalize SHA list (comma or whitespace separated).
raw='${{ inputs.source-shas }}'
normalized=$(echo "$raw" | tr ',' ' ' | xargs)
if [ -z "$normalized" ]; then
echo "::error::source-shas is required"
exit 1
fi
echo "shas=$normalized" >> "$GITHUB_OUTPUT"
# Sanitize target branch for use in branch name (replace / with -).
sanitized=$(echo "$target" | tr '/ ' '--')
echo "sanitized-target=$sanitized" >> "$GITHUB_OUTPUT"
# Short SHA of the first commit, for branch naming.
first=$(echo "$normalized" | awk '{print $1}')
short=$(echo "$first" | cut -c1-8)
echo "first-short=$short" >> "$GITHUB_OUTPUT"
ts=$(date -u +%Y%m%d%H%M%S)
echo "timestamp=$ts" >> "$GITHUB_OUTPUT"

- name: Cherry-pick commits onto target
id: cherry-pick
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -euo pipefail
target="${{ inputs.target-branch }}"
shas="${{ steps.prep.outputs.shas }}"
mainline="${{ inputs.mainline }}"
branch="patch/${{ steps.prep.outputs.sanitized-target }}/${{ steps.prep.outputs.first-short }}-${{ steps.prep.outputs.timestamp }}"

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

git fetch --no-tags --prune origin "+refs/heads/*:refs/remotes/origin/*"
# Verify target exists.
if ! git rev-parse --verify "refs/remotes/origin/$target" >/dev/null 2>&1; then
echo "::error::Target branch '$target' not found on origin."
echo "status=failed" >> "$GITHUB_OUTPUT"
exit 1
fi
# Verify all SHAs are reachable.
for sha in $shas; do
if ! git cat-file -e "$sha^{commit}" 2>/dev/null; then
echo "::error::Commit $sha not found in repository."
echo "status=failed" >> "$GITHUB_OUTPUT"
exit 1
fi
done

git checkout -B "$branch" "refs/remotes/origin/$target"

has_conflicts=false
applied=0
skipped=0
for sha in $shas; do
# Skip if already in target history.
if git merge-base --is-ancestor "$sha" HEAD 2>/dev/null; then
echo "::notice::Commit $sha is already in $target; skipping."
skipped=$((skipped+1))
continue
fi
extra=()
if [ -n "$mainline" ]; then
extra+=("-m" "$mainline")
fi
if git cherry-pick -x "${extra[@]}" "$sha"; then
applied=$((applied+1))
continue
fi
# Conflict path: stage all changes and commit with markers.
echo "::warning::Cherry-pick of $sha had conflicts; committing with markers."
has_conflicts=true
git add -A
if ! git -c core.editor=true cherry-pick --continue; then
# If --continue fails (e.g., empty), commit manually.
git commit -am "cherry-pick (with conflicts): $sha" || true
fi
applied=$((applied+1))
done

echo "branch-name=$branch" >> "$GITHUB_OUTPUT"
echo "has-conflicts=$has_conflicts" >> "$GITHUB_OUTPUT"

if [ "$applied" -eq 0 ]; then
echo "::notice::No commits applied to $target (all skipped as no-op)."
echo "status=skipped-noop" >> "$GITHUB_OUTPUT"
exit 0
fi

# Push patch branch.
remote_url="https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git push "$remote_url" "HEAD:refs/heads/$branch"
echo "status=created" >> "$GITHUB_OUTPUT"

- name: Open pull request
id: open-pr
if: steps.cherry-pick.outputs.status == 'created'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -euo pipefail
target="${{ inputs.target-branch }}"
branch="${{ steps.cherry-pick.outputs.branch-name }}"
prefix="${{ inputs.pr-title-prefix }}"
labels="${{ inputs.labels }}"
has_conflicts="${{ steps.cherry-pick.outputs.has-conflicts }}"
draft_always="${{ inputs.draft-always }}"
source_branch="${{ inputs.source-branch }}"
shas="${{ steps.prep.outputs.shas }}"
extra="${{ inputs.pr-body-extra }}"

first_sha=$(echo "$shas" | awk '{print $1}')
first_subject=$(git log -1 --pretty=%s "$first_sha")
title="$prefix $first_subject → $target"

commit_list=""
for sha in $shas; do
subject=$(git log -1 --pretty=%s "$sha")
short=$(echo "$sha" | cut -c1-8)
commit_list+="- [\`${short}\`](https://github.com/${GITHUB_REPOSITORY}/commit/${sha}) ${subject}\n"
done

conflict_section=""
if [ "$has_conflicts" = "true" ]; then
conflict_section="### ⚠️ Conflicts detected\n\nThis PR contains commits with unresolved conflict markers. **Manual resolution required** before merging.\n"
fi

template_path="$GITHUB_ACTION_PATH/PR_TEMPLATE.md"
body=$(cat "$template_path")
body="${body//\{\{TARGET_BRANCH\}\}/$target}"
body="${body//\{\{SOURCE_BRANCH\}\}/${source_branch:-n/a}}"
body="${body//\{\{COMMIT_LIST\}\}/$(printf '%b' "$commit_list")}"
body="${body//\{\{CONFLICT_SECTION\}\}/$(printf '%b' "$conflict_section")}"
if [ -n "$extra" ]; then
body+=$'\n\n---\n\n'"$extra"
fi

draft_flag=()
if [ "$has_conflicts" = "true" ] || [ "$draft_always" = "true" ]; then
draft_flag+=("--draft")
fi

# Create PR without labels first, so unknown labels never abort creation.
pr_url=$(gh pr create \
--base "$target" \
--head "$branch" \
--title "$title" \
--body "$body" \
"${draft_flag[@]}")
echo "pr-url=$pr_url" >> "$GITHUB_OUTPUT"
pr_number=$(basename "$pr_url")
echo "pr-number=$pr_number" >> "$GITHUB_OUTPUT"

# Apply labels best-effort: skip any that don't exist on the repo.
all_labels=()
IFS=',' read -ra label_arr <<< "$labels"
for l in "${label_arr[@]}"; do
l_trim=$(echo "$l" | xargs)
[ -n "$l_trim" ] && all_labels+=("$l_trim")
done
if [ "$has_conflicts" = "true" ]; then
all_labels+=("has-conflicts")
fi
for l in "${all_labels[@]}"; do
if gh pr edit "$pr_number" --add-label "$l" >/dev/null 2>&1; then
echo "::notice::Applied label '$l' to PR #$pr_number"
else
echo "::warning::Label '$l' does not exist in repo; skipping."
fi
done
Loading
Loading