diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c5ac6223..b3dd90017 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,9 @@ permissions: concurrency: group: ci-${{ github.ref }} - cancel-in-progress: true + # Keep main CI runs alive so every successful main merge can trigger production deploy. + # Pull request runs still cancel superseded commits for faster feedback. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: changes: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0b6f17e0d..82e1905b2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,6 +26,8 @@ concurrency: cancel-in-progress: false jobs: + # Gate only on the GitHub Actions CI workflow result. SonarCloud is a + # separate GitHub App check and must not suppress production deploys. deploy: if: >- github.event_name == 'workflow_dispatch' || diff --git a/packages/shared/src/constants/ai-services.ts b/packages/shared/src/constants/ai-services.ts index c03bf7be6..d26710fd2 100644 --- a/packages/shared/src/constants/ai-services.ts +++ b/packages/shared/src/constants/ai-services.ts @@ -177,6 +177,60 @@ export interface PlatformAIModel { unifiedApiModelId: string | null; } +const ALL_AGENT_SCOPES: ModelAllowedScope[] = ['workspace', 'project', 'top-level']; +const WORKSPACE_PROJECT_SCOPES: ModelAllowedScope[] = ['workspace', 'project']; +const WORKSPACE_ONLY_SCOPES: ModelAllowedScope[] = ['workspace']; + +type ModelDefinition = Omit & { + allowedScopes?: ModelAllowedScope[]; +}; + +function workersAIModel(input: ModelDefinition): PlatformAIModel { + return { + ...input, + provider: 'workers-ai', + allowedScopes: input.allowedScopes ?? WORKSPACE_ONLY_SCOPES, + unifiedApiModelId: null, + }; +} + +function anthropicModel(input: ModelDefinition): PlatformAIModel { + return { + ...input, + provider: 'anthropic', + allowedScopes: input.allowedScopes ?? ALL_AGENT_SCOPES, + unifiedApiModelId: 'anthropic/' + input.id, + }; +} + +function openAIModel(input: ModelDefinition): PlatformAIModel { + return { + ...input, + provider: 'openai', + allowedScopes: input.allowedScopes ?? WORKSPACE_PROJECT_SCOPES, + unifiedApiModelId: 'openai/' + input.id, + }; +} + +const OPENAI_CODEX_PREMIUM_PROFILE = { + tier: 'premium', + costPer1kInputTokens: 0.00175, + costPer1kOutputTokens: 0.014, + contextWindow: 400000, + toolCallSupport: 'excellent', + intendedRole: 'workspace-agent', + fallbackGroup: 'openai-premium', +} satisfies Pick< + ModelDefinition, + | 'tier' + | 'costPer1kInputTokens' + | 'costPer1kOutputTokens' + | 'contextWindow' + | 'toolCallSupport' + | 'intendedRole' + | 'fallbackGroup' +>; + /** Models available through the SAM Platform AI proxy. * This is the single source of truth — the DEFAULT_AI_PROXY_ALLOWED_MODELS * string and the UI dropdown both derive from this list. @@ -184,11 +238,10 @@ export interface PlatformAIModel { * models routed through Cloudflare AI Gateway with Unified Billing. */ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ // --- Workers AI (Cloudflare-billed low-cost tier) --- - { + workersAIModel({ id: '@cf/meta/llama-4-scout-17b-16e-instruct', label: 'Llama 4 Scout 17B', isDefault: true, - provider: 'workers-ai', tier: 'low-cost', costPer1kInputTokens: 0.00027, costPer1kOutputTokens: 0.00085, @@ -196,13 +249,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'limited', intendedRole: 'utility', fallbackGroup: 'workers-general', - allowedScopes: ['workspace'], - unifiedApiModelId: null, - }, - { + }), + workersAIModel({ id: '@cf/qwen/qwen3-30b-a3b-fp8', label: 'Qwen 3 30B', - provider: 'workers-ai', tier: 'low-cost', costPer1kInputTokens: 0.000051, costPer1kOutputTokens: 0.000335, @@ -210,13 +260,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'good', intendedRole: 'workspace-agent', fallbackGroup: 'workers-coding', - allowedScopes: ['workspace'], - unifiedApiModelId: null, - }, - { + }), + workersAIModel({ id: '@cf/qwen/qwen2.5-coder-32b-instruct', label: 'Qwen 2.5 Coder 32B', - provider: 'workers-ai', tier: 'low-cost', costPer1kInputTokens: 0.00066, costPer1kOutputTokens: 0.001, @@ -224,13 +271,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'good', intendedRole: 'workspace-agent', fallbackGroup: 'workers-coding', - allowedScopes: ['workspace'], - unifiedApiModelId: null, - }, - { + }), + workersAIModel({ id: '@cf/google/gemma-4-26b-a4b-it', label: 'Gemma 4 26B', - provider: 'workers-ai', tier: 'low-cost', costPer1kInputTokens: 0.0001, costPer1kOutputTokens: 0.0003, @@ -238,13 +282,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'good', intendedRole: 'workspace-agent', fallbackGroup: 'workers-coding', - allowedScopes: ['workspace'], - unifiedApiModelId: null, - }, - { + }), + workersAIModel({ id: '@cf/google/gemma-3-12b-it', label: 'Gemma 3 12B', - provider: 'workers-ai', tier: 'low-cost', costPer1kInputTokens: 0.00035, costPer1kOutputTokens: 0.00056, @@ -252,14 +293,11 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'none', intendedRole: 'utility', fallbackGroup: 'workers-utility', - allowedScopes: ['workspace'], - unifiedApiModelId: null, - }, + }), // --- Anthropic (via AI Gateway) --- - { + anthropicModel({ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', - provider: 'anthropic', tier: 'standard', costPer1kInputTokens: 0.001, costPer1kOutputTokens: 0.005, @@ -267,13 +305,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'utility', fallbackGroup: 'anthropic-fast', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-haiku-4-5-20251001', - }, - { + }), + anthropicModel({ id: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4', - provider: 'anthropic', tier: 'standard', costPer1kInputTokens: 0.003, costPer1kOutputTokens: 0.015, @@ -281,13 +316,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'anthropic-standard', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-sonnet-4-20250514', - }, - { + }), + anthropicModel({ id: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', - provider: 'anthropic', tier: 'standard', costPer1kInputTokens: 0.003, costPer1kOutputTokens: 0.015, @@ -295,13 +327,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'anthropic-standard', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-sonnet-4-5-20250929', - }, - { + }), + anthropicModel({ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', - provider: 'anthropic', tier: 'standard', costPer1kInputTokens: 0.003, costPer1kOutputTokens: 0.015, @@ -309,13 +338,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'any', fallbackGroup: 'anthropic-standard', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-sonnet-4-6', - }, - { + }), + anthropicModel({ id: 'claude-opus-4-6', label: 'Claude Opus 4.6', - provider: 'anthropic', tier: 'premium', costPer1kInputTokens: 0.005, costPer1kOutputTokens: 0.025, @@ -323,13 +349,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'sam-agent', fallbackGroup: 'anthropic-premium', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-opus-4-6', - }, - { + }), + anthropicModel({ id: 'claude-opus-4-7', label: 'Claude Opus 4.7', - provider: 'anthropic', tier: 'premium', costPer1kInputTokens: 0.005, costPer1kOutputTokens: 0.025, @@ -337,13 +360,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'sam-agent', fallbackGroup: 'anthropic-premium', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-opus-4-7', - }, - { + }), + anthropicModel({ id: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5', - provider: 'anthropic', tier: 'premium', costPer1kInputTokens: 0.005, costPer1kOutputTokens: 0.025, @@ -351,13 +371,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'anthropic-premium', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-opus-4-5-20251101', - }, - { + }), + anthropicModel({ id: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1', - provider: 'anthropic', tier: 'premium', costPer1kInputTokens: 0.015, costPer1kOutputTokens: 0.075, @@ -365,15 +382,12 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'anthropic-premium', - allowedScopes: ['workspace', 'project', 'top-level'], - unifiedApiModelId: 'anthropic/claude-opus-4-1-20250805', - }, + }), // --- OpenAI (via AI Gateway) --- // GPT-5.5 series (current flagship) - { + openAIModel({ id: 'gpt-5.5-pro', label: 'GPT-5.5 Pro', - provider: 'openai', tier: 'premium', costPer1kInputTokens: 0.03, costPer1kOutputTokens: 0.18, @@ -381,13 +395,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.5-pro', - }, - { + }), + openAIModel({ id: 'gpt-5.5', label: 'GPT-5.5', - provider: 'openai', tier: 'premium', costPer1kInputTokens: 0.005, costPer1kOutputTokens: 0.03, @@ -395,14 +406,11 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.5', - }, + }), // GPT-5.4 series (current) - { + openAIModel({ id: 'gpt-5.4-pro', label: 'GPT-5.4 Pro', - provider: 'openai', tier: 'premium', costPer1kInputTokens: 0.03, costPer1kOutputTokens: 0.18, @@ -410,13 +418,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.4-pro', - }, - { + }), + openAIModel({ id: 'gpt-5.4', label: 'GPT-5.4', - provider: 'openai', tier: 'premium', costPer1kInputTokens: 0.0025, costPer1kOutputTokens: 0.015, @@ -424,13 +429,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.4', - }, - { + }), + openAIModel({ id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.00075, costPer1kOutputTokens: 0.0045, @@ -438,13 +440,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-standard', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.4-mini', - }, - { + }), + openAIModel({ id: 'gpt-5.4-nano', label: 'GPT-5.4 Nano', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.0002, costPer1kOutputTokens: 0.00125, @@ -452,58 +451,28 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'utility', fallbackGroup: 'openai-fast', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.4-nano', - }, + }), // GPT-5.3 Codex (current coding model) - { + openAIModel({ id: 'gpt-5.3-codex', label: 'GPT-5.3 Codex', - provider: 'openai', - tier: 'premium', - costPer1kInputTokens: 0.00175, - costPer1kOutputTokens: 0.014, - contextWindow: 400000, - toolCallSupport: 'excellent', - intendedRole: 'workspace-agent', - fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.3-codex', - }, + ...OPENAI_CODEX_PREMIUM_PROFILE, + }), // GPT-5.2 Codex (deprecating Aug 10, 2026) - { + openAIModel({ id: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', - provider: 'openai', - tier: 'premium', - costPer1kInputTokens: 0.00175, - costPer1kOutputTokens: 0.014, - contextWindow: 400000, - toolCallSupport: 'excellent', - intendedRole: 'workspace-agent', - fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.2-codex', - }, + ...OPENAI_CODEX_PREMIUM_PROFILE, + }), // GPT-5.1 Codex series (deprecating Jul 23, 2026) - { + openAIModel({ id: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', - provider: 'openai', - tier: 'premium', - costPer1kInputTokens: 0.00175, - costPer1kOutputTokens: 0.014, - contextWindow: 400000, - toolCallSupport: 'excellent', - intendedRole: 'workspace-agent', - fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.1-codex-max', - }, - { + ...OPENAI_CODEX_PREMIUM_PROFILE, + }), + openAIModel({ id: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.00075, costPer1kOutputTokens: 0.0045, @@ -511,14 +480,11 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-standard', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5.1-codex-mini', - }, + }), // GPT-5 Mini (deprecating Aug 10, 2026) - { + openAIModel({ id: 'gpt-5-mini', label: 'GPT-5 Mini', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.00025, costPer1kOutputTokens: 0.002, @@ -526,14 +492,11 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-standard', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-5-mini', - }, + }), // Reasoning models (deprecating Oct 23, 2026) - { + openAIModel({ id: 'o4-mini', label: 'O4 Mini', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.00055, costPer1kOutputTokens: 0.0022, @@ -541,13 +504,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-standard', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/o4-mini', - }, - { + }), + openAIModel({ id: 'o3', label: 'O3', - provider: 'openai', tier: 'premium', costPer1kInputTokens: 0.002, costPer1kOutputTokens: 0.008, @@ -555,14 +515,11 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-premium', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/o3', - }, + }), // GPT-4.1 (legacy, still available) - { + openAIModel({ id: 'gpt-4.1', label: 'GPT-4.1', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.002, costPer1kOutputTokens: 0.008, @@ -570,13 +527,10 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'workspace-agent', fallbackGroup: 'openai-standard', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-4.1', - }, - { + }), + openAIModel({ id: 'gpt-4.1-mini', label: 'GPT-4.1 Mini', - provider: 'openai', tier: 'standard', costPer1kInputTokens: 0.0004, costPer1kOutputTokens: 0.0016, @@ -584,9 +538,7 @@ export const PLATFORM_AI_MODELS: PlatformAIModel[] = [ toolCallSupport: 'excellent', intendedRole: 'utility', fallbackGroup: 'openai-fast', - allowedScopes: ['workspace', 'project'], - unifiedApiModelId: 'openai/gpt-4.1-mini', - }, + }), ]; /** KV key for the admin-configured default model. Stored by the admin AI proxy config endpoint. */ diff --git a/prototype-blurred-bg.html b/prototype-blurred-bg.html index d5359120f..5bc4ea3ad 100644 --- a/prototype-blurred-bg.html +++ b/prototype-blurred-bg.html @@ -67,7 +67,7 @@ display: flex; gap: 8px; align-items: flex-end; } .input-area textarea { - flex: 1; resize: none; border: none; outline: none; + flex: 1; resize: none; outline: none; background: rgba(255,255,255,0.06); border: 1px solid rgba(60, 180, 120, 0.15); border-radius: 12px; padding: 12px 16px; @@ -76,7 +76,7 @@ } .input-area textarea::placeholder { color: rgba(255,255,255,0.25); } .input-area button { - width: 44px; height: 44px; border-radius: 12px; border: none; + width: 44px; height: 44px; border-radius: 12px; background: rgba(60, 180, 120, 0.2); border: 1px solid rgba(60, 180, 120, 0.25); color: white; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; diff --git a/sonar-project.properties b/sonar-project.properties index cf62879fe..0b103abc6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,3 +6,10 @@ sonar.go.coverage.reportPaths=packages/cli/coverage.out # test scaffolding (D1 seed helpers, DO stub factories) is intentionally # similar across test suites and does not warrant abstraction. sonar.cpd.exclusions=**/*.config.ts,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx,**/*_test.go,**/tests/**,.github/workflows/**,vitest.coverage.ts,sonar-project.properties + +# packages/cli is currently a standard-library-only Go module. Go does not +# generate go.sum without module dependencies, so suppress the lockfile rule +# for this module file only instead of committing a meaningless placeholder. +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=text:S8566 +sonar.issue.ignore.multicriteria.e1.resourceKey=packages/cli/go.mod diff --git a/tasks/backlog/2026-05-23-fix-sonarcloud-quality-gate.md b/tasks/archive/2026-05-23-fix-sonarcloud-quality-gate.md similarity index 88% rename from tasks/backlog/2026-05-23-fix-sonarcloud-quality-gate.md rename to tasks/archive/2026-05-23-fix-sonarcloud-quality-gate.md index 9f190d37c..71d35b55a 100644 --- a/tasks/backlog/2026-05-23-fix-sonarcloud-quality-gate.md +++ b/tasks/archive/2026-05-23-fix-sonarcloud-quality-gate.md @@ -24,13 +24,13 @@ The PR must not be merged automatically. It should be left ready for human revie ## Implementation Checklist -- [ ] Preserve the do-not-merge constraint in state, task tracking, and PR wording. -- [ ] Update deploy workflow behavior or documentation so production deploy coupling is explicitly to GitHub Actions CI only, not the separate SonarCloud GitHub App check. -- [ ] Refactor `packages/shared/src/constants/ai-services.ts` to remove repeated model metadata blocks without changing exported model values. -- [ ] Re-query or locally inspect Sonar duplication after the refactor; address the next offender if the margin is still too narrow. -- [ ] Inspect Sonar reliability/security-rating issue evidence and fix any small, clear correctness issue found. -- [ ] Run relevant unit/type/lint/build validation. -- [ ] Run required specialist review skills for config/business-logic/test changes. +- [x] Preserve the do-not-merge constraint in state, task tracking, and PR wording. +- [x] Update deploy workflow behavior or documentation so production deploy coupling is explicitly to GitHub Actions CI only, not the separate SonarCloud GitHub App check. +- [x] Refactor `packages/shared/src/constants/ai-services.ts` to remove repeated model metadata blocks without changing exported model values. +- [x] Re-query or locally inspect Sonar duplication after the refactor; address the next offender if the margin is still too narrow. +- [x] Inspect Sonar reliability/security-rating issue evidence and fix any small, clear correctness issue found. +- [x] Run relevant unit/type/lint/build validation. +- [x] Run required specialist review skills for config/business-logic/test changes. - [ ] Check active staging deploy runs, deploy the branch to staging, and verify the workflow/config change. - [ ] Create a PR and stop without merging.