Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
181 commits
Select commit Hold shift + click to select a range
3faf562
update working branch (#419)
marc-romu Mar 26, 2026
a785221
feat(json): add comprehensive JSON component suite with AI-powered te…
marc-romu Mar 26, 2026
66232f6
Merge branch 'feature/2.0.0-text2json' of https://github.com/architec…
marc-romu Mar 26, 2026
d842f99
feat(json): add visual JSON Schema builder components for property co…
marc-romu Mar 26, 2026
62fa947
docs: fix file2md tool naming inconsistencies in documentation and co…
marc-romu Mar 26, 2026
0a72e67
feat(batch): add batch processing support to AI components with resul…
marc-romu Mar 26, 2026
39f038d
feat(components): add Keywords property to AI components for improved…
marc-romu Mar 27, 2026
b4edf5f
chore: add Keywords property to AIText2JsonComponent and fix code for…
marc-romu Mar 27, 2026
6a8e64b
chore: add 'filetomd' keyword to AIFile2MdComponent for improved sear…
marc-romu Mar 27, 2026
fb0482f
feat(ai): expire solution when provider changes in AIExtraSettings to…
marc-romu Mar 27, 2026
95889ed
fix(batch): surface individual batch item errors to Grasshopper compo…
marc-romu Mar 27, 2026
b2c3168
feat(batch): rework AIFile2MdComponent to batch only image descriptio…
marc-romu Mar 27, 2026
cda9b50
feat(batch): validate batch requests and surface warnings/errors befo…
marc-romu Mar 27, 2026
32e8863
feat(file2md): use placeholder-based image insertion instead of appen…
marc-romu Mar 27, 2026
a8114c6
chore(release): reduce version promotion waiting period from 30 to 15…
marc-romu Mar 27, 2026
176281c
fix(batch): correct validation message handling and add missing closi…
marc-romu Mar 27, 2026
7b55dba
refactor(batch): move metrics clearing to OnEnteringProcessingState a…
marc-romu Mar 27, 2026
999f8b9
chore: rename WebToMd to Web2Md for consistency with component naming…
marc-romu Mar 27, 2026
1747877
feat(discourse): add generic Discourse forum components and AI tools …
marc-romu Mar 27, 2026
04816ff
refactor(discourse): unify discourse-related aitools
marc-romu Mar 27, 2026
5eeb2d5
fix(batch): use SetPersistentRuntimeMessage for batch validation warn…
marc-romu Mar 27, 2026
9bc1e46
refactor(discourse): collapse multi-line JSON schema strings to singl…
marc-romu Mar 27, 2026
4469013
feat(text2json): add error handling and null checks to batch processi…
marc-romu Mar 27, 2026
47b0f06
fix(batch): resolve HTTPClient timeout issues in batch operations by …
marc-romu Mar 27, 2026
8fcda04
feat(network): add global HTTP timeout settings for all AI providers …
marc-romu Mar 27, 2026
02a7ea2
refactor(batch): restructure File2Md batch context from per-image to …
marc-romu Mar 27, 2026
edb6ab0
refactor(network): read HTTP timeout settings from global settings in…
marc-romu Mar 27, 2026
6a620ce
refactor(batch): improve batch processing debugging and prevent senti…
marc-romu Mar 28, 2026
29f11d6
refactor(file2md): add batch context persistence and unify image desc…
marc-romu Mar 28, 2026
0c570e4
refactor(batch): add HasActiveBatchSubmission property and fix metric…
marc-romu Mar 28, 2026
a569a1e
feat(models): add GPT-5.4 series, MistralAI versioned aliases, and Cl…
marc-romu Mar 28, 2026
4a2498d
refactor(batch): add batch completion time tracking and defer metrics…
marc-romu Mar 28, 2026
f03f6a6
refactor(providers): improve tool timeout error message and comment o…
marc-romu Mar 28, 2026
b54fad6
refactor(json): reorganize JsonSchema component inputs and add auto-e…
marc-romu Mar 28, 2026
e5babe5
refactor(state): add OnEnteringNeedsRunState lifecycle hook and move …
marc-romu Mar 28, 2026
11df555
refactor(knowledge): remove explicit RunOnlyOnInputChanges = false fr…
marc-romu Mar 28, 2026
63b0ac9
refactor(json): remove manual Required input from JsonSchema componen…
marc-romu Mar 28, 2026
c9b09f4
refactor(knowledge): reorganize component exposure levels and clarify…
marc-romu Mar 28, 2026
c4815f4
fix(knowledge): correct spelling of GH_Exposure.quarternary for Ladyb…
marc-romu Mar 28, 2026
0857692
refactor(providers): mark Claude 4.6 series as unverified and add add…
marc-romu Mar 28, 2026
6d8363f
refactor(aistatefulasync): unified batch and non-batch methods for si…
marc-romu Mar 28, 2026
97c44d3
refactor(state): introduce _persistedMetrics field to avoid computed-…
marc-romu Mar 29, 2026
329dc5d
fix(batch): add sentinel forwarding for boolean-output components and…
marc-romu Mar 29, 2026
64664bb
docs(readme): add batch processing column to provider feature compari…
marc-romu Mar 29, 2026
5d7ab68
feature(ai-providers): add batch path support to surface in batch pro…
marc-romu Mar 30, 2026
8a48eb0
fix(anthropic): fix position of effort parameter in api request
marc-romu Mar 30, 2026
e5824c3
refactor(json): new array input in jsonschemacomponent and jsonschema…
marc-romu Mar 30, 2026
9749e1e
refactor(components): improved keywords
marc-romu Mar 30, 2026
6567e2c
refactor(json): move :required suffix handling from JsonSchemaCompone…
marc-romu Mar 30, 2026
0363723
refactor(json): simplify required field handling by building required…
marc-romu Mar 30, 2026
33dede8
refactor(json): add required parameter to JsonSchemaObject component …
marc-romu Mar 30, 2026
bc29396
refactor(json): allow colons in property descriptions by detecting :r…
marc-romu Mar 30, 2026
5bbe132
refactor(ai-tools): standardize prompt formatting with section header…
marc-romu Mar 30, 2026
4b13ca8
refactor(ai): centralize batch finalization and auto-inject required …
marc-romu Mar 30, 2026
f09ec9d
refactor(ai): transition to NeedsRun when batch completes with unreso…
marc-romu Mar 30, 2026
59c3bc1
refactor(ai-boolean): add fallback parameter and used-fallback output…
marc-romu Mar 30, 2026
d8083e8
refactor(ai): transition to Error state when batch completes with err…
marc-romu Mar 30, 2026
09d030c
refactor(ai-boolean): fix fallback structure initialization and simpl…
marc-romu Mar 30, 2026
25b8093
refactor(ai-boolean): mark Fallback parameter as optional and add nul…
marc-romu Mar 30, 2026
8e768c9
refactor(ai-boolean): mark Fallback parameter as optional and add nul…
marc-romu Mar 30, 2026
0e39979
refactor(ai-boolean): reuse Result sentinel for Used Fallback output …
marc-romu Mar 30, 2026
33785b7
refactor(ai-boolean): change Fallback parameter from text to boolean …
marc-romu Mar 31, 2026
d28d8b1
refactor(datatree): surface tree processing messages as persistent ru…
marc-romu Mar 31, 2026
cad70c8
refactor(datatree-test): update test components to access Outputs pro…
marc-romu Mar 31, 2026
28e65c1
refactor(ai-boolean): migrate AIText2Boolean and AIList2Boolean to mi…
marc-romu Mar 31, 2026
d47c2aa
refactor(ai-boolean): wrap extracted result tree in dictionary before…
marc-romu Mar 31, 2026
b3abfe8
refactor(stateful-component): add context menu items for batch result…
marc-romu Mar 31, 2026
a89a357
refactor(datatree): improve path mismatch messages with Oxford comma …
marc-romu Mar 31, 2026
816e60a
refactor(datatree): downgrade path mismatch and omission messages fro…
marc-romu Mar 31, 2026
4e1ec52
feat(ai-provider): new Gemini API provider
marc-romu Mar 31, 2026
3806f71
refactor(ai-request): clarify UnknownModel message to indicate valida…
marc-romu Mar 31, 2026
d501ede
refactor(stateful-component): add batch capability validation to prev…
marc-romu Mar 31, 2026
d3dfce9
refactor(stateful-component): add immediate first poll option when re…
marc-romu Mar 31, 2026
c342ed7
refactor(workflows): standardize git config to use SMARTHOPPER_BOT va…
marc-romu Apr 2, 2026
c678fdf
docs(testing): add comprehensive testing plan for SmartHopper 2.0.0 r…
marc-romu Apr 2, 2026
1057fd8
refactor(workflows): handle milestone creation race condition with re…
marc-romu Apr 2, 2026
d4e1194
chore: normalize file encoding to UTF-8 with BOM across 56 source files
marc-romu Apr 2, 2026
40ec935
refactor(providers): rename Google provider to Gemini throughout code…
marc-romu Apr 2, 2026
4d5cfad
refactor(workflows): add pagination to milestone fetching and remove …
marc-romu Apr 3, 2026
f0e4c84
test(converters): add comprehensive unit tests for file converter system
marc-romu Apr 3, 2026
6282446
chore: normalize file encoding to UTF-8 with BOM and refactor test files
marc-romu Apr 3, 2026
bb363a5
docs(testing): add comprehensive Grasshopper test components document…
marc-romu Apr 3, 2026
60bedd3
refactor(json): add colon validation to JsonSchemaObject and JsonSche…
marc-romu Apr 3, 2026
1b94102
docs(testing): expand Grasshopper test components documentation with …
marc-romu Apr 3, 2026
d007a67
fix(file2md): correct typo in default image description prompt
marc-romu Apr 3, 2026
eccdbe8
feat(gemini): add provider icon resource
marc-romu Apr 3, 2026
6cd3d60
feat(icons): add component-specific icons for JSON and Knowledge comp…
marc-romu Apr 3, 2026
8681d1e
docs(formatting): remove trailing whitespace across documentation and…
marc-romu Apr 3, 2026
844c68d
refactor(anthropic): migrate test components to new AICall architectu…
marc-romu Apr 3, 2026
0d8e404
feat(icons): add settings component icons and update provider instant…
marc-romu Apr 3, 2026
bd12bdf
feat(icons): add JSON array/object component icons and update forum c…
marc-romu Apr 3, 2026
61d7f79
feat(gemini): add service tier selection, fix batch API format, add u…
marc-romu Apr 5, 2026
1ead187
feat(error-handling): add structured HTTP error handling across all p…
marc-romu Apr 5, 2026
182b881
fix(json): remove colon validation from schema components and clarify…
marc-romu Apr 6, 2026
47bcbfa
refactor(timeout): centralize timeout configuration and simplify reso…
marc-romu Apr 10, 2026
0e0d2f0
Merge branch 'dev' of https://github.com/architects-toolkit/SmartHopp…
marc-romu Apr 11, 2026
5b55ec4
fix(about-dialog): update Rhinoceros icon attribution link and reorde…
marc-romu Apr 11, 2026
59ae7e7
fix(tool-call): add explicit double cast to timeout calculation for T…
marc-romu Apr 11, 2026
9e709f8
fix(timeout): add explicit casts and simplify timeout calculation for…
marc-romu Apr 11, 2026
b75606a
feat(json): add bracket notation support to JsonGetValue and centrali…
marc-romu Apr 11, 2026
dd52ff6
feat(json): add centralized JSON formatting utility with markdown ext…
marc-romu Apr 11, 2026
c9efb3f
refactor(chat): add clarifying comment for turn completion state tran…
marc-romu Apr 11, 2026
1b5c23e
refactor(gemini): normalize whitespace in ApplySafetySettings method
marc-romu Apr 12, 2026
dcb7504
refactor(ai-settings): remove trailing whitespace from AISettingsComp…
marc-romu Apr 12, 2026
72faf62
fix(test-components): replace placeholder GUIDs with unique identifie…
marc-romu Apr 18, 2026
db08db0
refactor(gemini-batch): replace Call/Decode pipeline with direct HTTP…
marc-romu Apr 18, 2026
4d2cdf8
fix(text2boolean): simplify fallback handling for non-parseable strin…
marc-romu Apr 20, 2026
78dbbdc
feat(timeout): introduce centralized TimeoutDefaults and unify fallba…
marc-romu Apr 25, 2026
b5c7bdd
fix(text2boolean): eliminate triple-decode in batch mode and restore …
marc-romu Apr 25, 2026
cb6a123
refactor(changelog): document breaking change in AISettingsComponent …
marc-romu Apr 25, 2026
74a08fd
refactor(gemini): eliminate duplicate encoding logic in full-request …
marc-romu Apr 25, 2026
a1c94b5
refactor(http-errors): unify HTTP error classification across streami…
marc-romu Apr 25, 2026
4d08924
fix(image-interaction): throw ArgumentException when SetResult receiv…
marc-romu Apr 25, 2026
fe6a443
feat(model-registry): refresh AI model catalog across all providers w…
marc-romu Apr 25, 2026
ad1950c
fix(openai): always send reasoning_effort for o-series/gpt-5 models r…
marc-romu Apr 25, 2026
303f126
fix(changelog): correct tool and component name typos in v0.9.0 break…
marc-romu Apr 25, 2026
7fd125c
Merge branch 'dev' into feature/2.0.0-text2json
marc-romu Apr 25, 2026
a6cfc8d
feat: input and output components
marc-romu Apr 26, 2026
5a5ec92
refactor: remove orphaned `boolean_classify` AI tool and `BooleanClas…
marc-romu Apr 26, 2026
d7cd3b4
feat(ai2-components): add fallback inputs to AI2Boolean/Integer/Numbe…
marc-romu Apr 26, 2026
4efd1ff
feat(ai2-components): add fallback input and robust JSON extraction t…
marc-romu Apr 26, 2026
52be3b7
refactor(ai-input): rename AIInstructionsComponent to AIPromptComponent
marc-romu Apr 26, 2026
283284b
refactor(ai-input): centralize output registration in AIInputAdapterB…
marc-romu Apr 26, 2026
0bfa37a
fix: address review feedback on workflow step ref, file I/O error han…
devin-ai-integration[bot] Apr 26, 2026
8748ff7
feat(list-io): add typed list input/output adapters with unified Outp…
marc-romu Apr 26, 2026
66d425b
docs(architecture): fix markdown formatting and whitespace in AIInput…
marc-romu Apr 26, 2026
f199e57
chore: normalize file encoding to UTF-8 with BOM and complete license…
marc-romu Apr 26, 2026
42f8e48
docs(architecture): add ComponentBase documentation for AI input/outp…
marc-romu Apr 26, 2026
460ffe3
docs(architecture): document ComponentBase deep refactor with IProvid…
marc-romu Apr 27, 2026
fe4b3bf
refactor(selecting): extract selection logic into SelectingSupport he…
marc-romu Apr 27, 2026
b5de29a
refactor(batch-state): extract batch/metrics fields into BatchRunStat…
marc-romu Apr 27, 2026
63ff5ae
refactor(state): replace direct persistentOutputs access with GetPers…
marc-romu Apr 27, 2026
113ce3d
refactor(componentbase): new tool-result wrapper and component-base o…
marc-romu Apr 27, 2026
fee0cd5
Merge remote-tracking branch 'public/dev' into feature/2.0.0-text2json
marc-romu May 3, 2026
464c949
ci: automate license header updates on PRs
marc-romu May 3, 2026
b5d7f39
chore(ci): update license headers
github-actions[bot] May 3, 2026
f32e61f
Merge remote-tracking branch 'public/main' into feature/2.0.0-text2json
marc-romu May 3, 2026
4ed41fd
refactor: reorder AICapability bit positions, normalize capability or…
marc-romu May 3, 2026
81cb2c3
Merge branch 'dev' into feature/2.0.0-text2json
marc-romu May 3, 2026
36f815d
chore: update StronglyTypedResourceBuilder version from 16.0.0.0 to 1…
marc-romu May 3, 2026
95e05d7
refactor: add Default capabilities to gpt-image-2 model and discourag…
marc-romu May 3, 2026
6195971
Merge remote-tracking branch 'public/main' into feature/2.0.0-text2json
marc-romu May 4, 2026
1c079af
feat: add Gemini 3.1 preview models and implement automated model man…
marc-romu May 4, 2026
cb4e831
feat: add Created and Pricing properties to AIModelCapabilities with …
marc-romu May 4, 2026
19f5602
refactor: simplify manifest change detection and add success conditio…
marc-romu May 4, 2026
ed14ac3
test: add comprehensive unit tests for file converters, AIReturn metr…
marc-romu May 4, 2026
305b8a9
chore: update model verification template description
marc-romu May 4, 2026
7852c68
Merge branch 'dev' into feature/2.0.0-text2json
marc-romu May 4, 2026
5cc0120
chore(ci): update license headers
github-actions[bot] May 4, 2026
fad86da
test: remove redundant Success assertions and fix validation tests in…
marc-romu May 4, 2026
b01a9c8
feat(aitools): add configurable HTML readability modes and link/image…
marc-romu May 12, 2026
6f13ed6
chore(ci): update license headers
github-actions[bot] May 12, 2026
b7ae381
chore(ci): extend PR blocking workflow to include hotfix and release …
marc-romu May 13, 2026
754054a
chore(ci): extend model verification template workflow to include dev…
marc-romu May 13, 2026
00a0c8a
chore(ci): extend version main release workflow to include main-*, ho…
marc-romu May 13, 2026
59273db
chore(ci): extend workflow branch triggers to include main-*, dev-*, …
marc-romu May 13, 2026
ca3ef54
chore(ci): add Gemini provider support and DEV.md provider model sync…
marc-romu May 13, 2026
db3e536
docs(readme): add Trademark and Logo Usage Policy (#473)
devin-ai-integration[bot] May 14, 2026
ea0235c
fix(aioutput): skip system text interactions when merging to prevent …
marc-romu May 22, 2026
5cbaf91
test(providers): standardize encoding tests to use System and User me…
marc-romu May 22, 2026
41eacaa
refactor(ai-request-base): add toolFilter parameter to Initialize method
marc-romu May 22, 2026
04c9d6d
refactor(knowledge): remove HTML Readability parameter from File2Md, …
marc-romu May 22, 2026
d1559e8
test(providers): expand provider tests to comprehensive multi-scenari…
marc-romu May 23, 2026
f83dd6d
test(providers): add AIReturnSnapshot and metrics output to all provi…
marc-romu May 23, 2026
f7eefd5
Add GhJSON diff and patch AI tools and Grasshopper components
May 13, 2026
7b72683
chore(ci): update license headers
github-actions[bot] May 13, 2026
62bfe09
Merge branch 'feature/2.0.0-text2json' of https://github.com/architec…
marc-romu May 23, 2026
c407031
Merge remote-tracking branch 'public/dev' into feature/2.0.0-text2json
marc-romu May 23, 2026
b97e42c
chore(ci): update license headers
github-actions[bot] May 23, 2026
8cfe495
chore(ci): add Gemini provider labels and update documentation labeler
marc-romu May 23, 2026
312c9f8
chore(ci): normalize BOM encoding across test and component files
marc-romu May 23, 2026
0394b78
Merge branch 'feature/2.0.0-text2json' of https://github.com/architec…
marc-romu May 23, 2026
e8eb8e8
chore(test): remove trailing whitespace from provider decode test com…
marc-romu May 23, 2026
a8d2796
refactor(test): migrate batch test components to use IAIBatchProvider…
marc-romu May 23, 2026
e7afa61
ci(provider-tests): add provider cancellation test components
marc-romu May 23, 2026
3fe4ad3
refactor(test): improve provider test components to use JSON parsing …
marc-romu May 23, 2026
e926987
refactor(test): remove batch and vision test components for provider …
marc-romu May 23, 2026
c0548bc
refactor(openrouter): add vision input support via OpenAI-compatible …
marc-romu May 23, 2026
075cd5e
feat(providers): default OpenAI to Responses API and cross-provider r…
marc-romu May 24, 2026
b8b3b9d
feat(openai): add Responses API streaming support and tool format con…
marc-romu May 24, 2026
34587fa
fix(provider): several providers fixes
marc-romu May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
25 changes: 24 additions & 1 deletion .github/ISSUE_TEMPLATE/model-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ body:
id: provider-model
attributes:
label: Provider / Model
description: Select the provider/model pair. Use type-to-filter to search. Only non-deprecated models are listed. This list is auto-updated from `*ProviderModels.cs`.
description: Select the provider/model pair. Only non-deprecated models are listed. This list is auto-updated from registered models in SmartHopper providers.
options:
# AUTO-GENERATED-MODEL-OPTIONS-START β€” updated by tools/Update-ModelVerificationTemplate.ps1
- Anthropic / claude-sonnet-4-6
Expand All @@ -66,6 +66,29 @@ body:
- Anthropic / claude-sonnet-4-5-20250929
- DeepSeek / deepseek-v4-flash
- DeepSeek / deepseek-v4-pro
- Gemini / lyria-3-clip-preview
- Gemini / lyria-3-pro-preview
- Gemini / gemma-4-26b-a4b-it
- Gemini / gemma-4-31b-it
- Gemini / gemini-3.1-flash-lite-preview
- Gemini / gemini-3.1-flash-image-preview
- Gemini / gemini-3.1-pro-preview
- Gemini / gemini-3.1-pro-preview-customtools
- Gemini / gemini-3-flash-preview
- Gemini / gemini-3-pro-image-preview
- Gemini / gemini-2.5-flash-image
- Gemini / gemini-2.5-flash-lite-preview-09-2025
- Gemini / gemini-2.5-flash-lite
- Gemini / gemini-2.5-flash
- Gemini / gemini-2.5-pro
- Gemini / gemma-3n-e2b-it
- Gemini / gemma-3n-e4b-it
- Gemini / gemini-2.5-pro-preview
- Gemini / gemini-2.5-pro-preview-05-06
- Gemini / gemma-3-4b-it
- Gemini / gemma-3-12b-it
- Gemini / gemma-3-27b-it
- Gemini / gemma-2-27b-it
- MistralAI / mistral-small-2603
- MistralAI / mistral-medium-3-5
- MistralAI / ministral-3b-2512
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/ai/fetch-models/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ description: 'Fetch model metadata from OpenRouter and update *ProviderModels.cs

inputs:
provider:
description: 'Provider identifier (e.g. OpenAI, MistralAI, Anthropic, OpenRouter, DeepSeek)'
description: 'Provider identifier (e.g. OpenAI, MistralAI, Anthropic, OpenRouter, DeepSeek, Gemini)'
required: true
api-key:
description: 'OpenRouter API key (used as the single source of truth for all providers)'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ runs:
## Technical Requirements
- Rhino 8.24 or above is required
- Windows 10/11 or macOS
- Valid API keys for MistralAI, OpenAI, DeepSeek, Anthropic or OpenRouter
- Valid API keys for MistralAI, OpenAI, DeepSeek, Anthropic, OpenRouter or Gemini

## Important Notes
${STAGE_LINE}
Expand Down
98 changes: 33 additions & 65 deletions .github/actions/milestone/assign-pr/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,19 @@ inputs:
required: false
default: 'true'

outputs:
milestone-number:
description: 'The assigned milestone number'
value: ${{ steps.create-or-get.outputs.milestone-number }}
milestone-title:
description: 'The assigned milestone title'
value: ${{ steps.create-or-get.outputs.milestone-title }}

runs:
using: 'composite'
steps:
- name: Read version and assign to milestone
- name: Read version from Solution.props
id: read-version
uses: actions/github-script@v7
with:
github-token: ${{ inputs.token }}
Expand Down Expand Up @@ -76,86 +85,45 @@ runs:
processedVersion = processedVersion.replace('-dev', '-alpha');

console.log('Processed version for milestone:', processedVersion);

// Find milestone with matching title β€” paginate to walk through ALL
// milestones (default page size is 30, and the repo has more than that).
const milestones = await github.paginate(github.rest.issues.listMilestones, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all', // Include both open and closed milestones
per_page: 100
});

let targetMilestone = milestones.find(milestone => milestone.title === processedVersion);

if (!targetMilestone) {
console.log(`No milestone found with title: ${processedVersion}`);
console.log('Available milestones:', milestones.map(m => m.title));

// Create the milestone if it doesn't exist
console.log(`Creating new milestone: ${processedVersion}`);
try {
const { data: newMilestone } = await github.rest.issues.createMilestone({
owner: context.repo.owner,
repo: context.repo.repo,
title: processedVersion,
description: `Milestone for version ${processedVersion}`,
state: 'open'
});

targetMilestone = newMilestone;
console.log(`Successfully created milestone: ${targetMilestone.title}`);

} catch (error) {
// GitHub returns 422 with error code "already_exists" when a milestone
// with the same title already exists. This can happen if pagination
// missed it or another job created it concurrently β€” recover by
// re-listing and picking up the existing one instead of failing.
const msg = error.message || '';
if (error.status === 422 && /already[_ ]exists/.test(msg)) {
console.log(`ℹ️ Milestone ${processedVersion} already exists β€” re-listing to find it`);
const refreshed = await github.paginate(github.rest.issues.listMilestones, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: 100
});
targetMilestone = refreshed.find(m => m.title === processedVersion);
if (!targetMilestone) {
core.setFailed(`Milestone ${processedVersion} reported as already_exists but could not be found after re-listing.`);
return;
}
console.log(`Found existing milestone after re-list: ${targetMilestone.title} (${targetMilestone.state})`);
} else {
console.error('Error creating milestone:', error);
core.setFailed(`Failed to create milestone: ${error.message}`);
return;
}
}
} else {
console.log(`Found existing milestone: ${targetMilestone.title} (${targetMilestone.state})`);
}

// Assign PR to milestone
core.setOutput('milestone-title', processedVersion);

- name: Create or get milestone
id: create-or-get
if: steps.read-version.outputs.milestone-title != ''
uses: ./.github/actions/milestone/create-or-get
with:
title: ${{ steps.read-version.outputs.milestone-title }}
description: 'Milestone for version ${{ steps.read-version.outputs.milestone-title }}'
state: 'open'
token: ${{ inputs.token }}

- name: Assign PR to milestone
if: steps.create-or-get.outputs.milestone-number != ''
uses: actions/github-script@v7
with:
github-token: ${{ inputs.token }}
script: |
const prNumber = parseInt('${{ inputs.pr-number }}', 10);
const milestoneNumber = parseInt('${{ steps.create-or-get.outputs.milestone-number }}', 10);
const milestoneTitle = '${{ steps.create-or-get.outputs.milestone-title }}';

try {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
milestone: targetMilestone.number
milestone: milestoneNumber
});

console.log(`Successfully assigned PR #${prNumber} to milestone "${targetMilestone.title}"`);
console.log(`Successfully assigned PR #${prNumber} to milestone "${milestoneTitle}"`);

// Add a comment to the PR if requested
if ('${{ inputs.comment-on-pr }}' === 'true') {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `🏷️ This PR has been automatically assigned to milestone **${targetMilestone.title}** based on the version in \`Solution.props\`.`
body: `🏷️ This PR has been automatically assigned to milestone **${milestoneTitle}** based on the version in \`Solution.props\`.`
});
}

Expand Down
103 changes: 103 additions & 0 deletions .github/actions/milestone/create-or-get/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: 'Create or Get Milestone'
description: 'Creates a milestone if it does not exist, or returns the existing one. Handles already_exists errors gracefully.'

inputs:
title:
description: 'Milestone title to create or find'
required: true
description:
description: 'Milestone description (only used when creating new)'
required: false
default: ''
state:
description: 'Milestone state (open/closed), only used when creating new'
required: false
default: 'open'
token:
description: 'GitHub token with issues:write permission'
required: true

outputs:
milestone-number:
description: 'The milestone number (for API usage)'
value: ${{ steps.create-or-get.outputs.milestone-number }}
milestone-title:
description: 'The milestone title'
value: ${{ steps.create-or-get.outputs.milestone-title }}
created:
description: 'Whether the milestone was newly created (true) or already existed (false)'
value: ${{ steps.create-or-get.outputs.created }}

runs:
using: composite
steps:
- name: Create or get milestone
id: create-or-get
uses: actions/github-script@v7
with:
github-token: ${{ inputs.token }}
script: |
const title = '${{ inputs.title }}';
const description = '${{ inputs.description }}' || `Milestone for ${title}`;
const state = '${{ inputs.state }}';

// List milestones to check if it exists
const { data: milestones } = await github.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: 100
});

let milestone = milestones.find(m => m.title === title);

if (milestone) {
console.log(`Found existing milestone: ${milestone.title} (#${milestone.number})`);
core.setOutput('milestone-number', milestone.number);
core.setOutput('milestone-title', milestone.title);
core.setOutput('created', 'false');
return;
}

// Try to create the milestone
try {
const { data: newMilestone } = await github.rest.issues.createMilestone({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
description: description,
state: state
});

console.log(`Created new milestone: ${newMilestone.title} (#${newMilestone.number})`);
core.setOutput('milestone-number', newMilestone.number);
core.setOutput('milestone-title', newMilestone.title);
core.setOutput('created', 'true');

} catch (error) {
// Handle case where milestone was created by another concurrent process
if (error.status === 422 && error.message && error.message.includes('already_exists')) {
console.log(`Milestone "${title}" already exists (concurrent creation). Fetching...`);

// Re-fetch to get the existing milestone
const { data: allMilestones } = await github.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: 100
});

milestone = allMilestones.find(m => m.title === title);

if (milestone) {
console.log(`Found existing milestone: ${milestone.title} (#${milestone.number})`);
core.setOutput('milestone-number', milestone.number);
core.setOutput('milestone-title', milestone.title);
core.setOutput('created', 'false');
} else {
core.setFailed(`Milestone exists but could not be found: ${error.message}`);
}
} else {
core.setFailed(`Failed to create milestone: ${error.message}`);
}
}
2 changes: 2 additions & 0 deletions .github/issue-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- '(?i)\bDeepSeek\b'
"provider: OpenRouter":
- '(?i)\bOpenRouter\b'
"provider: Gemini":
- '(?i)\bGemini\b'

"os: Windows":
- '(?i)\bWindows\b'
Expand Down
5 changes: 4 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
- changed-files:
- any-glob-to-any-file: 'src/SmartHopper.Providers.OpenRouter/**'

"provider: Gemini":
- changed-files:
- any-glob-to-any-file: 'src/SmartHopper.Providers.Gemini/**'

# --- Scope labels ---
"scope: UI":
- changed-files:
Expand Down Expand Up @@ -68,6 +72,5 @@ ci:
documentation:
- changed-files:
- any-glob-to-any-file:
- '**/*.md'
- 'docs/**'
- '.github/ISSUE_TEMPLATE/**'
3 changes: 3 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
- name: "provider: Anthropic"
color: "000"
description: "Issues related to the Anthropic provider"
- name: "provider: Gemini"
color: "000"
description: "Issues related to the Gemini provider"

# Scope Labels
- name: "scope: Settings"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/chore-update-contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ on:
branches:
- main
- dev
- 'main-*'
- 'dev-*'
- 'release/**'
- 'hotfix/**'

Expand All @@ -45,8 +47,8 @@ jobs:

- name: Configure Git
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git config --local user.email "${{ vars.SMARTHOPPER_BOT_EMAIL }}"
git config --local user.name "${{ vars.SMARTHOPPER_BOT_NAME }}"

- name: Get target branch and setup
run: |
Expand Down
Loading
Loading