@kitschpatrol/shared-config 8.11.0 → 8.12.0

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.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)}"`)+`
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.0",
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.0",
58
+ "@kitschpatrol/eslint-config": "8.12.0",
59
+ "@kitschpatrol/knip-config": "8.12.0",
60
+ "@kitschpatrol/mdat-config": "8.12.0",
61
+ "@kitschpatrol/prettier-config": "8.12.0",
62
+ "@kitschpatrol/remark-config": "8.12.0",
63
+ "@kitschpatrol/repo-config": "8.12.0",
64
+ "@kitschpatrol/stylelint-config": "8.12.0",
65
+ "@kitschpatrol/typescript-config": "8.12.0",
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,686 @@
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 and
48
+ peer-warning suppressions. Report missing consumer information, credentials,
49
+ permissions, or validation resources instead of requesting them. Continue
50
+ independent work; if a required prerequisite prevents progress, report the
51
+ affected work as blocked or incomplete without waiting or bypassing it.
52
+
53
+ Review and validation remain required in both modes. Defer candidates whose
54
+ impact cannot be established or whose migration cannot be validated. If safe
55
+ updates cannot be isolated without an unapproved change, leave those updates
56
+ unapplied and explain the blocker. Report incompatible releases already allowed
57
+ by published runtime ranges as existing consumer exposure; an older lockfile
58
+ does not resolve that exposure.
59
+
60
+ Produce changes and a report suitable for later PR automation. This skill does
61
+ not create workflows or pull requests, or split work into multiple PRs. If
62
+ nothing can be applied, produce the report without manufacturing a change.
63
+
64
+ ## Policy
65
+
66
+ - **Compatible by default at 1.0.0 or later:** assess the effect on this repository
67
+ and its consumers, not just the dependency's version label. Minor and patch
68
+ updates can break compatibility; a dependency major can still be compatible.
69
+ Present useful breaking candidates with their impact, migration, validation,
70
+ and release implications before asking for approval; apply only within the
71
+ approved scope. Existing explicit approval remains valid. Defer unapproved
72
+ breaks and updates whose impact cannot be established. This includes authored
73
+ packages, production transitives, and workflow actions.
74
+ - **Zero-versioned projects:** if the project's current release version has
75
+ major version zero (`0.x.y`), any breaking change within this update workflow
76
+ is permitted in both interactive and unattended modes, including changes to
77
+ public contracts and consumer Node.js requirements. This exception overrides
78
+ compatibility-preservation requirements below. Use the project's version, not
79
+ the dependency's version; in monorepos,
80
+ assess each affected published package or shared release version, not just a
81
+ private workspace root. Review and report breaking changes, make required
82
+ migrations, and validate the resulting behavior. All other policies still
83
+ apply.
84
+ - **Release maturity:** third-party releases must be at least 24 hours old
85
+ (`--maturity-period 1` for Taze, `--min-age 1` for Actions Up, pnpm
86
+ `minimumReleaseAge` of 1440 minutes). Never disable installation safeguards to
87
+ make an update succeed.
88
+ - **Authored packages:** packages owned by `kitschpatrol` (discovered at runtime
89
+ in Setup) are exempt from maturity and target their latest stable release,
90
+ including majors. The exemption does not bypass security or compatibility
91
+ review.
92
+ - **Consumer ranges:** review the newest published version allowed by each
93
+ runtime dependency's declared range, as well as the lockfile. A fresh consumer
94
+ install can resolve that version even when CI locks an older one. Holding
95
+ back an in-range release requires a genuinely restrictive published range
96
+ (for example, `~1.8.0` instead of `^1.8.0` to exclude `1.9.0`), not just an
97
+ older lockfile entry. Obtain explicit approval before narrowing a runtime
98
+ range to hold a release back, even for a zero-versioned project; explain the
99
+ consumer effect. Do not use local overrides as a substitute for that decision.
100
+ - **TypeScript:** select a compiler within the peer range of the accepted
101
+ `@kitschpatrol/typescript-config` version, intersected with other required
102
+ peers. Do not hard-code a compiler version. Offer the migration when an
103
+ authored major requires a newer compiler; see step 3. If that config is not
104
+ used, follow the project's compiler policy and peer requirements.
105
+ - **Node types:** preserve `@types/node` declarations, including catalogs and
106
+ overrides, unless a reviewed migration has explicit authorization to change
107
+ them. New compiler libraries do not establish runtime API availability.
108
+ - **Package manager:** only Setup manages `packageManager`. Exclude `pnpm`
109
+ from Taze discovery and writes.
110
+ - **No weakening:** do not relax tests, lint rules, type checking, or security
111
+ settings to get a pass; do not blindly accept changed snapshots or generated
112
+ output. Compatibility-preserving lint disables described in step 5 are an
113
+ exception.
114
+ - **No new noise:** every check is compared against the Setup baseline. A new
115
+ warning, deprecation, or peer issue is a finding to trace and report, not
116
+ background noise. Report compatibility-preserving lint disables from step 5;
117
+ ask before silencing other findings.
118
+
119
+ ## Environment
120
+
121
+ Shell variables do not persist between commands. Save anything later steps need
122
+ under `node_modules/.cache/ksc-update/` and pass values literally.
123
+
124
+ Plan network access for `registry.npmjs.org`, `raw.githubusercontent.com`,
125
+ `api.github.com`, and `nodejs.org`; identify any additional upstream hosts as
126
+ needed. Prefer registry metadata and raw changelog files over GitHub API calls
127
+ for package reviews. Cache responses and share them between reviewers.
128
+
129
+ Under Claude Code's sandbox:
130
+
131
+ - **pnpm store:** pnpm 12 cannot take its store lock inside the sandbox
132
+ (`ERR_PNPM_STORE_DIR_OPEN_OPERATION_LOCK`), even with writable store, cache,
133
+ and temp directories. `pnpm install`, `pnpm update`, `pnpm dedupe`,
134
+ `pnpx taze`, and `pnpx actions-up` must be listed in the user's
135
+ `sandbox.excludedCommands` and `permissions.allow`. If the Setup install fails
136
+ this way, stop the affected work and ask the user to add them in interactive
137
+ mode; in unattended mode, report the missing prerequisite. Do not route around
138
+ it. Run these commands bare (no pipes, redirects, or command substitution) so the
139
+ exclusions match. Run each as its own invocation, without `&&` or `||`.
140
+ Save captured tool output to log files in a separate operation.
141
+ - **Proxy:** Node-based tools ignore the proxy variables. If one fails with
142
+ `ENOTFOUND`, rerun it with `NODE_USE_ENV_PROXY=1`.
143
+ - **Sockets:** the `tsx` CLI cannot open its IPC socket. Run the same script
144
+ with `node --import tsx <script>` and report the substitution.
145
+ - **GitHub API:** without `GITHUB_TOKEN`, GitHub allows 60 requests per hour.
146
+ One Actions Up run plus a few release-note lookups can exhaust it. The sandbox
147
+ cannot read `gh` credentials, so budget lookups or ask for a token in
148
+ interactive mode. In unattended mode, report any review blocked by missing
149
+ credentials or exhausted API limits.
150
+ - **JSON output:** pnpm can emit `[WARN]` lines on stdout. Sync `packageManager`
151
+ before parsing pnpm JSON; if warnings remain, save them separately, remove
152
+ only lines beginning with `[WARN]`, and parse the rest strictly. Do not hide
153
+ malformed output or other diagnostics.
154
+ - **Supply-chain settings:** an unreadable global config or `~/.npmrc` can make
155
+ a sandboxed config query return `undefined`; this does not mean the policy is
156
+ disabled. Read accessible workspace settings and record pnpm's successful
157
+ `Lockfile passes supply-chain policies` message from the actual install.
158
+ That message confirms the active checks passed, not the numeric release-age
159
+ threshold. Verify the required 1440 minutes from accessible configuration or
160
+ release timestamps when the effective setting cannot be read. Do not disable
161
+ safeguards or access private config through a workaround.
162
+
163
+ ## Setup
164
+
165
+ 1. Read repository instructions. Note uncommitted changes in the working tree
166
+ and preserve them. If files change during the run that you did not touch,
167
+ leave them alone and report them.
168
+ Read the `Known consumers` section in the project's `CLAUDE.md` or `AGENTS.md`.
169
+ Maintain a short section in the existing instruction file using confirmed
170
+ consumer names, local paths, consumed packages, test commands, and CLI
171
+ fixtures. If none are documented, ask for known consumers in interactive
172
+ mode; in unattended mode, report the missing information and use available
173
+ repository evidence. Continue independent review; do not invent consumers or
174
+ treat example paths as real. Report unavailable consumer validation as a gap.
175
+ 2. Map the workspace: root, every maintained package root, catalogs, overrides,
176
+ `pnpm-workspace.yaml`, Taze config. Exclude fixtures, generated files, and
177
+ dependency directories. Run each step once per shared-lockfile workspace and
178
+ separately in any independent package root. Check Taze config for anything
179
+ that overrides mode, adds maturity exemptions, or auto-writes/installs.
180
+ Record current project and published package versions to determine where the
181
+ zero-versioned exception applies.
182
+ Create the baseline folder and preserve the starting manifests and lockfile
183
+ before the package-manager sync or any other edit.
184
+ 3. Sync `packageManager` before installs and parsed pnpm output. Read the actual
185
+ installed pnpm from an empty directory so the project's stale pin cannot
186
+ select or download another version:
187
+
188
+ ```bash
189
+ pnpm --version
190
+ ```
191
+
192
+ Run that command with a new empty directory under `${TMPDIR:-/tmp}` as its
193
+ working directory. Then edit only the workspace root's `packageManager`
194
+ directly to `pnpm@<observed-version>`, preserving all other manifest fields.
195
+ Use the editor or a structured JSON edit, not `pnpm pkg set`; use direct
196
+ edits for later specifiers and engines too. pnpm commands can trigger
197
+ version-manager downloads after the pin changes. If this crosses a pnpm
198
+ major, report it and verify the CI installer supports the selected version.
199
+
200
+ 4. Install the starting dependency declarations and record a baseline before
201
+ dependency updates. Keep the original files saved in Setup 2 even if
202
+ installation fails.
203
+
204
+ ```bash
205
+ pnpm install --frozen-lockfile
206
+ ```
207
+
208
+ On `ERR_PNPM_OUTDATED_LOCKFILE` only, run this as a separate bare command:
209
+
210
+ ```bash
211
+ pnpm install --no-frozen-lockfile
212
+ ```
213
+
214
+ Save the lockfile diff against the starting copy in the baseline folder and
215
+ report the drift. Treat checks after repair as the repaired baseline, not as
216
+ proof that the original locked tree passed. Other installation failures need
217
+ their own diagnosis; do not generalize this fallback or relax safeguards.
218
+
219
+ Save the output of each install attempt and of `pnpm build`, `pnpm test`, and
220
+ `pnpm lint` to `node_modules/.cache/ksc-update/baseline/`. If the repository
221
+ produces build output (`dist/` or similar), copy it there as well. Use the
222
+ repository's switches to disable nondeterministic build steps (encryption,
223
+ timestamps) and note the exact command and environment; later comparison
224
+ builds must match them. Baseline failures are pre-existing; record them.
225
+
226
+ Record each published package's declared `engines.node`, effective consumer
227
+ Node.js minimum and supported range, and separate development requirement.
228
+ Include published dependency trees and built artifacts, not just the root
229
+ manifest or current CI runtime. If no Node.js support policy is declared,
230
+ establish the existing requirement from the published code and dependencies;
231
+ do not assume the developer's Node.js version is the consumer minimum.
232
+ Save production-tree engine inventories and baseline consumer results using
233
+ [the verification recipes](references/verification.md). Distinguish the
234
+ starting locked tree, the newest versions a fresh consumer install can
235
+ resolve, and the published release with its old dependencies when available.
236
+
237
+ 5. Discover authored packages:
238
+
239
+ ```bash
240
+ set -euo pipefail
241
+ mkdir -p node_modules/.cache/ksc-update
242
+ curl -fsS https://registry.npmjs.org/-/user/kitschpatrol/package \
243
+ | jq -er 'keys | join(",") | select(length > 0)' \
244
+ > node_modules/.cache/ksc-update/authored-packages.txt
245
+ ```
246
+
247
+ If registry discovery fails or returns empty, stop and report. Intersect the
248
+ result with direct dependency names across the maintained manifests in this
249
+ workspace, including dev, optional, and peer dependencies and dependencies
250
+ using catalogs or npm aliases. Save the intersection separately; an empty
251
+ intersection is valid. `<authored-packages>` below means only those exact
252
+ matching names, comma-separated and passed literally, with no wildcards.
253
+ Omit `--maturity-period-exclude` if the intersection is empty. Keep the full
254
+ ownership list only for evaluating other packages encountered later.
255
+
256
+ ## 1. Workflow actions
257
+
258
+ ```bash
259
+ pnpx actions-up --yes --min-age 1 --style sha
260
+ ```
261
+
262
+ Read the whole output. Actions Up can print "All actions are up to date!" after
263
+ checks failed; any `Skipped`, `Failed`, or `Rate Limit` line means the run is
264
+ incomplete. Fix the cause and rerun.
265
+
266
+ Review the diff: major action bumps, runner, input, output, and permission
267
+ changes. Every external GitHub Action and reusable workflow must be pinned to
268
+ its full commit SHA, including `kitschpatrol/*`; retain version comments for
269
+ readability. Do not exclude first-party actions from pinning or allow their
270
+ floating refs in zizmor's `unpinned-uses` policy. Resolve skipped branch/tag
271
+ refs and unchanged versions to verified commits too; if an update is deferred,
272
+ pin the retained version. Check existing zizmor configuration for exceptions
273
+ that contradict this policy. Pin container actions by digest; local `./` actions
274
+ use the checked-out tree. Keep only reviewed changes without disturbing user
275
+ edits. Local tests say nothing about remote workflows.
276
+
277
+ ## 2. Discover all candidates (read-only)
278
+
279
+ ```bash
280
+ pnpx taze major -r --force --include-locked \
281
+ --no-github-actions --no-node-version \
282
+ --exclude '@types/node,pnpm' \
283
+ --maturity-period 1 \
284
+ --maturity-period-exclude '<authored-packages>'
285
+ ```
286
+
287
+ Major mode lists every available update, minor and major. Confirm authored
288
+ packages are offered their latest stable release, not the latest within their
289
+ current major. Do not opt into new prerelease channels. Taze's newest candidate
290
+ does not replace the separate newest-in-range runtime review in step 3.
291
+
292
+ ## 3. Evaluate candidates
293
+
294
+ ### Runtime ranges and review scope
295
+
296
+ For each runtime dependency of a published package, inspect registry metadata
297
+ for the newest stable version satisfying its current declared range, accounting
298
+ for catalogs and aliases. Record declared range, locked version, newest allowed
299
+ version, and proposed range/version. Review that newest allowed release even
300
+ if it is already in range or too young for this run to install; consumer
301
+ package managers may not enforce this repository's maturity policy. Keep the
302
+ installation safeguards and report any untested consumer exposure.
303
+
304
+ Review direct candidates and production-tree changes fully. For development-only
305
+ transitives, check advisories, deprecations, and engines; investigate release
306
+ notes only when those checks, validation failures, or output differences give
307
+ cause. A package bundled or shipped with a CLI/config package belongs to the
308
+ consumer review even if this repository also uses it as development tooling.
309
+
310
+ An in-range behavior change may already reach the published release's consumers.
311
+ Do not describe an older lockfile as protecting them or infer a new breaking
312
+ release solely from the lockfile movement. Test the published release and
313
+ updated build against actual consumer resolution, and separately preserve the
314
+ old locked baseline. To hold back an incompatible allowed release, present a
315
+ specific narrower range and obtain the policy's required approval.
316
+
317
+ ### Breaking candidates
318
+
319
+ Track breaking-candidate decisions throughout the run; report applied breaks
320
+ and deferred opportunities separately.
321
+ For each candidate, identify affected APIs, behavior, Node.js support, known
322
+ consumers, migration work, validation results or gaps, and release implications.
323
+ Prepare a concrete proposal and, where useful, validate a trial migration in an
324
+ isolated copy before seeking a decision. For stable projects in interactive
325
+ mode, ask explicitly whether to accept each reviewed break unless that exact
326
+ scope was already authorized; continue independent updates while awaiting the
327
+ answer. In unattended mode, defer unapproved stable-project breaks and include
328
+ them under `Breaking changes available` in the report without asking. General
329
+ permission to update dependencies is not approval for an unspecified break.
330
+ For `0.x` projects, both modes authorize reviewed breaking migrations without
331
+ another question, but runtime-range narrowing still needs explicit approval.
332
+ Record accepted, declined, pending, and deferred decisions separately.
333
+
334
+ ### Node.js compatibility
335
+
336
+ For every candidate, minor and patch included, distinguish consumer requirements
337
+ from development requirements:
338
+
339
+ - **Consumers:** for projects at 1.0.0 or later, keep the user-facing Node.js
340
+ minimum and supported range unchanged unless explicitly approved under
341
+ `Breaking candidates`. A higher effective consumer minimum or loss of any
342
+ supported Node.js version is a break even if `engines.node` is left unchanged.
343
+ Check direct, transitive, optional, peer, and workspace dependencies used by
344
+ published packages, plus bundled code, emitted syntax, and runtime APIs.
345
+ A tool used only for development here is still a consumer dependency if it
346
+ ships in a published CLI or configuration package. Compare
347
+ full engine ranges, not just the lowest version in each range.
348
+ For authorized breaks or zero-versioned projects, update published
349
+ `engines.node` to reflect the actual supported range and validate against it.
350
+ - **Development:** a higher Node.js requirement for development-only tools,
351
+ builds, or CI is allowed when published artifacts remain compatible. If it
352
+ exceeds the consumer requirement, add or update the Node.js entry in the
353
+ relevant `package.json`'s `devEngines.runtime` with `name: "node"` and a
354
+ `version` range covering the actual development requirement. Use the workspace
355
+ root for shared tooling and package manifests for package-specific tooling;
356
+ preserve other runtime entries and settings. This is the development Node.js
357
+ dependency notice; do not raise published `engines.node` to satisfy dev tools.
358
+ Align development version pins and build CI as needed while retaining consumer
359
+ compatibility checks at the applicable supported minimums (the original ones
360
+ unless an approved break or the zero-versioned exception changes them).
361
+
362
+ ### TypeScript migrations
363
+
364
+ Read the accepted `@kitschpatrol/typescript-config` release's TypeScript peer
365
+ range and base configuration. If it requires a new compiler major, offer that
366
+ migration with its concrete effects rather than automatically deferring the
367
+ authored update. Apply it within existing authorization; ask only for additional
368
+ breaking or Node-types changes that the policy actually requires approval for.
369
+
370
+ - Add `"node"` to `compilerOptions.types` where Node globals are needed,
371
+ preserving other required type entries and the separation from browser code.
372
+ - Fix errors exposed by newly enabled strict options; do not disable those
373
+ options just to pass.
374
+ - Check the new base `lib`, emitted syntax, declarations, and APIs used by
375
+ shipped code against the consumer Node.js floor and browser targets. A newer
376
+ `lib` can make unavailable APIs type-check; it does not polyfill them.
377
+
378
+ Run type checks, build, and consumer validation after the migration, retaining
379
+ the distinction between development and consumer Node.js requirements.
380
+
381
+ ### Compatibility review
382
+
383
+ For direct candidates and production-tree changes, read relevant upstream
384
+ release notes, migration guides, and advisories, and assess:
385
+
386
+ - **Security:** vulnerabilities fixed or introduced, ownership or publishing
387
+ anomalies, new install scripts or permissions.
388
+ - **Breaking:** APIs, types, defaults, configuration, module format, peer
389
+ ranges, runtime requirements, measured against this repository's actual use.
390
+ - **Coupling:** peer and engine requirements against the versions this update
391
+ will produce, not the current ones. A major can require a newer minor of a
392
+ sibling package (`@astrojs/mdx` 8 requires `astro` 7.2.10 or later).
393
+ - **Worth it:** fixes and performance adoptable under the version policy.
394
+ Note features exposed by the update and optional feature opportunities for the
395
+ report; do not implement new features as extra work.
396
+
397
+ When delegation is available, run independent release-note reviews in parallel,
398
+ split into authored packages, remaining runtime packages, and remaining direct
399
+ development packages. Give each reviewer its disjoint package list, a short
400
+ survey of actual usage and relevant source paths, consumer requirements, and
401
+ the version policy. Reviewers return decisions and sources without editing
402
+ dependencies; the primary agent resolves coupling and combines the results.
403
+ Share cached metadata and the GitHub API budget. Review sequentially when
404
+ delegation is unavailable.
405
+
406
+ Record current → proposed version, decision, and sources. Accept only when
407
+ compatibility is preserved, an explicit approval covers the break, or the
408
+ zero-versioned exception permits it; defer the rest with a reason. Do not
409
+ defer a migration merely because it needs a major release recommendation.
410
+
411
+ ## 4. Apply
412
+
413
+ Apply only accepted candidates, including minors and patches, by exact package
414
+ name. Major mode can apply all accepted update sizes:
415
+
416
+ ```bash
417
+ pnpx taze major -rw --force --include-locked \
418
+ --no-github-actions --no-node-version \
419
+ --include 'pkg-a,pkg-b' \
420
+ --exclude '@types/node,pnpm' \
421
+ --maturity-period 1 \
422
+ --maturity-period-exclude '<authored-packages>'
423
+ ```
424
+
425
+ Replace the example names with the accepted set; skip the command if it is
426
+ empty. If only an older compatible target was accepted, set that reviewed
427
+ version manually instead of letting Taze choose the latest. Then check the
428
+ diff: every manifest and catalog covered, only reviewed versions applied, no
429
+ excluded or deferred update slipped in, range style and `workspace:` references
430
+ intact. A package accepted in one workspace package is not automatically
431
+ accepted in every other one. Hand-edit anything Taze missed (catalogs,
432
+ overrides), and add any required development Node.js notice. Use direct edits
433
+ for reviewed compiler versions inside the required peer range and for
434
+ authorized Node-types changes; do not let Taze choose an incompatible compiler.
435
+ Verify `packageManager` remains at Setup's selected version.
436
+
437
+ ## 5. Resolve and validate
438
+
439
+ Run each command as a separate invocation:
440
+
441
+ ```bash
442
+ pnpm update -r
443
+ ```
444
+
445
+ ```bash
446
+ pnpm dedupe --check
447
+ ```
448
+
449
+ Inspect the result. If it reports available deduplication, run the following
450
+ separately; other failures need diagnosis, not an unconditional retry:
451
+
452
+ ```bash
453
+ pnpm dedupe
454
+ ```
455
+
456
+ Then run the checks, each separately:
457
+
458
+ ```bash
459
+ pnpm build
460
+ pnpm fix
461
+ pnpm build # only if fix changed source files
462
+ pnpm test
463
+ pnpm lint
464
+ ```
465
+
466
+ Always use bare `pnpm update -r` for the full refresh, without package selectors.
467
+ Named-package updates do not provide the same transitive refresh; do not use
468
+ `!pkg` selectors as a workaround for deferred candidates. It resolves within
469
+ the written ranges and rechecks release age without discarding `node_modules`.
470
+ In pnpm 12 it can also raise range floors in `package.json` and catalogs
471
+ (for example, `^1.8.0` to `^1.9.0`), so review the manifest and catalog diffs
472
+ after every update, not just the lockfile. Preserve excluded declarations and
473
+ undo unreviewed range changes by direct edits, then synchronize the lockfile.
474
+
475
+ Deduplication consolidates compatible references onto newer versions already
476
+ in the tree; it is not a way to hold back or downgrade a dependency. Review
477
+ production changes fully and development-only transitives under step 3's
478
+ narrower checks. If a deferred version is still allowed by a published runtime
479
+ range, use the explicit range/consumer decision path rather than fighting the
480
+ resolver with an older lockfile or an unapproved override.
481
+ If an authored release is blocked by `minimumReleaseAge`, add only that
482
+ package/version to `minimumReleaseAgeExclude`; never its dependencies.
483
+
484
+ Compare every result with the baseline:
485
+
486
+ - **Consumer compatibility:** recheck the resolved published dependency trees
487
+ and build artifacts against the Node.js baseline, including transitive
488
+ changes from resolution or deduplication. Validate consumer installation and
489
+ representative runtime behavior at the applicable supported minimums separately
490
+ from checks needing the newer development runtime. Review `pnpm fix` changes
491
+ to `engines.node` and `devEngines.runtime`; a passing fixer or build on newer
492
+ Node.js does not establish consumer compatibility. Diff the production-engine
493
+ inventories, test the packed build in known consumers, and run fixtures at the
494
+ exact supported Node.js floor using [the verification recipes](references/verification.md).
495
+ For stable projects, do not retain a newly introduced break outside explicit
496
+ approval; present the candidate or defer it. Do not mask it with an unapproved
497
+ `engines.node` increase. For authorized breaks and zero-versioned projects,
498
+ validate the new contracts and Node.js range and report the baseline changes.
499
+ - **Logs:** for each new warning, deprecation, or peer issue, find the package
500
+ that introduced it (`pnpm why`) and search upstream for a known issue. Report
501
+ it with the link. For a peer declaration lagging a verified compatible
502
+ version, offer a scoped `peerDependencyRules.allowedVersions` entry such as
503
+ `"parent@range>peer": "tested-range"` in `pnpm-workspace.yaml`, explaining
504
+ that it suppresses a warning rather than fixing incompatibility. Show the
505
+ exact entry and evidence, and ask before applying unless already authorized.
506
+ Do not use broad `allowAny` or disable strict peer checks. Revisit such
507
+ workarounds during step 8.
508
+ - **Build output:** if the baseline includes build output, rebuild with the
509
+ same command and environment and diff the trees with content-hashed filenames
510
+ normalized (`name.[hash].js`). Explain every remaining difference with an
511
+ upstream changelog entry, issue, or reviewed migration/configuration change;
512
+ anything unexplained needs investigation. Unless explicitly configured,
513
+ tsdown derives its build target from `engines.node`, so an engine change can
514
+ legitimately alter emitted syntax. Record the effective targets before and
515
+ after, distinguish expected output changes from regressions, and check runtime
516
+ APIs separately because syntax lowering does not provide polyfills.
517
+ - **After `pnpm fix`:** review the diff, then rebuild and diff against the
518
+ pre-fix build. Formatting changes must produce identical output. A lint
519
+ rule's suggested fix can change behavior (default parameters do not replace
520
+ `null`); in that case keep the code and add an inline disable that says why.
521
+ If the project can or is likely to run in browsers, including alongside
522
+ Node.js, disable the offending rule for the affected shipped code when its
523
+ fix would introduce regex flags or DOM APIs unsupported by the project's
524
+ browser targets. Explain the compatibility reason and keep the compatible
525
+ code instead of applying the fix; revert the fix if already applied. This
526
+ also applies to zero-versioned projects, but does not apply to Node.js-only
527
+ projects.
528
+ Resolve remaining lint errors by hand.
529
+
530
+ On failure, make the smallest change consistent with the version policy and
531
+ rerun from the failing step:
532
+
533
+ - To tell a new failure from a pre-existing one, check the baseline logs.
534
+ `git stash` does not revert `node_modules`, so rerunning a check after
535
+ stashing still uses the new dependencies.
536
+ - To isolate a breaking update: set half the accepted updates back to their
537
+ previous specifiers, `pnpm update -r`, rerun the failing check, repeat.
538
+ For an out-of-range update, defer the culprit unless an approval or the
539
+ zero-versioned exception permits it and the migration resolves the failure.
540
+ For an already allowed runtime release, follow the range decision path in
541
+ step 3; resetting a specifier or lockfile may not exclude it.
542
+
543
+ Add focused tests only where a compatibility change touches behavior not
544
+ already covered. Review `readme.md` accuracy and automation opportunities in step 7.
545
+
546
+ ## 6. Exclusion cleanup
547
+
548
+ Only if `minimumReleaseAgeExclude` is non-empty in any `pnpm-workspace.yaml`
549
+ (at the start, or after step 5). Empty the list (`[]` or remove the key),
550
+ preserving neighboring keys and comments. Repeat step 5's separate bare
551
+ `pnpm update -r` and `pnpm dedupe --check` invocations, followed by a separate
552
+ `pnpm dedupe` only when the check reports available changes. Then run each check
553
+ separately:
554
+
555
+ ```bash
556
+ pnpm build
557
+ pnpm test
558
+ pnpm lint
559
+ ```
560
+
561
+ If `pnpm-lock.yaml` comes out byte-identical and no ranges or build settings
562
+ changed, the installed tree did not change; rerun only `pnpm lint`.
563
+
564
+ If resolution is blocked: for an authored release, re-add the narrowest
565
+ package/version entry; for a third-party release, choose a mature version or
566
+ defer that upgrade. Obtain approval if that choice requires narrowing a
567
+ published runtime range. Never restore the whole previous list.
568
+
569
+ If the list was empty throughout, skip this section and continue to step 7.
570
+ If cleanup changes resolution, repeat step 5's consumer compatibility review
571
+ before reviewing documentation.
572
+
573
+ ## 7. Documentation review
574
+
575
+ Review `readme.md` against the updated project. Correct stale installation
576
+ instructions, requirements, commands, examples, configuration guidance, and
577
+ feature descriptions. If `readme.md` is generated or copied from another file,
578
+ update its maintained source and regenerate it through the existing workflow.
579
+
580
+ Review each section for content that would be more reliably maintained through
581
+ mdat, especially content duplicated from package metadata, CLI help, source
582
+ files, or other documentation. Prefer a rule bundled with mdat when it fits,
583
+ then a suitable mdat plugin, or a custom rule in `mdat.config.ts` for
584
+ project-specific needs. Check the installed rules and relevant documentation
585
+ before choosing an implementation. Replace manually maintained sections where
586
+ generation reduces duplication or prevents drift while preserving their useful
587
+ content and readability; keep explanatory prose manual when automation adds
588
+ no benefit. Any new plugin dependency must satisfy the dependency review,
589
+ release maturity, and compatibility policies above.
590
+
591
+ Run the project's mdat generation and documentation checks, inspect the
592
+ rendered content for accuracy, and confirm that a second generation produces
593
+ no further changes. Validate any new dependency under steps 5 and 6 before
594
+ continuing. Include documentation corrections and mdat conversions in the
595
+ final report.
596
+
597
+ ## 8. @kitschpatrol/shared-config override minimization
598
+
599
+ Run this as the final cleanup after all accepted updates, migrations,
600
+ validation, release-age exclusion cleanup, and documentation review, before
601
+ the release recommendation and report.
602
+
603
+ Review every shared-config-related configuration file in the project root,
604
+ including dotfiles, companion ignore files, and configuration stored in
605
+ `package.json`. Include the individual `@kitschpatrol/*-config` packages used by
606
+ the project. Inventory every local override beyond the upstream default export
607
+ or equivalent base configuration: rule settings, options, ignores, file globs,
608
+ and other customizations.
609
+ Include scoped peer-warning workarounds added during the update; remove them
610
+ when upstream peer declarations now cover the tested versions.
611
+
612
+ For each override, compare it with the defaults and effective configuration of
613
+ the exact versions installed after the updates. Check the current code it
614
+ targets and, for upstream workarounds, the relevant upstream changes or fixes.
615
+ Remove overrides that duplicate current defaults, target code or files that no
616
+ longer exist, or compensate for issues now fixed upstream. Do not infer that an
617
+ override is redundant merely because checks pass while it is still enabled.
618
+
619
+ Validate removals in small groups using the affected tools' checks without
620
+ caches, covering the affected files and any files newly included by removing
621
+ ignores. Recheck effective configuration where merge order or file matching
622
+ matters. Keep overrides that still serve a concrete project requirement,
623
+ including the behavior and browser-compatibility protections from step 5, and
624
+ record why each is needed. If necessity cannot be established, retain the
625
+ override and report the unresolved evidence instead of guessing.
626
+
627
+ After changing configuration, rerun the affected build, test, type, and lint
628
+ checks and compare results and build output as in step 5. The final report must
629
+ account for every override: removed with evidence, retained with a current
630
+ justification, or unresolved with the missing evidence identified.
631
+
632
+ ## Release recommendation
633
+
634
+ Recommend `patch`, `minor`, or `major` from the final consumer-visible changes,
635
+ following the repository's release conventions. Do not infer the recommendation
636
+ from the largest dependency version bump:
637
+
638
+ - **Patch:** compatible bug or security fixes, performance improvements, or
639
+ dependency/tooling maintenance with no new consumer-facing capability. A
640
+ development-only Node.js increase with the required notice can remain a patch.
641
+ - **Minor:** backward-compatible functionality or capabilities actually exposed
642
+ to consumers by the accepted updates. Explain what consumers gain; an upstream
643
+ minor or major alone is not a reason for a minor release. For zero-versioned
644
+ projects, also recommend a minor release for accepted consumer-facing breaking
645
+ changes unless the repository's pre-1.0 release conventions specify otherwise.
646
+ - **Major:** explicitly approved consumer-facing breaks in projects at 1.0.0
647
+ or later, including a raised consumer Node.js floor. A dependency major or
648
+ development-only requirement by itself does not justify a project major.
649
+
650
+ Give a short rationale per affected published package, or one recommendation
651
+ for a shared release version using the highest applicable level. If nothing
652
+ releasable changed, say no release is needed. An approved major release is
653
+ within scope; an unapproved breaking candidate stays pending or deferred.
654
+ For `0.x`, accepted consumer breaks normally require a minor, not a patch;
655
+ follow an explicit repository convention if it differs. Assess changes already
656
+ reachable through the old published ranges separately from newly introduced
657
+ consumer breaks. Recommend only; do not bump repository versions.
658
+
659
+ ## Report
660
+
661
+ State the execution mode and why it was selected. Include a `Breaking changes
662
+ available` section for deferred breaking candidates: current and proposed
663
+ versions, affected consumers/contracts, required migration, validation results
664
+ or gaps, recommended release level, and the decision still needed. Distinguish
665
+ these from applied breaks authorized explicitly or by the `0.x` policy. In
666
+ unattended mode, record decisions needed as follow-up items, not questions or
667
+ pending waits. Report unavailable checks and blocked work even when independent
668
+ updates succeeded.
669
+
670
+ Review the full diff for unrelated changes, excluded-package edits, and stray
671
+ exemptions first. Then summarize: applied updates; major decisions with
672
+ sources; consumer impact, approvals, declined/deferred decisions, and breaks
673
+ accepted under the zero-versioned policy;
674
+ output differences and their upstream or migration explanations;
675
+ consumer Node.js requirements before and after, development Node.js changes and
676
+ their `devEngines.runtime` notices; the release recommendation and its rationale;
677
+ baseline lockfile drift; runtime ranges, fresh-install exposure, and approved
678
+ range restrictions; TypeScript migrations; new warnings and scoped peer
679
+ workarounds; what was validated, at which exact Node.js versions, and in which
680
+ known consumers, including CLI/fixture comparisons and unavailable baselines;
681
+ remaining `minimumReleaseAgeExclude` entries with reasons; deferred upgrades,
682
+ documentation corrections and mdat conversions;
683
+ shared-config overrides removed or retained with their justifications and any
684
+ unresolved override reviews; unresolved failures, checks that could not run,
685
+ and steps run differently because of the environment. Passing checks do not
686
+ 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.