@lensmcp/cluster 1.25.25 → 1.25.26

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.
@@ -20,6 +20,15 @@
20
20
  * reported as the checker failing, never as a verdict;
21
21
  * - one checker is held to a few cores (`GOMAXPROCS`).
22
22
  *
23
+ * INCREMENTAL (issues/closed/foodguard-94). Cold was the design — and on foodguard every save to a shared library
24
+ * re-checked all of it once per service: 1,054 checks in 12 hours, up to 5.9GB each. So a check keeps what it learnt
25
+ * in a build-info file beside its shim (`tmp/lensmcp-tsgo/<app>.tsbuildinfo`, `--incremental --tsBuildInfoFile`): the
26
+ * next one re-checks only the files a change can affect, and reports the errors of the rest from that file. Still
27
+ * nothing is resident between saves — the knowledge is on disk. A run that gave no verdict (killed at its timeout, the
28
+ * checker failing) deletes the file, so a bad one is never read twice. `LENSMCP_TSGO_INCREMENTAL=0` checks cold.
29
+ * And a check's turn waits for memory: the queue holds it while the machine has little free, and it says so
30
+ * (`[tsgo] <app>: check held …`, and a `phase: 'held'` event the lens shows in `service_state`).
31
+ *
23
32
  * The output CONTRACT is the point: `--pretty false` prints classic `file.ts(1,2): error TS1234:`
24
33
  * lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
25
34
  * "service error" signal — the same wire the fork-ts-checker fed. Advisory like
@@ -73,9 +82,17 @@ export interface TsgoCheckPluginOptions {
73
82
  timeoutMs?: number;
74
83
  /** Cores one checker may use (`GOMAXPROCS`). Default {@link checkerMaxProcs}. */
75
84
  maxProcs?: number;
85
+ /** Keep a build-info file between checks (see the header). Default {@link tsgoIncremental}. */
86
+ incremental?: boolean;
76
87
  }
77
88
  /** `LENSMCP_TYPECHECK_GOMAXPROCS`, else {@link DEFAULT_MAX_PROCS}. An explicit `GOMAXPROCS` in the env wins over both. */
78
89
  export declare function checkerMaxProcs(env?: NodeJS.ProcessEnv): number;
90
+ /** `LENSMCP_TSGO_INCREMENTAL` — on unless it is `0`/`false`/`off`. */
91
+ export declare function tsgoIncremental(env?: NodeJS.ProcessEnv): boolean;
92
+ /** Where a service's check keeps what it learnt: beside its shim, in the workspace's git-ignored `tmp/`. */
93
+ export declare function tsgoBuildInfoPath(appName: string, workspaceRoot: string): string;
94
+ /** The checker's arguments. `--pretty false` is the output contract (see the header); the build info is optional. */
95
+ export declare function tsgoCheckArgs(shimPath: string, buildInfo?: string): string[];
79
96
  /** The npm package that ships the tsgo compiler — a real `dependency` of `@lensmcp/cluster`. */
80
97
  export declare const TSGO_PACKAGE = "@typescript/native-preview";
81
98
  /** Seams for {@link resolveTsgoBin}, so every branch of the precedence is testable without an install. */
@@ -165,6 +182,8 @@ export declare class TsgoCheckPlugin {
165
182
  private inflight;
166
183
  private askedAt;
167
184
  private shimPath;
185
+ /** The build-info file the checks keep, when they are incremental. */
186
+ private readonly buildInfo;
168
187
  private disabled;
169
188
  private disposed;
170
189
  constructor(opts: TsgoCheckPluginOptions);
@@ -178,6 +197,10 @@ export declare class TsgoCheckPlugin {
178
197
  * it cannot be stopped politely, and alone it is done in seconds.
179
198
  */
180
199
  private request;
200
+ /** Its turn came and waits for memory: said once, so a check that has not run reads «held», not «slow». */
201
+ private held;
202
+ /** A run that gave no verdict may have left a build-info file nobody should trust: the next check starts cold. */
203
+ private forgetBuildInfo;
181
204
  /** Asked through a method on purpose: `run()` may have moved the phase inside the call above. */
182
205
  private isIdle;
183
206
  /**
@@ -1,5 +1,5 @@
1
- "use strict";var y=Object.defineProperty;var h=(e,t)=>y(e,"name",{value:t,configurable:!0});var S=Object.defineProperty,a=h((e,t)=>S(e,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.resolveTsgoPackageDir=resolveTsgoPackageDir,exports.resolveTsgoBin=resolveTsgoBin,exports.reportTsgoUnavailable=reportTsgoUnavailable,exports.effectivePaths=effectivePaths,exports.writeShimTsconfig=writeShimTsconfig;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus"),typecheck_queue_1=require("./typecheck-queue"),DEFAULT_TIMEOUT_MS=12e4,DEFAULT_MAX_PROCS=4;function checkerMaxProcs(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_GOMAXPROCS);return Number.isInteger(t)&&t>=1?t:DEFAULT_MAX_PROCS}h(checkerMaxProcs,"checkerMaxProcs"),a(checkerMaxProcs,"checkerMaxProcs");const livePlugins=new Set;let exitHooked=!1;function stopWithTheProcess(e){livePlugins.add(e),!exitHooked&&(exitHooked=!0,process.once("exit",()=>{for(const t of livePlugins)t.dispose()}))}h(stopWithTheProcess,"stopWithTheProcess"),a(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}h(shimNames,"shimNames"),a(shimNames,"shimNames");function isExecutableFile(e,t){try{if(!fs.statSync(e).isFile())return!1}catch{return!1}if(t==="win32")return!0;try{return fs.accessSync(e,fs.constants.X_OK),!0}catch{return!1}}h(isExecutableFile,"isExecutableFile"),a(isExecutableFile,"isExecutableFile");function resolveTsgoPackageDir(){try{return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`))}catch{}try{let e=path.dirname(require.resolve(exports.TSGO_PACKAGE));for(let t=0;t<6;t++){const o=path.join(e,"package.json");if(fs.existsSync(o))try{const{name:s}=JSON.parse(fs.readFileSync(o,"utf8"));if(s===exports.TSGO_PACKAGE)return e}catch{}const r=path.dirname(e);if(r===e)break;e=r}}catch{}return null}h(resolveTsgoPackageDir,"resolveTsgoPackageDir"),a(resolveTsgoPackageDir,"resolveTsgoPackageDir");function packageDeclaredBin(e){try{const{bin:t}=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8")),o=typeof t=="string"?t:t?.tsgo;return o?path.join(e,o):null}catch{return null}}h(packageDeclaredBin,"packageDeclaredBin"),a(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const o=path.resolve(e,"..","..",".bin"),r=shimNames(t).map(i=>path.join(o,i)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...r]:r}h(ownBinCandidates,"ownBinCandidates"),a(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,o={}){const r=o.platform??process.platform,s=o.isExecutable??(n=>isExecutableFile(n,r));if(t)return s(t)?t:null;for(const n of shimNames(r)){const l=path.join(e,"node_modules",".bin",n);if(s(l))return l}const i=(o.ownPackageDir??resolveTsgoPackageDir)();return i?ownBinCandidates(i,r).find(s)??null:null}h(resolveTsgoBin,"resolveTsgoBin"),a(resolveTsgoBin,"resolveTsgoBin"),exports.TSGO_MISSING_CAUSE=`tsgo binary not found \u2014 @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;function reportTsgoUnavailable(e){try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:e.workspaceRoot,project:e.appName,tool:"tsgo",tsConfig:e.tsConfig,output:"",durationMs:0,toolError:e.detail,...e.eventFile?{eventFile:e.eventFile}:{}})}catch{}}h(reportTsgoUnavailable,"reportTsgoUnavailable"),a(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",o=!1,r=!1,s=!1;for(let i=0;i<e.length;i++){const n=e[i],l=e[i+1];if(r){n===`
2
- `&&(r=!1,t+=n);continue}if(s){n==="*"&&l==="/"&&(s=!1,i++);continue}if(o){t+=n,n==="\\"?(t+=l??"",i++):n==='"'&&(o=!1);continue}if(n==='"'){o=!0,t+=n;continue}if(n==="/"&&l==="/"){r=!0,i++;continue}if(n==="/"&&l==="*"){s=!0,i++;continue}t+=n}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}h(parseJsonc,"parseJsonc"),a(parseJsonc,"parseJsonc");function resolveExtends(e,t){if(!e.startsWith(".")&&!path.isAbsolute(e))return null;const o=path.isAbsolute(e)?e:path.resolve(t,e);for(const r of[o,`${o}.json`,path.join(o,"tsconfig.json")])try{if(fs.statSync(r).isFile())return r}catch{}return null}h(resolveExtends,"resolveExtends"),a(resolveExtends,"resolveExtends");function effectivePaths(e,t=new Set){if(t.has(e))return null;t.add(e);let o;try{o=fs.readFileSync(e,"utf8")}catch{return null}const r=parseJsonc(o);if(!r)return null;const s=path.dirname(e),i=r.compilerOptions??{},n=Array.isArray(r.extends)?r.extends.filter(p=>typeof p=="string"):typeof r.extends=="string"?[r.extends]:[],l=a(()=>{for(const p of[...n].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=i.paths,c=typeof i.baseUrl=="string"?path.resolve(s,i.baseUrl):void 0;if(!u){const p=l();return p?{paths:p.paths,anchorDir:c??p.anchorDir}:null}return{paths:u,anchorDir:c??l()?.anchorDir??s}}h(effectivePaths,"effectivePaths"),a(effectivePaths,"effectivePaths");function writeShimTsconfig(e,t,o){const r=path.join(o,"tmp","lensmcp-tsgo");fs.mkdirSync(r,{recursive:!0});const s=path.join(r,`${e}.tsconfig.json`),i=effectivePaths(t),n=i?Object.fromEntries(Object.entries(i.paths).map(([l,u])=>[l,(Array.isArray(u)?u:[]).map(c=>path.isAbsolute(c)?c:path.resolve(i.anchorDir,c))])):void 0;return fs.writeFileSync(s,JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...n&&Object.keys(n).length?{paths:n}:{}}},null,2)),s}h(writeShimTsconfig,"writeShimTsconfig"),a(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{h(this,"TsgoCheckPlugin")}static{a(this,"TsgoCheckPlugin")}constructor(t){this.timer=null,this.phase="idle",this.dirty=!1,this.ticket=null,this.inflight=null,this.askedAt=0,this.shimPath=null,this.disabled=!1,this.disposed=!1,this.opts=t,this.spawnImpl=t.spawnImpl??node_child_process_1.spawn,this.queue=t.queue??(0,typecheck_queue_1.machineTypecheckQueue)()}apply(t){t.hooks.done.tap("TsgoCheckPlugin",o=>{o.hasErrors()||this.schedule()}),t.hooks.shutdown?.tap?.("TsgoCheckPlugin",()=>this.dispose()),stopWithTheProcess(this)}schedule(){this.disabled||this.disposed||(this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.request(),this.opts.debounceMs??500),this.timer.unref?.())}request(){if(this.disabled||this.disposed)return;if(this.phase==="running"){this.dirty=!0;return}if(this.phase==="waiting")return;this.phase="waiting",this.askedAt=Date.now();const t=this.queue(this.opts.appName,()=>this.run());this.isIdle()?t.release():this.ticket=t}isIdle(){return this.phase==="idle"}publish(t){if(this.opts.publishEvents!==!1)try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,output:t.output,durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{},...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}run(){if(this.disposed)return this.leave();let t;try{t=resolveTsgoBin(this.opts.workspaceRoot,this.opts.binPath),t&&!this.shimPath&&(this.shimPath=writeShimTsconfig(this.opts.appName,this.opts.tsConfig,this.opts.workspaceRoot))}catch(c){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${c.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${c.message}`}),this.leave()}if(!t){this.disabled=!0;const c=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${c}.`),this.publish({output:"",durationMs:0,toolError:c}),this.leave()}this.phase="running",this.dirty=!1;const o=Date.now(),r=o-this.askedAt,s=this.spawnImpl(t,["--noEmit","--pretty","false","-p",this.shimPath],{cwd:this.opts.workspaceRoot,stdio:["ignore","pipe","pipe"],env:{...process.env,GOMAXPROCS:process.env.GOMAXPROCS??String(this.opts.maxProcs??checkerMaxProcs())}});this.inflight=s;let i="",n=!1;const l=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{n=!0,s.kill("SIGKILL")},l);u.unref?.(),s.stdout?.on("data",c=>{i+=c.toString()}),s.stderr?.on("data",c=>{i+=c.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.publish({output:"",durationMs:Date.now()-o,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(c,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-o,f=(d/1e3).toFixed(1),m=r>=1e3?`${f}s, waited ${(r/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(n){const k=`the check was killed after ${Math.round(l/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${k} \u2014 no verdict.`),this.publish({output:"",durationMs:d,toolError:k}),this.finish(s)}if(p)return this.finish(s);if(c===0)return console.log(`[tsgo] ${this.opts.appName}: types clean (${m})`),this.publish({output:"",durationMs:d}),this.finish(s);i.trim()&&process.stdout.write(i.endsWith(`
3
- `)?i:`${i}
4
- `);const g=(i.match(/error TS\d+/g)??[]).length;console.log(`[tsgo] ${this.opts.appName}: ${g} type error${g===1?"":"s"} (${m})`),this.publish({output:i,durationMs:d,...g===0?{toolError:`tsgo exited ${c} with no diagnostics: ${firstLine(i)}`}:{}}),this.finish(s)})}finish(t){this.inflight===t&&(this.inflight=null),this.leave(),this.dirty&&!this.disposed&&(this.dirty=!1,this.request())}leave(){this.phase="idle",this.ticket?.release(),this.ticket=null}dispose(){this.disposed||(this.disposed=!0,livePlugins.delete(this),this.timer&&clearTimeout(this.timer),this.inflight?.kill("SIGKILL"),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
5
- `)[0]?.slice(0,300)??""}h(firstLine,"firstLine"),a(firstLine,"firstLine");
1
+ "use strict";var v=Object.defineProperty;var a=(e,t)=>v(e,"name",{value:t,configurable:!0});var b=Object.defineProperty,l=a((e,t)=>b(e,"name",{value:t,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.tsgoIncremental=tsgoIncremental,exports.tsgoBuildInfoPath=tsgoBuildInfoPath,exports.tsgoCheckArgs=tsgoCheckArgs,exports.resolveTsgoPackageDir=resolveTsgoPackageDir,exports.resolveTsgoBin=resolveTsgoBin,exports.reportTsgoUnavailable=reportTsgoUnavailable,exports.effectivePaths=effectivePaths,exports.writeShimTsconfig=writeShimTsconfig;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus"),typecheck_queue_1=require("./typecheck-queue"),DEFAULT_TIMEOUT_MS=12e4,DEFAULT_MAX_PROCS=4;function checkerMaxProcs(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_GOMAXPROCS);return Number.isInteger(t)&&t>=1?t:DEFAULT_MAX_PROCS}a(checkerMaxProcs,"checkerMaxProcs"),l(checkerMaxProcs,"checkerMaxProcs");function tsgoIncremental(e=process.env){const t=(e.LENSMCP_TSGO_INCREMENTAL??"").trim().toLowerCase();return!(t==="0"||t==="false"||t==="off"||t==="no")}a(tsgoIncremental,"tsgoIncremental"),l(tsgoIncremental,"tsgoIncremental");function tsgoBuildInfoPath(e,t){return path.join(t,"tmp","lensmcp-tsgo",`${e}.tsbuildinfo`)}a(tsgoBuildInfoPath,"tsgoBuildInfoPath"),l(tsgoBuildInfoPath,"tsgoBuildInfoPath");function tsgoCheckArgs(e,t){return["--noEmit","--pretty","false",...t?["--incremental","--tsBuildInfoFile",t]:[],"-p",e]}a(tsgoCheckArgs,"tsgoCheckArgs"),l(tsgoCheckArgs,"tsgoCheckArgs");const livePlugins=new Set;let exitHooked=!1;function stopWithTheProcess(e){livePlugins.add(e),!exitHooked&&(exitHooked=!0,process.once("exit",()=>{for(const t of livePlugins)t.dispose()}))}a(stopWithTheProcess,"stopWithTheProcess"),l(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}a(shimNames,"shimNames"),l(shimNames,"shimNames");function isExecutableFile(e,t){try{if(!fs.statSync(e).isFile())return!1}catch{return!1}if(t==="win32")return!0;try{return fs.accessSync(e,fs.constants.X_OK),!0}catch{return!1}}a(isExecutableFile,"isExecutableFile"),l(isExecutableFile,"isExecutableFile");function resolveTsgoPackageDir(){try{return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`))}catch{}try{let e=path.dirname(require.resolve(exports.TSGO_PACKAGE));for(let t=0;t<6;t++){const i=path.join(e,"package.json");if(fs.existsSync(i))try{const{name:s}=JSON.parse(fs.readFileSync(i,"utf8"));if(s===exports.TSGO_PACKAGE)return e}catch{}const n=path.dirname(e);if(n===e)break;e=n}}catch{}return null}a(resolveTsgoPackageDir,"resolveTsgoPackageDir"),l(resolveTsgoPackageDir,"resolveTsgoPackageDir");function packageDeclaredBin(e){try{const{bin:t}=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8")),i=typeof t=="string"?t:t?.tsgo;return i?path.join(e,i):null}catch{return null}}a(packageDeclaredBin,"packageDeclaredBin"),l(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const i=path.resolve(e,"..","..",".bin"),n=shimNames(t).map(o=>path.join(i,o)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...n]:n}a(ownBinCandidates,"ownBinCandidates"),l(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,i={}){const n=i.platform??process.platform,s=i.isExecutable??(r=>isExecutableFile(r,n));if(t)return s(t)?t:null;for(const r of shimNames(n)){const c=path.join(e,"node_modules",".bin",r);if(s(c))return c}const o=(i.ownPackageDir??resolveTsgoPackageDir)();return o?ownBinCandidates(o,n).find(s)??null:null}a(resolveTsgoBin,"resolveTsgoBin"),l(resolveTsgoBin,"resolveTsgoBin"),exports.TSGO_MISSING_CAUSE=`tsgo binary not found \u2014 @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;function reportTsgoUnavailable(e){try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:e.workspaceRoot,project:e.appName,tool:"tsgo",tsConfig:e.tsConfig,output:"",durationMs:0,toolError:e.detail,...e.eventFile?{eventFile:e.eventFile}:{}})}catch{}}a(reportTsgoUnavailable,"reportTsgoUnavailable"),l(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",i=!1,n=!1,s=!1;for(let o=0;o<e.length;o++){const r=e[o],c=e[o+1];if(n){r===`
2
+ `&&(n=!1,t+=r);continue}if(s){r==="*"&&c==="/"&&(s=!1,o++);continue}if(i){t+=r,r==="\\"?(t+=c??"",o++):r==='"'&&(i=!1);continue}if(r==='"'){i=!0,t+=r;continue}if(r==="/"&&c==="/"){n=!0,o++;continue}if(r==="/"&&c==="*"){s=!0,o++;continue}t+=r}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}a(parseJsonc,"parseJsonc"),l(parseJsonc,"parseJsonc");function resolveExtends(e,t){if(!e.startsWith(".")&&!path.isAbsolute(e))return null;const i=path.isAbsolute(e)?e:path.resolve(t,e);for(const n of[i,`${i}.json`,path.join(i,"tsconfig.json")])try{if(fs.statSync(n).isFile())return n}catch{}return null}a(resolveExtends,"resolveExtends"),l(resolveExtends,"resolveExtends");function effectivePaths(e,t=new Set){if(t.has(e))return null;t.add(e);let i;try{i=fs.readFileSync(e,"utf8")}catch{return null}const n=parseJsonc(i);if(!n)return null;const s=path.dirname(e),o=n.compilerOptions??{},r=Array.isArray(n.extends)?n.extends.filter(p=>typeof p=="string"):typeof n.extends=="string"?[n.extends]:[],c=l(()=>{for(const p of[...r].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=o.paths,h=typeof o.baseUrl=="string"?path.resolve(s,o.baseUrl):void 0;if(!u){const p=c();return p?{paths:p.paths,anchorDir:h??p.anchorDir}:null}return{paths:u,anchorDir:h??c()?.anchorDir??s}}a(effectivePaths,"effectivePaths"),l(effectivePaths,"effectivePaths");function writeShimTsconfig(e,t,i){const n=path.join(i,"tmp","lensmcp-tsgo");fs.mkdirSync(n,{recursive:!0});const s=path.join(n,`${e}.tsconfig.json`),o=effectivePaths(t),r=o?Object.fromEntries(Object.entries(o.paths).map(([c,u])=>[c,(Array.isArray(u)?u:[]).map(h=>path.isAbsolute(h)?h:path.resolve(o.anchorDir,h))])):void 0;return fs.writeFileSync(s,JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...r&&Object.keys(r).length?{paths:r}:{}}},null,2)),s}a(writeShimTsconfig,"writeShimTsconfig"),l(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{a(this,"TsgoCheckPlugin")}static{l(this,"TsgoCheckPlugin")}constructor(t){this.timer=null,this.phase="idle",this.dirty=!1,this.ticket=null,this.inflight=null,this.askedAt=0,this.shimPath=null,this.disabled=!1,this.disposed=!1,this.opts=t,this.spawnImpl=t.spawnImpl??node_child_process_1.spawn,this.queue=t.queue??(0,typecheck_queue_1.machineTypecheckQueue)(),this.buildInfo=t.incremental??tsgoIncremental()?tsgoBuildInfoPath(t.appName,t.workspaceRoot):void 0}apply(t){t.hooks.done.tap("TsgoCheckPlugin",i=>{i.hasErrors()||this.schedule()}),t.hooks.shutdown?.tap?.("TsgoCheckPlugin",()=>this.dispose()),stopWithTheProcess(this)}schedule(){this.disabled||this.disposed||(this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.request(),this.opts.debounceMs??500),this.timer.unref?.())}request(){if(this.disabled||this.disposed)return;if(this.phase==="running"){this.dirty=!0;return}if(this.phase==="waiting")return;this.phase="waiting",this.askedAt=Date.now();const t=this.queue(this.opts.appName,()=>this.run(),{onHold:l(i=>this.held(i),"onHold")});this.isIdle()?t.release():this.ticket=t}held(t){if(!this.disposed&&(console.log(`[tsgo] ${this.opts.appName}: check held \u2014 ${t.availableMb} MB free, it waits for ${t.minFreeMb} MB (LENSMCP_TYPECHECK_MIN_FREE_MB)`),this.opts.publishEvents!==!1))try{(0,typecheck_bus_1.publishTypecheckHeld)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,availableMb:t.availableMb,minFreeMb:t.minFreeMb,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}forgetBuildInfo(){if(this.buildInfo)try{fs.rmSync(this.buildInfo,{force:!0})}catch{}}isIdle(){return this.phase==="idle"}publish(t){if(this.opts.publishEvents!==!1)try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,output:t.output,durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{},...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}run(){if(this.disposed)return this.leave();let t;try{t=resolveTsgoBin(this.opts.workspaceRoot,this.opts.binPath),t&&!this.shimPath&&(this.shimPath=writeShimTsconfig(this.opts.appName,this.opts.tsConfig,this.opts.workspaceRoot))}catch(h){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${h.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${h.message}`}),this.leave()}if(!t){this.disabled=!0;const h=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${h}.`),this.publish({output:"",durationMs:0,toolError:h}),this.leave()}this.phase="running",this.dirty=!1;const i=Date.now(),n=i-this.askedAt,s=this.spawnImpl(t,tsgoCheckArgs(this.shimPath,this.buildInfo),{cwd:this.opts.workspaceRoot,stdio:["ignore","pipe","pipe"],env:{...process.env,GOMAXPROCS:process.env.GOMAXPROCS??String(this.opts.maxProcs??checkerMaxProcs())}});this.inflight=s;let o="",r=!1;const c=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{r=!0,s.kill("SIGKILL")},c);u.unref?.(),s.stdout?.on("data",h=>{o+=h.toString()}),s.stderr?.on("data",h=>{o+=h.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.forgetBuildInfo(),this.publish({output:"",durationMs:Date.now()-i,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(h,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-i,f=(d/1e3).toFixed(1),m=n>=1e3?`${f}s, waited ${(n/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(r){const k=`the check was killed after ${Math.round(c/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${k} \u2014 no verdict.`),this.forgetBuildInfo(),this.publish({output:"",durationMs:d,toolError:k}),this.finish(s)}if(p)return this.forgetBuildInfo(),this.finish(s);if(h===0)return console.log(`[tsgo] ${this.opts.appName}: types clean (${m})`),this.publish({output:"",durationMs:d}),this.finish(s);o.trim()&&process.stdout.write(o.endsWith(`
3
+ `)?o:`${o}
4
+ `);const g=(o.match(/error TS\d+/g)??[]).length;console.log(`[tsgo] ${this.opts.appName}: ${g} type error${g===1?"":"s"} (${m})`),g===0&&this.forgetBuildInfo(),this.publish({output:o,durationMs:d,...g===0?{toolError:`tsgo exited ${h} with no diagnostics: ${firstLine(o)}`}:{}}),this.finish(s)})}finish(t){this.inflight===t&&(this.inflight=null),this.leave(),this.dirty&&!this.disposed&&(this.dirty=!1,this.request())}leave(){this.phase="idle",this.ticket?.release(),this.ticket=null}dispose(){this.disposed||(this.disposed=!0,livePlugins.delete(this),this.timer&&clearTimeout(this.timer),this.inflight&&(this.inflight.kill("SIGKILL"),this.forgetBuildInfo()),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
5
+ `)[0]?.slice(0,300)??""}a(firstLine,"firstLine"),l(firstLine,"firstLine");
@@ -96,3 +96,27 @@ export declare function publishTypecheckRun(args: {
96
96
  toolError?: string;
97
97
  eventFile?: string;
98
98
  }): TypecheckBusEvent[];
99
+ /**
100
+ * A check whose turn came and is held for memory (issues/closed/foodguard-94) — `typecheck-queue.ts` holds the head of
101
+ * the line while the machine has less free than a check waits for. Said once per held check, so a check that has not
102
+ * run reads «held», not «slow». No verdict: `phase: 'held'` is neither a run's START (its title does NOT start with
103
+ * `tsc clear`, which an older reducer resets on) nor its END. Producer-only — session's mirror has no checker to hold.
104
+ */
105
+ export declare function buildTypecheckHeldEvent(args: {
106
+ project: string;
107
+ tool: string;
108
+ tsConfig?: string;
109
+ availableMb: number;
110
+ minFreeMb: number;
111
+ now?: () => number;
112
+ idImpl?: () => string;
113
+ }): TypecheckBusEvent;
114
+ export declare function publishTypecheckHeld(args: {
115
+ workspaceRoot: string;
116
+ project: string;
117
+ tool: string;
118
+ tsConfig?: string;
119
+ availableMb: number;
120
+ minFreeMb: number;
121
+ eventFile?: string;
122
+ }): TypecheckBusEvent;
package/typecheck-bus.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var d=Object.defineProperty;var s=(e,r)=>d(e,"name",{value:r,configurable:!0});var f=Object.defineProperty,i=s((e,r)=>f(e,"name",{value:r,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.MAX_DIAGNOSTICS_PER_RUN=void 0,exports.stripAnsi=stripAnsi,exports.parseTscDiagnosticLine=parseTscDiagnosticLine,exports.parseTscOutput=parseTscOutput,exports.buildTypecheckRunEvents=buildTypecheckRunEvents,exports.resolveEventFile=resolveEventFile,exports.appendTypecheckEvents=appendTypecheckEvents,exports.publishTypecheckDiagnostics=publishTypecheckDiagnostics,exports.publishTypecheckRun=publishTypecheckRun;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})(),DIAG_LINE_REGEX=/^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/,ANSI_CSI=new RegExp("\x1B\\[[0-9;]*[A-Za-z]","g");function stripAnsi(e){return e.replace(ANSI_CSI,"")}s(stripAnsi,"stripAnsi"),i(stripAnsi,"stripAnsi");function parseTscDiagnosticLine(e){const r=stripAnsi(e).trim();if(!r)return null;const o=DIAG_LINE_REGEX.exec(r);if(!o)return null;const[,t,u,l,c,a,p]=o;return!t||!u||!l||!c||!a||!p?null:{file:t,line:Number(u),column:Number(l),severity:c==="warning"?"warning":"error",code:a,message:p}}s(parseTscDiagnosticLine,"parseTscDiagnosticLine"),i(parseTscDiagnosticLine,"parseTscDiagnosticLine");function parseTscOutput(e){const r=[];for(const o of e.split(`
2
- `)){const t=parseTscDiagnosticLine(o);t&&r.push(t)}return r}s(parseTscOutput,"parseTscOutput"),i(parseTscOutput,"parseTscOutput"),exports.MAX_DIAGNOSTICS_PER_RUN=200;let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}s(makeId,"makeId"),i(makeId,"makeId");function buildTypecheckRunEvents(e){const r=(e.now??Date.now)(),o=e.idImpl??makeId,t="pending",u={sessionId:t,source:"typescript",category:"typecheck",context:{sessionId:t,projectName:e.project}},l={project:e.project,tool:e.tool,...e.tsConfig?{tsConfig:e.tsConfig}:{}},c=e.diagnostics.filter(n=>n.severity==="error"),a=e.diagnostics.slice(0,exports.MAX_DIAGNOSTICS_PER_RUN),p=[];if(!e.toolError){p.push({...u,id:o(),timestamp:r,severity:"info",title:`tsc clear: ${e.project}`,message:`typecheck run starting for ${e.project}`,fingerprint:`tsc:${e.project}:run-start`,raw:{...l,kind:"typecheck-run",phase:"start"}});for(const n of a)p.push({...u,id:o(),timestamp:r,severity:n.severity,title:`${n.code}: ${n.message}`,message:n.message,location:{file:n.file,line:n.line,column:n.column},fingerprint:`tsc:${e.project}:${n.file}:${n.line}:${n.column}:${n.code}`,raw:{...l,kind:"typecheck-diagnostic",code:n.code}})}const g=e.toolError?"error":c.length>0?"failing":"clean";return p.push({...u,id:o(),timestamp:r,severity:"info",title:`typecheck run: ${e.project} \u2014 ${e.toolError?"tool error":`${c.length} error${c.length===1?"":"s"}`} (${e.durationMs}ms)`,...e.toolError?{message:e.toolError}:{},fingerprint:`tsc:${e.project}:run-end`,raw:{...l,kind:"typecheck-run",phase:"end",status:g,durationMs:e.durationMs,errorCount:c.length,warningCount:e.diagnostics.length-c.length,...e.diagnostics.length>a.length?{truncatedDiagnostics:e.diagnostics.length-a.length}:{},...e.toolError?{error:e.toolError}:{}}}),p}s(buildTypecheckRunEvents,"buildTypecheckRunEvents"),i(buildTypecheckRunEvents,"buildTypecheckRunEvents");function resolveEventFile(e){return process.env.LENSMCP_EVENT_FILE||path.join(e,".lensmcp","events.jsonl")}s(resolveEventFile,"resolveEventFile"),i(resolveEventFile,"resolveEventFile");let ensuredDir=null;function ensureDir(e){const r=path.dirname(e);ensuredDir!==r&&(fs.mkdirSync(r,{recursive:!0}),ensuredDir=r)}s(ensureDir,"ensureDir"),i(ensureDir,"ensureDir");function appendTypecheckEvents(e,r){if(r.length===0)return;const o=r.map(t=>JSON.stringify(t)).join(`
1
+ "use strict";var g=Object.defineProperty;var n=(t,e)=>g(t,"name",{value:e,configurable:!0});var f=Object.defineProperty,o=n((t,e)=>f(t,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.MAX_DIAGNOSTICS_PER_RUN=void 0,exports.stripAnsi=stripAnsi,exports.parseTscDiagnosticLine=parseTscDiagnosticLine,exports.parseTscOutput=parseTscOutput,exports.buildTypecheckRunEvents=buildTypecheckRunEvents,exports.resolveEventFile=resolveEventFile,exports.appendTypecheckEvents=appendTypecheckEvents,exports.publishTypecheckDiagnostics=publishTypecheckDiagnostics,exports.publishTypecheckRun=publishTypecheckRun,exports.buildTypecheckHeldEvent=buildTypecheckHeldEvent,exports.publishTypecheckHeld=publishTypecheckHeld;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})(),DIAG_LINE_REGEX=/^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/,ANSI_CSI=new RegExp("\x1B\\[[0-9;]*[A-Za-z]","g");function stripAnsi(t){return t.replace(ANSI_CSI,"")}n(stripAnsi,"stripAnsi"),o(stripAnsi,"stripAnsi");function parseTscDiagnosticLine(t){const e=stripAnsi(t).trim();if(!e)return null;const i=DIAG_LINE_REGEX.exec(e);if(!i)return null;const[,s,l,u,c,a,p]=i;return!s||!l||!u||!c||!a||!p?null:{file:s,line:Number(l),column:Number(u),severity:c==="warning"?"warning":"error",code:a,message:p}}n(parseTscDiagnosticLine,"parseTscDiagnosticLine"),o(parseTscDiagnosticLine,"parseTscDiagnosticLine");function parseTscOutput(t){const e=[];for(const i of t.split(`
2
+ `)){const s=parseTscDiagnosticLine(i);s&&e.push(s)}return e}n(parseTscOutput,"parseTscOutput"),o(parseTscOutput,"parseTscOutput"),exports.MAX_DIAGNOSTICS_PER_RUN=200;let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}n(makeId,"makeId"),o(makeId,"makeId");function buildTypecheckRunEvents(t){const e=(t.now??Date.now)(),i=t.idImpl??makeId,s="pending",l={sessionId:s,source:"typescript",category:"typecheck",context:{sessionId:s,projectName:t.project}},u={project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{}},c=t.diagnostics.filter(r=>r.severity==="error"),a=t.diagnostics.slice(0,exports.MAX_DIAGNOSTICS_PER_RUN),p=[];if(!t.toolError){p.push({...l,id:i(),timestamp:e,severity:"info",title:`tsc clear: ${t.project}`,message:`typecheck run starting for ${t.project}`,fingerprint:`tsc:${t.project}:run-start`,raw:{...u,kind:"typecheck-run",phase:"start"}});for(const r of a)p.push({...l,id:i(),timestamp:e,severity:r.severity,title:`${r.code}: ${r.message}`,message:r.message,location:{file:r.file,line:r.line,column:r.column},fingerprint:`tsc:${t.project}:${r.file}:${r.line}:${r.column}:${r.code}`,raw:{...u,kind:"typecheck-diagnostic",code:r.code}})}const d=t.toolError?"error":c.length>0?"failing":"clean";return p.push({...l,id:i(),timestamp:e,severity:"info",title:`typecheck run: ${t.project} \u2014 ${t.toolError?"tool error":`${c.length} error${c.length===1?"":"s"}`} (${t.durationMs}ms)`,...t.toolError?{message:t.toolError}:{},fingerprint:`tsc:${t.project}:run-end`,raw:{...u,kind:"typecheck-run",phase:"end",status:d,durationMs:t.durationMs,errorCount:c.length,warningCount:t.diagnostics.length-c.length,...t.diagnostics.length>a.length?{truncatedDiagnostics:t.diagnostics.length-a.length}:{},...t.toolError?{error:t.toolError}:{}}}),p}n(buildTypecheckRunEvents,"buildTypecheckRunEvents"),o(buildTypecheckRunEvents,"buildTypecheckRunEvents");function resolveEventFile(t){return process.env.LENSMCP_EVENT_FILE||path.join(t,".lensmcp","events.jsonl")}n(resolveEventFile,"resolveEventFile"),o(resolveEventFile,"resolveEventFile");let ensuredDir=null;function ensureDir(t){const e=path.dirname(t);ensuredDir!==e&&(fs.mkdirSync(e,{recursive:!0}),ensuredDir=e)}n(ensureDir,"ensureDir"),o(ensureDir,"ensureDir");function appendTypecheckEvents(t,e){if(e.length===0)return;const i=e.map(s=>JSON.stringify(s)).join(`
3
3
  `)+`
4
- `;try{ensureDir(e),busHygiene?.maybeRotateEventFile(e,o.length),fs.appendFileSync(e,o)}catch(t){if(t?.code==="ENOENT"){try{ensuredDir=null,ensureDir(e),fs.appendFileSync(e,o)}catch{}return}}}s(appendTypecheckEvents,"appendTypecheckEvents"),i(appendTypecheckEvents,"appendTypecheckEvents");function publishTypecheckDiagnostics(e){const r=buildTypecheckRunEvents({project:e.project,tool:e.tool,...e.tsConfig?{tsConfig:e.tsConfig}:{},diagnostics:e.diagnostics,durationMs:e.durationMs});return appendTypecheckEvents(e.eventFile??resolveEventFile(e.workspaceRoot),r),r}s(publishTypecheckDiagnostics,"publishTypecheckDiagnostics"),i(publishTypecheckDiagnostics,"publishTypecheckDiagnostics");function publishTypecheckRun(e){const r=buildTypecheckRunEvents({project:e.project,tool:e.tool,...e.tsConfig?{tsConfig:e.tsConfig}:{},diagnostics:e.toolError?[]:parseTscOutput(e.output),durationMs:e.durationMs,...e.toolError?{toolError:e.toolError}:{}});return appendTypecheckEvents(e.eventFile??resolveEventFile(e.workspaceRoot),r),r}s(publishTypecheckRun,"publishTypecheckRun"),i(publishTypecheckRun,"publishTypecheckRun");
4
+ `;try{ensureDir(t),busHygiene?.maybeRotateEventFile(t,i.length),fs.appendFileSync(t,i)}catch(s){if(s?.code==="ENOENT"){try{ensuredDir=null,ensureDir(t),fs.appendFileSync(t,i)}catch{}return}}}n(appendTypecheckEvents,"appendTypecheckEvents"),o(appendTypecheckEvents,"appendTypecheckEvents");function publishTypecheckDiagnostics(t){const e=buildTypecheckRunEvents({project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},diagnostics:t.diagnostics,durationMs:t.durationMs});return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),e),e}n(publishTypecheckDiagnostics,"publishTypecheckDiagnostics"),o(publishTypecheckDiagnostics,"publishTypecheckDiagnostics");function publishTypecheckRun(t){const e=buildTypecheckRunEvents({project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},diagnostics:t.toolError?[]:parseTscOutput(t.output),durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{}});return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),e),e}n(publishTypecheckRun,"publishTypecheckRun"),o(publishTypecheckRun,"publishTypecheckRun");function buildTypecheckHeldEvent(t){const e="pending";return{id:(t.idImpl??makeId)(),sessionId:e,timestamp:(t.now??Date.now)(),source:"typescript",category:"typecheck",severity:"info",context:{sessionId:e,projectName:t.project},title:`typecheck held: ${t.project} \u2014 ${t.availableMb} MB free, a check waits for ${t.minFreeMb} MB`,message:`the type check of ${t.project} waits until ${t.minFreeMb} MB is free (LENSMCP_TYPECHECK_MIN_FREE_MB)`,fingerprint:`tsc:${t.project}:run-held`,raw:{project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},kind:"typecheck-run",phase:"held",availableMb:t.availableMb,minFreeMb:t.minFreeMb}}}n(buildTypecheckHeldEvent,"buildTypecheckHeldEvent"),o(buildTypecheckHeldEvent,"buildTypecheckHeldEvent");function publishTypecheckHeld(t){const e=buildTypecheckHeldEvent(t);return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),[e]),e}n(publishTypecheckHeld,"publishTypecheckHeld"),o(publishTypecheckHeld,"publishTypecheckHeld");
@@ -2,8 +2,17 @@ export interface QueueTicket {
2
2
  /** Give the slot back, or leave the line. Idempotent. */
3
3
  release(): void;
4
4
  }
5
+ /** What a caller in line is told besides its turn. */
6
+ export interface QueueHooks {
7
+ /** Its turn came, and is held: the machine has less memory free than a check waits for. Called once per ticket. */
8
+ onHold?(hold: MemoryHold): void;
9
+ }
10
+ export interface MemoryHold {
11
+ availableMb: number;
12
+ minFreeMb: number;
13
+ }
5
14
  /** Join the line; `onTurn` is called once, when this caller may run. */
6
- export type TypecheckQueue = (label: string, onTurn: () => void) => QueueTicket;
15
+ export type TypecheckQueue = (label: string, onTurn: () => void, hooks?: QueueHooks) => QueueTicket;
7
16
  export interface MachineQueueOptions {
8
17
  dir?: string;
9
18
  slots?: number;
@@ -20,11 +29,25 @@ export interface MachineQueueOptions {
20
29
  pid?: number;
21
30
  isAlive?: (pid: number) => boolean;
22
31
  now?: () => number;
32
+ /** A turn is held while less than this is free (MB); 0 holds nothing. Default {@link typecheckMinFreeMb}. */
33
+ minFreeMb?: number;
34
+ /** The memory the OS can still hand out, in MB; undefined when it cannot tell. Default {@link availableMemoryMb}. */
35
+ availableMb?: () => number | undefined;
36
+ /** How often a held turn looks at the memory again. */
37
+ holdPollMs?: number;
23
38
  }
24
39
  /** `~/.lensmcp/typecheck-queue` — machine-wide on purpose; `LENSMCP_HOME` moves it (tests, sandboxes). */
25
40
  export declare function typecheckQueueDir(env?: NodeJS.ProcessEnv): string;
26
41
  /** `LENSMCP_TYPECHECK_CONCURRENCY`, default ONE. Anything unreadable is one, never unlimited. */
27
42
  export declare function typecheckSlots(env?: NodeJS.ProcessEnv): number;
43
+ /** `LENSMCP_TYPECHECK_MIN_FREE_MB`, default 2048; `0` turns the hold off. Anything unreadable is the default. */
44
+ export declare function typecheckMinFreeMb(env?: NodeJS.ProcessEnv): number;
45
+ /**
46
+ * What the OS can still hand out, in MB — `process.availableMemory()`: free plus reclaimable on macOS (free + inactive
47
+ * pages), and a container's own limit under cgroups. Not `os.freemem()`: on macOS that counts only the free pages, a
48
+ * few hundred MB on a busy machine that has gigabytes to give. A Node without it cannot tell: undefined, nothing held.
49
+ */
50
+ export declare function availableMemoryMb(): number | undefined;
28
51
  /**
29
52
  * The line, oldest first. A ticket whose owner is dead — or that has sat past `maxAgeMs` — is
30
53
  * deleted on the way, so a crashed service never holds a slot.
@@ -1 +1 @@
1
- "use strict";var T=Object.defineProperty;var c=(e,t)=>T(e,"name",{value:t,configurable:!0});var E=Object.defineProperty,r=c((e,t)=>E(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.immediateQueue=void 0,exports.typecheckQueueDir=typecheckQueueDir,exports.typecheckSlots=typecheckSlots,exports.liveTickets=liveTickets,exports.machineTypecheckQueue=machineTypecheckQueue;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),TICKET_SUFFIX=".ticket",DEFAULT_POLL_MS=250,DEFAULT_SETTLE_MS=120,DEFAULT_MAX_AGE_MS=15*6e4,MAX_SLOTS=8;function typecheckQueueDir(e=process.env){return path.join(e.LENSMCP_HOME||os.homedir(),".lensmcp","typecheck-queue")}c(typecheckQueueDir,"typecheckQueueDir"),r(typecheckQueueDir,"typecheckQueueDir");function typecheckSlots(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_CONCURRENCY);return Number.isInteger(t)&&t>=1?Math.min(t,MAX_SLOTS):1}c(typecheckSlots,"typecheckSlots"),r(typecheckSlots,"typecheckSlots");function pidAlive(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}c(pidAlive,"pidAlive"),r(pidAlive,"pidAlive");const ticketPid=r(e=>{const t=Number(e.split("-")[0]);return Number.isInteger(t)&&t>0?t:void 0},"ticketPid");function liveTickets(e,t={}){const h=t.isAlive??pidAlive,S=(t.now??Date.now)(),m=t.maxAgeMs??DEFAULT_MAX_AGE_MS;let s;try{s=fs.readdirSync(e).filter(i=>i.endsWith(TICKET_SUFFIX))}catch{return[]}const u=[];for(const i of s){const n=path.join(e,i);let o;try{o=fs.statSync(n,{bigint:!0})}catch{continue}const a=ticketPid(i);if(a===void 0||!h(a)||S-Number(o.mtimeMs)>m){try{fs.unlinkSync(n)}catch{}continue}u.push({name:i,at:o.mtimeNs})}return u.sort((i,n)=>i.at<n.at?-1:i.at>n.at?1:i.name<n.name?-1:i.name>n.name?1:0),u.map(i=>i.name)}c(liveTickets,"liveTickets"),r(liveTickets,"liveTickets");let ticketSeq=0;function machineTypecheckQueue(e={}){const t=e.dir??typecheckQueueDir(),h=Math.max(1,e.slots??typecheckSlots()),S=e.pollMs??DEFAULT_POLL_MS,m=e.settleMs??DEFAULT_SETTLE_MS,s=e.pid??process.pid,u=r(()=>liveTickets(t,e),"scan");return(i,n)=>{const o=`${s}-${(ticketSeq++).toString(36)}-${Math.random().toString(36).slice(2,8)}${TICKET_SUFFIX}`,a=path.join(t,o);let l=!1,y=!1,p;const f=r(()=>{l||y||(y=!0,n())},"turn"),_=r(()=>{try{return fs.mkdirSync(t,{recursive:!0}),fs.writeFileSync(a,JSON.stringify({pid:s,label:i,at:(e.now??Date.now)()})),!0}catch{return!1}},"write"),k=r(d=>{p=setTimeout(M,d),p.unref?.()},"later");function M(){if(l||y)return;const d=u().indexOf(o);if(d===-1)return _()?k(m):f();if(d<h)return f();k(S)}return c(M,"k"),r(M,"look"),_()?(k(m),{release(){if(!l){l=!0,p&&clearTimeout(p);try{fs.unlinkSync(a)}catch{}}}}):(f(),{release(){l=!0}})}}c(machineTypecheckQueue,"machineTypecheckQueue"),r(machineTypecheckQueue,"machineTypecheckQueue");const immediateQueue=r((e,t)=>(t(),{release(){}}),"immediateQueue");exports.immediateQueue=immediateQueue;
1
+ "use strict";var P=Object.defineProperty;var i=(e,t)=>P(e,"name",{value:t,configurable:!0});var x=Object.defineProperty,n=i((e,t)=>x(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.immediateQueue=void 0,exports.typecheckQueueDir=typecheckQueueDir,exports.typecheckSlots=typecheckSlots,exports.typecheckMinFreeMb=typecheckMinFreeMb,exports.availableMemoryMb=availableMemoryMb,exports.liveTickets=liveTickets,exports.machineTypecheckQueue=machineTypecheckQueue;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),TICKET_SUFFIX=".ticket",DEFAULT_POLL_MS=250,DEFAULT_SETTLE_MS=120,DEFAULT_MAX_AGE_MS=15*6e4,MAX_SLOTS=8,DEFAULT_HOLD_POLL_MS=2e3,DEFAULT_MIN_FREE_MB=2048;function typecheckQueueDir(e=process.env){return path.join(e.LENSMCP_HOME||os.homedir(),".lensmcp","typecheck-queue")}i(typecheckQueueDir,"typecheckQueueDir"),n(typecheckQueueDir,"typecheckQueueDir");function typecheckSlots(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_CONCURRENCY);return Number.isInteger(t)&&t>=1?Math.min(t,MAX_SLOTS):1}i(typecheckSlots,"typecheckSlots"),n(typecheckSlots,"typecheckSlots");function typecheckMinFreeMb(e=process.env){const t=e.LENSMCP_TYPECHECK_MIN_FREE_MB,o=Number(t);return t!==void 0&&t.trim()!==""&&Number.isFinite(o)&&o>=0?Math.round(o):DEFAULT_MIN_FREE_MB}i(typecheckMinFreeMb,"typecheckMinFreeMb"),n(typecheckMinFreeMb,"typecheckMinFreeMb");function availableMemoryMb(){const e=process.availableMemory;if(typeof e=="function")try{const t=e.call(process);return Number.isFinite(t)&&t>0?Math.round(t/(1024*1024)):void 0}catch{return}}i(availableMemoryMb,"availableMemoryMb"),n(availableMemoryMb,"availableMemoryMb");function pidAlive(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}i(pidAlive,"pidAlive"),n(pidAlive,"pidAlive");const ticketPid=n(e=>{const t=Number(e.split("-")[0]);return Number.isInteger(t)&&t>0?t:void 0},"ticketPid");function liveTickets(e,t={}){const o=t.isAlive??pidAlive,_=(t.now??Date.now)(),p=t.maxAgeMs??DEFAULT_MAX_AGE_MS;let u;try{u=fs.readdirSync(e).filter(r=>r.endsWith(TICKET_SUFFIX))}catch{return[]}const s=[];for(const r of u){const c=path.join(e,r);let a;try{a=fs.statSync(c,{bigint:!0})}catch{continue}const M=ticketPid(r);if(M===void 0||!o(M)||_-Number(a.mtimeMs)>p){try{fs.unlinkSync(c)}catch{}continue}s.push({name:r,at:a.mtimeNs})}return s.sort((r,c)=>r.at<c.at?-1:r.at>c.at?1:r.name<c.name?-1:r.name>c.name?1:0),s.map(r=>r.name)}i(liveTickets,"liveTickets"),n(liveTickets,"liveTickets");let ticketSeq=0;function machineTypecheckQueue(e={}){const t=e.dir??typecheckQueueDir(),o=Math.max(1,e.slots??typecheckSlots()),_=e.pollMs??DEFAULT_POLL_MS,p=e.settleMs??DEFAULT_SETTLE_MS,u=e.pid??process.pid,s=e.minFreeMb??typecheckMinFreeMb(),r=e.availableMb??availableMemoryMb,c=e.holdPollMs??DEFAULT_HOLD_POLL_MS,a=n(()=>liveTickets(t,e),"scan"),M=n(()=>{if(s<=0)return;const l=r();return l!==void 0&&l<s?{availableMb:l,minFreeMb:s}:void 0},"memoryHold");return(l,N,L)=>{const v=`${u}-${(ticketSeq++).toString(36)}-${Math.random().toString(36).slice(2,8)}${TICKET_SUFFIX}`,S=path.join(t,v);let m=!1,b=!1,k=!1,d;const f=n(()=>{m||b||(b=!0,N())},"turn"),E=n(()=>{try{return fs.mkdirSync(t,{recursive:!0}),fs.writeFileSync(S,JSON.stringify({pid:u,label:l,at:(e.now??Date.now)()})),!0}catch{return!1}},"write"),y=n(h=>{d=setTimeout(T,h),d.unref?.()},"later");function T(){if(m||b)return;const h=a().indexOf(v);if(h===-1)return E()?y(p):f();if(h<o){const F=M();if(!F)return f();if(!k){k=!0;try{L?.onHold?.(F)}catch{}}return y(c)}y(_)}return i(T,"L"),n(T,"look"),E()?(y(p),{release(){if(!m){m=!0,d&&clearTimeout(d);try{fs.unlinkSync(S)}catch{}}}}):(f(),{release(){m=!0}})}}i(machineTypecheckQueue,"machineTypecheckQueue"),n(machineTypecheckQueue,"machineTypecheckQueue");const immediateQueue=n((e,t)=>(t(),{release(){}}),"immediateQueue");exports.immediateQueue=immediateQueue;