Repository: toeverything/AFFiNE
Pull request #12186: feat(server): workspace doc update event from indexer
Author: login: darkskygit
Labels: name: mod:i18n
description: Related to i18n
name: mod:component
name: app:server
name: test
description: Related to test cases
name: app:core
Created: 2025-05-08T05:09:43Z | Merged: 2025-05-09T08:50:47Z

fix AI-108
fix AI-109
fix AI-13


Diff:
diff --git a/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.md b/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.md
new file mode 100644
index 0000000000000..b4d85cc168119
--- /dev/null
+++ b/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.md
@@ -0,0 +1,11 @@
+# Snapshot report for `src/__tests__/models/copilot-context.spec.ts`
+
+The actual snapshot is saved in `copilot-context.spec.ts.snap`.
+
+Generated by [AVA](https://avajs.dev).
+
+## should insert embedding by doc id
+
+> should return empty array when embedding deleted
+
+    []
diff --git a/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.snap b/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.snap
new file mode 100644
index 0000000000000..d76cf43ae72a9
Binary files /dev/null and b/packages/backend/server/src/__tests__/models/__snapshots__/copilot-context.spec.ts.snap differ
diff --git a/packages/backend/server/src/__tests__/models/copilot-context.spec.ts b/packages/backend/server/src/__tests__/models/copilot-context.spec.ts
index 7a57708556a04..d6a014f892e1d 100644
--- a/packages/backend/server/src/__tests__/models/copilot-context.spec.ts
+++ b/packages/backend/server/src/__tests__/models/copilot-context.spec.ts
@@ -116,7 +116,7 @@ test('should insert embedding by doc id', async t => {
     }
 
     {
-      await t.context.copilotContext.deleteEmbedding(contextId, 'file-id');
+      await t.context.copilotContext.deleteFileEmbedding(contextId, 'file-id');
       const ret = await t.context.copilotContext.matchFileEmbedding(
         Array.from({ length: 1024 }, () => 0.9),
         contextId,
@@ -169,6 +169,20 @@ test('should insert embedding by doc id', async t => {
       t.is(ret.length, 1);
       t.is(ret[0].content, 'content');
     }
+
+    {
+      await t.context.copilotContext.deleteWorkspaceEmbedding(
+        workspace.id,
+        docId
+      );
+      const ret = await t.context.copilotContext.matchWorkspaceEmbedding(
+        Array.from({ length: 1024 }, () => 0.9),
+        workspace.id,
+        1,
+        1
+      );
+      t.snapshot(ret, 'should return empty array when embedding deleted');
+    }
   }
 });
 
diff --git a/packages/backend/server/src/models/copilot-context.ts b/packages/backend/server/src/models/copilot-context.ts
index 2a392815a45ab..ab3453066611d 100644
--- a/packages/backend/server/src/models/copilot-context.ts
+++ b/packages/backend/server/src/models/copilot-context.ts
@@ -168,6 +168,12 @@ export class CopilotContextModel extends BaseModel {
   `;
   }
 
+  async deleteFileEmbedding(contextId: string, fileId: string) {
+    await this.db.aiContextEmbedding.deleteMany({
+      where: { contextId, fileId },
+    });
+  }
+
   async matchFileEmbedding(
     embedding: number[],
     contextId: string,
@@ -205,6 +211,12 @@ export class CopilotContextModel extends BaseModel {
     `;
   }
 
+  async deleteWorkspaceEmbedding(workspaceId: string, docId: string) {
+    await this.db.aiWorkspaceEmbedding.deleteMany({
+      where: { workspaceId, docId },
+    });
+  }
+
   async matchWorkspaceEmbedding(
     embedding: number[],
     workspaceId: string,
@@ -222,10 +234,4 @@ export class CopilotContextModel extends BaseModel {
     `;
     return similarityChunks.filter(c => Number(c.distance) <= threshold);
   }
-
-  async deleteEmbedding(contextId: string, fileId: string) {
-    await this.db.aiContextEmbedding.deleteMany({
-      where: { contextId, fileId },
-    });
-  }
 }
diff --git a/packages/backend/server/src/plugins/copilot/context/job.ts b/packages/backend/server/src/plugins/copilot/context/job.ts
index 70c7412765dd0..c272d2c2610f9 100644
--- a/packages/backend/server/src/plugins/copilot/context/job.ts
+++ b/packages/backend/server/src/plugins/copilot/context/job.ts
@@ -87,6 +87,30 @@ export class CopilotContextDocJob {
     }
   }
 
+  // @OnEvent('doc.indexer.updated')
+  async addDocEmbeddingQueueFromEvent(
+    // TODO(@darkskygit): replace this with real event type
+    doc: { workspaceId: string; docId: string } //Events['doc.indexer.updated'],
+  ) {
+    if (!this.supportEmbedding) return;
+
+    await this.queue.add('copilot.embedding.docs', {
+      workspaceId: doc.workspaceId,
+      docId: doc.workspaceId,
+    });
+  }
+
+  // @OnEvent('doc.indexer.deleted')
+  async deleteDocEmbeddingQueueFromEvent(
+    // TODO(@darkskygit): replace this with real event type
+    doc: { workspaceId: string; docId: string } //Events['doc.indexer.deleted'],
+  ) {
+    await this.models.copilotContext.deleteWorkspaceEmbedding(
+      doc.workspaceId,
+      doc.docId
+    );
+  }
+
   async readCopilotBlob(
     userId: string,
     workspaceId: string,
diff --git a/packages/backend/server/src/plugins/copilot/context/session.ts b/packages/backend/server/src/plugins/copilot/context/session.ts
index c4ea10bc4df7d..05dd9ff6afc99 100644
--- a/packages/backend/server/src/plugins/copilot/context/session.ts
+++ b/packages/backend/server/src/plugins/copilot/context/session.ts
@@ -164,7 +164,10 @@ export class ContextSession implements AsyncDisposable {
   }
 
   async removeFile(fileId: string): Promise<boolean> {
-    await this.models.copilotContext.deleteEmbedding(this.contextId, fileId);
+    await this.models.copilotContext.deleteFileEmbedding(
+      this.contextId,
+      fileId
+    );
     this.config.files = this.config.files.filter(f => f.id !== fileId);
     await this.save();
     return true;


Reviews:
user: login: akumatus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T08:51:35Z
user: login: darkskygit
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T09:23:20Z
user: login: akumatus
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T03:00:03Z

Review comments:
diff_hunk: @@ -164,7 +164,10 @@ export class ContextSession implements AsyncDisposable {
   }
 
   async removeFile(fileId: string): Promise<boolean> {
-    await this.models.copilotContext.deleteEmbedding(this.contextId, fileId);
+    await this.models.copilotContext.deleteFileEmbedding(
+      this.contextId,
+      fileId
+    );
path: packages/backend/server/src/plugins/copilot/context/session.ts
user: login: akumatus
body: Where is the cleanup logic for additional files uploaded by users in the worksapce setting?
created_at: 2025-05-08T08:51:34Z
author_association: MEMBER
diff_hunk: @@ -164,7 +164,10 @@ export class ContextSession implements AsyncDisposable {
   }
 
   async removeFile(fileId: string): Promise<boolean> {
-    await this.models.copilotContext.deleteEmbedding(this.contextId, fileId);
+    await this.models.copilotContext.deleteFileEmbedding(
+      this.contextId,
+      fileId
+    );
path: packages/backend/server/src/plugins/copilot/context/session.ts
user: login: darkskygit
body: removeWorkspaceEmbeddingFiles
created_at: 2025-05-08T09:23:19Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-08T05:09:50Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=toeverything/AFFiNE&utm_content=12186):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: darkskygit
created_at: 2025-05-08T05:09:56Z
body: * **#12064** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12064?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>: 2  dependent PRs ([#11554](https://github.com/toeverything/AFFiNE/pull/11554) <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/11554?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>, [#11665](https://github.com/toeverything/AFFiNE/pull/11665) <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/11665?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>)
* **#12186** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12186?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12186?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#12139** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12139?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12023** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12023?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12039** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12039?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12141** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12141?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `canary`
<hr />

## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>merge</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/toeverything?ref=merge-queue-instructions-in-stack-comment&prId=12186).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>

<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: graphite-app[bot]
created_at: 2025-05-09T03:34:03Z
body: ### Merge activity

* **May 8, 11:34 PM EDT**: `darkskygit` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **May 9, 12:15 AM EDT**: The [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE) removed this pull request due to **downstack failures on PR #[12139](https://app.graphite.dev/github/pr/toeverything/AFFiNE/12139)**.
* **May 9, 4:15 AM UTC**: The [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE) removed this pull request due to **downstack failures on PR #[12139](https://app.graphite.dev/github/pr/toeverything/AFFiNE/12139)**.
* **May 9, 3:35 AM EDT**: `darkskygit` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **May 9, 7:49 AM UTC**: The [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE) couldn't merge this PR because **it was not satisfying all requirements** (Failed CI: '3, 2, 1 Launch', 'Server Copilot Api Test').
* **May 9, 4:49 AM EDT**: `darkskygit` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **May 9, 4:50 AM EDT**: Merged by the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).

author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-09T04:06:36Z
body: ## [Codecov](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) Report
Attention: Patch coverage is `45.00000%` with `22 lines` in your changes missing coverage. Please review.
> Project coverage is 54.12%. Comparing base [(`918b3b2`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/918b3b2dab7d6a5089cee0fc3ebc892a2c496c9a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) to head [(`5f5de8e`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/5f5de8e89d292781cfa5369fcdc55dda486de151?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).
> Report is 1 commits behind head on canary.

| [Files with missing lines](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Patch % | Lines |
|---|---|---|
| [.../backend/server/src/plugins/copilot/context/job.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?src=pr&el=tree&filepath=packages%2Fbackend%2Fserver%2Fsrc%2Fplugins%2Fcopilot%2Fcontext%2Fjob.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-cGFja2FnZXMvYmFja2VuZC9zZXJ2ZXIvc3JjL3BsdWdpbnMvY29waWxvdC9jb250ZXh0L2pvYi50cw==) | 25.00% | [18 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [...kend/server/src/plugins/copilot/context/session.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?src=pr&el=tree&filepath=packages%2Fbackend%2Fserver%2Fsrc%2Fplugins%2Fcopilot%2Fcontext%2Fsession.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-cGFja2FnZXMvYmFja2VuZC9zZXJ2ZXIvc3JjL3BsdWdpbnMvY29waWxvdC9jb250ZXh0L3Nlc3Npb24udHM=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           canary   #12186      +/-   ##
==========================================
- Coverage   54.14%   54.12%   -0.02%     
==========================================
  Files        2666     2666              
  Lines      119344   119377      +33     
  Branches    19396    19396              
==========================================
+ Hits        64614    64616       +2     
- Misses      52651    53203     +552     
+ Partials     2079     1558     -521     
```

| [Flag](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Coverage Δ | |
|---|---|---|
| [server-test](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `78.43% <45.00%> (-0.04%)` | :arrow_down: |
| [unittest](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `30.85% <ø> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12186?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE