@redue/cli 0.1.0-alpha.12 → 0.1.0-alpha.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.13 — 2026-10-19
4
+
5
+ Bench numbers below are from one 800-file project on macOS arm64, Node 22.13.0.
6
+ Released alpha.12 and this build ran interleaved on the same machine; the
7
+ decision path is unchanged within noise (warm `status --sync` 192/197 ms on
8
+ alpha.12, 174/198 ms on alpha.13; states correct, no idle probe launches).
9
+ On the CI screen of 38 public TypeScript projects, 22 qualify for the direct
10
+ recipe, up from 19 at alpha.12: worldmonitor, Recordly and open-cowork, which
11
+ the fixed 5 s probe deadline blocked.
12
+
13
+ - A full state probe may run longer on large projects. Its deadline was a
14
+ fixed 5 s (10 s on Windows and for pnpm); now it is the larger of twice the
15
+ last successful probe and the last completed run, at least that floor and at
16
+ most 60 s, doubling after a timeout. A probe that never finishes is still
17
+ killed at its deadline and the check stays UNVERIFIED. `redue status --sync`
18
+ and `redue run` wait for the observer up to the sum of its checks' probe
19
+ deadlines plus 10 s (`probe_deadline_ms` in status JSON), never less than
20
+ before. While a full probe runs the observer does not answer, so with a hung
21
+ probe a decision can wait up to 60 s per check before it is UNVERIFIED.
22
+
3
23
  ## 0.1.0-alpha.12 — 2026-10-19
4
24
 
5
25
  Bench numbers below are from one 800-file project on macOS arm64, Node 22.13.0.
package/README.md CHANGED
@@ -37,7 +37,7 @@ it; relevant source edits make it stale. [Run the reproducible demo](docs/contri
37
37
 
38
38
  ## Install the alpha
39
39
 
40
- The opt-in pilot uses **0.1.0-alpha.12**. Follow the
40
+ The opt-in pilot uses **0.1.0-alpha.13**. Follow the
41
41
  [fixed-version pilot guide](docs/user/pilot.md) for supported projects, setup,
42
42
  updates and voluntary feedback. No external usage results are claimed yet.
43
43
 
@@ -96,9 +96,42 @@ Setup is explicit, reversible, and does not change tool permissions or run check
96
96
 
97
97
  The experimental claim is **opt-in project-local agent activation and direct TypeScript
98
98
  evidence, with fresh-session reuse demonstrated in the tested Codex/macOS
99
- configuration**. Claude setup/removal mechanics are tested; Claude behavior and
100
- cross-host reuse are not. This is experimental guidance, not guaranteed agent
101
- compliance, general sandbox compatibility, or a promise of time savings.
99
+ configuration and measured for Claude Code below**. Cross-host reuse is not
100
+ tested. This is experimental guidance, not guaranteed agent compliance, general
101
+ sandbox compatibility, or a promise of time savings.
102
+
103
+ ### Measured with Claude Code
104
+
105
+ Four public TypeScript projects, four tasks each, three sessions per task with
106
+ and without REDUE: 96 headless Claude Code sessions (Sonnet 5.5, CI Linux
107
+ runner, alpha.12 code, October 2026). Plain `tsc --noEmit` takes 10–19 s on
108
+ these projects. Medians per task, summed over the four projects:
109
+
110
+ | Task | Session time, without → with REDUE | Input tokens | Typechecks skipped |
111
+ |---|---|---|---|
112
+ | Docs edit, then "make sure it still typechecks" | 97 → 45 s | 169k → 162k | 12 of 12 |
113
+ | "Does it still typecheck?" after a pull | 74 → 22 s | 117k → 89k | 12 of 12 |
114
+ | Source change, typecheck, then a docs edit | 141 → 112 s | 210k → 200k | 9 of 12 |
115
+ | Source change with tests | 152 → 151 s | 269k → 234k | 0 of 12 |
116
+ | **All tasks** | **464 → 330 s** | **765k → 685k** | 33 |
117
+
118
+ - **Correctness:** every typecheck REDUE skipped was checked afterwards with
119
+ plain tsc on the exact tree it was skipped on. All 33 agreed, and all 96
120
+ final trees passed plain tsc and the task checks in both arms.
121
+ - **Cost:** API cost was the same ($0.65 without, $0.66 with). One-time setup
122
+ per checkout was 3–5 s, plus one recorded typecheck; the CLAUDE.md section
123
+ REDUE adds is about 60 tokens.
124
+ - **How:** each session gets a fresh clone at a pinned commit; the REDUE arm
125
+ first runs `redue init`, `agent setup claude`, `start` and one
126
+ `redue run typecheck`, standing for an earlier session's passing check.
127
+ The harness, tasks and raw results are in [`eval/`](eval/README.md).
128
+ - **Limits:** four projects and three runs per cell show direction, not a
129
+ general rate. Savings come only when an earlier pass still applies: tasks
130
+ that change source code save nothing, and on one project the docs edit came
131
+ after source edits that made the result STALE, so nothing was skipped. The
132
+ projects were chosen because they qualify for the direct TypeScript recipe;
133
+ of 38 screened projects, 19 qualified at alpha.12 and 22 at alpha.13. Times are agent session
134
+ time only, on one runner type.
102
135
 
103
136
  ## What the states mean
104
137
 
package/docs/user/json.md CHANGED
@@ -31,6 +31,7 @@ Stable-enough-for-alpha fields:
31
31
  | `freshness` | `CURRENT`, `STALE`, or `UNVERIFIED`, independent of result. |
32
32
  | `reuse_eligible` | True only for qualified, applicable passing evidence. Never derive permission to reuse from outcome alone. Missing means false. |
33
33
  | `run_facts` | Number of lookups outside the discovery certificate that the receipt's run made (direct TypeScript recipe). Status asks each again before answering CURRENT. Absent when there are none. |
34
+ | `probe_deadline_ms` | Top level. How long the full state probes of one decision may take in total: each check's deadline (5 s to 60 s, from earlier probes and runs) times its probe count, summed. Callers wait at least this long plus 10 s for a synchronized answer. |
34
35
  | `reason` | Human explanation; not a stable machine enum. Do not parse it to authorize reuse. |
35
36
  | `changed_inputs` | Known changed relevant paths when available, possibly only the first difference; empty/missing is not proof of no changes. |
36
37
  | `invocation` | Null without a receipt; otherwise run ID, completion `status`, duration, exit code/signal when known. Status distinguishes exited, interrupted, and start/capture failure. |
@@ -1,4 +1,4 @@
1
- # Opt-in development pilot: alpha.12
1
+ # Opt-in development pilot: alpha.13
2
2
 
3
3
  Use one fixed build for several ordinary development sessions. The owner will
4
4
  invite two or three willing testers and approve their projects; nothing is
@@ -35,7 +35,7 @@ are **NOT RUN**. The recording shortcut is macOS/direct-recipe only.
35
35
  For a new default npm installation:
36
36
 
37
37
  ```sh
38
- npm install --global @redue/cli@0.1.0-alpha.12
38
+ npm install --global @redue/cli@0.1.0-alpha.13
39
39
  redue --version
40
40
  cd my-project
41
41
  redue --config redue.config.json init --recipe typescript-direct --check typecheck --dry-run
@@ -89,7 +89,7 @@ using it; keep the same npm prefix, config and any explicit state selection.
89
89
 
90
90
  ```sh
91
91
  redue --config redue.config.json stop
92
- npm install --global @redue/cli@0.1.0-alpha.12
92
+ npm install --global @redue/cli@0.1.0-alpha.13
93
93
  redue --config redue.config.json agent setup codex --dry-run
94
94
  redue --config redue.config.json agent setup codex --apply
95
95
  redue --config redue.config.json start
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redue/cli",
3
3
  "private": false,
4
- "version": "0.1.0-alpha.12",
4
+ "version": "0.1.0-alpha.13",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
package/src/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import {builtInProbe,probeFloorMs,publishedDeadline} from './probe-deadline.mjs';
2
3
  import fs from 'node:fs';
3
4
  import {directQualification,directInterpretation,observedNpmContext} from './direct-typescript.mjs';
4
5
  import {directRunCertificateAllowed,directRunCertificate,directRunRecordable,directRunRecording,probeQuestions,validateRunRecord} from './direct-run-certificate.mjs';
@@ -43,7 +44,14 @@ timing('cli.config_resolution',configStarted);
43
44
  // A native Windows decision reconciles declared contents instead of trusting
44
45
  // watcher history. The measured 6k-file pnpm workspace can legitimately need
45
46
  // more than the former 15 s default; uncertainty still wins on expiry.
46
- const decisionDeadlineMs=process.platform==='win32'?30000:15000;
47
+ // A decision can include a full probe, whose deadline the observer publishes.
48
+ // Read when a wait starts, so a deadline the previous decision raised counts.
49
+ const decisionBaseMs=process.platform==='win32'?30000:15000;
50
+ function publishedProbeDeadline() {
51
+ try{return publishedDeadline(JSON.parse(fs.readFileSync(path.join(state,'status.json'),'utf8')).probe_deadline_ms);}
52
+ catch{return 0;}
53
+ }
54
+ const decisionDeadline=()=>Math.max(decisionBaseMs,publishedProbeDeadline()+10000);
47
55
  const digest=value=>createHash('sha256').update(value).digest('hex');
48
56
  function nodeIdentity() {const real=fs.realpathSync(process.execPath),st=fs.statSync(real,{bigint:true});
49
57
  return digest(JSON.stringify([real,process.version,...['dev','ino','mode','size','mtimeNs','ctimeNs']
@@ -69,11 +77,10 @@ function contextHashes() {return Object.fromEntries(config.checks.map(check=>[
69
77
  function probeHash(plan) {
70
78
  const started=performance.now();
71
79
  const values=[];
80
+ // The observer's current deadline, so a large project's probe is not cut short.
81
+ const timeout=Math.max(probeFloorMs(plan.qualification),publishedProbeDeadline());
72
82
  for(const argv of plan.probes||[]) {
73
- const timeout=plan.qualification==='pnpm-tsc-v1'||
74
- (process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
75
- .includes(plan.qualification))?10000:5000;
76
- const out=spawnSync(argv[0],argv.slice(1),{cwd:plan.cwd,
83
+ const out=spawnSync(argv[0],[...argv.slice(1),...(builtInProbe(argv)?[`--redue-deadline=${timeout}`]:[])],{cwd:plan.cwd,
77
84
  timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe'],env:process.env});
78
85
  if(out.error||out.signal||out.status!==0){
79
86
  timing('cli.probe',started,{outcome:out.error?.code||out.signal||out.status||'error',
@@ -210,7 +217,7 @@ async function execute() {
210
217
  const observedContext=selected.qualification===directQualification?{interpretation:directInterpretation,npmProxyDigest:observedNpmContext()}:null;
211
218
  timing('cli.context_before',contextStarted);
212
219
  const
213
- captureTimeout=Number(process.env.VSTATE_CAPTURE_TIMEOUT_MS||decisionDeadlineMs);
220
+ captureTimeout=Number(process.env.VSTATE_CAPTURE_TIMEOUT_MS||decisionDeadline());
214
221
  let before=null,after=null;
215
222
  // Expensive state probes can take seconds. Keep the filesystem checkpoint
216
223
  // adjacent to process launch so a late notification from earlier work
@@ -325,7 +332,9 @@ async function execute() {
325
332
  const endSnapshotStarted=performance.now();
326
333
  if(!unobservedDirectRun){
327
334
  try{after=await call({action:'snapshot',name,contextHashes:contextHashes(),
328
- ...(recording?{runCertificate:1,adopt:recording}:recorded&&!certificate?{runCertificate:1,runFacts:1}:{})},captureTimeout);}
335
+ ...(recording?{runCertificate:1,adopt:recording}:recorded&&!certificate?{runCertificate:1,runFacts:1}:{})},
336
+ // The start checkpoint's probe may have raised the deadline.
337
+ Number(process.env.VSTATE_CAPTURE_TIMEOUT_MS||decisionDeadline()));}
329
338
  catch{captureIssues.push('end observation unavailable');}
330
339
  finally{if(recordFile)fs.rmSync(recordFile,{force:true});}
331
340
  timing('cli.end_checkpoint',endSnapshotStarted,{available:after?.snapshots?.[name]?1:0});
@@ -489,7 +498,7 @@ async function main(){
489
498
  observedNpmDigest:observedNpmContext(),proxyShapes});
490
499
  }
491
500
  let value;try{value=await decisionCall({action:'sync',contextHashes:contextHashes()},
492
- Number(process.env.VSTATE_SYNC_TIMEOUT_MS||decisionDeadlineMs));
501
+ Number(process.env.VSTATE_SYNC_TIMEOUT_MS||decisionDeadline()));
493
502
  // A client upgrade can encounter an older running observer. Its successful
494
503
  // control response does not establish that it evaluated the latest receipt.
495
504
  if(value.receipt_revision===null){
package/src/daemon.mjs CHANGED
@@ -18,6 +18,7 @@ import {atomicJson,readReceipts,readReceiptSnapshot,receiptRevision} from './sta
18
18
  import {history,closeHistory} from './observation.mjs';
19
19
  import {inputKey,planGuard,toolchainKey} from './decision-validation.mjs';
20
20
  import {queriesHold,runFactReason as runFactChanged} from './typescript-list.mjs';
21
+ import {nextProbeHint,probeDeadline,probeFloorMs} from './probe-deadline.mjs';
21
22
  import {statusSummary} from './cached-state.mjs';
22
23
  import {directProject,directProbeValue,reviewedTypeScriptVersions} from './direct-typescript.mjs';
23
24
  import {buildInfoPath,recordedCertificate} from './direct-run-certificate.mjs';
@@ -153,6 +154,14 @@ function reuseCertificate(row,key,allowReuse,forceFull,checked) {
153
154
  metrics.probeReuses=(metrics.probeReuses||0)+1;
154
155
  return null;
155
156
  }
157
+ // The current full-probe deadline for a check; see probe-deadline.mjs. Hints
158
+ // are kept per check name, so a plan rebuild (each Windows decision) keeps them.
159
+ const probeHints=new Map();
160
+ function fullProbeDeadline(row) {
161
+ const run=receipts[row.plan.name]?.invocation;
162
+ return probeDeadline({floor:probeFloorMs(row.plan.qualification),hint:probeHints.get(row.plan.name),
163
+ runMs:run?.status==='exited'?run.durationMs:0});
164
+ }
156
165
  function probe(row,allowReuse=false,{launch=true}={}) {
157
166
  const started=performance.now();
158
167
  const supported=new Map([
@@ -205,18 +214,21 @@ function probe(row,allowReuse=false,{launch=true}={}) {
205
214
  row.probeCache=null;
206
215
  }
207
216
  const values=[];let checkpoint=null;
217
+ // The slowest of this check's probes sets the hint for all of them.
218
+ const deadline=fullProbeDeadline(row);let hint=0;
219
+ const learn=()=>{if(hint)probeHints.set(row.plan.name,hint);};
208
220
  for(const argv of row.plan.probes) {
209
- // Supported TypeScript probes perform bounded input/toolchain hashing.
210
- // Native NTFS runs have exceeded the generic 5 s cap under load, despite
211
- // completing normally and with the duplicated work already removed.
212
- const timeout=row.plan.qualification==='pnpm-tsc-v1'||
213
- (process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
214
- .includes(row.plan.qualification))?10000:5000;
221
+ const timeout=deadline,launched=performance.now();
215
222
  mark('daemon.probe_launch',{mode:'full',reason:fullReason,deadlineMs:timeout});
216
- const out=spawnSync(argv[0],[...argv.slice(1),...(cacheable?['--redue-checkpoint']:[])],{cwd:row.plan.cwd,
223
+ // A built-in probe gets its deadline, so its inner compiler calls share it.
224
+ const out=spawnSync(argv[0],[...argv.slice(1),...(cacheable?['--redue-checkpoint']:[]),
225
+ ...(builtIn?[`--redue-deadline=${timeout}`]:[])],{cwd:row.plan.cwd,
217
226
  timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe'],env:traceEnvironment()});
218
227
  mark('daemon.probe_complete',{mode:'full',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
228
+ const timedOut=out.error?.code==='ETIMEDOUT';
229
+ if(!out.error||timedOut)hint=Math.max(hint,nextProbeHint({durationMs:performance.now()-launched,deadlineMs:timeout,timedOut}));
219
230
  if(out.error||out.signal||out.status!==0) {
231
+ learn();
220
232
  if(!row.probeError){row.revision++;row.lastReason='declared state probe became unavailable';}
221
233
  row.probeHash=null;row.probeAt=0;
222
234
  const code=['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1','pnpm-tsc-v1']
@@ -280,6 +292,7 @@ function probe(row,allowReuse=false,{launch=true}={}) {
280
292
  }
281
293
  values.push([argv,out.status,sha(cacheable?checkpoint.output:out.stdout),sha(out.stderr)]);
282
294
  }
295
+ learn();
283
296
  const value=sha(JSON.stringify(values));
284
297
  if((row.probeHash!==null&&row.probeHash!==value)||row.probeError){
285
298
  row.revision++;row.lastReason='declared state probe changed or recovered';}
@@ -478,7 +491,10 @@ function status(contextHashes,synchronizedInstalled=false,synchronizedFilesystem
478
491
  // wrapper may persist its outcome even when its control connection is denied.
479
492
  refreshReceipts();
480
493
  const checks=rows(contextHashes,synchronizedInstalled,synchronizedFilesystem);
494
+ // A decision may run every check's probes in turn: callers wait for all of them.
495
+ const probing=[...(index?.checks.values()||[])].filter(row=>row.plan.probes.length);
481
496
  const now=Date.now();return {schema:1,receipt_revision:receiptStateRevision,...statusSummary(checks),checks,
497
+ ...(probing.length?{probe_deadline_ms:probing.reduce((sum,row)=>sum+fullProbeDeadline(row)*row.plan.probes.length,0)}:{}),
482
498
  observation:{healthy:healthy&&!planning&&!recovering&&!index?.unavailable,
483
499
  generation,observed_at:lastObservation,
484
500
  events:(index?.eventCount||0)+[...external.values()].reduce((n,e)=>n+(e.index?.eventCount||0),0),
@@ -1,6 +1,7 @@
1
1
  // Pinned pnpm 12 node-modules installation provider. The engine sees only
2
2
  // repository-relative input sets and resolution relationships, never pnpm
3
3
  // virtual-store names as semantic identities.
4
+ import {probeTimeLeft} from './probe-deadline.mjs';
4
5
  import {compilerFiles} from './typescript-list.mjs';
5
6
  import fs from 'node:fs';
6
7
  import path from 'node:path';
@@ -96,7 +97,7 @@ export function pnpmWorkspacePackages(root,patterns){
96
97
  const files=execFileSync('git',['ls-files','--cached','--others','--exclude-standard',
97
98
  '-z','--',':(glob)**/package.json'],{cwd:root,
98
99
  env:{...process.env,GIT_OPTIONAL_LOCKS:'0'},maxBuffer:32*1024*1024,
99
- timeout:5000}).toString().split('\0').filter(Boolean);
100
+ timeout:probeTimeLeft(5000)}).toString().split('\0').filter(Boolean);
100
101
  for(const file of files){
101
102
  const dir=path.posix.dirname(file);
102
103
  if(dir==='.'||dir.split('/').includes('node_modules')||
@@ -202,7 +203,7 @@ function listedTypeScriptFiles(root,selected,tsc){
202
203
  {cwd:path.join(root,selected.dir),env:process.env,encoding:'utf8',
203
204
  // The native NTFS public workspace takes 3-5 s to list ~770 inputs;
204
205
  // 5 s sometimes expired despite bounded, progressing compiler work.
205
- timeout:10000,maxBuffer:32*1024*1024,stdio:['ignore','pipe','pipe']});
206
+ timeout:probeTimeLeft(10000),maxBuffer:32*1024*1024,stdio:['ignore','pipe','pipe']});
206
207
  if(out.error||out.signal||out.status!==0)
207
208
  throw Error('TypeScript input listing is unavailable');
208
209
  const files=out.stdout.trim().split(/\r?\n/).filter(Boolean)
@@ -0,0 +1,37 @@
1
+ // How long a full probe may run before it is killed and the check stays
2
+ // UNVERIFIED. It grows with the project: the larger of the hint from earlier
3
+ // probes (twice the last successful one, or twice the deadline that last timed
4
+ // out) and the last completed run, between the platform floor and a ceiling.
5
+ import {fileURLToPath} from 'node:url';
6
+ export const probeCeilingMs=process.env.VSTATE_TEST_FAULTS==='1'&&
7
+ Number(process.env.VSTATE_TEST_PROBE_CEILING_MS)||60000;
8
+ // Supported TypeScript probes perform bounded input/toolchain hashing.
9
+ // Native NTFS runs have exceeded the generic 5 s cap under load, despite
10
+ // completing normally and with the duplicated work already removed.
11
+ export const probeFloorMs=qualification=>qualification==='pnpm-tsc-v1'||
12
+ (process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
13
+ .includes(qualification))?10000:5000;
14
+ // REDUE's own probes, which accept --redue-deadline; a user's probe never gets it.
15
+ const builtIn=new Set(['typescript-contract-probe.mjs','direct-typescript-probe.mjs',
16
+ 'yarn-workspace-typecheck-probe.mjs','pnpm-typecheck-probe.mjs'].map(name=>fileURLToPath(new URL(name,import.meta.url))));
17
+ export const builtInProbe=argv=>builtIn.has(argv?.[1]);
18
+ // A deadline read from status.json, as a whole number of milliseconds no
19
+ // longer than a decision could need (0 when absent or unusable).
20
+ export function publishedDeadline(value) {
21
+ return Number.isFinite(value)&&value>0?Math.min(Math.round(value),10*60*1000):0;
22
+ }
23
+ export function probeDeadline({floor,hint=0,runMs=0,ceiling=probeCeilingMs}) {
24
+ const finite=value=>Number.isFinite(value)&&value>0?value:0;
25
+ return Math.round(Math.min(ceiling,Math.max(floor,finite(hint),finite(runMs))));
26
+ }
27
+ // The hint the next probe starts from.
28
+ export function nextProbeHint({durationMs,deadlineMs,timedOut}) {
29
+ return 2*(timedOut?deadlineMs:durationMs);
30
+ }
31
+ // Inside a probe process: what is left of the deadline its caller passed with
32
+ // --redue-deadline=MS, or the fallback when none was passed. An inner call
33
+ // always ends before its parent is killed, so it never outlives the probe.
34
+ export function probeTimeLeft(fallback) {
35
+ const deadline=Number(/^--redue-deadline=(\d+)$/.exec(process.argv.find(arg=>arg.startsWith('--redue-deadline='))||'')?.[1]);
36
+ return deadline?Math.max(1,Math.round(deadline-performance.now()-250)):fallback;
37
+ }
@@ -3,6 +3,7 @@ import fs from 'node:fs';import path from 'node:path';import {createHash} from '
3
3
  import {createRequire} from 'node:module';import {spawnSync} from 'node:child_process';
4
4
  import {compilerFiles} from './typescript-list.mjs';
5
5
  import {buildInfoFor} from './direct-typescript.mjs';
6
+ import {probeTimeLeft} from './probe-deadline.mjs';
6
7
  import {withinPath as within,realObservedPath} from './path-identity.mjs';
7
8
  import {timing} from './decision-profile.mjs';
8
9
  const require=createRequire(import.meta.url),hash=value=>createHash('sha256').update(value).digest('hex');
@@ -45,7 +46,7 @@ export function typeScriptInputFacts(root,tsRoot,tsc){
45
46
  if(/"(?:extends|references|plugins|incremental|composite)"\s*:/.test(raw))
46
47
  fail('typescript-config-graph-unsupported');
47
48
  const shown=spawnSync(process.execPath,[tsc,'--showConfig'],{cwd:root,
48
- env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
49
+ env:process.env,encoding:'utf8',timeout:probeTimeLeft(4000),maxBuffer:16*1024*1024,
49
50
  stdio:['ignore','pipe','pipe']});
50
51
  if(shown.error||shown.signal||shown.status!==0)fail('typescript-config-unavailable');
51
52
  const effective=JSON.parse(shown.stdout);
@@ -59,7 +60,7 @@ export function typeScriptInputFacts(root,tsRoot,tsc){
59
60
  const listingStarted=performance.now();
60
61
  const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(tsRoot,root):null;
61
62
  const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'--noEmit','--listFilesOnly'],
62
- {cwd:root,env:process.env,encoding:'utf8',timeout:process.platform==='win32'?10000:4000,
63
+ {cwd:root,env:process.env,encoding:'utf8',timeout:probeTimeLeft(process.platform==='win32'?10000:4000),
63
64
  maxBuffer:16*1024*1024,
64
65
  stdio:['ignore','pipe','pipe']});
65
66
  timing('typescript_contract.list_files',listingStarted,{status:listed.status??null,
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Read-only applicability probe. Its output is one digest or a fixed reason;
3
3
  // source, configuration, environment values, and command output are not logged.
4
+ import {probeTimeLeft} from './probe-deadline.mjs';
4
5
  import {compilerFiles} from './typescript-list.mjs';
5
6
  import {contextOnly,probeResult} from './probe-result.mjs';
6
7
  import fs from 'node:fs';
@@ -79,7 +80,7 @@ try{
79
80
  const tsc=yarnLocalTsc(root);
80
81
  const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(path.join(root,'node_modules/typescript'),cwd):null;
81
82
  const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'-p','.','--listFilesOnly'],
82
- {cwd,env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
83
+ {cwd,env:process.env,encoding:'utf8',timeout:probeTimeLeft(4000),maxBuffer:16*1024*1024,
83
84
  stdio:['ignore','pipe','pipe']});
84
85
  if(listed.error||listed.signal||listed.status!==0)fail('typescript-input-list-unavailable');
85
86
  const inputs=listed.stdout.trim().split(/\r?\n/).filter(Boolean)