mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-25 17:21:01 -07:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d5bdc0525 | |||
| cefbd08f7a | |||
| 7fd336f5fc | |||
| 5116b5ac05 | |||
| 31bdf112d2 | |||
| 8482eccf5d | |||
| 61c6a2adf8 | |||
| c48c419f3b | |||
| a9b630f808 | |||
| ee7e037c93 |
@@ -2,8 +2,7 @@
|
||||
"experimental": {
|
||||
"extensionReloading": true,
|
||||
"modelSteering": true,
|
||||
"autoMemory": true,
|
||||
"gemma": true
|
||||
"autoMemory": true
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: '🧠 Gemini CLI Bot: Brain'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Every 24 hours
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
issues: 'write'
|
||||
pull-requests: 'write'
|
||||
|
||||
jobs:
|
||||
brain:
|
||||
name: 'Brain (Reasoning Layer)'
|
||||
runs-on: 'ubuntu-latest'
|
||||
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build Gemini CLI'
|
||||
run: 'npm run bundle'
|
||||
|
||||
- name: 'Download Previous Metrics'
|
||||
uses: 'actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093' # ratchet:actions/download-artifact@v4
|
||||
with:
|
||||
name: 'metrics-before'
|
||||
path: 'tools/gemini-cli-bot/history/'
|
||||
continue-on-error: true
|
||||
@@ -1,59 +0,0 @@
|
||||
name: '🔄 Gemini CLI Bot: Pulse'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/30 * * * *' # Every 30 minutes
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
issues: 'write'
|
||||
pull-requests: 'write'
|
||||
|
||||
jobs:
|
||||
pulse:
|
||||
name: 'Pulse (Reflex Layer)'
|
||||
runs-on: 'ubuntu-latest'
|
||||
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Collect Metrics'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: 'npm run metrics'
|
||||
|
||||
- name: 'Archive Metrics'
|
||||
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'metrics-before'
|
||||
path: 'metrics-before.csv'
|
||||
|
||||
- name: 'Run Reflex Processes'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: |
|
||||
if [ -d "tools/gemini-cli-bot/processes/scripts" ] && [ "$(ls -A tools/gemini-cli-bot/processes/scripts)" ]; then
|
||||
for script in tools/gemini-cli-bot/processes/scripts/*.ts; do
|
||||
echo "Running reflex script: $script"
|
||||
npx tsx "$script"
|
||||
done
|
||||
else
|
||||
echo "No reflex scripts found."
|
||||
fi
|
||||
@@ -371,8 +371,6 @@ for planned features and priorities.
|
||||
|
||||
## 📖 Resources
|
||||
|
||||
- **[Free Course](https://learn.deeplearning.ai/courses/gemini-cli-code-and-create-with-an-open-source-agent/information)** -
|
||||
Learn the basics.
|
||||
- **[Official Roadmap](./ROADMAP.md)** - See what's coming next.
|
||||
- **[Changelog](https://www.geminicli.com/docs/changelogs)** - See recent
|
||||
notable updates.
|
||||
|
||||
@@ -163,7 +163,6 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Gemma Models | `experimental.gemma` | Enable access to Gemma 4 models (experimental). | `false` |
|
||||
| Enable Git Worktrees | `experimental.worktrees` | Enable automated Git worktree management for parallel work. | `false` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
| Use OSC 52 Copy | `experimental.useOSC52Copy` | Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
|
||||
@@ -563,18 +563,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
"model": "gemini-2.5-flash-lite"
|
||||
}
|
||||
},
|
||||
"gemma-4-31b-it": {
|
||||
"extends": "chat-base-3",
|
||||
"modelConfig": {
|
||||
"model": "gemma-4-31b-it"
|
||||
}
|
||||
},
|
||||
"gemma-4-26b-a4b-it": {
|
||||
"extends": "chat-base-3",
|
||||
"modelConfig": {
|
||||
"model": "gemma-4-26b-a4b-it"
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash-base": {
|
||||
"extends": "base",
|
||||
"modelConfig": {
|
||||
@@ -846,28 +834,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
"multimodalToolUse": false
|
||||
}
|
||||
},
|
||||
"gemma-4-31b-it": {
|
||||
"displayName": "gemma-4-31b-it",
|
||||
"tier": "custom",
|
||||
"family": "gemma-4",
|
||||
"isPreview": false,
|
||||
"isVisible": true,
|
||||
"features": {
|
||||
"thinking": true,
|
||||
"multimodalToolUse": false
|
||||
}
|
||||
},
|
||||
"gemma-4-26b-a4b-it": {
|
||||
"displayName": "gemma-4-26b-a4b-it",
|
||||
"tier": "custom",
|
||||
"family": "gemma-4",
|
||||
"isPreview": false,
|
||||
"isVisible": true,
|
||||
"features": {
|
||||
"thinking": true,
|
||||
"multimodalToolUse": false
|
||||
}
|
||||
},
|
||||
"auto": {
|
||||
"tier": "auto",
|
||||
"isPreview": true,
|
||||
@@ -938,12 +904,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"gemma-4-31b-it": {
|
||||
"default": "gemma-4-31b-it"
|
||||
},
|
||||
"gemma-4-26b-a4b-it": {
|
||||
"default": "gemma-4-26b-a4b-it"
|
||||
},
|
||||
"gemini-3.1-pro-preview": {
|
||||
"default": "gemini-3.1-pro-preview",
|
||||
"contexts": [
|
||||
@@ -1191,7 +1151,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1207,7 +1167,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1224,7 +1184,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1240,7 +1200,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1257,7 +1217,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1272,7 +1232,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1288,7 +1248,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"transient": "sticky_retry",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
@@ -1686,11 +1646,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
#### `experimental`
|
||||
|
||||
- **`experimental.gemma`** (boolean):
|
||||
- **Description:** Enable access to Gemma 4 models (experimental).
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.adk.agentSessionNoninteractiveEnabled`** (boolean):
|
||||
- **Description:** Enable non-interactive agent sessions.
|
||||
- **Default:** `false`
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Hierarchical Memory', () => {
|
||||
params: {
|
||||
settings: {
|
||||
security: {
|
||||
folderTrust: { enabled: false },
|
||||
folderTrust: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -55,7 +55,7 @@ What is my favorite fruit? Tell me just the name of the fruit.`,
|
||||
params: {
|
||||
settings: {
|
||||
security: {
|
||||
folderTrust: { enabled: false },
|
||||
folderTrust: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -96,7 +96,7 @@ Provide the answer as an XML block like this:
|
||||
params: {
|
||||
settings: {
|
||||
security: {
|
||||
folderTrust: { enabled: false },
|
||||
folderTrust: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Generated
+13
-398
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -991,37 +991,6 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz",
|
||||
@@ -1069,24 +1038,6 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
||||
@@ -1100,19 +1051,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
|
||||
@@ -3795,37 +3733,6 @@
|
||||
"path-browserify": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ts-morph/common/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ts-morph/common/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ts-morph/common/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
@@ -4986,13 +4893,6 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@vscode/vsce/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vscode/vsce/node_modules/hosted-git-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
@@ -5032,30 +4932,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/vsce/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/vsce/node_modules/minimatch/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/vsce/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
@@ -6553,13 +6429,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/config-chain": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
|
||||
@@ -7075,23 +6944,6 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/depcheck/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depcheck/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depcheck/node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
@@ -7151,22 +7003,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/depcheck/node_modules/minimatch": {
|
||||
"version": "7.4.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.9.tgz",
|
||||
"integrity": "sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/depcheck/node_modules/resolve-from": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||
@@ -8057,24 +7893,6 @@
|
||||
"eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
@@ -8085,19 +7903,6 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
@@ -8154,37 +7959,6 @@
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
@@ -8243,37 +8017,6 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
@@ -11914,12 +11657,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
@@ -12100,37 +11843,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multimatch/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multimatch/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/multimatch/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
||||
@@ -12371,24 +12083,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@@ -12438,19 +12132,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@@ -15817,39 +15498,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/minimatch": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/text-decoder": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
||||
@@ -16608,23 +16256,6 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/ignore": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||
@@ -16635,22 +16266,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/minimatch": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
@@ -18077,7 +17692,7 @@
|
||||
},
|
||||
"packages/a2a-server": {
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "0.3.11",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
@@ -18206,7 +17821,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
@@ -18354,7 +17969,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "0.3.11",
|
||||
@@ -18664,7 +18279,7 @@
|
||||
},
|
||||
"packages/devtools": {
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
@@ -18679,7 +18294,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -18710,7 +18325,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -18742,7 +18357,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/google-gemini/gemini-cli.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.41.0-nightly.20260423.gaa05b4583"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.40.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
@@ -63,7 +63,6 @@
|
||||
"lint:all": "node scripts/lint.js",
|
||||
"format": "prettier --experimental-cli --write .",
|
||||
"typecheck": "npm run typecheck --workspaces --if-present && tsc -b evals/tsconfig.json integration-tests/tsconfig.json memory-tests/tsconfig.json",
|
||||
"metrics": "tsx tools/gemini-cli-bot/metrics/index.ts",
|
||||
"preflight": "npm run clean && npm ci && npm run format && npm run build && npm run lint:ci && npm run typecheck && npm run test:ci",
|
||||
"prepare": "husky && npm run bundle",
|
||||
"prepare:package": "node scripts/prepare-package.js",
|
||||
@@ -82,7 +81,8 @@
|
||||
"glob": "^12.0.0",
|
||||
"node-domexception": "npm:empty@^0.10.1",
|
||||
"prebuild-install": "npm:nop@1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
"cross-spawn": "^7.0.6",
|
||||
"minimatch": "^10.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"gemini": "bundle/gemini.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -112,7 +112,6 @@ export function createMockConfig(
|
||||
}),
|
||||
isContextManagementEnabled: vi.fn().mockReturnValue(false),
|
||||
getContextManagementConfig: vi.fn().mockReturnValue({ enabled: false }),
|
||||
getExperimentalGemma: vi.fn().mockReturnValue(false),
|
||||
...overrides,
|
||||
} as unknown as Config;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"description": "Gemini CLI",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.41.0-nightly.20260423.gaa05b4583"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.40.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
|
||||
@@ -3055,18 +3055,6 @@ describe('loadCliConfig gemmaModelRouter', () => {
|
||||
expect(gemmaSettings.classifier?.model).toBe('custom-gemma');
|
||||
});
|
||||
|
||||
it('should load experimental.gemma setting from merged settings', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
gemma: true,
|
||||
},
|
||||
});
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getExperimentalGemma()).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle partial gemmaModelRouter settings', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
|
||||
@@ -1011,7 +1011,6 @@ export async function loadCliConfig(
|
||||
experimentalJitContext,
|
||||
experimentalMemoryV2: settings.experimental?.memoryV2,
|
||||
experimentalAutoMemory: settings.experimental?.autoMemory,
|
||||
experimentalGemma: settings.experimental?.gemma,
|
||||
contextManagement,
|
||||
modelSteering: settings.experimental?.modelSteering,
|
||||
topicUpdateNarration:
|
||||
|
||||
@@ -2052,15 +2052,6 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Setting to enable experimental features',
|
||||
showInDialog: false,
|
||||
properties: {
|
||||
gemma: {
|
||||
type: 'boolean',
|
||||
label: 'Gemma Models',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable access to Gemma 4 models (experimental).',
|
||||
showInDialog: true,
|
||||
},
|
||||
adk: {
|
||||
type: 'object',
|
||||
label: 'ADK',
|
||||
|
||||
@@ -306,7 +306,6 @@ describe('gemini.tsx main function cleanup', () => {
|
||||
getMessageBus: () => ({ subscribe: vi.fn() }),
|
||||
getEnableHooks: vi.fn(() => true),
|
||||
getHookSystem: vi.fn(() => undefined),
|
||||
getExperimentalGemma: vi.fn(() => false),
|
||||
initialize: vi.fn(),
|
||||
storage: { initialize: vi.fn().mockResolvedValue(undefined) },
|
||||
getContentGeneratorConfig: vi.fn(),
|
||||
|
||||
@@ -89,7 +89,6 @@ describe('ShellProcessor', () => {
|
||||
getPolicyEngine: vi.fn().mockReturnValue({
|
||||
check: mockPolicyEngineCheck,
|
||||
}),
|
||||
getExperimentalGemma: vi.fn().mockReturnValue(false),
|
||||
get config() {
|
||||
return this as unknown as Config;
|
||||
},
|
||||
|
||||
@@ -168,7 +168,6 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
getAdminSkillsEnabled: vi.fn().mockReturnValue(false),
|
||||
getDisabledSkills: vi.fn().mockReturnValue([]),
|
||||
getExperimentalJitContext: vi.fn().mockReturnValue(false),
|
||||
getExperimentalGemma: vi.fn().mockReturnValue(false),
|
||||
getMemoryBoundaryMarkers: vi.fn().mockReturnValue(['.git']),
|
||||
getTerminalBackground: vi.fn().mockReturnValue(undefined),
|
||||
getEmbeddingModel: vi.fn().mockReturnValue('embedding-model'),
|
||||
|
||||
@@ -65,7 +65,6 @@ describe('<ModelDialog />', () => {
|
||||
getGemini31FlashLiteLaunchedSync: () => boolean;
|
||||
getProModelNoAccess: () => Promise<boolean>;
|
||||
getProModelNoAccessSync: () => boolean;
|
||||
getExperimentalGemma: () => boolean;
|
||||
getLastRetrievedQuota: () =>
|
||||
| {
|
||||
buckets: Array<{
|
||||
@@ -86,7 +85,6 @@ describe('<ModelDialog />', () => {
|
||||
getGemini31FlashLiteLaunchedSync: mockGetGemini31FlashLiteLaunchedSync,
|
||||
getProModelNoAccess: mockGetProModelNoAccess,
|
||||
getProModelNoAccessSync: mockGetProModelNoAccessSync,
|
||||
getExperimentalGemma: () => false,
|
||||
getLastRetrievedQuota: () => ({ buckets: [] }),
|
||||
getSessionId: () => 'test-session-id',
|
||||
};
|
||||
|
||||
@@ -19,8 +19,6 @@ import {
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
GEMMA_4_31B_IT_MODEL,
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
ModelSlashCommandEvent,
|
||||
logModelSlashCommand,
|
||||
getDisplayString,
|
||||
@@ -224,9 +222,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
}
|
||||
|
||||
// --- LEGACY PATH ---
|
||||
const showGemmaModels = config?.getExperimentalGemma() ?? false;
|
||||
|
||||
const options = [
|
||||
const list = [
|
||||
{
|
||||
value: DEFAULT_GEMINI_MODEL,
|
||||
title: getDisplayString(DEFAULT_GEMINI_MODEL),
|
||||
@@ -244,21 +240,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
if (showGemmaModels) {
|
||||
options.push(
|
||||
{
|
||||
value: GEMMA_4_31B_IT_MODEL,
|
||||
title: getDisplayString(GEMMA_4_31B_IT_MODEL),
|
||||
key: GEMMA_4_31B_IT_MODEL,
|
||||
},
|
||||
{
|
||||
value: GEMMA_4_26B_A4B_IT_MODEL,
|
||||
title: getDisplayString(GEMMA_4_26B_A4B_IT_MODEL),
|
||||
key: GEMMA_4_26B_A4B_IT_MODEL,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldShowPreviewModels) {
|
||||
const previewProModel = useGemini31
|
||||
? PREVIEW_GEMINI_3_1_MODEL
|
||||
@@ -289,15 +270,15 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
});
|
||||
}
|
||||
|
||||
options.unshift(...previewOptions);
|
||||
list.unshift(...previewOptions);
|
||||
}
|
||||
|
||||
if (!hasAccessToProModel) {
|
||||
// Filter out all Pro models for free tier
|
||||
return options.filter((option) => !isProModel(option.value));
|
||||
return list.filter((option) => !isProModel(option.value));
|
||||
}
|
||||
|
||||
return options;
|
||||
return list;
|
||||
}, [
|
||||
shouldShowPreviewModels,
|
||||
useGemini31,
|
||||
|
||||
@@ -86,7 +86,6 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
getProjectTempDir: () => '/tmp/test',
|
||||
},
|
||||
getSessionId: () => 'default-session-id',
|
||||
getExperimentalGemma: () => false,
|
||||
...overrides,
|
||||
}) as Config;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ enum TerminalKeys {
|
||||
LEFT_ARROW = '\u001B[D',
|
||||
RIGHT_ARROW = '\u001B[C',
|
||||
ESCAPE = '\u001B',
|
||||
BACKSPACE = '\x7f',
|
||||
BACKSPACE = '\u0008',
|
||||
CTRL_P = '\u0010',
|
||||
CTRL_N = '\u000E',
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import { HintMessage } from './HintMessage.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
|
||||
describe('HintMessage', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders normal hint message with correct prefix', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<HintMessage text="Try this instead" />,
|
||||
{ width: 80 },
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('💡');
|
||||
expect(output).toContain('Steering Hint: Try this instead');
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('with NO_COLOR set', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('NO_COLOR', '1');
|
||||
});
|
||||
|
||||
it('uses margins instead of background blocks when NO_COLOR is set', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<HintMessage text="Try this instead" />,
|
||||
{ width: 80, config: makeFakeConfig({ useBackgroundColor: true }) },
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
// In NO_COLOR mode, the block characters (▄/▀) should NOT be present.
|
||||
expect(output).not.toContain('▄');
|
||||
expect(output).not.toContain('▀');
|
||||
|
||||
const lines = output.split('\n').filter((l) => l.trim() !== '');
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0]).toContain('💡');
|
||||
expect(lines[0]).toContain('Steering Hint: Try this instead');
|
||||
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -19,9 +19,7 @@ export const HintMessage: React.FC<HintMessageProps> = ({ text }) => {
|
||||
const prefix = '💡 ';
|
||||
const prefixWidth = prefix.length;
|
||||
const config = useConfig();
|
||||
const useBackgroundColorSetting = config.getUseBackgroundColor();
|
||||
const useBackgroundColor =
|
||||
useBackgroundColorSetting && !!theme.background.message;
|
||||
const useBackgroundColor = config.getUseBackgroundColor();
|
||||
|
||||
return (
|
||||
<HalfLinePaddedBox
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import { UserMessage } from './UserMessage.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the commandUtils to control isSlashCommand behavior
|
||||
vi.mock('../../utils/commandUtils.js', () => ({
|
||||
@@ -15,11 +14,6 @@ vi.mock('../../utils/commandUtils.js', () => ({
|
||||
}));
|
||||
|
||||
describe('UserMessage', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders normal user message with correct prefix', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<UserMessage text="Hello Gemini" width={80} />,
|
||||
@@ -66,32 +60,4 @@ describe('UserMessage', () => {
|
||||
expect(output).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('with NO_COLOR set', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('NO_COLOR', '1');
|
||||
});
|
||||
|
||||
it('uses margins instead of background blocks when NO_COLOR is set', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<UserMessage text="Hello Gemini" width={80} />,
|
||||
{ width: 80, config: makeFakeConfig({ useBackgroundColor: true }) },
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
// In NO_COLOR mode, the block characters (▄/▀) should NOT be present.
|
||||
expect(output).not.toContain('▄');
|
||||
expect(output).not.toContain('▀');
|
||||
|
||||
// There should be empty lines above and below the message due to marginY={1}.
|
||||
// lastFrame() returns the full buffer, so we can check for leading/trailing newlines or empty lines.
|
||||
const lines = output.split('\n').filter((l) => l.trim() !== '');
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0]).toContain('> Hello Gemini');
|
||||
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,9 +27,7 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text, width }) => {
|
||||
const prefixWidth = prefix.length;
|
||||
const isSlashCommand = checkIsSlashCommand(text);
|
||||
const config = useConfig();
|
||||
const useBackgroundColorSetting = config.getUseBackgroundColor();
|
||||
const useBackgroundColor =
|
||||
useBackgroundColorSetting && !!theme.background.message;
|
||||
const useBackgroundColor = config.getUseBackgroundColor();
|
||||
|
||||
const textColor = isSlashCommand ? theme.text.accent : theme.text.primary;
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import { UserShellMessage } from './UserShellMessage.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
|
||||
describe('UserShellMessage', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders normal shell message with correct prefix', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<UserShellMessage text="ls -la" width={80} />,
|
||||
{ width: 80 },
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('$ ls -la');
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('with NO_COLOR set', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('NO_COLOR', '1');
|
||||
});
|
||||
|
||||
it('uses margins instead of background blocks when NO_COLOR is set', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<UserShellMessage text="ls -la" width={80} />,
|
||||
{ width: 80, config: makeFakeConfig({ useBackgroundColor: true }) },
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
// In NO_COLOR mode, the block characters (▄/▀) should NOT be present.
|
||||
expect(output).not.toContain('▄');
|
||||
expect(output).not.toContain('▀');
|
||||
|
||||
const lines = output.split('\n').filter((l) => l.trim() !== '');
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0]).toContain('$ ls -la');
|
||||
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,9 +20,7 @@ export const UserShellMessage: React.FC<UserShellMessageProps> = ({
|
||||
width,
|
||||
}) => {
|
||||
const config = useConfig();
|
||||
const useBackgroundColorSetting = config.getUseBackgroundColor();
|
||||
const useBackgroundColor =
|
||||
useBackgroundColorSetting && !!theme.background.message;
|
||||
const useBackgroundColor = config.getUseBackgroundColor();
|
||||
|
||||
// Remove leading '!' if present, as App.tsx adds it for the processor.
|
||||
const commandToDisplay = text.startsWith('!') ? text.substring(1) : text;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`HintMessage > with NO_COLOR set > uses margins instead of background blocks when NO_COLOR is set 1`] = `
|
||||
"
|
||||
💡 Steering Hint: Try this instead
|
||||
"
|
||||
`;
|
||||
@@ -28,9 +28,3 @@ exports[`UserMessage > transforms image paths in user message 1`] = `
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`UserMessage > with NO_COLOR set > uses margins instead of background blocks when NO_COLOR is set 1`] = `
|
||||
"
|
||||
> Hello Gemini
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`UserShellMessage > with NO_COLOR set > uses margins instead of background blocks when NO_COLOR is set 1`] = `
|
||||
"
|
||||
$ ls -la
|
||||
"
|
||||
`;
|
||||
@@ -24,7 +24,7 @@ enum TerminalKeys {
|
||||
LEFT_ARROW = '\u001B[D',
|
||||
RIGHT_ARROW = '\u001B[C',
|
||||
ESCAPE = '\u001B',
|
||||
BACKSPACE = '\x7f',
|
||||
BACKSPACE = '\u0008',
|
||||
CTRL_L = '\u000C',
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,7 @@ import { act } from 'react';
|
||||
import { renderHookWithProviders } from '../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import type { Mock } from 'vitest';
|
||||
import {
|
||||
vi,
|
||||
afterAll,
|
||||
beforeAll,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
} from 'vitest';
|
||||
import { vi, afterAll, beforeAll, type Mock } from 'vitest';
|
||||
import {
|
||||
useKeypressContext,
|
||||
ESC_TIMEOUT,
|
||||
@@ -441,80 +431,6 @@ describe('KeypressContext', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('Windows Terminal Backspace handling', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should NOT treat \\b as ctrl when WT_SESSION is NOT present and OS is not Windows_NT', async () => {
|
||||
vi.stubEnv('WT_SESSION', '');
|
||||
vi.stubEnv('OS', 'Linux');
|
||||
const { keyHandler } = await setupKeypressTest();
|
||||
|
||||
act(() => {
|
||||
stdin.write('\b');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should treat \\b as ctrl when WT_SESSION IS present (even if not Windows_NT)', async () => {
|
||||
vi.stubEnv('WT_SESSION', 'some-id');
|
||||
vi.stubEnv('OS', 'Linux');
|
||||
const { keyHandler } = await setupKeypressTest();
|
||||
|
||||
act(() => {
|
||||
stdin.write('\b');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'backspace',
|
||||
ctrl: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should treat \\b as ctrl when OS is Windows_NT', async () => {
|
||||
vi.stubEnv('WT_SESSION', '');
|
||||
vi.stubEnv('OS', 'Windows_NT');
|
||||
const { keyHandler } = await setupKeypressTest();
|
||||
|
||||
act(() => {
|
||||
stdin.write('\b');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'backspace',
|
||||
ctrl: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should treat \\x7f as regular backspace regardless of WT_SESSION or OS', async () => {
|
||||
vi.stubEnv('WT_SESSION', 'some-id');
|
||||
vi.stubEnv('OS', 'Windows_NT');
|
||||
const { keyHandler } = await setupKeypressTest();
|
||||
|
||||
act(() => {
|
||||
stdin.write('\x7f');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paste mode', () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -651,20 +651,8 @@ function* emitKeys(
|
||||
// tab
|
||||
name = 'tab';
|
||||
alt = escaped;
|
||||
} else if (ch === '\b') {
|
||||
// ctrl+h / ctrl+backspace (windows terminals send \x08 for ctrl+backspace)
|
||||
name = 'backspace';
|
||||
// In Windows environments, \b is sent for Ctrl+Backspace (standard backspace is translated to \x7f).
|
||||
// We scope this to Windows/WT_SESSION to avoid breaking other unixes where \b is a plain backspace.
|
||||
if (
|
||||
typeof process !== 'undefined' &&
|
||||
(process.env?.['OS'] === 'Windows_NT' || !!process.env?.['WT_SESSION'])
|
||||
) {
|
||||
ctrl = true;
|
||||
}
|
||||
alt = escaped;
|
||||
} else if (ch === '\x7f') {
|
||||
// backspace
|
||||
} else if (ch === '\b' || ch === '\x7f') {
|
||||
// backspace or ctrl+h
|
||||
name = 'backspace';
|
||||
alt = escaped;
|
||||
} else if (ch === ESC) {
|
||||
|
||||
@@ -513,70 +513,6 @@ describe('useSlashCompletion', () => {
|
||||
unmountResume();
|
||||
});
|
||||
|
||||
it('should NOT suggest the auto-list command when typing a non-matching partial after /chat', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
name: 'chat',
|
||||
description: 'Manage chat history',
|
||||
subCommands: [
|
||||
createTestCommand({ name: 'list', description: 'List chats' }),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/chat x', // 'x' does not match 'list'
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
);
|
||||
|
||||
await resolveMatch();
|
||||
|
||||
await waitFor(() => {
|
||||
// It should NOT have the 'auto' section 'list' suggestion
|
||||
const autoSuggestion = result.current.suggestions.find(
|
||||
(s) => s.sectionTitle === 'auto',
|
||||
);
|
||||
expect(autoSuggestion).toBeUndefined();
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should STILL suggest the auto-list command when typing a matching partial after /chat', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
name: 'chat',
|
||||
description: 'Manage chat history',
|
||||
subCommands: [
|
||||
createTestCommand({ name: 'list', description: 'List chats' }),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/chat l', // 'l' matches 'list'
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
);
|
||||
|
||||
await resolveMatch();
|
||||
|
||||
await waitFor(() => {
|
||||
const autoSuggestion = result.current.suggestions.find(
|
||||
(s) => s.sectionTitle === 'auto',
|
||||
);
|
||||
expect(autoSuggestion).toBeDefined();
|
||||
expect(autoSuggestion?.label).toBe('list');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should sort exact altName matches to the top', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
|
||||
@@ -338,23 +338,17 @@ function useCommandSuggestions(
|
||||
|
||||
if (isTopLevelChatOrResumeContext) {
|
||||
const canonicalParentName = leafCommand.name;
|
||||
const autoLabel = 'list';
|
||||
if (
|
||||
partial === '' ||
|
||||
autoLabel.toLowerCase().startsWith(partial.toLowerCase())
|
||||
) {
|
||||
const autoSectionSuggestion: Suggestion = {
|
||||
label: autoLabel,
|
||||
value: autoLabel,
|
||||
insertValue: canonicalParentName,
|
||||
description: 'Browse auto-saved chats',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
sectionTitle: 'auto',
|
||||
submitValue: `/${canonicalParentName}`,
|
||||
};
|
||||
setSuggestions([autoSectionSuggestion, ...finalSuggestions]);
|
||||
return;
|
||||
}
|
||||
const autoSectionSuggestion: Suggestion = {
|
||||
label: 'list',
|
||||
value: 'list',
|
||||
insertValue: canonicalParentName,
|
||||
description: 'Browse auto-saved chats',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
sectionTitle: 'auto',
|
||||
submitValue: `/${canonicalParentName}`,
|
||||
};
|
||||
setSuggestions([autoSectionSuggestion, ...finalSuggestions]);
|
||||
return;
|
||||
}
|
||||
|
||||
setSuggestions(finalSuggestions);
|
||||
|
||||
@@ -69,7 +69,6 @@ describe('Session Cleanup (Refactored)', () => {
|
||||
},
|
||||
getSessionId: () => 'current123',
|
||||
getDebugMode: () => false,
|
||||
getExperimentalGemma: () => false,
|
||||
initialize: async () => {},
|
||||
...overrides,
|
||||
} as unknown as Config;
|
||||
|
||||
@@ -12,8 +12,6 @@ export {
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
GEMMA_4_31B_IT_MODEL,
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
} from './src/config/models.js';
|
||||
export {
|
||||
serializeTerminalToObject,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"description": "Gemini CLI Core",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('policyCatalog', () => {
|
||||
it('marks preview transients as sticky retries', () => {
|
||||
const [previewPolicy] = getModelPolicyChain({ previewEnabled: true });
|
||||
expect(previewPolicy.model).toBe(PREVIEW_GEMINI_MODEL);
|
||||
expect(previewPolicy.stateTransitions.transient).toBe('terminal');
|
||||
expect(previewPolicy.stateTransitions.transient).toBe('sticky_retry');
|
||||
});
|
||||
|
||||
it('applies default actions and state transitions for unspecified kinds', () => {
|
||||
|
||||
@@ -50,7 +50,7 @@ export const SILENT_ACTIONS: ModelPolicyActionMap = {
|
||||
|
||||
const DEFAULT_STATE: ModelPolicyStateMap = {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
};
|
||||
|
||||
@@ -960,11 +960,8 @@ describe('Server Config (config.ts)', () => {
|
||||
});
|
||||
|
||||
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
|
||||
await config.getExperimentsAsync();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(config.getModel()).toBe(PREVIEW_GEMINI_FLASH_MODEL);
|
||||
});
|
||||
expect(config.getModel()).toBe(PREVIEW_GEMINI_FLASH_MODEL);
|
||||
});
|
||||
|
||||
it('should NOT switch to flash model if user has Pro access and model is auto', async () => {
|
||||
@@ -3645,47 +3642,6 @@ describe('Config JIT Initialization', () => {
|
||||
expect(config.isAutoMemoryEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when experimentalGemma is true', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
experimentalGemma: true,
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
expect(config.getExperimentalGemma()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when experimentalGemma is false', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
experimentalGemma: false,
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
expect(config.getExperimentalGemma()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when experimentalGemma is not provided', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
expect(config.getExperimentalGemma()).toBe(false);
|
||||
});
|
||||
|
||||
it('should be independent of experimentalMemoryV2', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
|
||||
@@ -711,7 +711,6 @@ export interface ConfigParameters {
|
||||
autoDistillation?: boolean;
|
||||
experimentalMemoryV2?: boolean;
|
||||
experimentalAutoMemory?: boolean;
|
||||
experimentalGemma?: boolean;
|
||||
experimentalContextManagementConfig?: string;
|
||||
experimentalAgentHistoryTruncation?: boolean;
|
||||
experimentalAgentHistoryTruncationThreshold?: number;
|
||||
@@ -957,7 +956,6 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private readonly experimentalJitContext: boolean;
|
||||
private readonly experimentalMemoryV2: boolean;
|
||||
private readonly experimentalAutoMemory: boolean;
|
||||
private readonly experimentalGemma: boolean;
|
||||
private readonly experimentalContextManagementConfig?: string;
|
||||
private readonly memoryBoundaryMarkers: readonly string[];
|
||||
private readonly topicUpdateNarration: boolean;
|
||||
@@ -1176,7 +1174,6 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.experimentalJitContext = params.experimentalJitContext ?? true;
|
||||
this.experimentalMemoryV2 = params.experimentalMemoryV2 ?? true;
|
||||
this.experimentalAutoMemory = params.experimentalAutoMemory ?? false;
|
||||
this.experimentalGemma = params.experimentalGemma ?? false;
|
||||
this.experimentalContextManagementConfig =
|
||||
params.experimentalContextManagementConfig;
|
||||
this.memoryBoundaryMarkers = params.memoryBoundaryMarkers ?? ['.git'];
|
||||
@@ -1593,12 +1590,8 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const [experiments] = await Promise.all([
|
||||
this.experimentsPromise,
|
||||
quotaPromise.catch((e) => {
|
||||
debugLogger.error('Failed to fetch user quota', e);
|
||||
}),
|
||||
]);
|
||||
// Fetch experiments and update timeouts before continuing initialization
|
||||
const experiments = await this.experimentsPromise;
|
||||
|
||||
const requestTimeoutMs = this.getRequestTimeoutMs();
|
||||
if (requestTimeoutMs !== undefined) {
|
||||
@@ -1608,6 +1601,8 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
// Initialize BaseLlmClient now that the ContentGenerator and experiments are available
|
||||
this.baseLlmClient = new BaseLlmClient(this.contentGenerator, this);
|
||||
|
||||
await quotaPromise;
|
||||
|
||||
const authType = this.contentGeneratorConfig.authType;
|
||||
if (
|
||||
authType === AuthType.USE_GEMINI ||
|
||||
@@ -1628,21 +1623,16 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
const adminControlsEnabled =
|
||||
experiments?.flags[ExperimentFlags.ENABLE_ADMIN_CONTROLS]?.boolValue ??
|
||||
false;
|
||||
|
||||
try {
|
||||
const adminControls = await fetchAdminControls(
|
||||
codeAssistServer,
|
||||
this.getRemoteAdminSettings(),
|
||||
adminControlsEnabled,
|
||||
(newSettings: AdminControlsSettings) => {
|
||||
this.setRemoteAdminSettings(newSettings);
|
||||
coreEvents.emitAdminSettingsChanged();
|
||||
},
|
||||
);
|
||||
this.setRemoteAdminSettings(adminControls);
|
||||
} catch (e) {
|
||||
debugLogger.error('Failed to fetch admin controls', e);
|
||||
}
|
||||
const adminControls = await fetchAdminControls(
|
||||
codeAssistServer,
|
||||
this.getRemoteAdminSettings(),
|
||||
adminControlsEnabled,
|
||||
(newSettings: AdminControlsSettings) => {
|
||||
this.setRemoteAdminSettings(newSettings);
|
||||
coreEvents.emitAdminSettingsChanged();
|
||||
},
|
||||
);
|
||||
this.setRemoteAdminSettings(adminControls);
|
||||
|
||||
if ((await this.getProModelNoAccess()) && isAutoModel(this.model)) {
|
||||
this.setModel(PREVIEW_GEMINI_FLASH_MODEL);
|
||||
@@ -2524,10 +2514,6 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.experimentalAutoMemory;
|
||||
}
|
||||
|
||||
getExperimentalGemma(): boolean {
|
||||
return this.experimentalGemma;
|
||||
}
|
||||
|
||||
getExperimentalContextManagementConfig(): string | undefined {
|
||||
return this.experimentalContextManagementConfig;
|
||||
}
|
||||
|
||||
@@ -89,19 +89,6 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
model: 'gemini-2.5-flash-lite',
|
||||
},
|
||||
},
|
||||
'gemma-4-31b-it': {
|
||||
extends: 'chat-base-3',
|
||||
modelConfig: {
|
||||
model: 'gemma-4-31b-it',
|
||||
},
|
||||
},
|
||||
'gemma-4-26b-a4b-it': {
|
||||
extends: 'chat-base-3',
|
||||
modelConfig: {
|
||||
model: 'gemma-4-26b-a4b-it',
|
||||
},
|
||||
},
|
||||
|
||||
// Bases for the internal model configs.
|
||||
'gemini-2.5-flash-base': {
|
||||
extends: 'base',
|
||||
@@ -330,23 +317,6 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
isVisible: true,
|
||||
features: { thinking: false, multimodalToolUse: false },
|
||||
},
|
||||
'gemma-4-31b-it': {
|
||||
displayName: 'gemma-4-31b-it',
|
||||
tier: 'custom',
|
||||
family: 'gemma-4',
|
||||
isPreview: false,
|
||||
isVisible: true,
|
||||
features: { thinking: true, multimodalToolUse: false },
|
||||
},
|
||||
'gemma-4-26b-a4b-it': {
|
||||
displayName: 'gemma-4-26b-a4b-it',
|
||||
tier: 'custom',
|
||||
family: 'gemma-4',
|
||||
isPreview: false,
|
||||
isVisible: true,
|
||||
features: { thinking: true, multimodalToolUse: false },
|
||||
},
|
||||
|
||||
// Aliases
|
||||
auto: {
|
||||
tier: 'auto',
|
||||
@@ -392,13 +362,6 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
},
|
||||
modelIdResolutions: {
|
||||
'gemma-4-31b-it': {
|
||||
default: 'gemma-4-31b-it',
|
||||
},
|
||||
'gemma-4-26b-a4b-it': {
|
||||
default: 'gemma-4-26b-a4b-it',
|
||||
},
|
||||
|
||||
'gemini-3.1-pro-preview': {
|
||||
default: 'gemini-3.1-pro-preview',
|
||||
contexts: [
|
||||
@@ -557,7 +520,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -573,7 +536,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -590,7 +553,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -606,7 +569,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -623,7 +586,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -638,7 +601,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
@@ -654,7 +617,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
transient: 'sticky_retry',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
|
||||
@@ -32,8 +32,6 @@ import {
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
isPreviewModel,
|
||||
isProModel,
|
||||
GEMMA_4_31B_IT_MODEL,
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
} from './models.js';
|
||||
import type { Config } from './config.js';
|
||||
import { ModelConfigService } from '../services/modelConfigService.js';
|
||||
@@ -358,10 +356,6 @@ describe('getDisplayString', () => {
|
||||
|
||||
it('should return the model name as is for other models', () => {
|
||||
expect(getDisplayString('custom-model')).toBe('custom-model');
|
||||
expect(getDisplayString(GEMMA_4_31B_IT_MODEL)).toBe(GEMMA_4_31B_IT_MODEL);
|
||||
expect(getDisplayString(GEMMA_4_26B_A4B_IT_MODEL)).toBe(
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
);
|
||||
expect(getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe(
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
);
|
||||
@@ -579,17 +573,6 @@ describe('isActiveModel', () => {
|
||||
expect(isActiveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for Gemma 4 models only when experimentalGemma is true', () => {
|
||||
expect(isActiveModel(GEMMA_4_31B_IT_MODEL)).toBe(false);
|
||||
expect(isActiveModel(GEMMA_4_26B_A4B_IT_MODEL)).toBe(false);
|
||||
expect(isActiveModel(GEMMA_4_31B_IT_MODEL, false, false, false, true)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isActiveModel(GEMMA_4_26B_A4B_IT_MODEL, false, false, false, true),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for Gemini 3.1 models when Gemini 3.1 is not launched', () => {
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL)).toBe(false);
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(false);
|
||||
|
||||
@@ -61,9 +61,6 @@ export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
|
||||
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
|
||||
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
|
||||
|
||||
export const GEMMA_4_31B_IT_MODEL = 'gemma-4-31b-it';
|
||||
export const GEMMA_4_26B_A4B_IT_MODEL = 'gemma-4-26b-a4b-it';
|
||||
|
||||
export const VALID_GEMINI_MODELS = new Set([
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
@@ -73,9 +70,6 @@ export const VALID_GEMINI_MODELS = new Set([
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
|
||||
GEMMA_4_31B_IT_MODEL,
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
]);
|
||||
|
||||
export const PREVIEW_GEMINI_MODEL_AUTO = 'auto-gemini-3';
|
||||
@@ -263,10 +257,6 @@ export function getDisplayString(
|
||||
return 'Auto (Gemini 3)';
|
||||
case DEFAULT_GEMINI_MODEL_AUTO:
|
||||
return 'Auto (Gemini 2.5)';
|
||||
case GEMMA_4_31B_IT_MODEL:
|
||||
return GEMMA_4_31B_IT_MODEL;
|
||||
case GEMMA_4_26B_A4B_IT_MODEL:
|
||||
return GEMMA_4_26B_A4B_IT_MODEL;
|
||||
case GEMINI_MODEL_ALIAS_PRO:
|
||||
return PREVIEW_GEMINI_MODEL;
|
||||
case GEMINI_MODEL_ALIAS_FLASH:
|
||||
@@ -448,14 +438,10 @@ export function isActiveModel(
|
||||
useGemini3_1: boolean = false,
|
||||
useGemini3_1FlashLite: boolean = false,
|
||||
useCustomToolModel: boolean = false,
|
||||
experimentalGemma: boolean = false,
|
||||
): boolean {
|
||||
if (!VALID_GEMINI_MODELS.has(model)) {
|
||||
return false;
|
||||
}
|
||||
if (model === GEMMA_4_31B_IT_MODEL || model === GEMMA_4_26B_A4B_IT_MODEL) {
|
||||
return experimentalGemma;
|
||||
}
|
||||
if (model === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL) {
|
||||
return useGemini3_1FlashLite;
|
||||
}
|
||||
|
||||
@@ -1244,6 +1244,9 @@ ${JSON.stringify(
|
||||
count: 2,
|
||||
});
|
||||
|
||||
const abortSpy = vi.spyOn(AbortController.prototype, 'abort');
|
||||
|
||||
// Act
|
||||
const stream = client.sendMessageStream(
|
||||
[{ text: 'Hi' }],
|
||||
new AbortController().signal,
|
||||
@@ -1264,6 +1267,7 @@ ${JSON.stringify(
|
||||
|
||||
// Assert
|
||||
expect(events).toContainEqual({ type: GeminiEventType.LoopDetected });
|
||||
expect(abortSpy).toHaveBeenCalled();
|
||||
expect(finalResult).toBeInstanceOf(Turn);
|
||||
});
|
||||
|
||||
|
||||
@@ -684,6 +684,9 @@ export class GeminiClient {
|
||||
// Re-initialize turn with fresh history
|
||||
turn = new Turn(this.getChat(), prompt_id);
|
||||
|
||||
const controller = new AbortController();
|
||||
const linkedSignal = AbortSignal.any([signal, controller.signal]);
|
||||
|
||||
const loopResult = await this.loopDetector.turnStarted(signal);
|
||||
if (loopResult.count > 1) {
|
||||
yield { type: GeminiEventType.LoopDetected };
|
||||
@@ -744,7 +747,7 @@ export class GeminiClient {
|
||||
const resultStream = turn.run(
|
||||
modelConfigKey,
|
||||
request,
|
||||
signal,
|
||||
linkedSignal,
|
||||
displayContent,
|
||||
);
|
||||
let isError = false;
|
||||
@@ -780,6 +783,7 @@ export class GeminiClient {
|
||||
}
|
||||
|
||||
if (loopDetectedAbort) {
|
||||
controller.abort();
|
||||
return turn;
|
||||
}
|
||||
|
||||
@@ -791,8 +795,10 @@ export class GeminiClient {
|
||||
boundedTurns,
|
||||
isInvalidStreamRetry,
|
||||
displayContent,
|
||||
controller,
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return turn;
|
||||
}
|
||||
@@ -1246,7 +1252,10 @@ export class GeminiClient {
|
||||
boundedTurns: number,
|
||||
isInvalidStreamRetry: boolean,
|
||||
displayContent?: PartListUnion,
|
||||
controllerToAbort?: AbortController,
|
||||
): AsyncGenerator<ServerGeminiStreamEvent, Turn> {
|
||||
controllerToAbort?.abort();
|
||||
|
||||
// Clear the detection flag so the recursive turn can proceed, but the count remains 1.
|
||||
this.loopDetector.clearDetection();
|
||||
|
||||
|
||||
@@ -10,23 +10,17 @@ import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
GEMMA_4_31B_IT_MODEL,
|
||||
GEMMA_4_26B_A4B_IT_MODEL,
|
||||
} from '../config/models.js';
|
||||
|
||||
type Model = string;
|
||||
type TokenCount = number;
|
||||
|
||||
export const DEFAULT_TOKEN_LIMIT = 1_048_576;
|
||||
export const GEMMA_4_TOKEN_LIMIT = 256_000;
|
||||
|
||||
export function tokenLimit(model: Model): TokenCount {
|
||||
// Add other models as they become relevant or if specified by config
|
||||
// Pulled from https://ai.google.dev/gemini-api/docs/models
|
||||
switch (model) {
|
||||
case GEMMA_4_31B_IT_MODEL:
|
||||
case GEMMA_4_26B_A4B_IT_MODEL:
|
||||
return GEMMA_4_TOKEN_LIMIT;
|
||||
case PREVIEW_GEMINI_MODEL:
|
||||
case PREVIEW_GEMINI_FLASH_MODEL:
|
||||
case DEFAULT_GEMINI_MODEL:
|
||||
|
||||
@@ -97,30 +97,6 @@
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemma-4-31b-it": {
|
||||
"model": "gemma-4-31b-it",
|
||||
"generateContentConfig": {
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"thinkingConfig": {
|
||||
"includeThoughts": true,
|
||||
"thinkingLevel": "HIGH"
|
||||
},
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemma-4-26b-a4b-it": {
|
||||
"model": "gemma-4-26b-a4b-it",
|
||||
"generateContentConfig": {
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"thinkingConfig": {
|
||||
"includeThoughts": true,
|
||||
"thinkingLevel": "HIGH"
|
||||
},
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash-base": {
|
||||
"model": "gemini-2.5-flash",
|
||||
"generateContentConfig": {
|
||||
|
||||
@@ -97,30 +97,6 @@
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemma-4-31b-it": {
|
||||
"model": "gemma-4-31b-it",
|
||||
"generateContentConfig": {
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"thinkingConfig": {
|
||||
"includeThoughts": true,
|
||||
"thinkingLevel": "HIGH"
|
||||
},
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemma-4-26b-a4b-it": {
|
||||
"model": "gemma-4-26b-a4b-it",
|
||||
"generateContentConfig": {
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"thinkingConfig": {
|
||||
"includeThoughts": true,
|
||||
"thinkingLevel": "HIGH"
|
||||
},
|
||||
"topK": 64
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash-base": {
|
||||
"model": "gemini-2.5-flash",
|
||||
"generateContentConfig": {
|
||||
|
||||
@@ -1993,27 +1993,6 @@ describe('mcp-client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should unconditionally attach a data listener to stderr to prevent process hang', async () => {
|
||||
const mockStderr = {
|
||||
on: vi.fn(),
|
||||
};
|
||||
|
||||
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({
|
||||
stderr: mockStderr,
|
||||
} as unknown as SdkClientStdioLib.StdioClientTransport);
|
||||
|
||||
await createTransport(
|
||||
'test-server',
|
||||
{
|
||||
command: 'test-command',
|
||||
},
|
||||
false, // debugMode = false
|
||||
MOCK_CONTEXT,
|
||||
);
|
||||
|
||||
expect(mockStderr.on).toHaveBeenCalledWith('data', expect.any(Function));
|
||||
});
|
||||
|
||||
it('sets an env variable GEMINI_CLI=1 for stdio MCP servers', async () => {
|
||||
const mockedTransport = vi
|
||||
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
|
||||
|
||||
@@ -2291,40 +2291,31 @@ export async function createTransport(
|
||||
transport = new XcodeMcpBridgeFixTransport(transport);
|
||||
}
|
||||
|
||||
// The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`,
|
||||
// which exposes `stderr` for debug logging. We need to unwrap it to attach the listener.
|
||||
if (debugMode) {
|
||||
// The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`,
|
||||
// which exposes `stderr` for debug logging. We need to unwrap it to attach the listener.
|
||||
|
||||
const underlyingTransport =
|
||||
transport instanceof XcodeMcpBridgeFixTransport
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(transport as unknown as { transport: unknown }).transport
|
||||
: transport;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const underlyingTransport =
|
||||
transport instanceof XcodeMcpBridgeFixTransport
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
(transport as any).transport
|
||||
: transport;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const streamTransport = underlyingTransport as {
|
||||
stderr?: {
|
||||
on: (event: string, listener: (data: unknown) => void) => void;
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
streamTransport &&
|
||||
typeof streamTransport === 'object' &&
|
||||
'stderr' in streamTransport &&
|
||||
streamTransport.stderr &&
|
||||
typeof streamTransport.stderr.on === 'function'
|
||||
) {
|
||||
streamTransport.stderr.on('data', (data: unknown) => {
|
||||
if (debugMode) {
|
||||
const stderrStr = String(data).trim();
|
||||
if (
|
||||
underlyingTransport instanceof StdioClientTransport &&
|
||||
underlyingTransport.stderr
|
||||
) {
|
||||
underlyingTransport.stderr.on('data', (data) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const stderrStr = data.toString().trim();
|
||||
debugLogger.debug(
|
||||
`[DEBUG] [MCP STDERR (${mcpServerName})]: `,
|
||||
stderrStr,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ describe('Tracker Tools Integration', () => {
|
||||
model: 'gemini-3-flash',
|
||||
debugMode: false,
|
||||
});
|
||||
await config.initialize();
|
||||
messageBus = new MessageBus(null as unknown as PolicyEngine, false);
|
||||
});
|
||||
|
||||
@@ -121,14 +120,8 @@ describe('Tracker Tools Integration', () => {
|
||||
);
|
||||
|
||||
const tasks = await config.getTrackerService().listTasks();
|
||||
const parentTask = tasks.find((t) => t.title === 'Parent Task');
|
||||
const childTask = tasks.find((t) => t.title === 'Child Task');
|
||||
|
||||
expect(parentTask).toBeDefined();
|
||||
expect(childTask).toBeDefined();
|
||||
|
||||
const parentId = parentTask!.id;
|
||||
const childId = childTask!.id;
|
||||
const parentId = tasks.find((t) => t.title === 'Parent Task')!.id;
|
||||
const childId = tasks.find((t) => t.title === 'Child Task')!.id;
|
||||
|
||||
// Add Dependency
|
||||
const addDepTool = new TrackerAddDependencyTool(config, messageBus);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"description": "Gemini CLI SDK",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -680,7 +680,6 @@ export class TestRig {
|
||||
key !== 'GEMINI_DEBUG' &&
|
||||
key !== 'GEMINI_CLI_TEST_VAR' &&
|
||||
key !== 'GEMINI_CLI_INTEGRATION_TEST' &&
|
||||
key !== 'GOOGLE_GEMINI_BASE_URL' &&
|
||||
!key.startsWith('GEMINI_CLI_ACTIVITY_LOG')
|
||||
) {
|
||||
delete cleanEnv[key];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"displayName": "Gemini CLI Companion",
|
||||
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
|
||||
"version": "0.41.0-nightly.20260423.gaa05b4583",
|
||||
"version": "0.40.0",
|
||||
"publisher": "google",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
+19
-106
File diff suppressed because one or more lines are too long
@@ -1,51 +0,0 @@
|
||||
# Gemini CLI Bot (Cognitive Repository)
|
||||
|
||||
This directory contains the foundational architecture for the `gemini-cli-bot`,
|
||||
transforming the repository into a proactive, evolutionary system.
|
||||
|
||||
It implements a dual-layer approach to balance immediate responsiveness with
|
||||
long-term strategic optimization.
|
||||
|
||||
## Layered Execution Model
|
||||
|
||||
### 1. System 1: The Pulse (Reflex Layer)
|
||||
|
||||
- **Purpose**: High-frequency, deterministic maintenance and data collection.
|
||||
- **Frequency**: 30-minute cron (`.github/workflows/gemini-cli-bot-pulse.yml`).
|
||||
- **Implementation**: Pure TypeScript/JavaScript scripts.
|
||||
- **Role**: Currently focuses on gathering repository metrics
|
||||
(`tools/gemini-cli-bot/metrics/scripts`).
|
||||
- **Output**: Action execution and `metrics-before.csv` artifact generation.
|
||||
|
||||
### 2. System 2: The Brain (Reasoning Layer)
|
||||
|
||||
- **Purpose**: Strategic investigation, policy refinement, and
|
||||
self-optimization.
|
||||
- **Frequency**: 24-hour cron (`.github/workflows/gemini-cli-bot-brain.yml`).
|
||||
- **Implementation**: Agentic Gemini CLI phases.
|
||||
- **Role**: Analyzing metric trends and running deeper repository health
|
||||
investigations.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- `metrics/`: Contains the deterministic runner (`index.ts`) and individual
|
||||
TypeScript scripts (`scripts/`) that use the GitHub CLI to track metrics like
|
||||
open issues, PR latency, throughput, and reviewer domain expertise.
|
||||
- `processes/scripts/`: Placeholder directory for future deterministic triage
|
||||
and routing scripts.
|
||||
- `investigations/`: Placeholder directory for agentic root-cause analysis
|
||||
phases.
|
||||
- `critique/`: Placeholder directory for policy evaluation.
|
||||
- `history/`: Storage for downloaded metrics artifacts from previous runs.
|
||||
|
||||
## Usage
|
||||
|
||||
To manually collect repository metrics locally, run the following command from
|
||||
the workspace root:
|
||||
|
||||
```bash
|
||||
npm run metrics
|
||||
```
|
||||
|
||||
This will execute all scripts within `metrics/scripts/` and output the results
|
||||
to a `metrics-before.csv` file in the root directory.
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { readdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const SCRIPTS_DIR = join(
|
||||
process.cwd(),
|
||||
'tools',
|
||||
'gemini-cli-bot',
|
||||
'metrics',
|
||||
'scripts',
|
||||
);
|
||||
const OUTPUT_FILE = join(process.cwd(), 'metrics-before.csv');
|
||||
|
||||
function processOutputLine(line: string, results: string[]) {
|
||||
const trimmedLine = line.trim();
|
||||
if (!trimmedLine) return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmedLine);
|
||||
if (
|
||||
parsed &&
|
||||
typeof parsed === 'object' &&
|
||||
'metric' in parsed &&
|
||||
'value' in parsed
|
||||
) {
|
||||
results.push(`${parsed.metric},${parsed.value}`);
|
||||
} else {
|
||||
results.push(trimmedLine);
|
||||
}
|
||||
} catch {
|
||||
results.push(trimmedLine);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const scripts = readdirSync(SCRIPTS_DIR).filter(
|
||||
(file) => file.endsWith('.ts') || file.endsWith('.js'),
|
||||
);
|
||||
|
||||
const results: string[] = ['metric,value'];
|
||||
|
||||
for (const script of scripts) {
|
||||
console.log(`Running metric script: ${script}`);
|
||||
try {
|
||||
const scriptPath = join(SCRIPTS_DIR, script);
|
||||
const output = execSync(`npx tsx ${JSON.stringify(scriptPath)}`, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const lines = output.trim().split('\n');
|
||||
for (const line of lines) {
|
||||
processOutputLine(line, results);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error running ${script}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(OUTPUT_FILE, results.join('\n'));
|
||||
console.log(`Saved metrics to ${OUTPUT_FILE}`);
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
@@ -1,157 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const repoRoot = path.resolve(__dirname, '../../../../');
|
||||
|
||||
try {
|
||||
// 1. Fetch recent PR numbers and reviews from GitHub (so we have reviewer names/logins)
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100, states: MERGED) {
|
||||
nodes {
|
||||
number
|
||||
reviews(first: 20) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
author { login, ... on User { name } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
// 2. Map PR numbers to local commits using git log
|
||||
const logOutput = execSync('git log -n 5000 --format="%H|%s"', {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
const prCommits = new Map<number, string>();
|
||||
for (const line of logOutput.split('\n')) {
|
||||
if (!line) continue;
|
||||
const [hash, subject] = line.split('|');
|
||||
const match = subject.match(/\(#(\d+)\)$/);
|
||||
if (match) {
|
||||
prCommits.set(parseInt(match[1], 10), hash);
|
||||
}
|
||||
}
|
||||
|
||||
let totalMaintainerReviews = 0;
|
||||
let maintainerReviewsWithExpertise = 0;
|
||||
|
||||
for (const pr of data.pullRequests.nodes) {
|
||||
if (!pr.reviews?.nodes || pr.reviews.nodes.length === 0) continue;
|
||||
|
||||
const commitHash = prCommits.get(pr.number);
|
||||
if (!commitHash) continue; // Skip if we don't have the commit locally
|
||||
|
||||
// 3. Get exact files changed using local git diff-tree, bypassing GraphQL limits
|
||||
const diffTreeOutput = execSync(
|
||||
`git diff-tree --no-commit-id --name-only -r ${commitHash}`,
|
||||
{ cwd: repoRoot, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },
|
||||
);
|
||||
const files = diffTreeOutput.split('\n').filter(Boolean);
|
||||
if (files.length === 0) continue;
|
||||
|
||||
// Cache git log authors per path to avoid redundant child_process calls
|
||||
const authorCache = new Map<string, string>();
|
||||
const getAuthors = (targetPath: string) => {
|
||||
if (authorCache.has(targetPath)) return authorCache.get(targetPath)!;
|
||||
try {
|
||||
const authors = execSync(
|
||||
`git log --format="%an|%ae" -- ${JSON.stringify(targetPath)}`,
|
||||
{
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
},
|
||||
).toLowerCase();
|
||||
authorCache.set(targetPath, authors);
|
||||
return authors;
|
||||
} catch {
|
||||
authorCache.set(targetPath, '');
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const reviewersOnPR = new Map<string, { name?: string }>();
|
||||
for (const review of pr.reviews.nodes) {
|
||||
if (
|
||||
['MEMBER', 'OWNER'].includes(review.authorAssociation) &&
|
||||
review.author?.login
|
||||
) {
|
||||
const login = review.author.login.toLowerCase();
|
||||
if (login.endsWith('[bot]') || login.includes('bot')) continue;
|
||||
reviewersOnPR.set(login, review.author);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [login, authorInfo] of reviewersOnPR.entries()) {
|
||||
totalMaintainerReviews++;
|
||||
let hasExpertise = false;
|
||||
const name = authorInfo.name ? authorInfo.name.toLowerCase() : '';
|
||||
|
||||
for (const file of files) {
|
||||
// Precise check: immediate file
|
||||
let authorsStr = getAuthors(file);
|
||||
if (authorsStr.includes(login) || (name && authorsStr.includes(name))) {
|
||||
hasExpertise = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Fallback: file's directory
|
||||
const dir = path.dirname(file);
|
||||
authorsStr = getAuthors(dir);
|
||||
if (authorsStr.includes(login) || (name && authorsStr.includes(name))) {
|
||||
hasExpertise = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExpertise) {
|
||||
maintainerReviewsWithExpertise++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ratio =
|
||||
totalMaintainerReviews > 0
|
||||
? maintainerReviewsWithExpertise / totalMaintainerReviews
|
||||
: 0;
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify(<MetricOutput>{
|
||||
metric: 'domain_expertise',
|
||||
value: Math.round(ratio * 100) / 100,
|
||||
timestamp,
|
||||
details: {
|
||||
totalMaintainerReviews,
|
||||
maintainerReviewsWithExpertise,
|
||||
},
|
||||
}) + '\n',
|
||||
);
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100, states: MERGED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
createdAt
|
||||
mergedAt
|
||||
}
|
||||
}
|
||||
issues(last: 100, states: CLOSED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
createdAt
|
||||
closedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
const prs = data.pullRequests.nodes.map(
|
||||
(p: {
|
||||
authorAssociation: string;
|
||||
mergedAt: string;
|
||||
createdAt: string;
|
||||
}) => ({
|
||||
association: p.authorAssociation,
|
||||
latencyHours:
|
||||
(new Date(p.mergedAt).getTime() - new Date(p.createdAt).getTime()) /
|
||||
(1000 * 60 * 60),
|
||||
}),
|
||||
);
|
||||
const issues = data.issues.nodes.map(
|
||||
(i: {
|
||||
authorAssociation: string;
|
||||
closedAt: string;
|
||||
createdAt: string;
|
||||
}) => ({
|
||||
association: i.authorAssociation,
|
||||
latencyHours:
|
||||
(new Date(i.closedAt).getTime() - new Date(i.createdAt).getTime()) /
|
||||
(1000 * 60 * 60),
|
||||
}),
|
||||
);
|
||||
|
||||
const isMaintainer = (assoc: string) =>
|
||||
['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc);
|
||||
const calculateAvg = (
|
||||
items: { association: string; latencyHours: number }[],
|
||||
) =>
|
||||
items.length
|
||||
? items.reduce((a, b) => a + b.latencyHours, 0) / items.length
|
||||
: 0;
|
||||
|
||||
const prMaintainers = calculateAvg(
|
||||
prs.filter((i: { association: string; latencyHours: number }) =>
|
||||
isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const prCommunity = calculateAvg(
|
||||
prs.filter(
|
||||
(i: { association: string; latencyHours: number }) =>
|
||||
!isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const prOverall = calculateAvg(prs);
|
||||
|
||||
const issueMaintainers = calculateAvg(
|
||||
issues.filter((i: { association: string; latencyHours: number }) =>
|
||||
isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const issueCommunity = calculateAvg(
|
||||
issues.filter(
|
||||
(i: { association: string; latencyHours: number }) =>
|
||||
!isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const issueOverall = calculateAvg(issues);
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
const metrics: MetricOutput[] = [
|
||||
{
|
||||
metric: 'latency_pr_overall_hours',
|
||||
value: Math.round(prOverall * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'latency_pr_maintainers_hours',
|
||||
value: Math.round(prMaintainers * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'latency_pr_community_hours',
|
||||
value: Math.round(prCommunity * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'latency_issue_overall_hours',
|
||||
value: Math.round(issueOverall * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'latency_issue_maintainers_hours',
|
||||
value: Math.round(issueMaintainers * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'latency_issue_community_hours',
|
||||
value: Math.round(issueCommunity * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
];
|
||||
|
||||
metrics.forEach((m) => process.stdout.write(JSON.stringify(m) + '\n'));
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const count = execSync(
|
||||
'gh issue list --state open --limit 1000 --json number --jq length',
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
).trim();
|
||||
console.log(`open_issues,${count}`);
|
||||
} catch {
|
||||
// Fallback if gh fails or no issues found
|
||||
console.log('open_issues,0');
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const count = execSync(
|
||||
'gh pr list --state open --limit 1000 --json number --jq length',
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
).trim();
|
||||
console.log(`open_prs,${count}`);
|
||||
} catch {
|
||||
// Fallback if gh fails or no PRs found
|
||||
console.log('open_prs,0');
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100) {
|
||||
nodes {
|
||||
reviews(first: 50) {
|
||||
nodes {
|
||||
author { login }
|
||||
authorAssociation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
const reviewCounts: Record<string, number> = {};
|
||||
|
||||
for (const pr of data.pullRequests.nodes) {
|
||||
if (!pr.reviews?.nodes) continue;
|
||||
// We only count one review per author per PR to avoid counting multiple review comments as multiple reviews
|
||||
const reviewersOnPR = new Set<string>();
|
||||
|
||||
for (const review of pr.reviews.nodes) {
|
||||
if (
|
||||
['MEMBER', 'OWNER'].includes(review.authorAssociation) &&
|
||||
review.author?.login
|
||||
) {
|
||||
const login = review.author.login.toLowerCase();
|
||||
if (login.endsWith('[bot]') || login.includes('bot')) {
|
||||
continue; // Ignore bots
|
||||
}
|
||||
reviewersOnPR.add(review.author.login);
|
||||
}
|
||||
}
|
||||
|
||||
for (const reviewer of reviewersOnPR) {
|
||||
reviewCounts[reviewer] = (reviewCounts[reviewer] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const counts = Object.values(reviewCounts);
|
||||
|
||||
let variance = 0;
|
||||
if (counts.length > 0) {
|
||||
const mean = counts.reduce((a, b) => a + b, 0) / counts.length;
|
||||
variance =
|
||||
counts.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / counts.length;
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify(<MetricOutput>{
|
||||
metric: 'review_distribution_variance',
|
||||
value: Math.round(variance * 100) / 100,
|
||||
timestamp,
|
||||
details: reviewCounts,
|
||||
}) + '\n',
|
||||
);
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100, states: MERGED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
mergedAt
|
||||
}
|
||||
}
|
||||
issues(last: 100, states: CLOSED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
closedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
const prs = data.pullRequests.nodes
|
||||
.map((p: { authorAssociation: string; mergedAt: string }) => ({
|
||||
association: p.authorAssociation,
|
||||
date: new Date(p.mergedAt).getTime(),
|
||||
}))
|
||||
.sort((a: { date: number }, b: { date: number }) => a.date - b.date);
|
||||
|
||||
const issues = data.issues.nodes
|
||||
.map((i: { authorAssociation: string; closedAt: string }) => ({
|
||||
association: i.authorAssociation,
|
||||
date: new Date(i.closedAt).getTime(),
|
||||
}))
|
||||
.sort((a: { date: number }, b: { date: number }) => a.date - b.date);
|
||||
|
||||
const isMaintainer = (assoc: string) =>
|
||||
['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc);
|
||||
|
||||
const calculateThroughput = (
|
||||
items: { association: string; date: number }[],
|
||||
) => {
|
||||
if (items.length < 2) return 0;
|
||||
const first = items[0].date;
|
||||
const last = items[items.length - 1].date;
|
||||
const days = (last - first) / (1000 * 60 * 60 * 24);
|
||||
return days > 0 ? items.length / days : items.length; // items per day
|
||||
};
|
||||
|
||||
const prOverall = calculateThroughput(prs);
|
||||
const prMaintainers = calculateThroughput(
|
||||
prs.filter((i: { association: string; date: number }) =>
|
||||
isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const prCommunity = calculateThroughput(
|
||||
prs.filter(
|
||||
(i: { association: string; date: number }) =>
|
||||
!isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
|
||||
const issueOverall = calculateThroughput(issues);
|
||||
const issueMaintainers = calculateThroughput(
|
||||
issues.filter((i: { association: string; date: number }) =>
|
||||
isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
const issueCommunity = calculateThroughput(
|
||||
issues.filter(
|
||||
(i: { association: string; date: number }) =>
|
||||
!isMaintainer(i.association),
|
||||
),
|
||||
);
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
const metrics: MetricOutput[] = [
|
||||
{
|
||||
metric: 'throughput_pr_overall_per_day',
|
||||
value: Math.round(prOverall * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_pr_maintainers_per_day',
|
||||
value: Math.round(prMaintainers * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_pr_community_per_day',
|
||||
value: Math.round(prCommunity * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_overall_per_day',
|
||||
value: Math.round(issueOverall * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_maintainers_per_day',
|
||||
value: Math.round(issueMaintainers * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_community_per_day',
|
||||
value: Math.round(issueCommunity * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_overall_days_per_issue',
|
||||
value: issueOverall > 0 ? Math.round((1 / issueOverall) * 100) / 100 : 0,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_maintainers_days_per_issue',
|
||||
value:
|
||||
issueMaintainers > 0
|
||||
? Math.round((1 / issueMaintainers) * 100) / 100
|
||||
: 0,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'throughput_issue_community_days_per_issue',
|
||||
value:
|
||||
issueCommunity > 0 ? Math.round((1 / issueCommunity) * 100) / 100 : 0,
|
||||
timestamp,
|
||||
},
|
||||
];
|
||||
|
||||
metrics.forEach((m) => process.stdout.write(JSON.stringify(m) + '\n'));
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
author { login }
|
||||
createdAt
|
||||
comments(first: 20) {
|
||||
nodes {
|
||||
author { login }
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
reviews(first: 20) {
|
||||
nodes {
|
||||
author { login }
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
issues(last: 100) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
author { login }
|
||||
createdAt
|
||||
comments(first: 20) {
|
||||
nodes {
|
||||
author { login }
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
const getFirstResponseTime = (item: {
|
||||
createdAt: string;
|
||||
author: { login: string };
|
||||
comments: { nodes: { createdAt: string; author?: { login: string } }[] };
|
||||
reviews?: { nodes: { createdAt: string; author?: { login: string } }[] };
|
||||
}) => {
|
||||
const authorLogin = item.author?.login;
|
||||
let earliestResponse: number | null = null;
|
||||
|
||||
const checkNodes = (
|
||||
nodes: { createdAt: string; author?: { login: string } }[],
|
||||
) => {
|
||||
for (const node of nodes) {
|
||||
if (node.author?.login && node.author.login !== authorLogin) {
|
||||
const login = node.author.login.toLowerCase();
|
||||
if (login.endsWith('[bot]') || login.includes('bot')) {
|
||||
continue; // Ignore bots
|
||||
}
|
||||
const time = new Date(node.createdAt).getTime();
|
||||
if (!earliestResponse || time < earliestResponse) {
|
||||
earliestResponse = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (item.comments?.nodes) checkNodes(item.comments.nodes);
|
||||
if (item.reviews?.nodes) checkNodes(item.reviews.nodes);
|
||||
|
||||
if (earliestResponse) {
|
||||
return (
|
||||
(earliestResponse - new Date(item.createdAt).getTime()) /
|
||||
(1000 * 60 * 60)
|
||||
);
|
||||
}
|
||||
return null; // No response yet
|
||||
};
|
||||
const processItems = (
|
||||
items: {
|
||||
authorAssociation: string;
|
||||
createdAt: string;
|
||||
author: { login: string };
|
||||
comments: {
|
||||
nodes: { createdAt: string; author?: { login: string } }[];
|
||||
};
|
||||
reviews?: {
|
||||
nodes: { createdAt: string; author?: { login: string } }[];
|
||||
};
|
||||
}[],
|
||||
) => {
|
||||
return items
|
||||
.map((item) => ({
|
||||
association: item.authorAssociation,
|
||||
ttfr: getFirstResponseTime(item),
|
||||
}))
|
||||
.filter((i) => i.ttfr !== null) as {
|
||||
association: string;
|
||||
ttfr: number;
|
||||
}[];
|
||||
};
|
||||
const prs = processItems(data.pullRequests.nodes);
|
||||
const issues = processItems(data.issues.nodes);
|
||||
const allItems = [...prs, ...issues];
|
||||
|
||||
const isMaintainer = (assoc: string) => ['MEMBER', 'OWNER'].includes(assoc);
|
||||
const is1P = (assoc: string) => ['COLLABORATOR'].includes(assoc);
|
||||
|
||||
const calculateAvg = (items: { ttfr: number; association: string }[]) =>
|
||||
items.length ? items.reduce((a, b) => a + b.ttfr, 0) / items.length : 0;
|
||||
|
||||
const maintainers = calculateAvg(
|
||||
allItems.filter((i) => isMaintainer(i.association)),
|
||||
);
|
||||
const firstParty = calculateAvg(allItems.filter((i) => is1P(i.association)));
|
||||
const overall = calculateAvg(allItems);
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
const metrics: MetricOutput[] = [
|
||||
{
|
||||
metric: 'time_to_first_response_overall_hours',
|
||||
value: Math.round(overall * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'time_to_first_response_maintainers_hours',
|
||||
value: Math.round(maintainers * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
{
|
||||
metric: 'time_to_first_response_1p_hours',
|
||||
value: Math.round(firstParty * 100) / 100,
|
||||
timestamp,
|
||||
},
|
||||
];
|
||||
|
||||
metrics.forEach((m) => process.stdout.write(JSON.stringify(m) + '\n'));
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
import { GITHUB_OWNER, GITHUB_REPO, type MetricOutput } from '../types.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
try {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(last: 100, states: MERGED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
comments { totalCount }
|
||||
reviews { totalCount }
|
||||
}
|
||||
}
|
||||
issues(last: 100, states: CLOSED) {
|
||||
nodes {
|
||||
authorAssociation
|
||||
comments { totalCount }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const output = execSync(
|
||||
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
const data = JSON.parse(output).data.repository;
|
||||
|
||||
const prs = data.pullRequests.nodes;
|
||||
const issues = data.issues.nodes;
|
||||
|
||||
const allItems = [
|
||||
...prs.map(
|
||||
(p: {
|
||||
authorAssociation: string;
|
||||
comments: { totalCount: number };
|
||||
reviews?: { totalCount: number };
|
||||
}) => ({
|
||||
association: p.authorAssociation,
|
||||
touches: p.comments.totalCount + (p.reviews ? p.reviews.totalCount : 0),
|
||||
}),
|
||||
),
|
||||
...issues.map(
|
||||
(i: { authorAssociation: string; comments: { totalCount: number } }) => ({
|
||||
association: i.authorAssociation,
|
||||
touches: i.comments.totalCount,
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
const isMaintainer = (assoc: string) =>
|
||||
['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc);
|
||||
|
||||
const calculateAvg = (items: { touches: number; association: string }[]) =>
|
||||
items.length ? items.reduce((a, b) => a + b.touches, 0) / items.length : 0;
|
||||
|
||||
const overall = calculateAvg(allItems);
|
||||
const maintainers = calculateAvg(
|
||||
allItems.filter((i) => isMaintainer(i.association)),
|
||||
);
|
||||
const community = calculateAvg(
|
||||
allItems.filter((i) => !isMaintainer(i.association)),
|
||||
);
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify(<MetricOutput>{
|
||||
metric: 'user_touches_overall',
|
||||
value: Math.round(overall * 100) / 100,
|
||||
timestamp,
|
||||
}) + '\n',
|
||||
);
|
||||
process.stdout.write(
|
||||
JSON.stringify(<MetricOutput>{
|
||||
metric: 'user_touches_maintainers',
|
||||
value: Math.round(maintainers * 100) / 100,
|
||||
timestamp,
|
||||
}) + '\n',
|
||||
);
|
||||
process.stdout.write(
|
||||
JSON.stringify(<MetricOutput>{
|
||||
metric: 'user_touches_community',
|
||||
value: Math.round(community * 100) / 100,
|
||||
timestamp,
|
||||
}) + '\n',
|
||||
);
|
||||
} catch (err) {
|
||||
process.stderr.write(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
export interface MetricOutput {
|
||||
metric: string;
|
||||
value: number | string;
|
||||
timestamp: string;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const GITHUB_OWNER = 'google-gemini';
|
||||
export const GITHUB_REPO = 'gemini-cli';
|
||||
Reference in New Issue
Block a user