├── .cursor └── rules │ ├── core.mdc │ └── memory-bank.mdc ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── index.js ├── package-lock.json └── package.json /.cursor/rules/core.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | ## Core Rules 7 | 8 | You have two modes of operation: 9 | 10 | 1. Plan mode - You will work with the user to define a plan, you will gather all the information you need to make the changes but will not make any changes 11 | 2. Act mode - You will make changes to the codebase based on the plan 12 | 13 | - You start in plan mode and will not move to act mode until the plan is approved by the user. 14 | - You will print `# Mode: PLAN` when in plan mode and `# Mode: ACT` when in act mode at the beginning of each response. 15 | - Unless the user explicity asks you to move to act mode, by typing `ACT` you will stay in plan mode. 16 | - You will move back to plan mode after every response and when the user types `PLAN`. 17 | - If the user asks you to take an action while in plan mode you will remind them that you are in plan mode and that they need to approve the plan first. 18 | - When in plan mode always output the full updated plan in every response. -------------------------------------------------------------------------------- /.cursor/rules/memory-bank.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | # Cursor's Memory Bank 7 | 8 | I am Cursor, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. 9 | 10 | ## Memory Bank Structure 11 | 12 | The Memory Bank consists of required core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy: 13 | 14 | \```mermaid 15 | flowchart TD 16 | PB[projectbrief.md] --> PC[productContext.md] 17 | PB --> SP[systemPatterns.md] 18 | PB --> TC[techContext.md] 19 | 20 | PC --> AC[activeContext.md] 21 | SP --> AC 22 | TC --> AC 23 | 24 | AC --> P[progress.md] 25 | \``` 26 | 27 | ### Core Files (Required) 28 | 1. `projectbrief.md` 29 | - Foundation document that shapes all other files 30 | - Created at project start if it doesn't exist 31 | - Defines core requirements and goals 32 | - Source of truth for project scope 33 | 34 | 2. `productContext.md` 35 | - Why this project exists 36 | - Problems it solves 37 | - How it should work 38 | - User experience goals 39 | 40 | 3. `activeContext.md` 41 | - Current work focus 42 | - Recent changes 43 | - Next steps 44 | - Active decisions and considerations 45 | 46 | 4. `systemPatterns.md` 47 | - System architecture 48 | - Key technical decisions 49 | - Design patterns in use 50 | - Component relationships 51 | 52 | 5. `techContext.md` 53 | - Technologies used 54 | - Development setup 55 | - Technical constraints 56 | - Dependencies 57 | 58 | 6. `progress.md` 59 | - What works 60 | - What's left to build 61 | - Current status 62 | - Known issues 63 | 64 | ### Additional Context 65 | Create additional files/folders within memory-bank/ when they help organize: 66 | - Complex feature documentation 67 | - Integration specifications 68 | - API documentation 69 | - Testing strategies 70 | - Deployment procedures 71 | 72 | ## Core Workflows 73 | 74 | ### Plan Mode 75 | \```mermaid 76 | flowchart TD 77 | Start[Start] --> ReadFiles[Read Memory Bank] 78 | ReadFiles --> CheckFiles{Files Complete?} 79 | 80 | CheckFiles -->|No| Plan[Create Plan] 81 | Plan --> Document[Document in Chat] 82 | 83 | CheckFiles -->|Yes| Verify[Verify Context] 84 | Verify --> Strategy[Develop Strategy] 85 | Strategy --> Present[Present Approach] 86 | \``` 87 | 88 | ### Act Mode 89 | \```mermaid 90 | flowchart TD 91 | Start[Start] --> Context[Check Memory Bank] 92 | Context --> Update[Update Documentation] 93 | Update --> Rules[Update .cursor/rules if needed] 94 | Rules --> Execute[Execute Task] 95 | Execute --> Document[Document Changes] 96 | \``` 97 | 98 | ## Documentation Updates 99 | 100 | Memory Bank updates occur when: 101 | 1. Discovering new project patterns 102 | 2. After implementing significant changes 103 | 3. When user requests with **update memory bank** (MUST review ALL files) 104 | 4. When context needs clarification 105 | 106 | \```mermaid 107 | flowchart TD 108 | Start[Update Process] 109 | 110 | subgraph Process 111 | P1[Review ALL Files] 112 | P2[Document Current State] 113 | P3[Clarify Next Steps] 114 | P4[Update .cursor/rules] 115 | 116 | P1 --> P2 --> P3 --> P4 117 | end 118 | 119 | Start --> Process 120 | \``` 121 | 122 | Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state. 123 | 124 | ## Project Intelligence (.cursor/rules) 125 | 126 | The .cursor/rules file is my learning journal for each project. It captures important patterns, preferences, and project intelligence that help me work more effectively. As I work with you and the project, I'll discover and document key insights that aren't obvious from the code alone. 127 | 128 | \```mermaid 129 | flowchart TD 130 | Start{Discover New Pattern} 131 | 132 | subgraph Learn [Learning Process] 133 | D1[Identify Pattern] 134 | D2[Validate with User] 135 | D3[Document in .cursor/rules] 136 | end 137 | 138 | subgraph Apply [Usage] 139 | A1[Read .cursor/rules] 140 | A2[Apply Learned Patterns] 141 | A3[Improve Future Work] 142 | end 143 | 144 | Start --> Learn 145 | Learn --> Apply 146 | \``` 147 | 148 | ### What to Capture 149 | - Critical implementation paths 150 | - User preferences and workflow 151 | - Project-specific patterns 152 | - Known challenges 153 | - Evolution of project decisions 154 | - Tool usage patterns 155 | 156 | The format is flexible - focus on capturing valuable insights that help me work more effectively with you and the project. Think of .cursor/rules as a living document that grows smarter as we work together. 157 | 158 | REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version bump type (patch, minor, major)' 8 | required: true 9 | default: 'patch' 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | 16 | jobs: 17 | publish: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: '20.x' 31 | registry-url: 'https://registry.npmjs.org/' 32 | 33 | - name: Install dependencies 34 | run: npm ci 35 | 36 | - name: Version bump 37 | id: version 38 | run: | 39 | git config --global user.name 'GitHub Action' 40 | git config --global user.email 'action@github.com' 41 | npm version ${{ github.event.inputs.version }} -m "Bump version to %s [skip ci]" 42 | echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT 43 | 44 | - name: Push changes 45 | run: git push --follow-tags 46 | 47 | - name: Create GitHub Release 48 | uses: softprops/action-gh-release@v1 49 | with: 50 | tag_name: v${{ steps.version.outputs.VERSION }} 51 | generate_release_notes: true 52 | 53 | - name: Publish to npm 54 | run: npm publish --access public 55 | env: 56 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Mac files 5 | .DS_Store 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Environment variables 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | # Editor directories and files 22 | .idea/ 23 | .vscode/ 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | memory-bank/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alexey Elizarov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cursor Memory Bank 2 | 3 | Cursor Memory Bank is a powerful feature that enhances AI assistance by maintaining perfect documentation between sessions. It addresses AI's session memory limitations by creating a structured documentation system that serves as the AI's persistent memory. 4 | 5 | ## Installation 6 | 7 | ### Option 1: Using npx (Recommended) 8 | 9 | Run the following command in your project root directory: 10 | 11 | ```bash 12 | npx cursor-bank init 13 | ``` 14 | 15 | This will automatically: 16 | 1. Copy the `.cursor/rules` directory to your project 17 | 2. Create a `memory-bank` directory in your project root 18 | 19 |
20 | Other options 21 | 22 | ### Option 2: Global Installation 23 | 24 | ```bash 25 | npm install -g cursor-bank 26 | ``` 27 | 28 | 2. Run the init command in your project: 29 | 30 | ```bash 31 | cursor-bank init 32 | ``` 33 | 34 | ### Option 3: Download Files Directly 35 | 36 | You can also download the `.cursor/rules` directory manually from: 37 | https://github.com/tacticlaunch/cursor-bank/tree/main/.cursor/rules 38 |
39 | 40 | ## After Installation 41 | 42 | - For exists project write to Cursor agent - **initialize memory bank** 43 | - For new project I would recommend this flow: 44 | - Write to Cursor agent 45 | ``` 46 | PLAN 47 | 48 | 49 | ``` 50 | - After Cursor agent end its speach write to it - **initialize memory bank** 51 | 52 | ## Usage 53 | 54 | ### Basic Commands 55 | 56 | - `PLAN` - Enter or return to plan mode 57 | - `ACT` - Approve plan and switch to implementation mode 58 | - `update memory bank` - Trigger documentation update 59 | 60 | ## Links 61 | 62 | [tacticlaunch/mcp-linear](https://github.com/tacticlaunch/mcp-linear) - If you are a developer seeking to enhance your workflow with Linear, consider giving it a try. 63 | 64 | ## License 65 | 66 | This project is licensed under the MIT License - see the LICENSE file for details. 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | const os = require('os'); 6 | const { simpleGit } = require('simple-git'); 7 | const { program } = require('commander'); 8 | const { version } = require('./package.json'); 9 | const _debug = require('debug'); 10 | const debug = require('debug')('cursor-bank'); 11 | 12 | const git = simpleGit(); 13 | 14 | // Configuration 15 | const config = { 16 | repoUrl: 'https://github.com/tacticlaunch/cursor-bank', 17 | sourcePath: '.cursor/rules', 18 | targetPath: '.cursor/rules', 19 | branch: 'main' 20 | }; 21 | 22 | // Custom logger function 23 | const log = { 24 | info: (message) => console.log(message), 25 | debug: (message) => debug(message), 26 | error: (message) => console.error(message) 27 | }; 28 | 29 | async function cloneAndCopy(options) { 30 | // Enable debug logging if the debug flag is set 31 | if (options.debug) { 32 | _debug.enable('cursor-bank'); 33 | debug('Debug mode enabled'); 34 | } 35 | 36 | // Create a temporary directory 37 | const tempDir = path.join(os.tmpdir(), `cursor-bank-${Date.now()}`); 38 | 39 | log.debug(`Creating temporary directory: ${tempDir}`); 40 | 41 | try { 42 | // Clone the repository 43 | log.info(`Cloning repository...`); 44 | log.debug(`Cloning from ${config.repoUrl} branch ${config.branch}`); 45 | 46 | await git.clone(config.repoUrl, tempDir, [ 47 | '--depth', '1', 48 | '--single-branch', 49 | '--branch', config.branch 50 | ]); 51 | 52 | // Path to source directory in the cloned repo 53 | const sourcePath = path.join(tempDir, config.sourcePath); 54 | 55 | // Path to target directory in current working directory 56 | const targetPath = path.join(process.cwd(), config.targetPath); 57 | 58 | // Check if source exists 59 | if (!fs.existsSync(sourcePath)) { 60 | throw new Error(`Source path ${config.sourcePath} not found in repository`); 61 | } 62 | 63 | // Create target directory if it doesn't exist 64 | log.info(`Copying rules to ${config.targetPath}...`); 65 | log.debug(`Full source path: ${sourcePath}`); 66 | log.debug(`Full target path: ${targetPath}`); 67 | 68 | await fs.ensureDir(path.dirname(targetPath)); 69 | 70 | // Copy directory 71 | await fs.copy(sourcePath, targetPath); 72 | 73 | log.debug(`Successfully copied ${config.sourcePath} to ${targetPath}`); 74 | 75 | // Create memory-bank directory if it doesn't exist 76 | const memoryBankPath = path.join(process.cwd(), 'memory-bank'); 77 | if (!fs.existsSync(memoryBankPath)) { 78 | log.info('Creating memory-bank directory...'); 79 | await fs.ensureDir(memoryBankPath); 80 | log.debug('Successfully created memory-bank directory'); 81 | } else { 82 | log.debug('memory-bank directory already exists'); 83 | } 84 | 85 | log.info('\n✅ Setup complete! You can now use the Cursor Memory Bank.'); 86 | log.info('For new projects, I recommend starting with a PLAN command before initializing the memory bank. For more details, visit https://github.com/tacticlaunch/cursor-bank'); 87 | log.info('For existing projects, write to your Cursor assistant: "initialize memory bank".'); 88 | } catch (error) { 89 | log.error('An error occurred:'); 90 | log.error(error.message); 91 | if (options.debug) { 92 | log.debug(error.stack); 93 | } 94 | process.exit(1); 95 | } finally { 96 | // Clean up temporary directory 97 | if (fs.existsSync(tempDir)) { 98 | log.debug(`Cleaning up temporary directory: ${tempDir}`); 99 | await fs.remove(tempDir); 100 | } 101 | } 102 | } 103 | 104 | // Setup CLI commands 105 | program 106 | .name('cursor-bank') 107 | .description('Cursor Memory Bank installer and utilities') 108 | .version(version); 109 | 110 | program 111 | .command('init') 112 | .description('Initialize Cursor Memory Bank in the current project') 113 | .option('-d, --debug', 'Enable debug output') 114 | .action((options) => { 115 | log.info('Initializing Cursor Memory Bank...'); 116 | cloneAndCopy(options); 117 | }); 118 | 119 | // If no arguments provided, show help 120 | if (process.argv.length === 2) { 121 | program.help(); 122 | } 123 | 124 | program.parse(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cursor-bank", 3 | "version": "1.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cursor-bank", 9 | "version": "1.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "commander": "^12.1.0", 13 | "debug": "^4.4.0", 14 | "fs-extra": "^11.1.1", 15 | "simple-git": "^3.21.0" 16 | }, 17 | "bin": { 18 | "cursor-bank": "index.js" 19 | } 20 | }, 21 | "node_modules/@kwsites/file-exists": { 22 | "version": "1.1.1", 23 | "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", 24 | "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", 25 | "dependencies": { 26 | "debug": "^4.1.1" 27 | } 28 | }, 29 | "node_modules/@kwsites/promise-deferred": { 30 | "version": "1.1.1", 31 | "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", 32 | "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" 33 | }, 34 | "node_modules/commander": { 35 | "version": "12.1.0", 36 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 37 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 38 | "engines": { 39 | "node": ">=18" 40 | } 41 | }, 42 | "node_modules/debug": { 43 | "version": "4.4.0", 44 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 45 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 46 | "dependencies": { 47 | "ms": "^2.1.3" 48 | }, 49 | "engines": { 50 | "node": ">=6.0" 51 | }, 52 | "peerDependenciesMeta": { 53 | "supports-color": { 54 | "optional": true 55 | } 56 | } 57 | }, 58 | "node_modules/fs-extra": { 59 | "version": "11.3.0", 60 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", 61 | "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", 62 | "dependencies": { 63 | "graceful-fs": "^4.2.0", 64 | "jsonfile": "^6.0.1", 65 | "universalify": "^2.0.0" 66 | }, 67 | "engines": { 68 | "node": ">=14.14" 69 | } 70 | }, 71 | "node_modules/graceful-fs": { 72 | "version": "4.2.11", 73 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 74 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 75 | }, 76 | "node_modules/jsonfile": { 77 | "version": "6.1.0", 78 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 79 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 80 | "dependencies": { 81 | "universalify": "^2.0.0" 82 | }, 83 | "optionalDependencies": { 84 | "graceful-fs": "^4.1.6" 85 | } 86 | }, 87 | "node_modules/ms": { 88 | "version": "2.1.3", 89 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 90 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 91 | }, 92 | "node_modules/simple-git": { 93 | "version": "3.27.0", 94 | "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", 95 | "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", 96 | "dependencies": { 97 | "@kwsites/file-exists": "^1.1.1", 98 | "@kwsites/promise-deferred": "^1.1.1", 99 | "debug": "^4.3.5" 100 | }, 101 | "funding": { 102 | "type": "github", 103 | "url": "https://github.com/steveukx/git-js?sponsor=1" 104 | } 105 | }, 106 | "node_modules/universalify": { 107 | "version": "2.0.1", 108 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 109 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 110 | "engines": { 111 | "node": ">= 10.0.0" 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cursor-bank", 3 | "public": true, 4 | "version": "1.0.1", 5 | "license": "MIT", 6 | "description": "Cursor Memory Bank is a powerful feature that enhances AI assistance by maintaining perfect documentation between sessions. It addresses the AI's session memory limitations by creating a structured documentation system that serves as the AI's persistent memory.", 7 | "keywords": [ 8 | "memory-bank", 9 | "cursor", 10 | "ai", 11 | "documentation", 12 | "claude" 13 | ], 14 | "main": "index.js", 15 | "bin": { 16 | "cursor-bank": "./index.js" 17 | }, 18 | "author": "Alexey Elizarov ", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/tacticlaunch/cursor-bank.git" 22 | }, 23 | "scripts": { 24 | "start": "node index.js" 25 | }, 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/tacticlaunch/cursor-bank/issues" 31 | }, 32 | "homepage": "https://github.com/tacticlaunch/cursor-bank#readme", 33 | "dependencies": { 34 | "commander": "^12.1.0", 35 | "debug": "^4.4.0", 36 | "fs-extra": "^11.1.1", 37 | "simple-git": "^3.21.0" 38 | } 39 | } 40 | --------------------------------------------------------------------------------