@kitschpatrol/shared-config 8.11.0 → 8.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{cosmiconfig as e}from"cosmiconfig";import{TypeScriptLoader as t}from"cosmiconfig-typescript-loader";import{execa as n}from"execa";import r from"fs-extra";import i,{constants as a}from"node:fs";import{availableParallelism as o}from"node:os";import s from"node:path";import{PassThrough as c,Transform as l}from"node:stream";import{packageUp as u,packageUpSync as d}from"package-up";import f from"yargs";import{hideBin as p}from"yargs/helpers";import m from"picocolors";import{stripVTControlCharacters as h}from"node:util";import g from"@pinojs/json-colorizer";import ee from"decircular";import te from"deepmerge";import _ from"json-stringify-pretty-compact";import{findWorkspaces as v,findWorkspacesRoot as ne}from"find-workspaces";import y,{access as re}from"node:fs/promises";import{getDefaultConfigLoader as b,resolveConfigFileImports as ie}from"cspell-lib";import{fileURLToPath as x}from"node:url";import{createReaderWriter as ae}from"cspell-config-lib";import{loadFile as oe,writeFile as se}from"magicast";import{getDefaultExportOptions as ce}from"magicast/helpers";import{lint as le}from"cspell";import"deepmerge-ts";import{loadConfig as ue}from"mdat";import{globby as de}from"globby";import S,{gt as fe,minVersion as pe}from"semver";import{readWantedLockfile as me}from"@pnpm/lockfile.fs";import he from"stylelint";var ge=`8.11.0`;const _e=[`json`,`machine`,`native`],ve=_e;function ye(e){return typeof e==`string`&&_e.includes(e)}function C(){let e=process.env.KSC_FORMAT;return ye(e)?e:`native`}function be(){let{argv:e}=process;for(let[t,n]of e.entries()){let r;n===`--format`?r=e[t+1]:n.startsWith(`--format=`)&&(r=n.slice(9)),ye(r)&&(process.env.KSC_FORMAT=r)}}function xe(){let{env:e}=process;return e.NO_COLOR!==void 0&&e.NO_COLOR!==``?!1:e.FORCE_COLOR===void 0?e.TERM!==`dumb`&&void 0:e.FORCE_COLOR!==`0`&&e.FORCE_COLOR!==`false`}function w(e,{ciColor:t=!0}={}){return xe()??(e.isTTY===!0||t&&!!process.env.CI)}function Se(){return C()===`json`?process.stderr:process.stdout}function T(){return C()===`machine`?m.createColors(!1):m.createColors(w(Se()))}function Ce(e){if(!e)return{FORCE_COLOR:`0`,NO_COLOR:`1`};let t=process.env.FORCE_COLOR;return t===void 0||t===``?{FORCE_COLOR:`1`}:{}}function E(e,t){let n=s.isAbsolute(e)?e:s.resolve(t,e),r=s.relative(process.cwd(),n);return r===``?`.`:r}const we=/\s*\n\s*/gv,Te=/\r?\n/v;function Ee(e){let t=e.message.replaceAll(we,` `),n=e.rule===void 0?e.tool:`${e.tool}/${e.rule}`;return e.file===void 0?`${e.severity}: ${t} [${n}]`:`${e.file}:${e.line??1}:${e.column??1}: ${e.severity}: ${t} [${n}]`}function De(e,t){let n=(e.file??`￿`).localeCompare(t.file??`￿`);if(n!==0)return n;let r=(e.line??0)-(t.line??0);return r===0?(e.column??0)-(t.column??0):r}function Oe(e,t){let n=t.toSorted(De);return{cwd:process.cwd(),diagnostics:n,success:e.every(e=>e.exitCode===0),summary:{errors:n.filter(e=>e.severity===`error`).length,infos:n.filter(e=>e.severity===`info`).length,warnings:n.filter(e=>e.severity===`warning`).length},tools:e,version:1}}function D(e){return h(e).split(Te).map(e=>e.trimEnd()).filter(e=>e.trim().length>0)}function ke(e){return e instanceof Error&&`exitCode`in e&&typeof e.exitCode==`number`}function O(e){return w(process.stdout,{ciColor:!1})?je(e):Ae(e)}function Ae(e){return _(ee(e),{indent:2,replacer(e,t){return typeof t==`function`?t.name:t}})}function je(e){return g(Ae(e),{colors:{BRACKET:`gray`}})}const Me=(e,t,n)=>{let r=[...e];for(let[i,a]of t.entries())r[i]===void 0?r[i]=n.cloneUnlessOtherwiseSpecified(a,n):n.isMergeableObject(a)?r[i]=k(e[i],a,n):e.includes(a)||r.push(a);return r};function k(e,t,n={arrayMerge:Me}){return te(e,t,n)}function Ne(e){let{tasks:t}=e;return Array.isArray(t)?t:[]}function Pe(e,t){let n=[...Ne(e)];for(let e of Ne(t)){let t=n.findIndex(t=>t.label!==void 0&&t.label===e.label);t===-1?n.push(e):n[t]=e}return{...e,...t,tasks:n}}function Fe(e,t){return e.startsWith(t+s.sep)}function A(){let e=j(),t=/* @__PURE__ */ new Set([e]),n=v();if(n!==null)for(let r of n){let n=s.resolve(r.location);Fe(n,e)&&t.add(n)}return[...t]}function j(){let e=d();if(e===void 0)throw Error(`No package.json found.`);return s.dirname(e)}function Ie(){return ne()!==null}function M(){let e=ne();return e===null?j():s.resolve(e.location)}function Le(e){let t=s.join(M(),e);return r.existsSync(t)?t:void 0}function Re(e){if(e===`workspace-root`)return M();if(e===`package-dir`)return j();if(typeof e==`string`){if(!r.pathExistsSync(e))throw Error(`Custom cwd directory does not exist: ${e}`);return e}return process.cwd()}async function ze(e,t){try{let{default:n}=await import(`prettier`),r=await n.resolveConfig(e),i=await n.format(t,{filepath:e,...r});await y.writeFile(e,i,`utf8`)}catch{console.warn(`Skipped formatting ${e} since Prettier is not installed.`)}}async function N(e){try{await ze(e,await y.readFile(e,`utf8`))}catch{}}const Be=/\r?\n/v;function Ve(e){let t=``,n=t=>t.filter(t=>{let n=h(t);return n.trim()!==``&&!e(n)}).map(e=>`${e}\n`).join(``);return new l({flush(e){e(void 0,n([t]))},transform(e,r,i){let a=(t+e.toString()).split(Be);t=a.pop()??``,i(void 0,n(a))}})}function P(e,t){let n=C()===`native`?e:void 0,r=n===void 0||n===``?``:t===void 0?n:T()[t](n),i=``,a=e=>e.filter(e=>e.trim().length>0).map(e=>r===``?`${e}\n`:`${r} ${e}\n`).join(``);return new l({flush(e){e(void 0,a([i]))},transform(e,t,n){let r=(i+e.toString()).split(Be);i=r.pop()??``,n(void 0,a(r))}})}async function He(e){let t=[];return new Promise((n,r)=>{e.on(`data`,e=>{t.push(e)}),e.on(`error`,e=>{r(e)}),e.on(`end`,()=>{n(Buffer.concat(t).toString(`utf8`))})})}function Ue(e){return e.replaceAll(/[A-Z\u{C0}-\u{D6}\u{D8}-\u{DE}]/gv,e=>`-`+e.toLowerCase())}function F(e,t){return t===1?e:e+`s`}async function I(e){return typeof e==`function`?e():e}async function We(e,t,n,r,i){let a;if(r.logPrefix===void 0)a=e;else{let t=P(r.logPrefix,r.logColor);t.pipe(e),a=t}i&&a.write(T().bold(`Running: "${r.name}() with Positional arguments: ${String(t)} and Option flags: ${String(n)}"`)+`
2
+ import{cosmiconfig as e}from"cosmiconfig";import{TypeScriptLoader as t}from"cosmiconfig-typescript-loader";import{execa as n}from"execa";import r from"fs-extra";import i,{constants as a}from"node:fs";import{availableParallelism as o}from"node:os";import s from"node:path";import{PassThrough as c,Transform as l}from"node:stream";import{packageUp as u,packageUpSync as d}from"package-up";import f from"yargs";import{hideBin as p}from"yargs/helpers";import m from"picocolors";import{stripVTControlCharacters as h}from"node:util";import g from"@pinojs/json-colorizer";import ee from"decircular";import te from"deepmerge";import _ from"json-stringify-pretty-compact";import{findWorkspaces as v,findWorkspacesRoot as ne}from"find-workspaces";import y,{access as re}from"node:fs/promises";import{getDefaultConfigLoader as b,resolveConfigFileImports as ie}from"cspell-lib";import{fileURLToPath as x}from"node:url";import{createReaderWriter as ae}from"cspell-config-lib";import{loadFile as oe,writeFile as se}from"magicast";import{getDefaultExportOptions as ce}from"magicast/helpers";import{lint as le}from"cspell";import"deepmerge-ts";import{loadConfig as ue}from"mdat";import{globby as de}from"globby";import S,{gt as fe,minVersion as pe}from"semver";import{readWantedLockfile as me}from"@pnpm/lockfile.fs";import he from"stylelint";var ge=`8.12.1`;const _e=[`json`,`machine`,`native`],ve=_e;function ye(e){return typeof e==`string`&&_e.includes(e)}function C(){let e=process.env.KSC_FORMAT;return ye(e)?e:`native`}function be(){let{argv:e}=process;for(let[t,n]of e.entries()){let r;n===`--format`?r=e[t+1]:n.startsWith(`--format=`)&&(r=n.slice(9)),ye(r)&&(process.env.KSC_FORMAT=r)}}function xe(){let{env:e}=process;return e.NO_COLOR!==void 0&&e.NO_COLOR!==``?!1:e.FORCE_COLOR===void 0?e.TERM!==`dumb`&&void 0:e.FORCE_COLOR!==`0`&&e.FORCE_COLOR!==`false`}function w(e,{ciColor:t=!0}={}){return xe()??(e.isTTY===!0||t&&!!process.env.CI)}function Se(){return C()===`json`?process.stderr:process.stdout}function T(){return C()===`machine`?m.createColors(!1):m.createColors(w(Se()))}function Ce(e){if(!e)return{FORCE_COLOR:`0`,NO_COLOR:`1`};let t=process.env.FORCE_COLOR;return t===void 0||t===``?{FORCE_COLOR:`1`}:{}}function E(e,t){let n=s.isAbsolute(e)?e:s.resolve(t,e),r=s.relative(process.cwd(),n);return r===``?`.`:r}const we=/\s*\n\s*/gv,Te=/\r?\n/v;function Ee(e){let t=e.message.replaceAll(we,` `),n=e.rule===void 0?e.tool:`${e.tool}/${e.rule}`;return e.file===void 0?`${e.severity}: ${t} [${n}]`:`${e.file}:${e.line??1}:${e.column??1}: ${e.severity}: ${t} [${n}]`}function De(e,t){let n=(e.file??`￿`).localeCompare(t.file??`￿`);if(n!==0)return n;let r=(e.line??0)-(t.line??0);return r===0?(e.column??0)-(t.column??0):r}function Oe(e,t){let n=t.toSorted(De);return{cwd:process.cwd(),diagnostics:n,success:e.every(e=>e.exitCode===0),summary:{errors:n.filter(e=>e.severity===`error`).length,infos:n.filter(e=>e.severity===`info`).length,warnings:n.filter(e=>e.severity===`warning`).length},tools:e,version:1}}function D(e){return h(e).split(Te).map(e=>e.trimEnd()).filter(e=>e.trim().length>0)}function ke(e){return e instanceof Error&&`exitCode`in e&&typeof e.exitCode==`number`}function O(e){return w(process.stdout,{ciColor:!1})?je(e):Ae(e)}function Ae(e){return _(ee(e),{indent:2,replacer(e,t){return typeof t==`function`?t.name:t}})}function je(e){return g(Ae(e),{colors:{BRACKET:`gray`}})}const Me=(e,t,n)=>{let r=[...e];for(let[i,a]of t.entries())r[i]===void 0?r[i]=n.cloneUnlessOtherwiseSpecified(a,n):n.isMergeableObject(a)?r[i]=k(e[i],a,n):e.includes(a)||r.push(a);return r};function k(e,t,n={arrayMerge:Me}){return te(e,t,n)}function Ne(e){let{tasks:t}=e;return Array.isArray(t)?t:[]}function Pe(e,t){let n=[...Ne(e)];for(let e of Ne(t)){let t=n.findIndex(t=>t.label!==void 0&&t.label===e.label);t===-1?n.push(e):n[t]=e}return{...e,...t,tasks:n}}function Fe(e,t){return e.startsWith(t+s.sep)}function A(){let e=j(),t=/* @__PURE__ */ new Set([e]),n=v();if(n!==null)for(let r of n){let n=s.resolve(r.location);Fe(n,e)&&t.add(n)}return[...t]}function j(){let e=d();if(e===void 0)throw Error(`No package.json found.`);return s.dirname(e)}function Ie(){return ne()!==null}function M(){let e=ne();return e===null?j():s.resolve(e.location)}function Le(e){let t=s.join(M(),e);return r.existsSync(t)?t:void 0}function Re(e){if(e===`workspace-root`)return M();if(e===`package-dir`)return j();if(typeof e==`string`){if(!r.pathExistsSync(e))throw Error(`Custom cwd directory does not exist: ${e}`);return e}return process.cwd()}async function ze(e,t){try{let{default:n}=await import(`prettier`),r=await n.resolveConfig(e),i=await n.format(t,{filepath:e,...r});await y.writeFile(e,i,`utf8`)}catch{console.warn(`Skipped formatting ${e} since Prettier is not installed.`)}}async function N(e){try{await ze(e,await y.readFile(e,`utf8`))}catch{}}const Be=/\r?\n/v;function Ve(e){let t=``,n=t=>t.filter(t=>{let n=h(t);return n.trim()!==``&&!e(n)}).map(e=>`${e}\n`).join(``);return new l({flush(e){e(void 0,n([t]))},transform(e,r,i){let a=(t+e.toString()).split(Be);t=a.pop()??``,i(void 0,n(a))}})}function P(e,t){let n=C()===`native`?e:void 0,r=n===void 0||n===``?``:t===void 0?n:T()[t](n),i=``,a=e=>e.filter(e=>e.trim().length>0).map(e=>r===``?`${e}\n`:`${r} ${e}\n`).join(``);return new l({flush(e){e(void 0,a([i]))},transform(e,t,n){let r=(i+e.toString()).split(Be);i=r.pop()??``,n(void 0,a(r))}})}async function He(e){let t=[];return new Promise((n,r)=>{e.on(`data`,e=>{t.push(e)}),e.on(`error`,e=>{r(e)}),e.on(`end`,()=>{n(Buffer.concat(t).toString(`utf8`))})})}function Ue(e){return e.replaceAll(/[A-Z\u{C0}-\u{D6}\u{D8}-\u{DE}]/gv,e=>`-`+e.toLowerCase())}function F(e,t){return t===1?e:e+`s`}async function I(e){return typeof e==`function`?e():e}async function We(e,t,n,r,i){let a;if(r.logPrefix===void 0)a=e;else{let t=P(r.logPrefix,r.logColor);t.pipe(e),a=t}i&&a.write(T().bold(`Running: "${r.name}() with Positional arguments: ${String(t)} and Option flags: ${String(n)}"`)+`
3
3
  `);try{return await r.execute(a,t,n)}catch(e){return console.error(String(e)),1}}function Ge(e,t,n,r,i){return[...e.subcommands??[],...e.receiveOptionFlags?n:[],...r??[],...i&&e.cache!==void 0?[...e.cache.flags,`--cache-location`,s.join(M(),`node_modules`,`.cache`,`ksc`,e.cache.name)]:[],...e.receivePositionalArguments?t:[],...e.positionalArguments??[]]}async function Ke(e,t,r,i,a,o){let s=1,l;if(i.logPrefix===void 0)l=e;else{let t=P(i.logPrefix,i.logColor);t.pipe(e),l=t}let u=Ge(i,t,r,i.optionFlags,a),d=Re(i.cwdOverride);o&&l.write(`Running: "${i.name} ${u.join(` `)}"\n`);let f=i.prettyJsonOutput?new c:void 0,p=f??l,m=Ce(C()===`native`&&i.prettyJsonOutput!==!0&&w(Se()));try{let e=n(i.name,u,{cwd:d,env:m,preferLocal:!0,reject:!1,stdin:`inherit`});if(i.outputFilter){let t=Ve(i.outputFilter),n=Ve(i.outputFilter);e.stdout.pipe(t).pipe(p,{end:!1}),e.stderr.pipe(n).pipe(p,{end:!1})}else e.stdout.pipe(p,{end:!1}),e.stderr.pipe(p,{end:!1});let t=await e;if(f!==void 0){f.end();let e=await He(f),t=O(JSON.parse(e)).split(`
4
4
  `);for(let e of t)l.write(`${e}\n`)}s=t.exitCode??1}catch(e){console.error(`${i.name} failed with error:`),console.error(e),ke(e)&&(s=typeof e.exitCode==`number`?e.exitCode:1)}return s}function L(e){return`execute`in e}function R(e){return`kind`in e}async function qe(e,t,r,i,a){let o=Ge(r,e,t,r.collect?.optionFlags??r.optionFlags,i),s=Re(r.cwdOverride);try{let e=n(r.name,o,{cwd:s,env:Ce(!1),preferLocal:!0,reject:!1,stdin:`inherit`});a!==void 0&&e.stderr.pipe(a,{end:!1});let t=await e;return{cwd:s,exitCode:t.exitCode??1,stderr:t.stderr,stdout:t.stdout}}catch(e){return console.error(`${r.name} failed with error:`),console.error(e),{cwd:s,exitCode:ke(e)&&typeof e.exitCode==`number`?e.exitCode:1,stderr:``,stdout:``}}}function Je(e){return typeof e==`object`&&!!e&&`version`in e&&e.version===1&&`diagnostics`in e&&Array.isArray(e.diagnostics)&&`success`in e&&typeof e.success==`boolean`&&`tools`in e&&Array.isArray(e.tools)}function Ye(e,t){return e===0&&t.some(({severity:e})=>e===`error`)?1:e}async function Xe(e,t,n,r,i){let a=Date.now(),o=()=>Date.now()-a;if(L(r)){if(r.collect!==void 0)try{let e=await r.collect(t,n),i=Ye(e.exitCode,e.diagnostics);return{diagnostics:e.diagnostics,exitCode:i,tools:[{durationMs:o(),exitCode:i,name:r.name,unparsed:e.unparsed}]}}catch(e){return{diagnostics:[],exitCode:1,tools:[{durationMs:o(),exitCode:1,name:r.name,unparsed:[String(e)]}]}}let e=``,i=new c;i.on(`data`,t=>{e+=t.toString()});let a;try{a=await r.execute(i,t,n)}catch(t){e+=`\n${String(t)}`,a=1}return{diagnostics:[],exitCode:a,tools:[{durationMs:o(),exitCode:a,name:r.name,unparsed:D(e)}]}}if(R(r))throw Error(`Command groups must be expanded before collection`);if(r.outputFormatAware){let a=await qe(t,n,r,i,e);try{let e=JSON.parse(a.stdout);if(!Je(e))throw Error(`Child output is not a lint report`);return{diagnostics:e.diagnostics,exitCode:a.exitCode===0&&e.success&&e.diagnostics.every(({severity:e})=>e!==`error`)?0:1,tools:e.tools}}catch{return{diagnostics:[],exitCode:a.exitCode,tools:[{durationMs:o(),exitCode:a.exitCode,name:r.name,unparsed:D(a.stdout)}]}}}let s=await qe(t,n,r,i);if(r.collect!==void 0)try{let{diagnostics:e,unparsed:t}=r.collect.parse(s),n=Ye(s.exitCode,e);return{diagnostics:e,exitCode:n,tools:[{durationMs:o(),exitCode:n,name:r.name,unparsed:t}]}}catch(e){return{diagnostics:[],exitCode:s.exitCode,tools:[{durationMs:o(),exitCode:s.exitCode,name:r.name,unparsed:[...D(s.stdout),...D(s.stderr),String(e)]}]}}return{diagnostics:[],exitCode:s.exitCode,tools:[{durationMs:o(),exitCode:s.exitCode,name:r.name,unparsed:[...D(s.stdout),...D(s.stderr)]}]}}const Ze=/^ksc-/v;function z(e){return e.replace(Ze,``)}function B(e){return e===void 0||e.length===0?[]:e.flatMap(e=>e.split(`,`)).map(e=>z(e.trim()))}function V(e){return e.option(`skip`,{array:!0,describe:`Tool names to skip (with or without "ksc-" prefix).`,type:`string`})}function Qe(e){return e.option(`format`,{choices:ve,default:`native`,describe:`Output format: "native" streams each tool's own output, "machine" prints one parseable line per issue for editor problem matchers, "json" prints an aggregate report.`})}function $e(e){return e.option(`cache`,{boolean:!0,default:!0,describe:`Use tool-native caches stored below node_modules/.cache/ksc at the workspace root. Disable with --no-cache.`})}function et(e,t){return t===`native`||t===`machine`&&!L(e)&&!R(e)&&e.outputFormatAware===!0}function tt(e,t){for(let n of t.diagnostics)e.write(`${Ee(n)}\n`);for(let n of t.tools)for(let t of n.unparsed)e.write(`${t}\n`)}function nt(){return Math.min(4,o())}var rt=class{concurrency;active=0;queue=[];constructor(e){this.concurrency=e}async run(e){this.active>=this.concurrency&&await new Promise(e=>{this.queue.push(e)}),this.active+=1;try{return await e()}finally{--this.active,this.queue.shift()?.()}}};async function it(e){let t=e.cache??!1,n=e.concurrency??nt();if(!Number.isInteger(n)||n<1)throw Error(`Concurrency must be a positive integer.`);return t&&await r.ensureDir(s.join(M(),`node_modules`,`.cache`,`ksc`)),{cache:t,scheduler:new rt(n)}}const at=/[*?\[\]\{\}]/v;function H(e,t){return t===void 0?e:e.filter(e=>at.test(e)||i.statSync(e,{throwIfNoEntry:!1})?.isDirectory()===!0||t.includes(s.extname(e).slice(1).toLowerCase()))}function ot(e,t){return e.length===0||H(e,t).length>0}function st(e,t){return e.positionalArgumentMode===`none`?[]:t.length>0?H(t,e.positionalArgumentExtensions):e.positionalArgumentDefault===void 0?[]:[e.positionalArgumentDefault]}function ct(e,t){if(t.logPrefix===void 0)return e;let n=P(t.logPrefix,t.logColor);return n.pipe(e),n}function lt(e){return R(e)?`${e.name} ${e.subcommand}`:!L(e)&&e.subcommands!==void 0?[e.name,...e.subcommands].join(` `):e.name}async function ut(e,t){if(R(e)){let n=ct(t.logStream,e);try{let r=await I(e.commands);if(t.nativeVerbose){let n=[e.subcommand,...t.positionalArguments].join(` `),i=e.showResolvedCommands?` (${r.map(e=>lt(e)).join(`, `)})`:``;t.logStream.write(`Running: "${e.name} ${n}"${i}\n`)}let i=await dt(r,{...t,logStream:n,nativeVerbose:e.verbose,positionalArguments:st(e,t.positionalArguments)},e.parallel===!0);return{diagnostics:i.flatMap(({diagnostics:e})=>e),exitCode:+!i.every(({exitCode:e})=>e===0),machineOutcomes:i.flatMap(({machineOutcomes:e})=>e),name:e.name,tools:i.flatMap(({tools:e})=>e)}}catch(r){let i=String(r),a={durationMs:0,exitCode:1,name:e.name,unparsed:[i]};return t.format===`native`&&n.write(`${i}\n`),{diagnostics:[],exitCode:1,machineOutcomes:[{diagnostics:[],exitCode:1,tools:[a]}],name:e.name,tools:[a]}}}return t.scheduler.run(async()=>{if(et(e,t.format))return{diagnostics:[],exitCode:await(L(e)?We(t.logStream,t.positionalArguments,t.optionFlags,e,t.nativeVerbose):Ke(t.logStream,t.positionalArguments,t.optionFlags,e,t.cache,t.nativeVerbose)),machineOutcomes:[],name:e.name,tools:[]};let n=await Xe(t.logStream,t.positionalArguments,t.optionFlags,e,t.cache);return{diagnostics:n.diagnostics,exitCode:n.exitCode,machineOutcomes:[n],name:e.name,tools:n.tools}})}async function dt(e,t,n){if(!n||t.scheduler.concurrency===1){let n=[];for(let r of e)n.push(await ut(r,t));return n}await Promise.resolve();let r=/* @__PURE__ */ new Map;for(let[t,n]of e.entries()){let e=n.stage??0,i=r.get(e)??[];i.push({command:n,index:t}),r.set(e,i)}let i=Array.from({length:e.length}),a=r.keys().toArray().toSorted((e,t)=>e-t);for(let e of a){let n=r.get(e)??[],a=await Promise.all(n.map(async({command:e,index:n})=>({index:n,outcome:await ut(e,t)})));for(let{index:e,outcome:t}of a)i[e]=t}return i.filter(e=>e!==void 0)}function ft(e,t,n,r,i){let a=[],o=[],s=ot(r,i);for(let e of t){let t=!R(e)||ot(r,e.positionalArgumentExtensions);!s||!t||n.includes(z(e.name))?o.push(e):a.push(e)}if(n.length>0){let r=new Set(o.map(e=>z(e.name))),i=n.filter(e=>!r.has(e));if(i.length>0){let n=t.map(e=>z(e.name)).join(`, `);e.write(`⚠️ ${T().yellow(`Unrecognized --skip ${F(`value`,i.length)}: ${i.join(`, `)}. Available: ${n}`)}\n`)}}return{commandsToRun:a,skippedCommands:o}}async function U(e,t,n,r,i,a,o,s=`native`){let c=typeof s==`string`?{}:s,l=typeof s==`string`?s:c.format??`native`,{commandsToRun:u,skippedCommands:d}=ft(e,r,o??[],t,c.positionalArgumentExtensions),f=l===`native`&&i,{cache:p,scheduler:m}=await it(c),h=await dt(u,{cache:p,format:l,logStream:e,nativeVerbose:f,optionFlags:n,positionalArguments:H(t,c.positionalArgumentExtensions),scheduler:m},c.parallel===!0),g=h.map(({exitCode:e,name:t})=>({exitCode:e,name:t})),ee=h.flatMap(({tools:e})=>e),te=h.flatMap(({diagnostics:e})=>e);if(l===`machine`)for(let t of h)for(let n of t.machineOutcomes)tt(e,n);let _=r.length,v=T();if(l===`native`&&d.length>0){let t=d.map(({name:e})=>e),n=v.bold(`${t.length} / ${_} ${F(`Command`,t.length)} Skipped:`);e.write(`⏭️ ${v.dim(n)} ${v.dim(t.join(`, `))}\n`)}if(l===`native`&&a){let t=g.filter(({exitCode:e})=>e===0).map(({name:e})=>e),n=g.filter(({exitCode:e})=>e!==0).map(({name:e})=>e);if(t.length>0){let n=v.bold(`${t.length} / ${_} ${F(`Command`,t.length)} Succeeded:`);e.write(`✅ ${v.green(n)} ${v.green(t.join(`, `))}\n`)}if(n.length>0){let t=v.bold(`${n.length} / ${_} ${F(`Command`,n.length)} Failed:`);e.write(`❌ ${v.red(t)} ${v.red(n.join(`, `))}\n`)}}return{exitCode:+!g.every(({exitCode:e})=>e===0),report:l===`json`?Oe(ee,te):void 0}}async function pt(e){let t=await u({cwd:e});return t===void 0?void 0:s.join(s.dirname(t),`init`)}async function mt(e,t,n,a){let o=await u();if(o===void 0)throw Error("The `init` command must be used in a directory with a package.json file");let c=await pt(import.meta.dirname);if(c===void 0)return e.write(`Error: Could not locate the package containing this CLI.
5
5
  `),1;let l=s.dirname(o),d=(t===`file`||t===`package`)&&n!==void 0&&a!==void 0;try{if(d){let n=Object.keys(a)[0],i=r.readJsonSync(o);if(t===`package`){e.write(`Merging: \nPackage config key "${n}" → "${l}" (Because --location is set to "package")\n`);let t=k(i,a);r.writeJSONSync(o,t,{spaces:` `}),await N(o)}else n!==void 0&&Object.keys(i).includes(n)&&(e.write(`Deleting: \nPackage config key "${n}" in "${l}" (Because --location is set to "file")\n`),delete i[n],r.writeJSONSync(o,i,{spaces:` `}),await N(o))}if(!await r.pathExists(c))return 0;if((await r.readdir(c)).length===0)return e.write(`Source directory "${c}" is empty.\n`),0;e.write(`Adding initial configuration files from:\n"${c}" → "${l}"\n`),await r.copy(c,l,{async filter(a,o){let c=i.statSync(a).isFile(),l=i.existsSync(o);if(c){if(d&&t===`package`&&a.includes(n))return l?(e.write(`Deleting: \n"${a}" → "${o}" (Because --location is set to "package")\n`),r.removeSync(o)):e.write(`Skipping: \n"${a}" → "${o}" (Because --location is set to "package")\n`),!1;if(l&&(o.includes(`.vscode/`)||o.includes(`package.json`))&&s.extname(o)===`.json`){e.write(`Merging: \n"${a}" → "${o}"\n`);let t=r.readJSONSync(a),n=r.readJSONSync(o),i=o.endsWith(`.vscode/tasks.json`)?Pe(n,t):k(n,t);return r.writeJSONSync(o,i,{spaces:` `}),await N(o),!1}return l?(e.write(`Overwriting: \n"${a}" → "${o}"\n`),await N(o),!0):(e.write(`Copying: \n"${a}" → "${o}"\n`),await N(o),!0)}return!0},overwrite:!0})}catch(e){return console.error(String(e)),1}return 0}async function ht(e){let{commands:{fix:t,init:n,lint:r,printConfig:i},description:a,logColor:o,logPrefix:s,name:c,showSummary:l,verbose:u}=e;be();let d=P(s,o);d.pipe(Se());let m=f(p(process.argv)).scriptName(c).usage(`$0 <command>`,a);n!==void 0&&m.command({builder(e){let t=n.locationOptionFlag?e.option(`location`,{choices:[`file`,`package`],default:`file`,describe:`Where to store the configuration.`,type:`string`}):e;return l?V(t):t},command:`init`,describe:n.description??`Initialize by copying starter config files to your project root${n.locationOptionFlag?` or to your package.json file.`:`.`}`,async handler(e){let t=n.locationOptionFlag?e.location:void 0,r=B(e.skip),{exitCode:i}=await U(d,[],t===void 0?[]:[`--location`,t],[{async execute(e,t,r){return mt(e,r.at(1),n.configFile,n.configPackageJson)},name:`copyAndMergeInitFiles`},...n.commands??[]],void 0,void 0,r);process.exitCode=i}}),r!==void 0&&m.command({builder(e){let t=$e(Qe(r.positionalArgumentMode===`none`?e:e.positional(`files`,{array:!0,...r.positionalArgumentDefault!==void 0&&{default:r.positionalArgumentDefault},describe:`Files or glob pattern to lint.`,type:`string`})));return l?V(t):t},command:r.positionalArgumentMode===`none`?`lint`:r.positionalArgumentMode===`optional`?`lint [files..]`:`lint <files..>`,describe:r.description,async handler(e){let t=e.files??[],n=B(e.skip),i=C(),{exitCode:a,report:o}=await U(d,t,[],await I(r.commands),u,l,n,{cache:e.cache,format:i,parallel:r.parallel,positionalArgumentExtensions:r.positionalArgumentExtensions});o!==void 0&&process.stdout.write(`${O(o)}\n`),process.exitCode=a}}),t!==void 0&&m.command({builder(e){let n=$e(Qe(t.positionalArgumentMode===`none`?e:e.positional(`files`,{array:!0,...t.positionalArgumentDefault!==void 0&&{default:t.positionalArgumentDefault},describe:`Files or glob pattern to fix.`,type:`string`})));return l?V(n):n},command:t.positionalArgumentMode===`none`?`fix`:t.positionalArgumentMode===`optional`?`fix [files..]`:`fix <files..>`,describe:t.description,async handler(e){let n=e.files??[],r=B(e.skip),i=C(),{exitCode:a,report:o}=await U(d,n,[],await I(t.commands),void 0,void 0,r,{cache:e.cache,format:i,parallel:t.parallel,positionalArgumentExtensions:t.positionalArgumentExtensions});o!==void 0&&process.stdout.write(`${O(o)}\n`),process.exitCode=a}}),i!==void 0&&m.command({builder(e){let t=i.positionalArgumentMode===`none`?e:e.positional(`file`,{...i.positionalArgumentDefault!==void 0&&{default:i.positionalArgumentDefault},describe:`File or glob pattern to print configuration for.`,type:`string`});return l?V(t):t},command:i.positionalArgumentMode===`none`?`print-config`:i.positionalArgumentMode===`optional`?`print-config [file]`:`print-config <file>`,describe:i.description,async handler(e){let t=e.file??void 0,n=t===void 0?[]:[t],r=B(e.skip),{exitCode:a}=await U(d,n,[],await I(i.commands),u,l,r);process.exitCode=a}}),m.alias(`h`,`help`),m.version(ge),m.alias(`v`,`version`),m.help(),m.wrap(process.stdout.isTTY?Math.min(120,m.terminalWidth()):0),await m.parseAsync()}function W(e){return{async execute(t){let n=await G(e);if(n===void 0)return 1;let{config:r,filepath:i,isEmpty:a}=n;if(t.write(`Found ${e} configuration at "${i}"\n`),a)return t.write(`Configuration is empty.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitschpatrol/shared-config",
3
- "version": "8.11.0",
3
+ "version": "8.12.1",
4
4
  "description": "A collection of shared configurations, linters, and formatting tools for TypeScript projects. All managed as a single dependency. All invoked via a single CLI command.",
5
5
  "keywords": [
6
6
  "shared-config",
@@ -54,15 +54,15 @@
54
54
  "skills/*"
55
55
  ],
56
56
  "dependencies": {
57
- "@kitschpatrol/cspell-config": "8.11.0",
58
- "@kitschpatrol/eslint-config": "8.11.0",
59
- "@kitschpatrol/knip-config": "8.11.0",
60
- "@kitschpatrol/mdat-config": "8.11.0",
61
- "@kitschpatrol/prettier-config": "8.11.0",
62
- "@kitschpatrol/remark-config": "8.11.0",
63
- "@kitschpatrol/repo-config": "8.11.0",
64
- "@kitschpatrol/stylelint-config": "8.11.0",
65
- "@kitschpatrol/typescript-config": "8.11.0",
57
+ "@kitschpatrol/cspell-config": "8.12.1",
58
+ "@kitschpatrol/eslint-config": "8.12.1",
59
+ "@kitschpatrol/knip-config": "8.12.1",
60
+ "@kitschpatrol/mdat-config": "8.12.1",
61
+ "@kitschpatrol/prettier-config": "8.12.1",
62
+ "@kitschpatrol/remark-config": "8.12.1",
63
+ "@kitschpatrol/repo-config": "8.12.1",
64
+ "@kitschpatrol/stylelint-config": "8.12.1",
65
+ "@kitschpatrol/typescript-config": "8.12.1",
66
66
  "@pinojs/json-colorizer": "^4.0.0",
67
67
  "@pnpm/lockfile.fs": "^1100.2.12",
68
68
  "cosmiconfig": "^10.0.1",
@@ -79,7 +79,7 @@
79
79
  "globby": "^16.2.4",
80
80
  "json-stringify-pretty-compact": "^4.0.0",
81
81
  "magicast": "^0.5.5",
82
- "mdat": "^3.5.0",
82
+ "mdat": "^3.6.1",
83
83
  "package-up": "^5.0.0",
84
84
  "picocolors": "^1.1.1",
85
85
  "prettier": "^3.9.9",
package/readme.md CHANGED
@@ -190,12 +190,18 @@ pnpm --package=@kitschpatrol/repo-config dlx ksc-repo init && pnpm i && pnpm add
190
190
 
191
191
  5. Set up GitHub action credentials (if desired)
192
192
 
193
- The GitHub actions included in @kitschpatrol/repo-config require permissions to create releases and update your repository metadata. You can add these through the GitHub website under the _Settings → Secrets and variables → Actions_ page under the key `PERSONAL_ACCESS_TOKEN`, or with the [GitHub CLI](https://cli.github.com) and a credential manager like [1Password CLI](https://www.1password.dev/cli/get-started):
193
+ The GitHub actions included in @kitschpatrol/repo-config require permissions to create releases, update your repository metadata, and open dependency update pull requests. You can add these through the GitHub website under the _Settings → Secrets and variables → Actions_ page under the key `PERSONAL_ACCESS_TOKEN`, or with the [GitHub CLI](https://cli.github.com) and a credential manager like [1Password CLI](https://www.1password.dev/cli/get-started):
194
194
 
195
195
  ```sh
196
196
  gh secret set PERSONAL_ACCESS_TOKEN --app actions --body $(op read 'op://Personal/GitHub Mika/PERSONAL_ACCESS_TOKEN')
197
197
  ```
198
198
 
199
+ The weekly dependency update workflow also needs a `CLAUDE_CODE_OAUTH_TOKEN` secret, a Claude subscription token generated with `claude setup-token`:
200
+
201
+ ```sh
202
+ gh secret set CLAUDE_CODE_OAUTH_TOKEN --app actions --body $(op read 'op://Personal/Claude Code OAuth Token/credential')
203
+ ```
204
+
199
205
  See the [@kitschpatrol/repo-config readme](https://github.com/kitschpatrol/shared-config/blob/main/packages/repo-config/readme.md#github-configuration) for more details.
200
206
 
201
207
  ## Usage
@@ -216,7 +222,7 @@ pnpm run fix
216
222
 
217
223
  ### CLI
218
224
 
219
- <!-- cli-help({ command: 'ksc' }) -->
225
+ <!-- cli-help({ command: 'ksc', heading: false, headingLevel: 3 }) -->
220
226
 
221
227
  #### Command: `ksc`
222
228
 
@@ -374,31 +380,29 @@ The tasks share a problem matcher owner, so the panel reflects the most recent r
374
380
 
375
381
  ## Agent skills
376
382
 
377
- This project includes an [Agent Skill](https://agentskills.io) that teaches coding agents like Claude Code and Codex how to work with @kitschpatrol/shared-config:
378
-
379
- - **[`ksc`](skills/ksc/SKILL.md)**: Code conventions and lint workflow for projects that use @kitschpatrol/shared-config and its ksc CLI, which wraps ESLint, Prettier, TypeScript, Stylelint, CSpell, Knip, remark, and mdat. Use when writing or editing TypeScript, JavaScript, CSS, Markdown, JSON, or YAML in a project that depends on @kitschpatrol/shared-config, when running or fixing `ksc lint` / `ksc fix` failures, when writing eslint-disable comments, or when editing the tool config files it manages.
383
+ This project bundles 2 [Agent Skills](https://agentskills.io) in its published package to help coding agents work with @kitschpatrol/shared-config.
380
384
 
381
- The skill is published in the `skills` directory of the `@kitschpatrol/shared-config` package. Nothing is added to your project until you install it with one of the tools below.
385
+ To sync the skills into your project, run Vercel's [skills CLI](https://github.com/vercel-labs/skills) from your project root:
382
386
 
383
- ### Sync from the installed package (recommended)
387
+ ```sh
388
+ npx skills experimental_sync
389
+ ```
384
390
 
385
- With `@kitschpatrol/shared-config` installed as a project dependency, the [`skills`](https://github.com/vercel-labs/skills) CLI finds skills bundled in your dependencies and copies them into your project's agent skill directories, so they match the version of `@kitschpatrol/shared-config` you have installed:
391
+ Or install globally:
386
392
 
387
393
  ```sh
388
- npx skills experimental_sync
394
+ npx skills add kitschpatrol/shared-config --global
389
395
  ```
390
396
 
391
- Run the command again after upgrading `@kitschpatrol/shared-config` to refresh the copies. The `experimental_sync` command is experimental and its behavior may change.
397
+ Included skills:
392
398
 
393
- ### Install from the repository
399
+ ### Skill: [`ksc`](skills/ksc/SKILL.md)
394
400
 
395
- If `@kitschpatrol/shared-config` is not a dependency of your project, for example because you use a global installation, install the skill from the repository instead:
401
+ Code conventions and lint workflow for projects that use @kitschpatrol/shared-config and its ksc CLI, which wraps ESLint, Prettier, TypeScript, Stylelint, CSpell, Knip, remark, and mdat.
396
402
 
397
- ```sh
398
- npx skills add kitschpatrol/shared-config
399
- ```
403
+ ### Skill: [`ksc-update`](skills/ksc-update/SKILL.md)
400
404
 
401
- A skill installed this way follows the repository's default branch rather than your installed version of `@kitschpatrol/shared-config`.
405
+ Update dependencies in kitschpatrol shared config pnpm repositories and monorepos with compatible changes, explicitly approved breaking changes, or breaking changes for zero-versioned projects.
402
406
 
403
407
  <!-- /skills -->
404
408
 
@@ -0,0 +1,762 @@
1
+ ---
2
+ name: ksc-update
3
+ description: >-
4
+ Update dependencies in kitschpatrol shared config pnpm repositories and
5
+ monorepos with compatible changes, explicitly approved breaking changes, or
6
+ breaking changes for zero-versioned projects. Use when asked to update,
7
+ upgrade, refresh, or audit repository dependencies or GitHub Actions. Applies
8
+ a 24-hour release maturity policy with exceptions for kitschpatrol's packages,
9
+ reviews fresh-install consumer compatibility, validates known consumers,
10
+ minimizes configuration overrides, and recommends a release level.
11
+ Does not commit or release.
12
+ compatibility: Requires Node.js, pnpm/pnpx, curl, jq, git, Bash, and access to npm, GitHub, and upstream release documentation.
13
+ ---
14
+
15
+ # Update dependencies
16
+
17
+ Accept dependency and GitHub Actions updates under the version policy below.
18
+ For projects at 1.0.0 or later, preserve existing contracts: public APIs, CLI
19
+ behavior, outputs, defaults, supported consumer environments, and peer
20
+ compatibility, unless the user explicitly approves a reviewed breaking change.
21
+ Make the migrations accepted updates require. Do not commit, push, tag, publish,
22
+ or release.
23
+
24
+ ## Execution mode
25
+
26
+ Determine the mode before Setup and record it with the reason. Use unattended
27
+ mode when explicitly requested, when `GITHUB_ACTIONS=true` or `CI=true`, or when
28
+ the host cannot receive user replies. Otherwise use interactive mode; a missing
29
+ TTY alone does not establish that an agent session is unattended.
30
+
31
+ In unattended mode, never ask questions, wait for answers, or invoke a command
32
+ that requires an interactive response. Existing explicit approvals still apply;
33
+ silence and CI execution do not grant new approval. This mode overrides every
34
+ instruction below to ask, confirm, or await a decision:
35
+
36
+ - **Projects at 1.0.0 or later:** apply compatible changes and any reviewed
37
+ breaks already explicitly authorized. Defer other breaking candidates and
38
+ report them for a later decision. Judge consumer impact, not dependency
39
+ version labels; a compatible dependency major remains eligible.
40
+ - **Zero-versioned projects:** retain the existing `0.x` allowance in unattended
41
+ runs. Apply reviewed breaking changes, complete their migrations, validate
42
+ the new contracts, and normally recommend a minor release. Do not defer them
43
+ merely because no person is present to approve the break. Other requirements,
44
+ including explicit approval for runtime-range restrictions and Node-types
45
+ changes, still apply.
46
+ - **Other decisions and missing prerequisites:** without prior authorization,
47
+ leave approval-dependent changes unapplied, including range restrictions.
48
+ Scoped peer-warning suppressions with verified compatibility (step 5) are not
49
+ approval-dependent: apply them and report them. Report missing consumer
50
+ information, credentials, permissions, or validation resources instead of
51
+ requesting them. Continue independent work; if a required prerequisite
52
+ prevents progress, report the affected work as blocked or incomplete without
53
+ waiting or bypassing it.
54
+
55
+ Review and validation remain required in both modes. Defer candidates whose
56
+ impact cannot be established or whose migration cannot be validated. If safe
57
+ updates cannot be isolated without an unapproved change, leave those updates
58
+ unapplied and explain the blocker. Report incompatible releases already allowed
59
+ by published runtime ranges as existing consumer exposure; an older lockfile
60
+ does not resolve that exposure.
61
+
62
+ Produce changes and a report suitable for later PR automation. This skill does
63
+ not create workflows or pull requests, or split work into multiple PRs. If
64
+ nothing can be applied, produce the report without manufacturing a change.
65
+
66
+ ## Policy
67
+
68
+ - **Compatible by default at 1.0.0 or later:** assess the effect on this repository
69
+ and its consumers, not just the dependency's version label. Minor and patch
70
+ updates can break compatibility; a dependency major can still be compatible.
71
+ Present useful breaking candidates with their impact, migration, validation,
72
+ and release implications before asking for approval; apply only within the
73
+ approved scope. Existing explicit approval remains valid. Defer unapproved
74
+ breaks and updates whose impact cannot be established. This includes authored
75
+ packages, production transitives, and workflow actions.
76
+ - **Zero-versioned projects:** if the project's current release version has
77
+ major version zero (`0.x.y`), any breaking change within this update workflow
78
+ is permitted in both interactive and unattended modes, including changes to
79
+ public contracts and consumer Node.js requirements. This exception overrides
80
+ compatibility-preservation requirements below. Use the project's version, not
81
+ the dependency's version; in monorepos,
82
+ assess each affected published package or shared release version, not just a
83
+ private workspace root. Review and report breaking changes, make required
84
+ migrations, and validate the resulting behavior. All other policies still
85
+ apply.
86
+ - **Release maturity:** third-party releases must be at least 24 hours old
87
+ (`--maturity-period 1` for Taze, `--min-age 1` for Actions Up, pnpm
88
+ `minimumReleaseAge` of 1440 minutes). The period governs which releases a run
89
+ may introduce; it never requires downgrading a version that was locked before
90
+ the run. Never disable installation safeguards to make an update succeed.
91
+ - **Authored packages:** packages owned by `kitschpatrol` (discovered at runtime
92
+ in Setup) are exempt from maturity and target their latest stable release,
93
+ including majors. pnpm enforces `minimumReleaseAge` silently by selecting the
94
+ newest mature version, so the exemption is only real while a
95
+ `minimumReleaseAgeExclude` entry for the young release exists: add it when
96
+ needed (step 5) and keep it through cleanup (step 6). The maturity period
97
+ must never hold an authored package back or downgrade it. The exemption does
98
+ not bypass security or compatibility review.
99
+ - **Consumer ranges:** review the newest published version allowed by each
100
+ runtime dependency's declared range, as well as the lockfile. A fresh consumer
101
+ install can resolve that version even when CI locks an older one. Holding
102
+ back an in-range release requires a genuinely restrictive published range
103
+ (for example, `~1.8.0` instead of `^1.8.0` to exclude `1.9.0`), not just an
104
+ older lockfile entry. Obtain explicit approval before narrowing a runtime
105
+ range to hold a release back, even for a zero-versioned project; explain the
106
+ consumer effect. Do not use local overrides as a substitute for that decision.
107
+ - **TypeScript:** select a compiler within the peer range of the accepted
108
+ `@kitschpatrol/typescript-config` version, intersected with other required
109
+ peers. Do not hard-code a compiler version. Offer the migration when an
110
+ authored major requires a newer compiler; see step 3. If that config is not
111
+ used, follow the project's compiler policy and peer requirements.
112
+ - **Node types:** preserve `@types/node` declarations, including catalogs and
113
+ overrides, unless a reviewed migration has explicit authorization to change
114
+ them. New compiler libraries do not establish runtime API availability.
115
+ - **Package manager:** only Setup manages `packageManager`. Exclude `pnpm`
116
+ from Taze discovery and writes.
117
+ - **No weakening:** do not relax tests, lint rules, type checking, or security
118
+ settings to get a pass; do not blindly accept changed snapshots or generated
119
+ output. Compatibility-preserving lint disables described in step 5 are an
120
+ exception.
121
+ - **No new noise:** every check is compared against the Setup baseline. A new
122
+ warning, deprecation, or peer issue is a finding to trace and report, not
123
+ background noise. Report compatibility-preserving lint disables and scoped
124
+ peer-warning entries from step 5; ask before silencing other findings.
125
+
126
+ ## Environment
127
+
128
+ Shell variables do not persist between commands. Save anything later steps need
129
+ under `node_modules/.cache/ksc-update/` and pass values literally.
130
+
131
+ Plan network access for `registry.npmjs.org`, `raw.githubusercontent.com`,
132
+ `api.github.com`, and `nodejs.org`; identify any additional upstream hosts as
133
+ needed. Prefer registry metadata and raw changelog files over GitHub API calls
134
+ for package reviews. Cache responses and share them between reviewers.
135
+
136
+ Under Claude Code's sandbox:
137
+
138
+ - **pnpm store:** pnpm 12 cannot take its store lock inside the sandbox
139
+ (`ERR_PNPM_STORE_DIR_OPEN_OPERATION_LOCK`), even with writable store, cache,
140
+ and temp directories. `pnpm install`, `pnpm update`, `pnpm dedupe`,
141
+ `pnpx taze`, and `pnpx actions-up` must be listed in the user's
142
+ `sandbox.excludedCommands` and `permissions.allow`. If the Setup install fails
143
+ this way, stop the affected work and ask the user to add them in interactive
144
+ mode; in unattended mode, report the missing prerequisite. Do not route around
145
+ it. Run these commands bare (no pipes, redirects, or command substitution) so the
146
+ exclusions match. Run each as its own invocation, without `&&` or `||`.
147
+ Save captured tool output to log files in a separate operation.
148
+ - **Proxy:** Node-based tools ignore the proxy variables. If one fails with
149
+ `ENOTFOUND`, rerun it with `NODE_USE_ENV_PROXY=1`.
150
+ - **Sockets:** the `tsx` CLI cannot open its IPC socket. Run the same script
151
+ with `node --import tsx <script>` and report the substitution.
152
+ - **GitHub API:** without `GITHUB_TOKEN`, GitHub allows 60 requests per hour.
153
+ One Actions Up run plus a few release-note lookups can exhaust it. The sandbox
154
+ cannot read `gh` credentials, so budget lookups or ask for a token in
155
+ interactive mode. In unattended mode, report any review blocked by missing
156
+ credentials or exhausted API limits.
157
+ - **JSON output:** pnpm can emit `[WARN]` lines on stdout. Sync `packageManager`
158
+ before parsing pnpm JSON; if warnings remain, save them separately, remove
159
+ only lines beginning with `[WARN]`, and parse the rest strictly. Do not hide
160
+ malformed output or other diagnostics.
161
+ - **Supply-chain settings:** an unreadable global config or `~/.npmrc` can make
162
+ a sandboxed config query return `undefined`; this does not mean the policy is
163
+ disabled. Read accessible workspace settings and record pnpm's successful
164
+ `Lockfile passes supply-chain policies` message from the actual install.
165
+ That message confirms the active checks passed, not the numeric release-age
166
+ threshold. Verify the required 1440 minutes from accessible configuration or
167
+ release timestamps when the effective setting cannot be read. Do not disable
168
+ safeguards or access private config through a workaround.
169
+
170
+ ## Setup
171
+
172
+ 1. Read repository instructions. Note uncommitted changes in the working tree
173
+ and preserve them. If files change during the run that you did not touch,
174
+ leave them alone and report them.
175
+ Read the `Known consumers` section in the project's `CLAUDE.md` or `AGENTS.md`.
176
+ Maintain a short section in the existing instruction file using confirmed
177
+ consumer names, local paths, consumed packages, test commands, and CLI
178
+ fixtures. If none are documented, ask for known consumers in interactive
179
+ mode; in unattended mode, report the missing information and use available
180
+ repository evidence. Continue independent review; do not invent consumers or
181
+ treat example paths as real. Report unavailable consumer validation as a gap.
182
+ 2. Map the workspace: root, every maintained package root, catalogs, overrides,
183
+ `pnpm-workspace.yaml`, Taze config. Exclude fixtures, generated files, and
184
+ dependency directories. Run each step once per shared-lockfile workspace and
185
+ separately in any independent package root. Check Taze config for anything
186
+ that overrides mode, adds maturity exemptions, or auto-writes/installs.
187
+ Record current project and published package versions to determine where the
188
+ zero-versioned exception applies. Record every existing
189
+ `minimumReleaseAgeExclude` entry with the publish time of that exact version
190
+ (`time` in the registry metadata), so step 6 can tell which entries still
191
+ matter.
192
+ Create the baseline folder and preserve the starting manifests and lockfile
193
+ before the package-manager sync or any other edit.
194
+ 3. Sync `packageManager` before installs and parsed pnpm output. Choose the
195
+ newest of three versions, so the pin only ever moves up:
196
+
197
+ - the current `packageManager` pin;
198
+ - the pnpm installed on this host, read from a new empty directory under
199
+ `${TMPDIR:-/tmp}` so the project's stale pin cannot select or download
200
+ another version:
201
+
202
+ ```bash
203
+ pnpm --version
204
+ ```
205
+
206
+ - the newest stable pnpm release at least 24 hours old, from `versions` and
207
+ `time` in `https://registry.npmjs.org/pnpm` (`dist-tags.latest` when it
208
+ qualifies).
209
+
210
+ A CI runner usually installs the pinned version itself, so the installed
211
+ pnpm alone never moves the pin; the registry lookup is what keeps it
212
+ current. Then edit only the workspace root's `packageManager` directly to
213
+ `pnpm@<chosen-version>`, preserving all other manifest fields. Use the
214
+ editor or a structured JSON edit, not `pnpm pkg set`; use direct edits for
215
+ later specifiers and engines too. pnpm downloads and runs the pinned
216
+ version on demand after the pin changes. If this crosses a pnpm major,
217
+ report it, review the pnpm release notes for lockfile and settings changes,
218
+ and verify the CI installer supports the selected version.
219
+
220
+ 4. Install the starting dependency declarations and record a baseline before
221
+ dependency updates. Keep the original files saved in Setup 2 even if
222
+ installation fails.
223
+
224
+ ```bash
225
+ pnpm install --frozen-lockfile
226
+ ```
227
+
228
+ On `ERR_PNPM_OUTDATED_LOCKFILE` only, run this as a separate bare command:
229
+
230
+ ```bash
231
+ pnpm install --no-frozen-lockfile
232
+ ```
233
+
234
+ Save the lockfile diff against the starting copy in the baseline folder and
235
+ report the drift. Treat checks after repair as the repaired baseline, not as
236
+ proof that the original locked tree passed. Other installation failures need
237
+ their own diagnosis; do not generalize this fallback or relax safeguards.
238
+
239
+ Save the output of each install attempt and of `pnpm build`, `pnpm test`, and
240
+ `pnpm lint` to `node_modules/.cache/ksc-update/baseline/`. If the repository
241
+ produces build output (`dist/` or similar), copy it there as well. Use the
242
+ repository's switches to disable nondeterministic build steps (encryption,
243
+ timestamps) and note the exact command and environment; later comparison
244
+ builds must match them. Baseline failures are pre-existing; record them.
245
+
246
+ Record each published package's declared `engines.node`, effective consumer
247
+ Node.js minimum and supported range, and separate development requirement.
248
+ Include published dependency trees and built artifacts, not just the root
249
+ manifest or current CI runtime. If no Node.js support policy is declared,
250
+ establish the existing requirement from the published code and dependencies;
251
+ do not assume the developer's Node.js version is the consumer minimum.
252
+ Save production-tree engine inventories and baseline consumer results using
253
+ [the verification recipes](references/verification.md). Distinguish the
254
+ starting locked tree, the newest versions a fresh consumer install can
255
+ resolve, and the published release with its old dependencies when available.
256
+
257
+ 5. Discover authored packages:
258
+
259
+ ```bash
260
+ set -euo pipefail
261
+ mkdir -p node_modules/.cache/ksc-update
262
+ curl -fsS https://registry.npmjs.org/-/user/kitschpatrol/package \
263
+ | jq -er 'keys | join(",") | select(length > 0)' \
264
+ > node_modules/.cache/ksc-update/authored-packages.txt
265
+ ```
266
+
267
+ If registry discovery fails or returns empty, stop and report. Intersect the
268
+ result with direct dependency names across the maintained manifests in this
269
+ workspace, including dev, optional, and peer dependencies and dependencies
270
+ using catalogs or npm aliases. Save the intersection separately; an empty
271
+ intersection is valid. `<authored-packages>` below means only those exact
272
+ matching names, comma-separated and passed literally, with no wildcards.
273
+ Omit `--maturity-period-exclude` if the intersection is empty. Keep the full
274
+ ownership list only for evaluating other packages encountered later.
275
+
276
+ ## 1. Workflow actions
277
+
278
+ ```bash
279
+ pnpx actions-up --yes --min-age 1 --style sha
280
+ ```
281
+
282
+ Read the whole output. Actions Up can print "All actions are up to date!" after
283
+ checks failed; any `Skipped`, `Failed`, or `Rate Limit` line means the run is
284
+ incomplete. Fix the cause and rerun.
285
+
286
+ Review the diff: major action bumps, runner, input, output, and permission
287
+ changes. Every external GitHub Action and reusable workflow must be pinned to
288
+ its full commit SHA, including `kitschpatrol/*`; retain version comments for
289
+ readability. Do not exclude first-party actions from pinning or allow their
290
+ floating refs in zizmor's `unpinned-uses` policy. Resolve skipped branch/tag
291
+ refs and unchanged versions to verified commits too; if an update is deferred,
292
+ pin the retained version. Check existing zizmor configuration for exceptions
293
+ that contradict this policy. Pin container actions by digest; local `./` actions
294
+ use the checked-out tree. Keep only reviewed changes without disturbing user
295
+ edits. Local tests say nothing about remote workflows.
296
+
297
+ ## 2. Discover all candidates (read-only)
298
+
299
+ ```bash
300
+ pnpx taze major -r --force --include-locked \
301
+ --no-github-actions --no-node-version \
302
+ --exclude '@types/node,pnpm' \
303
+ --maturity-period 1 \
304
+ --maturity-period-exclude '<authored-packages>'
305
+ ```
306
+
307
+ Major mode lists every available update, minor and major. Confirm authored
308
+ packages are offered their latest stable release, not the latest within their
309
+ current major. Do not opt into new prerelease channels. Taze's newest candidate
310
+ does not replace the separate newest-in-range runtime review in step 3.
311
+
312
+ ## 3. Evaluate candidates
313
+
314
+ ### Runtime ranges and review scope
315
+
316
+ For each runtime dependency of a published package, inspect registry metadata
317
+ for the newest stable version satisfying its current declared range, accounting
318
+ for catalogs and aliases. Record declared range, locked version, newest allowed
319
+ version, and proposed range/version. Review that newest allowed release even
320
+ if it is already in range or too young for this run to install; consumer
321
+ package managers may not enforce this repository's maturity policy. Keep the
322
+ installation safeguards and report any untested consumer exposure.
323
+
324
+ Review direct candidates and production-tree changes fully. For development-only
325
+ transitives, check advisories, deprecations, and engines; investigate release
326
+ notes only when those checks, validation failures, or output differences give
327
+ cause. A package bundled or shipped with a CLI/config package belongs to the
328
+ consumer review even if this repository also uses it as development tooling.
329
+
330
+ An in-range behavior change may already reach the published release's consumers.
331
+ Do not describe an older lockfile as protecting them or infer a new breaking
332
+ release solely from the lockfile movement. Test the published release and
333
+ updated build against actual consumer resolution, and separately preserve the
334
+ old locked baseline. To hold back an incompatible allowed release, present a
335
+ specific narrower range and obtain the policy's required approval.
336
+
337
+ ### Breaking candidates
338
+
339
+ Track breaking-candidate decisions throughout the run; report applied breaks
340
+ and deferred opportunities separately.
341
+ For each candidate, identify affected APIs, behavior, Node.js support, known
342
+ consumers, migration work, validation results or gaps, and release implications.
343
+ Prepare a concrete proposal and, where useful, validate a trial migration in an
344
+ isolated copy before seeking a decision. For stable projects in interactive
345
+ mode, ask explicitly whether to accept each reviewed break unless that exact
346
+ scope was already authorized; continue independent updates while awaiting the
347
+ answer. In unattended mode, defer unapproved stable-project breaks and include
348
+ them under `Breaking changes available` in the report without asking. General
349
+ permission to update dependencies is not approval for an unspecified break.
350
+ For `0.x` projects, both modes authorize reviewed breaking migrations without
351
+ another question, but runtime-range narrowing still needs explicit approval.
352
+ Record accepted, declined, pending, and deferred decisions separately.
353
+
354
+ ### Node.js compatibility
355
+
356
+ For every candidate, minor and patch included, distinguish consumer requirements
357
+ from development requirements:
358
+
359
+ - **Consumers:** for projects at 1.0.0 or later, keep the user-facing Node.js
360
+ minimum and supported range unchanged unless explicitly approved under
361
+ `Breaking candidates`. A higher effective consumer minimum or loss of any
362
+ supported Node.js version is a break even if `engines.node` is left unchanged.
363
+ Check direct, transitive, optional, peer, and workspace dependencies used by
364
+ published packages, plus bundled code, emitted syntax, and runtime APIs.
365
+ A tool used only for development here is still a consumer dependency if it
366
+ ships in a published CLI or configuration package. Compare
367
+ full engine ranges, not just the lowest version in each range.
368
+ For authorized breaks or zero-versioned projects, update published
369
+ `engines.node` to reflect the actual supported range and validate against it.
370
+ - **Development:** a higher Node.js requirement for development-only tools,
371
+ builds, or CI is allowed when published artifacts remain compatible. If it
372
+ exceeds the consumer requirement, add or update the Node.js entry in the
373
+ relevant `package.json`'s `devEngines.runtime` with `name: "node"` and a
374
+ `version` range covering the actual development requirement. Use the workspace
375
+ root for shared tooling and package manifests for package-specific tooling;
376
+ preserve other runtime entries and settings. This is the development Node.js
377
+ dependency notice; do not raise published `engines.node` to satisfy dev tools.
378
+ Align development version pins and build CI as needed while retaining consumer
379
+ compatibility checks at the applicable supported minimums (the original ones
380
+ unless an approved break or the zero-versioned exception changes them).
381
+
382
+ ### TypeScript migrations
383
+
384
+ Read the accepted `@kitschpatrol/typescript-config` release's TypeScript peer
385
+ range and base configuration. If it requires a new compiler major, offer that
386
+ migration with its concrete effects rather than automatically deferring the
387
+ authored update. Apply it within existing authorization; ask only for additional
388
+ breaking or Node-types changes that the policy actually requires approval for.
389
+
390
+ - Add `"node"` to `compilerOptions.types` where Node globals are needed,
391
+ preserving other required type entries and the separation from browser code.
392
+ - Fix errors exposed by newly enabled strict options; do not disable those
393
+ options just to pass.
394
+ - Check the new base `lib`, emitted syntax, declarations, and APIs used by
395
+ shipped code against the consumer Node.js floor and browser targets. A newer
396
+ `lib` can make unavailable APIs type-check; it does not polyfill them.
397
+
398
+ Run type checks, build, and consumer validation after the migration, retaining
399
+ the distinction between development and consumer Node.js requirements.
400
+
401
+ ### Compatibility review
402
+
403
+ For direct candidates and production-tree changes, read relevant upstream
404
+ release notes, migration guides, and advisories, and assess:
405
+
406
+ - **Security:** vulnerabilities fixed or introduced, ownership or publishing
407
+ anomalies, new install scripts or permissions.
408
+ - **Breaking:** APIs, types, defaults, configuration, module format, peer
409
+ ranges, runtime requirements, measured against this repository's actual use.
410
+ - **Coupling:** peer and engine requirements against the versions this update
411
+ will produce, not the current ones. A major can require a newer minor of a
412
+ sibling package (`@astrojs/mdx` 8 requires `astro` 7.2.10 or later).
413
+ - **Worth it:** fixes and performance adoptable under the version policy.
414
+ Note features exposed by the update and optional feature opportunities for the
415
+ report; do not implement new features as extra work.
416
+
417
+ When delegation is available, run independent release-note reviews in parallel,
418
+ split into authored packages, remaining runtime packages, and remaining direct
419
+ development packages. Give each reviewer its disjoint package list, a short
420
+ survey of actual usage and relevant source paths, consumer requirements, and
421
+ the version policy. Reviewers return decisions and sources without editing
422
+ dependencies; the primary agent resolves coupling and combines the results.
423
+ Share cached metadata and the GitHub API budget. Review sequentially when
424
+ delegation is unavailable.
425
+
426
+ Record current → proposed version, decision, and sources. Accept only when
427
+ compatibility is preserved, an explicit approval covers the break, or the
428
+ zero-versioned exception permits it; defer the rest with a reason. Do not
429
+ defer a migration merely because it needs a major release recommendation.
430
+
431
+ ## 4. Apply
432
+
433
+ Apply only accepted candidates, including minors and patches, by exact package
434
+ name. Major mode can apply all accepted update sizes:
435
+
436
+ ```bash
437
+ pnpx taze major -rw --force --include-locked \
438
+ --no-github-actions --no-node-version \
439
+ --include 'pkg-a,pkg-b' \
440
+ --exclude '@types/node,pnpm' \
441
+ --maturity-period 1 \
442
+ --maturity-period-exclude '<authored-packages>'
443
+ ```
444
+
445
+ Replace the example names with the accepted set; skip the command if it is
446
+ empty. If only an older compatible target was accepted, set that reviewed
447
+ version manually instead of letting Taze choose the latest. Then check the
448
+ diff: every manifest and catalog covered, only reviewed versions applied, no
449
+ excluded or deferred update slipped in, range style and `workspace:` references
450
+ intact. A package accepted in one workspace package is not automatically
451
+ accepted in every other one. Hand-edit anything Taze missed (catalogs,
452
+ overrides), and add any required development Node.js notice. Use direct edits
453
+ for reviewed compiler versions inside the required peer range and for
454
+ authorized Node-types changes; do not let Taze choose an incompatible compiler.
455
+ Verify `packageManager` remains at Setup's selected version.
456
+
457
+ ## 5. Resolve and validate
458
+
459
+ Run each command as a separate invocation:
460
+
461
+ ```bash
462
+ pnpm update -r
463
+ ```
464
+
465
+ ```bash
466
+ pnpm dedupe --check
467
+ ```
468
+
469
+ Inspect the result. If it reports available deduplication, run the following
470
+ separately; other failures need diagnosis, not an unconditional retry:
471
+
472
+ ```bash
473
+ pnpm dedupe
474
+ ```
475
+
476
+ Then run the checks, each separately:
477
+
478
+ ```bash
479
+ pnpm build
480
+ pnpm fix
481
+ pnpm build # only if fix changed source files
482
+ pnpm test
483
+ pnpm lint
484
+ ```
485
+
486
+ Always use bare `pnpm update -r` for the full refresh, without package selectors.
487
+ Named-package updates do not provide the same transitive refresh; do not use
488
+ `!pkg` selectors as a workaround for deferred candidates. It resolves within
489
+ the written ranges and rechecks release age without discarding `node_modules`.
490
+ In pnpm 12 it can also raise range floors in `package.json` and catalogs
491
+ (for example, `^1.8.0` to `^1.9.0`), so review the manifest and catalog diffs
492
+ after every update, not just the lockfile. Preserve excluded declarations and
493
+ undo unreviewed range changes by direct edits, then synchronize the lockfile.
494
+
495
+ Deduplication consolidates compatible references onto newer versions already
496
+ in the tree; it is not a way to hold back or downgrade a dependency. Review
497
+ production changes fully and development-only transitives under step 3's
498
+ narrower checks. If a deferred version is still allowed by a published runtime
499
+ range, use the explicit range/consumer decision path rather than fighting the
500
+ resolver with an older lockfile or an unapproved override.
501
+ After every `pnpm update -r`, confirm each accepted authored package resolved
502
+ to its accepted version. pnpm does not report a maturity holdback, so a lower
503
+ resolved version means the release is blocked by `minimumReleaseAge`: add only
504
+ that package/version to `minimumReleaseAgeExclude`, never its dependencies,
505
+ and update again. If the release still cannot resolve because its own
506
+ third-party dependencies are too young, defer that authored update, keep the
507
+ previously locked version, and report the date it becomes installable.
508
+
509
+ Compare every result with the baseline:
510
+
511
+ - **Consumer compatibility:** recheck the resolved published dependency trees
512
+ and build artifacts against the Node.js baseline, including transitive
513
+ changes from resolution or deduplication. Validate consumer installation and
514
+ representative runtime behavior at the applicable supported minimums separately
515
+ from checks needing the newer development runtime. Review `pnpm fix` changes
516
+ to `engines.node` and `devEngines.runtime`; a passing fixer or build on newer
517
+ Node.js does not establish consumer compatibility. Diff the production-engine
518
+ inventories, test the packed build in known consumers, and run fixtures at the
519
+ exact supported Node.js floor using [the verification recipes](references/verification.md).
520
+ For stable projects, do not retain a newly introduced break outside explicit
521
+ approval; present the candidate or defer it. Do not mask it with an unapproved
522
+ `engines.node` increase. For authorized breaks and zero-versioned projects,
523
+ validate the new contracts and Node.js range and report the baseline changes.
524
+ - **Logs:** for each new warning, deprecation, or peer issue, find the package
525
+ that introduced it (`pnpm why`) and search upstream for a known issue. Report
526
+ it with the link. For a peer declaration lagging a verified compatible
527
+ version, add a scoped `peerDependencyRules.allowedVersions` entry such as
528
+ `"parent@range>peer": "tested-range"` to `pnpm-workspace.yaml` in both modes,
529
+ and report the entry, the evidence, and that it suppresses a warning rather
530
+ than fixing incompatibility. Compatibility is verified when the checks pass
531
+ with the installed combination and no upstream report contradicts it. Apply
532
+ the entry even when the lagging declaration arrives through an authored
533
+ package: the consumer's `pnpm-workspace.yaml` is where the warning is
534
+ silenced, and the report flags the upstream fix separately. Do not use broad
535
+ `allowAny` or disable strict peer checks. Revisit such workarounds during
536
+ step 9.
537
+ - **Build output:** if the baseline includes build output, rebuild with the
538
+ same command and environment and diff the trees with content-hashed filenames
539
+ normalized (`name.[hash].js`). Explain every remaining difference with an
540
+ upstream changelog entry, issue, or reviewed migration/configuration change;
541
+ anything unexplained needs investigation. Unless explicitly configured,
542
+ tsdown derives its build target from `engines.node`, so an engine change can
543
+ legitimately alter emitted syntax. Record the effective targets before and
544
+ after, distinguish expected output changes from regressions, and check runtime
545
+ APIs separately because syntax lowering does not provide polyfills.
546
+ - **After `pnpm fix`:** review the diff, then rebuild and diff against the
547
+ pre-fix build. Formatting changes must produce identical output. A lint
548
+ rule's suggested fix can change behavior (default parameters do not replace
549
+ `null`); in that case keep the code and add an inline disable that says why.
550
+ If the project can or is likely to run in browsers, including alongside
551
+ Node.js, disable the offending rule for the affected shipped code when its
552
+ fix would introduce regex flags or DOM APIs unsupported by the project's
553
+ browser targets. Explain the compatibility reason and keep the compatible
554
+ code instead of applying the fix; revert the fix if already applied. This
555
+ also applies to zero-versioned projects, but does not apply to Node.js-only
556
+ projects.
557
+ Resolve remaining lint errors by hand.
558
+
559
+ On failure, make the smallest change consistent with the version policy and
560
+ rerun from the failing step:
561
+
562
+ - To tell a new failure from a pre-existing one, check the baseline logs.
563
+ `git stash` does not revert `node_modules`, so rerunning a check after
564
+ stashing still uses the new dependencies.
565
+ - To isolate a breaking update: set half the accepted updates back to their
566
+ previous specifiers, `pnpm update -r`, rerun the failing check, repeat.
567
+ For an out-of-range update, defer the culprit unless an approval or the
568
+ zero-versioned exception permits it and the migration resolves the failure.
569
+ For an already allowed runtime release, follow the range decision path in
570
+ step 3; resetting a specifier or lockfile may not exclude it.
571
+
572
+ Add focused tests only where a compatibility change touches behavior not
573
+ already covered. Review `readme.md` accuracy and automation opportunities in step 8.
574
+
575
+ ## 6. Exclusion cleanup
576
+
577
+ Only if `minimumReleaseAgeExclude` is non-empty in any `pnpm-workspace.yaml`
578
+ (at the start, or after step 5). Cleanup removes entries that no longer do
579
+ anything; it never changes which version is installed and never downgrades a
580
+ package.
581
+
582
+ Decide each entry from the publish time recorded in Setup, or look it up now:
583
+
584
+ - **Mature** (published at least 24 hours before the run): remove it; the
585
+ version resolves without it.
586
+ - **Young, authored package:** keep it while that version is the release this
587
+ run installs. Removing it would hold the package back to an older release.
588
+ - **Young, third-party package:** keep it while it holds a version that is
589
+ already locked; removing it would downgrade an installed package rather than
590
+ block a new one. Report it as pending with the date it can be removed. Do not
591
+ add new third-party entries.
592
+ - **Unused** (the version is neither locked nor targeted): remove it.
593
+
594
+ Edit the list directly, preserving neighboring keys and comments; use `[]` or
595
+ remove the key when nothing remains. Then repeat step 5's separate bare
596
+ `pnpm update -r` and `pnpm dedupe --check` invocations, followed by a separate
597
+ `pnpm dedupe` only when the check reports available changes, and run each check
598
+ separately:
599
+
600
+ ```bash
601
+ pnpm build
602
+ pnpm test
603
+ pnpm lint
604
+ ```
605
+
606
+ Compare the resolved versions with the lockfile saved before cleanup. pnpm
607
+ does not report a maturity holdback; it silently selects the newest mature
608
+ version. An authored package that resolved below its latest stable release, or
609
+ any package that resolved below its previous locked version, means a removed
610
+ entry was still needed: re-add that narrowest package/version entry and resolve
611
+ again. Never restore the whole previous list.
612
+
613
+ If `pnpm-lock.yaml` comes out byte-identical and no ranges or build settings
614
+ changed, the installed tree did not change; rerun only `pnpm lint`.
615
+
616
+ If the list was empty throughout, skip this section and continue to step 7.
617
+ If cleanup changes resolution, repeat step 5's consumer compatibility review
618
+ before reviewing documentation.
619
+
620
+ ## 7. Side effects declaration
621
+
622
+ For each published package, check that `sideEffects` in `package.json` tells
623
+ the truth about the shipped code. Bundlers drop any import they consider
624
+ side-effect-free when the declaration says `false`, and keep every module
625
+ otherwise, so a wrong `false` silently removes code consumers rely on and a
626
+ missing `false` on a pure package forfeits tree shaking.
627
+
628
+ 1. Read the declaration: `false`, an array of file patterns, `true`, or absent,
629
+ which means `true`.
630
+ 2. Inspect the published entry points and every module they reach, in the
631
+ build output that actually ships (`dist/` and the `files` list), including
632
+ bundled dependencies. Look for work that runs on import rather than on
633
+ call: assignments to globals or prototypes, polyfills, registrations such
634
+ as `customElements.define`, event listeners, process or environment setup,
635
+ CSS and asset imports, and bare imports kept only for their effects.
636
+ 3. Set the declaration to match: `false` when nothing runs on import, an array
637
+ naming the exact shipped files that do when only some do, and `true` when
638
+ most modules have effects. A package with no importable entry point, such
639
+ as a `bin`-only CLI, is unaffected either way; leave its declaration as is.
640
+ 4. Report every evaluation: the package, which modules were inspected, what
641
+ runs on import if anything, the declaration before and after, and why it
642
+ changed or stayed. A wrong `false` is a bug; a newly added `false` is a
643
+ performance improvement. Count either under the release recommendation.
644
+
645
+ ## 8. Documentation review
646
+
647
+ Review `readme.md` against the updated project. Correct stale installation
648
+ instructions, requirements, commands, examples, configuration guidance, and
649
+ feature descriptions. If `readme.md` is generated or copied from another file,
650
+ update its maintained source and regenerate it through the existing workflow.
651
+
652
+ Review each section for content that would be more reliably maintained through
653
+ mdat, especially content duplicated from package metadata, CLI help, source
654
+ files, or other documentation. Prefer a rule bundled with mdat when it fits,
655
+ then a suitable mdat plugin, or a custom rule in `mdat.config.ts` for
656
+ project-specific needs. Check the installed rules and relevant documentation
657
+ before choosing an implementation. Replace manually maintained sections where
658
+ generation reduces duplication or prevents drift while preserving their useful
659
+ content and readability; keep explanatory prose manual when automation adds
660
+ no benefit. Any new plugin dependency must satisfy the dependency review,
661
+ release maturity, and compatibility policies above.
662
+
663
+ Run the project's mdat generation and documentation checks, inspect the
664
+ rendered content for accuracy, and confirm that a second generation produces
665
+ no further changes. Validate any new dependency under steps 5 and 6 before
666
+ continuing. Include documentation corrections and mdat conversions in the
667
+ final report.
668
+
669
+ ## 9. @kitschpatrol/shared-config override minimization
670
+
671
+ Run this as the final cleanup after all accepted updates, migrations,
672
+ validation, release-age exclusion cleanup, and documentation review, before
673
+ the release recommendation and report.
674
+
675
+ Review every shared-config-related configuration file in the project root,
676
+ including dotfiles, companion ignore files, and configuration stored in
677
+ `package.json`. Include the individual `@kitschpatrol/*-config` packages used by
678
+ the project. Inventory every local override beyond the upstream default export
679
+ or equivalent base configuration: rule settings, options, ignores, file globs,
680
+ and other customizations.
681
+ Include scoped peer-warning workarounds added during the update; remove them
682
+ when upstream peer declarations now cover the tested versions.
683
+
684
+ For each override, compare it with the defaults and effective configuration of
685
+ the exact versions installed after the updates. Check the current code it
686
+ targets and, for upstream workarounds, the relevant upstream changes or fixes.
687
+ Remove overrides that duplicate current defaults, target code or files that no
688
+ longer exist, or compensate for issues now fixed upstream. Do not infer that an
689
+ override is redundant merely because checks pass while it is still enabled.
690
+
691
+ Validate removals in small groups using the affected tools' checks without
692
+ caches, covering the affected files and any files newly included by removing
693
+ ignores. Recheck effective configuration where merge order or file matching
694
+ matters. Keep overrides that still serve a concrete project requirement,
695
+ including the behavior and browser-compatibility protections from step 5, and
696
+ record why each is needed. If necessity cannot be established, retain the
697
+ override and report the unresolved evidence instead of guessing.
698
+
699
+ After changing configuration, rerun the affected build, test, type, and lint
700
+ checks and compare results and build output as in step 5. The final report must
701
+ account for every override: removed with evidence, retained with a current
702
+ justification, or unresolved with the missing evidence identified.
703
+
704
+ ## Release recommendation
705
+
706
+ Recommend `patch`, `minor`, or `major` from the final consumer-visible changes,
707
+ following the repository's release conventions. Do not infer the recommendation
708
+ from the largest dependency version bump:
709
+
710
+ - **Patch:** compatible bug or security fixes, performance improvements, or
711
+ dependency/tooling maintenance with no new consumer-facing capability. A
712
+ development-only Node.js increase with the required notice can remain a
713
+ patch, and so does a corrected or newly added `sideEffects` declaration.
714
+ - **Minor:** backward-compatible functionality or capabilities actually exposed
715
+ to consumers by the accepted updates. Explain what consumers gain; an upstream
716
+ minor or major alone is not a reason for a minor release. For zero-versioned
717
+ projects, also recommend a minor release for accepted consumer-facing breaking
718
+ changes unless the repository's pre-1.0 release conventions specify otherwise.
719
+ - **Major:** explicitly approved consumer-facing breaks in projects at 1.0.0
720
+ or later, including a raised consumer Node.js floor. A dependency major or
721
+ development-only requirement by itself does not justify a project major.
722
+
723
+ Give a short rationale per affected published package, or one recommendation
724
+ for a shared release version using the highest applicable level. If nothing
725
+ releasable changed, say no release is needed. An approved major release is
726
+ within scope; an unapproved breaking candidate stays pending or deferred.
727
+ For `0.x`, accepted consumer breaks normally require a minor, not a patch;
728
+ follow an explicit repository convention if it differs. Assess changes already
729
+ reachable through the old published ranges separately from newly introduced
730
+ consumer breaks. Recommend only; do not bump repository versions.
731
+
732
+ ## Report
733
+
734
+ State the execution mode and why it was selected. Include a `Breaking changes
735
+ available` section for deferred breaking candidates: current and proposed
736
+ versions, affected consumers/contracts, required migration, validation results
737
+ or gaps, recommended release level, and the decision still needed. Distinguish
738
+ these from applied breaks authorized explicitly or by the `0.x` policy. In
739
+ unattended mode, record decisions needed as follow-up items, not questions or
740
+ pending waits. Report unavailable checks and blocked work even when independent
741
+ updates succeeded.
742
+
743
+ Review the full diff for unrelated changes, excluded-package edits, and stray
744
+ exemptions first. Then summarize: applied updates; major decisions with
745
+ sources; consumer impact, approvals, declined/deferred decisions, and breaks
746
+ accepted under the zero-versioned policy;
747
+ output differences and their upstream or migration explanations;
748
+ consumer Node.js requirements before and after, development Node.js changes and
749
+ their `devEngines.runtime` notices; the release recommendation and its rationale;
750
+ baseline lockfile drift; the `packageManager` version chosen and its source;
751
+ `sideEffects` evaluations with their evidence and outcome; runtime ranges,
752
+ fresh-install exposure, and approved
753
+ range restrictions; TypeScript migrations; new warnings and scoped peer
754
+ workarounds; what was validated, at which exact Node.js versions, and in which
755
+ known consumers, including CLI/fixture comparisons and unavailable baselines;
756
+ remaining `minimumReleaseAgeExclude` entries with reasons and the dates they can
757
+ be removed; deferred upgrades,
758
+ documentation corrections and mdat conversions;
759
+ shared-config overrides removed or retained with their justifications and any
760
+ unresolved override reviews; unresolved failures, checks that could not run,
761
+ and steps run differently because of the environment. Passing checks do not
762
+ prove the absence of breaking changes. Leave everything uncommitted.
@@ -0,0 +1,168 @@
1
+ # Consumer verification recipes
2
+
3
+ Use these recipes during Setup and step 5. Save commands, versions, raw logs,
4
+ and results under `node_modules/.cache/ksc-update/`. Run pnpm commands as separate
5
+ bare invocations; save their captured output in a later operation so sandbox
6
+ command exclusions still match.
7
+
8
+ ## Production-tree engines
9
+
10
+ For each published package, run in its directory before and after updates:
11
+
12
+ ```bash
13
+ pnpm list --prod --depth Infinity --json
14
+ ```
15
+
16
+ Save stdout as `production-tree.json` in the corresponding baseline or updated
17
+ results folder. If stdout contains lines beginning with `[WARN]`, preserve those
18
+ warnings separately and remove only those lines before parsing. Require a
19
+ successful command and valid JSON; do not extract an arbitrary JSON-looking
20
+ substring from failed output. In a monorepo, include every published package,
21
+ not only a private root with no production dependencies.
22
+
23
+ The list supplies installed paths; read each installed `package.json` for its
24
+ actual engine declaration. Save this helper in the run's cache as
25
+ `production-engines.mjs` and run it with the saved JSON path as its argument:
26
+
27
+ ```js
28
+ import { readFileSync } from 'node:fs'
29
+ import { join } from 'node:path'
30
+
31
+ const roots = JSON.parse(readFileSync(process.argv[2], 'utf8'))
32
+ if (!Array.isArray(roots)) {
33
+ throw new TypeError('Expected pnpm list JSON array')
34
+ }
35
+
36
+ const manifests = new Map()
37
+ const rows = new Set()
38
+
39
+ function visit(pkg) {
40
+ if (!pkg.path) {
41
+ throw new Error(`Missing installed path: ${pkg.name ?? pkg.version}`)
42
+ }
43
+
44
+ if (!manifests.has(pkg.path)) {
45
+ manifests.set(pkg.path, JSON.parse(readFileSync(join(pkg.path, 'package.json'), 'utf8')))
46
+ }
47
+
48
+ const manifest = manifests.get(pkg.path)
49
+ rows.add(
50
+ JSON.stringify({
51
+ name: manifest.name,
52
+ node: manifest.engines?.node,
53
+ version: manifest.version,
54
+ }),
55
+ )
56
+ for (const group of ['dependencies', 'optionalDependencies']) {
57
+ const children = Object.values(pkg[group] ?? {})
58
+ for (const child of children) {
59
+ visit(child)
60
+ }
61
+ }
62
+ }
63
+
64
+ for (const root of roots) {
65
+ visit(root)
66
+ }
67
+
68
+ process.stdout.write(`${[...rows].toSorted().join('\n')}\n`)
69
+ ```
70
+
71
+ Save the resulting inventory and diff it after resolution and deduplication.
72
+ Investigate missing installed paths instead of omitting them. An omitted `node`
73
+ field means undeclared support, not proof of compatibility. Check full semver ranges
74
+ against every supported consumer Node.js line; check platform-specific optional
75
+ dependencies and shipped/bundled code separately because one host's installed
76
+ tree cannot prove their compatibility. This inventory does not replace the
77
+ newest-in-range registry review.
78
+
79
+ ## Known consumers and packed builds
80
+
81
+ Read confirmed consumers from the repository's existing `CLAUDE.md` or
82
+ `AGENTS.md`. A useful `Known consumers` entry contains the consumer's name and
83
+ path, which package/imports/CLI it uses, its test command, and representative
84
+ CLI commands and fixture inputs. Preserve existing instructions; add only facts
85
+ confirmed by the user or repository evidence.
86
+
87
+ 1. Create a temporary directory under `${TMPDIR:-/tmp}`. Clone each local
88
+ consumer into it with `git clone --no-hardlinks <consumer-path> <temp-copy>`.
89
+ Record the commit. A clone excludes uncommitted changes; if those matter,
90
+ explicitly copy the needed working-tree files and record that baseline.
91
+ Do not modify the consumer's original checkout.
92
+ 2. Install and test the copied consumer with its starting lockfile. Save
93
+ its tests' results and representative CLI stdout, stderr, and exit codes.
94
+ Preserve this copy for baseline comparison.
95
+ 3. Build the updated package with its development Node.js version and run
96
+ `pnpm pack` as a bare command in that package. Inspect the archive's contents
97
+ and manifest, including exported entrypoints, engines, and dependency ranges.
98
+ 4. In a second consumer copy, replace the consumed dependency with a `file:`
99
+ specifier pointing to that tarball, using a direct manifest edit. Install
100
+ and rerun the same tests and CLI fixtures. If a workspace-local dependency
101
+ or alias is involved, replace the actual consuming edge and verify the
102
+ resolved path/version; do not assume a root-only edit reaches it. A linked
103
+ built package is useful for iteration, but the tarball verifies what ships
104
+ and avoids a link silently borrowing the producer's `node_modules`.
105
+ 5. Diff CLI output and fixture results. Inspect semantic differences, including
106
+ behavior JSON may hide, such as object prototypes, missing versus undefined
107
+ values, ordering, and error handling. Explain each change against accepted
108
+ migrations and consumer expectations.
109
+
110
+ Also test a fresh isolated install of the published release with the newest
111
+ dependencies its ranges allow. Keep this distinct from the published release
112
+ running on its old dependencies: reproduce the latter from a known historical
113
+ lockfile or preserved installation where possible. A registry tarball alone
114
+ does not recover the old transitive tree. If it cannot be reconstructed, report
115
+ that gap instead of labeling a fresh install the historical baseline.
116
+
117
+ ## Exact Node.js floor
118
+
119
+ Use the exact minimum supported release, not just the same major or the active
120
+ developer runtime. For disjoint supported Node.js lines, check each relevant
121
+ minimum. For an approved engine bump, compare the old release at its old floor
122
+ with the updated package at its new floor and on an overlapping supported
123
+ runtime where available.
124
+
125
+ If the version manager has no suitable installed binary, download the exact
126
+ official archive from `https://nodejs.org/dist/v<version>/` for the host's OS and
127
+ architecture into a temporary folder, alongside `SHASUMS256.txt`. Verify the
128
+ archive's SHA-256 against that file, then extract it there. Use the resulting
129
+ Node binary by absolute path; do not install globally or change the user's
130
+ default runtime. Record `<absolute-node-path> --version` with the results.
131
+
132
+ Install/build with the development runtime if the tooling requires it, then
133
+ invoke the packed package's built JavaScript entrypoint and runtime fixtures
134
+ with the exact-floor binary. For subprocesses that use `env node`, put that
135
+ binary's directory first in the test process's `PATH` only. Confirm which
136
+ binary the child process uses. If the test runner itself requires newer Node,
137
+ run a small fixture harness or the built CLI directly at the floor; running
138
+ only the newer test runner is not floor validation. Report when installation
139
+ or execution on the exact runtime could not be verified.
140
+
141
+ Record stdout, stderr, exit codes, and relevant API results for identical
142
+ fixtures across runtimes and dependency baselines. Normalize only known
143
+ nondeterministic fields using the same documented transformation for all runs.
144
+ Save SHA-256 digests of normalized fixture output for quick comparison, retain
145
+ the actual output for reviewing differences, and add targeted assertions for
146
+ behavior that serialization omits.
147
+
148
+ ## Tool references
149
+
150
+ Check these primary sources or the installed tool's help when behavior differs:
151
+
152
+ - [GitHub Actions variables](https://docs.github.com/en/actions/reference/workflows-and-actions/variables):
153
+ `GITHUB_ACTIONS` and `CI` identify unattended execution in Actions.
154
+ - [pnpm update](https://pnpm.io/cli/update): manifest/catalog writes and selectors.
155
+ This workflow uses a bare update for its full dependency refresh; do not
156
+ assume exclusion syntax works identically in every pnpm implementation.
157
+ - [pnpm dedupe](https://pnpm.io/cli/dedupe) and
158
+ [pnpm list](https://pnpm.io/cli/list): deduplication checks and tree inventories.
159
+ - [pnpm settings](https://pnpm.io/settings) and
160
+ [peer rules](https://pnpm.io/settings/peer-dependencies#peerdependencyrulesallowedversions):
161
+ current configuration locations and scoped warning allowances. Current pnpm
162
+ uses `.npmrc` for registry/auth settings, not all policy settings.
163
+ - [Taze](https://github.com/antfu-collective/taze): candidate filters and maturity.
164
+ - [Actions Up](https://github.com/azat-io/actions-up#update-style): SHA pinning.
165
+ - [tsdown target](https://tsdown.dev/options/target): implicit engine-derived
166
+ targets affect emitted syntax and do not provide API polyfills.
167
+ - [Node.js release archives](https://nodejs.org/dist/): exact runtime downloads
168
+ and checksum manifests.