[{"data":1,"prerenderedAt":4913},["ShallowReactive",2],{"post-maven-and-github-actions":3,"rel-maven-and-github-actions":1074,"sib-maven-and-github-actions":4809},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"layout":11,"subtitle":12,"image":13,"optimized_image":13,"category":14,"tags":15,"author":20,"paginate":6,"body":21,"_type":1068,"_id":1069,"_source":1070,"_file":1071,"_stem":1072,"_extension":1073},"/posts/maven-and-github-actions","posts",false,"","Automating Java Builds with Maven and GitHub Actions CI/CD","Learn how to set up automated Java build pipelines using Maven and GitHub Actions, including practical examples and detailed instructions.","2024-06-15T11:00:00.000Z","post","A Step-by-Step Guide to Continuous Integration and Deployment","/assets/img/uploads/java-maven-github-actions.png","code",[14,16,17,18,19],"java","maven","github-actions","cicd","jaimedearcos",{"type":22,"children":23,"toc":1057},"root",[24,51,58,81,87,92,101,114,473,479,538,544,549,710,716,721,793,805,811,816,878,890,896,919,925,930,985,1000,1006,1011,1018,1051],{"type":25,"tag":26,"props":27,"children":28},"element","blockquote",{},[29],{"type":25,"tag":30,"props":31,"children":32},"p",{},[33,36,42,44,49],{"type":34,"value":35},"text","Continuous Integration and Continuous Deployment (CI/CD) streamline development workflows, improve software quality, and accelerate delivery. This tutorial guides you through setting up automated Java builds using ",{"type":25,"tag":37,"props":38,"children":39},"strong",{},[40],{"type":34,"value":41},"Maven",{"type":34,"value":43}," and ",{"type":25,"tag":37,"props":45,"children":46},{},[47],{"type":34,"value":48},"GitHub Actions",{"type":34,"value":50},".",{"type":25,"tag":52,"props":53,"children":55},"h2",{"id":54},"prerequisites",[56],{"type":34,"value":57},"Prerequisites",{"type":25,"tag":59,"props":60,"children":61},"ul",{},[62,76],{"type":25,"tag":63,"props":64,"children":65},"li",{},[66,68,74],{"type":34,"value":67},"A Java project with Maven configured (",{"type":25,"tag":14,"props":69,"children":71},{"className":70},[],[72],{"type":34,"value":73},"pom.xml",{"type":34,"value":75},").",{"type":25,"tag":63,"props":77,"children":78},{},[79],{"type":34,"value":80},"GitHub repository.",{"type":25,"tag":52,"props":82,"children":84},{"id":83},"step-1-setting-up-your-github-actions-workflow",[85],{"type":34,"value":86},"Step 1: Setting Up Your GitHub Actions Workflow",{"type":25,"tag":30,"props":88,"children":89},{},[90],{"type":34,"value":91},"In your repository, create the following directory structure:",{"type":25,"tag":93,"props":94,"children":96},"pre",{"code":95},".github/workflows/\n",[97],{"type":25,"tag":14,"props":98,"children":99},{"__ignoreMap":7},[100],{"type":34,"value":95},{"type":25,"tag":30,"props":102,"children":103},{},[104,106,112],{"type":34,"value":105},"Create a new file named ",{"type":25,"tag":14,"props":107,"children":109},{"className":108},[],[110],{"type":34,"value":111},"maven-ci.yml",{"type":34,"value":113}," within this directory:",{"type":25,"tag":93,"props":115,"children":119},{"code":116,"language":117,"meta":7,"className":118,"style":7},"name: Maven CI\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up JDK 21\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n\n      - name: Build with Maven\n        run: mvn clean install\n","yml","language-yml shiki shiki-themes github-dark github-light",[120],{"type":25,"tag":14,"props":121,"children":122},{"__ignoreMap":7},[123,147,157,172,185,209,222,242,250,263,276,294,302,315,338,359,377,390,408,426,434,455],{"type":25,"tag":124,"props":125,"children":128},"span",{"class":126,"line":127},"line",1,[129,135,141],{"type":25,"tag":124,"props":130,"children":132},{"style":131},"--shiki-default:#85E89D;--shiki-light:#22863A",[133],{"type":34,"value":134},"name",{"type":25,"tag":124,"props":136,"children":138},{"style":137},"--shiki-default:#E1E4E8;--shiki-light:#24292E",[139],{"type":34,"value":140},": ",{"type":25,"tag":124,"props":142,"children":144},{"style":143},"--shiki-default:#9ECBFF;--shiki-light:#032F62",[145],{"type":34,"value":146},"Maven CI\n",{"type":25,"tag":124,"props":148,"children":150},{"class":126,"line":149},2,[151],{"type":25,"tag":124,"props":152,"children":154},{"emptyLinePlaceholder":153},true,[155],{"type":34,"value":156},"\n",{"type":25,"tag":124,"props":158,"children":160},{"class":126,"line":159},3,[161,167],{"type":25,"tag":124,"props":162,"children":164},{"style":163},"--shiki-default:#79B8FF;--shiki-light:#005CC5",[165],{"type":34,"value":166},"on",{"type":25,"tag":124,"props":168,"children":169},{"style":137},[170],{"type":34,"value":171},":\n",{"type":25,"tag":124,"props":173,"children":175},{"class":126,"line":174},4,[176,181],{"type":25,"tag":124,"props":177,"children":178},{"style":131},[179],{"type":34,"value":180},"  push",{"type":25,"tag":124,"props":182,"children":183},{"style":137},[184],{"type":34,"value":171},{"type":25,"tag":124,"props":186,"children":188},{"class":126,"line":187},5,[189,194,199,204],{"type":25,"tag":124,"props":190,"children":191},{"style":131},[192],{"type":34,"value":193},"    branches",{"type":25,"tag":124,"props":195,"children":196},{"style":137},[197],{"type":34,"value":198},": [ ",{"type":25,"tag":124,"props":200,"children":201},{"style":143},[202],{"type":34,"value":203},"\"main\"",{"type":25,"tag":124,"props":205,"children":206},{"style":137},[207],{"type":34,"value":208}," ]\n",{"type":25,"tag":124,"props":210,"children":212},{"class":126,"line":211},6,[213,218],{"type":25,"tag":124,"props":214,"children":215},{"style":131},[216],{"type":34,"value":217},"  pull_request",{"type":25,"tag":124,"props":219,"children":220},{"style":137},[221],{"type":34,"value":171},{"type":25,"tag":124,"props":223,"children":225},{"class":126,"line":224},7,[226,230,234,238],{"type":25,"tag":124,"props":227,"children":228},{"style":131},[229],{"type":34,"value":193},{"type":25,"tag":124,"props":231,"children":232},{"style":137},[233],{"type":34,"value":198},{"type":25,"tag":124,"props":235,"children":236},{"style":143},[237],{"type":34,"value":203},{"type":25,"tag":124,"props":239,"children":240},{"style":137},[241],{"type":34,"value":208},{"type":25,"tag":124,"props":243,"children":245},{"class":126,"line":244},8,[246],{"type":25,"tag":124,"props":247,"children":248},{"emptyLinePlaceholder":153},[249],{"type":34,"value":156},{"type":25,"tag":124,"props":251,"children":253},{"class":126,"line":252},9,[254,259],{"type":25,"tag":124,"props":255,"children":256},{"style":131},[257],{"type":34,"value":258},"jobs",{"type":25,"tag":124,"props":260,"children":261},{"style":137},[262],{"type":34,"value":171},{"type":25,"tag":124,"props":264,"children":266},{"class":126,"line":265},10,[267,272],{"type":25,"tag":124,"props":268,"children":269},{"style":131},[270],{"type":34,"value":271},"  build",{"type":25,"tag":124,"props":273,"children":274},{"style":137},[275],{"type":34,"value":171},{"type":25,"tag":124,"props":277,"children":279},{"class":126,"line":278},11,[280,285,289],{"type":25,"tag":124,"props":281,"children":282},{"style":131},[283],{"type":34,"value":284},"    runs-on",{"type":25,"tag":124,"props":286,"children":287},{"style":137},[288],{"type":34,"value":140},{"type":25,"tag":124,"props":290,"children":291},{"style":143},[292],{"type":34,"value":293},"ubuntu-latest\n",{"type":25,"tag":124,"props":295,"children":297},{"class":126,"line":296},12,[298],{"type":25,"tag":124,"props":299,"children":300},{"emptyLinePlaceholder":153},[301],{"type":34,"value":156},{"type":25,"tag":124,"props":303,"children":305},{"class":126,"line":304},13,[306,311],{"type":25,"tag":124,"props":307,"children":308},{"style":131},[309],{"type":34,"value":310},"    steps",{"type":25,"tag":124,"props":312,"children":313},{"style":137},[314],{"type":34,"value":171},{"type":25,"tag":124,"props":316,"children":318},{"class":126,"line":317},14,[319,324,329,333],{"type":25,"tag":124,"props":320,"children":321},{"style":137},[322],{"type":34,"value":323},"      - ",{"type":25,"tag":124,"props":325,"children":326},{"style":131},[327],{"type":34,"value":328},"uses",{"type":25,"tag":124,"props":330,"children":331},{"style":137},[332],{"type":34,"value":140},{"type":25,"tag":124,"props":334,"children":335},{"style":143},[336],{"type":34,"value":337},"actions/checkout@v4\n",{"type":25,"tag":124,"props":339,"children":341},{"class":126,"line":340},15,[342,346,350,354],{"type":25,"tag":124,"props":343,"children":344},{"style":137},[345],{"type":34,"value":323},{"type":25,"tag":124,"props":347,"children":348},{"style":131},[349],{"type":34,"value":134},{"type":25,"tag":124,"props":351,"children":352},{"style":137},[353],{"type":34,"value":140},{"type":25,"tag":124,"props":355,"children":356},{"style":143},[357],{"type":34,"value":358},"Set up JDK 21\n",{"type":25,"tag":124,"props":360,"children":362},{"class":126,"line":361},16,[363,368,372],{"type":25,"tag":124,"props":364,"children":365},{"style":131},[366],{"type":34,"value":367},"        uses",{"type":25,"tag":124,"props":369,"children":370},{"style":137},[371],{"type":34,"value":140},{"type":25,"tag":124,"props":373,"children":374},{"style":143},[375],{"type":34,"value":376},"actions/setup-java@v4\n",{"type":25,"tag":124,"props":378,"children":380},{"class":126,"line":379},17,[381,386],{"type":25,"tag":124,"props":382,"children":383},{"style":131},[384],{"type":34,"value":385},"        with",{"type":25,"tag":124,"props":387,"children":388},{"style":137},[389],{"type":34,"value":171},{"type":25,"tag":124,"props":391,"children":393},{"class":126,"line":392},18,[394,399,403],{"type":25,"tag":124,"props":395,"children":396},{"style":131},[397],{"type":34,"value":398},"          java-version",{"type":25,"tag":124,"props":400,"children":401},{"style":137},[402],{"type":34,"value":140},{"type":25,"tag":124,"props":404,"children":405},{"style":143},[406],{"type":34,"value":407},"'21'\n",{"type":25,"tag":124,"props":409,"children":411},{"class":126,"line":410},19,[412,417,421],{"type":25,"tag":124,"props":413,"children":414},{"style":131},[415],{"type":34,"value":416},"          distribution",{"type":25,"tag":124,"props":418,"children":419},{"style":137},[420],{"type":34,"value":140},{"type":25,"tag":124,"props":422,"children":423},{"style":143},[424],{"type":34,"value":425},"'temurin'\n",{"type":25,"tag":124,"props":427,"children":429},{"class":126,"line":428},20,[430],{"type":25,"tag":124,"props":431,"children":432},{"emptyLinePlaceholder":153},[433],{"type":34,"value":156},{"type":25,"tag":124,"props":435,"children":437},{"class":126,"line":436},21,[438,442,446,450],{"type":25,"tag":124,"props":439,"children":440},{"style":137},[441],{"type":34,"value":323},{"type":25,"tag":124,"props":443,"children":444},{"style":131},[445],{"type":34,"value":134},{"type":25,"tag":124,"props":447,"children":448},{"style":137},[449],{"type":34,"value":140},{"type":25,"tag":124,"props":451,"children":452},{"style":143},[453],{"type":34,"value":454},"Build with Maven\n",{"type":25,"tag":124,"props":456,"children":458},{"class":126,"line":457},22,[459,464,468],{"type":25,"tag":124,"props":460,"children":461},{"style":131},[462],{"type":34,"value":463},"        run",{"type":25,"tag":124,"props":465,"children":466},{"style":137},[467],{"type":34,"value":140},{"type":25,"tag":124,"props":469,"children":470},{"style":143},[471],{"type":34,"value":472},"mvn clean install\n",{"type":25,"tag":52,"props":474,"children":476},{"id":475},"step-2-understanding-the-workflow",[477],{"type":34,"value":478},"Step 2: Understanding the Workflow",{"type":25,"tag":59,"props":480,"children":481},{},[482,500,510,520],{"type":25,"tag":63,"props":483,"children":484},{},[485,490,492,498],{"type":25,"tag":37,"props":486,"children":487},{},[488],{"type":34,"value":489},"Trigger:",{"type":34,"value":491}," Activated on push or pull request to the ",{"type":25,"tag":14,"props":493,"children":495},{"className":494},[],[496],{"type":34,"value":497},"main",{"type":34,"value":499}," branch.",{"type":25,"tag":63,"props":501,"children":502},{},[503,508],{"type":25,"tag":37,"props":504,"children":505},{},[506],{"type":34,"value":507},"Checkout:",{"type":34,"value":509}," Retrieves the source code from your repository.",{"type":25,"tag":63,"props":511,"children":512},{},[513,518],{"type":25,"tag":37,"props":514,"children":515},{},[516],{"type":34,"value":517},"JDK Setup:",{"type":34,"value":519}," Installs Java 21 from Temurin.",{"type":25,"tag":63,"props":521,"children":522},{},[523,528,530,536],{"type":25,"tag":37,"props":524,"children":525},{},[526],{"type":34,"value":527},"Maven Build:",{"type":34,"value":529}," Executes Maven goals (",{"type":25,"tag":14,"props":531,"children":533},{"className":532},[],[534],{"type":34,"value":535},"clean install",{"type":34,"value":537},") to compile, test, and package your Java application.",{"type":25,"tag":52,"props":539,"children":541},{"id":540},"step-3-add-maven-test-reports",[542],{"type":34,"value":543},"Step 3: Add Maven Test Reports",{"type":25,"tag":30,"props":545,"children":546},{},[547],{"type":34,"value":548},"Enhance your workflow by generating and uploading test reports:",{"type":25,"tag":93,"props":550,"children":552},{"code":551,"language":117,"meta":7,"className":118,"style":7},"- name: Run tests and upload reports\n  run: |\n    mvn test\n  continue-on-error: true\n\n- name: Upload test report\n  uses: actions/upload-artifact@v4\n  with:\n    name: test-report\n    path: target/surefire-reports/\n",[553],{"type":25,"tag":14,"props":554,"children":555},{"__ignoreMap":7},[556,577,595,603,620,627,647,664,676,693],{"type":25,"tag":124,"props":557,"children":558},{"class":126,"line":127},[559,564,568,572],{"type":25,"tag":124,"props":560,"children":561},{"style":137},[562],{"type":34,"value":563},"- ",{"type":25,"tag":124,"props":565,"children":566},{"style":131},[567],{"type":34,"value":134},{"type":25,"tag":124,"props":569,"children":570},{"style":137},[571],{"type":34,"value":140},{"type":25,"tag":124,"props":573,"children":574},{"style":143},[575],{"type":34,"value":576},"Run tests and upload reports\n",{"type":25,"tag":124,"props":578,"children":579},{"class":126,"line":149},[580,585,589],{"type":25,"tag":124,"props":581,"children":582},{"style":131},[583],{"type":34,"value":584},"  run",{"type":25,"tag":124,"props":586,"children":587},{"style":137},[588],{"type":34,"value":140},{"type":25,"tag":124,"props":590,"children":592},{"style":591},"--shiki-default:#F97583;--shiki-light:#D73A49",[593],{"type":34,"value":594},"|\n",{"type":25,"tag":124,"props":596,"children":597},{"class":126,"line":159},[598],{"type":25,"tag":124,"props":599,"children":600},{"style":143},[601],{"type":34,"value":602},"    mvn test\n",{"type":25,"tag":124,"props":604,"children":605},{"class":126,"line":174},[606,611,615],{"type":25,"tag":124,"props":607,"children":608},{"style":131},[609],{"type":34,"value":610},"  continue-on-error",{"type":25,"tag":124,"props":612,"children":613},{"style":137},[614],{"type":34,"value":140},{"type":25,"tag":124,"props":616,"children":617},{"style":163},[618],{"type":34,"value":619},"true\n",{"type":25,"tag":124,"props":621,"children":622},{"class":126,"line":187},[623],{"type":25,"tag":124,"props":624,"children":625},{"emptyLinePlaceholder":153},[626],{"type":34,"value":156},{"type":25,"tag":124,"props":628,"children":629},{"class":126,"line":211},[630,634,638,642],{"type":25,"tag":124,"props":631,"children":632},{"style":137},[633],{"type":34,"value":563},{"type":25,"tag":124,"props":635,"children":636},{"style":131},[637],{"type":34,"value":134},{"type":25,"tag":124,"props":639,"children":640},{"style":137},[641],{"type":34,"value":140},{"type":25,"tag":124,"props":643,"children":644},{"style":143},[645],{"type":34,"value":646},"Upload test report\n",{"type":25,"tag":124,"props":648,"children":649},{"class":126,"line":224},[650,655,659],{"type":25,"tag":124,"props":651,"children":652},{"style":131},[653],{"type":34,"value":654},"  uses",{"type":25,"tag":124,"props":656,"children":657},{"style":137},[658],{"type":34,"value":140},{"type":25,"tag":124,"props":660,"children":661},{"style":143},[662],{"type":34,"value":663},"actions/upload-artifact@v4\n",{"type":25,"tag":124,"props":665,"children":666},{"class":126,"line":244},[667,672],{"type":25,"tag":124,"props":668,"children":669},{"style":131},[670],{"type":34,"value":671},"  with",{"type":25,"tag":124,"props":673,"children":674},{"style":137},[675],{"type":34,"value":171},{"type":25,"tag":124,"props":677,"children":678},{"class":126,"line":252},[679,684,688],{"type":25,"tag":124,"props":680,"children":681},{"style":131},[682],{"type":34,"value":683},"    name",{"type":25,"tag":124,"props":685,"children":686},{"style":137},[687],{"type":34,"value":140},{"type":25,"tag":124,"props":689,"children":690},{"style":143},[691],{"type":34,"value":692},"test-report\n",{"type":25,"tag":124,"props":694,"children":695},{"class":126,"line":265},[696,701,705],{"type":25,"tag":124,"props":697,"children":698},{"style":131},[699],{"type":34,"value":700},"    path",{"type":25,"tag":124,"props":702,"children":703},{"style":137},[704],{"type":34,"value":140},{"type":25,"tag":124,"props":706,"children":707},{"style":143},[708],{"type":34,"value":709},"target/surefire-reports/\n",{"type":25,"tag":52,"props":711,"children":713},{"id":712},"step-4-deploy-artifacts-optional",[714],{"type":34,"value":715},"Step 4: Deploy Artifacts (Optional)",{"type":25,"tag":30,"props":717,"children":718},{},[719],{"type":34,"value":720},"To automatically deploy artifacts, include the deployment steps:",{"type":25,"tag":93,"props":722,"children":724},{"code":723,"language":117,"meta":7,"className":118,"style":7},"- name: Deploy package\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n  run: mvn deploy\n",[725],{"type":25,"tag":14,"props":726,"children":727},{"__ignoreMap":7},[728,748,760,777],{"type":25,"tag":124,"props":729,"children":730},{"class":126,"line":127},[731,735,739,743],{"type":25,"tag":124,"props":732,"children":733},{"style":137},[734],{"type":34,"value":563},{"type":25,"tag":124,"props":736,"children":737},{"style":131},[738],{"type":34,"value":134},{"type":25,"tag":124,"props":740,"children":741},{"style":137},[742],{"type":34,"value":140},{"type":25,"tag":124,"props":744,"children":745},{"style":143},[746],{"type":34,"value":747},"Deploy package\n",{"type":25,"tag":124,"props":749,"children":750},{"class":126,"line":149},[751,756],{"type":25,"tag":124,"props":752,"children":753},{"style":131},[754],{"type":34,"value":755},"  env",{"type":25,"tag":124,"props":757,"children":758},{"style":137},[759],{"type":34,"value":171},{"type":25,"tag":124,"props":761,"children":762},{"class":126,"line":159},[763,768,772],{"type":25,"tag":124,"props":764,"children":765},{"style":131},[766],{"type":34,"value":767},"    GITHUB_TOKEN",{"type":25,"tag":124,"props":769,"children":770},{"style":137},[771],{"type":34,"value":140},{"type":25,"tag":124,"props":773,"children":774},{"style":143},[775],{"type":34,"value":776},"${{ secrets.GITHUB_TOKEN }}\n",{"type":25,"tag":124,"props":778,"children":779},{"class":126,"line":174},[780,784,788],{"type":25,"tag":124,"props":781,"children":782},{"style":131},[783],{"type":34,"value":584},{"type":25,"tag":124,"props":785,"children":786},{"style":137},[787],{"type":34,"value":140},{"type":25,"tag":124,"props":789,"children":790},{"style":143},[791],{"type":34,"value":792},"mvn deploy\n",{"type":25,"tag":30,"props":794,"children":795},{},[796,798,803],{"type":34,"value":797},"Ensure your ",{"type":25,"tag":14,"props":799,"children":801},{"className":800},[],[802],{"type":34,"value":73},{"type":34,"value":804}," is configured for deployment (e.g., GitHub Packages).",{"type":25,"tag":52,"props":806,"children":808},{"id":807},"step-5-committing-and-running-your-workflow",[809],{"type":34,"value":810},"Step 5: Committing and Running Your Workflow",{"type":25,"tag":30,"props":812,"children":813},{},[814],{"type":34,"value":815},"Commit your workflow file and push the changes:",{"type":25,"tag":93,"props":817,"children":821},{"code":818,"language":819,"meta":7,"className":820,"style":7},"git add .github/workflows/maven-ci.yml\ngit commit -m \"Add Maven CI GitHub Actions workflow\"\ngit push\n","bash","language-bash shiki shiki-themes github-dark github-light",[822],{"type":25,"tag":14,"props":823,"children":824},{"__ignoreMap":7},[825,844,866],{"type":25,"tag":124,"props":826,"children":827},{"class":126,"line":127},[828,834,839],{"type":25,"tag":124,"props":829,"children":831},{"style":830},"--shiki-default:#B392F0;--shiki-light:#6F42C1",[832],{"type":34,"value":833},"git",{"type":25,"tag":124,"props":835,"children":836},{"style":143},[837],{"type":34,"value":838}," add",{"type":25,"tag":124,"props":840,"children":841},{"style":143},[842],{"type":34,"value":843}," .github/workflows/maven-ci.yml\n",{"type":25,"tag":124,"props":845,"children":846},{"class":126,"line":149},[847,851,856,861],{"type":25,"tag":124,"props":848,"children":849},{"style":830},[850],{"type":34,"value":833},{"type":25,"tag":124,"props":852,"children":853},{"style":143},[854],{"type":34,"value":855}," commit",{"type":25,"tag":124,"props":857,"children":858},{"style":163},[859],{"type":34,"value":860}," -m",{"type":25,"tag":124,"props":862,"children":863},{"style":143},[864],{"type":34,"value":865}," \"Add Maven CI GitHub Actions workflow\"\n",{"type":25,"tag":124,"props":867,"children":868},{"class":126,"line":159},[869,873],{"type":25,"tag":124,"props":870,"children":871},{"style":830},[872],{"type":34,"value":833},{"type":25,"tag":124,"props":874,"children":875},{"style":143},[876],{"type":34,"value":877}," push\n",{"type":25,"tag":30,"props":879,"children":880},{},[881,883,888],{"type":34,"value":882},"Navigate to the ",{"type":25,"tag":37,"props":884,"children":885},{},[886],{"type":34,"value":887},"Actions",{"type":34,"value":889}," tab on your GitHub repository to view and monitor your pipeline.",{"type":25,"tag":52,"props":891,"children":893},{"id":892},"advantages-of-automating-java-builds",[894],{"type":34,"value":895},"Advantages of Automating Java Builds",{"type":25,"tag":59,"props":897,"children":898},{},[899,904,909,914],{"type":25,"tag":63,"props":900,"children":901},{},[902],{"type":34,"value":903},"Rapid feedback on code integration.",{"type":25,"tag":63,"props":905,"children":906},{},[907],{"type":34,"value":908},"Early identification of bugs and issues.",{"type":25,"tag":63,"props":910,"children":911},{},[912],{"type":34,"value":913},"Automated testing and deployment.",{"type":25,"tag":63,"props":915,"children":916},{},[917],{"type":34,"value":918},"Improved reliability and consistency.",{"type":25,"tag":52,"props":920,"children":922},{"id":921},"pricing",[923],{"type":34,"value":924},"Pricing",{"type":25,"tag":30,"props":926,"children":927},{},[928],{"type":34,"value":929},"GitHub Actions offers a generous free tier suitable for most projects:",{"type":25,"tag":59,"props":931,"children":932},{},[933,959],{"type":25,"tag":63,"props":934,"children":935},{},[936,941],{"type":25,"tag":37,"props":937,"children":938},{},[939],{"type":34,"value":940},"Free Tier:",{"type":25,"tag":59,"props":942,"children":943},{},[944,949,954],{"type":25,"tag":63,"props":945,"children":946},{},[947],{"type":34,"value":948},"2,000 minutes per month (private repositories).",{"type":25,"tag":63,"props":950,"children":951},{},[952],{"type":34,"value":953},"Unlimited minutes for public repositories.",{"type":25,"tag":63,"props":955,"children":956},{},[957],{"type":34,"value":958},"Free usage of GitHub-hosted runners for most common tasks.",{"type":25,"tag":63,"props":960,"children":961},{},[962,967],{"type":25,"tag":37,"props":963,"children":964},{},[965],{"type":34,"value":966},"Additional Minutes:",{"type":25,"tag":59,"props":968,"children":969},{},[970,975,980],{"type":25,"tag":63,"props":971,"children":972},{},[973],{"type":34,"value":974},"$0.008 per minute for Linux runners.",{"type":25,"tag":63,"props":976,"children":977},{},[978],{"type":34,"value":979},"$0.016 per minute for Windows runners.",{"type":25,"tag":63,"props":981,"children":982},{},[983],{"type":34,"value":984},"$0.08 per minute for macOS runners.",{"type":25,"tag":30,"props":986,"children":987},{},[988,990,999],{"type":34,"value":989},"For detailed and up-to-date pricing, visit ",{"type":25,"tag":991,"props":992,"children":996},"a",{"href":993,"rel":994},"https://github.com/pricing#actions",[995],"nofollow",[997],{"type":34,"value":998},"GitHub Actions Pricing",{"type":34,"value":50},{"type":25,"tag":52,"props":1001,"children":1003},{"id":1002},"conclusion",[1004],{"type":34,"value":1005},"Conclusion",{"type":25,"tag":30,"props":1007,"children":1008},{},[1009],{"type":34,"value":1010},"Using GitHub Actions with Maven provides an efficient and robust CI/CD pipeline, significantly improving your development lifecycle. Start implementing these practices today and streamline your Java application delivery.",{"type":25,"tag":1012,"props":1013,"children":1015},"h1",{"id":1014},"further-reading",[1016],{"type":34,"value":1017},"Further Reading",{"type":25,"tag":59,"props":1019,"children":1020},{},[1021,1031,1041],{"type":25,"tag":63,"props":1022,"children":1023},{},[1024],{"type":25,"tag":991,"props":1025,"children":1028},{"href":1026,"rel":1027},"https://docs.github.com/en/actions",[995],[1029],{"type":34,"value":1030},"GitHub Actions Documentation",{"type":25,"tag":63,"props":1032,"children":1033},{},[1034],{"type":25,"tag":991,"props":1035,"children":1038},{"href":1036,"rel":1037},"https://maven.apache.org/guides/",[995],[1039],{"type":34,"value":1040},"Maven Official Documentation",{"type":25,"tag":63,"props":1042,"children":1043},{},[1044],{"type":25,"tag":991,"props":1045,"children":1048},{"href":1046,"rel":1047},"https://docs.github.com/en/packages/working-with-a-github-packages-registry",[995],[1049],{"type":34,"value":1050},"Publishing Java Packages with GitHub Packages",{"type":25,"tag":1052,"props":1053,"children":1054},"style",{},[1055],{"type":34,"value":1056},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":7,"searchDepth":149,"depth":149,"links":1058},[1059,1060,1061,1062,1063,1064,1065,1066,1067],{"id":54,"depth":149,"text":57},{"id":83,"depth":149,"text":86},{"id":475,"depth":149,"text":478},{"id":540,"depth":149,"text":543},{"id":712,"depth":149,"text":715},{"id":807,"depth":149,"text":810},{"id":892,"depth":149,"text":895},{"id":921,"depth":149,"text":924},{"id":1002,"depth":149,"text":1005},"markdown","content:posts:maven-and-github-actions.md","content","posts/maven-and-github-actions.md","posts/maven-and-github-actions","md",[1075,2231,3626],{"_path":1076,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1077,"description":1078,"layout":11,"date":1079,"subtitle":1080,"image":1081,"optimized_image":1081,"category":14,"tags":1082,"author":20,"paginate":6,"body":1086,"_type":1068,"_id":2228,"_source":1070,"_file":2229,"_stem":2230,"_extension":1073},"/posts/ai-development-workspace-opencode-openspec","Build Your AI Development Workspace with OpenCode and OpenSpec","A practical introduction to OpenCode, OpenSpec, AGENTS.md, custom agents, and skills, using one small API change to show what each piece does.","2026-10-06T05:00:00.000Z","Give an AI coding agent context, a clear change, and reusable ways to work","/assets/img/uploads/ai-development-workspace.jpg",[14,1083,1084,1085],"ai","architecture","best practices",{"type":22,"children":1087,"toc":2221},[1088,1101,1132,1256,1276,1282,1295,1346,1358,1387,1450,1463,1469,1474,1488,1538,1568,1581,1596,1601,1640,1666,1751,1763,1769,1783,1796,1897,1927,1932,1938,1958,1969,2098,2126,2138,2144,2149,2207,2212,2217],{"type":25,"tag":30,"props":1089,"children":1090},{},[1091,1093,1099],{"type":34,"value":1092},"An AI coding agent can edit files and run tests within minutes. That speed is useful, but it also makes a familiar mistake faster: implementing a reasonable answer to a question nobody has properly defined. The agent may know the language and framework while knowing very little about ",{"type":25,"tag":1094,"props":1095,"children":1096},"em",{},[1097],{"type":34,"value":1098},"your",{"type":34,"value":1100}," repository, the behavior you promised users, or the checks your team expects before a change is done.",{"type":25,"tag":30,"props":1102,"children":1103},{},[1104,1106,1113,1115,1121,1123,1130],{"type":34,"value":1105},"My starting point is a small development workspace with five distinct parts: ",{"type":25,"tag":991,"props":1107,"children":1110},{"href":1108,"rel":1109},"https://opencode.ai/docs/",[995],[1111],{"type":34,"value":1112},"OpenCode",{"type":34,"value":1114}," to work in the repository, ",{"type":25,"tag":14,"props":1116,"children":1118},{"className":1117},[],[1119],{"type":34,"value":1120},"AGENTS.md",{"type":34,"value":1122}," to explain the repository, ",{"type":25,"tag":991,"props":1124,"children":1127},{"href":1125,"rel":1126},"https://github.com/Fission-AI/OpenSpec/blob/main/docs/getting-started.md",[995],[1128],{"type":34,"value":1129},"OpenSpec",{"type":34,"value":1131}," to make a change reviewable, and custom agents and skills for work that benefits from a repeatable role or procedure. You do not need to configure every part on day one. It helps to understand what question each one answers.",{"type":25,"tag":1133,"props":1134,"children":1135},"table",{},[1136,1160],{"type":25,"tag":1137,"props":1138,"children":1139},"thead",{},[1140],{"type":25,"tag":1141,"props":1142,"children":1143},"tr",{},[1144,1150,1155],{"type":25,"tag":1145,"props":1146,"children":1147},"th",{},[1148],{"type":34,"value":1149},"Piece",{"type":25,"tag":1145,"props":1151,"children":1152},{},[1153],{"type":34,"value":1154},"Question it answers",{"type":25,"tag":1145,"props":1156,"children":1157},{},[1158],{"type":34,"value":1159},"Example",{"type":25,"tag":1161,"props":1162,"children":1163},"tbody",{},[1164,1182,1203,1220,1238],{"type":25,"tag":1141,"props":1165,"children":1166},{},[1167,1172,1177],{"type":25,"tag":1168,"props":1169,"children":1170},"td",{},[1171],{"type":34,"value":1112},{"type":25,"tag":1168,"props":1173,"children":1174},{},[1175],{"type":34,"value":1176},"Who can inspect and change this repository with me?",{"type":25,"tag":1168,"props":1178,"children":1179},{},[1180],{"type":34,"value":1181},"Explore the API, edit code, run tests",{"type":25,"tag":1141,"props":1183,"children":1184},{},[1185,1193,1198],{"type":25,"tag":1168,"props":1186,"children":1187},{},[1188],{"type":25,"tag":14,"props":1189,"children":1191},{"className":1190},[],[1192],{"type":34,"value":1120},{"type":25,"tag":1168,"props":1194,"children":1195},{},[1196],{"type":34,"value":1197},"What should any agent know about this project?",{"type":25,"tag":1168,"props":1199,"children":1200},{},[1201],{"type":34,"value":1202},"Architecture, commands, conventions",{"type":25,"tag":1141,"props":1204,"children":1205},{},[1206,1210,1215],{"type":25,"tag":1168,"props":1207,"children":1208},{},[1209],{"type":34,"value":1129},{"type":25,"tag":1168,"props":1211,"children":1212},{},[1213],{"type":34,"value":1214},"What behavior are we agreeing to change?",{"type":25,"tag":1168,"props":1216,"children":1217},{},[1218],{"type":34,"value":1219},"A named change with scenarios and tasks",{"type":25,"tag":1141,"props":1221,"children":1222},{},[1223,1228,1233],{"type":25,"tag":1168,"props":1224,"children":1225},{},[1226],{"type":34,"value":1227},"Custom agent",{"type":25,"tag":1168,"props":1229,"children":1230},{},[1231],{"type":34,"value":1232},"Who should take a specialized role?",{"type":25,"tag":1168,"props":1234,"children":1235},{},[1236],{"type":34,"value":1237},"A reviewer with no edit permission",{"type":25,"tag":1141,"props":1239,"children":1240},{},[1241,1246,1251],{"type":25,"tag":1168,"props":1242,"children":1243},{},[1244],{"type":34,"value":1245},"Skill",{"type":25,"tag":1168,"props":1247,"children":1248},{},[1249],{"type":34,"value":1250},"How do we repeat a particular procedure?",{"type":25,"tag":1168,"props":1252,"children":1253},{},[1254],{"type":34,"value":1255},"A checklist for reviewing API compatibility",{"type":25,"tag":30,"props":1257,"children":1258},{},[1259,1261,1267,1269,1275],{"type":34,"value":1260},"These are different layers, not competing ways to write a longer prompt. Let's assemble them around a small example: an existing task API needs an optional ",{"type":25,"tag":14,"props":1262,"children":1264},{"className":1263},[],[1265],{"type":34,"value":1266},"status",{"type":34,"value":1268}," filter on ",{"type":25,"tag":14,"props":1270,"children":1272},{"className":1271},[],[1273],{"type":34,"value":1274},"GET /tasks",{"type":34,"value":50},{"type":25,"tag":52,"props":1277,"children":1279},{"id":1278},"start-with-repository-context",[1280],{"type":34,"value":1281},"Start with repository context",{"type":25,"tag":30,"props":1283,"children":1284},{},[1285,1287,1293],{"type":34,"value":1286},"Install OpenCode using one of the methods in its ",{"type":25,"tag":991,"props":1288,"children":1290},{"href":1108,"rel":1289},[995],[1291],{"type":34,"value":1292},"official setup guide",{"type":34,"value":1294},". For a Node-based setup, the documented command is:",{"type":25,"tag":93,"props":1296,"children":1298},{"className":820,"code":1297,"language":819,"meta":7,"style":7},"npm install -g opencode-ai\ncd path/to/your-project\nopencode\n",[1299],{"type":25,"tag":14,"props":1300,"children":1301},{"__ignoreMap":7},[1302,1325,1338],{"type":25,"tag":124,"props":1303,"children":1304},{"class":126,"line":127},[1305,1310,1315,1320],{"type":25,"tag":124,"props":1306,"children":1307},{"style":830},[1308],{"type":34,"value":1309},"npm",{"type":25,"tag":124,"props":1311,"children":1312},{"style":143},[1313],{"type":34,"value":1314}," install",{"type":25,"tag":124,"props":1316,"children":1317},{"style":163},[1318],{"type":34,"value":1319}," -g",{"type":25,"tag":124,"props":1321,"children":1322},{"style":143},[1323],{"type":34,"value":1324}," opencode-ai\n",{"type":25,"tag":124,"props":1326,"children":1327},{"class":126,"line":149},[1328,1333],{"type":25,"tag":124,"props":1329,"children":1330},{"style":163},[1331],{"type":34,"value":1332},"cd",{"type":25,"tag":124,"props":1334,"children":1335},{"style":143},[1336],{"type":34,"value":1337}," path/to/your-project\n",{"type":25,"tag":124,"props":1339,"children":1340},{"class":126,"line":159},[1341],{"type":25,"tag":124,"props":1342,"children":1343},{"style":830},[1344],{"type":34,"value":1345},"opencode\n",{"type":25,"tag":30,"props":1347,"children":1348},{},[1349,1351,1356],{"type":34,"value":1350},"Connect a model provider using OpenCode's setup flow. Before asking it to implement the filter, let it inspect the project: “Where is ",{"type":25,"tag":14,"props":1352,"children":1354},{"className":1353},[],[1355],{"type":34,"value":1274},{"type":34,"value":1357}," implemented, and which tests define its current behavior?” A useful answer should point to actual files and acknowledge uncertainty. If it invents an endpoint, stop there; adding more roles will not repair missing context.",{"type":25,"tag":30,"props":1359,"children":1360},{},[1361,1363,1368,1370,1376,1378,1385],{"type":34,"value":1362},"OpenCode can create a starter ",{"type":25,"tag":14,"props":1364,"children":1366},{"className":1365},[],[1367],{"type":34,"value":1120},{"type":34,"value":1369}," with ",{"type":25,"tag":14,"props":1371,"children":1373},{"className":1372},[],[1374],{"type":34,"value":1375},"/init",{"type":34,"value":1377},". Review what it writes. This file is durable project context, so it should contain facts and working rules that future sessions need, rather than a transcript of today's task. OpenCode's ",{"type":25,"tag":991,"props":1379,"children":1382},{"href":1380,"rel":1381},"https://opencode.ai/docs/rules/",[995],[1383],{"type":34,"value":1384},"rules documentation",{"type":34,"value":1386}," recommends project-specific instructions such as build commands, architecture, and conventions. A compact example:",{"type":25,"tag":93,"props":1388,"children":1391},{"className":1389,"code":1390,"language":1068,"meta":7,"style":7},"language-markdown shiki shiki-themes github-dark github-light","# Task API\n\n- Routes live in `src/http`; persistence lives in `src/repositories`.\n- Preserve the response shape of existing endpoints.\n- Run `npm test` for behavior changes and `npm run build` before handoff.\n- Do not modify database migrations that have already been applied.\n- Record API contract changes in `openapi.yaml`.\n",[1392],{"type":25,"tag":14,"props":1393,"children":1394},{"__ignoreMap":7},[1395,1403,1410,1418,1426,1434,1442],{"type":25,"tag":124,"props":1396,"children":1397},{"class":126,"line":127},[1398],{"type":25,"tag":124,"props":1399,"children":1400},{},[1401],{"type":34,"value":1402},"# Task API\n",{"type":25,"tag":124,"props":1404,"children":1405},{"class":126,"line":149},[1406],{"type":25,"tag":124,"props":1407,"children":1408},{"emptyLinePlaceholder":153},[1409],{"type":34,"value":156},{"type":25,"tag":124,"props":1411,"children":1412},{"class":126,"line":159},[1413],{"type":25,"tag":124,"props":1414,"children":1415},{},[1416],{"type":34,"value":1417},"- Routes live in `src/http`; persistence lives in `src/repositories`.\n",{"type":25,"tag":124,"props":1419,"children":1420},{"class":126,"line":174},[1421],{"type":25,"tag":124,"props":1422,"children":1423},{},[1424],{"type":34,"value":1425},"- Preserve the response shape of existing endpoints.\n",{"type":25,"tag":124,"props":1427,"children":1428},{"class":126,"line":187},[1429],{"type":25,"tag":124,"props":1430,"children":1431},{},[1432],{"type":34,"value":1433},"- Run `npm test` for behavior changes and `npm run build` before handoff.\n",{"type":25,"tag":124,"props":1435,"children":1436},{"class":126,"line":211},[1437],{"type":25,"tag":124,"props":1438,"children":1439},{},[1440],{"type":34,"value":1441},"- Do not modify database migrations that have already been applied.\n",{"type":25,"tag":124,"props":1443,"children":1444},{"class":126,"line":224},[1445],{"type":25,"tag":124,"props":1446,"children":1447},{},[1448],{"type":34,"value":1449},"- Record API contract changes in `openapi.yaml`.\n",{"type":25,"tag":30,"props":1451,"children":1452},{},[1453,1455,1461],{"type":34,"value":1454},"The exact paths and commands must match your repository. If your actual check is ",{"type":25,"tag":14,"props":1456,"children":1458},{"className":1457},[],[1459],{"type":34,"value":1460},"./gradlew test",{"type":34,"value":1462},", write that instead. Treat this file like code: review it, keep it short, and update it when the project changes. It should help an agent avoid avoidable mistakes; it cannot guarantee correctness.",{"type":25,"tag":52,"props":1464,"children":1466},{"id":1465},"give-the-change-a-contract-with-openspec",[1467],{"type":34,"value":1468},"Give the change a contract with OpenSpec",{"type":25,"tag":30,"props":1470,"children":1471},{},[1472],{"type":34,"value":1473},"The request “add a status filter” still leaves questions. Which statuses are valid? What happens when the parameter is omitted? Is an unknown status an empty result or a client error? Does filtering happen before pagination? Those decisions affect clients, so I want them visible before implementation.",{"type":25,"tag":30,"props":1475,"children":1476},{},[1477,1479,1486],{"type":34,"value":1478},"OpenSpec can be installed and initialized from the terminal. Its ",{"type":25,"tag":991,"props":1480,"children":1483},{"href":1481,"rel":1482},"https://github.com/Fission-AI/OpenSpec/blob/main/docs/installation.md",[995],[1484],{"type":34,"value":1485},"installation guide",{"type":34,"value":1487}," documents the Node requirement and setup commands:",{"type":25,"tag":93,"props":1489,"children":1491},{"className":820,"code":1490,"language":819,"meta":7,"style":7},"npm install -g @fission-ai/openspec@latest\nopenspec init --tools opencode\n",[1492],{"type":25,"tag":14,"props":1493,"children":1494},{"__ignoreMap":7},[1495,1515],{"type":25,"tag":124,"props":1496,"children":1497},{"class":126,"line":127},[1498,1502,1506,1510],{"type":25,"tag":124,"props":1499,"children":1500},{"style":830},[1501],{"type":34,"value":1309},{"type":25,"tag":124,"props":1503,"children":1504},{"style":143},[1505],{"type":34,"value":1314},{"type":25,"tag":124,"props":1507,"children":1508},{"style":163},[1509],{"type":34,"value":1319},{"type":25,"tag":124,"props":1511,"children":1512},{"style":143},[1513],{"type":34,"value":1514}," @fission-ai/openspec@latest\n",{"type":25,"tag":124,"props":1516,"children":1517},{"class":126,"line":149},[1518,1523,1528,1533],{"type":25,"tag":124,"props":1519,"children":1520},{"style":830},[1521],{"type":34,"value":1522},"openspec",{"type":25,"tag":124,"props":1524,"children":1525},{"style":143},[1526],{"type":34,"value":1527}," init",{"type":25,"tag":124,"props":1529,"children":1530},{"style":163},[1531],{"type":34,"value":1532}," --tools",{"type":25,"tag":124,"props":1534,"children":1535},{"style":143},[1536],{"type":34,"value":1537}," opencode\n",{"type":25,"tag":30,"props":1539,"children":1540},{},[1541,1543,1549,1551,1557,1559,1566],{"type":34,"value":1542},"Run ",{"type":25,"tag":14,"props":1544,"children":1546},{"className":1545},[],[1547],{"type":34,"value":1548},"openspec init",{"type":34,"value":1550}," at the project root. The ",{"type":25,"tag":14,"props":1552,"children":1554},{"className":1553},[],[1555],{"type":34,"value":1556},"--tools opencode",{"type":34,"value":1558}," option selects the OpenCode integration without an interactive picker. OpenSpec will create its planning directory and the workflow files for that integration. Check the generated files rather than assuming that every tool exposes the same slash-command names; OpenSpec's ",{"type":25,"tag":991,"props":1560,"children":1563},{"href":1561,"rel":1562},"https://github.com/Fission-AI/OpenSpec/blob/main/docs/supported-tools.md",[995],[1564],{"type":34,"value":1565},"tool reference",{"type":34,"value":1567}," describes those differences.",{"type":25,"tag":30,"props":1569,"children":1570},{},[1571,1573,1579],{"type":34,"value":1572},"In OpenCode, the OpenSpec integration currently provides ",{"type":25,"tag":14,"props":1574,"children":1576},{"className":1575},[],[1577],{"type":34,"value":1578},"/opsx-propose",{"type":34,"value":1580}," for this step. Start a named change in the chat:",{"type":25,"tag":93,"props":1582,"children":1585},{"className":1583,"code":1584,"language":34,"meta":7,"style":7},"language-text shiki shiki-themes github-dark github-light","/opsx-propose add-task-status-filter\n",[1586],{"type":25,"tag":14,"props":1587,"children":1588},{"__ignoreMap":7},[1589],{"type":25,"tag":124,"props":1590,"children":1591},{"class":126,"line":127},[1592],{"type":25,"tag":124,"props":1593,"children":1594},{},[1595],{"type":34,"value":1584},{"type":25,"tag":30,"props":1597,"children":1598},{},[1599],{"type":34,"value":1600},"Give it the context the command needs:",{"type":25,"tag":93,"props":1602,"children":1604},{"className":1583,"code":1603,"language":34,"meta":7,"style":7},"Propose a change to add an optional status filter to GET /tasks.\nInspect the current endpoint and tests first. Define the behavior when the\nparameter is absent, when it is valid, and when it is invalid. Do not\nimplement the change until we have reviewed the proposal and scenarios.\n",[1605],{"type":25,"tag":14,"props":1606,"children":1607},{"__ignoreMap":7},[1608,1616,1624,1632],{"type":25,"tag":124,"props":1609,"children":1610},{"class":126,"line":127},[1611],{"type":25,"tag":124,"props":1612,"children":1613},{},[1614],{"type":34,"value":1615},"Propose a change to add an optional status filter to GET /tasks.\n",{"type":25,"tag":124,"props":1617,"children":1618},{"class":126,"line":149},[1619],{"type":25,"tag":124,"props":1620,"children":1621},{},[1622],{"type":34,"value":1623},"Inspect the current endpoint and tests first. Define the behavior when the\n",{"type":25,"tag":124,"props":1625,"children":1626},{"class":126,"line":159},[1627],{"type":25,"tag":124,"props":1628,"children":1629},{},[1630],{"type":34,"value":1631},"parameter is absent, when it is valid, and when it is invalid. Do not\n",{"type":25,"tag":124,"props":1633,"children":1634},{"class":126,"line":174},[1635],{"type":25,"tag":124,"props":1636,"children":1637},{},[1638],{"type":34,"value":1639},"implement the change until we have reviewed the proposal and scenarios.\n",{"type":25,"tag":30,"props":1641,"children":1642},{},[1643,1645,1650,1652,1657,1659,1664],{"type":34,"value":1644},"The generated command is part of the OpenSpec workflow installed for OpenCode; check the names printed by ",{"type":25,"tag":14,"props":1646,"children":1648},{"className":1647},[],[1649],{"type":34,"value":1548},{"type":34,"value":1651}," if your installation differs. In the default spec-driven structure, a proposal explains ",{"type":25,"tag":1094,"props":1653,"children":1654},{},[1655],{"type":34,"value":1656},"why",{"type":34,"value":1658},", a spec describes ",{"type":25,"tag":1094,"props":1660,"children":1661},{},[1662],{"type":34,"value":1663},"observable behavior",{"type":34,"value":1665},", a design captures technical decisions when needed, and tasks track implementation. A scenario might say:",{"type":25,"tag":93,"props":1667,"children":1669},{"className":1389,"code":1668,"language":1068,"meta":7,"style":7},"### Requirement: Filter tasks by status\nThe API MUST allow clients to filter tasks by a supported status.\n\n#### Scenario: No filter\n- **WHEN** a client calls GET /tasks without a status parameter\n- **THEN** the existing unfiltered behavior is preserved\n\n#### Scenario: Unsupported status\n- **WHEN** a client requests a status the API does not support\n- **THEN** the API returns the documented validation error\n",[1670],{"type":25,"tag":14,"props":1671,"children":1672},{"__ignoreMap":7},[1673,1681,1689,1696,1704,1712,1720,1727,1735,1743],{"type":25,"tag":124,"props":1674,"children":1675},{"class":126,"line":127},[1676],{"type":25,"tag":124,"props":1677,"children":1678},{},[1679],{"type":34,"value":1680},"### Requirement: Filter tasks by status\n",{"type":25,"tag":124,"props":1682,"children":1683},{"class":126,"line":149},[1684],{"type":25,"tag":124,"props":1685,"children":1686},{},[1687],{"type":34,"value":1688},"The API MUST allow clients to filter tasks by a supported status.\n",{"type":25,"tag":124,"props":1690,"children":1691},{"class":126,"line":159},[1692],{"type":25,"tag":124,"props":1693,"children":1694},{"emptyLinePlaceholder":153},[1695],{"type":34,"value":156},{"type":25,"tag":124,"props":1697,"children":1698},{"class":126,"line":174},[1699],{"type":25,"tag":124,"props":1700,"children":1701},{},[1702],{"type":34,"value":1703},"#### Scenario: No filter\n",{"type":25,"tag":124,"props":1705,"children":1706},{"class":126,"line":187},[1707],{"type":25,"tag":124,"props":1708,"children":1709},{},[1710],{"type":34,"value":1711},"- **WHEN** a client calls GET /tasks without a status parameter\n",{"type":25,"tag":124,"props":1713,"children":1714},{"class":126,"line":211},[1715],{"type":25,"tag":124,"props":1716,"children":1717},{},[1718],{"type":34,"value":1719},"- **THEN** the existing unfiltered behavior is preserved\n",{"type":25,"tag":124,"props":1721,"children":1722},{"class":126,"line":224},[1723],{"type":25,"tag":124,"props":1724,"children":1725},{"emptyLinePlaceholder":153},[1726],{"type":34,"value":156},{"type":25,"tag":124,"props":1728,"children":1729},{"class":126,"line":244},[1730],{"type":25,"tag":124,"props":1731,"children":1732},{},[1733],{"type":34,"value":1734},"#### Scenario: Unsupported status\n",{"type":25,"tag":124,"props":1736,"children":1737},{"class":126,"line":252},[1738],{"type":25,"tag":124,"props":1739,"children":1740},{},[1741],{"type":34,"value":1742},"- **WHEN** a client requests a status the API does not support\n",{"type":25,"tag":124,"props":1744,"children":1745},{"class":126,"line":265},[1746],{"type":25,"tag":124,"props":1747,"children":1748},{},[1749],{"type":34,"value":1750},"- **THEN** the API returns the documented validation error\n",{"type":25,"tag":30,"props":1752,"children":1753},{},[1754,1756,1762],{"type":34,"value":1755},"That is only an example contract. Your API may deliberately handle invalid values differently. Decide it with the people who own the API, then write the chosen behavior down. For a deeper walkthrough of proposals, scenarios, and design trade-offs, see my ",{"type":25,"tag":991,"props":1757,"children":1759},{"href":1758},"/spec-driven-development-openspec/",[1760],{"type":34,"value":1761},"OpenSpec article",{"type":34,"value":50},{"type":25,"tag":52,"props":1764,"children":1766},{"id":1765},"add-a-specialist-only-when-it-has-a-clear-job",[1767],{"type":34,"value":1768},"Add a specialist only when it has a clear job",{"type":25,"tag":30,"props":1770,"children":1771},{},[1772,1774,1781],{"type":34,"value":1773},"Once the change is implemented, a second perspective can help. OpenCode supports ",{"type":25,"tag":991,"props":1775,"children":1778},{"href":1776,"rel":1777},"https://opencode.ai/docs/agents/",[995],[1779],{"type":34,"value":1780},"custom primary agents and subagents",{"type":34,"value":1782},". A custom agent has a role and its own permissions; it is not a second specification. For this example, a review subagent should read the diff and contract, question edge cases, and report findings without editing files.",{"type":25,"tag":30,"props":1784,"children":1785},{},[1786,1788,1794],{"type":34,"value":1787},"Create ",{"type":25,"tag":14,"props":1789,"children":1791},{"className":1790},[],[1792],{"type":34,"value":1793},".opencode/agents/api-reviewer.md",{"type":34,"value":1795},":",{"type":25,"tag":93,"props":1797,"children":1799},{"className":1389,"code":1798,"language":1068,"meta":7,"style":7},"---\ndescription: Reviews API changes against their contract and existing clients\nmode: subagent\npermission:\n  edit: deny\n  bash: ask\n---\n\nReview the proposed API change and its tests. Compare the implementation\nwith the OpenSpec scenarios and the existing endpoint behavior. Look for\ncompatibility changes, pagination mistakes, invalid input handling, and\nmissing tests. Cite the files and lines behind each finding. Do not edit.\n",[1800],{"type":25,"tag":14,"props":1801,"children":1802},{"__ignoreMap":7},[1803,1811,1819,1827,1835,1843,1851,1858,1865,1873,1881,1889],{"type":25,"tag":124,"props":1804,"children":1805},{"class":126,"line":127},[1806],{"type":25,"tag":124,"props":1807,"children":1808},{},[1809],{"type":34,"value":1810},"---\n",{"type":25,"tag":124,"props":1812,"children":1813},{"class":126,"line":149},[1814],{"type":25,"tag":124,"props":1815,"children":1816},{},[1817],{"type":34,"value":1818},"description: Reviews API changes against their contract and existing clients\n",{"type":25,"tag":124,"props":1820,"children":1821},{"class":126,"line":159},[1822],{"type":25,"tag":124,"props":1823,"children":1824},{},[1825],{"type":34,"value":1826},"mode: subagent\n",{"type":25,"tag":124,"props":1828,"children":1829},{"class":126,"line":174},[1830],{"type":25,"tag":124,"props":1831,"children":1832},{},[1833],{"type":34,"value":1834},"permission:\n",{"type":25,"tag":124,"props":1836,"children":1837},{"class":126,"line":187},[1838],{"type":25,"tag":124,"props":1839,"children":1840},{},[1841],{"type":34,"value":1842},"  edit: deny\n",{"type":25,"tag":124,"props":1844,"children":1845},{"class":126,"line":211},[1846],{"type":25,"tag":124,"props":1847,"children":1848},{},[1849],{"type":34,"value":1850},"  bash: ask\n",{"type":25,"tag":124,"props":1852,"children":1853},{"class":126,"line":224},[1854],{"type":25,"tag":124,"props":1855,"children":1856},{},[1857],{"type":34,"value":1810},{"type":25,"tag":124,"props":1859,"children":1860},{"class":126,"line":244},[1861],{"type":25,"tag":124,"props":1862,"children":1863},{"emptyLinePlaceholder":153},[1864],{"type":34,"value":156},{"type":25,"tag":124,"props":1866,"children":1867},{"class":126,"line":252},[1868],{"type":25,"tag":124,"props":1869,"children":1870},{},[1871],{"type":34,"value":1872},"Review the proposed API change and its tests. Compare the implementation\n",{"type":25,"tag":124,"props":1874,"children":1875},{"class":126,"line":265},[1876],{"type":25,"tag":124,"props":1877,"children":1878},{},[1879],{"type":34,"value":1880},"with the OpenSpec scenarios and the existing endpoint behavior. Look for\n",{"type":25,"tag":124,"props":1882,"children":1883},{"class":126,"line":278},[1884],{"type":25,"tag":124,"props":1885,"children":1886},{},[1887],{"type":34,"value":1888},"compatibility changes, pagination mistakes, invalid input handling, and\n",{"type":25,"tag":124,"props":1890,"children":1891},{"class":126,"line":296},[1892],{"type":25,"tag":124,"props":1893,"children":1894},{},[1895],{"type":34,"value":1896},"missing tests. Cite the files and lines behind each finding. Do not edit.\n",{"type":25,"tag":30,"props":1898,"children":1899},{},[1900,1902,1908,1910,1916,1918,1925],{"type":34,"value":1901},"You can invoke that subagent by mentioning ",{"type":25,"tag":14,"props":1903,"children":1905},{"className":1904},[],[1906],{"type":34,"value":1907},"@api-reviewer",{"type":34,"value":1909}," in OpenCode. It cannot edit files, and it must ask before running a shell command such as ",{"type":25,"tag":14,"props":1911,"children":1913},{"className":1912},[],[1914],{"type":34,"value":1915},"git diff",{"type":34,"value":1917},". Those permissions make its role explicit. They do not make its analysis infallible: the engineer still decides whether each finding is correct and whether the implementation satisfies the contract. OpenCode's ",{"type":25,"tag":991,"props":1919,"children":1922},{"href":1920,"rel":1921},"https://opencode.ai/docs/permissions/",[995],[1923],{"type":34,"value":1924},"permissions guide",{"type":34,"value":1926}," is worth reading before giving a specialist shell or editing access.",{"type":25,"tag":30,"props":1928,"children":1929},{},[1930],{"type":34,"value":1931},"Do you need separate “backend”, “tester”, “architect”, and “reviewer” agents for this endpoint? Probably not. Start with the general agent and add a specialist when you can name a recurring job, its inputs, its output, and the permissions it needs. More agents can also create more handoffs and inconsistent assumptions.",{"type":25,"tag":52,"props":1933,"children":1935},{"id":1934},"capture-a-procedure-as-a-skill",[1936],{"type":34,"value":1937},"Capture a procedure as a skill",{"type":25,"tag":30,"props":1939,"children":1940},{},[1941,1943,1948,1950,1956],{"type":34,"value":1942},"A skill is different from an agent. It packages instructions for ",{"type":25,"tag":1094,"props":1944,"children":1945},{},[1946],{"type":34,"value":1947},"how",{"type":34,"value":1949}," to do a recurring task, and OpenCode loads it when relevant. For instance, the API reviewer's role is “review this change”; an ",{"type":25,"tag":14,"props":1951,"children":1953},{"className":1952},[],[1954],{"type":34,"value":1955},"api-compatibility",{"type":34,"value":1957}," skill could define the checks the reviewer should apply across many API changes.",{"type":25,"tag":30,"props":1959,"children":1960},{},[1961,1962,1968],{"type":34,"value":1787},{"type":25,"tag":14,"props":1963,"children":1965},{"className":1964},[],[1966],{"type":34,"value":1967},".opencode/skills/api-compatibility/SKILL.md",{"type":34,"value":1795},{"type":25,"tag":93,"props":1970,"children":1972},{"className":1389,"code":1971,"language":1068,"meta":7,"style":7},"---\nname: api-compatibility\ndescription: Check an API change for client-visible compatibility risks\n---\n\n## Use this skill when\n\nAn endpoint, request, response, or documented error behavior changes.\n\n## Procedure\n\n1. Read the current API contract and at least one existing client call.\n2. Compare old and new request and response shapes.\n3. Check defaults, invalid input, pagination, and error status codes.\n4. Identify changes that require clients to deploy at the same time.\n5. Report each risk with evidence and a compatible migration option.\n",[1973],{"type":25,"tag":14,"props":1974,"children":1975},{"__ignoreMap":7},[1976,1983,1991,1999,2006,2013,2021,2028,2036,2043,2051,2058,2066,2074,2082,2090],{"type":25,"tag":124,"props":1977,"children":1978},{"class":126,"line":127},[1979],{"type":25,"tag":124,"props":1980,"children":1981},{},[1982],{"type":34,"value":1810},{"type":25,"tag":124,"props":1984,"children":1985},{"class":126,"line":149},[1986],{"type":25,"tag":124,"props":1987,"children":1988},{},[1989],{"type":34,"value":1990},"name: api-compatibility\n",{"type":25,"tag":124,"props":1992,"children":1993},{"class":126,"line":159},[1994],{"type":25,"tag":124,"props":1995,"children":1996},{},[1997],{"type":34,"value":1998},"description: Check an API change for client-visible compatibility risks\n",{"type":25,"tag":124,"props":2000,"children":2001},{"class":126,"line":174},[2002],{"type":25,"tag":124,"props":2003,"children":2004},{},[2005],{"type":34,"value":1810},{"type":25,"tag":124,"props":2007,"children":2008},{"class":126,"line":187},[2009],{"type":25,"tag":124,"props":2010,"children":2011},{"emptyLinePlaceholder":153},[2012],{"type":34,"value":156},{"type":25,"tag":124,"props":2014,"children":2015},{"class":126,"line":211},[2016],{"type":25,"tag":124,"props":2017,"children":2018},{},[2019],{"type":34,"value":2020},"## Use this skill when\n",{"type":25,"tag":124,"props":2022,"children":2023},{"class":126,"line":224},[2024],{"type":25,"tag":124,"props":2025,"children":2026},{"emptyLinePlaceholder":153},[2027],{"type":34,"value":156},{"type":25,"tag":124,"props":2029,"children":2030},{"class":126,"line":244},[2031],{"type":25,"tag":124,"props":2032,"children":2033},{},[2034],{"type":34,"value":2035},"An endpoint, request, response, or documented error behavior changes.\n",{"type":25,"tag":124,"props":2037,"children":2038},{"class":126,"line":252},[2039],{"type":25,"tag":124,"props":2040,"children":2041},{"emptyLinePlaceholder":153},[2042],{"type":34,"value":156},{"type":25,"tag":124,"props":2044,"children":2045},{"class":126,"line":265},[2046],{"type":25,"tag":124,"props":2047,"children":2048},{},[2049],{"type":34,"value":2050},"## Procedure\n",{"type":25,"tag":124,"props":2052,"children":2053},{"class":126,"line":278},[2054],{"type":25,"tag":124,"props":2055,"children":2056},{"emptyLinePlaceholder":153},[2057],{"type":34,"value":156},{"type":25,"tag":124,"props":2059,"children":2060},{"class":126,"line":296},[2061],{"type":25,"tag":124,"props":2062,"children":2063},{},[2064],{"type":34,"value":2065},"1. Read the current API contract and at least one existing client call.\n",{"type":25,"tag":124,"props":2067,"children":2068},{"class":126,"line":304},[2069],{"type":25,"tag":124,"props":2070,"children":2071},{},[2072],{"type":34,"value":2073},"2. Compare old and new request and response shapes.\n",{"type":25,"tag":124,"props":2075,"children":2076},{"class":126,"line":317},[2077],{"type":25,"tag":124,"props":2078,"children":2079},{},[2080],{"type":34,"value":2081},"3. Check defaults, invalid input, pagination, and error status codes.\n",{"type":25,"tag":124,"props":2083,"children":2084},{"class":126,"line":340},[2085],{"type":25,"tag":124,"props":2086,"children":2087},{},[2088],{"type":34,"value":2089},"4. Identify changes that require clients to deploy at the same time.\n",{"type":25,"tag":124,"props":2091,"children":2092},{"class":126,"line":361},[2093],{"type":25,"tag":124,"props":2094,"children":2095},{},[2096],{"type":34,"value":2097},"5. Report each risk with evidence and a compatible migration option.\n",{"type":25,"tag":30,"props":2099,"children":2100},{},[2101,2103,2110,2112,2117,2118,2124],{"type":34,"value":2102},"OpenCode's ",{"type":25,"tag":991,"props":2104,"children":2107},{"href":2105,"rel":2106},"https://opencode.ai/docs/skills/",[995],[2108],{"type":34,"value":2109},"skill documentation",{"type":34,"value":2111}," describes this directory structure and the required ",{"type":25,"tag":14,"props":2113,"children":2115},{"className":2114},[],[2116],{"type":34,"value":134},{"type":34,"value":43},{"type":25,"tag":14,"props":2119,"children":2121},{"className":2120},[],[2122],{"type":34,"value":2123},"description",{"type":34,"value":2125}," frontmatter. Keep the description specific: it helps the agent decide whether to load the skill. A skill should carry a procedure that survives one task. If it merely repeats today's prompt, it probably belongs in the conversation instead.",{"type":25,"tag":30,"props":2127,"children":2128},{},[2129,2131,2136],{"type":34,"value":2130},"You can ask the reviewer to use ",{"type":25,"tag":14,"props":2132,"children":2134},{"className":2133},[],[2135],{"type":34,"value":1955},{"type":34,"value":2137},", or let the agent discover it when the task matches its description. For a small project, you might not need this file yet. Write it when you notice yourself repeating the same review steps across changes.",{"type":25,"tag":52,"props":2139,"children":2141},{"id":2140},"run-one-complete-change",[2142],{"type":34,"value":2143},"Run one complete change",{"type":25,"tag":30,"props":2145,"children":2146},{},[2147],{"type":34,"value":2148},"With those pieces in place, the loop is straightforward:",{"type":25,"tag":2150,"props":2151,"children":2152},"ol",{},[2153,2158,2163,2182,2194],{"type":25,"tag":63,"props":2154,"children":2155},{},[2156],{"type":34,"value":2157},"Ask OpenCode to inspect the current endpoint and tests.",{"type":25,"tag":63,"props":2159,"children":2160},{},[2161],{"type":34,"value":2162},"Use OpenSpec to propose the filter and review its scenarios. Resolve ambiguous client behavior before coding.",{"type":25,"tag":63,"props":2164,"children":2165},{},[2166,2168,2174,2176,2181],{"type":34,"value":2167},"Use ",{"type":25,"tag":14,"props":2169,"children":2171},{"className":2170},[],[2172],{"type":34,"value":2173},"/opsx-apply",{"type":34,"value":2175}," to implement the approved change, then run the repository checks from ",{"type":25,"tag":14,"props":2177,"children":2179},{"className":2178},[],[2180],{"type":34,"value":1120},{"type":34,"value":50},{"type":25,"tag":63,"props":2183,"children":2184},{},[2185,2187,2192],{"type":34,"value":2186},"Ask ",{"type":25,"tag":14,"props":2188,"children":2190},{"className":2189},[],[2191],{"type":34,"value":1907},{"type":34,"value":2193}," to compare the result with the spec and apply the compatibility procedure.",{"type":25,"tag":63,"props":2195,"children":2196},{},[2197,2199,2205],{"type":34,"value":2198},"Read the diff, run the checks yourself or inspect their output, and correct any mismatch. Update the spec if a decision legitimately changed during implementation. Archive the change with ",{"type":25,"tag":14,"props":2200,"children":2202},{"className":2201},[],[2203],{"type":34,"value":2204},"/opsx-archive",{"type":34,"value":2206}," once its implementation is finished and accepted.",{"type":25,"tag":30,"props":2208,"children":2209},{},[2210],{"type":34,"value":2211},"Notice what remains the developer's responsibility: choosing the behavior, assessing trade-offs, checking evidence, and deciding when the change is ready. The workspace helps the agent work with better context and leaves a clearer trail for a human reviewer. It does not turn a generated patch into a trustworthy one by itself.",{"type":25,"tag":30,"props":2213,"children":2214},{},[2215],{"type":34,"value":2216},"There is also no prize for using every component in every repository. Start with OpenCode and accurate project instructions. Add OpenSpec when the risk is implementing the wrong behavior; add a custom agent when a specialized perspective repeatedly helps; add a skill when a procedure deserves to be reused. That progression keeps the setup understandable as the project grows.",{"type":25,"tag":1052,"props":2218,"children":2219},{},[2220],{"type":34,"value":1056},{"title":7,"searchDepth":149,"depth":149,"links":2222},[2223,2224,2225,2226,2227],{"id":1278,"depth":149,"text":1281},{"id":1465,"depth":149,"text":1468},{"id":1765,"depth":149,"text":1768},{"id":1934,"depth":149,"text":1937},{"id":2140,"depth":149,"text":2143},"content:posts:ai-development-workspace-opencode-openspec.md","posts/ai-development-workspace-opencode-openspec.md","posts/ai-development-workspace-opencode-openspec",{"_path":2232,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":2233,"description":2234,"layout":11,"date":2235,"subtitle":2236,"image":2237,"optimized_image":2237,"category":14,"tags":2238,"author":20,"paginate":6,"body":2240,"_type":1068,"_id":3623,"_source":1070,"_file":3624,"_stem":3625,"_extension":1073},"/posts/spec-driven-development-openspec","Spec-Driven Development with OpenSpec: From Vague Request to Verifiable Change","A practical OpenSpec walkthrough: clarify an order-cancellation request, write behavior as scenarios, make design decisions, implement with AI, and revise the spec when reality changes.","2025-08-15T11:00:00.000Z","Turn an idea into behavior you can review, implement, and test","/assets/img/uploads/sdd-openspec.jpg",[14,1084,1083,2239],"testing",{"type":22,"children":2241,"toc":3611},[2242,2247,2259,2264,2270,2275,2378,2397,2402,2408,2413,2482,2555,2560,2691,2696,2702,2715,2720,2957,2970,2991,2997,3002,3087,3100,3112,3118,3128,3167,3172,3186,3192,3197,3236,3263,3276,3282,3301,3306,3345,3357,3363,3368,3467,3472,3519,3524,3544,3550,3563,3568,3573,3607],{"type":25,"tag":30,"props":2243,"children":2244},{},[2245],{"type":34,"value":2246},"“Let customers cancel orders” sounds like a small feature. A developer or an AI coding agent could start writing an endpoint immediately. A few hours later, the code might compile and the happy-path test might pass. We could still disagree about what happens after shipment starts, whether a retry creates a second cancellation, or which customer is allowed to act.",{"type":25,"tag":30,"props":2248,"children":2249},{},[2250,2252,2257],{"type":34,"value":2251},"I use OpenSpec for this kind of conversation: make the intended behavior visible before implementation hides the unanswered questions. ",{"type":25,"tag":37,"props":2253,"children":2254},{},[2255],{"type":34,"value":2256},"Spec-driven development (SDD) is useful when the expensive mistake is building a plausible interpretation of an unclear request.",{"type":34,"value":2258}," It does not remove judgment from the engineer. It gives people and AI agents something concrete to review, challenge, and verify.",{"type":25,"tag":30,"props":2260,"children":2261},{},[2262],{"type":34,"value":2263},"Let's follow one illustrative change from the first request to a finished implementation. The business rules below are example decisions for this walkthrough, not universal rules for order systems.",{"type":25,"tag":52,"props":2265,"children":2267},{"id":2266},"the-request-is-not-yet-a-specification",[2268],{"type":34,"value":2269},"The request is not yet a specification",{"type":25,"tag":30,"props":2271,"children":2272},{},[2273],{"type":34,"value":2274},"Before opening an editor, ask what “cancel” means in this service:",{"type":25,"tag":1133,"props":2276,"children":2277},{},[2278,2294],{"type":25,"tag":1137,"props":2279,"children":2280},{},[2281],{"type":25,"tag":1141,"props":2282,"children":2283},{},[2284,2289],{"type":25,"tag":1145,"props":2285,"children":2286},{},[2287],{"type":34,"value":2288},"Question",{"type":25,"tag":1145,"props":2290,"children":2291},{},[2292],{"type":34,"value":2293},"A decision the team needs to make",{"type":25,"tag":1161,"props":2295,"children":2296},{},[2297,2310,2331,2344,2357],{"type":25,"tag":1141,"props":2298,"children":2299},{},[2300,2305],{"type":25,"tag":1168,"props":2301,"children":2302},{},[2303],{"type":34,"value":2304},"Who may cancel?",{"type":25,"tag":1168,"props":2306,"children":2307},{},[2308],{"type":34,"value":2309},"The order owner, an administrator, or both?",{"type":25,"tag":1141,"props":2311,"children":2312},{},[2313,2318],{"type":25,"tag":1168,"props":2314,"children":2315},{},[2316],{"type":34,"value":2317},"Until when?",{"type":25,"tag":1168,"props":2319,"children":2320},{},[2321,2323,2329],{"type":34,"value":2322},"Only while ",{"type":25,"tag":14,"props":2324,"children":2326},{"className":2325},[],[2327],{"type":34,"value":2328},"PENDING",{"type":34,"value":2330},", or also after payment and before shipment?",{"type":25,"tag":1141,"props":2332,"children":2333},{},[2334,2339],{"type":25,"tag":1168,"props":2335,"children":2336},{},[2337],{"type":34,"value":2338},"What does a retry do?",{"type":25,"tag":1168,"props":2340,"children":2341},{},[2342],{"type":34,"value":2343},"Return the existing cancellation, or fail?",{"type":25,"tag":1141,"props":2345,"children":2346},{},[2347,2352],{"type":25,"tag":1168,"props":2348,"children":2349},{},[2350],{"type":34,"value":2351},"What if two requests race?",{"type":25,"tag":1168,"props":2353,"children":2354},{},[2355],{"type":34,"value":2356},"Can both succeed without recording two transitions?",{"type":25,"tag":1141,"props":2358,"children":2359},{},[2360,2365],{"type":25,"tag":1168,"props":2361,"children":2362},{},[2363],{"type":34,"value":2364},"What happens downstream?",{"type":25,"tag":1168,"props":2366,"children":2367},{},[2368,2370,2376],{"type":34,"value":2369},"Is an ",{"type":25,"tag":14,"props":2371,"children":2373},{"className":2372},[],[2374],{"type":34,"value":2375},"OrderCancelled",{"type":34,"value":2377}," event part of the contract?",{"type":25,"tag":30,"props":2379,"children":2380},{},[2381,2383,2388,2390,2395],{"type":34,"value":2382},"The list is not an excuse to delay work indefinitely. It is a way to find the decisions that would otherwise be made accidentally in a controller, an ORM mapping, or an agent's generated code. For this example, assume the team agrees that the authenticated owner may cancel a ",{"type":25,"tag":14,"props":2384,"children":2386},{"className":2385},[],[2387],{"type":34,"value":2328},{"type":34,"value":2389}," order; a shipped order cannot be cancelled; repeating a successful request returns the cancelled state; and one logical ",{"type":25,"tag":14,"props":2391,"children":2393},{"className":2392},[],[2394],{"type":34,"value":2375},{"type":34,"value":2396}," event is produced for the transition.",{"type":25,"tag":30,"props":2398,"children":2399},{},[2400],{"type":34,"value":2401},"The goal is now specific enough to propose a change.",{"type":25,"tag":52,"props":2403,"children":2405},{"id":2404},"create-a-change-that-explains-why",[2406],{"type":34,"value":2407},"Create a change that explains why",{"type":25,"tag":30,"props":2409,"children":2410},{},[2411],{"type":34,"value":2412},"With OpenSpec initialized in the project, create a named change:",{"type":25,"tag":93,"props":2414,"children":2416},{"className":820,"code":2415,"language":819,"meta":7,"style":7},"openspec new change add-order-cancellation \\\n  --goal \"Customers can cancel their pending orders safely\"\nopenspec status --change add-order-cancellation\n",[2417],{"type":25,"tag":14,"props":2418,"children":2419},{"__ignoreMap":7},[2420,2447,2460],{"type":25,"tag":124,"props":2421,"children":2422},{"class":126,"line":127},[2423,2427,2432,2437,2442],{"type":25,"tag":124,"props":2424,"children":2425},{"style":830},[2426],{"type":34,"value":1522},{"type":25,"tag":124,"props":2428,"children":2429},{"style":143},[2430],{"type":34,"value":2431}," new",{"type":25,"tag":124,"props":2433,"children":2434},{"style":143},[2435],{"type":34,"value":2436}," change",{"type":25,"tag":124,"props":2438,"children":2439},{"style":143},[2440],{"type":34,"value":2441}," add-order-cancellation",{"type":25,"tag":124,"props":2443,"children":2444},{"style":163},[2445],{"type":34,"value":2446}," \\\n",{"type":25,"tag":124,"props":2448,"children":2449},{"class":126,"line":149},[2450,2455],{"type":25,"tag":124,"props":2451,"children":2452},{"style":163},[2453],{"type":34,"value":2454},"  --goal",{"type":25,"tag":124,"props":2456,"children":2457},{"style":143},[2458],{"type":34,"value":2459}," \"Customers can cancel their pending orders safely\"\n",{"type":25,"tag":124,"props":2461,"children":2462},{"class":126,"line":159},[2463,2467,2472,2477],{"type":25,"tag":124,"props":2464,"children":2465},{"style":830},[2466],{"type":34,"value":1522},{"type":25,"tag":124,"props":2468,"children":2469},{"style":143},[2470],{"type":34,"value":2471}," status",{"type":25,"tag":124,"props":2473,"children":2474},{"style":163},[2475],{"type":34,"value":2476}," --change",{"type":25,"tag":124,"props":2478,"children":2479},{"style":143},[2480],{"type":34,"value":2481}," add-order-cancellation\n",{"type":25,"tag":30,"props":2483,"children":2484},{},[2485,2487,2493,2495,2501,2503,2509,2511,2515,2517,2522,2524,2530,2532,2536,2538,2544,2546,2553],{"type":34,"value":2486},"The CLI creates ",{"type":25,"tag":14,"props":2488,"children":2490},{"className":2489},[],[2491],{"type":34,"value":2492},"openspec/changes/add-order-cancellation/",{"type":34,"value":2494},". In OpenSpec's default ",{"type":25,"tag":14,"props":2496,"children":2498},{"className":2497},[],[2499],{"type":34,"value":2500},"spec-driven",{"type":34,"value":2502}," workflow, ",{"type":25,"tag":14,"props":2504,"children":2506},{"className":2505},[],[2507],{"type":34,"value":2508},"proposal.md",{"type":34,"value":2510}," captures ",{"type":25,"tag":37,"props":2512,"children":2513},{},[2514],{"type":34,"value":1656},{"type":34,"value":2516},", a spec delta captures ",{"type":25,"tag":37,"props":2518,"children":2519},{},[2520],{"type":34,"value":2521},"what behavior changes",{"type":34,"value":2523},", ",{"type":25,"tag":14,"props":2525,"children":2527},{"className":2526},[],[2528],{"type":34,"value":2529},"design.md",{"type":34,"value":2531}," records ",{"type":25,"tag":37,"props":2533,"children":2534},{},[2535],{"type":34,"value":1947},{"type":34,"value":2537}," where a design is needed, and ",{"type":25,"tag":14,"props":2539,"children":2541},{"className":2540},[],[2542],{"type":34,"value":2543},"tasks.md",{"type":34,"value":2545}," tracks implementation. Proposal comes first; spec and design can follow in either order; tasks depend on both. The ",{"type":25,"tag":991,"props":2547,"children":2550},{"href":2548,"rel":2549},"https://openspec.dev/docs/schemas/spec-driven",[995],[2551],{"type":34,"value":2552},"schema reference",{"type":34,"value":2554}," describes those roles.",{"type":25,"tag":30,"props":2556,"children":2557},{},[2558],{"type":34,"value":2559},"The proposal should make the problem and scope clear without pretending to be the implementation plan. A shortened example:",{"type":25,"tag":93,"props":2561,"children":2563},{"className":1389,"code":2562,"language":1068,"meta":7,"style":7},"# Proposal\n\n## Why\nCustomers cannot cancel an eligible order without contacting support.\n\n## What Changes\n- Allow an authenticated owner to cancel a pending order.\n- Preserve a single logical cancellation when requests are repeated.\n- Publish the cancellation for downstream consumers.\n\n## Capabilities\n### New Capabilities\n- `order-cancellation`: Rules and outcomes of cancelling an order.\n\n## Impact\nOrder API, order persistence, cancellation event, and contract tests.\n",[2564],{"type":25,"tag":14,"props":2565,"children":2566},{"__ignoreMap":7},[2567,2575,2582,2590,2598,2605,2613,2621,2629,2637,2644,2652,2660,2668,2675,2683],{"type":25,"tag":124,"props":2568,"children":2569},{"class":126,"line":127},[2570],{"type":25,"tag":124,"props":2571,"children":2572},{},[2573],{"type":34,"value":2574},"# Proposal\n",{"type":25,"tag":124,"props":2576,"children":2577},{"class":126,"line":149},[2578],{"type":25,"tag":124,"props":2579,"children":2580},{"emptyLinePlaceholder":153},[2581],{"type":34,"value":156},{"type":25,"tag":124,"props":2583,"children":2584},{"class":126,"line":159},[2585],{"type":25,"tag":124,"props":2586,"children":2587},{},[2588],{"type":34,"value":2589},"## Why\n",{"type":25,"tag":124,"props":2591,"children":2592},{"class":126,"line":174},[2593],{"type":25,"tag":124,"props":2594,"children":2595},{},[2596],{"type":34,"value":2597},"Customers cannot cancel an eligible order without contacting support.\n",{"type":25,"tag":124,"props":2599,"children":2600},{"class":126,"line":187},[2601],{"type":25,"tag":124,"props":2602,"children":2603},{"emptyLinePlaceholder":153},[2604],{"type":34,"value":156},{"type":25,"tag":124,"props":2606,"children":2607},{"class":126,"line":211},[2608],{"type":25,"tag":124,"props":2609,"children":2610},{},[2611],{"type":34,"value":2612},"## What Changes\n",{"type":25,"tag":124,"props":2614,"children":2615},{"class":126,"line":224},[2616],{"type":25,"tag":124,"props":2617,"children":2618},{},[2619],{"type":34,"value":2620},"- Allow an authenticated owner to cancel a pending order.\n",{"type":25,"tag":124,"props":2622,"children":2623},{"class":126,"line":244},[2624],{"type":25,"tag":124,"props":2625,"children":2626},{},[2627],{"type":34,"value":2628},"- Preserve a single logical cancellation when requests are repeated.\n",{"type":25,"tag":124,"props":2630,"children":2631},{"class":126,"line":252},[2632],{"type":25,"tag":124,"props":2633,"children":2634},{},[2635],{"type":34,"value":2636},"- Publish the cancellation for downstream consumers.\n",{"type":25,"tag":124,"props":2638,"children":2639},{"class":126,"line":265},[2640],{"type":25,"tag":124,"props":2641,"children":2642},{"emptyLinePlaceholder":153},[2643],{"type":34,"value":156},{"type":25,"tag":124,"props":2645,"children":2646},{"class":126,"line":278},[2647],{"type":25,"tag":124,"props":2648,"children":2649},{},[2650],{"type":34,"value":2651},"## Capabilities\n",{"type":25,"tag":124,"props":2653,"children":2654},{"class":126,"line":296},[2655],{"type":25,"tag":124,"props":2656,"children":2657},{},[2658],{"type":34,"value":2659},"### New Capabilities\n",{"type":25,"tag":124,"props":2661,"children":2662},{"class":126,"line":304},[2663],{"type":25,"tag":124,"props":2664,"children":2665},{},[2666],{"type":34,"value":2667},"- `order-cancellation`: Rules and outcomes of cancelling an order.\n",{"type":25,"tag":124,"props":2669,"children":2670},{"class":126,"line":317},[2671],{"type":25,"tag":124,"props":2672,"children":2673},{"emptyLinePlaceholder":153},[2674],{"type":34,"value":156},{"type":25,"tag":124,"props":2676,"children":2677},{"class":126,"line":340},[2678],{"type":25,"tag":124,"props":2679,"children":2680},{},[2681],{"type":34,"value":2682},"## Impact\n",{"type":25,"tag":124,"props":2684,"children":2685},{"class":126,"line":361},[2686],{"type":25,"tag":124,"props":2687,"children":2688},{},[2689],{"type":34,"value":2690},"Order API, order persistence, cancellation event, and contract tests.\n",{"type":25,"tag":30,"props":2692,"children":2693},{},[2694],{"type":34,"value":2695},"At review time, the most useful question is whether this is the right change. If the request also asks for refunds, inventory release, and cancelling shipments, the proposal needs to state which of those are in scope and which require another change. A small, named unit of work is easier to understand and integrate.",{"type":25,"tag":52,"props":2697,"children":2699},{"id":2698},"put-observable-behavior-in-the-spec",[2700],{"type":34,"value":2701},"Put observable behavior in the spec",{"type":25,"tag":30,"props":2703,"children":2704},{},[2705,2707,2713],{"type":34,"value":2706},"The delta spec belongs under the capability named in the proposal, for example ",{"type":25,"tag":14,"props":2708,"children":2710},{"className":2709},[],[2711],{"type":34,"value":2712},"openspec/changes/add-order-cancellation/specs/order-cancellation/spec.md",{"type":34,"value":2714},". It describes the behavior that changes, not the classes or SQL statements that happen to implement it.",{"type":25,"tag":30,"props":2716,"children":2717},{},[2718],{"type":34,"value":2719},"Here is an illustrative excerpt in OpenSpec's requirement-and-scenario format:",{"type":25,"tag":93,"props":2721,"children":2723},{"className":1389,"code":2722,"language":1068,"meta":7,"style":7},"# Spec Delta\n\n## Purpose\nDefine when the owner of an order may cancel it and how the service\nresponds to rejected or repeated cancellation requests.\n\n## ADDED Requirements\n\n### Requirement: Cancel an eligible order\nThe service MUST allow the authenticated owner to cancel a PENDING order.\n\n#### Scenario: Eligible order\n- **WHEN** the owner requests cancellation of a PENDING order\n- **THEN** the order becomes CANCELLED and one logical OrderCancelled event is recorded\n\n#### Scenario: Shipment has started\n- **WHEN** the owner requests cancellation of a SHIPPED order\n- **THEN** the service rejects the request and leaves the order unchanged\n\n#### Scenario: Not the owner\n- **WHEN** a different authenticated customer requests cancellation\n- **THEN** the service rejects the request and leaves the order unchanged\n\n### Requirement: Repeat a completed cancellation\nThe service MUST handle repeated requests without creating a second cancellation.\n\n#### Scenario: Repeated request\n- **WHEN** the owner repeats a successful cancellation request\n- **THEN** the service returns the existing CANCELLED state without a new transition\n",[2724],{"type":25,"tag":14,"props":2725,"children":2726},{"__ignoreMap":7},[2727,2735,2742,2750,2758,2766,2773,2781,2788,2796,2804,2811,2819,2827,2835,2842,2850,2858,2866,2873,2881,2889,2896,2904,2913,2922,2930,2939,2948],{"type":25,"tag":124,"props":2728,"children":2729},{"class":126,"line":127},[2730],{"type":25,"tag":124,"props":2731,"children":2732},{},[2733],{"type":34,"value":2734},"# Spec Delta\n",{"type":25,"tag":124,"props":2736,"children":2737},{"class":126,"line":149},[2738],{"type":25,"tag":124,"props":2739,"children":2740},{"emptyLinePlaceholder":153},[2741],{"type":34,"value":156},{"type":25,"tag":124,"props":2743,"children":2744},{"class":126,"line":159},[2745],{"type":25,"tag":124,"props":2746,"children":2747},{},[2748],{"type":34,"value":2749},"## Purpose\n",{"type":25,"tag":124,"props":2751,"children":2752},{"class":126,"line":174},[2753],{"type":25,"tag":124,"props":2754,"children":2755},{},[2756],{"type":34,"value":2757},"Define when the owner of an order may cancel it and how the service\n",{"type":25,"tag":124,"props":2759,"children":2760},{"class":126,"line":187},[2761],{"type":25,"tag":124,"props":2762,"children":2763},{},[2764],{"type":34,"value":2765},"responds to rejected or repeated cancellation requests.\n",{"type":25,"tag":124,"props":2767,"children":2768},{"class":126,"line":211},[2769],{"type":25,"tag":124,"props":2770,"children":2771},{"emptyLinePlaceholder":153},[2772],{"type":34,"value":156},{"type":25,"tag":124,"props":2774,"children":2775},{"class":126,"line":224},[2776],{"type":25,"tag":124,"props":2777,"children":2778},{},[2779],{"type":34,"value":2780},"## ADDED Requirements\n",{"type":25,"tag":124,"props":2782,"children":2783},{"class":126,"line":244},[2784],{"type":25,"tag":124,"props":2785,"children":2786},{"emptyLinePlaceholder":153},[2787],{"type":34,"value":156},{"type":25,"tag":124,"props":2789,"children":2790},{"class":126,"line":252},[2791],{"type":25,"tag":124,"props":2792,"children":2793},{},[2794],{"type":34,"value":2795},"### Requirement: Cancel an eligible order\n",{"type":25,"tag":124,"props":2797,"children":2798},{"class":126,"line":265},[2799],{"type":25,"tag":124,"props":2800,"children":2801},{},[2802],{"type":34,"value":2803},"The service MUST allow the authenticated owner to cancel a PENDING order.\n",{"type":25,"tag":124,"props":2805,"children":2806},{"class":126,"line":278},[2807],{"type":25,"tag":124,"props":2808,"children":2809},{"emptyLinePlaceholder":153},[2810],{"type":34,"value":156},{"type":25,"tag":124,"props":2812,"children":2813},{"class":126,"line":296},[2814],{"type":25,"tag":124,"props":2815,"children":2816},{},[2817],{"type":34,"value":2818},"#### Scenario: Eligible order\n",{"type":25,"tag":124,"props":2820,"children":2821},{"class":126,"line":304},[2822],{"type":25,"tag":124,"props":2823,"children":2824},{},[2825],{"type":34,"value":2826},"- **WHEN** the owner requests cancellation of a PENDING order\n",{"type":25,"tag":124,"props":2828,"children":2829},{"class":126,"line":317},[2830],{"type":25,"tag":124,"props":2831,"children":2832},{},[2833],{"type":34,"value":2834},"- **THEN** the order becomes CANCELLED and one logical OrderCancelled event is recorded\n",{"type":25,"tag":124,"props":2836,"children":2837},{"class":126,"line":340},[2838],{"type":25,"tag":124,"props":2839,"children":2840},{"emptyLinePlaceholder":153},[2841],{"type":34,"value":156},{"type":25,"tag":124,"props":2843,"children":2844},{"class":126,"line":361},[2845],{"type":25,"tag":124,"props":2846,"children":2847},{},[2848],{"type":34,"value":2849},"#### Scenario: Shipment has started\n",{"type":25,"tag":124,"props":2851,"children":2852},{"class":126,"line":379},[2853],{"type":25,"tag":124,"props":2854,"children":2855},{},[2856],{"type":34,"value":2857},"- **WHEN** the owner requests cancellation of a SHIPPED order\n",{"type":25,"tag":124,"props":2859,"children":2860},{"class":126,"line":392},[2861],{"type":25,"tag":124,"props":2862,"children":2863},{},[2864],{"type":34,"value":2865},"- **THEN** the service rejects the request and leaves the order unchanged\n",{"type":25,"tag":124,"props":2867,"children":2868},{"class":126,"line":410},[2869],{"type":25,"tag":124,"props":2870,"children":2871},{"emptyLinePlaceholder":153},[2872],{"type":34,"value":156},{"type":25,"tag":124,"props":2874,"children":2875},{"class":126,"line":428},[2876],{"type":25,"tag":124,"props":2877,"children":2878},{},[2879],{"type":34,"value":2880},"#### Scenario: Not the owner\n",{"type":25,"tag":124,"props":2882,"children":2883},{"class":126,"line":436},[2884],{"type":25,"tag":124,"props":2885,"children":2886},{},[2887],{"type":34,"value":2888},"- **WHEN** a different authenticated customer requests cancellation\n",{"type":25,"tag":124,"props":2890,"children":2891},{"class":126,"line":457},[2892],{"type":25,"tag":124,"props":2893,"children":2894},{},[2895],{"type":34,"value":2865},{"type":25,"tag":124,"props":2897,"children":2899},{"class":126,"line":2898},23,[2900],{"type":25,"tag":124,"props":2901,"children":2902},{"emptyLinePlaceholder":153},[2903],{"type":34,"value":156},{"type":25,"tag":124,"props":2905,"children":2907},{"class":126,"line":2906},24,[2908],{"type":25,"tag":124,"props":2909,"children":2910},{},[2911],{"type":34,"value":2912},"### Requirement: Repeat a completed cancellation\n",{"type":25,"tag":124,"props":2914,"children":2916},{"class":126,"line":2915},25,[2917],{"type":25,"tag":124,"props":2918,"children":2919},{},[2920],{"type":34,"value":2921},"The service MUST handle repeated requests without creating a second cancellation.\n",{"type":25,"tag":124,"props":2923,"children":2925},{"class":126,"line":2924},26,[2926],{"type":25,"tag":124,"props":2927,"children":2928},{"emptyLinePlaceholder":153},[2929],{"type":34,"value":156},{"type":25,"tag":124,"props":2931,"children":2933},{"class":126,"line":2932},27,[2934],{"type":25,"tag":124,"props":2935,"children":2936},{},[2937],{"type":34,"value":2938},"#### Scenario: Repeated request\n",{"type":25,"tag":124,"props":2940,"children":2942},{"class":126,"line":2941},28,[2943],{"type":25,"tag":124,"props":2944,"children":2945},{},[2946],{"type":34,"value":2947},"- **WHEN** the owner repeats a successful cancellation request\n",{"type":25,"tag":124,"props":2949,"children":2951},{"class":126,"line":2950},29,[2952],{"type":25,"tag":124,"props":2953,"children":2954},{},[2955],{"type":34,"value":2956},"- **THEN** the service returns the existing CANCELLED state without a new transition\n",{"type":25,"tag":30,"props":2958,"children":2959},{},[2960,2962,2968],{"type":34,"value":2961},"This still leaves questions for the API contract: should a shipped order return ",{"type":25,"tag":14,"props":2963,"children":2965},{"className":2964},[],[2966],{"type":34,"value":2967},"409 Conflict",{"type":34,"value":2969},"? What should an unauthenticated caller see? Those decisions should be made explicitly and added to the relevant spec or existing API contract before implementation. A spec is useful because its gaps are visible to a reviewer.",{"type":25,"tag":30,"props":2971,"children":2972},{},[2973,2975,2981,2983,2989],{"type":34,"value":2974},"Keep the wording at the level of behavior. “Use optimistic locking in ",{"type":25,"tag":14,"props":2976,"children":2978},{"className":2977},[],[2979],{"type":34,"value":2980},"OrderRepository",{"type":34,"value":2982},"” is a possible design decision, not a customer-facing requirement. If the implementation can switch from one concurrency mechanism to another without changing the promise, the mechanism belongs in the design. OpenSpec's ",{"type":25,"tag":991,"props":2984,"children":2986},{"href":2548,"rel":2985},[995],[2987],{"type":34,"value":2988},"spec guidance",{"type":34,"value":2990}," makes the same distinction.",{"type":25,"tag":52,"props":2992,"children":2994},{"id":2993},"use-the-design-for-technical-trade-offs",[2995],{"type":34,"value":2996},"Use the design for technical trade-offs",{"type":25,"tag":30,"props":2998,"children":2999},{},[3000],{"type":34,"value":3001},"The design connects those requirements to the actual system. For this example, it might record:",{"type":25,"tag":93,"props":3003,"children":3005},{"className":1389,"code":3004,"language":1068,"meta":7,"style":7},"# Design\n\n## Decisions\n- Use an atomic conditional update to change PENDING to CANCELLED.\n- Record the OrderCancelled event in the same database transaction.\n- On a lost race, reload the order and return its current state when it is CANCELLED.\n\n## Trade-offs\n- A conditional update avoids holding a database lock across the request.\n- The event must be durable even if the process stops after commit.\n",[3006],{"type":25,"tag":14,"props":3007,"children":3008},{"__ignoreMap":7},[3009,3017,3024,3032,3040,3048,3056,3063,3071,3079],{"type":25,"tag":124,"props":3010,"children":3011},{"class":126,"line":127},[3012],{"type":25,"tag":124,"props":3013,"children":3014},{},[3015],{"type":34,"value":3016},"# Design\n",{"type":25,"tag":124,"props":3018,"children":3019},{"class":126,"line":149},[3020],{"type":25,"tag":124,"props":3021,"children":3022},{"emptyLinePlaceholder":153},[3023],{"type":34,"value":156},{"type":25,"tag":124,"props":3025,"children":3026},{"class":126,"line":159},[3027],{"type":25,"tag":124,"props":3028,"children":3029},{},[3030],{"type":34,"value":3031},"## Decisions\n",{"type":25,"tag":124,"props":3033,"children":3034},{"class":126,"line":174},[3035],{"type":25,"tag":124,"props":3036,"children":3037},{},[3038],{"type":34,"value":3039},"- Use an atomic conditional update to change PENDING to CANCELLED.\n",{"type":25,"tag":124,"props":3041,"children":3042},{"class":126,"line":187},[3043],{"type":25,"tag":124,"props":3044,"children":3045},{},[3046],{"type":34,"value":3047},"- Record the OrderCancelled event in the same database transaction.\n",{"type":25,"tag":124,"props":3049,"children":3050},{"class":126,"line":211},[3051],{"type":25,"tag":124,"props":3052,"children":3053},{},[3054],{"type":34,"value":3055},"- On a lost race, reload the order and return its current state when it is CANCELLED.\n",{"type":25,"tag":124,"props":3057,"children":3058},{"class":126,"line":224},[3059],{"type":25,"tag":124,"props":3060,"children":3061},{"emptyLinePlaceholder":153},[3062],{"type":34,"value":156},{"type":25,"tag":124,"props":3064,"children":3065},{"class":126,"line":244},[3066],{"type":25,"tag":124,"props":3067,"children":3068},{},[3069],{"type":34,"value":3070},"## Trade-offs\n",{"type":25,"tag":124,"props":3072,"children":3073},{"class":126,"line":252},[3074],{"type":25,"tag":124,"props":3075,"children":3076},{},[3077],{"type":34,"value":3078},"- A conditional update avoids holding a database lock across the request.\n",{"type":25,"tag":124,"props":3080,"children":3081},{"class":126,"line":265},[3082],{"type":25,"tag":124,"props":3083,"children":3084},{},[3085],{"type":34,"value":3086},"- The event must be durable even if the process stops after commit.\n",{"type":25,"tag":30,"props":3088,"children":3089},{},[3090,3092,3098],{"type":34,"value":3091},"That is a sketch, not production-ready code. It shows where the technical reasoning lives. A database lock could be valid instead; the design should explain why the chosen approach fits this service. If the event is required for downstream consistency, the ",{"type":25,"tag":991,"props":3093,"children":3095},{"href":3094},"/transactional-outbox/",[3096],{"type":34,"value":3097},"Transactional Outbox",{"type":34,"value":3099}," pattern is one way to make state and publication intent atomic.",{"type":25,"tag":30,"props":3101,"children":3102},{},[3103,3105,3110],{"type":34,"value":3104},"Design is not compulsory paperwork for every change. OpenSpec can omit ",{"type":25,"tag":14,"props":3106,"children":3108},{"className":3107},[],[3109],{"type":34,"value":2529},{"type":34,"value":3111}," when the change does not need a separate technical decision. It becomes valuable when the choice has consequences for concurrency, migrations, security, dependencies, or recovery.",{"type":25,"tag":52,"props":3113,"children":3115},{"id":3114},"turn-the-plan-into-work-you-can-verify",[3116],{"type":34,"value":3117},"Turn the plan into work you can verify",{"type":25,"tag":30,"props":3119,"children":3120},{},[3121,3126],{"type":25,"tag":14,"props":3122,"children":3124},{"className":3123},[],[3125],{"type":34,"value":2543},{"type":34,"value":3127}," turns the spec and design into an implementation checklist. Each item should end in an observable result:",{"type":25,"tag":93,"props":3129,"children":3131},{"className":1389,"code":3130,"language":1068,"meta":7,"style":7},"- [ ] Add the cancellation API and authorization checks.\n- [ ] Persist the order transition and event intent atomically.\n- [ ] Test eligible, shipped, unauthorized, and repeated requests.\n- [ ] Run the project checks and review the API response contract.\n",[3132],{"type":25,"tag":14,"props":3133,"children":3134},{"__ignoreMap":7},[3135,3143,3151,3159],{"type":25,"tag":124,"props":3136,"children":3137},{"class":126,"line":127},[3138],{"type":25,"tag":124,"props":3139,"children":3140},{},[3141],{"type":34,"value":3142},"- [ ] Add the cancellation API and authorization checks.\n",{"type":25,"tag":124,"props":3144,"children":3145},{"class":126,"line":149},[3146],{"type":25,"tag":124,"props":3147,"children":3148},{},[3149],{"type":34,"value":3150},"- [ ] Persist the order transition and event intent atomically.\n",{"type":25,"tag":124,"props":3152,"children":3153},{"class":126,"line":159},[3154],{"type":25,"tag":124,"props":3155,"children":3156},{},[3157],{"type":34,"value":3158},"- [ ] Test eligible, shipped, unauthorized, and repeated requests.\n",{"type":25,"tag":124,"props":3160,"children":3161},{"class":126,"line":174},[3162],{"type":25,"tag":124,"props":3163,"children":3164},{},[3165],{"type":34,"value":3166},"- [ ] Run the project checks and review the API response contract.\n",{"type":25,"tag":30,"props":3168,"children":3169},{},[3170],{"type":34,"value":3171},"The spec says what must be true. The tasks say what work remains. Tests provide evidence that the implementation behaves as promised. None of these files replaces the others.",{"type":25,"tag":30,"props":3173,"children":3174},{},[3175,3177,3184],{"type":34,"value":3176},"Before code starts, review the three questions OpenSpec's ",{"type":25,"tag":991,"props":3178,"children":3181},{"href":3179,"rel":3180},"https://openspec.dev/docs/quickstart",[995],[3182],{"type":34,"value":3183},"quickstart",{"type":34,"value":3185}," emphasizes: Is the proposal the right problem and size? Would you accept the spec's scenarios as done? Do the tasks cover those scenarios without adding unrelated work? Fixing a sentence now is cheaper than reviewing a large implementation of the wrong rule.",{"type":25,"tag":52,"props":3187,"children":3189},{"id":3188},"give-the-agent-a-bounded-handoff",[3190],{"type":34,"value":3191},"Give the agent a bounded handoff",{"type":25,"tag":30,"props":3193,"children":3194},{},[3195],{"type":34,"value":3196},"Once the artifacts are agreed, the implementation request can be short:",{"type":25,"tag":93,"props":3198,"children":3200},{"className":1583,"code":3199,"language":34,"meta":7,"style":7},"Apply the add-order-cancellation change. Read its proposal, spec,\ndesign, and tasks before editing code. Implement one task at a time.\nRun the relevant tests and report any requirement that is ambiguous\nor cannot be met by the current design. Do not invent a new business rule.\n",[3201],{"type":25,"tag":14,"props":3202,"children":3203},{"__ignoreMap":7},[3204,3212,3220,3228],{"type":25,"tag":124,"props":3205,"children":3206},{"class":126,"line":127},[3207],{"type":25,"tag":124,"props":3208,"children":3209},{},[3210],{"type":34,"value":3211},"Apply the add-order-cancellation change. Read its proposal, spec,\n",{"type":25,"tag":124,"props":3213,"children":3214},{"class":126,"line":149},[3215],{"type":25,"tag":124,"props":3216,"children":3217},{},[3218],{"type":34,"value":3219},"design, and tasks before editing code. Implement one task at a time.\n",{"type":25,"tag":124,"props":3221,"children":3222},{"class":126,"line":159},[3223],{"type":25,"tag":124,"props":3224,"children":3225},{},[3226],{"type":34,"value":3227},"Run the relevant tests and report any requirement that is ambiguous\n",{"type":25,"tag":124,"props":3229,"children":3230},{"class":126,"line":174},[3231],{"type":25,"tag":124,"props":3232,"children":3233},{},[3234],{"type":34,"value":3235},"or cannot be met by the current design. Do not invent a new business rule.\n",{"type":25,"tag":30,"props":3237,"children":3238},{},[3239,3241,3246,3248,3253,3255,3261],{"type":34,"value":3240},"OpenSpec calls this the ",{"type":25,"tag":37,"props":3242,"children":3243},{},[3244],{"type":34,"value":3245},"apply",{"type":34,"value":3247}," phase. It tracks progress in the checkboxes in ",{"type":25,"tag":14,"props":3249,"children":3251},{"className":3250},[],[3252],{"type":34,"value":2543},{"type":34,"value":3254},"; it is not a promise that the CLI has turned the spec into correct code. The ",{"type":25,"tag":991,"props":3256,"children":3258},{"href":3179,"rel":3257},[995],[3259],{"type":34,"value":3260},"OpenSpec workflow",{"type":34,"value":3262}," explicitly allows the artifacts to be corrected while work is underway.",{"type":25,"tag":30,"props":3264,"children":3265},{},[3266,3268,3274],{"type":34,"value":3267},"This is particularly useful with AI agents. A new session can read the change and the remaining tasks instead of relying on an old chat transcript. The agent still needs access to the actual codebase and its tests, and a person still needs to review behavior at the boundary of the change. If several agents work in parallel, give each a separate ",{"type":25,"tag":991,"props":3269,"children":3271},{"href":3270},"/git-worktrees-parallel-development/",[3272],{"type":34,"value":3273},"Git worktree",{"type":34,"value":3275}," and keep ownership of integration clear.",{"type":25,"tag":52,"props":3277,"children":3279},{"id":3278},"when-implementation-reveals-a-missing-scenario",[3280],{"type":34,"value":3281},"When implementation reveals a missing scenario",{"type":25,"tag":30,"props":3283,"children":3284},{},[3285,3287,3292,3294,3299],{"type":34,"value":3286},"Suppose a test exposes a race: two cancellation requests read the same ",{"type":25,"tag":14,"props":3288,"children":3290},{"className":3289},[],[3291],{"type":34,"value":2328},{"type":34,"value":3293}," order before either writes. The original repeated-request scenario covers a request made ",{"type":25,"tag":1094,"props":3295,"children":3296},{},[3297],{"type":34,"value":3298},"after",{"type":34,"value":3300}," cancellation, but does not say what simultaneous requests should do.",{"type":25,"tag":30,"props":3302,"children":3303},{},[3304],{"type":34,"value":3305},"Do not silently choose the easiest behavior in code. Add the missing scenario to the spec:",{"type":25,"tag":93,"props":3307,"children":3309},{"className":1389,"code":3308,"language":1068,"meta":7,"style":7},"#### Scenario: Concurrent cancellation requests\n- **WHEN** two requests from the owner try to cancel the same PENDING order\n- **THEN** exactly one cancellation transition and one logical event are recorded\n- **AND** both requests can observe the final CANCELLED state\n",[3310],{"type":25,"tag":14,"props":3311,"children":3312},{"__ignoreMap":7},[3313,3321,3329,3337],{"type":25,"tag":124,"props":3314,"children":3315},{"class":126,"line":127},[3316],{"type":25,"tag":124,"props":3317,"children":3318},{},[3319],{"type":34,"value":3320},"#### Scenario: Concurrent cancellation requests\n",{"type":25,"tag":124,"props":3322,"children":3323},{"class":126,"line":149},[3324],{"type":25,"tag":124,"props":3325,"children":3326},{},[3327],{"type":34,"value":3328},"- **WHEN** two requests from the owner try to cancel the same PENDING order\n",{"type":25,"tag":124,"props":3330,"children":3331},{"class":126,"line":159},[3332],{"type":25,"tag":124,"props":3333,"children":3334},{},[3335],{"type":34,"value":3336},"- **THEN** exactly one cancellation transition and one logical event are recorded\n",{"type":25,"tag":124,"props":3338,"children":3339},{"class":126,"line":174},[3340],{"type":25,"tag":124,"props":3341,"children":3342},{},[3343],{"type":34,"value":3344},"- **AND** both requests can observe the final CANCELLED state\n",{"type":25,"tag":30,"props":3346,"children":3347},{},[3348,3350,3355],{"type":34,"value":3349},"Then revisit the design and tasks. The conditional update may let one request win and the other load the newly cancelled row; add a concurrency test that actually runs both requests. If the chosen API semantics require one request to report a conflict instead, record that decision in the spec and test it. The point is that ",{"type":25,"tag":37,"props":3351,"children":3352},{},[3353],{"type":34,"value":3354},"the spec changes when understanding changes",{"type":34,"value":3356},". It should describe the behavior the team intends to ship, not preserve an early guess for appearances.",{"type":25,"tag":52,"props":3358,"children":3360},{"id":3359},"verify-the-implementation-then-archive-the-change",[3361],{"type":34,"value":3362},"Verify the implementation, then archive the change",{"type":25,"tag":30,"props":3364,"children":3365},{},[3366],{"type":34,"value":3367},"The final review needs evidence for each important scenario:",{"type":25,"tag":1133,"props":3369,"children":3370},{},[3371,3387],{"type":25,"tag":1137,"props":3372,"children":3373},{},[3374],{"type":25,"tag":1141,"props":3375,"children":3376},{},[3377,3382],{"type":25,"tag":1145,"props":3378,"children":3379},{},[3380],{"type":34,"value":3381},"Scenario",{"type":25,"tag":1145,"props":3383,"children":3384},{},[3385],{"type":34,"value":3386},"Useful evidence",{"type":25,"tag":1161,"props":3388,"children":3389},{},[3390,3403,3416,3428,3441,3454],{"type":25,"tag":1141,"props":3391,"children":3392},{},[3393,3398],{"type":25,"tag":1168,"props":3394,"children":3395},{},[3396],{"type":34,"value":3397},"Eligible owner",{"type":25,"tag":1168,"props":3399,"children":3400},{},[3401],{"type":34,"value":3402},"API test confirms the state transition and response",{"type":25,"tag":1141,"props":3404,"children":3405},{},[3406,3411],{"type":25,"tag":1168,"props":3407,"children":3408},{},[3409],{"type":34,"value":3410},"Shipped order",{"type":25,"tag":1168,"props":3412,"children":3413},{},[3414],{"type":34,"value":3415},"API test confirms rejection and unchanged state",{"type":25,"tag":1141,"props":3417,"children":3418},{},[3419,3424],{"type":25,"tag":1168,"props":3420,"children":3421},{},[3422],{"type":34,"value":3423},"Different customer",{"type":25,"tag":1168,"props":3425,"children":3426},{},[3427],{"type":34,"value":3415},{"type":25,"tag":1141,"props":3429,"children":3430},{},[3431,3436],{"type":25,"tag":1168,"props":3432,"children":3433},{},[3434],{"type":34,"value":3435},"Repeated request",{"type":25,"tag":1168,"props":3437,"children":3438},{},[3439],{"type":34,"value":3440},"Test confirms no second logical transition or event",{"type":25,"tag":1141,"props":3442,"children":3443},{},[3444,3449],{"type":25,"tag":1168,"props":3445,"children":3446},{},[3447],{"type":34,"value":3448},"Concurrent requests",{"type":25,"tag":1168,"props":3450,"children":3451},{},[3452],{"type":34,"value":3453},"Integration test confirms the chosen race behavior",{"type":25,"tag":1141,"props":3455,"children":3456},{},[3457,3462],{"type":25,"tag":1168,"props":3458,"children":3459},{},[3460],{"type":34,"value":3461},"Downstream event",{"type":25,"tag":1168,"props":3463,"children":3464},{},[3465],{"type":34,"value":3466},"Test confirms the event intent commits with the order change",{"type":25,"tag":30,"props":3468,"children":3469},{},[3470],{"type":34,"value":3471},"Run OpenSpec's structural validation too:",{"type":25,"tag":93,"props":3473,"children":3475},{"className":820,"code":3474,"language":819,"meta":7,"style":7},"openspec validate add-order-cancellation --strict\nopenspec status --change add-order-cancellation\n",[3476],{"type":25,"tag":14,"props":3477,"children":3478},{"__ignoreMap":7},[3479,3500],{"type":25,"tag":124,"props":3480,"children":3481},{"class":126,"line":127},[3482,3486,3491,3495],{"type":25,"tag":124,"props":3483,"children":3484},{"style":830},[3485],{"type":34,"value":1522},{"type":25,"tag":124,"props":3487,"children":3488},{"style":143},[3489],{"type":34,"value":3490}," validate",{"type":25,"tag":124,"props":3492,"children":3493},{"style":143},[3494],{"type":34,"value":2441},{"type":25,"tag":124,"props":3496,"children":3497},{"style":163},[3498],{"type":34,"value":3499}," --strict\n",{"type":25,"tag":124,"props":3501,"children":3502},{"class":126,"line":149},[3503,3507,3511,3515],{"type":25,"tag":124,"props":3504,"children":3505},{"style":830},[3506],{"type":34,"value":1522},{"type":25,"tag":124,"props":3508,"children":3509},{"style":143},[3510],{"type":34,"value":2471},{"type":25,"tag":124,"props":3512,"children":3513},{"style":163},[3514],{"type":34,"value":2476},{"type":25,"tag":124,"props":3516,"children":3517},{"style":143},[3518],{"type":34,"value":2481},{"type":25,"tag":30,"props":3520,"children":3521},{},[3522],{"type":34,"value":3523},"Validation checks the change artifacts; it cannot prove that the service meets its requirements. Run the project's tests, inspect the diff, and compare the result with the spec. If code and spec disagree, decide which is wrong before declaring the task complete.",{"type":25,"tag":30,"props":3525,"children":3526},{},[3527,3529,3535,3537,3542],{"type":34,"value":3528},"When the implementation and checklist are complete, archive the change. For a behavior-changing capability, OpenSpec moves the change folder to ",{"type":25,"tag":14,"props":3530,"children":3532},{"className":3531},[],[3533],{"type":34,"value":3534},"openspec/changes/archive/",{"type":34,"value":3536}," and merges the delta requirements into the main specs, so they describe the system as built. The ",{"type":25,"tag":991,"props":3538,"children":3540},{"href":3179,"rel":3539},[995],[3541],{"type":34,"value":3183},{"type":34,"value":3543}," walks through this step. Use your team's normal Git review and integration process around it; OpenSpec does not replace that process.",{"type":25,"tag":52,"props":3545,"children":3547},{"id":3546},"keep-the-method-proportional-to-the-change",[3548],{"type":34,"value":3549},"Keep the method proportional to the change",{"type":25,"tag":30,"props":3551,"children":3552},{},[3553,3555,3561],{"type":34,"value":3554},"SDD earns its keep when an unclear decision would be expensive: public API behavior, cross-service workflows, state transitions, migrations, or security rules. It is lighter when you keep each change small and write only the artifacts it needs. A typo fix does not require an invented business requirement. OpenSpec supports ",{"type":25,"tag":14,"props":3556,"children":3558},{"className":3557},[],[3559],{"type":34,"value":3560},"skip_specs: true",{"type":34,"value":3562}," for changes with no spec-level behavior change, such as documentation or some tooling work, and allows a design to be omitted when no design decision warrants it.",{"type":25,"tag":30,"props":3564,"children":3565},{},[3566],{"type":34,"value":3567},"For me, the value is not producing more Markdown. It is discovering disagreement while the change is still easy to reshape, and giving implementation and review a shared definition of done. An AI agent can accelerate the code. A good spec makes it easier to tell whether that acceleration took us in the right direction.",{"type":25,"tag":52,"props":3569,"children":3570},{"id":1014},[3571],{"type":34,"value":3572},"Further reading",{"type":25,"tag":59,"props":3574,"children":3575},{},[3576,3586,3596],{"type":25,"tag":63,"props":3577,"children":3578},{},[3579,3585],{"type":25,"tag":991,"props":3580,"children":3582},{"href":3179,"rel":3581},[995],[3583],{"type":34,"value":3584},"OpenSpec: Quickstart",{"type":34,"value":50},{"type":25,"tag":63,"props":3587,"children":3588},{},[3589,3595],{"type":25,"tag":991,"props":3590,"children":3592},{"href":2548,"rel":3591},[995],[3593],{"type":34,"value":3594},"OpenSpec: spec-driven schema",{"type":34,"value":50},{"type":25,"tag":63,"props":3597,"children":3598},{},[3599,3606],{"type":25,"tag":991,"props":3600,"children":3603},{"href":3601,"rel":3602},"https://openspec.dev/docs/cli",[995],[3604],{"type":34,"value":3605},"OpenSpec: CLI reference",{"type":34,"value":50},{"type":25,"tag":1052,"props":3608,"children":3609},{},[3610],{"type":34,"value":1056},{"title":7,"searchDepth":149,"depth":149,"links":3612},[3613,3614,3615,3616,3617,3618,3619,3620,3621,3622],{"id":2266,"depth":149,"text":2269},{"id":2404,"depth":149,"text":2407},{"id":2698,"depth":149,"text":2701},{"id":2993,"depth":149,"text":2996},{"id":3114,"depth":149,"text":3117},{"id":3188,"depth":149,"text":3191},{"id":3278,"depth":149,"text":3281},{"id":3359,"depth":149,"text":3362},{"id":3546,"depth":149,"text":3549},{"id":1014,"depth":149,"text":3572},"content:posts:spec-driven-development-openspec.md","posts/spec-driven-development-openspec.md","posts/spec-driven-development-openspec",{"_path":3627,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":3628,"description":3629,"layout":11,"date":3630,"subtitle":3631,"image":3632,"optimized_image":3632,"category":14,"tags":3633,"author":20,"paginate":6,"body":3634,"_type":1068,"_id":4806,"_source":1070,"_file":4807,"_stem":4808,"_extension":1073},"/posts/git-worktrees-parallel-development","Git Worktrees: Parallel Work Without Clobbering Your Workspace","A hands-on guide to Git worktrees: create isolated checkouts, run parallel development and AI agents, manage dependencies and ports, integrate safely, and clean up.","2025-07-15T11:00:00.000Z","A practical workflow for separate tasks, builds, and AI coding agents in one repository","/assets/img/uploads/git-worktrees-parallel-development.jpg",[14,833,1083,1085],{"type":22,"children":3635,"toc":4796},[3636,3641,3653,3658,3664,3708,3763,3784,3789,3795,3814,3924,3974,3995,4000,4049,4069,4075,4080,4100,4228,4233,4246,4252,4257,4296,4301,4306,4311,4317,4342,4420,4445,4450,4511,4544,4570,4576,4581,4623,4674,4709,4715,4720,4748,4753,4757,4792],{"type":25,"tag":30,"props":3637,"children":3638},{},[3639],{"type":34,"value":3640},"You are halfway through a search refactor when an urgent bug arrives. Or an AI coding agent is editing the API while you want another agent to work on tests. One checkout forces you to stop, stash, switch branches, and later reconstruct the context you had. Two agents in that checkout can overwrite each other's files or mistake unfinished work for their own.",{"type":25,"tag":30,"props":3642,"children":3643},{},[3644,3646,3651],{"type":34,"value":3645},"Git worktrees give each task a separate working directory and branch while keeping them attached to the same repository. This is useful for human parallelism, and especially useful now that several AI agents can produce useful code at the same time. ",{"type":25,"tag":37,"props":3647,"children":3648},{},[3649],{"type":34,"value":3650},"Parallel coding only helps when each task has an explicit boundary and the results can be integrated safely.",{"type":34,"value":3652}," Worktrees provide the filesystem boundary; they do not make design decisions or resolve conflicting changes for you.",{"type":25,"tag":30,"props":3654,"children":3655},{},[3656],{"type":34,"value":3657},"Here is a workflow you can try with a repository you already have.",{"type":25,"tag":52,"props":3659,"children":3661},{"id":3660},"what-a-worktree-actually-separates",[3662],{"type":34,"value":3663},"What a worktree actually separates",{"type":25,"tag":30,"props":3665,"children":3666},{},[3667,3669,3675,3677,3682,3684,3690,3692,3698,3700,3707],{"type":34,"value":3668},"A linked worktree has its own checked-out files, ",{"type":25,"tag":14,"props":3670,"children":3672},{"className":3671},[],[3673],{"type":34,"value":3674},"HEAD",{"type":34,"value":3676},", index, and uncommitted changes. The worktrees share the repository's objects and branch references. You can have ",{"type":25,"tag":14,"props":3678,"children":3680},{"className":3679},[],[3681],{"type":34,"value":497},{"type":34,"value":3683}," open in one directory, ",{"type":25,"tag":14,"props":3685,"children":3687},{"className":3686},[],[3688],{"type":34,"value":3689},"feature/search-index",{"type":34,"value":3691}," in another, and ",{"type":25,"tag":14,"props":3693,"children":3695},{"className":3694},[],[3696],{"type":34,"value":3697},"feature/api-tests",{"type":34,"value":3699}," in a third without cloning the repository three times. Git documents this model in ",{"type":25,"tag":991,"props":3701,"children":3704},{"href":3702,"rel":3703},"https://git-scm.com/docs/git-worktree",[995],[3705],{"type":34,"value":3706},"git-worktree",{"type":34,"value":50},{"type":25,"tag":93,"props":3709,"children":3711},{"className":1583,"code":3710,"language":34,"meta":7,"style":7},"workspace/\n├── project/                         main; clean integration checkout\n└── .worktrees/\n    └── project/\n        ├── search-index/            feature/search-index\n        └── api-tests/               feature/api-tests\n",[3712],{"type":25,"tag":14,"props":3713,"children":3714},{"__ignoreMap":7},[3715,3723,3731,3739,3747,3755],{"type":25,"tag":124,"props":3716,"children":3717},{"class":126,"line":127},[3718],{"type":25,"tag":124,"props":3719,"children":3720},{},[3721],{"type":34,"value":3722},"workspace/\n",{"type":25,"tag":124,"props":3724,"children":3725},{"class":126,"line":149},[3726],{"type":25,"tag":124,"props":3727,"children":3728},{},[3729],{"type":34,"value":3730},"├── project/                         main; clean integration checkout\n",{"type":25,"tag":124,"props":3732,"children":3733},{"class":126,"line":159},[3734],{"type":25,"tag":124,"props":3735,"children":3736},{},[3737],{"type":34,"value":3738},"└── .worktrees/\n",{"type":25,"tag":124,"props":3740,"children":3741},{"class":126,"line":174},[3742],{"type":25,"tag":124,"props":3743,"children":3744},{},[3745],{"type":34,"value":3746},"    └── project/\n",{"type":25,"tag":124,"props":3748,"children":3749},{"class":126,"line":187},[3750],{"type":25,"tag":124,"props":3751,"children":3752},{},[3753],{"type":34,"value":3754},"        ├── search-index/            feature/search-index\n",{"type":25,"tag":124,"props":3756,"children":3757},{"class":126,"line":211},[3758],{"type":25,"tag":124,"props":3759,"children":3760},{},[3761],{"type":34,"value":3762},"        └── api-tests/               feature/api-tests\n",{"type":25,"tag":30,"props":3764,"children":3765},{},[3766,3768,3774,3776,3782],{"type":34,"value":3767},"This layout keeps task checkouts outside the main checkout, so a file search or build in ",{"type":25,"tag":14,"props":3769,"children":3771},{"className":3770},[],[3772],{"type":34,"value":3773},"project/",{"type":34,"value":3775}," does not accidentally traverse the other tasks. Choose a location your tools can discover and that your team can inspect. ",{"type":25,"tag":14,"props":3777,"children":3779},{"className":3778},[],[3780],{"type":34,"value":3781},"git worktree list",{"type":34,"value":3783}," shows every worktree Git knows about, regardless of where you put it.",{"type":25,"tag":30,"props":3785,"children":3786},{},[3787],{"type":34,"value":3788},"Git normally prevents the same branch from being checked out in two worktrees at once. That safeguard matters: each task needs its own branch. Starting two tasks from the same commit is fine; letting them edit the same branch or directory is the collision we are trying to avoid.",{"type":25,"tag":52,"props":3790,"children":3792},{"id":3791},"create-two-task-checkouts",[3793],{"type":34,"value":3794},"Create two task checkouts",{"type":25,"tag":30,"props":3796,"children":3797},{},[3798,3800,3805,3807,3812],{"type":34,"value":3799},"Run these commands from the existing ",{"type":25,"tag":14,"props":3801,"children":3803},{"className":3802},[],[3804],{"type":34,"value":3773},{"type":34,"value":3806}," checkout while ",{"type":25,"tag":14,"props":3808,"children":3810},{"className":3809},[],[3811],{"type":34,"value":497},{"type":34,"value":3813}," is clean:",{"type":25,"tag":93,"props":3815,"children":3817},{"className":820,"code":3816,"language":819,"meta":7,"style":7},"mkdir -p ../.worktrees/project\ngit worktree add -b feature/search-index ../.worktrees/project/search-index main\ngit worktree add -b feature/api-tests ../.worktrees/project/api-tests main\ngit worktree list\n",[3818],{"type":25,"tag":14,"props":3819,"children":3820},{"__ignoreMap":7},[3821,3839,3875,3908],{"type":25,"tag":124,"props":3822,"children":3823},{"class":126,"line":127},[3824,3829,3834],{"type":25,"tag":124,"props":3825,"children":3826},{"style":830},[3827],{"type":34,"value":3828},"mkdir",{"type":25,"tag":124,"props":3830,"children":3831},{"style":163},[3832],{"type":34,"value":3833}," -p",{"type":25,"tag":124,"props":3835,"children":3836},{"style":143},[3837],{"type":34,"value":3838}," ../.worktrees/project\n",{"type":25,"tag":124,"props":3840,"children":3841},{"class":126,"line":149},[3842,3846,3851,3855,3860,3865,3870],{"type":25,"tag":124,"props":3843,"children":3844},{"style":830},[3845],{"type":34,"value":833},{"type":25,"tag":124,"props":3847,"children":3848},{"style":143},[3849],{"type":34,"value":3850}," worktree",{"type":25,"tag":124,"props":3852,"children":3853},{"style":143},[3854],{"type":34,"value":838},{"type":25,"tag":124,"props":3856,"children":3857},{"style":163},[3858],{"type":34,"value":3859}," -b",{"type":25,"tag":124,"props":3861,"children":3862},{"style":143},[3863],{"type":34,"value":3864}," feature/search-index",{"type":25,"tag":124,"props":3866,"children":3867},{"style":143},[3868],{"type":34,"value":3869}," ../.worktrees/project/search-index",{"type":25,"tag":124,"props":3871,"children":3872},{"style":143},[3873],{"type":34,"value":3874}," main\n",{"type":25,"tag":124,"props":3876,"children":3877},{"class":126,"line":159},[3878,3882,3886,3890,3894,3899,3904],{"type":25,"tag":124,"props":3879,"children":3880},{"style":830},[3881],{"type":34,"value":833},{"type":25,"tag":124,"props":3883,"children":3884},{"style":143},[3885],{"type":34,"value":3850},{"type":25,"tag":124,"props":3887,"children":3888},{"style":143},[3889],{"type":34,"value":838},{"type":25,"tag":124,"props":3891,"children":3892},{"style":163},[3893],{"type":34,"value":3859},{"type":25,"tag":124,"props":3895,"children":3896},{"style":143},[3897],{"type":34,"value":3898}," feature/api-tests",{"type":25,"tag":124,"props":3900,"children":3901},{"style":143},[3902],{"type":34,"value":3903}," ../.worktrees/project/api-tests",{"type":25,"tag":124,"props":3905,"children":3906},{"style":143},[3907],{"type":34,"value":3874},{"type":25,"tag":124,"props":3909,"children":3910},{"class":126,"line":174},[3911,3915,3919],{"type":25,"tag":124,"props":3912,"children":3913},{"style":830},[3914],{"type":34,"value":833},{"type":25,"tag":124,"props":3916,"children":3917},{"style":143},[3918],{"type":34,"value":3850},{"type":25,"tag":124,"props":3920,"children":3921},{"style":143},[3922],{"type":34,"value":3923}," list\n",{"type":25,"tag":30,"props":3925,"children":3926},{},[3927,3929,3935,3937,3942,3944,3949,3951,3958,3960,3966,3967,3973],{"type":34,"value":3928},"The ",{"type":25,"tag":14,"props":3930,"children":3932},{"className":3931},[],[3933],{"type":34,"value":3934},"-b",{"type":34,"value":3936}," flag creates each branch at ",{"type":25,"tag":14,"props":3938,"children":3940},{"className":3939},[],[3941],{"type":34,"value":497},{"type":34,"value":3943}," and checks it out in the new directory. The final ",{"type":25,"tag":14,"props":3945,"children":3947},{"className":3946},[],[3948],{"type":34,"value":497},{"type":34,"value":3950}," is the starting commit, stated explicitly so the result does not depend on whichever branch happens to be active in your shell. The ",{"type":25,"tag":991,"props":3952,"children":3955},{"href":3953,"rel":3954},"https://git-scm.com/docs/git-worktree#_commands",[995],[3956],{"type":34,"value":3957},"Git manual",{"type":34,"value":3959}," describes both ",{"type":25,"tag":14,"props":3961,"children":3963},{"className":3962},[],[3964],{"type":34,"value":3965},"add",{"type":34,"value":43},{"type":25,"tag":14,"props":3968,"children":3970},{"className":3969},[],[3971],{"type":34,"value":3972},"list",{"type":34,"value":50},{"type":25,"tag":30,"props":3975,"children":3976},{},[3977,3979,3985,3987,3993],{"type":34,"value":3978},"Give each task a short, testable objective. For example: “Add an index for the order search query and measure the plan before and after” in ",{"type":25,"tag":14,"props":3980,"children":3982},{"className":3981},[],[3983],{"type":34,"value":3984},"search-index",{"type":34,"value":3986},"; “Add API contract tests for the current order response” in ",{"type":25,"tag":14,"props":3988,"children":3990},{"className":3989},[],[3991],{"type":34,"value":3992},"api-tests",{"type":34,"value":3994},". If both tasks need to rewrite the same handler, separate worktrees will prevent filesystem collisions but the integration may still be difficult. Split the work by stable interfaces, or sequence the dependent edits.",{"type":25,"tag":30,"props":3996,"children":3997},{},[3998],{"type":34,"value":3999},"Move into one directory to work on it:",{"type":25,"tag":93,"props":4001,"children":4003},{"className":820,"code":4002,"language":819,"meta":7,"style":7},"cd ../.worktrees/project/search-index\ngit status --short --branch\n# Edit, run checks, and commit on feature/search-index.\n",[4004],{"type":25,"tag":14,"props":4005,"children":4006},{"__ignoreMap":7},[4007,4019,4040],{"type":25,"tag":124,"props":4008,"children":4009},{"class":126,"line":127},[4010,4014],{"type":25,"tag":124,"props":4011,"children":4012},{"style":163},[4013],{"type":34,"value":1332},{"type":25,"tag":124,"props":4015,"children":4016},{"style":143},[4017],{"type":34,"value":4018}," ../.worktrees/project/search-index\n",{"type":25,"tag":124,"props":4020,"children":4021},{"class":126,"line":149},[4022,4026,4030,4035],{"type":25,"tag":124,"props":4023,"children":4024},{"style":830},[4025],{"type":34,"value":833},{"type":25,"tag":124,"props":4027,"children":4028},{"style":143},[4029],{"type":34,"value":2471},{"type":25,"tag":124,"props":4031,"children":4032},{"style":163},[4033],{"type":34,"value":4034}," --short",{"type":25,"tag":124,"props":4036,"children":4037},{"style":163},[4038],{"type":34,"value":4039}," --branch\n",{"type":25,"tag":124,"props":4041,"children":4042},{"class":126,"line":159},[4043],{"type":25,"tag":124,"props":4044,"children":4046},{"style":4045},"--shiki-default:#6A737D;--shiki-light:#6A737D",[4047],{"type":34,"value":4048},"# Edit, run checks, and commit on feature/search-index.\n",{"type":25,"tag":30,"props":4050,"children":4051},{},[4052,4054,4060,4062,4067],{"type":34,"value":4053},"From that directory, ",{"type":25,"tag":14,"props":4055,"children":4057},{"className":4056},[],[4058],{"type":34,"value":4059},"git status",{"type":34,"value":4061}," describes only that task's checkout. Changes there will not appear in the ",{"type":25,"tag":14,"props":4063,"children":4065},{"className":4064},[],[4066],{"type":34,"value":3992},{"type":34,"value":4068}," working directory. New commits and branches do become visible through the shared repository, which is why integration still needs coordination.",{"type":25,"tag":52,"props":4070,"children":4072},{"id":4071},"give-each-checkout-its-own-runtime",[4073],{"type":34,"value":4074},"Give each checkout its own runtime",{"type":25,"tag":30,"props":4076,"children":4077},{},[4078],{"type":34,"value":4079},"Worktrees separate tracked files and untracked files on disk. They do not automatically isolate everything your application uses. A local database, a Docker project name, an output bucket, and TCP port 3000 can still be shared accidentally.",{"type":25,"tag":30,"props":4081,"children":4082},{},[4083,4085,4091,4093,4098],{"type":34,"value":4084},"For a Node project, install dependencies in each worktree. Do not symlink one ",{"type":25,"tag":14,"props":4086,"children":4088},{"className":4087},[],[4089],{"type":34,"value":4090},"node_modules",{"type":34,"value":4092}," directory into several worktrees: package tooling and build caches can resolve paths relative to the checkout. Use the project's lockfile and version manager, then start each server on a distinct port. Start each terminal in ",{"type":25,"tag":14,"props":4094,"children":4096},{"className":4095},[],[4097],{"type":34,"value":3773},{"type":34,"value":4099}," for this example:",{"type":25,"tag":93,"props":4101,"children":4103},{"className":820,"code":4102,"language":819,"meta":7,"style":7},"cd ../.worktrees/project/search-index\nnpm ci\nnpm run dev -- --port 3101\n\n# In a second terminal:\ncd ../.worktrees/project/api-tests\nnpm ci\nnpm run dev -- --port 3102\n",[4104],{"type":25,"tag":14,"props":4105,"children":4106},{"__ignoreMap":7},[4107,4118,4130,4162,4169,4177,4189,4200],{"type":25,"tag":124,"props":4108,"children":4109},{"class":126,"line":127},[4110,4114],{"type":25,"tag":124,"props":4111,"children":4112},{"style":163},[4113],{"type":34,"value":1332},{"type":25,"tag":124,"props":4115,"children":4116},{"style":143},[4117],{"type":34,"value":4018},{"type":25,"tag":124,"props":4119,"children":4120},{"class":126,"line":149},[4121,4125],{"type":25,"tag":124,"props":4122,"children":4123},{"style":830},[4124],{"type":34,"value":1309},{"type":25,"tag":124,"props":4126,"children":4127},{"style":143},[4128],{"type":34,"value":4129}," ci\n",{"type":25,"tag":124,"props":4131,"children":4132},{"class":126,"line":159},[4133,4137,4142,4147,4152,4157],{"type":25,"tag":124,"props":4134,"children":4135},{"style":830},[4136],{"type":34,"value":1309},{"type":25,"tag":124,"props":4138,"children":4139},{"style":143},[4140],{"type":34,"value":4141}," run",{"type":25,"tag":124,"props":4143,"children":4144},{"style":143},[4145],{"type":34,"value":4146}," dev",{"type":25,"tag":124,"props":4148,"children":4149},{"style":163},[4150],{"type":34,"value":4151}," --",{"type":25,"tag":124,"props":4153,"children":4154},{"style":163},[4155],{"type":34,"value":4156}," --port",{"type":25,"tag":124,"props":4158,"children":4159},{"style":163},[4160],{"type":34,"value":4161}," 3101\n",{"type":25,"tag":124,"props":4163,"children":4164},{"class":126,"line":174},[4165],{"type":25,"tag":124,"props":4166,"children":4167},{"emptyLinePlaceholder":153},[4168],{"type":34,"value":156},{"type":25,"tag":124,"props":4170,"children":4171},{"class":126,"line":187},[4172],{"type":25,"tag":124,"props":4173,"children":4174},{"style":4045},[4175],{"type":34,"value":4176},"# In a second terminal:\n",{"type":25,"tag":124,"props":4178,"children":4179},{"class":126,"line":211},[4180,4184],{"type":25,"tag":124,"props":4181,"children":4182},{"style":163},[4183],{"type":34,"value":1332},{"type":25,"tag":124,"props":4185,"children":4186},{"style":143},[4187],{"type":34,"value":4188}," ../.worktrees/project/api-tests\n",{"type":25,"tag":124,"props":4190,"children":4191},{"class":126,"line":224},[4192,4196],{"type":25,"tag":124,"props":4193,"children":4194},{"style":830},[4195],{"type":34,"value":1309},{"type":25,"tag":124,"props":4197,"children":4198},{"style":143},[4199],{"type":34,"value":4129},{"type":25,"tag":124,"props":4201,"children":4202},{"class":126,"line":244},[4203,4207,4211,4215,4219,4223],{"type":25,"tag":124,"props":4204,"children":4205},{"style":830},[4206],{"type":34,"value":1309},{"type":25,"tag":124,"props":4208,"children":4209},{"style":143},[4210],{"type":34,"value":4141},{"type":25,"tag":124,"props":4212,"children":4213},{"style":143},[4214],{"type":34,"value":4146},{"type":25,"tag":124,"props":4216,"children":4217},{"style":163},[4218],{"type":34,"value":4151},{"type":25,"tag":124,"props":4220,"children":4221},{"style":163},[4222],{"type":34,"value":4156},{"type":25,"tag":124,"props":4224,"children":4225},{"style":163},[4226],{"type":34,"value":4227}," 3102\n",{"type":25,"tag":30,"props":4229,"children":4230},{},[4231],{"type":34,"value":4232},"The commands are illustrative; use your repository's build and start commands. Keep task-specific environment variables and local data separate where those resources can interfere. Never copy production secrets into a worktree merely to make a local preview start.",{"type":25,"tag":30,"props":4234,"children":4235},{},[4236,4238,4244],{"type":34,"value":4237},"Ignored files are also local to a worktree. A ",{"type":25,"tag":14,"props":4239,"children":4241},{"className":4240},[],[4242],{"type":34,"value":4243},".env.local",{"type":34,"value":4245}," file or generated build in one directory does not appear in another. This is convenient, but it means a fresh worktree may need its own safe local configuration before it can run.",{"type":25,"tag":52,"props":4247,"children":4249},{"id":4248},"one-agent-one-worktree-one-objective",[4250],{"type":34,"value":4251},"One agent, one worktree, one objective",{"type":25,"tag":30,"props":4253,"children":4254},{},[4255],{"type":34,"value":4256},"An AI agent should be given the absolute path of its worktree and a bounded task. That makes its file operations, terminal commands, and build artifacts easy to attribute. A useful brief looks like this:",{"type":25,"tag":93,"props":4258,"children":4260},{"className":1583,"code":4259,"language":34,"meta":7,"style":7},"Work only in /workspace/.worktrees/project/search-index.\nGoal: improve the order search query without changing the API response.\nRun the repository's relevant tests and report the query plan change.\nCommit your work on feature/search-index; do not integrate or deploy it.\n",[4261],{"type":25,"tag":14,"props":4262,"children":4263},{"__ignoreMap":7},[4264,4272,4280,4288],{"type":25,"tag":124,"props":4265,"children":4266},{"class":126,"line":127},[4267],{"type":25,"tag":124,"props":4268,"children":4269},{},[4270],{"type":34,"value":4271},"Work only in /workspace/.worktrees/project/search-index.\n",{"type":25,"tag":124,"props":4273,"children":4274},{"class":126,"line":149},[4275],{"type":25,"tag":124,"props":4276,"children":4277},{},[4278],{"type":34,"value":4279},"Goal: improve the order search query without changing the API response.\n",{"type":25,"tag":124,"props":4281,"children":4282},{"class":126,"line":159},[4283],{"type":25,"tag":124,"props":4284,"children":4285},{},[4286],{"type":34,"value":4287},"Run the repository's relevant tests and report the query plan change.\n",{"type":25,"tag":124,"props":4289,"children":4290},{"class":126,"line":174},[4291],{"type":25,"tag":124,"props":4292,"children":4293},{},[4294],{"type":34,"value":4295},"Commit your work on feature/search-index; do not integrate or deploy it.\n",{"type":25,"tag":30,"props":4297,"children":4298},{},[4299],{"type":34,"value":4300},"Give the second agent a different directory and objective. Do not send two agents into the same worktree, even if their prompts mention different files. A formatter, generated schema, package update, or broad search-and-replace can cross that informal boundary.",{"type":25,"tag":30,"props":4302,"children":4303},{},[4304],{"type":34,"value":4305},"Before starting another agent, ask whether the tasks are truly independent. One agent defining a new API while another writes tests against the old API is likely to create rework. A better split may be “agree on the contract first, then implement producer and consumer in separate branches.” Parallel execution is a scheduling tool, not a substitute for shared design.",{"type":25,"tag":30,"props":4307,"children":4308},{},[4309],{"type":34,"value":4310},"It also helps to keep one owner for integration. Agents can propose commits and report their checks, but the integrator should review the combined behavior, resolve conflicts, and run the full verification after combining changes. A passing build in each branch does not prove the combination passes.",{"type":25,"tag":52,"props":4312,"children":4314},{"id":4313},"integrate-one-result-at-a-time",[4315],{"type":34,"value":4316},"Integrate one result at a time",{"type":25,"tag":30,"props":4318,"children":4319},{},[4320,4322,4327,4329,4334,4336,4341],{"type":34,"value":4321},"Assume ",{"type":25,"tag":14,"props":4323,"children":4325},{"className":4324},[],[4326],{"type":34,"value":3689},{"type":34,"value":4328}," is ready. Starting in ",{"type":25,"tag":14,"props":4330,"children":4332},{"className":4331},[],[4333],{"type":34,"value":3773},{"type":34,"value":4335},", check its diff and verification results in its worktree, then update it onto the latest ",{"type":25,"tag":14,"props":4337,"children":4339},{"className":4338},[],[4340],{"type":34,"value":497},{"type":34,"value":1795},{"type":25,"tag":93,"props":4343,"children":4345},{"className":820,"code":4344,"language":819,"meta":7,"style":7},"cd ../.worktrees/project/search-index\ngit status --short --branch\ngit diff main...HEAD\ngit rebase main\n# Run the project's required checks again after the rebase.\n",[4346],{"type":25,"tag":14,"props":4347,"children":4348},{"__ignoreMap":7},[4349,4360,4379,4396,4412],{"type":25,"tag":124,"props":4350,"children":4351},{"class":126,"line":127},[4352,4356],{"type":25,"tag":124,"props":4353,"children":4354},{"style":163},[4355],{"type":34,"value":1332},{"type":25,"tag":124,"props":4357,"children":4358},{"style":143},[4359],{"type":34,"value":4018},{"type":25,"tag":124,"props":4361,"children":4362},{"class":126,"line":149},[4363,4367,4371,4375],{"type":25,"tag":124,"props":4364,"children":4365},{"style":830},[4366],{"type":34,"value":833},{"type":25,"tag":124,"props":4368,"children":4369},{"style":143},[4370],{"type":34,"value":2471},{"type":25,"tag":124,"props":4372,"children":4373},{"style":163},[4374],{"type":34,"value":4034},{"type":25,"tag":124,"props":4376,"children":4377},{"style":163},[4378],{"type":34,"value":4039},{"type":25,"tag":124,"props":4380,"children":4381},{"class":126,"line":159},[4382,4386,4391],{"type":25,"tag":124,"props":4383,"children":4384},{"style":830},[4385],{"type":34,"value":833},{"type":25,"tag":124,"props":4387,"children":4388},{"style":143},[4389],{"type":34,"value":4390}," diff",{"type":25,"tag":124,"props":4392,"children":4393},{"style":143},[4394],{"type":34,"value":4395}," main...HEAD\n",{"type":25,"tag":124,"props":4397,"children":4398},{"class":126,"line":174},[4399,4403,4408],{"type":25,"tag":124,"props":4400,"children":4401},{"style":830},[4402],{"type":34,"value":833},{"type":25,"tag":124,"props":4404,"children":4405},{"style":143},[4406],{"type":34,"value":4407}," rebase",{"type":25,"tag":124,"props":4409,"children":4410},{"style":143},[4411],{"type":34,"value":3874},{"type":25,"tag":124,"props":4413,"children":4414},{"class":126,"line":187},[4415],{"type":25,"tag":124,"props":4416,"children":4417},{"style":4045},[4418],{"type":34,"value":4419},"# Run the project's required checks again after the rebase.\n",{"type":25,"tag":30,"props":4421,"children":4422},{},[4423,4429,4431,4436,4437,4443],{"type":25,"tag":14,"props":4424,"children":4426},{"className":4425},[],[4427],{"type":34,"value":4428},"git diff main...HEAD",{"type":34,"value":4430}," shows committed changes on the task branch relative to the common ancestor. Review uncommitted changes separately with ",{"type":25,"tag":14,"props":4432,"children":4434},{"className":4433},[],[4435],{"type":34,"value":1915},{"type":34,"value":43},{"type":25,"tag":14,"props":4438,"children":4440},{"className":4439},[],[4441],{"type":34,"value":4442},"git diff --cached",{"type":34,"value":4444},"; do not assume the three-dot diff includes them. Rebase only a branch whose history your workflow permits you to rewrite. If it is shared with other people, agree on an update strategy before rewriting it.",{"type":25,"tag":30,"props":4446,"children":4447},{},[4448],{"type":34,"value":4449},"When the branch passes, integrate according to your repository's policy. For a team using squash commits, the main checkout can do this:",{"type":25,"tag":93,"props":4451,"children":4453},{"className":820,"code":4452,"language":819,"meta":7,"style":7},"cd ../../../project\ngit merge --squash feature/search-index\ngit commit -m \"perf: speed up order search\"\n",[4454],{"type":25,"tag":14,"props":4455,"children":4456},{"__ignoreMap":7},[4457,4469,4491],{"type":25,"tag":124,"props":4458,"children":4459},{"class":126,"line":127},[4460,4464],{"type":25,"tag":124,"props":4461,"children":4462},{"style":163},[4463],{"type":34,"value":1332},{"type":25,"tag":124,"props":4465,"children":4466},{"style":143},[4467],{"type":34,"value":4468}," ../../../project\n",{"type":25,"tag":124,"props":4470,"children":4471},{"class":126,"line":149},[4472,4476,4481,4486],{"type":25,"tag":124,"props":4473,"children":4474},{"style":830},[4475],{"type":34,"value":833},{"type":25,"tag":124,"props":4477,"children":4478},{"style":143},[4479],{"type":34,"value":4480}," merge",{"type":25,"tag":124,"props":4482,"children":4483},{"style":163},[4484],{"type":34,"value":4485}," --squash",{"type":25,"tag":124,"props":4487,"children":4488},{"style":143},[4489],{"type":34,"value":4490}," feature/search-index\n",{"type":25,"tag":124,"props":4492,"children":4493},{"class":126,"line":159},[4494,4498,4502,4506],{"type":25,"tag":124,"props":4495,"children":4496},{"style":830},[4497],{"type":34,"value":833},{"type":25,"tag":124,"props":4499,"children":4500},{"style":143},[4501],{"type":34,"value":855},{"type":25,"tag":124,"props":4503,"children":4504},{"style":163},[4505],{"type":34,"value":860},{"type":25,"tag":124,"props":4507,"children":4508},{"style":143},[4509],{"type":34,"value":4510}," \"perf: speed up order search\"\n",{"type":25,"tag":30,"props":4512,"children":4513},{},[4514,4516,4521,4523,4529,4531,4536,4538,4543],{"type":34,"value":4515},"The relative ",{"type":25,"tag":14,"props":4517,"children":4519},{"className":4518},[],[4520],{"type":34,"value":1332},{"type":34,"value":4522}," above assumes the exact layout shown earlier and starts in ",{"type":25,"tag":14,"props":4524,"children":4526},{"className":4525},[],[4527],{"type":34,"value":4528},"search-index/",{"type":34,"value":4530},"; adjust it if you chose another directory. Squash creates one integration commit on ",{"type":25,"tag":14,"props":4532,"children":4534},{"className":4533},[],[4535],{"type":34,"value":497},{"type":34,"value":4537},". A normal merge or pull request can be equally appropriate if that is your team's policy. The point is to integrate one reviewed result, then update the next branch against the new ",{"type":25,"tag":14,"props":4539,"children":4541},{"className":4540},[],[4542],{"type":34,"value":497},{"type":34,"value":50},{"type":25,"tag":30,"props":4545,"children":4546},{},[4547,4549,4554,4556,4561,4563,4568],{"type":34,"value":4548},"Now ",{"type":25,"tag":14,"props":4550,"children":4552},{"className":4551},[],[4553],{"type":34,"value":3697},{"type":34,"value":4555}," still starts from the old ",{"type":25,"tag":14,"props":4557,"children":4559},{"className":4558},[],[4560],{"type":34,"value":497},{"type":34,"value":4562},". Rebase it, resolve any overlap, and rerun its checks before integrating it. Finally run the combined repository checks on ",{"type":25,"tag":14,"props":4564,"children":4566},{"className":4565},[],[4567],{"type":34,"value":497},{"type":34,"value":4569},". Conflicts are useful information: they show that the tasks were coupled at a code boundary you may want to improve next time.",{"type":25,"tag":52,"props":4571,"children":4573},{"id":4572},"clean-up-without-losing-work",[4574],{"type":34,"value":4575},"Clean up without losing work",{"type":25,"tag":30,"props":4577,"children":4578},{},[4579],{"type":34,"value":4580},"Once a task has been integrated and you have checked that nothing remains uncommitted, retire its checkout:",{"type":25,"tag":93,"props":4582,"children":4584},{"className":820,"code":4583,"language":819,"meta":7,"style":7},"git worktree list\ngit worktree remove ../.worktrees/project/search-index\n",[4585],{"type":25,"tag":14,"props":4586,"children":4587},{"__ignoreMap":7},[4588,4603],{"type":25,"tag":124,"props":4589,"children":4590},{"class":126,"line":127},[4591,4595,4599],{"type":25,"tag":124,"props":4592,"children":4593},{"style":830},[4594],{"type":34,"value":833},{"type":25,"tag":124,"props":4596,"children":4597},{"style":143},[4598],{"type":34,"value":3850},{"type":25,"tag":124,"props":4600,"children":4601},{"style":143},[4602],{"type":34,"value":3923},{"type":25,"tag":124,"props":4604,"children":4605},{"class":126,"line":149},[4606,4610,4614,4619],{"type":25,"tag":124,"props":4607,"children":4608},{"style":830},[4609],{"type":34,"value":833},{"type":25,"tag":124,"props":4611,"children":4612},{"style":143},[4613],{"type":34,"value":3850},{"type":25,"tag":124,"props":4615,"children":4616},{"style":143},[4617],{"type":34,"value":4618}," remove",{"type":25,"tag":124,"props":4620,"children":4621},{"style":143},[4622],{"type":34,"value":4018},{"type":25,"tag":30,"props":4624,"children":4625},{},[4626,4628,4633,4635,4641,4643,4649,4651,4656,4658,4664,4666,4672],{"type":34,"value":4627},"Run those commands from the main ",{"type":25,"tag":14,"props":4629,"children":4631},{"className":4630},[],[4632],{"type":34,"value":3773},{"type":34,"value":4634}," checkout. ",{"type":25,"tag":14,"props":4636,"children":4638},{"className":4637},[],[4639],{"type":34,"value":4640},"git worktree remove",{"type":34,"value":4642}," refuses to remove a dirty worktree by default. Do not use ",{"type":25,"tag":14,"props":4644,"children":4646},{"className":4645},[],[4647],{"type":34,"value":4648},"--force",{"type":34,"value":4650}," as a routine cleanup step. If you used a squash merge, Git may not consider the original branch “merged” because its commit is not literally in ",{"type":25,"tag":14,"props":4652,"children":4654},{"className":4653},[],[4655],{"type":34,"value":497},{"type":34,"value":4657},"; after verifying the squash contains the intended changes, delete that local branch with ",{"type":25,"tag":14,"props":4659,"children":4661},{"className":4660},[],[4662],{"type":34,"value":4663},"git branch -D feature/search-index",{"type":34,"value":4665},". With a normal merge, ",{"type":25,"tag":14,"props":4667,"children":4669},{"className":4668},[],[4670],{"type":34,"value":4671},"git branch -d",{"type":34,"value":4673}," can check ancestry for you.",{"type":25,"tag":30,"props":4675,"children":4676},{},[4677,4679,4684,4686,4691,4693,4699,4701,4707],{"type":34,"value":4678},"Avoid deleting a worktree directory by hand. Git retains administrative records for linked worktrees; use ",{"type":25,"tag":14,"props":4680,"children":4682},{"className":4681},[],[4683],{"type":34,"value":4640},{"type":34,"value":4685},". If someone has already deleted a directory, inspect ",{"type":25,"tag":14,"props":4687,"children":4689},{"className":4688},[],[4690],{"type":34,"value":3781},{"type":34,"value":4692}," and use ",{"type":25,"tag":14,"props":4694,"children":4696},{"className":4695},[],[4697],{"type":34,"value":4698},"git worktree prune",{"type":34,"value":4700}," only for stale entries. The ",{"type":25,"tag":991,"props":4702,"children":4704},{"href":3953,"rel":4703},[995],[4705],{"type":34,"value":4706},"worktree cleanup documentation",{"type":34,"value":4708}," explains these commands.",{"type":25,"tag":52,"props":4710,"children":4712},{"id":4711},"a-small-operating-checklist",[4713],{"type":34,"value":4714},"A small operating checklist",{"type":25,"tag":30,"props":4716,"children":4717},{},[4718],{"type":34,"value":4719},"The practical habit is simple:",{"type":25,"tag":2150,"props":4721,"children":4722},{},[4723,4728,4733,4738,4743],{"type":25,"tag":63,"props":4724,"children":4725},{},[4726],{"type":34,"value":4727},"Start each task from a known commit on its own branch and worktree.",{"type":25,"tag":63,"props":4729,"children":4730},{},[4731],{"type":34,"value":4732},"Assign one person or agent to that worktree and keep the task narrow.",{"type":25,"tag":63,"props":4734,"children":4735},{},[4736],{"type":34,"value":4737},"Isolate dependencies, ports, caches, and local data that could collide.",{"type":25,"tag":63,"props":4739,"children":4740},{},[4741],{"type":34,"value":4742},"Review the diff and checks in each branch; integrate sequentially.",{"type":25,"tag":63,"props":4744,"children":4745},{},[4746],{"type":34,"value":4747},"Verify the combined result and remove finished worktrees.",{"type":25,"tag":30,"props":4749,"children":4750},{},[4751],{"type":34,"value":4752},"Worktrees make it cheap to keep several contexts open at once. AI makes producing changes in those contexts faster. The engineering value comes from keeping each change understandable, testable, and safe to combine.",{"type":25,"tag":52,"props":4754,"children":4755},{"id":1014},[4756],{"type":34,"value":3572},{"type":25,"tag":59,"props":4758,"children":4759},{},[4760,4770,4781],{"type":25,"tag":63,"props":4761,"children":4762},{},[4763,4769],{"type":25,"tag":991,"props":4764,"children":4766},{"href":3702,"rel":4765},[995],[4767],{"type":34,"value":4768},"Git: git-worktree documentation",{"type":34,"value":50},{"type":25,"tag":63,"props":4771,"children":4772},{},[4773,4780],{"type":25,"tag":991,"props":4774,"children":4777},{"href":4775,"rel":4776},"https://git-scm.com/docs/git-rebase",[995],[4778],{"type":34,"value":4779},"Git: git-rebase documentation",{"type":34,"value":50},{"type":25,"tag":63,"props":4782,"children":4783},{},[4784,4791],{"type":25,"tag":991,"props":4785,"children":4788},{"href":4786,"rel":4787},"https://git-scm.com/docs/git-merge",[995],[4789],{"type":34,"value":4790},"Git: git-merge documentation",{"type":34,"value":50},{"type":25,"tag":1052,"props":4793,"children":4794},{},[4795],{"type":34,"value":1056},{"title":7,"searchDepth":149,"depth":149,"links":4797},[4798,4799,4800,4801,4802,4803,4804,4805],{"id":3660,"depth":149,"text":3663},{"id":3791,"depth":149,"text":3794},{"id":4071,"depth":149,"text":4074},{"id":4248,"depth":149,"text":4251},{"id":4313,"depth":149,"text":4316},{"id":4572,"depth":149,"text":4575},{"id":4711,"depth":149,"text":4714},{"id":1014,"depth":149,"text":3572},"content:posts:git-worktrees-parallel-development.md","posts/git-worktrees-parallel-development.md","posts/git-worktrees-parallel-development",[4810,4811,4812,4813,4817,4821,4825,4829,4833,4837,4841,4845,4846,4850,4854,4858,4862,4866,4870,4874,4878,4882,4886,4890,4894,4898,4901,4905,4909],{"_path":1076,"title":1077,"date":1079},{"_path":2232,"title":2233,"date":2235},{"_path":3627,"title":3628,"date":3630},{"_path":4814,"title":4815,"date":4816},"/posts/transactional-outbox","Transactional Outbox: Saving State Without Losing Events","2025-06-15T11:00:00.000Z",{"_path":4818,"title":4819,"date":4820},"/posts/api-evolution-strategy","Your API Needs an Evolution Strategy","2025-05-15T11:00:00.000Z",{"_path":4822,"title":4823,"date":4824},"/posts/n-plus-one-queries","The N+1 Query Problem: Finding and Fixing a Slow Search Endpoint","2025-04-15T11:00:00.000Z",{"_path":4826,"title":4827,"date":4828},"/posts/relational-vs-nosql-databases","Relational vs. NoSQL Databases: Why PostgreSQL Is My Default","2025-03-15T11:00:00.000Z",{"_path":4830,"title":4831,"date":4832},"/posts/hidden-cost-event-driven-architectures","The Hidden Cost of Event-Driven Architectures","2025-02-15T11:00:00.000Z",{"_path":4834,"title":4835,"date":4836},"/posts/gpt-sql-integration","Natural Language to SQL with GPT: Build a Safer Query Interface","2025-01-15T11:00:00.000Z",{"_path":4838,"title":4839,"date":4840},"/posts/java-security-best-practices","Java Security Best Practices: Preventing Vulnerabilities and Threats","2024-08-15T11:00:00.000Z",{"_path":4842,"title":4843,"date":4844},"/posts/circuit-breaking-resilience","Circuit Breaking in Distributed Systems","2024-07-15T11:00:00.000Z",{"_path":4,"title":8,"date":10},{"_path":4847,"title":4848,"date":4849},"/posts/pattern-matching-sealed-classes-java21","A Guide to Pattern Matching and Sealed Classes in Java 21","2024-05-15T11:00:00.000Z",{"_path":4851,"title":4852,"date":4853},"/posts/async-java-completable-vs-reactive","Asynchronous Programming in Java: CompletableFuture vs Reactive Streams","2024-04-15T11:00:00.000Z",{"_path":4855,"title":4856,"date":4857},"/posts/mastering-concurrency","Mastering Concurrency in Java: Threads, Executors, and Virtual Threads","2024-03-15T11:00:00.000Z",{"_path":4859,"title":4860,"date":4861},"/posts/microservices-mistakes","Common Mistakes When Developing Microservices","2024-02-15T11:00:00.000Z",{"_path":4863,"title":4864,"date":4865},"/posts/tdd-testing","Test-Driven Development (TDD) in Java with JUnit and Mockito","2024-01-15T11:00:00.000Z",{"_path":4867,"title":4868,"date":4869},"/posts/single-table-design","Single Table Design","2023-12-27T11:00:00.000Z",{"_path":4871,"title":4872,"date":4873},"/posts/java-17","Exploring Java 17's New Features","2023-10-08T11:00:00.000Z",{"_path":4875,"title":4876,"date":4877},"/posts/hexagonal-architecture","Introduction to Hexagonal Architecture","2023-10-02 18:00:00",{"_path":4879,"title":4880,"date":4881},"/posts/terraform","Unleashing the Power of Infrastructure as Code (IaC)","2023-09-24 18:00:00",{"_path":4883,"title":4884,"date":4885},"/posts/python-virtual-environments","Python Virtual Environments (venv)","2023-09-17 22:00:00",{"_path":4887,"title":4888,"date":4889},"/posts/api-first-principles","API First principles","2023-09-01 22:15:18",{"_path":4891,"title":4892,"date":4893},"/posts/how-to-configure-ssh-key-based-authentication","How To Configure SSH Key-Based Authentication","2021-02-21 03:46:12",{"_path":4895,"title":4896,"date":4897},"/posts/algorithms-binary-search-tree","Algorithms - Binary Search Tree in kotlin","2020-12-06T11:00:00.000Z",{"_path":4899,"title":4900,"date":4897},"/posts/how-to-create-a-twitter-bot","How to create a Twitter bot",{"_path":4902,"title":4903,"date":4904},"/posts/manage-aws-infrastructure-with-terraform","Bootstrap complete Java application infrastructure in AWS with Terraform","2020-12-02T11:00:00.000Z",{"_path":4906,"title":4907,"date":4908},"/posts/algorithms-working-with-trees","Algorithms - Binary tree traversals in kotlin","2020-11-24T11:00:00.000Z",{"_path":4910,"title":4911,"date":4912},"/posts/playing-with-ocr-libraries","Playing with OCR libraries","2020-11-22T11:00:00.000Z",1791264529731]