diff --git a/.changeset/fix-standalone-adapter-conflicts-and-vite-plus-detection.md b/.changeset/fix-standalone-adapter-conflicts-and-vite-plus-detection.md new file mode 100644 index 00000000..457b3ec1 --- /dev/null +++ b/.changeset/fix-standalone-adapter-conflicts-and-vite-plus-detection.md @@ -0,0 +1,5 @@ +--- +"@area44/workflows": patch +--- + +Fix provider capability isolation in multi-language monorepos and support wrapped Vite+ command detection. diff --git a/dist/autofix.mjs b/dist/autofix.mjs index 85bad52b..8418d70a 100644 --- a/dist/autofix.mjs +++ b/dist/autofix.mjs @@ -303,11 +303,11 @@ const Vn=[`uv`,`pip`],Hn=[`.git`,`.venv`,`venv`,`env`,`.env`,`virtualenv`,`.virt `?` `:` `,i++;return i=t+1&&e[a]===`\\`;)n++,a--;if(n%2==0)return r+=` `,[r,i+1]}r+=` `,i++}return[r,i]}function dr(e,t,n){let r=` `,i=t+1;for(;i!Un(e))}function mr(e,t){if(!Un(e[`build-system`]))return!1;if(typeof t==`string`){let e=fr(t);return/(?:^|\n)\s*\[\s*build-system\s*\]\s*(?:\n|$)/.test(e)}let n=Object.entries(e[`build-system`]);return n.length===0||n.some(([,e])=>!Un(e))}function hr(e,t){if(pr(e,t)||mr(e,t))return!0;if(Un(e.tool)&&Un(e.tool.uv)){let t=e.tool.uv;return Yn(t.workspace)||Array.isArray(t[`dev-dependencies`])}return!1}function gr(e){return Un(e.tool)&&Un(e.tool.uv)}function _r(e,t){return pr(e,t)||mr(e,t)}function vr(e,t){return p.relative(e,t)||`.`}function yr(e,t){if(!t)throw Error(`Invalid Python project configuration: 'uv.lock' was detected without a valid 'pyproject.toml' project or workspace configuration ('[project]', '[build-system]', or '[tool.uv.workspace]'). 'uv sync' requires a valid 'pyproject.toml' file.`);return{name:`uv`,lockfile:`uv.lock`,configFile:`pyproject.toml`,installCommands:[{executable:`uv`,args:[`sync`]}],lockCommand:`uv lock`,syncCommand:`uv sync`,execPrefix:`uv run`}}function br(e,t){if(e.hasRequirementsTxt&&e.requirementsDir){let t=vr(e.cwd,p.join(e.requirementsDir,`requirements.txt`));return{name:`uv`,configFile:e.pyprojectDir?`pyproject.toml`:`uv.toml`,installCommands:[{executable:`uv`,args:[`pip`,`install`,`-r`,t]}],execPrefix:`uv run`}}return t?{name:`uv`,configFile:e.pyprojectDir?`pyproject.toml`:`uv.toml`,installCommands:[{executable:`uv`,args:[`sync`]}],lockCommand:`uv lock`,syncCommand:`uv sync`,execPrefix:`uv run`}:null}function xr(e){let t=e.pyprojectToml?.success===!0&&hr(e.pyprojectToml.data,e.pyprojectToml.rawContent),n=e.workspaceRootToml?.success===!0&&hr(e.workspaceRootToml.data,e.workspaceRootToml.rawContent),r=t||n,i=e.uvToml?.success===!0,a=e.pyprojectToml?.success===!0&&gr(e.pyprojectToml.data)||e.workspaceRootToml?.success===!0&&gr(e.workspaceRootToml.data);return e.hasUvLock?yr(e,r):a||i?br(e,r):null}function Sr(e){let t=e.pyprojectToml?.success===!0&&_r(e.pyprojectToml.data,e.pyprojectToml.rawContent);if(!(e.hasRequirementsTxt||e.hasSetupPy||e.hasSetupCfg||t))return null;let n;t?n=`pyproject.toml`:e.hasSetupPy?n=`setup.py`:e.hasSetupCfg&&(n=`setup.cfg`);let r=[],i,a=e.hasSetupPy||e.hasSetupCfg||t,o=e.requirementsDir?vr(e.cwd,p.join(e.requirementsDir,`requirements.txt`)):`requirements.txt`,s=vr(e.cwd,e.projectDir);return e.hasRequirementsTxt&&a?(r=[{executable:`pip`,args:[`install`,`-r`,o]},{executable:`pip`,args:[`install`,`-e`,s]}],i=`requirements.txt`):e.hasRequirementsTxt?(r=[{executable:`pip`,args:[`install`,`-r`,o]}],i=`requirements.txt`):a&&(r=[{executable:`pip`,args:[`install`,`-e`,s]}]),{name:`pip`,lockfile:i,configFile:n,installCommands:r,execPrefix:``}}function Cr(e=process.cwd(),t){if(t?.hasSource??or(e))return!0;let n=t?.ctx??ir(e);if(n.projectDir===n.cwd&&tr(n.cwd)&&(n.pyprojectToml?.success===!0&&hr(n.pyprojectToml.data,n.pyprojectToml.rawContent)||n.hasUvLock||n.hasRequirementsTxt||n.hasSetupPy||n.hasSetupCfg))return!0;if(n.workspaceRootDir&&n.workspaceRootToml?.success===!0){let t=Zn(n.workspaceRootDir);if(t&&Xn(n.workspaceRootDir,p.resolve(e),t.config))return!0}return!1}function wr(e,t){t!=null&&(e.pyprojectToml=t);for(let t of Vn)if(t===`uv`){let t=xr(e);if(t)return t}else if(t===`pip`){let t=Sr(e);if(t)return t}return{name:`pip`,installCommands:[],execPrefix:``,isStandalone:!0}}function Tr(e=process.cwd(),t,n){let r=n?.ctx??ir(e);return n?.isProject??Cr(e,{ctx:r,hasSource:n?.hasSource})?wr(r,t):null}function Er(e){if(!Array.isArray(e))throw Error(`Invalid Python environment: "pmInfo.installCommands" must be an array`);for(let t of e){if(!t||typeof t!=`object`)throw Error(`Invalid Python environment: malformed "installCommands" item`);let e=t;if(typeof e.executable!=`string`||!e.executable.trim()||!Array.isArray(e.args)||!e.args.every(e=>typeof e==`string`))throw Error(`Invalid Python environment: malformed "installCommands" item`)}}function Dr(e,t=`Invalid Python environment: "pmInfo" must be an object`){if(!e||typeof e!=`object`||Array.isArray(e))throw Error(t);let n=e;if(n.name!==`uv`&&n.name!==`pip`)throw Error(`Invalid Python environment: pmInfo name must be "uv" or "pip"`);return Er(n.installCommands),n}function Or(e){if(!process.argv[1])return!1;try{return l(e)===f.realpathSync(process.argv[1])}catch{return!1}}async function kr(e,t,n=process.platform){let r=p.join(e,`node_modules`,`.bin`),i=/* @__PURE__ */ new Set([`.cmd`,`.bat`,`.exe`]);if(n===`win32`){let e=p.extname(t).toLowerCase();if(e){if(!i.has(e))return;let n=p.join(r,t);if(f.existsSync(n))return n}else for(let e of[`.cmd`,`.bat`,`.exe`,``]){let n=p.join(r,`${t}${e}`);if(f.existsSync(n))return n}}else{let e=p.join(r,t);if(f.existsSync(e))return e}let a=await Vt(t,!1);if(a){if(n===`win32`){let e=p.extname(a).toLowerCase();if(e&&!i.has(e))return}return a}}var Ar=class{platform;constructor(e=process.platform){this.platform=e}async resolveTaskExecutable(e){if(e.toolId===`npm-scripts`)return e.executable;if(e.toolId===`ruff`){if(!await Vt(`ruff`,!1))throw Error(`Required Python binary 'ruff' is not available in PATH.`);return`ruff`}let t=await kr(e.cwd,e.executable,this.platform);if(!t){let t=e.toolName||e.toolId,n=e.requiredPackage||e.executable;throw Error(`Configured tool "${t}" could not run: executable "${e.executable}" is not available in node_modules/.bin or PATH. Please install "${n}" in project dependencies.`)}return t}async executeTasks(e){let t=[];for(let n of e){let e=`${n.executable} ${n.args.join(` `)}`.trim();try{let r=await this.resolveTaskExecutable(n);en(`Executing: ${e}`);let i=n.env?Object.fromEntries(Object.entries({...process.env,...n.env}).filter(e=>e[1]!==void 0)):void 0,a=await Yt(r,n.args,{cwd:n.cwd,env:i,ignoreReturnCode:!0});if(a!==0){let r=n.toolId===`ruff`?`Ruff command "${e}" failed. Check Python and Ruff configuration.`:n.toolId===`npm-scripts`?`Script "${n.args[1]||e}" failed while executing "${e}".`:`Task "${n.id}" (${e}) failed with exit code ${a}.`;t.push({task:n,exitCode:a,error:Error(r)})}else t.push({task:n,exitCode:a})}catch(e){let r=e instanceof Error?e:Error(String(e));t.push({task:n,exitCode:1,error:r})}}return t}};function jr(e,t){return t.has(`autofix`)?!0:e===`autofix`?t.has(`format`)&&t.has(`lint`)&&t.has(`assist`):t.has(e)}function Mr(e,t){let n=[],r=[];for(let i of e)jr(i.capability,t)?r.push(i):n.push(i);return{kept:n,skipped:r}}async function Nr(e,t,n,r,i,a,o){for(let s of e){let e=s.adapter,c=await e.plan(n,r);if(c.length===0)continue;let{kept:l,skipped:u}=Mr(c,t);if(l.length===0)a.push({id:e.id,name:e.name,reason:`all capabilities already covered by higher priority tool`});else{i.push({id:e.id,name:e.name,reason:s.reason});for(let e of l)if(t.add(e.capability),e.capabilities)for(let n of e.capabilities)t.add(n);if(o.push(...l),u.length>0){let t=Array.from(new Set(u.map(e=>e.capability))).join(`, `);a.push({id:e.id,name:e.name,reason:`partially skipped (${t} covered by higher priority tool)`})}}}}async function Pr(e,t,n,r,i){for(let a of e.getStandaloneAdapters()){let e=await a.detect(t);if(e.detected){r.push({id:a.id,name:a.name,reason:e.reason});let o=await a.plan(t,n);i.push(...o)}}}function Fr(e){let t=[],n=/* @__PURE__ */ new Set;for(let r of e)n.has(r.id)||(n.add(r.id),t.push(r));return t}async function Ir(e,t,n=`fix`){let r=t.hasPythonSource??or(t.cwd),i=t.jsEnv??fn(t.cwd),a={...t,jsEnv:i,hasPythonSource:r},o=[],s=[],c=[],l=e.getProviders(),u=/* @__PURE__ */ new Map;for(let e of l){if(!await e.detect(a))continue;let t=/* @__PURE__ */ new Set;u.set(e.id,t);let r=e.getAdapters(a),i=[];for(let e of r){let t=await e.detect(a);t.detected?i.push({adapter:e,reason:t.reason}):t.reason&&!t.reason.startsWith(`no `)&&s.push({id:e.id,name:e.name,reason:t.reason})}await Nr(i,t,a,n,o,s,c)}await Pr(e,a,n,o,c);let d=Fr(c);return{tasks:d,selectedTools:o,skippedTools:s,summary:Lr(e,a,o,s,d)}}function Lr(e,t,n,r,i){let a=[];a.push(`Detected environments:`);let o=t.hasPythonSource??!1,s=t.jsEnv?.hasPackageJson??!1;if(a.push(` Python: ${o}`),a.push(` JavaScript: ${s}`),o&&(a.push(``),a.push(`Python toolchain:`),n.filter(e=>e.id===`ruff`).length>0?a.push(` Ruff: default tool`):a.push(` None`)),s){a.push(``),a.push(`JavaScript toolchain:`);let e=n.filter(e=>e.id!==`ruff`);for(let t of e)a.push(` ${t.name}: selected`);let t=r.filter(e=>e.id!==`ruff`);for(let e of t)a.push(` ${e.name}: skipped (${e.reason})`)}if(a.push(``),a.push(`Execution plan:`),i.length===0?a.push(` (no tasks to execute)`):i.forEach((e,t)=>{let n=e.description||`${e.executable} ${e.args.join(` `)}`;a.push(` ${t+1}. ${n}`)}),r.length>0){a.push(``),a.push(`Skipped:`);for(let e of r)a.push(` - ${e.name}: ${e.reason}`)}return a.join(` -`)}function Rr(e){for(let t of[`format:write`,`format:fix`,`format`,`fmt`])if(e[t]&&qi(e[t],e))return t}function zr(e){if(e[`format:check`]&&Ki(e[`format:check`],e)===`read-only`)return`format:check`;if(e[`fmt:check`]&&Ki(e[`fmt:check`],e)===`read-only`)return`fmt:check`}function Br(e,t){return t===`fix`?Rr(e):zr(e)}function Vr(e,t){if(t===`fix`)return e[`lint:fix`]?`lint:fix`:e[`lint:write`]?`lint:write`:void 0;if(e[`lint:check`]&&Ki(e[`lint:check`],e)===`read-only`)return`lint:check`;if(e.lint&&Ki(e.lint,e)===`read-only`)return`lint`}function Hr(e,t,n){if(n.inSingleQuote||n.inDoubleQuote||n.isEscaped)return 0;let r=e[t];return r===`;`||r===` -`?1:r===`&`&&e[t+1]===`&`||r===`|`&&e[t+1]===`|`?2:+(r===`|`)}function Ur(e,t){let n=t;for(;nt?n-1:t}function Wr(e,t){return t.inSingleQuote||t.inDoubleQuote||t.isEscaped?!1:e.length===0||/\s/.test(e[e.length-1])}function Gr(e,t){return t.isEscaped=!1,{ch:e,advance:!0}}function Kr(e,t,n,r){let i=e[t];if(n.isEscaped)return{newCmd:r+Gr(i,n).ch,nextIndex:t,sepLen:0};if(i===`\\`&&!n.inSingleQuote)return n.isEscaped=!0,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`'`&&!n.inDoubleQuote)return n.inSingleQuote=!n.inSingleQuote,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`"`&&!n.inSingleQuote)return n.inDoubleQuote=!n.inDoubleQuote,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`#`&&Wr(r,n))return{newCmd:r,nextIndex:Ur(e,t),sepLen:0};let a=Hr(e,t,n);return{newCmd:r+(a===0?i:``),nextIndex:t,sepLen:a}}function qr(e){let t=[],n=``,r={inSingleQuote:!1,inDoubleQuote:!1,isEscaped:!1,hasSyntaxError:!1};for(let i=0;i0?(n.trim()&&t.push(n.trim()),n=``,i+=a.sepLen-1):(n=a.newCmd,i=a.nextIndex)}return(r.inSingleQuote||r.inDoubleQuote||r.isEscaped)&&(r.hasSyntaxError=!0),n.trim()&&t.push(n.trim()),{commands:t,hasSyntaxError:r.hasSyntaxError}}function Jr(e){return qr(e).commands}function Yr(e,t,n){return t.isEscaped=!1,n+e}function Xr(e,t){return e===`'`&&!t.inDoubleQuote?(t.inSingleQuote=!t.inSingleQuote,!0):e===`"`&&!t.inSingleQuote&&(t.inDoubleQuote=!t.inDoubleQuote,!0)}const Zr=/* @__PURE__ */ new Set([`@`,`*`,`#`,`?`,`-`,`$`,`!`,`0`]);function Qr(e,t,n){if(n.inSingleQuote||n.isEscaped||e[t]!==`$`||t+1>=e.length)return!1;let r=e[t+1];return!!(r===`{`||Zr.has(r)||r>=`1`&&r<=`9`||r===`_`||r>=`a`&&r<=`z`||r>=`A`&&r<=`Z`)}function $r(e,t,n){if(n.inSingleQuote||n.inDoubleQuote||n.isEscaped||e[t]!==`&`)return!1;let r=t>0?e[t-1]:``,i=t+1`}function ei(e,t,n){if(n.inSingleQuote||n.isEscaped)return!1;let r=e[t];return r==="`"||r===`$`&&t+1`)&&t+1`||r===`<`||r===`&`&&t+1`}function ri(e,t,n){return $r(e,t,n.state)||Qr(e,t,n.state)||ei(e,t,n.state)||ti(e,t,n.state)?(n.inspection.hasUnsupportedSyntax=!0,!0):ni(e,t,n.state)?(n.inspection.hasRedirection=!0,n.inspection.hasUnsupportedSyntax=!0,!0):!1}function ii(e,t,n,r){let i=e[t];return n.state.isEscaped?{word:Yr(i,n.state,r),hasChars:!0,skipped:!1}:i===`\\`&&!n.state.inSingleQuote?(n.state.isEscaped=!0,{word:r,hasChars:!1,skipped:!0}):(ri(e,t,n),Xr(i,n.state)?{word:r,hasChars:!0,skipped:!0}:{word:r+i,hasChars:!0,skipped:!1})}function ai(e){let t={tokens:[],hasUnsupportedSyntax:!1,hasRedirection:!1},n={inSingleQuote:!1,inDoubleQuote:!1,isEscaped:!1},r={inspection:t,state:n},i=``,a=!1;for(let o=0;o0&&n.push(t)}return n}function fi(e,t,n={},r=/* @__PURE__ */ new Set){if(!e||!t)return!1;let i=di(n[e]===void 0?e:n[e]);for(let e of i){if(ui(e,t))return!0;let i=Bi(e);if(i){if(i===t)return!0;if(!r.has(i)&&n[i]!==void 0){r.add(i);try{if(fi(n[i],t,n,r))return!0}finally{r.delete(i)}}}let a=Vi(e);if(a!==void 0&&fi(a,t,n,r))return!0}return!1}function pi(e,t,n){return!n||n===t?!1:!fi(e[t]||``,n,e)}function mi(e,t,n){return!n||n===t?!1:!fi(e[t]||``,n,e)}function hi(e){let[t]=e;return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||(e===`--fix`||e.startsWith(`--fix=`))&&(t!==`eslint`||e!==`--fix-dry-run`))}function gi(e){let[t]=e;return t===`prettier`&&e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function _i(e){let[t]=e;return t===`oxfmt`&&e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function vi(e){let[t,n]=e;return t!==`biome`||n!==`format`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function yi(e){let[t]=e;return t===`eslint`&&e.some(e=>(e===`--fix`||e.startsWith(`--fix=`))&&e!==`--fix-dry-run`)}function bi(e){let[t]=e;return t===`oxlint`&&e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function xi(e){let[t,n]=e;return t!==`biome`||n!==`lint`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`))}function Si(e){let[t,n]=e;return t!==`biome`||n!==`check`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`))}function Ci(e){let[t,n]=e;return t!==`vp`||n!==`fmt`?!1:!e.some(e=>e===`--check`||e===`-c`)}function wi(e){let[t,n]=e;return t!==`vp`||n!==`lint`?!1:e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function Ti(e){return e.some(e=>e===`--fix`||e.startsWith(`--fix=`)&&e!==`--fix-dry-run`||e===`-o`||e.startsWith(`-o=`)||e===`--output-file`||e.startsWith(`--output-file=`)||e===`--cache`||e===`--cache-file`||e.startsWith(`--cache-file=`)||e===`--cache-location`||e.startsWith(`--cache-location=`))}function Ei(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--cache`||e===`--cache-location`||e.startsWith(`--cache-location=`)||e===`--insert-pragma`)}function Di(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`)||e===`--reporter-file`||e.startsWith(`--reporter-file=`)||e===`--log-file`||e.startsWith(`--log-file=`))}function Oi(e){return e.some(e=>e===`--fix`||e.startsWith(`--fix=`)||e===`--fix-suggestions`||e===`--fix-dangerously`||e===`--init`)}function ki(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--init`||e===`--migrate`||e.startsWith(`--migrate=`))}function Ai(e){let[t]=e;return t===`prettier`&&e.some(e=>e===`--check`||e===`-c`||e===`--list-different`||e===`-l`)&&!Ei(e)}function ji(e){let[t]=e;return t===`oxfmt`&&e.some(e=>e===`--check`||e===`-c`)&&!ki(e)}function Mi(e){let[t,n]=e;return t!==`biome`||n!==`format`?!1:!Di(e)}function Ni(e){let[t]=e;return t===`eslint`&&!Ti(e)}function Pi(e){let[t]=e;return t===`oxlint`&&!Oi(e)}function Fi(e){let[t,n]=e;return t!==`biome`||n!==`lint`?!1:!Di(e)}function Ii(e){let[t,n]=e;return t!==`biome`||n!==`check`?!1:!Di(e)}function Li(e){let[t,n]=e;return t!==`vp`||n!==`fmt`?!1:e.some(e=>e===`--check`||e===`-c`)&&!ki(e)}function Ri(e){let[t,n]=e;return t!==`vp`||n!==`lint`?!1:!Oi(e)}function zi(e){let[t,n]=e;return t!==`vp`||n!==`check`?!1:!e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function Bi(e){if(e.length===0)return;let[t,n,r]=e;if((t===`npm`||t===`pnpm`||t===`yarn`||t===`bun`)&&n===`run`&&r)return r;if((t===`pnpm`||t===`bun`||t===`yarn`)&&n&&!n.startsWith(`-`))return n}function Vi(e){if(e.length>=3&&(e[0]===`sh`||e[0]===`bash`)&&e[1]===`-c`)return e[2]}function Hi(e){return gi(e)||_i(e)||vi(e)||yi(e)||bi(e)||xi(e)||Si(e)||Ci(e)||wi(e)}function Ui(e){return Ai(e)||ji(e)||Mi(e)||Ni(e)||Pi(e)||Fi(e)||Ii(e)||Li(e)||Ri(e)||zi(e)}function Wi(e,t,n){if(e.length===0)return`unknown`;if(hi(e))return`write-capable`;let r=Vi(e);if(r!==void 0)return Gi(r,t,n);let i=Bi(e);if(i){if(n.has(i))return`unknown`;let e=t[i];if(!e)return`unknown`;n.add(i);try{return Gi(e,t,n)}finally{n.delete(i)}}return Hi(e)?`write-capable`:Ui(e)?`read-only`:`unknown`}function Gi(e,t,n){let r=qr(e);if(r.hasSyntaxError)return`unknown`;let{commands:i}=r;if(i.length===0)return`unknown`;let a=!1;for(let e of i){let r=ai(e),i=Wi(li(r.tokens),t,n);if(i===`write-capable`)return`write-capable`;if(r.hasUnsupportedSyntax||i===`unknown`)return`unknown`;i===`read-only`&&(a=!0)}return a?`read-only`:`unknown`}function Ki(e,t={}){return Gi(e,t,/* @__PURE__ */ new Set)}function qi(e,t={},n=/* @__PURE__ */ new Set){return di(e).some(e=>{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return qi(e,t,n)}finally{n.delete(r)}}let i=Vi(e);return i===void 0?gi(e)||_i(e)||vi(e)||Ci(e):qi(i,t,n)})}function Ji(e,t={},n=/* @__PURE__ */ new Set){return di(e).some(e=>{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return Ji(e,t,n)}finally{n.delete(r)}}let i=Vi(e);return i===void 0?yi(e)||bi(e)||xi(e)||wi(e):Ji(i,t,n)})}function Yi(e,t={},n=/* @__PURE__ */ new Set){return di(e).some(e=>{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return Yi(e,t,n)}finally{n.delete(r)}}let i=Vi(e);if(i!==void 0)return Yi(i,t,n);if(Si(e))return!0;let[a,o]=e;return a===`vp`&&o===`check`&&e.some(e=>e===`--fix`||e.startsWith(`--fix=`))})}function Xi(e,t,n,r){return t&&fi(e,t,n)?!0:r(e)}function Zi(e,t,n,r,i){let a=Xi(t,r,e,t=>qi(t,e)),o=Xi(t,i,e,t=>Ji(t,e)),s=Yi(t,e);return a&&o?[{capability:`format`,capabilities:[`format`,`lint`],scriptName:n}]:a&&!s?[{capability:`format`,scriptName:n}]:o&&!s?[{capability:`lint`,scriptName:n}]:s?[{capability:`autofix`,scriptName:n}]:[{capability:`typecheck`,capabilities:[],scriptName:n}]}function Qi(e,t){return qi(t[e],t)?{capability:`format`,scriptName:e}:{capability:`typecheck`,capabilities:[],scriptName:e}}function $i(e,t){return Ji(t[e],t)?{capability:`lint`,scriptName:e}:{capability:`typecheck`,capabilities:[],scriptName:e}}function ea(e,t,n){let r=[];return e&&r.push(Qi(e,n)),t&&r.push($i(t,n)),r}function ta(e,t,n,r,i){r&&pi(t,n,r)&&e.push(Qi(r,t)),i&&mi(t,n,i)&&e.push($i(i,t))}function na(e){let t=e.fix?`fix`:e[`check:fix`]?`check:fix`:void 0,n=Br(e,`fix`),r=Vr(e,`fix`);if(!t)return ea(n,r,e);let i=Zi(e,e[t]||``,t,n,r);return ta(i,e,t,n,r),i}function ra(e){let t=[],n=Br(e,`check`),r=Vr(e,`check`);return n&&t.push({capability:`format`,scriptName:n}),r&&t.push({capability:`lint`,scriptName:r}),t}function ia(e,t){return t===`fix`?na(e):ra(e)}var aa=class{id=`npm-scripts`;name=`package.json scripts`;capabilities=[`autofix`,`format`,`lint`];detect(e){let t=e.jsEnv?.packageJson?.scripts;if(!t||Object.keys(t).length===0)return{detected:!1,reason:`no scripts declared in package.json`};let n=ia(t,`fix`),r=ia(t,`check`),i=[...n,...r];return i.length>0?{detected:!0,reason:`found supported scripts: ${Array.from(new Set(i.map(e=>`"${e.scriptName}"`))).join(`, `)}`}:{detected:!1,reason:`no supported autofix or check scripts found in package.json`}}plan(e,t){let n=ia(e.jsEnv?.packageJson?.scripts||{},t),r=cn(e.jsPm||`npm`);return n.map(n=>({id:`script-${n.scriptName}`,toolId:this.id,capability:n.capability,capabilities:n.capabilities,executable:r,args:[`run`,n.scriptName],cwd:e.cwd,mode:t,description:`package.json script: ${r} run ${n.scriptName}`}))}};function oa(e,t){return e?!!(e.dependencies?.[t]||e.devDependencies?.[t]||e.peerDependencies?.[t]||e.optionalDependencies?.[t]):!1}function sa(e,t){for(let n of t)if(f.existsSync(p.join(e,n)))return!0;return!1}var ca=class{id=`vite-plus`;name=`Vite+`;capabilities=[`autofix`,`format`,`lint`];detect(e){let t=e.jsEnv?.packageJson,n=sn(t),r=sa(e.cwd,[`vite.config.ts`,`vite.config.js`,`vite.config.mjs`,`vite.config.cjs`]);return n||r&&t?.scripts&&Object.values(t.scripts).some(e=>e.includes(`vp `))?{detected:!0,reason:`Vite+ dependency and/or configuration detected`}:{detected:!1,reason:`no Vite+ dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`vite-plus-fmt`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`format`,executable:`vp`,args:[`fmt`],cwd:e.cwd,mode:`fix`,description:`Vite+: vp fmt`},{id:`vite-plus-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`lint`,executable:`vp`,args:[`lint`,`--fix`],cwd:e.cwd,mode:`fix`,description:`Vite+: vp lint --fix`}]:[{id:`vite-plus-fmt-check`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`format`,executable:`vp`,args:[`fmt`,`--check`],cwd:e.cwd,mode:`check`,description:`Vite+: vp fmt --check`},{id:`vite-plus-lint`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`lint`,executable:`vp`,args:[`lint`],cwd:e.cwd,mode:`check`,description:`Vite+: vp lint`}]}},la=class{id=`biome`;name=`Biome`;capabilities=[`format`,`lint`,`assist`];detect(e){let t=e.jsEnv?.packageJson,n=oa(t,`@biomejs/biome`),r=sa(e.cwd,[`biome.json`,`biome.jsonc`]);return n&&r?{detected:!0,reason:`Biome dependency and configuration detected`}:r?{detected:!0,reason:`Biome configuration detected`}:n?{detected:!1,reason:`Biome dependency present without configuration file`}:{detected:!1,reason:`no Biome dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`biome-format-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`format`,executable:`biome`,args:[`format`,`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: format --write .`},{id:`biome-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`lint`,executable:`biome`,args:[`lint`,`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: lint --write .`},{id:`biome-assist-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`assist`,executable:`biome`,args:[`check`,`--write`,`--formatter-enabled=false`,`--linter-enabled=false`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: safe assist actions (--write)`}]:[{id:`biome-format-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`format`,executable:`biome`,args:[`format`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: format .`},{id:`biome-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`lint`,executable:`biome`,args:[`lint`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: lint .`},{id:`biome-assist-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`assist`,executable:`biome`,args:[`check`,`--formatter-enabled=false`,`--linter-enabled=false`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: check assist actions`}]}},ua=class{id=`prettier`;name=`Prettier`;capabilities=[`format`];detect(e){let t=e.jsEnv?.packageJson,n=oa(t,`prettier`),r=!!t?.prettier,i=sa(e.cwd,[`.prettierrc`,`.prettierrc.json`,`.prettierrc.yml`,`.prettierrc.yaml`,`.prettierrc.json5`,`.prettierrc.js`,`.prettierrc.cjs`,`.prettierrc.mjs`,`prettier.config.js`,`prettier.config.cjs`,`prettier.config.mjs`]);return n&&(r||i)?{detected:!0,reason:`Prettier dependency and configuration detected`}:r||i?{detected:!0,reason:`Prettier configuration detected`}:n?{detected:!1,reason:`Prettier dependency present without configuration`}:{detected:!1,reason:`no Prettier dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`prettier-format`,toolId:this.id,toolName:this.name,requiredPackage:`prettier`,capability:`format`,executable:`prettier`,args:[`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Prettier: format`}]:[{id:`prettier-check`,toolId:this.id,toolName:this.name,requiredPackage:`prettier`,capability:`format`,executable:`prettier`,args:[`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Prettier: format check`}]}},da=class{id=`eslint`;name=`ESLint`;capabilities=[`lint`];detect(e){let t=e.jsEnv?.packageJson,n=oa(t,`eslint`),r=!!t?.eslintConfig,i=sa(e.cwd,[`eslint.config.js`,`eslint.config.mjs`,`eslint.config.cjs`,`eslint.config.ts`,`.eslintrc`,`.eslintrc.js`,`.eslintrc.cjs`,`.eslintrc.json`,`.eslintrc.yaml`,`.eslintrc.yml`]);return n&&(r||i)?{detected:!0,reason:`ESLint dependency and configuration detected`}:r||i?{detected:!0,reason:`ESLint configuration detected`}:n?{detected:!1,reason:`ESLint dependency present without configuration`}:{detected:!1,reason:`no ESLint dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`eslint-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`eslint`,capability:`lint`,executable:`eslint`,args:[`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`ESLint: lint autofix`}]:[{id:`eslint-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`eslint`,capability:`lint`,executable:`eslint`,args:[`.`],cwd:e.cwd,mode:`check`,description:`ESLint: lint check`}]}},fa=class{id=`oxlint`;name=`Oxlint`;capabilities=[`lint`];detect(e){let t=e.jsEnv?.packageJson,n=oa(t,`oxlint`),r=sa(e.cwd,[`.oxlintrc.json`,`oxlint.json`,`oxlint.config.ts`,`oxlint.config.js`]);return n&&r?{detected:!0,reason:`Oxlint dependency and configuration detected`}:r?{detected:!0,reason:`Oxlint configuration detected`}:n?{detected:!1,reason:`Oxlint dependency present without configuration`}:{detected:!1,reason:`no Oxlint dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`oxlint-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`oxlint`,capability:`lint`,executable:`oxlint`,args:[`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`Oxlint: lint autofix`}]:[{id:`oxlint-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`oxlint`,capability:`lint`,executable:`oxlint`,args:[`.`],cwd:e.cwd,mode:`check`,description:`Oxlint: lint check`}]}},pa=class{id=`oxfmt`;name=`Oxfmt`;capabilities=[`format`];detect(e){let t=e.jsEnv?.packageJson,n=oa(t,`oxfmt`),r=sa(e.cwd,[`.oxfmtrc.json`,`oxfmt.json`,`oxfmt.config.ts`,`oxfmt.config.js`]);return n&&r?{detected:!0,reason:`Oxfmt dependency and configuration detected`}:r?{detected:!0,reason:`Oxfmt configuration detected`}:n?{detected:!1,reason:`Oxfmt dependency present without configuration`}:{detected:!1,reason:`no Oxfmt dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`oxfmt-format`,toolId:this.id,toolName:this.name,requiredPackage:`oxfmt`,capability:`format`,executable:`oxfmt`,args:[`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Oxfmt: format`}]:[{id:`oxfmt-check`,toolId:this.id,toolName:this.name,requiredPackage:`oxfmt`,capability:`format`,executable:`oxfmt`,args:[`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Oxfmt: format check`}]}},ma=class{id=`javascript`;name=`JavaScript/TypeScript`;scriptAdapter=new aa;vitePlusAdapter=new ca;biomeAdapter=new la;prettierAdapter=new ua;eslintAdapter=new da;oxlintAdapter=new fa;oxfmtAdapter=new pa;detect(e){return!!e.jsEnv?.hasPackageJson}getAdapters(e){return[this.scriptAdapter,this.vitePlusAdapter,this.biomeAdapter,this.oxfmtAdapter,this.prettierAdapter,this.oxlintAdapter,this.eslintAdapter]}},ha=class{id=`ruff`;name=`Ruff`;capabilities=[`lint`,`format`];detect(e){return e.hasPythonSource??or(e.cwd)?{detected:!0,reason:`default Python toolchain`}:{detected:!1,reason:`no Python source files detected`}}plan(e,t){let n=[];return t===`fix`?n.push({id:`ruff-lint-fix`,toolId:this.id,capability:`lint`,executable:`ruff`,args:[`check`,`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`Ruff: lint autofix`},{id:`ruff-format`,toolId:this.id,capability:`format`,executable:`ruff`,args:[`format`,`.`],cwd:e.cwd,mode:`fix`,description:`Ruff: format`}):n.push({id:`ruff-lint-check`,toolId:this.id,capability:`lint`,executable:`ruff`,args:[`check`,`.`],cwd:e.cwd,mode:`check`,description:`Ruff: lint check`},{id:`ruff-format-check`,toolId:this.id,capability:`format`,executable:`ruff`,args:[`format`,`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Ruff: format check`}),n}},ga=class{id=`python`;name=`Python`;ruffAdapter=new ha;detect(e){return e.hasPythonSource??or(e.cwd)}getAdapters(e){return[this.ruffAdapter]}},_a=class{providers=/* @__PURE__ */ new Map;standaloneAdapters=/* @__PURE__ */ new Map;registerProvider(e){this.providers.set(e.id,e)}getProvider(e){return this.providers.get(e)}getProviders(){return Array.from(this.providers.values())}registerAdapter(e){this.standaloneAdapters.set(e.id,e)}getAdapter(e){return this.standaloneAdapters.get(e)}getStandaloneAdapters(){return Array.from(this.standaloneAdapters.values())}clear(){this.providers.clear(),this.standaloneAdapters.clear()}};function va(){let e=new _a;return e.registerProvider(new ga),e.registerProvider(new ma),e}const ya=va();async function ba(e,t=process.cwd()){let n=`${e.command} ${e.args.join(` `)}`.trim();if(en(`Installing JS dependencies: ${n}`),await Yt(e.command,e.args,{cwd:t,ignoreReturnCode:!0})!==0)throw Error(`JS dependency installation failed executing "${n}". Check dependency constraints or lockfile compatibility.`)}async function xa(e=process.cwd()){let t=fn(e);if(!t.hasPackageJson){en(`No package.json found. Skipping scripts.`);return}if(t.packageJsonError)throw Error(t.packageJsonError);let n=cn(process.env.PACKAGE_MANAGER||_n(t).name),r=(await Ir(ya,{cwd:e,jsEnv:t,jsPm:n,hasPythonSource:!1},`fix`)).tasks.filter(e=>e.toolId!==`ruff`);if(r.length===0){en(`No matching scripts (fix, format, lint, check, etc.) found in package.json.`);return}await ba(vn(n),e);let i=(await new Ar().executeTasks(r)).filter(e=>e.error);if(i.length>0){let e=i.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` -`))}}async function Sa(e,t,n=process.cwd()){let r=`${e} ${t.join(` `)}`.trim();if(en(`Executing: ${r}`),await Yt(e,t,{cwd:n,ignoreReturnCode:!0})!==0)throw Error(`Python command "${r}" failed. Check Python environment and dependency configuration.`)}async function Ca(e,t=process.cwd()){Dr(e,`Invalid Python environment: "pmInfo" must be an object`);let n=e.name===`pip`?`pip`:e.name;if(!await Vt(n,!1)&&!e.isStandalone){let e=`Required Python package manager '${n}' is not available in PATH.`;throw Error(e)}if(!e.isStandalone&&e.installCommands.length>0){let n=e.installCommands;en(`Installing Python dependencies: ${n.map(e=>`${e.executable} ${e.args.join(` `)}`).join(` && `)}`);for(let{executable:e,args:r}of n)await Sa(e,r,t)}}async function wa(e=process.cwd(),t,n){if(!(t??or(e)))return;let r;if(n)r=n;else{if(!await Vt(`ruff`,!1))throw Error(`Required Python binary 'ruff' is not available in PATH.`);r=(await Ir(ya,{cwd:e,hasPythonSource:!0},`fix`)).tasks.filter(e=>e.toolId===`ruff`)}let i=(await new Ar().executeTasks(r)).filter(e=>e.error);if(i.length>0){let e=i.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` -`))}}async function Ta(e,t){if(process.env.JS_SETUP_OUTCOME===`failure`||process.env.NODE_SETUP_OUTCOME===`failure`||process.env.BUN_SETUP_OUTCOME===`failure`||process.env.PNPM_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`JavaScript runtime or package manager setup failed.`);try{if(t){let n=fn(e);if(!n.hasPackageJson)return en(`No package.json found. Skipping scripts.`),null;if(n.packageJsonError)throw Error(n.packageJsonError);if(t.length===0)return en(`No matching scripts (fix, format, lint, check, etc.) found in package.json.`),null;await ba(vn(cn(process.env.PACKAGE_MANAGER||_n(n).name)),e);let r=(await new Ar().executeTasks(t)).filter(e=>e.error);if(r.length>0){let e=r.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` -`))}return null}return await xa(e),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function Ea(e,t){if(process.env.PYTHON_SETUP_OUTCOME===`failure`||process.env.UV_SETUP_OUTCOME===`failure`||process.env.PY_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`Python runtime or package manager setup failed.`);let n=t&&`pmInfo`in t?t.pmInfo:Tr(e,void 0,{ctx:t?.ctx,hasSource:t?.hasSource});if(!n)return null;try{return await Ca(n,e),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function Da(e,t,n){if(process.env.RUFF_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`Ruff setup failed.`);try{return await wa(e,t,n),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function Oa(e,t,n){let r=t??or(e);if(!r)return[];let i=[],a=ir(e),o=await Ea(e,{ctx:a,hasSource:!0,pmInfo:Tr(e,void 0,{ctx:a,hasSource:!0,isProject:!0})});o&&i.push(o);let s=await Da(e,r,n);return s&&i.push(s),i}async function ka(e=process.cwd()){let t=or(e),n;try{let r=fn(e),i={cwd:e,jsEnv:r,jsPm:cn(process.env.PACKAGE_MANAGER||_n(r).name),hasPythonSource:t};n=await Ir(ya,i,`fix`),en(n.summary)}catch{}let r=[],i=n?n.tasks.filter(e=>e.toolId!==`ruff`):void 0,a=n?n.tasks.filter(e=>e.toolId===`ruff`):void 0,o=await Ta(e,i);o&&r.push(o);let s=await Oa(e,t,a);r.push(...s),r.length>0&&(Zt(`Failed to run scripts: ${r.map(e=>e.message).join(` -`)}`),process.env.NODE_ENV!==`test`&&process.exit(1))}process.env.NODE_ENV!==`test`&&Or(import.meta.url)&&ka();export{xa as executeJsWorkflow,Ca as executePythonWorkflow,wa as executeRuffAutofix,ka as run,cn as sanitizePackageManager}; \ No newline at end of file +`&&e[i]!==`\r`;)r+=` `,i++;return i!Un(e))}function mr(e,t){if(!Un(e[`build-system`]))return!1;if(typeof t==`string`){let e=fr(t);return/(?:^|\n)\s*\[\s*build-system\s*\]\s*(?:\n|$)/.test(e)}let n=Object.entries(e[`build-system`]);return n.length===0||n.some(([,e])=>!Un(e))}function hr(e,t){if(pr(e,t)||mr(e,t))return!0;if(Un(e.tool)&&Un(e.tool.uv)){let t=e.tool.uv;return Yn(t.workspace)||Array.isArray(t[`dev-dependencies`])}return!1}function gr(e){return Un(e.tool)&&Un(e.tool.uv)}function _r(e,t){return pr(e,t)||mr(e,t)}function vr(e,t){return p.relative(e,t)||`.`}function yr(e,t){if(!t)throw Error(`Invalid Python project configuration: 'uv.lock' was detected without a valid 'pyproject.toml' project or workspace configuration ('[project]', '[build-system]', or '[tool.uv.workspace]'). 'uv sync' requires a valid 'pyproject.toml' file.`);return{name:`uv`,lockfile:`uv.lock`,configFile:`pyproject.toml`,installCommands:[{executable:`uv`,args:[`sync`]}],lockCommand:`uv lock`,syncCommand:`uv sync`,execPrefix:`uv run`}}function br(e,t){if(e.hasRequirementsTxt&&e.requirementsDir){let t=vr(e.cwd,p.join(e.requirementsDir,`requirements.txt`));return{name:`uv`,configFile:e.pyprojectDir?`pyproject.toml`:`uv.toml`,installCommands:[{executable:`uv`,args:[`pip`,`install`,`-r`,t]}],execPrefix:`uv run`}}return t?{name:`uv`,configFile:e.pyprojectDir?`pyproject.toml`:`uv.toml`,installCommands:[{executable:`uv`,args:[`sync`]}],lockCommand:`uv lock`,syncCommand:`uv sync`,execPrefix:`uv run`}:null}function xr(e){let t=e.pyprojectToml?.success===!0&&hr(e.pyprojectToml.data,e.pyprojectToml.rawContent),n=e.workspaceRootToml?.success===!0&&hr(e.workspaceRootToml.data,e.workspaceRootToml.rawContent),r=t||n,i=e.uvToml?.success===!0,a=e.pyprojectToml?.success===!0&&gr(e.pyprojectToml.data)||e.workspaceRootToml?.success===!0&&gr(e.workspaceRootToml.data);return e.hasUvLock?yr(e,r):a||i?br(e,r):null}function Sr(e){let t=e.pyprojectToml?.success===!0&&_r(e.pyprojectToml.data,e.pyprojectToml.rawContent);if(!(e.hasRequirementsTxt||e.hasSetupPy||e.hasSetupCfg||t))return null;let n;t?n=`pyproject.toml`:e.hasSetupPy?n=`setup.py`:e.hasSetupCfg&&(n=`setup.cfg`);let r=[],i,a=e.hasSetupPy||e.hasSetupCfg||t,o=e.requirementsDir?vr(e.cwd,p.join(e.requirementsDir,`requirements.txt`)):`requirements.txt`,s=vr(e.cwd,e.projectDir);return e.hasRequirementsTxt&&a?(r=[{executable:`pip`,args:[`install`,`-r`,o]},{executable:`pip`,args:[`install`,`-e`,s]}],i=`requirements.txt`):e.hasRequirementsTxt?(r=[{executable:`pip`,args:[`install`,`-r`,o]}],i=`requirements.txt`):a&&(r=[{executable:`pip`,args:[`install`,`-e`,s]}]),{name:`pip`,lockfile:i,configFile:n,installCommands:r,execPrefix:``}}function Cr(e=process.cwd(),t){if(t?.hasSource??or(e))return!0;let n=t?.ctx??ir(e);if(n.projectDir===n.cwd&&tr(n.cwd)&&(n.pyprojectToml?.success===!0&&hr(n.pyprojectToml.data,n.pyprojectToml.rawContent)||n.hasUvLock||n.hasRequirementsTxt||n.hasSetupPy||n.hasSetupCfg))return!0;if(n.workspaceRootDir&&n.workspaceRootToml?.success===!0){let t=Zn(n.workspaceRootDir);if(t&&Xn(n.workspaceRootDir,p.resolve(e),t.config))return!0}return!1}function wr(e,t){t!=null&&(e.pyprojectToml=t);for(let t of Vn)if(t===`uv`){let t=xr(e);if(t)return t}else if(t===`pip`){let t=Sr(e);if(t)return t}return{name:`pip`,installCommands:[],execPrefix:``,isStandalone:!0}}function Tr(e=process.cwd(),t,n){let r=n?.ctx??ir(e);return n?.isProject??Cr(e,{ctx:r,hasSource:n?.hasSource})?wr(r,t):null}function Er(e){if(!Array.isArray(e))throw Error(`Invalid Python environment: "pmInfo.installCommands" must be an array`);for(let t of e){if(!t||typeof t!=`object`)throw Error(`Invalid Python environment: malformed "installCommands" item`);let e=t;if(typeof e.executable!=`string`||!e.executable.trim()||!Array.isArray(e.args)||!e.args.every(e=>typeof e==`string`))throw Error(`Invalid Python environment: malformed "installCommands" item`)}}function Dr(e,t=`Invalid Python environment: "pmInfo" must be an object`){if(!e||typeof e!=`object`||Array.isArray(e))throw Error(t);let n=e;if(n.name!==`uv`&&n.name!==`pip`)throw Error(`Invalid Python environment: pmInfo name must be "uv" or "pip"`);return Er(n.installCommands),n}function Or(e){if(!process.argv[1])return!1;try{return l(e)===f.realpathSync(process.argv[1])}catch{return!1}}async function kr(e,t,n=process.platform){let r=p.join(e,`node_modules`,`.bin`),i=/* @__PURE__ */ new Set([`.cmd`,`.bat`,`.exe`]);if(n===`win32`){let e=p.extname(t).toLowerCase();if(e){if(!i.has(e))return;let n=p.join(r,t);if(f.existsSync(n))return n}else for(let e of[`.cmd`,`.bat`,`.exe`,``]){let n=p.join(r,`${t}${e}`);if(f.existsSync(n))return n}}else{let e=p.join(r,t);if(f.existsSync(e))return e}let a=await Vt(t,!1);if(a){if(n===`win32`){let e=p.extname(a).toLowerCase();if(e&&!i.has(e))return}return a}}var Ar=class{platform;constructor(e=process.platform){this.platform=e}async resolveTaskExecutable(e){if(e.toolId===`npm-scripts`)return e.executable;if(e.toolId===`ruff`){if(!await Vt(`ruff`,!1))throw Error(`Required Python binary 'ruff' is not available in PATH.`);return`ruff`}let t=await kr(e.cwd,e.executable,this.platform);if(!t){let t=e.toolName||e.toolId,n=e.requiredPackage||e.executable;throw Error(`Configured tool "${t}" could not run: executable "${e.executable}" is not available in node_modules/.bin or PATH. Please install "${n}" in project dependencies.`)}return t}async executeTasks(e){let t=[];for(let n of e){let e=`${n.executable} ${n.args.join(` `)}`.trim();try{let r=await this.resolveTaskExecutable(n);en(`Executing: ${e}`);let i=n.env?Object.fromEntries(Object.entries({...process.env,...n.env}).filter(e=>e[1]!==void 0)):void 0,a=await Yt(r,n.args,{cwd:n.cwd,env:i,ignoreReturnCode:!0});if(a!==0){let r=n.toolId===`ruff`?`Ruff command "${e}" failed. Check Python and Ruff configuration.`:n.toolId===`npm-scripts`?`Script "${n.args[1]||e}" failed while executing "${e}".`:`Task "${n.id}" (${e}) failed with exit code ${a}.`;t.push({task:n,exitCode:a,error:Error(r)})}else t.push({task:n,exitCode:a})}catch(e){let r=e instanceof Error?e:Error(String(e));t.push({task:n,exitCode:1,error:r})}}return t}};function jr(e,t){return t.has(`autofix`)?!0:e===`autofix`?t.has(`format`)&&t.has(`lint`)&&t.has(`assist`):t.has(e)}function Mr(e,t){let n=[],r=[];for(let i of e)jr(i.capability,t)?r.push(i):n.push(i);return{kept:n,skipped:r}}function Nr(e,t){let n=e.get(t);return n||(n=/* @__PURE__ */ new Set,e.set(t,n)),n}async function Pr(e,t,n,r,i,a,o){for(let s of e){let e=s.adapter,c=Nr(t,s.scope),l=await e.plan(n,r);if(l.length===0)continue;let{kept:u,skipped:d}=Mr(l,c);if(u.length===0)a.push({id:e.id,name:e.name,reason:`all capabilities already covered by higher priority tool`});else{i.push({id:e.id,name:e.name,reason:s.reason});for(let e of u)if(c.add(e.capability),e.capabilities)for(let t of e.capabilities)c.add(t);if(o.push(...u),d.length>0){let t=Array.from(new Set(d.map(e=>e.capability))).join(`, `);a.push({id:e.id,name:e.name,reason:`partially skipped (${t} covered by higher priority tool)`})}}}}async function Fr(e,t,n,r,i,a,o){let s=[];for(let t of e.getStandaloneAdapters()){let e=await t.detect(n),r=t.scope??`standalone:${t.id}`;e.detected?s.push({adapter:t,reason:e.reason,scope:r}):e.reason&&!e.reason.startsWith(`no `)&&a.push({id:t.id,name:t.name,reason:e.reason})}await Pr(s,t,n,r,i,a,o)}function Ir(e){let t=[],n=/* @__PURE__ */ new Set;for(let r of e)n.has(r.id)||(n.add(r.id),t.push(r));return t}async function Lr(e,t,n=`fix`){let r=t.hasPythonSource??or(t.cwd),i=t.jsEnv??fn(t.cwd),a={...t,jsEnv:i,hasPythonSource:r},o=[],s=[],c=[],l=e.getProviders(),u=/* @__PURE__ */ new Map;for(let e of l){if(!await e.detect(a))continue;let t=e.getAdapters(a),r=[];for(let n of t){let t=await n.detect(a),i=n.scope??e.id;t.detected?r.push({adapter:n,reason:t.reason,scope:i}):t.reason&&!t.reason.startsWith(`no `)&&s.push({id:n.id,name:n.name,reason:t.reason})}await Pr(r,u,a,n,o,s,c)}await Fr(e,u,a,n,o,s,c);let d=Ir(c);return{tasks:d,selectedTools:o,skippedTools:s,summary:Rr(e,a,o,s,d)}}function Rr(e,t,n,r,i){let a=[];a.push(`Detected environments:`);let o=t.hasPythonSource??!1,s=t.jsEnv?.hasPackageJson??!1;if(a.push(` Python: ${o}`),a.push(` JavaScript: ${s}`),o&&(a.push(``),a.push(`Python toolchain:`),n.filter(e=>e.id===`ruff`).length>0?a.push(` Ruff: default tool`):a.push(` None`)),s){a.push(``),a.push(`JavaScript toolchain:`);let e=n.filter(e=>e.id!==`ruff`);for(let t of e)a.push(` ${t.name}: selected`);let t=r.filter(e=>e.id!==`ruff`);for(let e of t)a.push(` ${e.name}: skipped (${e.reason})`)}if(a.push(``),a.push(`Execution plan:`),i.length===0?a.push(` (no tasks to execute)`):i.forEach((e,t)=>{let n=e.description||`${e.executable} ${e.args.join(` `)}`;a.push(` ${t+1}. ${n}`)}),r.length>0){a.push(``),a.push(`Skipped:`);for(let e of r)a.push(` - ${e.name}: ${e.reason}`)}return a.join(` +`)}function zr(e){for(let t of[`format:write`,`format:fix`,`format`,`fmt`])if(e[t]&&Yi(e[t],e))return t}function Br(e){if(e[`format:check`]&&Ji(e[`format:check`],e)===`read-only`)return`format:check`;if(e[`fmt:check`]&&Ji(e[`fmt:check`],e)===`read-only`)return`fmt:check`}function Vr(e,t){return t===`fix`?zr(e):Br(e)}function Hr(e,t){if(t===`fix`)return e[`lint:fix`]?`lint:fix`:e[`lint:write`]?`lint:write`:void 0;if(e[`lint:check`]&&Ji(e[`lint:check`],e)===`read-only`)return`lint:check`;if(e.lint&&Ji(e.lint,e)===`read-only`)return`lint`}function Ur(e,t,n){if(n.inSingleQuote||n.inDoubleQuote||n.isEscaped)return 0;let r=e[t];return r===`;`||r===` +`?1:r===`&`&&e[t+1]===`&`||r===`|`&&e[t+1]===`|`?2:+(r===`|`)}function Wr(e,t){let n=t;for(;nt?n-1:t}function Gr(e,t){return t.inSingleQuote||t.inDoubleQuote||t.isEscaped?!1:e.length===0||/\s/.test(e[e.length-1])}function Kr(e,t){return t.isEscaped=!1,{ch:e,advance:!0}}function qr(e,t,n,r){let i=e[t];if(n.isEscaped)return{newCmd:r+Kr(i,n).ch,nextIndex:t,sepLen:0};if(i===`\\`&&!n.inSingleQuote)return n.isEscaped=!0,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`'`&&!n.inDoubleQuote)return n.inSingleQuote=!n.inSingleQuote,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`"`&&!n.inSingleQuote)return n.inDoubleQuote=!n.inDoubleQuote,{newCmd:r+i,nextIndex:t,sepLen:0};if(i===`#`&&Gr(r,n))return{newCmd:r,nextIndex:Wr(e,t),sepLen:0};let a=Ur(e,t,n);return{newCmd:r+(a===0?i:``),nextIndex:t,sepLen:a}}function Jr(e){let t=[],n=``,r={inSingleQuote:!1,inDoubleQuote:!1,isEscaped:!1,hasSyntaxError:!1};for(let i=0;i0?(n.trim()&&t.push(n.trim()),n=``,i+=a.sepLen-1):(n=a.newCmd,i=a.nextIndex)}return(r.inSingleQuote||r.inDoubleQuote||r.isEscaped)&&(r.hasSyntaxError=!0),n.trim()&&t.push(n.trim()),{commands:t,hasSyntaxError:r.hasSyntaxError}}function Yr(e,t,n){return t.isEscaped=!1,n+e}function Xr(e,t){return e===`'`&&!t.inDoubleQuote?(t.inSingleQuote=!t.inSingleQuote,!0):e===`"`&&!t.inSingleQuote&&(t.inDoubleQuote=!t.inDoubleQuote,!0)}const Zr=/* @__PURE__ */ new Set([`@`,`*`,`#`,`?`,`-`,`$`,`!`,`0`]);function Qr(e,t,n){if(n.inSingleQuote||n.isEscaped||e[t]!==`$`||t+1>=e.length)return!1;let r=e[t+1];return!!(r===`{`||Zr.has(r)||r>=`1`&&r<=`9`||r===`_`||r>=`a`&&r<=`z`||r>=`A`&&r<=`Z`)}function $r(e,t,n){if(n.inSingleQuote||n.inDoubleQuote||n.isEscaped||e[t]!==`&`)return!1;let r=t>0?e[t-1]:``,i=t+1`}function ei(e,t,n){if(n.inSingleQuote||n.isEscaped)return!1;let r=e[t];return r==="`"||r===`$`&&t+1`)&&t+1`||r===`<`||r===`&`&&t+1`}function ri(e,t,n){return $r(e,t,n.state)||Qr(e,t,n.state)||ei(e,t,n.state)||ti(e,t,n.state)?(n.inspection.hasUnsupportedSyntax=!0,!0):ni(e,t,n.state)?(n.inspection.hasRedirection=!0,n.inspection.hasUnsupportedSyntax=!0,!0):!1}function ii(e,t,n,r){let i=e[t];return n.state.isEscaped?{word:Yr(i,n.state,r),hasChars:!0,skipped:!1}:i===`\\`&&!n.state.inSingleQuote?(n.state.isEscaped=!0,{word:r,hasChars:!1,skipped:!0}):(ri(e,t,n),Xr(i,n.state)?{word:r,hasChars:!0,skipped:!0}:{word:r+i,hasChars:!0,skipped:!1})}function ai(e){let t={tokens:[],hasUnsupportedSyntax:!1,hasRedirection:!1,hasSyntaxError:!1},n={inSingleQuote:!1,inDoubleQuote:!1,isEscaped:!1},r={inspection:t,state:n},i=``,a=!1;for(let o=0;o0&&r.push(i)}return{invocations:r,hasSyntaxError:n}}function di(e){return ui(e).invocations}function fi(e,t,n={},r=/* @__PURE__ */ new Set){if(!e||!t)return!1;let i=di(n[e]===void 0?e:n[e]);for(let e of i){if(li(e,t))return!0;let i=Bi(e);if(i){if(i===t)return!0;if(!r.has(i)&&n[i]!==void 0){r.add(i);try{if(fi(n[i],t,n,r))return!0}finally{r.delete(i)}}}let a=Vi(e);if(a!==void 0&&fi(a,t,n,r))return!0}return!1}function pi(e,t,n){return!n||n===t?!1:!fi(e[t]||``,n,e)}function mi(e,t,n){return!n||n===t?!1:!fi(e[t]||``,n,e)}function hi(e){let[t]=e;return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||(e===`--fix`||e.startsWith(`--fix=`))&&(t!==`eslint`||e!==`--fix-dry-run`))}function gi(e){let[t]=e;return t===`prettier`&&e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function _i(e){let[t]=e;return t===`oxfmt`&&e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function vi(e){let[t,n]=e;return t!==`biome`||n!==`format`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`))}function yi(e){let[t]=e;return t===`eslint`&&e.some(e=>(e===`--fix`||e.startsWith(`--fix=`))&&e!==`--fix-dry-run`)}function bi(e){let[t]=e;return t===`oxlint`&&e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function xi(e){let[t,n]=e;return t!==`biome`||n!==`lint`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`))}function Si(e){let[t,n]=e;return t!==`biome`||n!==`check`?!1:e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`))}function Ci(e){let[t,n]=e;return t!==`vp`||n!==`fmt`?!1:!e.some(e=>e===`--check`||e===`-c`)}function wi(e){let[t,n]=e;return t!==`vp`||n!==`lint`?!1:e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function Ti(e){return e.some(e=>e===`--fix`||e.startsWith(`--fix=`)&&e!==`--fix-dry-run`||e===`-o`||e.startsWith(`-o=`)||e===`--output-file`||e.startsWith(`--output-file=`)||e===`--cache`||e===`--cache-file`||e.startsWith(`--cache-file=`)||e===`--cache-location`||e.startsWith(`--cache-location=`))}function Ei(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--cache`||e===`--cache-location`||e.startsWith(`--cache-location=`)||e===`--insert-pragma`)}function Di(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--fix`||e.startsWith(`--fix=`)||e===`--reporter-file`||e.startsWith(`--reporter-file=`)||e===`--log-file`||e.startsWith(`--log-file=`))}function Oi(e){return e.some(e=>e===`--fix`||e.startsWith(`--fix=`)||e===`--fix-suggestions`||e===`--fix-dangerously`||e===`--init`)}function ki(e){return e.some(e=>e===`-w`||e===`--write`||e.startsWith(`--write=`)||e===`--init`||e===`--migrate`||e.startsWith(`--migrate=`))}function Ai(e){let[t]=e;return t===`prettier`&&e.some(e=>e===`--check`||e===`-c`||e===`--list-different`||e===`-l`)&&!Ei(e)}function ji(e){let[t]=e;return t===`oxfmt`&&e.some(e=>e===`--check`||e===`-c`)&&!ki(e)}function Mi(e){let[t,n]=e;return t!==`biome`||n!==`format`?!1:!Di(e)}function Ni(e){let[t]=e;return t===`eslint`&&!Ti(e)}function Pi(e){let[t]=e;return t===`oxlint`&&!Oi(e)}function Fi(e){let[t,n]=e;return t!==`biome`||n!==`lint`?!1:!Di(e)}function Ii(e){let[t,n]=e;return t!==`biome`||n!==`check`?!1:!Di(e)}function Li(e){let[t,n]=e;return t!==`vp`||n!==`fmt`?!1:e.some(e=>e===`--check`||e===`-c`)&&!ki(e)}function Ri(e){let[t,n]=e;return t!==`vp`||n!==`lint`?!1:!Oi(e)}function zi(e){let[t,n]=e;return t!==`vp`||n!==`check`?!1:!e.some(e=>e===`--fix`||e.startsWith(`--fix=`))}function Bi(e){if(e.length===0)return;let[t,n,r]=e;if((t===`npm`||t===`pnpm`||t===`yarn`||t===`bun`)&&n===`run`&&r)return r;if((t===`pnpm`||t===`bun`||t===`yarn`)&&n&&!n.startsWith(`-`))return n}function Vi(e){if(e.length>=3&&(e[0]===`sh`||e[0]===`bash`)&&e[1]===`-c`)return e[2]}function Hi(e){if(e.length===0)return e;let[t]=e;if(t!==`cross-env`&&t!==`cross-env-shell`)return e;let n=1;for(;n{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return Yi(e,t,n)}finally{n.delete(r)}}let i=Vi(e);return i===void 0?gi(e)||_i(e)||vi(e)||Ci(e):Yi(i,t,n)})}function Xi(e,t={},n=/* @__PURE__ */ new Set){return di(e).some(e=>{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return Xi(e,t,n)}finally{n.delete(r)}}let i=Vi(e);return i===void 0?yi(e)||bi(e)||xi(e)||wi(e):Xi(i,t,n)})}function Zi(e,t={},n=/* @__PURE__ */ new Set){return di(e).some(e=>{let r=Bi(e);if(r){if(n.has(r))return!1;let e=t[r];if(!e)return!1;n.add(r);try{return Zi(e,t,n)}finally{n.delete(r)}}let i=Vi(e);if(i!==void 0)return Zi(i,t,n);if(Si(e))return!0;let[a,o]=e;return a===`vp`&&o===`check`&&e.some(e=>e===`--fix`||e.startsWith(`--fix=`))})}function Qi(e,t,n,r){return t&&fi(e,t,n)?!0:r(e)}function $i(e,t,n,r,i){let a=Qi(t,r,e,t=>Yi(t,e)),o=Qi(t,i,e,t=>Xi(t,e)),s=Zi(t,e);return a&&o?[{capability:`format`,capabilities:[`format`,`lint`],scriptName:n}]:a&&!s?[{capability:`format`,scriptName:n}]:o&&!s?[{capability:`lint`,scriptName:n}]:s?[{capability:`autofix`,scriptName:n}]:[{capability:`typecheck`,capabilities:[],scriptName:n}]}function ea(e,t){return Yi(t[e],t)?{capability:`format`,scriptName:e}:{capability:`typecheck`,capabilities:[],scriptName:e}}function ta(e,t){return Xi(t[e],t)?{capability:`lint`,scriptName:e}:{capability:`typecheck`,capabilities:[],scriptName:e}}function na(e,t,n){let r=[];return e&&r.push(ea(e,n)),t&&r.push(ta(t,n)),r}function ra(e,t,n,r,i){r&&pi(t,n,r)&&e.push(ea(r,t)),i&&mi(t,n,i)&&e.push(ta(i,t))}function ia(e){let t=e.fix?`fix`:e[`check:fix`]?`check:fix`:void 0,n=Vr(e,`fix`),r=Hr(e,`fix`);if(!t)return na(n,r,e);let i=$i(e,e[t]||``,t,n,r);return ra(i,e,t,n,r),i}function aa(e){let t=[],n=Vr(e,`check`),r=Hr(e,`check`);return n&&t.push({capability:`format`,scriptName:n}),r&&t.push({capability:`lint`,scriptName:r}),t}function oa(e,t){return t===`fix`?ia(e):aa(e)}var sa=class{id=`npm-scripts`;name=`package.json scripts`;capabilities=[`autofix`,`format`,`lint`];detect(e){let t=e.jsEnv?.packageJson?.scripts;if(!t||Object.keys(t).length===0)return{detected:!1,reason:`no scripts declared in package.json`};let n=oa(t,`fix`),r=oa(t,`check`),i=[...n,...r];return i.length>0?{detected:!0,reason:`found supported scripts: ${Array.from(new Set(i.map(e=>`"${e.scriptName}"`))).join(`, `)}`}:{detected:!1,reason:`no supported autofix or check scripts found in package.json`}}plan(e,t){let n=oa(e.jsEnv?.packageJson?.scripts||{},t),r=cn(e.jsPm||`npm`);return n.map(n=>({id:`script-${n.scriptName}`,toolId:this.id,capability:n.capability,capabilities:n.capabilities,executable:r,args:[`run`,n.scriptName],cwd:e.cwd,mode:t,description:`package.json script: ${r} run ${n.scriptName}`}))}};function ca(e){return e?Object.values(e).some(t=>Ui(t,e)):!1}function la(e,t){return e?!!(e.dependencies?.[t]||e.devDependencies?.[t]||e.peerDependencies?.[t]||e.optionalDependencies?.[t]):!1}function ua(e,t){for(let n of t)if(f.existsSync(p.join(e,n)))return!0;return!1}var da=class{id=`vite-plus`;name=`Vite+`;capabilities=[`autofix`,`format`,`lint`];detect(e){let t=e.jsEnv?.packageJson,n=sn(t),r=ua(e.cwd,[`vite.config.ts`,`vite.config.js`,`vite.config.mjs`,`vite.config.cjs`]);return n||r&&ca(t?.scripts)?{detected:!0,reason:`Vite+ dependency and/or configuration detected`}:{detected:!1,reason:`no Vite+ dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`vite-plus-fmt`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`format`,executable:`vp`,args:[`fmt`],cwd:e.cwd,mode:`fix`,description:`Vite+: vp fmt`},{id:`vite-plus-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`lint`,executable:`vp`,args:[`lint`,`--fix`],cwd:e.cwd,mode:`fix`,description:`Vite+: vp lint --fix`}]:[{id:`vite-plus-fmt-check`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`format`,executable:`vp`,args:[`fmt`,`--check`],cwd:e.cwd,mode:`check`,description:`Vite+: vp fmt --check`},{id:`vite-plus-lint`,toolId:this.id,toolName:this.name,requiredPackage:`vite-plus`,capability:`lint`,executable:`vp`,args:[`lint`],cwd:e.cwd,mode:`check`,description:`Vite+: vp lint`}]}},fa=class{id=`biome`;name=`Biome`;capabilities=[`format`,`lint`,`assist`];detect(e){let t=e.jsEnv?.packageJson,n=la(t,`@biomejs/biome`),r=ua(e.cwd,[`biome.json`,`biome.jsonc`]);return n&&r?{detected:!0,reason:`Biome dependency and configuration detected`}:r?{detected:!0,reason:`Biome configuration detected`}:n?{detected:!1,reason:`Biome dependency present without configuration file`}:{detected:!1,reason:`no Biome dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`biome-format-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`format`,executable:`biome`,args:[`format`,`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: format --write .`},{id:`biome-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`lint`,executable:`biome`,args:[`lint`,`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: lint --write .`},{id:`biome-assist-fix`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`assist`,executable:`biome`,args:[`check`,`--write`,`--formatter-enabled=false`,`--linter-enabled=false`,`.`],cwd:e.cwd,mode:`fix`,description:`Biome: safe assist actions (--write)`}]:[{id:`biome-format-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`format`,executable:`biome`,args:[`format`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: format .`},{id:`biome-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`lint`,executable:`biome`,args:[`lint`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: lint .`},{id:`biome-assist-check`,toolId:this.id,toolName:this.name,requiredPackage:`@biomejs/biome`,capability:`assist`,executable:`biome`,args:[`check`,`--formatter-enabled=false`,`--linter-enabled=false`,`.`],cwd:e.cwd,mode:`check`,description:`Biome: check assist actions`}]}},pa=class{id=`prettier`;name=`Prettier`;capabilities=[`format`];detect(e){let t=e.jsEnv?.packageJson,n=la(t,`prettier`),r=!!t?.prettier,i=ua(e.cwd,[`.prettierrc`,`.prettierrc.json`,`.prettierrc.yml`,`.prettierrc.yaml`,`.prettierrc.json5`,`.prettierrc.js`,`.prettierrc.cjs`,`.prettierrc.mjs`,`prettier.config.js`,`prettier.config.cjs`,`prettier.config.mjs`]);return n&&(r||i)?{detected:!0,reason:`Prettier dependency and configuration detected`}:r||i?{detected:!0,reason:`Prettier configuration detected`}:n?{detected:!1,reason:`Prettier dependency present without configuration`}:{detected:!1,reason:`no Prettier dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`prettier-format`,toolId:this.id,toolName:this.name,requiredPackage:`prettier`,capability:`format`,executable:`prettier`,args:[`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Prettier: format`}]:[{id:`prettier-check`,toolId:this.id,toolName:this.name,requiredPackage:`prettier`,capability:`format`,executable:`prettier`,args:[`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Prettier: format check`}]}},ma=class{id=`eslint`;name=`ESLint`;capabilities=[`lint`];detect(e){let t=e.jsEnv?.packageJson,n=la(t,`eslint`),r=!!t?.eslintConfig,i=ua(e.cwd,[`eslint.config.js`,`eslint.config.mjs`,`eslint.config.cjs`,`eslint.config.ts`,`.eslintrc`,`.eslintrc.js`,`.eslintrc.cjs`,`.eslintrc.json`,`.eslintrc.yaml`,`.eslintrc.yml`]);return n&&(r||i)?{detected:!0,reason:`ESLint dependency and configuration detected`}:r||i?{detected:!0,reason:`ESLint configuration detected`}:n?{detected:!1,reason:`ESLint dependency present without configuration`}:{detected:!1,reason:`no ESLint dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`eslint-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`eslint`,capability:`lint`,executable:`eslint`,args:[`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`ESLint: lint autofix`}]:[{id:`eslint-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`eslint`,capability:`lint`,executable:`eslint`,args:[`.`],cwd:e.cwd,mode:`check`,description:`ESLint: lint check`}]}},ha=class{id=`oxlint`;name=`Oxlint`;capabilities=[`lint`];detect(e){let t=e.jsEnv?.packageJson,n=la(t,`oxlint`),r=ua(e.cwd,[`.oxlintrc.json`,`oxlint.json`,`oxlint.config.ts`,`oxlint.config.js`]);return n&&r?{detected:!0,reason:`Oxlint dependency and configuration detected`}:r?{detected:!0,reason:`Oxlint configuration detected`}:n?{detected:!1,reason:`Oxlint dependency present without configuration`}:{detected:!1,reason:`no Oxlint dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`oxlint-lint-fix`,toolId:this.id,toolName:this.name,requiredPackage:`oxlint`,capability:`lint`,executable:`oxlint`,args:[`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`Oxlint: lint autofix`}]:[{id:`oxlint-lint-check`,toolId:this.id,toolName:this.name,requiredPackage:`oxlint`,capability:`lint`,executable:`oxlint`,args:[`.`],cwd:e.cwd,mode:`check`,description:`Oxlint: lint check`}]}},ga=class{id=`oxfmt`;name=`Oxfmt`;capabilities=[`format`];detect(e){let t=e.jsEnv?.packageJson,n=la(t,`oxfmt`),r=ua(e.cwd,[`.oxfmtrc.json`,`oxfmt.json`,`oxfmt.config.ts`,`oxfmt.config.js`]);return n&&r?{detected:!0,reason:`Oxfmt dependency and configuration detected`}:r?{detected:!0,reason:`Oxfmt configuration detected`}:n?{detected:!1,reason:`Oxfmt dependency present without configuration`}:{detected:!1,reason:`no Oxfmt dependency or configuration`}}plan(e,t){return t===`fix`?[{id:`oxfmt-format`,toolId:this.id,toolName:this.name,requiredPackage:`oxfmt`,capability:`format`,executable:`oxfmt`,args:[`--write`,`.`],cwd:e.cwd,mode:`fix`,description:`Oxfmt: format`}]:[{id:`oxfmt-check`,toolId:this.id,toolName:this.name,requiredPackage:`oxfmt`,capability:`format`,executable:`oxfmt`,args:[`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Oxfmt: format check`}]}},_a=class{id=`javascript`;name=`JavaScript/TypeScript`;scriptAdapter=new sa;vitePlusAdapter=new da;biomeAdapter=new fa;prettierAdapter=new pa;eslintAdapter=new ma;oxlintAdapter=new ha;oxfmtAdapter=new ga;detect(e){return!!e.jsEnv?.hasPackageJson}getAdapters(e){return[this.scriptAdapter,this.vitePlusAdapter,this.biomeAdapter,this.oxfmtAdapter,this.prettierAdapter,this.oxlintAdapter,this.eslintAdapter]}},va=class{id=`ruff`;name=`Ruff`;capabilities=[`lint`,`format`];detect(e){return e.hasPythonSource??or(e.cwd)?{detected:!0,reason:`default Python toolchain`}:{detected:!1,reason:`no Python source files detected`}}plan(e,t){let n=[];return t===`fix`?n.push({id:`ruff-lint-fix`,toolId:this.id,capability:`lint`,executable:`ruff`,args:[`check`,`--fix`,`.`],cwd:e.cwd,mode:`fix`,description:`Ruff: lint autofix`},{id:`ruff-format`,toolId:this.id,capability:`format`,executable:`ruff`,args:[`format`,`.`],cwd:e.cwd,mode:`fix`,description:`Ruff: format`}):n.push({id:`ruff-lint-check`,toolId:this.id,capability:`lint`,executable:`ruff`,args:[`check`,`.`],cwd:e.cwd,mode:`check`,description:`Ruff: lint check`},{id:`ruff-format-check`,toolId:this.id,capability:`format`,executable:`ruff`,args:[`format`,`--check`,`.`],cwd:e.cwd,mode:`check`,description:`Ruff: format check`}),n}},ya=class{id=`python`;name=`Python`;ruffAdapter=new va;detect(e){return e.hasPythonSource??or(e.cwd)}getAdapters(e){return[this.ruffAdapter]}},ba=class{providers=/* @__PURE__ */ new Map;standaloneAdapters=/* @__PURE__ */ new Map;registerProvider(e){this.providers.set(e.id,e)}getProvider(e){return this.providers.get(e)}getProviders(){return Array.from(this.providers.values())}registerAdapter(e){this.standaloneAdapters.set(e.id,e)}getAdapter(e){return this.standaloneAdapters.get(e)}getStandaloneAdapters(){return Array.from(this.standaloneAdapters.values())}clear(){this.providers.clear(),this.standaloneAdapters.clear()}};function xa(){let e=new ba;return e.registerProvider(new ya),e.registerProvider(new _a),e}const Sa=xa();async function Ca(e,t=process.cwd()){let n=`${e.command} ${e.args.join(` `)}`.trim();if(en(`Installing JS dependencies: ${n}`),await Yt(e.command,e.args,{cwd:t,ignoreReturnCode:!0})!==0)throw Error(`JS dependency installation failed executing "${n}". Check dependency constraints or lockfile compatibility.`)}async function wa(e=process.cwd()){let t=fn(e);if(!t.hasPackageJson){en(`No package.json found. Skipping scripts.`);return}if(t.packageJsonError)throw Error(t.packageJsonError);let n=cn(process.env.PACKAGE_MANAGER||_n(t).name),r=(await Lr(Sa,{cwd:e,jsEnv:t,jsPm:n,hasPythonSource:!1},`fix`)).tasks.filter(e=>e.toolId!==`ruff`);if(r.length===0){en(`No matching scripts (fix, format, lint, check, etc.) found in package.json.`);return}await Ca(vn(n),e);let i=(await new Ar().executeTasks(r)).filter(e=>e.error);if(i.length>0){let e=i.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` +`))}}async function Ta(e,t,n=process.cwd()){let r=`${e} ${t.join(` `)}`.trim();if(en(`Executing: ${r}`),await Yt(e,t,{cwd:n,ignoreReturnCode:!0})!==0)throw Error(`Python command "${r}" failed. Check Python environment and dependency configuration.`)}async function Ea(e,t=process.cwd()){Dr(e,`Invalid Python environment: "pmInfo" must be an object`);let n=e.name===`pip`?`pip`:e.name;if(!await Vt(n,!1)&&!e.isStandalone){let e=`Required Python package manager '${n}' is not available in PATH.`;throw Error(e)}if(!e.isStandalone&&e.installCommands.length>0){let n=e.installCommands;en(`Installing Python dependencies: ${n.map(e=>`${e.executable} ${e.args.join(` `)}`).join(` && `)}`);for(let{executable:e,args:r}of n)await Ta(e,r,t)}}async function Da(e=process.cwd(),t,n){if(!(t??or(e)))return;let r;if(n)r=n;else{if(!await Vt(`ruff`,!1))throw Error(`Required Python binary 'ruff' is not available in PATH.`);r=(await Lr(Sa,{cwd:e,hasPythonSource:!0},`fix`)).tasks.filter(e=>e.toolId===`ruff`)}let i=(await new Ar().executeTasks(r)).filter(e=>e.error);if(i.length>0){let e=i.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` +`))}}async function Oa(e,t){if(process.env.JS_SETUP_OUTCOME===`failure`||process.env.NODE_SETUP_OUTCOME===`failure`||process.env.BUN_SETUP_OUTCOME===`failure`||process.env.PNPM_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`JavaScript runtime or package manager setup failed.`);try{if(t){let n=fn(e);if(!n.hasPackageJson)return en(`No package.json found. Skipping scripts.`),null;if(n.packageJsonError)throw Error(n.packageJsonError);if(t.length===0)return en(`No matching scripts (fix, format, lint, check, etc.) found in package.json.`),null;await Ca(vn(cn(process.env.PACKAGE_MANAGER||_n(n).name)),e);let r=(await new Ar().executeTasks(t)).filter(e=>e.error);if(r.length>0){let e=r.map(e=>e.error?.message||`Task ${e.task.id} failed`);throw Error(e.join(` +`))}return null}return await wa(e),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function ka(e,t){if(process.env.PYTHON_SETUP_OUTCOME===`failure`||process.env.UV_SETUP_OUTCOME===`failure`||process.env.PY_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`Python runtime or package manager setup failed.`);let n=t&&`pmInfo`in t?t.pmInfo:Tr(e,void 0,{ctx:t?.ctx,hasSource:t?.hasSource});if(!n)return null;try{return await Ea(n,e),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function Aa(e,t,n){if(process.env.RUFF_SETUP_OUTCOME===`failure`)return/* @__PURE__ */ Error(`Ruff setup failed.`);try{return await Da(e,t,n),null}catch(e){return e instanceof Error?e:Error(String(e))}}async function ja(e,t,n){let r=t??or(e);if(!r)return[];let i=[],a=ir(e),o=await ka(e,{ctx:a,hasSource:!0,pmInfo:Tr(e,void 0,{ctx:a,hasSource:!0,isProject:!0})});o&&i.push(o);let s=await Aa(e,r,n);return s&&i.push(s),i}async function Ma(e=process.cwd()){let t=or(e),n;try{let r=fn(e),i={cwd:e,jsEnv:r,jsPm:cn(process.env.PACKAGE_MANAGER||_n(r).name),hasPythonSource:t};n=await Lr(Sa,i,`fix`),en(n.summary)}catch{}let r=[],i=n?n.tasks.filter(e=>e.toolId!==`ruff`):void 0,a=n?n.tasks.filter(e=>e.toolId===`ruff`):void 0,o=await Oa(e,i);o&&r.push(o);let s=await ja(e,t,a);r.push(...s),r.length>0&&(Zt(`Failed to run scripts: ${r.map(e=>e.message).join(` +`)}`),process.env.NODE_ENV!==`test`&&process.exit(1))}process.env.NODE_ENV!==`test`&&Or(import.meta.url)&&Ma();export{wa as executeJsWorkflow,Ea as executePythonWorkflow,Da as executeRuffAutofix,Ma as run,cn as sanitizePackageManager}; \ No newline at end of file diff --git a/src/autofix/adapters/javascript/script-adapter.ts b/src/autofix/adapters/javascript/script-adapter.ts index fa6e9bc0..af8581af 100644 --- a/src/autofix/adapters/javascript/script-adapter.ts +++ b/src/autofix/adapters/javascript/script-adapter.ts @@ -190,7 +190,7 @@ function splitChainedCommandsDetailed(shellText: string): SplitResult { return { commands, hasSyntaxError: state.hasSyntaxError }; } -function splitChainedCommands(shellText: string): string[] { +export function splitChainedCommands(shellText: string): string[] { return splitChainedCommandsDetailed(shellText).commands; } @@ -240,6 +240,7 @@ interface CommandInspection { tokens: string[]; hasUnsupportedSyntax: boolean; hasRedirection: boolean; + hasSyntaxError: boolean; } function isBackgroundOp(cmd: string, index: number, state: TokenizerState): boolean { @@ -343,6 +344,7 @@ function inspectAndTokenizeCommandLine(cmd: string): CommandInspection { tokens: [], hasUnsupportedSyntax: false, hasRedirection: false, + hasSyntaxError: false, }; const state: TokenizerState = { inSingleQuote: false, inDoubleQuote: false, isEscaped: false }; const ctx: TokenizeScanContext = { inspection, state }; @@ -368,6 +370,7 @@ function inspectAndTokenizeCommandLine(cmd: string): CommandInspection { if (state.inSingleQuote || state.inDoubleQuote || state.isEscaped) { inspection.hasUnsupportedSyntax = true; + inspection.hasSyntaxError = true; } if (hasCharsInWord) { @@ -377,7 +380,7 @@ function inspectAndTokenizeCommandLine(cmd: string): CommandInspection { return inspection; } -function tokenizeCommandLine(cmd: string): string[] { +export function tokenizeCommandLine(cmd: string): string[] { return inspectAndTokenizeCommandLine(cmd).tokens; } @@ -416,18 +419,33 @@ function matchesPmDelegation(cmdTokens: string[], targetScriptName: string): boo return false; } -export function parseCommandInvocations(scriptBody: string): string[][] { - if (!scriptBody) return []; - const commands = splitChainedCommands(scriptBody); +export interface ParsedCommandInvocations { + invocations: string[][]; + hasSyntaxError: boolean; +} + +export function parseCommandInvocationsDetailed(scriptBody: string): ParsedCommandInvocations { + if (!scriptBody) return { invocations: [], hasSyntaxError: false }; + const splitResult = splitChainedCommandsDetailed(scriptBody); + let hasSyntaxError = splitResult.hasSyntaxError; const invocations: string[][] = []; - for (const cmd of commands) { - const rawTokens = tokenizeCommandLine(cmd); - const cmdTokens = getCommandInvocationTokens(rawTokens); + + for (const cmd of splitResult.commands) { + const inspection = inspectAndTokenizeCommandLine(cmd); + if (inspection.hasSyntaxError) { + hasSyntaxError = true; + } + const cmdTokens = getCommandInvocationTokens(inspection.tokens); if (cmdTokens.length > 0) { invocations.push(cmdTokens); } } - return invocations; + + return { invocations, hasSyntaxError }; +} + +export function parseCommandInvocations(scriptBody: string): string[][] { + return parseCommandInvocationsDetailed(scriptBody).invocations; } /** @@ -742,13 +760,95 @@ function extractDelegatedTarget(tokens: string[]): string | undefined { return undefined; } -function extractShellWrapperScript(tokens: string[]): string | undefined { +export function extractShellWrapperScript(tokens: string[]): string | undefined { if (tokens.length >= 3 && (tokens[0] === "sh" || tokens[0] === "bash") && tokens[1] === "-c") { return tokens[2]; } return undefined; } +/** + * Unwraps environment wrapper commands like `cross-env` or `cross-env-shell`, + * stripping initial environment variable assignments to retrieve the target command tokens. + * In accordance with cross-env CLI syntax, cross-env accepts environment variable + * setters (`KEY=val`) followed by the command and its arguments. + */ +export function unwrapEnvironmentWrapper(tokens: string[]): string[] { + if (tokens.length === 0) return tokens; + const [exe] = tokens; + if (exe !== "cross-env" && exe !== "cross-env-shell") { + return tokens; + } + + let i = 1; + while (i < tokens.length && isEnvAssignment(tokens[i])) { + i++; + } + + return tokens.slice(i); +} + +/** + * Checks whether an invocation (or nested wrapped invocations) executes the `vp` command. + * Supports direct invocations, environment wrappers (cross-env), shell wrappers (sh -c, bash -c), + * package-manager execution wrappers (npx, bunx, pnpm exec, etc.), and script delegations. + */ +export function hasVitePlusCommand( + scriptBody: string, + scripts: Record = {}, + activePath: Set = new Set(), +): boolean { + if (!scriptBody) return false; + const { invocations, hasSyntaxError } = parseCommandInvocationsDetailed(scriptBody); + if (hasSyntaxError) { + return false; + } + + for (const rawTokens of invocations) { + const tokens = unwrapEnvironmentWrapper(rawTokens); + if (tokens.length === 0) continue; + + if (tokens[0] === "vp") { + return true; + } + + // Package-manager runner wrappers: npx vp, bunx vp, pnpm dlx vp, yarn dlx vp, npm exec vp, pnpm exec vp + if ((tokens[0] === "npx" || tokens[0] === "bunx") && tokens[1] === "vp") { + return true; + } + if ( + (tokens[0] === "npm" || tokens[0] === "pnpm" || tokens[0] === "yarn") && + (tokens[1] === "exec" || tokens[1] === "dlx") && + tokens[2] === "vp" + ) { + return true; + } + + // Shell wrappers: sh -c "...", bash -c "..." + const wrapped = extractShellWrapperScript(tokens); + if (wrapped !== undefined) { + if (hasVitePlusCommand(wrapped, scripts, activePath)) { + return true; + } + } + + // Package-manager script delegation + const target = extractDelegatedTarget(tokens); + if (target && !activePath.has(target) && scripts[target]) { + activePath.add(target); + try { + if (hasVitePlusCommand(scripts[target], scripts, activePath)) { + return true; + } + } finally { + activePath.delete(target); + } + } + } + + return false; +} + function isKnownWriteCommand(tokens: string[]): boolean { return ( isPrettierWrite(tokens) || diff --git a/src/autofix/adapters/javascript/tools.ts b/src/autofix/adapters/javascript/tools.ts index 69885a22..6645e141 100644 --- a/src/autofix/adapters/javascript/tools.ts +++ b/src/autofix/adapters/javascript/tools.ts @@ -11,6 +11,14 @@ import type { } from "../../types.js"; import { hasVitePlusDependency, type JsPackageManifest } from "../../../javascript-env.js"; +import { hasVitePlusCommand } from "./script-adapter.js"; + +function hasVitePlusScript(scripts?: Record): boolean { + if (!scripts) return false; + return Object.values(scripts).some((scriptBody) => { + return hasVitePlusCommand(scriptBody, scripts); + }); +} function hasDependency(pkg: JsPackageManifest | undefined, depName: string): boolean { if (!pkg) return false; @@ -47,10 +55,7 @@ export class VitePlusAdapter implements ToolAdapter { "vite.config.cjs", ]); - if ( - hasDep || - (hasConfig && pkg?.scripts && Object.values(pkg.scripts).some((s) => s.includes("vp "))) - ) { + if (hasDep || (hasConfig && hasVitePlusScript(pkg?.scripts))) { return { detected: true, reason: "Vite+ dependency and/or configuration detected" }; } return { detected: false, reason: "no Vite+ dependency or configuration" }; diff --git a/src/autofix/planner.ts b/src/autofix/planner.ts index bc379347..579d5388 100644 --- a/src/autofix/planner.ts +++ b/src/autofix/planner.ts @@ -14,6 +14,7 @@ import { hasPythonSource } from "../python-env.js"; interface CandidateTool { adapter: ToolAdapter; reason?: string; + scope: string; } function isTaskCapabilityCovered( @@ -54,9 +55,21 @@ function filterCandidateTasks( return { kept, skipped }; } +function getOrCreateScopeCapabilities( + coveredCapabilitiesByScope: Map>, + scope: string, +): Set { + let caps = coveredCapabilitiesByScope.get(scope); + if (!caps) { + caps = new Set(); + coveredCapabilitiesByScope.set(scope, caps); + } + return caps; +} + async function processCandidateTools( candidateTools: CandidateTool[], - providerCapabilities: Set, + coveredCapabilitiesByScope: Map>, context: ProjectContext, mode: ExecutionMode, selectedTools: Array<{ id: string; name: string; reason?: string }>, @@ -65,13 +78,17 @@ async function processCandidateTools( ): Promise { for (const candidate of candidateTools) { const adapter = candidate.adapter; + const scopeCapabilities = getOrCreateScopeCapabilities( + coveredCapabilitiesByScope, + candidate.scope, + ); const plannedTasks = await adapter.plan(context, mode); if (plannedTasks.length === 0) { continue; } - const { kept, skipped } = filterCandidateTasks(plannedTasks, providerCapabilities); + const { kept, skipped } = filterCandidateTasks(plannedTasks, scopeCapabilities); if (kept.length === 0) { skippedTools.push({ @@ -84,10 +101,10 @@ async function processCandidateTools( selectedTools.push({ id: adapter.id, name: adapter.name, reason: candidate.reason }); for (const task of kept) { - providerCapabilities.add(task.capability); + scopeCapabilities.add(task.capability); if (task.capabilities) { for (const cap of task.capabilities) { - providerCapabilities.add(cap); + scopeCapabilities.add(cap); } } } @@ -106,19 +123,33 @@ async function processCandidateTools( async function processStandaloneAdapters( registry: AutofixRegistry, + coveredCapabilitiesByScope: Map>, context: ProjectContext, mode: ExecutionMode, selectedTools: Array<{ id: string; name: string; reason?: string }>, + skippedTools: Array<{ id: string; name: string; reason: string }>, tasks: ExecutionTask[], ): Promise { + const candidateTools: CandidateTool[] = []; for (const adapter of registry.getStandaloneAdapters()) { const result = await adapter.detect(context); + const scope = adapter.scope ?? `standalone:${adapter.id}`; if (result.detected) { - selectedTools.push({ id: adapter.id, name: adapter.name, reason: result.reason }); - const toolTasks = await adapter.plan(context, mode); - tasks.push(...toolTasks); + candidateTools.push({ adapter, reason: result.reason, scope }); + } else if (result.reason && !result.reason.startsWith("no ")) { + skippedTools.push({ id: adapter.id, name: adapter.name, reason: result.reason }); } } + + await processCandidateTools( + candidateTools, + coveredCapabilitiesByScope, + context, + mode, + selectedTools, + skippedTools, + tasks, + ); } function deduplicateTasks(tasks: ExecutionTask[]): ExecutionTask[] { @@ -161,7 +192,7 @@ export async function planExecution( const tasks: ExecutionTask[] = []; const providers = registry.getProviders(); - const coveredCapabilities = new Map>(); + const coveredCapabilitiesByScope = new Map>(); for (const provider of providers) { const isDetected = await provider.detect(effectiveContext); @@ -169,17 +200,15 @@ export async function planExecution( continue; } - const providerCapabilities = new Set(); - coveredCapabilities.set(provider.id, providerCapabilities); - const adapters = provider.getAdapters(effectiveContext); const candidateTools: CandidateTool[] = []; // Detect tools for (const adapter of adapters) { const result = await adapter.detect(effectiveContext); + const scope = adapter.scope ?? provider.id; if (result.detected) { - candidateTools.push({ adapter, reason: result.reason }); + candidateTools.push({ adapter, reason: result.reason, scope }); } else if (result.reason && !result.reason.startsWith("no ")) { skippedTools.push({ id: adapter.id, name: adapter.name, reason: result.reason }); } @@ -187,7 +216,7 @@ export async function planExecution( await processCandidateTools( candidateTools, - providerCapabilities, + coveredCapabilitiesByScope, effectiveContext, mode, selectedTools, @@ -196,7 +225,15 @@ export async function planExecution( ); } - await processStandaloneAdapters(registry, effectiveContext, mode, selectedTools, tasks); + await processStandaloneAdapters( + registry, + coveredCapabilitiesByScope, + effectiveContext, + mode, + selectedTools, + skippedTools, + tasks, + ); const deduplicatedTasks = deduplicateTasks(tasks); diff --git a/src/autofix/types.ts b/src/autofix/types.ts index 5dd7ae71..91958836 100644 --- a/src/autofix/types.ts +++ b/src/autofix/types.ts @@ -38,6 +38,14 @@ export interface ToolAdapter { id: string; name: string; capabilities: readonly Capability[]; + /** + * Conflict domain scope for capability deduplication and conflict resolution. + * - For provider adapters, defaults to `provider.id` (e.g. "javascript", "python"). + * - For standalone adapters, can explicitly match a provider id (e.g. "javascript") + * or another shared scope name to resolve genuine conflicts within that domain. + * When omitted, defaults to an isolated per-adapter scope (`standalone:${id}`). + */ + scope?: string; detect(context: ProjectContext): Promise | AdapterDetectionResult; diff --git a/tests/autofix-architecture.test.ts b/tests/autofix-architecture.test.ts index 862e9605..a6ed04cc 100644 --- a/tests/autofix-architecture.test.ts +++ b/tests/autofix-architecture.test.ts @@ -1668,6 +1668,707 @@ describe("Refactored Autofix Architecture & Tool Adapters", () => { }); }); + describe("Vite+ Detection & False-Positive Prevention", () => { + it("detects Vite+ when package script invokes vp command directly", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + fmt: "vp fmt", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + // Script fmt runs, and VitePlusAdapter is detected (and fmt script covers format) + expect(plan.selectedTools.map((t) => t.id)).toContain("vite-plus"); + }); + + it("detects Vite+ when package script invokes vp in chained command or with env prefix", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + build: "echo ready && vp build", + check: "NODE_ENV=production vp check", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.selectedTools.map((t) => t.id)).toContain("vite-plus"); + expect(plan.tasks.map((t) => t.id)).toContain("vite-plus-fmt"); + expect(plan.tasks.map((t) => t.id)).toContain("vite-plus-lint-fix"); + }); + + it("detects Vite+ through command wrappers: cross-env, sh -c, bash -c, and runner wrappers", async () => { + // 1. cross-env + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + format: "cross-env NODE_ENV=production vp fmt", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const planCrossEnv = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnv.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 2. cross-env-shell + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + format: "cross-env-shell NODE_ENV=production vp fmt", + }, + }), + ); + const planCrossEnvShell = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvShell.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 3. cross-env with multiple environment variable assignments + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + formatMultipleEnv: "cross-env CI=true NODE_ENV=production vp lint", + }, + }), + ); + const planCrossEnvMultiple = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvMultiple.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 4. cross-env delegating to a script that invokes vp + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + formatDelegated: "cross-env NODE_ENV=production npm run format:vp", + "format:vp": "vp fmt", + }, + }), + ); + const planCrossEnvDelegated = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvDelegated.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 5. unsupported options or flags must not be treated as valid and must not cause false-positive Vite+ detection + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + formatShort: "cross-env -f .env vp fmt", + formatLong: "cross-env --file .env vp fmt", + formatUnknown: "cross-env -unknown-flag vp fmt", + }, + }), + ); + const planCrossEnvUnknown = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvUnknown.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 6. cross-env with missing command after environment assignments + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + noCommand: "cross-env NODE_ENV=production", + }, + }), + ); + const planCrossEnvNoCommand = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvNoCommand.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 7. cross-env running an unrelated command with vp in arguments must not trigger Vite+ detection + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + grepTest: "cross-env NODE_ENV=production grep vp file.txt", + }, + }), + ); + const planCrossEnvGrep = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planCrossEnvGrep.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 5. sh -c + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + check: "sh -c 'vp check'", + }, + }), + ); + const planSh = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planSh.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 6. bash -c + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + fmt: "bash -c 'vp fmt'", + }, + }), + ); + const planBash = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planBash.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 7. npx vp / bunx vp / pnpm exec vp + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + fmt1: "npx vp fmt", + fmt2: "bunx vp fmt", + fmt3: "pnpm exec vp fmt", + }, + }), + ); + const planRunners = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planRunners.selectedTools.map((t) => t.id)).toContain("vite-plus"); + + // 8. script delegation invoking vp + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + all: "npm run vp-task", + "vp-task": "vp fmt", + }, + }), + ); + const planDelegation = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planDelegation.selectedTools.map((t) => t.id)).toContain("vite-plus"); + }); + + it("prevents false positive when 'vp ' only appears inside echo or descriptive string", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + help: 'echo "vp is not installed"', + info: 'echo "run vp fmt later"', + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + expect(plan.tasks.map((t) => t.id)).not.toContain("vite-plus-fmt"); + expect(plan.tasks.map((t) => t.id)).not.toContain("vite-plus-lint-fix"); + }); + + it("prevents false positive when 'vp ' only appears in a comment", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + test: "node test.js # vp fmt", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + }); + + it("prevents false positive when 'vp' appears as an argument to an unrelated command", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + search: "grep vp file.txt", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + }); + + it("prevents false positive when project has vite.config.ts but scripts do not invoke vp", async () => { + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + build: "vite build", + test: "vitest run", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + }); + + it("rejects malformed commands with confirmed shell syntax errors during Vite+ detection", async () => { + // 1. Unterminated single quote + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken1: "vp fmt 'unterminated", + }, + }), + ); + fs.writeFileSync(path.join(tempDir, "vite.config.ts"), "export default {};"); + + const registry = createDefaultRegistry(); + const planSingle = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planSingle.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 2. Unterminated double quote + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken2: 'vp fmt "unterminated', + }, + }), + ); + const planDouble = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planDouble.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 3. Command ending with an incomplete trailing escape \ + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken3: "vp fmt \\", + }, + }), + ); + const planEscape = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planEscape.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 4. Malformed nested shell-wrapper invocation (unterminated quote in sh -c argument) + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken4: "sh -c 'vp check", + }, + }), + ); + const planMalformedSh = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planMalformedSh.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 5. Malformed nested shell-wrapper invocation (unterminated quote inside wrapped script) + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken5: 'bash -c "vp fmt \'unterminated"', + }, + }), + ); + const planMalformedBash = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planMalformedBash.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + + // 6. Malformed command containing vp in argument position + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + broken6: "grep 'unterminated vp", + }, + }), + ); + const planGrepMalformed = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(planGrepMalformed.selectedTools.map((t) => t.id)).not.toContain("vite-plus"); + }); + }); + + describe("Multi-Language Monorepo Capability Isolation", () => { + it("preserves independent tasks for Python (Ruff) and JavaScript (Prettier/ESLint) without mutual suppression", async () => { + // Python source file + fs.writeFileSync(path.join(tempDir, "app.py"), "print('hello')\n"); + + // JS configuration with prettier and eslint + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + "format:write": "prettier --write .", + "lint:fix": "eslint --fix .", + }, + }), + ); + + const registry = createDefaultRegistry(); + const plan = await planExecution(registry, { cwd: tempDir, hasPythonSource: true }, "fix"); + + // Verify Python tasks are scheduled (Ruff lint and format) + expect(plan.tasks.some((t) => t.id === "ruff-lint-fix")).toBe(true); + expect(plan.tasks.some((t) => t.id === "ruff-format")).toBe(true); + + // Verify JavaScript tasks are NOT suppressed by Python's lint/format capabilities + expect(plan.tasks.some((t) => t.id === "script-format:write")).toBe(true); + expect(plan.tasks.some((t) => t.id === "script-lint:fix")).toBe(true); + + // Verify plan contains all 4 tasks + expect(plan.tasks.map((t) => t.id)).toEqual([ + "ruff-lint-fix", + "ruff-format", + "script-format:write", + "script-lint:fix", + ]); + + // Summary contains both Python and JavaScript toolchains + expect(plan.summary).toContain("Python toolchain:"); + expect(plan.summary).toContain("Ruff: default tool"); + expect(plan.summary).toContain("JavaScript toolchain:"); + }); + }); + + describe("Standalone Adapter Conflict Resolution", () => { + it("skips a standalone adapter task when its capability is already covered by a regular adapter in the same scope", async () => { + const registry = createDefaultRegistry(); + + class StandaloneFormatAdapter implements ToolAdapter { + id = "standalone-fmt"; + name = "Standalone Formatter"; + capabilities: ["format"] = ["format"]; + scope = "javascript"; + detect() { + return { detected: true, reason: "standalone formatter config detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "standalone-fmt-task", + toolId: this.id, + capability: "format", + executable: "standalone-fmt", + args: ["."], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + registry.registerAdapter(new StandaloneFormatAdapter()); + + // Configure explicit script covering format + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + "format:write": "prettier --write .", + }, + }), + ); + + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.tasks.map((t) => t.id)).toEqual(["script-format:write"]); + expect(plan.skippedTools).toContainEqual( + expect.objectContaining({ + id: "standalone-fmt", + reason: "all capabilities already covered by higher priority tool", + }), + ); + }); + + it("skips a lower-priority adapter when a higher-priority standalone adapter ran first and covered the capability in the same scope", async () => { + const customRegistry = new AutofixRegistry(); + + class StandaloneFormatAdapter implements ToolAdapter { + id = "standalone-fmt"; + name = "Standalone Formatter"; + capabilities: ["format"] = ["format"]; + scope = "custom-format-scope"; + detect() { + return { detected: true, reason: "standalone formatter config detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "standalone-fmt-task", + toolId: this.id, + capability: "format", + executable: "standalone-fmt", + args: ["."], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + // Test two standalone adapters sharing the same scope where first standalone adapter conflicts with second standalone adapter: + class SecondStandaloneFormatAdapter implements ToolAdapter { + id = "second-fmt"; + name = "Second Formatter"; + capabilities: ["format"] = ["format"]; + scope = "custom-format-scope"; + detect() { + return { detected: true, reason: "second formatter config detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "second-fmt-task", + toolId: this.id, + capability: "format", + executable: "second-fmt", + args: ["."], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + customRegistry.registerAdapter(new StandaloneFormatAdapter()); + customRegistry.registerAdapter(new SecondStandaloneFormatAdapter()); + + const plan = await planExecution(customRegistry, { cwd: tempDir }, "fix"); + expect(plan.tasks.map((t) => t.id)).toEqual(["standalone-fmt-task"]); + expect(plan.skippedTools).toContainEqual( + expect.objectContaining({ + id: "second-fmt", + reason: "all capabilities already covered by higher priority tool", + }), + ); + }); + + it("retains tasks when standalone and regular adapters produce independent capabilities", async () => { + const registry = createDefaultRegistry(); + + class StandaloneTypecheckAdapter implements ToolAdapter { + id = "standalone-typecheck"; + name = "Standalone Typechecker"; + capabilities: ["typecheck"] = ["typecheck"]; + detect() { + return { detected: true, reason: "typecheck config detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "standalone-typecheck-task", + toolId: this.id, + capability: "typecheck", + executable: "tsc", + args: ["--noEmit"], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + registry.registerAdapter(new StandaloneTypecheckAdapter()); + + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + "format:write": "prettier --write .", + }, + }), + ); + + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.tasks.map((t) => t.id)).toEqual([ + "script-format:write", + "standalone-typecheck-task", + ]); + expect(plan.selectedTools.map((t) => t.id)).toContain("standalone-typecheck"); + }); + + it("partially skips overlapping capabilities between standalone and regular adapters in the same scope", async () => { + const registry = createDefaultRegistry(); + + class StandaloneMultiToolAdapter implements ToolAdapter { + id = "standalone-multi"; + name = "Standalone Multi Tool"; + capabilities: ["format", "assist"] = ["format", "assist"]; + scope = "javascript"; + detect() { + return { detected: true, reason: "multi tool config detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "standalone-multi-fmt", + toolId: this.id, + capability: "format", + executable: "standalone-multi", + args: ["fmt"], + cwd: ctx.cwd, + mode, + }, + { + id: "standalone-multi-assist", + toolId: this.id, + capability: "assist", + executable: "standalone-multi", + args: ["assist"], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + registry.registerAdapter(new StandaloneMultiToolAdapter()); + + fs.writeFileSync( + path.join(tempDir, "package.json"), + JSON.stringify({ + scripts: { + "format:write": "prettier --write .", + }, + }), + ); + + const plan = await planExecution(registry, { cwd: tempDir }, "fix"); + expect(plan.tasks.map((t) => t.id)).toEqual([ + "script-format:write", + "standalone-multi-assist", + ]); + expect(plan.skippedTools).toContainEqual( + expect.objectContaining({ + id: "standalone-multi", + reason: "partially skipped (format covered by higher priority tool)", + }), + ); + }); + + it("preserves task ID deduplication behavior", async () => { + const customRegistry = new AutofixRegistry(); + + class StandaloneAdapter1 implements ToolAdapter { + id = "adapter-1"; + name = "Adapter 1"; + capabilities: ["typecheck"] = ["typecheck"]; + detect() { + return { detected: true, reason: "adapter 1 detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "shared-task-id", + toolId: this.id, + capability: "typecheck", + executable: "check-1", + args: [], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + class StandaloneAdapter2 implements ToolAdapter { + id = "adapter-2"; + name = "Adapter 2"; + capabilities: ["assist"] = ["assist"]; + detect() { + return { detected: true, reason: "adapter 2 detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "shared-task-id", + toolId: this.id, + capability: "assist", + executable: "check-2", + args: [], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + customRegistry.registerAdapter(new StandaloneAdapter1()); + customRegistry.registerAdapter(new StandaloneAdapter2()); + + const plan = await planExecution(customRegistry, { cwd: tempDir }, "fix"); + // Even though capabilities are independent (typecheck vs assist), task IDs are identical, so second is deduplicated + expect(plan.tasks).toHaveLength(1); + expect(plan.tasks[0].executable).toBe("check-1"); + }); + + it("ensures standalone adapters without explicit scope do not conflict with each other or provider adapters", async () => { + const customRegistry = new AutofixRegistry(); + + // Standalone format adapter with default scope + class StandaloneDefault1 implements ToolAdapter { + id = "default-standalone-1"; + name = "Default Standalone 1"; + capabilities: ["format"] = ["format"]; + detect() { + return { detected: true, reason: "default standalone 1 detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "default-standalone-1-fmt", + toolId: this.id, + capability: "format", + executable: "fmt-1", + args: [], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + // Standalone format adapter with default scope + class StandaloneDefault2 implements ToolAdapter { + id = "default-standalone-2"; + name = "Default Standalone 2"; + capabilities: ["format"] = ["format"]; + detect() { + return { detected: true, reason: "default standalone 2 detected" }; + } + plan(ctx: ProjectContext, mode: ExecutionMode): ExecutionTask[] { + return [ + { + id: "default-standalone-2-fmt", + toolId: this.id, + capability: "format", + executable: "fmt-2", + args: [], + cwd: ctx.cwd, + mode, + }, + ]; + } + } + + customRegistry.registerAdapter(new StandaloneDefault1()); + customRegistry.registerAdapter(new StandaloneDefault2()); + + const plan = await planExecution(customRegistry, { cwd: tempDir }, "fix"); + // Both default standalone adapters have independent default scopes (standalone:default-standalone-1 and standalone:default-standalone-2), + // so neither suppresses the other! + expect(plan.tasks.map((t) => t.id)).toEqual([ + "default-standalone-1-fmt", + "default-standalone-2-fmt", + ]); + }); + }); + describe("Registry Extensibility", () => { it("resolves project binaries and Windows executable shims cleanly", async () => { const binDir = path.join(tempDir, "node_modules", ".bin");