diff --git a/bun.lock b/bun.lock index 72b46b8..213b7a6 100644 --- a/bun.lock +++ b/bun.lock @@ -35,7 +35,7 @@ }, "packages/core": { "name": "@google/jules-sdk", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "yaml": "^2.8.2", "zod": "^3.25.76", @@ -65,6 +65,19 @@ "typescript": "^5.0.0", }, }, + "packages/core/examples/gitpatch-review": { + "name": "jules-gitpatch-review-example", + "version": "1.0.0", + "dependencies": { + "@google/jules-sdk": "workspace:*", + "citty": "^0.1.6", + "zod": "^3.23.0", + }, + "devDependencies": { + "bun-types": "^1.1.8", + "execa": "^9.6.1", + }, + }, "packages/core/examples/webhook": { "name": "webhook", "version": "1.0.0", @@ -79,7 +92,7 @@ }, "packages/fleet": { "name": "@google/jules-fleet", - "version": "0.0.1-experimental.31", + "version": "0.0.1-experimental.32", "bin": { "jules-fleet": "dist/cli/index.mjs", }, @@ -105,7 +118,7 @@ }, "packages/mcp": { "name": "@google/jules-mcp", - "version": "0.1.0", + "version": "0.2.0", "bin": { "jules-mcp": "./dist/cli.mjs", }, @@ -439,6 +452,8 @@ "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.1.5", "", { "dependencies": { "@rushstack/terminal": "0.19.5", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-YmrFTFUdHXblYSa+Xc9OO9FsL/XFcckZy0ycQ6q7VSBsVs5P0uD9vcges5Q9vctGlVdu27w+Ct6IuJ458V0cTQ=="], + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], @@ -453,6 +468,8 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], "@types/aws-lambda": ["@types/aws-lambda@8.10.160", "", {}, "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA=="], @@ -657,6 +674,8 @@ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -675,6 +694,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "find-up": ["find-up@8.0.0", "", { "dependencies": { "locate-path": "^8.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww=="], @@ -695,6 +716,8 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], @@ -731,6 +754,8 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], @@ -747,10 +772,16 @@ "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], @@ -773,6 +804,8 @@ "jules-github-actions-example": ["jules-github-actions-example@workspace:packages/core/examples/github-actions"], + "jules-gitpatch-review-example": ["jules-gitpatch-review-example@workspace:packages/core/examples/gitpatch-review"], + "jules-sdk-example": ["jules-sdk-example@workspace:examples/simple"], "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], @@ -837,6 +870,8 @@ "niftty": ["niftty@0.1.3", "", { "dependencies": { "chalk": "^5.6.2", "shiki": "^3.12.2", "string-length": "^6.0.0", "tinycolor2": "^1.6.0" } }, "sha512-wy8Kysxzh/R3hBq0BDlBbnzxDU/b/3PUtWfWVm1KwOestaVF3423U4iHD7TthPMF/RTHPXGenxh6YNERaD8M2g=="], + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -857,6 +892,8 @@ "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], @@ -887,6 +924,8 @@ "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -979,6 +1018,8 @@ "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], @@ -1125,6 +1166,8 @@ "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -1145,8 +1188,14 @@ "@google/jules-fleet/@google/jules-merge": ["@google/jules-merge@0.0.2", "", { "dependencies": { "@google/jules-sdk": "^0.1.0", "@octokit/auth-app": "^8.2.0", "@octokit/rest": "^21.0.0", "citty": "^0.1.6", "zod": "^3.25.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.1" }, "optionalPeers": ["@modelcontextprotocol/sdk"], "bin": { "jules-merge": "dist/cli/index.mjs" } }, "sha512-VPpbdBt48AbmFByg5RGztv2sQPPJ5fFJARXTvX41lY/b9M+qdyZPp19+ay3qfxEHgj1EvnRMwf/HFOrMMXZ7vQ=="], + "@google/jules-fleet/@google/jules-sdk": ["@google/jules-sdk@0.1.0", "", { "dependencies": { "yaml": "^2.8.2", "zod": "^3.25.76" } }, "sha512-DBVhFOsLfWaVtO0miEeX+zQoazB55EYmK5/0VJSX/YHdeheZqNuPlKRV1vrnd9uSQAtU6ACRicQssMbnvJM6ng=="], + "@google/jules-fleet/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "@google/jules-mcp/@google/jules-sdk": ["@google/jules-sdk@0.1.0", "", { "dependencies": { "yaml": "^2.8.2", "zod": "^3.25.76" } }, "sha512-DBVhFOsLfWaVtO0miEeX+zQoazB55EYmK5/0VJSX/YHdeheZqNuPlKRV1vrnd9uSQAtU6ACRicQssMbnvJM6ng=="], + + "@google/jules-merge/@google/jules-sdk": ["@google/jules-sdk@0.1.0", "", { "dependencies": { "yaml": "^2.8.2", "zod": "^3.25.76" } }, "sha512-DBVhFOsLfWaVtO0miEeX+zQoazB55EYmK5/0VJSX/YHdeheZqNuPlKRV1vrnd9uSQAtU6ACRicQssMbnvJM6ng=="], + "@microsoft/api-extractor/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], "@microsoft/api-extractor/typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], @@ -1217,6 +1266,8 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "octokit/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="], "octokit/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], @@ -1251,6 +1302,8 @@ "@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@google/jules-fleet/@google/jules-merge/@google/jules-sdk": ["@google/jules-sdk@workspace:packages/core"], + "@google/jules-fleet/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "@google/jules-fleet/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], diff --git a/packages/core/README.md b/packages/core/README.md index c3d32e7..46c9b47 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -11,6 +11,7 @@ Orchestrate complex, long-running coding tasks to an ephemeral cloud environment - [Agent Workflow](./examples/agent/README.md) - [Webhook Integration](./examples/webhook/README.md) - [GitHub Actions](./examples/github-actions/README.md) +- [Gitpatch Review](./examples/gitpatch-review/README.md) ## Send work to a Cloud based session diff --git a/packages/core/examples/gitpatch-review/README.md b/packages/core/examples/gitpatch-review/README.md new file mode 100644 index 0000000..4364448 --- /dev/null +++ b/packages/core/examples/gitpatch-review/README.md @@ -0,0 +1,91 @@ +# GitPatch Review Example (CLI) + +This example demonstrates how to use Jules' session GitPatch to review and analyze code generated by a Jules coding agent against the context of a GitHub repository. + +It is structured as a CLI using [citty](https://github.com/unjs/citty) and follows the **Typed Service Contract** (Spec & Handler) pattern to clearly separate argument parsing, schema validation, and business logic execution. + +## Overview + +The CLI orchestrates two sequential sessions using the Jules SDK: + +1. **Generation Session**: Takes a user-provided prompt (e.g., instructing an agent to write poor code), producing a Git patch with changes. +2. **Review Session**: Takes the resulting Git patch generated from the first session and creates a *new* session, passing the patch string as context to instruct the agent to review and analyze the code against standard coding practices. + +This demonstrates common workflows such as: +- Structuring Agent CLIs for deterministic and predictable behavior. +- Using `session.stream()` to output real-time CLI feedback. +- Extracting raw `gitPatch` strings or parsed `ChangeSet` structures directly from the `session.snapshot()`. + +## Prerequisites + +- Node.js or Bun installed. +- A Jules API Key. Set it using: + ```bash + export JULES_API_KEY= + ``` + +## Running the Example + +You can run this example using `bun` (recommended) or via Node/TSX. + +### Basic Usage + +```bash +bun run index.ts -r "owner/repo" -b "main" -p "Write a badly formatted hello world function in Python" +``` + +### CLI Arguments + +| Argument | Alias | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| `repository` | `-r` | **Yes** | | The target GitHub repository (e.g. `davideast/dataprompt`) | +| `prompt` | `-p` | **Yes** | | The prompt to generate the code change | +| `baseBranch` | `-b` | No | `main` | The base branch of the repository | +| `--json` | | No | `false` | Output the final result or error as structured JSON. Useful for agents or piping outputs. | + +### Example Output + +```text +Starting code generation session for davideast/dataprompt... +Code Generation Session ID: jules:session:12345 +[Code Gen] Planning changes +[Code Gen] Generated plan with 1 steps. +[Code Gen] Editing code +[Code Gen Agent]: I've created the requested bad code. + +--- Extracted Patch Content --- +--- a/bad_code.py ++++ b/bad_code.py +@@ -0,0 +1,2 @@ ++def hw(): ++ print("hello") +------------------------------- + +Starting review session... +Review Session ID: jules:session:67890 +[Review] Analyzing changes +[Review Agent]: This code lacks typing, has bad indentation, and uses a poor function name. + +======================================= + REVIEW COMPLETE +======================================= + +This code lacks typing, has bad indentation, and uses a poor function name. I recommend renaming it to 'hello_world' and fixing the indentation. +``` + +### JSON Output + +When run with the `--json` flag, all stdout/stderr progress logs are suppressed or piped differently, and the final output is a structured JSON response (following the Result pattern). + +```bash +bun run index.ts -r "owner/repo" -p "Write bad code" --json +``` + +```json +{ + "codeGenSessionId": "jules:session:12345", + "reviewSessionId": "jules:session:67890", + "gitPatchStr": "...", + "reviewMessage": "This code lacks typing..." +} +``` diff --git a/packages/core/examples/gitpatch-review/e2e-test.ts b/packages/core/examples/gitpatch-review/e2e-test.ts new file mode 100644 index 0000000..55647f7 --- /dev/null +++ b/packages/core/examples/gitpatch-review/e2e-test.ts @@ -0,0 +1,104 @@ +import { execa } from 'execa'; +import { z } from 'zod'; +import { ReviewSuccess } from './src/spec.js'; + +/** + * End-to-End Test for the GitPatch Review CLI + * + * This script invokes the CLI as a separate process to verify that: + * 1. The CLI can authenticate with the Jules API (using JULES_API_KEY). + * 2. It successfully starts and streams two consecutive sessions. + * 3. When the `--json` flag is provided, the final `stdout` is exclusively + * a valid JSON payload matching the `ReviewSuccess` schema. + * 4. Progress logs are successfully piped to `stderr` and don't corrupt the JSON. + */ +async function runE2E() { + const apiKey = process.env.JULES_API_KEY; + + if (!apiKey) { + console.error('āŒ E2E Test Failed: JULES_API_KEY environment variable is missing.'); + process.exit(1); + } + + console.log('šŸš€ Starting GitPatch Review CLI E2E Test...\n'); + + try { + // We use execa to easily spawn the CLI, capture stdout/stderr separately, + // and provide a timeout. The target repo here is arbitrary but must be valid. + const subprocess = execa('bun', [ + 'run', + 'index.ts', + '-r', + 'davideast/dataprompt', + '-b', + 'main', + '-p', + 'Write a Python function that adds two numbers, but name it very badly, use no types, and mess up the indentation.', + '--json', + ], { + env: { JULES_API_KEY: apiKey }, + timeout: 900000, // 15 minute timeout for two LLM sessions + cwd: import.meta.dir // Ensure we run relative to this e2e script + }); + + // Pipe stderr to our current console so we can watch the progress logs live + if (subprocess.stderr) { + subprocess.stderr.pipe(process.stderr); + } + + const { stdout, exitCode } = await subprocess; + + console.log('\n\nāœ… CLI process exited with code:', exitCode); + + if (exitCode !== 0) { + console.error('āŒ E2E Test Failed: CLI exited with a non-zero status code.'); + process.exit(1); + } + + console.log('--- Raw CLI JSON Output (stdout) ---'); + console.log(stdout); + console.log('------------------------------------\n'); + + // Parse and validate the stdout output against our expected Zod schema + const parsedJson = JSON.parse(stdout); + const validationResult = ReviewSuccess.safeParse({ success: true, data: parsedJson }); + + if (!validationResult.success) { + console.error('āŒ E2E Test Failed: The JSON output did not match the expected schema.'); + console.error(validationResult.error.format()); + process.exit(1); + } + + const { data } = validationResult.data; + + console.log('āœ… Validation Passed: Output is valid JSON.'); + console.log(`- Code Gen Session ID: ${data.codeGenSessionId}`); + console.log(`- Review Session ID: ${data.reviewSessionId}`); + + if (data.gitPatchStr && data.gitPatchStr.length > 0) { + console.log(`- Git Patch Extracted: YES (${data.gitPatchStr.split('\\n').length} lines)`); + } else { + console.error('āŒ E2E Test Failed: No Git Patch string was found in the output.'); + process.exit(1); + } + + if (data.reviewMessage && data.reviewMessage.length > 0) { + console.log(`- Review Message Generated: YES`); + } else { + console.error('āŒ E2E Test Failed: No Review Message was found in the output.'); + process.exit(1); + } + + console.log('\nšŸŽ‰ E2E Test Completed Successfully!'); + + } catch (error: any) { + console.error('\nāŒ E2E Test Failed with an exception:'); + if (error.shortMessage) { + console.error(error.shortMessage); // execa formatting + } + console.error(error.message); + process.exit(1); + } +} + +runE2E(); diff --git a/packages/core/examples/gitpatch-review/index.ts b/packages/core/examples/gitpatch-review/index.ts new file mode 100644 index 0000000..72a6645 --- /dev/null +++ b/packages/core/examples/gitpatch-review/index.ts @@ -0,0 +1,82 @@ +import { defineCommand, runMain } from 'citty'; +import { ReviewHandler } from './src/handler.js'; +import { ReviewInputSchema } from './src/spec.js'; + +const main = defineCommand({ + meta: { + name: 'jules-gitpatch-review', + version: '1.0.0', + description: 'Use Jules to review generated code patches against GitHub repo context.', + }, + args: { + repository: { + type: 'string', + description: 'The target GitHub repository (e.g. owner/repo)', + required: true, + alias: 'r', + }, + baseBranch: { + type: 'string', + description: 'The base branch of the repository', + default: 'main', + alias: 'b', + }, + prompt: { + type: 'string', + description: 'The prompt to generate the code change', + required: true, + alias: 'p', + }, + json: { + type: 'boolean', + description: 'Output the final result as JSON', + default: false, + }, + }, + async run({ args }) { + // 1. Validate Input (Parse, don't validate) + const inputResult = ReviewInputSchema.safeParse({ + repository: args.repository, + baseBranch: args.baseBranch, + prompt: args.prompt, + json: args.json, + }); + + if (!inputResult.success) { + console.error('Invalid arguments provided:'); + console.error(inputResult.error.format()); + process.exit(1); + } + + // 2. Instantiate the Handler + const handler = new ReviewHandler(); + + // 3. Execute Business Logic + const result = await handler.execute(inputResult.data); + + // 4. Handle Results Deterministically + if (!result.success) { + if (args.json) { + console.error(JSON.stringify(result.error, null, 2)); + } else { + console.error(`\n[ERROR] ${result.error.code}: ${result.error.message}`); + if (result.error.suggestion) { + console.error(`Suggestion: ${result.error.suggestion}`); + } + } + process.exit(1); + } + + // 5. Output Success State + if (args.json) { + console.log(JSON.stringify(result.data, null, 2)); + } else { + console.log('\n======================================='); + console.log(' REVIEW COMPLETE'); + console.log('=======================================\n'); + console.log(result.data.reviewMessage); + } + }, +}); + +runMain(main); diff --git a/packages/core/examples/gitpatch-review/package.json b/packages/core/examples/gitpatch-review/package.json new file mode 100644 index 0000000..b76a135 --- /dev/null +++ b/packages/core/examples/gitpatch-review/package.json @@ -0,0 +1,20 @@ +{ + "name": "jules-gitpatch-review-example", + "version": "1.0.0", + "description": "Example demonstrating how to use Jules' session GitPatch to review and analyze generated code", + "main": "index.ts", + "type": "module", + "scripts": { + "start": "bun run index.ts", + "test:e2e": "bun run e2e-test.ts" + }, + "dependencies": { + "@google/jules-sdk": "workspace:*", + "citty": "^0.1.6", + "zod": "^3.23.0" + }, + "devDependencies": { + "bun-types": "^1.1.8", + "execa": "^9.6.1" + } +} diff --git a/packages/core/examples/gitpatch-review/src/handler.ts b/packages/core/examples/gitpatch-review/src/handler.ts new file mode 100644 index 0000000..afaf84e --- /dev/null +++ b/packages/core/examples/gitpatch-review/src/handler.ts @@ -0,0 +1,211 @@ +import { jules, Session, Activity } from '@google/jules-sdk'; +import { ReviewSpec, ReviewInput, ReviewResult } from './spec.js'; + +export class ReviewHandler implements ReviewSpec { + async execute(input: ReviewInput): Promise { + try { + if (!process.env.JULES_API_KEY) { + return { + success: false, + error: { + code: 'UNAUTHORIZED', + message: 'JULES_API_KEY environment variable is not set.', + suggestion: 'Export JULES_API_KEY="your-api-key" before running the CLI.', + recoverable: true, + }, + }; + } + + this.log(`Starting code generation session for ${input.repository}...`, input.json); + + const source = { github: input.repository, baseBranch: input.baseBranch }; + + // 1. Generate bad code + const codeGenSession = await jules.session({ + prompt: input.prompt, + source, + }); + + this.log(`Code Generation Session ID: ${codeGenSession.id}`, input.json); + + // If the session isn't immediately finished, stream it until it is + const genInfo = await codeGenSession.info(); + if (genInfo.state !== 'completed' && genInfo.state !== 'failed') { + try { + await this.streamSessionActivities(codeGenSession, 'Code Gen', input.json); + } catch(e) { + // Occasionally activities return 404 momentarily immediately after session creation + // in some environments. Ignore and fall through to wait on result(). + } + } + + const genOutcome = await codeGenSession.result(); + + if (genOutcome.state === 'failed') { + return { + success: false, + error: { + code: 'SESSION_FAILED', + message: `Code generation session failed: ${codeGenSession.id}`, + recoverable: false, + }, + }; + } + + // 2. Extract GitPatch + const snapshot = codeGenSession.snapshot(); + let changeSet; + + // Type guarding the `snapshot.changeSet` function because the underlying SDK + // abstraction may change or omit it. + if (typeof snapshot.changeSet === 'function') { + changeSet = snapshot.changeSet(); + } + + let gitPatchStr = ''; + + if (changeSet && changeSet.gitPatch && changeSet.gitPatch.unidiffPatch) { + // Prefer the raw unidiff patch from the GitPatch object if available + gitPatchStr = changeSet.gitPatch.unidiffPatch; + } else if (changeSet && typeof changeSet.parsed === 'function') { + // Fallback to rebuilding it from parsed diff + const parsed = changeSet.parsed(); + for(const file of parsed.files) { + gitPatchStr += `--- a/${file.path}\n+++ b/${file.path}\n`; + for(const chunk of file.chunks) { + gitPatchStr += `@@ -${chunk.oldStart},${chunk.oldLines} +${chunk.newStart},${chunk.newLines} @@\n`; + for(const change of chunk.changes) { + if(change.type === 'add') gitPatchStr += `+${change.content}\n`; + if(change.type === 'del') gitPatchStr += `-${change.content}\n`; + if(change.type === 'normal') gitPatchStr += ` ${change.content}\n`; + } + } + } + } else { + // Fallback to checking generated files if changeset isn't structured nicely + const files = genOutcome.generatedFiles(); + for (const [filename, content] of files.entries()) { + gitPatchStr += `File: ${filename}\n${content.content}\n`; + } + } + + if (!gitPatchStr || gitPatchStr.trim() === '') { + return { + success: false, + error: { + code: 'NO_CHANGES_GENERATED', + message: 'The agent did not generate any code changes.', + recoverable: false, + }, + }; + } + + this.log('\n--- Extracted Patch Content ---', input.json); + this.log(gitPatchStr, input.json); + this.log('-------------------------------\n', input.json); + + this.log('Starting review session...', input.json); + + // 3. Review the code + const reviewSession = await jules.session({ + prompt: `Review the following code change patch and determine if it adheres to clean coding standards. +Provide a short summary of the issues found and how they should be fixed. + +## Git Patch +\`\`\`diff +${gitPatchStr} +\`\`\` +`, + source, + }); + + this.log(`Review Session ID: ${reviewSession.id}`, input.json); + + let reviewMessage = ''; + + // Stream to get live updates and block until finished + const revInfo = await reviewSession.info(); + if (revInfo.state !== 'completed' && revInfo.state !== 'failed') { + try { + for await (const activity of reviewSession.stream()) { + this.logActivity(activity, 'Review', input.json); + if (activity.type === 'agentMessaged') { + reviewMessage = activity.message; + } + } + } catch(e) { + // Ignore stream fetch errors + } + } + + const reviewOutcome = await reviewSession.result(); + + if (reviewOutcome.state === 'failed') { + return { + success: false, + error: { + code: 'SESSION_FAILED', + message: `Review session failed: ${reviewSession.id}`, + recoverable: false, + }, + }; + } + + if (!reviewMessage) { + // Check files if no message + const files = reviewOutcome.generatedFiles(); + if (files.size > 0) { + for (const [filename, content] of files.entries()) { + reviewMessage += `\nFile: ${filename}\n${content.content}\n`; + } + } else { + reviewMessage = "The review completed but the agent provided no feedback."; + } + } + + return { + success: true, + data: { + codeGenSessionId: codeGenSession.id, + reviewSessionId: reviewSession.id, + gitPatchStr, + reviewMessage, + }, + }; + + } catch (error) { + return { + success: false, + error: { + code: 'UNKNOWN_ERROR', + message: error instanceof Error ? error.message : String(error), + recoverable: false, + }, + }; + } + } + + private async streamSessionActivities(session: Session, prefix: string, isJson: boolean) { + for await (const activity of session.stream()) { + this.logActivity(activity, prefix, isJson); + } + } + + private logActivity(activity: Activity, prefix: string, isJson: boolean) { + if (activity.type === 'progressUpdated') { + this.log(`[${prefix}] ${activity.title}`, isJson); + } else if (activity.type === 'agentMessaged') { + this.log(`[${prefix} Agent]: ${activity.message}`, isJson); + } else if (activity.type === 'planGenerated') { + this.log(`[${prefix}] Generated plan with ${activity.plan.steps.length} steps.`, isJson); + } + } + + private log(message: string, isJson: boolean) { + if (isJson) { + console.error(message); + } else { + console.log(message); + } + } +} diff --git a/packages/core/examples/gitpatch-review/src/spec.ts b/packages/core/examples/gitpatch-review/src/spec.ts new file mode 100644 index 0000000..0878041 --- /dev/null +++ b/packages/core/examples/gitpatch-review/src/spec.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; + +// 1. INPUT (The Command) - "Parse, don't validate" +export const ReviewInputSchema = z.object({ + repository: z.string().min(1, 'Repository must be provided (e.g., owner/repo)'), + baseBranch: z.string().min(1, 'Base branch must be provided (e.g., main)'), + prompt: z.string().min(1, 'A prompt to generate code must be provided'), + json: z.boolean().default(false), +}); + +export type ReviewInput = z.infer; + +// 2. ERROR CODES (Exhaustive) +export const ReviewErrorCode = z.enum([ + 'SESSION_FAILED', + 'NO_CHANGES_GENERATED', + 'UNKNOWN_ERROR', + 'UNAUTHORIZED', +]); + +// 3. RESULT (The Monad) +export const ReviewSuccess = z.object({ + success: z.literal(true), + data: z.object({ + reviewMessage: z.string(), + codeGenSessionId: z.string(), + reviewSessionId: z.string(), + gitPatchStr: z.string(), + }), +}); + +export const ReviewFailure = z.object({ + success: z.literal(false), + error: z.object({ + code: ReviewErrorCode, + message: z.string(), + suggestion: z.string().optional(), + recoverable: z.boolean(), + }), +}); + +export type ReviewResult = z.infer | z.infer; + +// 4. INTERFACE (The Capability) +export interface ReviewSpec { + execute(input: ReviewInput): Promise; +}