Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8469885
ci(sync): add tools/ to dev-stage allow-list for automation scripts
marc-romu May 17, 2026
9aabcc9
feat(validation): exclude models discouraged for all tools from defau…
marc-romu May 17, 2026
7a65496
chore(models): remove deprecated MistralAI voxtral-mini-transcribe-re…
marc-romu May 17, 2026
7203d2d
ci(sync): add tools/ to dev-stage allow-list for automation scripts
marc-romu May 17, 2026
e853281
feat(validation): exclude models discouraged for all tools from defau…
marc-romu May 17, 2026
a0f8bb0
chore(models): remove deprecated MistralAI voxtral-mini-transcribe-re…
marc-romu May 17, 2026
af0a207
chore(templates): sync model-verification template with ProviderModel…
github-actions[bot] May 17, 2026
7930cac
Merge branch 'main' of https://github.com/architects-toolkit/SmartHopper
marc-romu May 17, 2026
ffe65dd
chore(templates): sync model-verification template with ProviderModel…
github-actions[bot] May 17, 2026
2ce632e
Merge branch 'main' of https://github.com/architects-toolkit/SmartHopper
marc-romu May 17, 2026
603283f
feat(models): add default capabilities to MistralAI models
marc-romu May 17, 2026
796e29f
fix(ci): handle milestone pagination and concurrent creation in PR as…
marc-romu May 17, 2026
799d357
chore: add provider hash manifest for 1.4.2-rc (#490)
github-actions[bot] May 17, 2026
f1fd1df
fix: update manifest text dynamically in YAK upload workflow (#493)
devin-ai-integration[bot] May 17, 2026
1da094f
fix: update manifest text dynamically in YAK upload workflow (#493)
devin-ai-integration[bot] May 17, 2026
baa2c8c
ci: workflow restructuration (#495)
devin-ai-integration[bot] May 17, 2026
5560a53
Merge branch 'main' of https://github.com/architects-toolkit/SmartHopper
marc-romu May 17, 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
111 changes: 111 additions & 0 deletions .github/WORKFLOWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Workflow Conventions

> Canonical reference for all GitHub Actions conventions in SmartHopper.
> Every contributor and every new workflow **must** follow these rules.

## Branch Protection Model

| Pattern | Protection |
|---------|-----------|
| `main`, `main-*` | PR + codeowner approval required |
| `dev`, `dev-*` | PR + codeowner approval required |
| `hotfix/*` | PR + codeowner approval required |
| `release/**` | PR + codeowner approval required |

**No workflow may push directly to a protected branch.** All automated
changes must go through a PR created by `peter-evans/create-pull-request@v6`
(preferred) or `gh pr create`.

## Action Version Pinning

| Action | Pin style |
|--------|-----------|
| First-party (`actions/*`) | **Tag** — e.g. `actions/checkout@v4` |
| Third-party (community) | **Tag** — e.g. `peter-evans/create-pull-request@v6` |
| Local composite actions | **Path** — e.g. `./.github/actions/…` |

Do **not** use SHA pinning (`@abc123…`). Tag pinning is easier to audit
and auto-update with Dependabot.

## PR Creation Convention

Use `peter-evans/create-pull-request@v6` for chore workflows that modify
files in the working tree. Use `gh pr create` only when constructing PRs
from existing branches (e.g. release pipeline).

Every automated PR must:
1. Carry the `automated` label.
2. Call `./.github/actions/milestone/assign-pr` to assign the current milestone.
3. Call `./.github/actions/dispatch-required-pr-checks` to trigger status checks.

## Concurrency & `cancel-in-progress`

| Workflow type | `cancel-in-progress` | Rationale |
|---------------|---------------------|-----------|
| PR validation / CI checks | `true` | Superseded by newer pushes |
| Chore (version-date, badge, manifest, …) | `false` | Must complete to avoid stale state |
| Release pipeline (`release-1` … `release-6`) | `false` | Irreversible side-effects |
| Issue/label management | `false` | Quick, idempotent |

## Timeout Policy

Every job **must** declare `timeout-minutes`. Defaults:

| Job type | Timeout |
|----------|---------|
| Lightweight (label, comment, branch delete) | 5 min |
| Standard (checkout + script) | 10 min |
| Build / test (.NET CI) | 30 min |
| Heavy (model fetch, AI generation) | 45 min |

## PowerShell Conventions

When a workflow step uses `shell: pwsh`, call scripts with the `&`
(call operator), **not** by spawning a nested `pwsh` process:

```yaml
# Good
shell: pwsh
run: |
& .\tools\My-Script.ps1 -Param value

# Bad — spawns a child pwsh inside the pwsh shell
shell: pwsh
run: |
pwsh -ExecutionPolicy Bypass -File .\tools\My-Script.ps1 -Param value
```

## Validation Tiers

### Provider Model Validation (`Update-ProviderModels.ps1`)

| Tier | Severity | Blocks merge? | Examples |
|------|----------|---------------|----------|
| Error | `::error::` | Yes | Invalid capability expression, realtime model in list, pending capability |
| Warning | `::warning::` | No | Missing default for a composite capability category |

### PR Validation (`pr-validation.yml`)

| Check | Blocks merge? | Notes |
|-------|---------------|-------|
| Version format | Yes | Must be valid semver |
| Code style | Yes | Trailing whitespace, namespace, using order |
| Changelog | **Warning only** | Skipped for `chore/ci/style/build/revert/docs` PRs |
| PR title | Yes | Must follow conventional commits |

## Node.js Runtime

Set `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` as a repository-level
environment variable (or in individual workflows) to opt into Node.js 24
before the June 2026 deadline.

## Cross-Reference Comments

When two workflows overlap in trigger or purpose, each must carry a
header comment referencing the other. Example:

```yaml
# Related workflows:
# - chore-version-date.yml (updates version date on dev pushes)
# - chore-version-badge.yml (updates README badge on version change)
```
8 changes: 4 additions & 4 deletions .github/actions/dotnet-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ runs:
if (-not (Test-Path signing.snk)) {
if ("${{ inputs.signing_snk_base64 }}" -ne "") {
Write-Host "Decoding signing.snk from Base64 secret"
pwsh tools/Sign-StrongNames.ps1 -Base64 "${{ inputs.signing_snk_base64 }}"
& tools/Sign-StrongNames.ps1 -Base64 "${{ inputs.signing_snk_base64 }}"
} else {
Write-Host "Generating signing.snk"
pwsh tools/Sign-StrongNames.ps1 -Generate
& tools/Sign-StrongNames.ps1 -Generate
}
} else {
Write-Host "signing.snk already exists"
Expand All @@ -71,7 +71,7 @@ runs:
shell: pwsh
run: |
Write-Host "Updating InternalsVisibleTo entries with public key from signing.snk"
pwsh tools/Update-InternalsVisibleTo.ps1
& tools/Update-InternalsVisibleTo.ps1

- name: 'Checkout unlicensed lib'
uses: actions/checkout@v4
Expand Down Expand Up @@ -183,4 +183,4 @@ runs:
Write-Host "Auth-signing SmartHopper provider DLLs in $buildPath"
$trimmedPassword = $env:PFX_PASSWORD.Trim()
./tools/Sign-Authenticode.ps1 -Sign $buildPath -Password $trimmedPassword
if ($LASTEXITCODE -ne 0) { Write-Error "Authenticode signing failed for provider assemblies"; exit $LASTEXITCODE }
if ($LASTEXITCODE -ne 0) { Write-Error "Authenticode signing failed for provider assemblies"; exit $LASTEXITCODE }
127 changes: 127 additions & 0 deletions .github/actions/utils/safe-commit/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Safe Commit
description: |
Commits staged changes and pushes. If the push fails (e.g. protected branch),
automatically creates a PR instead. This removes the need to hardcode protected
branch patterns in workflows.

inputs:
commit-message:
description: 'Commit message for the changes'
required: true
pr-title:
description: 'PR title if a PR needs to be created (defaults to commit message)'
required: false
default: ''
pr-body:
description: 'PR body if a PR needs to be created'
required: false
default: 'Automated changes that could not be pushed directly to the branch.'
pr-labels:
description: 'Comma-separated labels to add to the PR'
required: false
default: ''
token:
description: 'GitHub token for creating PRs'
required: true

outputs:
method:
description: 'How the changes were applied: "direct" (pushed) or "pr" (pull request created)'
value: ${{ steps.result.outputs.method }}
pr-number:
description: 'PR number if a PR was created (empty for direct push)'
value: ${{ steps.result.outputs.pr_number }}
pr-url:
description: 'PR URL if a PR was created (empty for direct push)'
value: ${{ steps.result.outputs.pr_url }}
changed:
description: 'Whether any changes were committed: "true" or "false"'
value: ${{ steps.commit.outputs.changed }}

runs:
using: composite
steps:
- name: Check for changes and commit
id: commit
shell: bash
run: |
if git diff --cached --quiet; then
echo "No staged changes to commit"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
git commit -m "${{ inputs.commit-message }}"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Try direct push
id: push
if: steps.commit.outputs.changed == 'true'
shell: bash
run: |
if git push 2>&1; then
echo "push_ok=true" >> "$GITHUB_OUTPUT"
else
echo "Direct push failed (branch may be protected), will create PR"
echo "push_ok=false" >> "$GITHUB_OUTPUT"
fi

- name: Create PR if push failed
id: create-pr
if: steps.commit.outputs.changed == 'true' && steps.push.outputs.push_ok == 'false'
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
run: |
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
TEMP_BRANCH="chore/auto-$(date +%s)-$(echo "${{ inputs.commit-message }}" | tr ' ' '-' | tr -cd '[:alnum:]-' | head -c 40)"

# Create temp branch and push
git checkout -b "$TEMP_BRANCH"
git push origin "$TEMP_BRANCH"

# Create PR
PR_TITLE="${{ inputs.pr-title }}"
if [ -z "$PR_TITLE" ]; then
PR_TITLE="${{ inputs.commit-message }}"
fi

PR_URL=$(gh pr create \
--base "$CURRENT_BRANCH" \
--head "$TEMP_BRANCH" \
--title "$PR_TITLE" \
--body "${{ inputs.pr-body }}")

PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$')

# Add labels if specified
if [ -n "${{ inputs.pr-labels }}" ]; then
IFS=',' read -ra LABELS <<< "${{ inputs.pr-labels }}"
for label in "${LABELS[@]}"; do
label=$(echo "$label" | xargs)
gh pr edit "$PR_NUMBER" --add-label "$label" 2>/dev/null || true
done
fi

echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"

# Switch back to original branch
git checkout "$CURRENT_BRANCH"

- name: Set result
id: result
shell: bash
run: |
if [ "${{ steps.commit.outputs.changed }}" != "true" ]; then
echo "method=none" >> "$GITHUB_OUTPUT"
echo "pr_number=" >> "$GITHUB_OUTPUT"
echo "pr_url=" >> "$GITHUB_OUTPUT"
elif [ "${{ steps.push.outputs.push_ok }}" == "true" ]; then
echo "method=direct" >> "$GITHUB_OUTPUT"
echo "pr_number=" >> "$GITHUB_OUTPUT"
echo "pr_url=" >> "$GITHUB_OUTPUT"
else
echo "method=pr" >> "$GITHUB_OUTPUT"
echo "pr_number=${{ steps.create-pr.outputs.pr_number }}" >> "$GITHUB_OUTPUT"
echo "pr_url=${{ steps.create-pr.outputs.pr_url }}" >> "$GITHUB_OUTPUT"
fi
Loading
Loading