From 5f256cd85682f089f85b9818007ce2a0b659c6f7 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:01:22 +0800 Subject: [PATCH 1/7] docs(ci): define PR quality gates --- .../pr-check-quality-gates/plan.md | 141 ++++++++++++++++++ .../pr-check-quality-gates/spec.md | 122 +++++++++++++++ .../pr-check-quality-gates/tasks.md | 48 ++++++ 3 files changed, 311 insertions(+) create mode 100644 docs/architecture/pr-check-quality-gates/plan.md create mode 100644 docs/architecture/pr-check-quality-gates/spec.md create mode 100644 docs/architecture/pr-check-quality-gates/tasks.md diff --git a/docs/architecture/pr-check-quality-gates/plan.md b/docs/architecture/pr-check-quality-gates/plan.md new file mode 100644 index 000000000..9d2a9f3f0 --- /dev/null +++ b/docs/architecture/pr-check-quality-gates/plan.md @@ -0,0 +1,141 @@ +# PR Check Quality Gates — Implementation Plan + +> Requirements and acceptance criteria are defined in [spec.md](./spec.md), and execution progress is +> recorded in [tasks.md](./tasks.md). + +## 1. Implementation Strategy + +The change is split into three independently reviewable implementation layers after this architecture +record: + +1. Repair the Light OCR compatibility regression discovered while auditing the current full suite. +2. Repair local one-shot test entrypoints and protect them with a static contract test. +3. Replace the monolithic PR workflow with parallel quality gates and protect its topology with a + parsed-YAML contract test. + +The OCR correction is behaviorally separate from CI, but it must land before the full main suite +becomes a required gate so the new gate reflects the intended compatibility contract. + +## 2. Light OCR Compatibility Prerequisite + +`AttachmentCapabilityRouter` must apply the eight-image resource bound only after attachments are +classified as OCR candidates. Images routed directly to a vision-capable model remain image +representations regardless of their position in the turn. + +Focused tests cover: + +- more than eight images routed directly to vision; +- a mixed vision/OCR turn in which only the ninth OCR candidate is rejected; +- the existing explicit and automatic OCR batch cap. + +The retained Light OCR feature specification and follow-up hardening record are updated to distinguish +the OCR resource limit from general vision input behavior. + +## 3. Test Entrypoints + +Package scripts use explicit `vitest run` commands for default, main, renderer, and coverage suites. +`test:watch` stays interactive. The stale local Native SQLite script is removed rather than repaired +because the maintained Memory architecture assigns Node ABI rebuilding to CI. + +A main-process static contract test reads `package.json` and the Windows ARM64 workflow. It asserts the +exact one-shot/watch command semantics, rejects the obsolete Native script, and verifies the maintained +Native Memory test path exists and is referenced. + +## 4. Workflow Topology + +The workflow keeps `main-release-guard` and introduces five always-on quality gates: + +1. `static` +2. `test-main` +3. `test-renderer` +4. `test-native-memory` +5. `build` + +All jobs use the repository-pinned setup actions, Node version, pnpm version, and Ubuntu image. Each job +installs an independent dependency tree to prevent ABI mutation or generated state from leaking between +responsibilities. + +### 4.1 Static + +The static job runs lint, format checking, localization parity, renderer architecture baseline, icon +generation verification, and type checking. It does not build artifacts or run test suites. + +### 4.2 Test Suites + +The main and renderer jobs invoke only their authoritative package scripts. Their names expose the +failing boundary directly in the GitHub Actions UI. + +### 4.3 Native Memory + +The Native Memory job retains this strict order: + +1. Install dependencies. +2. Install and smoke-test DuckDB VSS. +3. Run portable Memory tests, including the internal scope guard. +4. Rebuild SQLite for the Node ABI. +5. Run the encrypted SQLite smoke check. +6. Run the encrypted OCR artifact suite. +7. Run required-Native Memory tests. +8. Run deterministic retrieval evaluation. +9. Run Memory performance checks. +10. Upload the retrieval report even after prior failure. + +No separate scope step precedes `test:memory`, avoiding duplicate work without weakening ownership +validation. + +### 4.4 Build + +The build job runs the canonical `pnpm run build`. Prebuild registry refresh and repeated type checking +remain deliberate parts of that contract. + +## 5. Aggregate Check + +`pr-required` has no checkout or dependency installation. With `if: always()`, it receives the explicit +result of every dependency and fails unless: + +- every always-on quality gate is `success`; and +- `main-release-guard` is `success` for a `main` target or `skipped` for a `dev` target. + +This avoids treating an unexpected skipped job as an implicit success and provides one stable branch +protection target. + +## 6. Workflow Contract Test + +The contract test parses `.github/workflows/prcheck.yml` with the repository's YAML dependency and +asserts: + +- permissions, concurrency, runner images, timeouts, and pinned action references; +- the exact job graph and aggregate dependencies; +- required static, test, Native, and build commands; +- Native command ordering and environment requirements; +- active fail-closed aggregate result checks; +- absence of the single-element matrix, `install:sharp`, redundant Agent evaluation, and duplicated + Memory scope invocation. + +Behavioral CI execution remains the source of truth; the static test prevents structural drift before a +workflow is pushed. + +## 7. Validation and Rollout + +Validation proceeds from focused tests to full local gates: + +- Light OCR routing tests; +- entrypoint and workflow contract tests; +- full default, main, and renderer suites; +- portable Memory tests; +- format, localization, lint, architecture, icon, and type checks; +- canonical build. + +Local validation must not rebuild SQLite for the Node ABI. The first pushed workflow run remains +responsible for Native Memory and Windows ARM64 evidence. After a successful run, repository +administrators may configure only `PR Check / pr-required` as the branch-protection requirement. + +If the canonical build refreshes tracked provider or ACP registries, those generated changes are +reviewed and committed separately so CI architecture changes remain auditable. + +## 8. Rollback + +Each implementation layer is independently revertible. Reverting the workflow restores the previous +job names and coverage but removes complete main/renderer requirements and the aggregate failure +contract. Reverting entrypoint changes restores ambiguous interactive behavior and stale Native script +surface. Neither rollback changes application data or public APIs. diff --git a/docs/architecture/pr-check-quality-gates/spec.md b/docs/architecture/pr-check-quality-gates/spec.md new file mode 100644 index 000000000..baff0835b --- /dev/null +++ b/docs/architecture/pr-check-quality-gates/spec.md @@ -0,0 +1,122 @@ +# PR Check Quality Gates — Specification + +> Status: **planned** +> +> Classification: **architecture** +> +> GitHub issue: **not requested** + +This document defines the maintained pull-request quality gates and their local test-entrypoint +contracts. The implementation design is described in [plan.md](./plan.md), and execution progress is +tracked in [tasks.md](./tasks.md). + +## 1. Purpose + +The existing PR workflow combines unrelated checks in one generic build job, omits the complete main +and renderer test suites, and contains stale or ineffective commands. This makes failures harder to +diagnose and allows changes to merge without exercising the repository's primary test boundaries. + +The new architecture separates independent evidence into named jobs, retains the specialized Native +Memory sequence, and exposes one fail-closed aggregate check for branch protection. + +## 2. Goals + +- Make the default and scoped Vitest commands explicitly one-shot in non-interactive and interactive + environments. +- Keep local test commands aligned with maintained test locations and Native SQLite ownership. +- Run static analysis, main tests, renderer tests, Native Memory validation, and the production + prebuild/build contract as independent PR jobs. +- Preserve required Native Memory ordering across portable tests, Node ABI rebuild, smoke validation, + Native suites, evaluation, performance checks, and report upload. +- Make one lightweight aggregate job fail when any required quality gate fails, is cancelled, or is + unexpectedly skipped. +- Keep workflow behavior statically testable so job topology and critical commands cannot silently + drift. + +## 3. Acceptance Criteria + +### AC-1 — One-Shot Test Entrypoints + +- `test`, `test:main`, `test:renderer`, and `test:coverage` use `vitest run`. +- `test:watch` retains explicit watch behavior. +- The obsolete `test:main:native-sqlite` script is absent. +- Native SQLite invocation remains workflow-owned because rebuilding for the Node ABI can replace the + Electron-compatible binding in a developer dependency tree. +- The Windows ARM64 workflow references the maintained Native Memory test path. +- A static contract test covers these script and workflow-path requirements. + +### AC-2 — Parallel PR Gates + +- The workflow always schedules `static`, `test-main`, `test-renderer`, `test-native-memory`, and + `build` for pull requests targeting `main` or `dev`. +- `static` runs lint, formatting, localization parity, renderer architecture baseline, icon + generation, and type checking. +- `test-main` and `test-renderer` run their complete one-shot suites. +- `build` runs the canonical `pnpm run build`, including its prebuild data refresh and full local + build contract. +- No single-element matrix, path filter, renderer sharding, packages job, dependency-cache policy + change, or `install:sharp` step is introduced. + +### AC-3 — Native Memory Gate + +- `test-native-memory` runs the portable Memory command before rebuilding SQLite for the Node ABI. +- The workflow verifies DuckDB VSS before portable Memory tests. +- The rebuilt SQLite binding passes the encrypted smoke test before any required-Native suite. +- Encrypted OCR artifact, Native Memory, retrieval evaluation, and performance suites remain covered. +- The workflow does not run `test:memory:scope` separately because `test:memory` owns that guard. +- The retrieval report is uploaded with `if: always()`. + +### AC-4 — Fail-Closed Aggregation + +- `pr-required` declares every quality gate and `main-release-guard` in `needs`. +- `pr-required` uses `if: always()` and actively validates each dependency result. +- `main-release-guard` must be `success` for pull requests targeting `main` and may be `skipped` only + for pull requests targeting `dev`. +- A failed, cancelled, or unexpectedly skipped required quality gate makes `pr-required` fail. +- Branch protection can use `PR Check / pr-required` without depending on individual job names. + +### AC-5 — Workflow Safety and Maintainability + +- Workflow permissions are explicitly read-only for repository contents. +- Pull-request concurrency cancels obsolete runs for the same pull request. +- Every job has a timeout. +- Actions remain pinned to full commit SHAs and jobs remain on `ubuntu-24.04`. +- Checkout does not persist credentials. +- A parsed-YAML contract test covers topology, commands, Native ordering, permissions, concurrency, + aggregation, and prohibited legacy steps. + +## 4. Constraints + +- Do not modify application public APIs, stored data, or runtime behavior as part of the CI + architecture. +- Do not add a packages job when the repository has no packages test boundary. +- Do not add path-based job skipping or test sharding before the always-on baseline is established. +- Do not enable pnpm caching or change lockfile policy while `pnpm-lock.yaml` is not tracked. +- Do not rebuild the Native SQLite dependency during local validation. +- Do not change remote branch-protection rules in this change. +- Do not create or sync a GitHub issue for this architecture record. + +## 5. Non-Goals + +- Reducing runner-minute usage through selective execution. +- Splitting renderer tests into shards. +- Changing dependency installation or generated-registry policies. +- Replacing the existing test framework or build pipeline. +- Proving Windows ARM64 or Node-ABI Native behavior outside their owning workflows. + +## 6. Compatibility and Risks + +- The one-shot script change intentionally removes implicit watch behavior from developer-facing test + commands; `test:watch` remains the explicit interactive entrypoint. +- Parallel jobs repeat dependency installation and increase aggregate runner minutes. They reduce + wall-clock latency and isolate failures, but optimization is deferred until a reliable baseline + exists. +- `build` intentionally repeats type checking already performed by `static` because the canonical + build command is itself a maintained release contract. +- Native workflow behavior remains ABI-sensitive and must be verified after a push; local validation + covers only portable Memory behavior and static workflow contracts. + +## 7. Open Questions + +None. The job topology, command ownership, failure semantics, and deferred optimizations are fixed for +this increment. diff --git a/docs/architecture/pr-check-quality-gates/tasks.md b/docs/architecture/pr-check-quality-gates/tasks.md new file mode 100644 index 000000000..1a154561c --- /dev/null +++ b/docs/architecture/pr-check-quality-gates/tasks.md @@ -0,0 +1,48 @@ +# PR Check Quality Gates — Tasks + +> Requirements are defined in [spec.md](./spec.md), and the implementation design is described in +> [plan.md](./plan.md). + +## Architecture Record + +- [x] Define the quality-gate responsibilities and always-on topology. +- [x] Define one-shot test command and Native ABI ownership contracts. +- [x] Define fail-closed aggregate semantics. +- [x] Record deferred caching, filtering, sharding, and branch-protection work. +- [x] Record the decision not to create or sync a GitHub issue. + +## Light OCR Compatibility + +- [ ] Limit only actual OCR candidates to eight images. +- [ ] Preserve unrestricted image representation for vision-routed attachments. +- [ ] Add pure-vision and mixed vision/OCR boundary tests. +- [ ] Update the retained Light OCR compatibility documentation. + +## Test Entrypoints + +- [ ] Make default, main, renderer, and coverage commands explicitly one-shot. +- [ ] Preserve the explicit watch command. +- [ ] Remove the obsolete local Native SQLite entrypoint. +- [ ] Repair the Windows ARM64 Native Memory test path. +- [ ] Update test documentation. +- [ ] Add a static entrypoint and workflow-path contract test. + +## PR Workflow + +- [ ] Add read-only permissions, PR concurrency cancellation, and job timeouts. +- [ ] Split static, main, renderer, Native Memory, and build responsibilities. +- [ ] Remove the single-element matrix, ineffective Sharp step, redundant Agent evaluation, and + duplicated Memory scope step. +- [ ] Add a fail-closed `pr-required` aggregate job. +- [ ] Add the parsed-YAML workflow contract test. +- [ ] Update maintained Native Memory architecture references to the new job name. + +## Validation + +- [ ] Run focused Light OCR routing tests. +- [ ] Run entrypoint and workflow contract tests. +- [ ] Run default, main, and renderer test commands. +- [ ] Run portable Memory tests without a local Node ABI rebuild. +- [ ] Run format, localization, lint, architecture, icon, and type checks. +- [ ] Run the canonical build and review generated registry changes. +- [ ] Verify Native Memory and Windows ARM64 workflows after a future push. From f1bf73055d90c53e06d0cadf547bb892e8f4b2a5 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:03:05 +0800 Subject: [PATCH 2/7] fix(ocr): keep vision images unrestricted --- .../pr-check-quality-gates/tasks.md | 10 ++--- docs/features/light-ocr-integration/spec.md | 3 +- .../light-ocr-follow-up-hardening/spec.md | 13 +++--- src/main/ocr/attachmentCapabilityRouter.ts | 18 ++------ .../ocr/attachmentCapabilityRouter.test.ts | 43 +++++++++++++++---- 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/docs/architecture/pr-check-quality-gates/tasks.md b/docs/architecture/pr-check-quality-gates/tasks.md index 1a154561c..0ca477237 100644 --- a/docs/architecture/pr-check-quality-gates/tasks.md +++ b/docs/architecture/pr-check-quality-gates/tasks.md @@ -13,10 +13,10 @@ ## Light OCR Compatibility -- [ ] Limit only actual OCR candidates to eight images. -- [ ] Preserve unrestricted image representation for vision-routed attachments. -- [ ] Add pure-vision and mixed vision/OCR boundary tests. -- [ ] Update the retained Light OCR compatibility documentation. +- [x] Limit only actual OCR candidates to eight images. +- [x] Preserve unrestricted image representation for vision-routed attachments. +- [x] Add pure-vision and mixed vision/OCR boundary tests. +- [x] Update the retained Light OCR compatibility documentation. ## Test Entrypoints @@ -39,7 +39,7 @@ ## Validation -- [ ] Run focused Light OCR routing tests. +- [x] Run focused Light OCR routing tests. - [ ] Run entrypoint and workflow contract tests. - [ ] Run default, main, and renderer test commands. - [ ] Run portable Memory tests without a local Node ABI rebuild. diff --git a/docs/features/light-ocr-integration/spec.md b/docs/features/light-ocr-integration/spec.md index 5cb688305..a898f1dff 100644 --- a/docs/features/light-ocr-integration/spec.md +++ b/docs/features/light-ocr-integration/spec.md @@ -88,7 +88,8 @@ returns an actionable explanation instead of synthesizing a generic caption or c - Read each source path once into an immutable byte snapshot; hash and preprocess that same buffer. - Per image: configured upload limit capped at 50 MiB, 50 megapixels and 16,384 pixels per side. -- Per turn: at most 8 images and 120 MiB of encoded source bytes. +- Per OCR preparation: at most 8 OCR candidates and 120 MiB of encoded OCR source bytes. + Vision-routed images are not subject to this OCR resource limit. - Apply EXIF rotation, use the first animated frame/page, flatten transparency on white, resize without enlargement to 4,096 pixels longest side, and emit PNG. - Support JPEG, PNG, WebP, TIFF, GIF and uncompressed 24/32-bit BMP. Reject other BMP variants, diff --git a/docs/issues/light-ocr-follow-up-hardening/spec.md b/docs/issues/light-ocr-follow-up-hardening/spec.md index 1ff71ea3f..79f449472 100644 --- a/docs/issues/light-ocr-follow-up-hardening/spec.md +++ b/docs/issues/light-ocr-follow-up-hardening/spec.md @@ -1,6 +1,6 @@ # Light OCR Follow-up Hardening -Status: deferred until after the merge-blocking Light OCR review fixes. +Status: deferred follow-ups; vision image-limit compatibility resolved. GitHub issue: not created; this is a local SDD record by explicit decision. @@ -11,6 +11,12 @@ maintenance risks that are real but do not block the current offline chat-attach must remain visible as concrete follow-up work rather than being implied by the implementation or left only in review discussion. +## Resolved Findings + +- The eight-image resource limit now applies only to actual OCR candidates. Vision-only images and + the vision-routed images in a mixed turn retain their image representation beyond the eighth + attachment, while explicit and automatic OCR remain bounded to eight candidates. + ## Deferred Findings ### High-priority fast follow @@ -36,9 +42,6 @@ left only in review discussion. ### Compatibility and lifecycle -- The eight-image limit also affects vision-only turns even though the resource limit was introduced - for OCR. Restrict the limit to OCR candidates or explicitly define a product-wide image limit - after compatibility testing. - Availability failures are negatively cached for the process lifetime. Add an invalidation trigger for asset repair or runtime-state change. - Cache clearing can silently do nothing while an extraction owns a lease. Return an explicit @@ -82,7 +85,7 @@ left only in review discussion. - [ ] Avoid helper startup on trustworthy cache hits. - [ ] Replace hard global reservation rejection with bounded cancellable admission. - [ ] Eliminate redundant transcript and base64 processing. -- [ ] Resolve the vision-turn image-limit compatibility mismatch. +- [x] Restrict the eight-image resource limit to OCR candidates. - [ ] Add availability/cache-clear recovery semantics. - [ ] Harden prompt metadata and attachment numbering. - [ ] Move attachment pointer handling out of the shared shadcn primitive. diff --git a/src/main/ocr/attachmentCapabilityRouter.ts b/src/main/ocr/attachmentCapabilityRouter.ts index 40ce63aeb..30049ff11 100644 --- a/src/main/ocr/attachmentCapabilityRouter.ts +++ b/src/main/ocr/attachmentCapabilityRouter.ts @@ -26,7 +26,7 @@ import type { LightOcrBackendPreference } from './lightOcrProtocol' import { LightOcrProcessHostError } from './lightOcrProcessHost' import type { OcrRuntimeAvailability } from './ocrRuntimeAssetResolver' -const MAX_IMAGES_PER_TURN = 8 +const MAX_OCR_IMAGES_PER_TURN = 8 const MAX_TURN_OCR_TEXT_TOKENS = 16_000 export interface AttachmentOcrRuntimePort extends ImageTextExtractionPort { @@ -99,23 +99,11 @@ export class AttachmentCapabilityRouter { const routingDiagnostics: AttachmentRoutingDiagnostic[] = [] const ocrDiagnostics = new Map() const automaticOcrEnabled = this.options.getAutomaticOcrEnabled() - let imageCount = 0 for (let attachmentIndex = 0; attachmentIndex < files.length; attachmentIndex += 1) { const sourceFile = input.content.files?.[attachmentIndex] const file = files[attachmentIndex] if (!sourceFile || !isImageAttachment(sourceFile)) continue - imageCount += 1 - if (imageCount > MAX_IMAGES_PER_TURN) { - this.markUnavailable( - file, - attachmentIndex, - 'image_limit_exceeded', - issues, - routingDiagnostics - ) - continue - } const preference = sourceFile.requestedRepresentation ?? 'auto' if (input.content.attachmentFallbackPolicy === 'send_without_image_content') { @@ -230,8 +218,8 @@ export class AttachmentCapabilityRouter { ocrDiagnostics: Map, signal?: AbortSignal ): Promise { - const processable = candidates.slice(0, MAX_IMAGES_PER_TURN) - for (const candidate of candidates.slice(MAX_IMAGES_PER_TURN)) { + const processable = candidates.slice(0, MAX_OCR_IMAGES_PER_TURN) + for (const candidate of candidates.slice(MAX_OCR_IMAGES_PER_TURN)) { this.markUnavailable( candidate.file, candidate.attachmentIndex, diff --git a/test/main/ocr/attachmentCapabilityRouter.test.ts b/test/main/ocr/attachmentCapabilityRouter.test.ts index 3b95b038a..650b5ed22 100644 --- a/test/main/ocr/attachmentCapabilityRouter.test.ts +++ b/test/main/ocr/attachmentCapabilityRouter.test.ts @@ -443,7 +443,7 @@ describe('AttachmentCapabilityRouter', () => { ]) }) - it('applies the eight-image limit to vision routing as well as OCR', async () => { + it('keeps more than eight vision-routed images available without starting OCR', async () => { const { router, extraction } = createRouter() const result = await prepare( router, @@ -451,17 +451,44 @@ describe('AttachmentCapabilityRouter', () => { true ) - expect(result.summary.status).toBe('degraded') + expect(result.summary).toEqual({ status: 'ready', issues: [], suggestedActions: [] }) + expect( + result.content.files?.every((file) => file.resolvedRepresentation?.kind === 'image') + ).toBe(true) + expect(extraction.getAvailability).not.toHaveBeenCalled() + }) + + it('counts only OCR candidates when enforcing the eight-image OCR limit', async () => { + const { router, extraction } = createRouter() + const files = Array.from({ length: 18 }, (_, index) => + image(index + 1, { + requestedRepresentation: index % 2 === 0 ? 'image' : 'ocr_text' + }) + ) + + const result = await prepare(router, { text: '', files }, true) + + expect(vi.mocked(extraction.extractBatch).mock.calls[0][0]).toHaveLength(8) + expect(result.summary).toEqual({ + status: 'degraded', + issues: [{ attachmentIndex: 17, reason: 'image_limit_exceeded' }], + suggestedActions: [] + }) expect( result.content.files - ?.slice(0, 8) + ?.filter((_, index) => index % 2 === 0) .every((file) => file.resolvedRepresentation?.kind === 'image') ).toBe(true) - expect(result.content.files?.slice(8).map((file) => file.resolvedRepresentation)).toEqual([ - { kind: 'unavailable', reason: 'image_limit_exceeded' }, - { kind: 'unavailable', reason: 'image_limit_exceeded' } - ]) - expect(extraction.getAvailability).not.toHaveBeenCalled() + expect( + result.content.files + ?.slice(0, 17) + .filter((_, index) => index % 2 === 1) + .every((file) => file.resolvedRepresentation?.kind === 'ocr_text') + ).toBe(true) + expect(result.content.files?.[17].resolvedRepresentation).toEqual({ + kind: 'unavailable', + reason: 'image_limit_exceeded' + }) }) it('reapplies the per-turn OCR token budget to merged prepared snapshots', async () => { From aaac3ebc01c48c232a48bd6777574b8227352041 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:10:08 +0800 Subject: [PATCH 3/7] fix(test): repair one-shot entrypoints --- .github/workflows/windows-arm64-e2e.yml | 2 +- .../pr-check-quality-gates/tasks.md | 17 +-- package.json | 9 +- test/README.md | 103 ++++++++++-------- test/main/scripts/testEntrypoints.test.ts | 49 +++++++++ 5 files changed, 118 insertions(+), 62 deletions(-) create mode 100644 test/main/scripts/testEntrypoints.test.ts diff --git a/.github/workflows/windows-arm64-e2e.yml b/.github/workflows/windows-arm64-e2e.yml index d4055bb53..2e42cb5b7 100644 --- a/.github/workflows/windows-arm64-e2e.yml +++ b/.github/workflows/windows-arm64-e2e.yml @@ -51,7 +51,7 @@ jobs: - name: Validate native memory vector storage env: DEEPCHAT_REQUIRE_NATIVE_DUCKDB_VSS: '1' - run: pnpm exec vitest --config vitest.config.memory-native.ts --run test/main/presenter/memoryVectorStoreV2Native.test.ts + run: pnpm exec vitest --config vitest.config.memory-native.ts --run test/main/memory/memoryVectorStoreV2Native.test.ts - name: Build Windows arm64 package run: | diff --git a/docs/architecture/pr-check-quality-gates/tasks.md b/docs/architecture/pr-check-quality-gates/tasks.md index 0ca477237..db7c74a37 100644 --- a/docs/architecture/pr-check-quality-gates/tasks.md +++ b/docs/architecture/pr-check-quality-gates/tasks.md @@ -20,12 +20,12 @@ ## Test Entrypoints -- [ ] Make default, main, renderer, and coverage commands explicitly one-shot. -- [ ] Preserve the explicit watch command. -- [ ] Remove the obsolete local Native SQLite entrypoint. -- [ ] Repair the Windows ARM64 Native Memory test path. -- [ ] Update test documentation. -- [ ] Add a static entrypoint and workflow-path contract test. +- [x] Make default, main, renderer, and coverage commands explicitly one-shot. +- [x] Preserve the explicit watch command. +- [x] Remove the obsolete local Native SQLite entrypoint. +- [x] Repair the Windows ARM64 Native Memory test path. +- [x] Update test documentation. +- [x] Add a static entrypoint and workflow-path contract test. ## PR Workflow @@ -40,8 +40,9 @@ ## Validation - [x] Run focused Light OCR routing tests. -- [ ] Run entrypoint and workflow contract tests. -- [ ] Run default, main, and renderer test commands. +- [x] Run the entrypoint contract tests. +- [ ] Run the workflow contract tests. +- [x] Run default, main, and renderer test commands. - [ ] Run portable Memory tests without a local Node ABI rebuild. - [ ] Run format, localization, lint, architecture, icon, and type checks. - [ ] Run the canonical build and review generated registry changes. diff --git a/package.json b/package.json index 54cc4c78a..b14da3e48 100644 --- a/package.json +++ b/package.json @@ -13,17 +13,16 @@ "scripts": { "prebuild": "node scripts/generate-icon-collections.mjs && node scripts/fetch-provider-db.mjs && node scripts/fetch-acp-registry.mjs", "preinstall": "npx only-allow pnpm", - "test": "vitest", - "test:main": "vitest --config vitest.config.ts test/main", + "test": "vitest run", + "test:main": "vitest run --config vitest.config.ts test/main", "test:memory:scope": "node scripts/check-memory-test-scope.mjs", "test:memory:type": "node scripts/typecheck-memory-tests.mjs", "test:memory": "pnpm run test:memory:scope && pnpm run test:memory:type && vitest --config vitest.config.memory.ts --run", "test:memory:eval": "vitest --config vitest.config.memory-eval.ts --run", "test:main:memory-perf": "vitest --config vitest.config.memory-perf.ts --run", "test:agent:eval": "vitest run --config vitest.config.ts test/main/evals/nativeAgent/nativeAgentBehavior.eval.test.ts", - "test:main:native-sqlite": "cross-env DEEPCHAT_REQUIRE_NATIVE_SQLITE=1 vitest --config vitest.config.ts test/main/presenter/agentMemoryTable.test.ts test/main/presenter/agentRuntimePresenter/tapeService.test.ts test/main/presenter/memoryRetrieval.eval.test.ts", - "test:renderer": "vitest --config vitest.config.renderer.ts test/renderer", - "test:coverage": "vitest --coverage", + "test:renderer": "vitest run --config vitest.config.renderer.ts test/renderer", + "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", "test:ui": "vitest --ui", "e2e:smoke": "playwright test -c test/e2e/playwright.config.ts", diff --git a/test/README.md b/test/README.md index 640b2718e..a47ab3d22 100644 --- a/test/README.md +++ b/test/README.md @@ -4,20 +4,24 @@ ``` test/ -├── main/ # 主进程测试 -│ ├── session/ # Session测试 -│ └── scripts/ # 架构检查 -├── renderer/ # 渲染进程测试 -│ └── shell/ # Shell应用测试 -│ ├── App.test.ts # App组件测试 -│ └── main.test.ts # 入口文件测试 +├── main/ # 主进程、脚本与 Node 环境测试 +├── renderer/ # 渲染进程与 Vue 组件测试 +├── e2e/ # Playwright 端到端测试 +├── manual/ # 手工验证页面 ├── setup.ts # 主进程测试设置 ├── setup.renderer.ts # 渲染进程测试设置 -└── README.md # 本文档 +└── README.md # 本文档 ``` ## 🚀 快速开始 +项目统一使用 pnpm。首次运行前安装依赖;需要完整本地运行环境时再安装 runtime: + +```bash +pnpm install +pnpm run installRuntime +``` + ## 🔗 手工验证 Deeplink Playground 仓库内提供了一个静态验证页: @@ -43,62 +47,56 @@ test/ 如果要验证应用内行为,建议先启动 DeepChat,再点击页面中的 `Open` 按钮。 -### 安装测试依赖 - -首先需要安装Vue组件测试所需的依赖: - -```bash -# 安装Vue测试工具 -npm install -D @vue/test-utils jsdom - -# 或使用yarn -yarn add -D @vue/test-utils jsdom -``` - ### 运行测试 ```bash -# 运行所有测试 -npm test +# 一次性运行全部 main 与 renderer 测试 +pnpm test -# 运行主进程测试 -npm run test:main +# 一次性运行主进程测试 +pnpm run test:main -# 运行渲染进程测试 -npm run test:renderer +# 一次性运行渲染进程测试 +pnpm run test:renderer # 运行测试并生成覆盖率报告 -npm run test:coverage +pnpm run test:coverage # 监听模式运行测试 -npm run test:watch +pnpm run test:watch ``` ## 📝 测试脚本 -在 `package.json` 中添加以下测试脚本: +`package.json` 中的主要测试入口: ```json { "scripts": { - "test": "vitest", - "test:main": "vitest --config vitest.config.ts test/main", - "test:renderer": "vitest --config vitest.config.renderer.ts test/renderer", - "test:coverage": "vitest --coverage", + "test": "vitest run", + "test:main": "vitest run --config vitest.config.ts test/main", + "test:renderer": "vitest run --config vitest.config.renderer.ts test/renderer", + "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", "test:ui": "vitest --ui" } } ``` +默认、main、renderer 和 coverage 入口都显式使用一次性运行语义,交互式监听必须通过 +`test:watch` 启动。Native SQLite 的 Node ABI 重编译与验证由 CI workflow 独占,避免本地命令 +替换 Electron ABI 依赖。 + ## 🧪 测试类型 ### 主进程测试 + - **环境**: Node.js - **配置**: `vitest.config.ts` - **重点**: 各 main 模块、边界和工具函数 ### 渲染进程测试 + - **环境**: jsdom - **配置**: `vitest.config.renderer.ts` - **重点**: Vue组件、Store、Composables @@ -108,52 +106,57 @@ npm run test:watch 生成测试覆盖率报告: ```bash -npm run test:coverage +pnpm run test:coverage ``` -覆盖率报告将生成在: -- `coverage/` - 主进程覆盖率 -- `coverage/renderer/` - 渲染进程覆盖率 +覆盖率报告生成在 `coverage/`。 打开 `coverage/index.html` 查看详细的覆盖率报告。 ## 🔧 配置文件 ### vitest.config.ts -主进程测试配置,使用Node.js环境。 + +默认项目配置,同时定义 main 与 renderer 项目。 ### vitest.config.renderer.ts + 渲染进程测试配置,使用jsdom环境,支持Vue组件测试。 ### test/setup.ts + 主进程测试的全局设置,包含Electron模块的mock。 ### test/setup.renderer.ts + 渲染进程测试的全局设置,包含Vue相关依赖的mock。 ## 📋 测试规范 ### 文件命名 + - 测试文件使用 `.test.ts` 或 `.spec.ts` 后缀 - 与源文件保持相同的目录结构 ### 测试描述 -- 使用中文描述测试场景 + +- 使用清晰、行为导向的英文描述测试场景 - 使用 `describe` 按功能模块分组 - 使用 `it` 描述具体的测试用例 ### 示例测试结构 + ```typescript -describe('模块名称', () => { +describe('module behavior', () => { beforeEach(() => { - // 测试前置准备 + // Arrange shared state. }) - describe('功能分组', () => { - it('应该能够执行某个操作', () => { - // Arrange - 准备测试数据 - // Act - 执行测试操作 - // Assert - 验证测试结果 + describe('feature boundary', () => { + it('handles the expected behavior', () => { + // Arrange + // Act + // Assert }) }) }) @@ -162,12 +165,13 @@ describe('模块名称', () => { ## 🐛 调试测试 ### 调试单个测试 + ```bash # 运行特定的测试文件 -npx vitest test/main/eventbus/eventbus.test.ts +pnpm exec vitest run test/main/eventbus/eventbus.test.ts # 运行特定的测试用例 -npx vitest -t "应该能够正确发送事件到主进程" +pnpm exec vitest --watch -t "sends events to the main process" ``` ### 调试配置 @@ -189,16 +193,19 @@ npx vitest -t "应该能够正确发送事件到主进程" ## 🎯 最佳实践 ### Mock策略 + 1. **外部依赖**:完全mock(网络请求、文件系统) 2. **内部模块**:选择性mock(复杂依赖、不稳定组件) 3. **纯函数**:尽量使用真实实现 ### 测试数据 + - 使用简单、明确的测试数据 - 避免使用真实的敏感数据 - 考虑使用工厂函数生成测试数据 ### 断言技巧 + ```typescript // 推荐的断言方式 expect(result).toBe(expected) // 严格相等 diff --git a/test/main/scripts/testEntrypoints.test.ts b/test/main/scripts/testEntrypoints.test.ts new file mode 100644 index 000000000..b15727b2d --- /dev/null +++ b/test/main/scripts/testEntrypoints.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it, vi } from 'vitest' + +const fs = await vi.importActual('node:fs') +const path = await vi.importActual('node:path') +const repositoryRoot = process.cwd() +const packageJson = JSON.parse( + fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8') +) as { + scripts: Record +} +const windowsArm64Workflow = fs.readFileSync( + path.join(repositoryRoot, '.github/workflows/windows-arm64-e2e.yml'), + 'utf8' +) + +describe('test entrypoint contracts', () => { + it('keeps complete test suites one-shot and watch mode explicit', () => { + expect(packageJson.scripts).toMatchObject({ + test: 'vitest run', + 'test:main': 'vitest run --config vitest.config.ts test/main', + 'test:renderer': 'vitest run --config vitest.config.renderer.ts test/renderer', + 'test:coverage': 'vitest run --coverage', + 'test:watch': 'vitest --watch' + }) + }) + + it('keeps Native SQLite validation workflow-owned', () => { + expect(packageJson.scripts).not.toHaveProperty('test:main:native-sqlite') + expect( + Object.entries(packageJson.scripts).filter(([, command]) => + [ + 'DEEPCHAT_REQUIRE_NATIVE_SQLITE', + 'vitest.config.memory-native.ts', + 'rebuild -f -w better-sqlite3' + ].some((marker) => command.includes(marker)) + ) + ).toEqual([]) + }) + + it('keeps the Windows ARM64 workflow aligned with the Native Memory test location', () => { + const nativeMemoryTest = 'test/main/memory/memoryVectorStoreV2Native.test.ts' + + expect(fs.existsSync(path.join(repositoryRoot, nativeMemoryTest))).toBe(true) + expect(windowsArm64Workflow).toContain(nativeMemoryTest) + expect(windowsArm64Workflow).not.toContain( + 'test/main/presenter/memoryVectorStoreV2Native.test.ts' + ) + }) +}) From c176a077549c8ef85e40a3a3e3c3be92f0443215 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:20:41 +0800 Subject: [PATCH 4/7] ci: enforce parallel PR quality gates --- .github/workflows/prcheck.yml | 176 +++++++++-- .../plan.md | 2 +- .../spec.md | 2 +- .../tasks.md | 4 +- .../pr-check-quality-gates/spec.md | 2 +- .../pr-check-quality-gates/tasks.md | 20 +- test/main/scripts/prcheckWorkflow.test.ts | 277 ++++++++++++++++++ 7 files changed, 442 insertions(+), 41 deletions(-) create mode 100644 test/main/scripts/prcheckWorkflow.test.ts diff --git a/.github/workflows/prcheck.yml b/.github/workflows/prcheck.yml index 9c3668a93..b59c7800c 100644 --- a/.github/workflows/prcheck.yml +++ b/.github/workflows/prcheck.yml @@ -6,13 +6,22 @@ on: - main - dev +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + env: + CI: 'true' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' jobs: main-release-guard: if: github.base_ref == 'main' runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - name: Validate release branch naming env: @@ -39,13 +48,9 @@ jobs: exit 1 fi - build-check: + static: runs-on: ubuntu-24.04 - strategy: - matrix: - arch: [x64] - include: - - arch: x64 + timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -63,37 +68,96 @@ jobs: - name: Install dependencies run: pnpm install - - name: Install and verify DuckDB VSS - run: | - pnpm run installRuntime:duckdb:vss:linux:x64 - pnpm run smoke:duckdb:vss -- --platform linux --arch x64 - - - name: lint + - name: Lint run: pnpm run lint - - name: format:check + - name: Check formatting run: pnpm run format:check + - name: Check translations + run: pnpm run i18n + - name: Check renderer architecture baseline run: pnpm run architecture:renderer-baseline:check - - name: Configure pnpm workspace for Linux ${{ matrix.arch }} - run: pnpm run install:sharp - env: - TARGET_OS: linux - TARGET_ARCH: ${{ matrix.arch }} + - name: Check generated icon collections + run: pnpm run icons:check - - name: Check translations - run: pnpm run i18n + - name: Typecheck + run: pnpm run typecheck + + test-main: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24.14.1' + package-manager-cache: false + + - name: Setup pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Install dependencies + run: pnpm install + + - name: Test main process + run: pnpm run test:main + + test-renderer: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24.14.1' + package-manager-cache: false + + - name: Setup pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - name: Native Agent behavior eval - run: pnpm run test:agent:eval + - name: Install dependencies + run: pnpm install + + - name: Test renderer + run: pnpm run test:renderer + + build: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24.14.1' + package-manager-cache: false + + - name: Setup pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Install dependencies + run: pnpm install - name: Build run: pnpm run build - memory-native-validation: + test-native-memory: runs-on: ubuntu-24.04 + timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -116,9 +180,6 @@ jobs: pnpm run installRuntime:duckdb:vss:linux:x64 pnpm run smoke:duckdb:vss -- --platform linux --arch x64 - - name: Validate memory test scope - run: pnpm run test:memory:scope - - name: Validate portable memory behavior run: pnpm run test:memory @@ -156,3 +217,66 @@ jobs: name: memory-retrieval-v1 path: test-results/memory/retrieval-v1.json if-no-files-found: warn + + pr-required: + if: always() + needs: + - main-release-guard + - static + - test-main + - test-renderer + - test-native-memory + - build + runs-on: ubuntu-24.04 + timeout-minutes: 2 + steps: + - name: Verify required PR checks + shell: bash + env: + BASE_REF: ${{ github.base_ref }} + MAIN_RELEASE_GUARD_RESULT: ${{ needs.main-release-guard.result }} + STATIC_RESULT: ${{ needs.static.result }} + TEST_MAIN_RESULT: ${{ needs.test-main.result }} + TEST_RENDERER_RESULT: ${{ needs.test-renderer.result }} + TEST_NATIVE_MEMORY_RESULT: ${{ needs.test-native-memory.result }} + BUILD_RESULT: ${{ needs.build.result }} + run: | + set -euo pipefail + + failures=() + + require_success() { + local job_name="$1" + local result="$2" + if [[ "${result}" != "success" ]]; then + failures+=("${job_name}=${result}") + fi + } + + require_success "static" "${STATIC_RESULT}" + require_success "test-main" "${TEST_MAIN_RESULT}" + require_success "test-renderer" "${TEST_RENDERER_RESULT}" + require_success "test-native-memory" "${TEST_NATIVE_MEMORY_RESULT}" + require_success "build" "${BUILD_RESULT}" + + case "${BASE_REF}" in + main) + require_success "main-release-guard" "${MAIN_RELEASE_GUARD_RESULT}" + ;; + dev) + if [[ "${MAIN_RELEASE_GUARD_RESULT}" != "skipped" ]]; then + failures+=("main-release-guard=${MAIN_RELEASE_GUARD_RESULT}") + fi + ;; + *) + failures+=("unsupported-base=${BASE_REF}") + ;; + esac + + if (( ${#failures[@]} > 0 )); then + printf 'Required PR checks did not pass:\n' >&2 + printf ' - %s\n' "${failures[@]}" >&2 + exit 1 + fi + + echo "All required PR checks passed." diff --git a/docs/architecture/memory-quality-gates-and-observability/plan.md b/docs/architecture/memory-quality-gates-and-observability/plan.md index 8666eb1bb..1ddd88619 100644 --- a/docs/architecture/memory-quality-gates-and-observability/plan.md +++ b/docs/architecture/memory-quality-gates-and-observability/plan.md @@ -183,6 +183,6 @@ and CAS contention are verified through controlled asynchronous behavior and obs Local validation covers type checking, the full main suite, focused Memory scope and behavior, deterministic eval primitives, performance bounds, the renderer suite, formatting, localization parity, and linting. -The updated `memory-native-validation` workflow is the final external gate. It must demonstrate that required +The updated `test-native-memory` workflow job is the final external gate. It must demonstrate that required Native storage, FTS, retrieval evaluation, and performance paths run without skip or fallback after the change is submitted to CI. diff --git a/docs/architecture/memory-quality-gates-and-observability/spec.md b/docs/architecture/memory-quality-gates-and-observability/spec.md index f8c73228b..8ac48f71b 100644 --- a/docs/architecture/memory-quality-gates-and-observability/spec.md +++ b/docs/architecture/memory-quality-gates-and-observability/spec.md @@ -42,7 +42,7 @@ metric calculation remain isolated from credentials, providers, and external net ### AC-1 — Native Memory CI -- The existing `memory-native-validation` job uses the repository-pinned Node 24 and pnpm toolchain. +- The `test-native-memory` job uses the repository-pinned Node 24 and pnpm toolchain. - The job installs an independent dependency tree and rebuilds the SQLite binding for the Node ABI. - A smoke step loads the binding, opens encrypted SQLite, creates a table, writes, reads, and closes it. - Native tests run with `DEEPCHAT_REQUIRE_NATIVE_SQLITE=1`; a missing binding, FTS, JSON, migration harness, or diff --git a/docs/architecture/memory-quality-gates-and-observability/tasks.md b/docs/architecture/memory-quality-gates-and-observability/tasks.md index 3aa058857..debafbeda 100644 --- a/docs/architecture/memory-quality-gates-and-observability/tasks.md +++ b/docs/architecture/memory-quality-gates-and-observability/tasks.md @@ -88,8 +88,8 @@ - [x] `mise exec -- pnpm run format` - [x] `mise exec -- pnpm run i18n` - [x] `mise exec -- pnpm run lint` -- [x] GitHub Actions `memory-native-validation` completes with required Native SQLite and no skip or fallback - in PR #1952. +- [x] The Native Memory job (then `memory-native-validation`, now `test-native-memory`) completes with + required Native SQLite and no skip or fallback in PR #1952. Native storage, retrieval evaluation, and performance validation passed in PR #1952. The remaining external gate is a successful upload of the generated retrieval JSON artifact; local validation must not substitute for diff --git a/docs/architecture/pr-check-quality-gates/spec.md b/docs/architecture/pr-check-quality-gates/spec.md index baff0835b..64f1599c7 100644 --- a/docs/architecture/pr-check-quality-gates/spec.md +++ b/docs/architecture/pr-check-quality-gates/spec.md @@ -1,6 +1,6 @@ # PR Check Quality Gates — Specification -> Status: **planned** +> Status: **implemented — external workflow validation pending** > > Classification: **architecture** > diff --git a/docs/architecture/pr-check-quality-gates/tasks.md b/docs/architecture/pr-check-quality-gates/tasks.md index db7c74a37..2b13ab15a 100644 --- a/docs/architecture/pr-check-quality-gates/tasks.md +++ b/docs/architecture/pr-check-quality-gates/tasks.md @@ -29,21 +29,21 @@ ## PR Workflow -- [ ] Add read-only permissions, PR concurrency cancellation, and job timeouts. -- [ ] Split static, main, renderer, Native Memory, and build responsibilities. -- [ ] Remove the single-element matrix, ineffective Sharp step, redundant Agent evaluation, and +- [x] Add read-only permissions, PR concurrency cancellation, and job timeouts. +- [x] Split static, main, renderer, Native Memory, and build responsibilities. +- [x] Remove the single-element matrix, ineffective Sharp step, redundant Agent evaluation, and duplicated Memory scope step. -- [ ] Add a fail-closed `pr-required` aggregate job. -- [ ] Add the parsed-YAML workflow contract test. -- [ ] Update maintained Native Memory architecture references to the new job name. +- [x] Add a fail-closed `pr-required` aggregate job. +- [x] Add the parsed-YAML workflow contract test. +- [x] Update maintained Native Memory architecture references to the new job name. ## Validation - [x] Run focused Light OCR routing tests. - [x] Run the entrypoint contract tests. -- [ ] Run the workflow contract tests. +- [x] Run the workflow contract tests. - [x] Run default, main, and renderer test commands. -- [ ] Run portable Memory tests without a local Node ABI rebuild. -- [ ] Run format, localization, lint, architecture, icon, and type checks. -- [ ] Run the canonical build and review generated registry changes. +- [x] Run portable Memory tests without a local Node ABI rebuild. +- [x] Run format, localization, lint, architecture, icon, and type checks. +- [x] Run the canonical build and review generated registry changes. - [ ] Verify Native Memory and Windows ARM64 workflows after a future push. diff --git a/test/main/scripts/prcheckWorkflow.test.ts b/test/main/scripts/prcheckWorkflow.test.ts new file mode 100644 index 000000000..642df48d4 --- /dev/null +++ b/test/main/scripts/prcheckWorkflow.test.ts @@ -0,0 +1,277 @@ +import { describe, expect, it, vi } from 'vitest' +import { parse } from 'yaml' + +const fs = await vi.importActual('node:fs') +const path = await vi.importActual('node:path') + +interface WorkflowStep { + name?: string + uses?: string + if?: string + shell?: string + run?: string + env?: Record + with?: Record +} + +interface WorkflowJob { + if?: string + needs?: string[] + 'runs-on': string + 'timeout-minutes': number + strategy?: unknown + steps: WorkflowStep[] +} + +interface PrCheckWorkflow { + on: { + pull_request: { + branches: string[] + paths?: string[] + 'paths-ignore'?: string[] + } + } + permissions: Record + concurrency: { + group: string + 'cancel-in-progress': boolean + } + env: Record + jobs: Record +} + +const workflowPath = path.join(process.cwd(), '.github/workflows/prcheck.yml') +const workflowSource = fs.readFileSync(workflowPath, 'utf8') +const workflow = parse(workflowSource) as PrCheckWorkflow + +const expectedJobNames = [ + 'main-release-guard', + 'static', + 'test-main', + 'test-renderer', + 'test-native-memory', + 'build', + 'pr-required' +] + +const expectedActionUses = [ + ...Array(6).fill('actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd'), + ...Array(5).fill('actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e'), + ...Array(5).fill('pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093'), + 'actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f' +] + +const getStep = (job: WorkflowJob, name: string): WorkflowStep => { + const step = job.steps.find((candidate) => candidate.name === name) + if (!step) { + throw new Error(`Missing workflow step: ${name}`) + } + return step +} + +const getRunCommands = (job: WorkflowJob): string[] => + job.steps.flatMap((step) => (step.run ? [step.run] : [])) + +describe('PR Check workflow contracts', () => { + it('keeps the workflow read-only, PR-scoped, and cancellation-aware', () => { + expect(workflow.on.pull_request).toEqual({ + branches: ['main', 'dev'] + }) + expect(workflow.permissions).toEqual({ + contents: 'read' + }) + expect(workflow.concurrency).toEqual({ + group: '${{ github.workflow }}-pr-${{ github.event.pull_request.number }}', + 'cancel-in-progress': true + }) + expect(workflow.env).toMatchObject({ + CI: 'true', + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + }) + }) + + it('keeps every quality gate independent, bounded, and pinned', () => { + expect(Object.keys(workflow.jobs).sort()).toEqual([...expectedJobNames].sort()) + + for (const job of Object.values(workflow.jobs)) { + expect(job['runs-on']).toBe('ubuntu-24.04') + expect(job['timeout-minutes']).toBeGreaterThan(0) + expect(job.strategy).toBeUndefined() + } + + const actionSteps = Object.values(workflow.jobs).flatMap((job) => + job.steps.filter((step) => step.uses) + ) + expect( + actionSteps.map((step) => step.uses).sort() + ).toEqual([...expectedActionUses].sort()) + for (const step of actionSteps) { + expect(step.uses).toMatch(/^[^@]+@[0-9a-f]{40}$/) + } + + const checkoutSteps = actionSteps.filter((step) => step.uses?.startsWith('actions/checkout@')) + expect(checkoutSteps).toHaveLength(6) + for (const step of checkoutSteps) { + expect(step.with).toMatchObject({ + 'persist-credentials': false + }) + } + + const setupNodeSteps = actionSteps.filter((step) => + step.uses?.startsWith('actions/setup-node@') + ) + expect(setupNodeSteps).toHaveLength(5) + for (const step of setupNodeSteps) { + expect(step.with).toMatchObject({ + 'node-version': '24.14.1', + 'package-manager-cache': false + }) + } + }) + + it('keeps static checks and complete test suites in dedicated jobs', () => { + expect(getRunCommands(workflow.jobs.static)).toEqual([ + 'pnpm install', + 'pnpm run lint', + 'pnpm run format:check', + 'pnpm run i18n', + 'pnpm run architecture:renderer-baseline:check', + 'pnpm run icons:check', + 'pnpm run typecheck' + ]) + expect(getRunCommands(workflow.jobs['test-main'])).toEqual([ + 'pnpm install', + 'pnpm run test:main' + ]) + expect(getRunCommands(workflow.jobs['test-renderer'])).toEqual([ + 'pnpm install', + 'pnpm run test:renderer' + ]) + expect(getRunCommands(workflow.jobs.build)).toEqual(['pnpm install', 'pnpm run build']) + + expect(workflowSource).not.toContain('matrix:') + expect(workflowSource).not.toContain('pnpm run install:sharp') + expect(workflowSource).not.toContain('pnpm run test:agent:eval') + }) + + it('keeps Native Memory validation ordered and workflow-owned', () => { + const nativeJob = workflow.jobs['test-native-memory'] + const orderedStepNames = [ + 'Install dependencies', + 'Install and verify DuckDB VSS', + 'Validate portable memory behavior', + 'Prepare native SQLite for the Node ABI', + 'Smoke native SQLite', + 'Validate encrypted OCR artifact storage', + 'Validate native memory storage', + 'Validate memory retrieval quality', + 'Validate memory performance bounds', + 'Upload memory retrieval report' + ] + const indexes = orderedStepNames.map((name) => + nativeJob.steps.findIndex((step) => step.name === name) + ) + + expect(indexes.every((index) => index >= 0)).toBe(true) + expect(indexes).toEqual([...indexes].sort((left, right) => left - right)) + expect( + getRunCommands(nativeJob).some((command) => + command.includes('pnpm run test:memory:scope') + ) + ).toBe(false) + expect(getStep(nativeJob, 'Install and verify DuckDB VSS').run).toContain( + 'pnpm run installRuntime:duckdb:vss:linux:x64' + ) + expect(getStep(nativeJob, 'Install and verify DuckDB VSS').run).toContain( + 'pnpm run smoke:duckdb:vss -- --platform linux --arch x64' + ) + expect(getStep(nativeJob, 'Validate portable memory behavior').run).toBe( + 'pnpm run test:memory' + ) + expect(getStep(nativeJob, 'Prepare native SQLite for the Node ABI').run).toBe( + 'pnpm --dir node_modules/better-sqlite3-multiple-ciphers run install' + ) + expect(getStep(nativeJob, 'Smoke native SQLite').run).toBe( + 'node scripts/smoke-memory-native-sqlite.js' + ) + expect(getStep(nativeJob, 'Validate encrypted OCR artifact storage').run).toBe( + 'pnpm exec vitest --config vitest.config.ts --run test/main/ocr/ocrArtifactStore.test.ts' + ) + expect(getStep(nativeJob, 'Validate encrypted OCR artifact storage').env).toEqual({ + DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' + }) + expect(getStep(nativeJob, 'Validate native memory storage').run).toBe( + 'pnpm exec vitest --config vitest.config.memory-native.ts --run' + ) + expect(getStep(nativeJob, 'Validate native memory storage').env).toEqual({ + DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1', + DEEPCHAT_REQUIRE_NATIVE_DUCKDB_VSS: '1' + }) + expect(getStep(nativeJob, 'Validate memory retrieval quality').run).toBe( + 'pnpm run test:memory:eval' + ) + expect(getStep(nativeJob, 'Validate memory retrieval quality').env).toEqual({ + DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' + }) + expect(getStep(nativeJob, 'Validate memory performance bounds').run).toBe( + 'pnpm run test:main:memory-perf' + ) + expect(getStep(nativeJob, 'Validate memory performance bounds').env).toEqual({ + DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' + }) + expect(getStep(nativeJob, 'Upload memory retrieval report')).toMatchObject({ + if: 'always()', + with: { + name: 'memory-retrieval-v1', + path: 'test-results/memory/retrieval-v1.json', + 'if-no-files-found': 'warn' + } + }) + }) + + it('fails closed unless every required result matches the PR base contract', () => { + const releaseGuardJob = workflow.jobs['main-release-guard'] + const aggregateJob = workflow.jobs['pr-required'] + const aggregateStep = getStep(aggregateJob, 'Verify required PR checks') + + expect(releaseGuardJob.if).toBe("github.base_ref == 'main'") + expect(aggregateJob.if).toBe('always()') + expect(aggregateJob.needs).toEqual([ + 'main-release-guard', + 'static', + 'test-main', + 'test-renderer', + 'test-native-memory', + 'build' + ]) + expect(aggregateJob.steps).toHaveLength(1) + expect(aggregateStep.shell).toBe('bash') + expect(aggregateStep.env).toEqual({ + BASE_REF: '${{ github.base_ref }}', + MAIN_RELEASE_GUARD_RESULT: '${{ needs.main-release-guard.result }}', + STATIC_RESULT: '${{ needs.static.result }}', + TEST_MAIN_RESULT: '${{ needs.test-main.result }}', + TEST_RENDERER_RESULT: '${{ needs.test-renderer.result }}', + TEST_NATIVE_MEMORY_RESULT: '${{ needs.test-native-memory.result }}', + BUILD_RESULT: '${{ needs.build.result }}' + }) + expect(aggregateStep.run).toContain('if [[ "${result}" != "success" ]]') + for (const jobName of [ + 'static', + 'test-main', + 'test-renderer', + 'test-native-memory', + 'build' + ]) { + expect(aggregateStep.run).toContain(`require_success "${jobName}"`) + } + expect(aggregateStep.run).toContain( + 'require_success "main-release-guard" "${MAIN_RELEASE_GUARD_RESULT}"' + ) + expect(aggregateStep.run).toContain( + 'if [[ "${MAIN_RELEASE_GUARD_RESULT}" != "skipped" ]]' + ) + expect(aggregateStep.run).toContain('failures+=("unsupported-base=${BASE_REF}")') + expect(aggregateStep.run).toContain('exit 1') + }) +}) From b788085173427acb26ced809715d07785da1d9ac Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:21:23 +0800 Subject: [PATCH 5/7] chore(data): refresh generated registries --- resources/acp-registry/registry.json | 9 +- resources/model-db/providers.json | 712 +++++++++++++++++++++++++++ 2 files changed, 720 insertions(+), 1 deletion(-) diff --git a/resources/acp-registry/registry.json b/resources/acp-registry/registry.json index e757545ff..daa0584db 100644 --- a/resources/acp-registry/registry.json +++ b/resources/acp-registry/registry.json @@ -756,7 +756,7 @@ "name": "Junie", "version": "2383.9.0", "description": "AI Coding Agent by JetBrains", - "repository": "https://github.com/JetBrains/junie", + "repository": "https://github.com/JetBrains/junie-acp-release", "website": "https://junie.jetbrains.com", "authors": [ "JetBrains" @@ -798,6 +798,13 @@ "args": [ "--acp=true" ] + }, + "windows-aarch64": { + "archive": "https://github.com/JetBrains/junie/releases/download/2383.9/junie-release-2383.9-windows-aarch64.zip", + "cmd": "./junie/junie.exe", + "args": [ + "--acp=true" + ] } } }, diff --git a/resources/model-db/providers.json b/resources/model-db/providers.json index f4f3016b7..45d1e072c 100644 --- a/resources/model-db/providers.json +++ b/resources/model-db/providers.json @@ -42300,6 +42300,678 @@ } ] }, + "ofox": { + "id": "ofox", + "name": "Ofox", + "display_name": "Ofox", + "api": "https://api.ofox.ai/v1", + "doc": "https://ofox.ai/docs", + "models": [ + { + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "cost": { + "input": 1.4, + "output": 4.4, + "cache_read": 0.26 + }, + "type": "chat" + }, + { + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", + "cost": { + "input": 5, + "output": 25, + "cache_read": 0.5, + "cache_write": 6.25 + }, + "type": "chat" + }, + { + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", + "cost": { + "input": 10, + "output": 50, + "cache_read": 1, + "cache_write": 12.5 + }, + "type": "chat" + }, + { + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", + "cost": { + "input": 2, + "output": 10, + "cache_read": 0.2, + "cache_write": 2.5 + }, + "type": "chat" + }, + { + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 30000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "cost": { + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 + }, + "type": "chat" + }, + { + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", + "cost": { + "input": 0.95, + "output": 4, + "cache_read": 0.16 + }, + "type": "chat" + }, + { + "id": "bailian/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", + "cost": { + "input": 2.5, + "output": 7.5, + "cache_read": 0.5, + "cache_write": 3.125 + }, + "type": "chat" + }, + { + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 384000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "cost": { + "input": 0.45, + "output": 0.88, + "cache_read": 0.0037 + }, + "type": "chat" + }, + { + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", + "cost": { + "input": 2, + "output": 12, + "cache_read": 0.2 + }, + "type": "chat" + }, + { + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "cost": { + "input": 2.5, + "output": 15, + "cache_read": 0.25, + "cache_write": 3.125 + }, + "type": "chat" + }, + { + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "cost": { + "input": 1, + "output": 6, + "cache_read": 0.1, + "cache_write": 1.25 + }, + "type": "chat" + }, + { + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "cost": { + "input": 5, + "output": 30, + "cache_read": 0.5, + "cache_write": 6.25 + }, + "type": "chat" + }, + { + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "cost": { + "input": 5, + "output": 30, + "cache_read": 0.5 + }, + "type": "chat" + } + ] + }, "google-vertex": { "id": "google-vertex", "name": "Vertex", @@ -91850,6 +92522,46 @@ }, "type": "chat" }, + { + "id": "cline-pass/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.3 + }, + "type": "chat" + }, { "id": "cline-pass/deepseek-v4-flash", "name": "DeepSeek V4 Flash", From 3dbea4e82f61f13e99bc20c4f0f6009162417c8a Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:34:59 +0800 Subject: [PATCH 6/7] fix(ci): pin icon generation inputs --- package.json | 4 ++-- test/main/scripts/prcheckWorkflow.test.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b14da3e48..fa4b40681 100644 --- a/package.json +++ b/package.json @@ -162,8 +162,8 @@ "@commitlint/config-conventional": "^21.1.0", "@electron-toolkit/tsconfig": "^1.0.1", "@electron/notarize": "^3.1.1", - "@iconify-json/lucide": "^1.2.108", - "@iconify-json/vscode-icons": "^1.2.49", + "@iconify-json/lucide": "1.2.109", + "@iconify-json/vscode-icons": "1.2.50", "@iconify/vue": "^5.0.1", "@lingual/i18n-check": "0.8.12", "@lucide/vue": "^1.24.0", diff --git a/test/main/scripts/prcheckWorkflow.test.ts b/test/main/scripts/prcheckWorkflow.test.ts index 642df48d4..4712a25f3 100644 --- a/test/main/scripts/prcheckWorkflow.test.ts +++ b/test/main/scripts/prcheckWorkflow.test.ts @@ -43,6 +43,11 @@ interface PrCheckWorkflow { const workflowPath = path.join(process.cwd(), '.github/workflows/prcheck.yml') const workflowSource = fs.readFileSync(workflowPath, 'utf8') const workflow = parse(workflowSource) as PrCheckWorkflow +const packageJson = JSON.parse( + fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8') +) as { + devDependencies: Record +} const expectedJobNames = [ 'main-release-guard', @@ -154,6 +159,13 @@ describe('PR Check workflow contracts', () => { expect(workflowSource).not.toContain('pnpm run test:agent:eval') }) + it('pins external icon generator inputs for reproducible checks', () => { + const exactVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/ + + expect(packageJson.devDependencies['@iconify-json/lucide']).toMatch(exactVersion) + expect(packageJson.devDependencies['@iconify-json/vscode-icons']).toMatch(exactVersion) + }) + it('keeps Native Memory validation ordered and workflow-owned', () => { const nativeJob = workflow.jobs['test-native-memory'] const orderedStepNames = [ From 22d5dac8443c0a4a2cb546c6432189861f623d33 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Thu, 23 Jul 2026 16:54:20 +0800 Subject: [PATCH 7/7] chore(ci): retrigger PR checks