@redue/cli 0.1.0-alpha.7 → 0.1.0-alpha.8

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.8 — 2026-10-08
4
+
5
+ - Consume compiler-certificate input asynchronously while preserving the input-size
6
+ limit, parent deadline, complete-input validation and conservative fallbacks.
7
+ - Support the exact reviewed TypeScript 5.5.2 distribution in the explicitly selected
8
+ single-package npm direct-compiler recipe. Enforce implementation membership and
9
+ reject ETW module overrides before compiler API loading.
10
+ - Real independent Codex/macOS sessions reused compiler evidence on the pinned
11
+ vscode-js-debug project. Cross-platform CLI/package acceptance is separate;
12
+ Claude behavior and cross-host handoff remain untested.
13
+ - The measured eight-case workflow avoided four compiler executions but cost
14
+ 20.01 seconds versus 16.97 seconds for eight ordinary compiler runs. No net
15
+ workflow or whole-agent speedup is claimed. This verifies compiler inputs only,
16
+ not npm lifecycle behavior or application correctness.
17
+ - Stop observers before upgrading, retain the same configuration/state, then
18
+ restart. Do not delete receipts or automatically convert existing checks.
19
+
3
20
  ## 0.1.0-alpha.7 — 2026-10-07
4
21
 
5
22
  - Guide explicitly configured direct-compiler reuse through one caller-aware
package/bin/redue.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
+ import {mark,traceBase,traceContext,traceEnvironment,traceId} from '../src/decision-profile.mjs';
4
+ traceBase({...traceContext(),request:traceId(),work:'client'});
5
+ mark('client.loaded');
3
6
  import {directCompiler,directQualification,directEnvironment,directInterpretation} from '../src/direct-typescript.mjs';
4
7
  import os from 'node:os';
5
8
  import path from 'node:path';
@@ -154,6 +157,7 @@ function printLifecycle(output,action,machine){
154
157
  if(stateOverride&&['stop','remove-state'].includes(command)){
155
158
  const runtime=path.join(stateOverride,'project-runtime-v1.json');
156
159
  if(!fs.existsSync(runtime)){console.log('No REDUE state exists at this path.');process.exit(0);}
160
+ mark('client.engine_submit');
157
161
  const result=spawnSync(process.execPath,[engine,runtime,command==='stop'?'stop':'uninstall'],
158
162
  {cwd:os.tmpdir(),stdio:['inherit','pipe','inherit'],encoding:'utf8',env:process.env});
159
163
  if(result.error)error(result.error.message);
@@ -334,9 +338,11 @@ if(command==='run'){
334
338
  if(outcome.error)error(outcome.error.message);
335
339
  process.exit(outcome.status??(outcome.signal?128+os.constants.signals[outcome.signal]:1));
336
340
  }
341
+ mark('client.engine_submit');
337
342
  const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
338
343
  {stdio:['inherit','pipe','inherit'],
339
- encoding:'utf8',env:process.env});
344
+ encoding:'utf8',env:traceEnvironment()});
345
+ mark('client.engine_complete',{exit:result.status,signal:result.signal});
340
346
  if(result.error)error(result.error.message);
341
347
  if(result.stdout){
342
348
  if(['status','detail','explain'].includes(command)){
@@ -5,7 +5,7 @@ check against declared, observed inputs and relevant execution context. It does
5
5
  not mean npm or npm lifecycle scripts completed.
6
6
 
7
7
  For a supported single-package npm project with an existing standalone
8
- `tsc --noEmit` script, installed dependencies, Node 22/24 and TypeScript 5.6.3/5.9.3:
8
+ `tsc --noEmit` script, installed dependencies, Node 22/24 and TypeScript 5.5.2/5.6.3/5.9.3:
9
9
 
10
10
  ```sh
11
11
  redue init --recipe typescript-direct --check typecheck --dry-run
@@ -33,7 +33,7 @@ A generated check contains:
33
33
 
34
34
  `script` identifies the reviewed source recipe; `command` is what actually runs:
35
35
  the resolved Node executable and local TypeScript implementation, with exact argv
36
- and no shell. TypeScript 5.6.3 uses `lib/tsc.js`; 5.9.3 uses `lib/_tsc.js` directly,
36
+ and no shell. TypeScript 5.5.2 and 5.6.3 use `lib/tsc.js`; 5.9.3 uses `lib/_tsc.js` directly,
37
37
  without its outer compile-cache-enabling shim. No compiler is fetched or bundled.
38
38
  The entire installed package must match reviewed official distribution bytes.
39
39
 
@@ -19,7 +19,7 @@ project script. Automatic npm-launcher applicability was withdrawn in alpha.4;
19
19
  ordinary npm scripts remain recording-only. See the [maintainer notice](npm-launcher-notice.md).
20
20
  The following direct recipe is opt-in and new in the alpha.5 candidate. The opt-in `init --recipe typescript-direct` supports
21
21
  a standalone `tsc --noEmit` script in a single-package npm installation using
22
- reviewed TypeScript 5.6.3/5.9.3 and Node 22/24. It executes the local compiler,
22
+ reviewed TypeScript 5.5.2/5.6.3/5.9.3 and Node 22/24. It executes the local compiler,
23
23
  not npm. Full installed compiler identity, config/membership, installed inputs
24
24
  and relevant Node environment are checked before reuse. See the
25
25
  [exact direct boundary](direct-typescript.md). Unsupported plugins, preloads,
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.7",
4
+ "version": "0.1.0-alpha.8",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
@@ -13,7 +13,8 @@
13
13
  "scripts": {
14
14
  "test": "node --test --test-concurrency=1 test/*.test.mjs",
15
15
  "test:package": "node test/acceptance/package-smoke.mjs",
16
- "demo": "node test/acceptance/package-smoke.mjs --demo"
16
+ "demo": "node test/acceptance/package-smoke.mjs --demo",
17
+ "pretest": "npm --prefix test/fixtures/typescript-5-5-2 ci --no-audit --no-fund"
17
18
  },
18
19
  "dependencies": {
19
20
  "@parcel/watcher": "2.5.6",
package/src/cli.mjs CHANGED
@@ -17,10 +17,11 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
17
17
  import {permittedRoots} from './installed-inputs.mjs';
18
18
  import {acquireRunLock,inspectRunLock,recoverRunLock} from './run-lock.mjs';
19
19
  import {atomicJson,commitReceipt,receiptRevision} from './state-store.mjs';
20
- import {timing,profiling} from './decision-profile.mjs';
20
+ import {timing,profiling,mark,traceContext} from './decision-profile.mjs';
21
21
  import {readCachedStatus,unavailableCached} from './cached-state.mjs';
22
22
  import {renderRun} from './presentation.mjs';
23
23
 
24
+ mark('cli.loaded');
24
25
  const here=path.dirname(fileURLToPath(import.meta.url));
25
26
  const [configArg,action,name]=process.argv.slice(2);
26
27
  if(!configArg||!action)throw Error('internal usage: cli.mjs CONFIG ACTION [check]');
@@ -93,13 +94,13 @@ function owner(){
93
94
  }
94
95
  function call(message,timeout=5000) {
95
96
  return new Promise((resolve,reject)=>{
96
- let data='',done=false;const client=net.createConnection(socket);
97
- const timer=setTimeout(()=>{client.destroy();reject(Error('observer synchronization timeout'));},timeout);
98
- client.on('connect',()=>client.write(JSON.stringify(message)+'\n'));
97
+ let data='',done=false;mark('client.control_connect',{deadlineMs:timeout});const client=net.createConnection(socket);
98
+ const timer=setTimeout(()=>{mark('client.control_timeout');client.destroy();reject(Error('observer synchronization timeout'));},timeout);
99
+ client.on('connect',()=>{mark('client.control_submit');client.write(JSON.stringify({...message,...(profiling?{diagnostic:traceContext()}: {})})+'\n');});
99
100
  client.on('data',chunk=>data+=chunk);
100
- client.on('end',()=>{clearTimeout(timer);if(done)return;done=true;
101
+ client.on('end',()=>{mark('client.control_complete',{bytes:data.length});clearTimeout(timer);if(done)return;done=true;
101
102
  try{resolve(JSON.parse(data));}catch(e){reject(e);}});
102
- client.on('error',e=>{clearTimeout(timer);reject(e);});
103
+ client.on('error',e=>{mark('client.control_error',{code:e.code});clearTimeout(timer);reject(e);});
103
104
  });
104
105
  }
105
106
  async function decisionCall(message,timeout) {
package/src/daemon.mjs CHANGED
@@ -17,7 +17,7 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
17
17
  import {atomicJson,readReceipts,readReceiptSnapshot,receiptRevision} from './state-store.mjs';
18
18
  import {history} from './observation.mjs';
19
19
  import {inputKey,planGuard} from './decision-validation.mjs';
20
- import {timing} from './decision-profile.mjs';
20
+ import {timing,mark,traceBase,traceContext,traceScope,traceEnvironment,traceId} from './decision-profile.mjs';
21
21
 
22
22
  const configFile=path.resolve(process.argv[2]);
23
23
  const config=JSON.parse(fs.readFileSync(configFile));
@@ -31,6 +31,7 @@ const endpoint=controlEndpoint(state),socket=endpoint.address,
31
31
  fs.mkdirSync(lock); // Existing lock requires operator inspection; never steal it.
32
32
  fs.writeFileSync(path.join(lock,'pid'),String(process.pid),{mode:0o600});
33
33
  const generation=randomUUID(), startedAt=Date.now();
34
+ traceBase({generation,work:'startup'});
34
35
  let index, receipts={}, watcher, healthy=false, reason='initial reconciliation', stopping=false;
35
36
  let receiptStateError=null;
36
37
  let receiptStateRevision=null;
@@ -136,10 +137,12 @@ function probe(row,allowReuse=false) {
136
137
  const forceFull=process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_PROBES==='1';
137
138
  if(cacheable&&allowReuse&&macGuarded&&!forceFull&&cache?.input===key){
138
139
  const argv=row.plan.probes[0];
140
+ mark('daemon.probe_launch',{mode:'certificate',deadlineMs:10000});
139
141
  const out=spawnSync(argv[0],[...argv.slice(1),macDirect?'--redue-validate-queries':'--redue-context'],{
140
- cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
142
+ cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,env:traceEnvironment(),
141
143
  ...(macDirect?{input:JSON.stringify({schema:cache.schema,context:cache.context,queries:cache.queries})}:
142
144
  {stdio:['ignore','pipe','pipe']})});
145
+ mark('daemon.probe_complete',{mode:'certificate',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
143
146
  metrics.probeContextProcesses=(metrics.probeContextProcesses||0)+1;
144
147
  let stillValid=!macDirect;
145
148
  if(macDirect)try{stillValid=macProbeBarrier()&&inputKey(row)===key&&planGuard(index.bundle)===guard;}catch{}
@@ -170,8 +173,10 @@ function probe(row,allowReuse=false) {
170
173
  const timeout=row.plan.qualification==='pnpm-tsc-v1'||
171
174
  (process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
172
175
  .includes(row.plan.qualification))?10000:5000;
176
+ mark('daemon.probe_launch',{mode:'full',reason:fullReason,deadlineMs:timeout});
173
177
  const out=spawnSync(argv[0],[...argv.slice(1),...(cacheable?['--redue-checkpoint']:[])],{cwd:row.plan.cwd,
174
- timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe']});
178
+ timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe'],env:traceEnvironment()});
179
+ mark('daemon.probe_complete',{mode:'full',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
175
180
  if(out.error||out.signal||out.status!==0) {
176
181
  if(!row.probeError){row.revision++;row.lastReason='declared state probe became unavailable';}
177
182
  row.probeHash=null;row.probeAt=0;
@@ -180,7 +185,7 @@ function probe(row,allowReuse=false) {
180
185
  /VSTATE_REASON:([a-z-]+)/.exec(out.stderr?.toString()||'')?.[1]:null;
181
186
  const explanations={
182
187
  'direct-typescript-implementation-unreviewed':'installed TypeScript bytes or membership differ from the reviewed compiler',
183
- 'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.6.3 and 5.9.3 only',
188
+ 'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.5.2, 5.6.3 and 5.9.3 only',
184
189
  'direct-execution-environment-unsupported':'direct compiler rejects development helpers, Node cache/preloads, and unreviewed runtime overrides',
185
190
  'direct-node-version-unsupported':'direct compiler recipe supports Node 22 and 24',
186
191
  'npm-launcher-unobserved-inputs':'npm launcher uses unobserved notifier inputs; execution is recording-only',
@@ -420,7 +425,7 @@ function buildInWorker(forceCold=false,reuse=null) {
420
425
  return new Promise((resolve,reject)=>{
421
426
  const workerStarted=performance.now();
422
427
  const file=path.join(path.dirname(fileURLToPath(import.meta.url)),'plan-worker.mjs');
423
- const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,
428
+ const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,diagnostic:traceContext(),
424
429
  reconcileFaultFile:process.env.VSTATE_TEST_RECONCILE_FAULT_FILE}});
425
430
  planningWorker=worker;let settled=false;
426
431
  const deadline=setTimeout(()=>{
@@ -794,7 +799,7 @@ async function catchUp() {
794
799
  lastObservation=Date.now();return;
795
800
  }
796
801
  if(historyDisabled)return;
797
- if(synchronizing)return synchronizing;
802
+ if(synchronizing){mark('daemon.catch_up_join');return synchronizing;}
798
803
  synchronizing=(async()=>{
799
804
  if(!verifyRoot())throw Error('checkout identity changed');
800
805
  if(!watcher)throw Error('subscription unavailable');
@@ -825,7 +830,8 @@ async function catchUp() {
825
830
  function respond(client,value) {
826
831
  if(client.destroyed){event('control_reply_unavailable',{classification:'client_destroyed'});return;}
827
832
  try{const payload=JSON.stringify(value)+'\n';
828
- client.end(payload);client.vstateReplied=true;
833
+ mark('server.response_constructed',{bytes:payload.length});
834
+ client.end(payload,()=>mark('server.response_delivered'));client.vstateReplied=true;
829
835
  if(process.env.VSTATE_DEBUG_CONTROL==='1')event('control_reply_sent',{bytes:payload.length});}
830
836
  catch(e){event('control_reply_unavailable',{classification:e.name||'serialization_error'});
831
837
  if(!client.destroyed)client.end('{"error":"control response unavailable"}\n');}
@@ -843,6 +849,7 @@ async function request(message) {
843
849
  if(message.action==='sync'||message.action==='snapshot'||message.action==='prelaunch') {
844
850
  const totalStarted=performance.now();
845
851
  const decision=++decisionSequence;
852
+ mark('server.decision_start',{decision,pending:pending.size,planDirty,planning,recovering,healthy,historyDisabled,decisionRequests,historyDeadlineMs,reconciliationDeadlineMs});
846
853
  timing('daemon.decision_reason',totalStarted,{decision,action:message.action,
847
854
  plan:process.platform==='win32'?'validate_dependencies_before_reuse':'existing_plan',
848
855
  probe:'reuse_only_with_identical_content_and_context',installed:'current_content_required',
@@ -970,9 +977,11 @@ const server=net.createServer(client=>{
970
977
  try{const message=JSON.parse(text);
971
978
  const decision=message.action==='sync'||message.action==='snapshot';
972
979
  if(decision)decisionRequests++;
973
- Promise.resolve(request(message)).then(v=>respond(client,v))
974
- .catch(e=>respond(client,{error:e.message}))
975
- .finally(()=>{if(decision)decisionRequests--;});}
980
+ traceScope({request:message.diagnostic?.request||traceId(),work:'request'},()=>{
981
+ mark('server.request_received',{activeDecisions:decisionRequests});
982
+ Promise.resolve(request(message)).then(v=>respond(client,v))
983
+ .catch(e=>respond(client,{error:e.message}))
984
+ .finally(()=>{mark('server.request_complete');if(decision)decisionRequests--;});});}
976
985
  catch(e){respond(client,{error:e.message});}
977
986
  });
978
987
  });
@@ -1032,7 +1041,8 @@ const heartbeat=setInterval(()=>{
1032
1041
  }
1033
1042
  publish();
1034
1043
  },1000);
1035
- const probeTimer=setInterval(async()=>{
1044
+ const probeTimer=setInterval(()=>traceScope({request:traceId(),work:'periodic'},async()=>{
1045
+ mark('server.periodic_start',{pending:pending.size,planDirty,planning,decisionRequests});
1036
1046
  // Windows cached reads are conservatively UNVERIFIED until a full decision
1037
1047
  // reconciliation. Periodic synchronous probes cannot strengthen that cache,
1038
1048
  // but can block control requests behind several seconds of child work.
@@ -1045,7 +1055,9 @@ const probeTimer=setInterval(async()=>{
1045
1055
  }
1046
1056
  publish();}
1047
1057
  catch(e){observationGap('periodic_query',e);}
1048
- }},5000);
1058
+ }
1059
+ mark('server.periodic_complete');
1060
+ }),5000);
1049
1061
  process.on('uncaughtExceptionMonitor',error=>{
1050
1062
  try{event('observer_uncaught_exception',{pid:process.pid,code:error.code||null,
1051
1063
  message:error.message,stack:error.stack});}catch{}
@@ -1,15 +1,29 @@
1
1
  // Opt-in, bounded local diagnostics. Never include paths, command arguments,
2
2
  // environment values, source, or probe output in a timing record.
3
3
  import fs from 'node:fs';
4
+ import {AsyncLocalStorage} from 'node:async_hooks';
5
+ import {randomUUID} from 'node:crypto';
4
6
 
5
7
  const file=process.env.REDUE_DECISION_PROFILE_FILE;
6
8
  export const profiling=process.env.REDUE_DECISION_PROFILE==='1'&&!!file;
7
9
 
10
+ const traces=new AsyncLocalStorage();
11
+ const safe=value=>typeof value==='string'&&/^[a-zA-Z0-9_-]{1,64}$/.test(value)?value:undefined;
12
+ const clean=value=>Object.fromEntries(['request','generation','work'].map(key=>[key,safe(value?.[key])]).filter(([,v])=>v));
13
+ let base={};
14
+ try{base=clean(JSON.parse(process.env.REDUE_PROFILE_CONTEXT||'{}'));}catch{}
15
+ export const traceContext=()=>profiling?{...base,...traces.getStore()}:{};
16
+ export function traceBase(value){if(profiling)base=clean(value);}
17
+ export const traceId=()=>randomUUID();
18
+ export const traceScope=(value,fn)=>profiling?traces.run({...traceContext(),...clean(value)},fn):fn();
19
+ export const traceEnvironment=()=>profiling?{...process.env,REDUE_PROFILE_CONTEXT:JSON.stringify(traceContext())}:process.env;
20
+ export function mark(phase,fields={}){timing(phase,performance.now(),fields);}
21
+
8
22
  export function timing(phase,started,counts={}) {
9
23
  if(!profiling)return;
10
24
  const memory=process.memoryUsage();
11
25
  try{fs.appendFileSync(file,'REDUE_TIMING '+JSON.stringify({
12
- at:Date.now(),pid:process.pid,phase,
26
+ at:Date.now(),monoNs:process.hrtime.bigint().toString(),pid:process.pid,phase,...traceContext(),
13
27
  ms:Math.round((performance.now()-started)*10)/10,
14
28
  rssMiB:Math.round(memory.rss/1048576*10)/10,
15
29
  ...counts
@@ -4,10 +4,9 @@ import {directProject,directInterpretation} from './direct-typescript.mjs';
4
4
  import {typeScriptInputFacts} from './typescript-input-facts.mjs';
5
5
  import {contextOnly,probeResult} from './probe-result.mjs';
6
6
  import {realObservedPath} from './path-identity.mjs';
7
- import fs from 'node:fs';
8
7
  import path from 'node:path';
9
8
  import {queriesMatch} from './typescript-list.mjs';
10
- import {timing} from './decision-profile.mjs';
9
+ import {timing,mark} from './decision-profile.mjs';
11
10
  const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
12
11
  try{
13
12
  const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
@@ -22,8 +21,16 @@ try{
22
21
  if(process.argv.includes('--redue-validate-queries')){
23
22
  // Internal, process-local discovery certificate; never a verification receipt.
24
23
  // Verify the current implementation above before queryAnswer can load its API.
25
- const started=performance.now(),raw=fs.readFileSync(0,'utf8');
26
- if(Buffer.byteLength(raw)>8*1024*1024)throw Error('certificate too large');
24
+ const started=performance.now();mark('certificate.input_start');
25
+ // A synchronous fd-0 read can stall with spawnSync's piped input on macOS
26
+ // Node 22.13.0. Drain the stream without blocking its input delivery. The
27
+ // parent's existing deadline and the same size limit remain authoritative.
28
+ const chunks=[];let bytes=0;
29
+ for await(const chunk of process.stdin){
30
+ bytes+=chunk.length;if(bytes>8*1024*1024)throw Error('certificate too large');
31
+ chunks.push(chunk);
32
+ }
33
+ const raw=Buffer.concat(chunks).toString('utf8');mark('certificate.input_complete',{bytes});
27
34
  const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
28
35
  'fileExists','directoryExists','realpath','directories','entries']);
29
36
  if(certificate.schema!==1||certificate.context!==contextHash||
@@ -34,7 +41,9 @@ try{
34
41
  q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
35
42
  q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
36
43
  throw Error('certificate unavailable');
44
+ mark('certificate.queries_start',{queries:certificate.queries.length});
37
45
  const matched=queriesMatch(certificate.queries);
46
+ mark('certificate.queries_complete',{matched});
38
47
  timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
39
48
  if(!matched)throw Error('compiler queries changed');
40
49
  process.stdout.write(contextHash);
@@ -7,17 +7,21 @@ import {realObservedPath,withinPath} from './path-identity.mjs';
7
7
  export const directInterpretation='npm-direct-typescript@1';
8
8
  export const directQualification='npm-typescript-direct-v1';
9
9
  const reviewed={
10
+ '5.5.2':{entry:'lib/tsc.js',digest:'953b4844b5f6edc745be844b6c367c227b59029b73c4182f508f90cc47e21816'},
10
11
  '5.6.3':{entry:'lib/tsc.js',digest:'e674b5dc4dac50f6e91be30094aeea983a1f340231717e62b0b6199ae3049eb2'},
11
12
  '5.9.3':{entry:'lib/_tsc.js',digest:'d1b32e75475710fdc85102b1365254d84ceb192d3639d962c53fe1355ce274ca'},
12
13
  };
13
14
  const fail=code=>{throw Object.assign(Error(code),{code});};
14
15
  const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
15
16
  export const directEnvironment={variables:['NODE_ENV','NODE_OPTIONS','NODE_PATH','NODE_COMPILE_CACHE','VSCODE_INSPECTOR_OPTIONS'],
16
- prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_'],executableIdentity:true};
17
+ prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_','TS_ETW_'],executableIdentity:true};
17
18
  export function directEnvironmentIssue(env=process.env){
18
19
  if(![22,24].includes(Number(process.versions.node.split('.')[0])))return 'direct-node-version-unsupported';
19
20
  if(env.VSCODE_INSPECTOR_OPTIONS)return 'direct-execution-environment-unsupported';
20
21
  for(const [key,value] of Object.entries(env)){
22
+ // The 5.5.2 API can require this unreviewed module during probe loading.
23
+ // Reject even empty/case aliases; do not mutate the caller's environment.
24
+ if(/^TS_ETW_MODULE_PATH$/i.test(key))return 'direct-execution-environment-unsupported';
21
25
  if(/^NODE_/i.test(key)){
22
26
  // Node 22.13.0 predates env-proxy initialization. This injected flag is
23
27
  // inert there, but remains strictly fingerprinted as NODE_ context.
@@ -1,6 +1,8 @@
1
1
  // Native history is an optional acceleration. Keep its operations off the
2
2
  // control thread so a wedged native call cannot block status or shutdown.
3
3
  import fs from 'node:fs';
4
+ import {mark} from './decision-profile.mjs';
5
+ mark('history.worker_loaded');
4
6
  import watcher from '@parcel/watcher';
5
7
 
6
8
  try {
@@ -10,8 +12,11 @@ try {
10
12
  catch(e){if(e.code!=='ENOENT')throw e;}
11
13
  if(fault==='hang')Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);
12
14
  if(fault==='fail')throw Error('injected history failure');
15
+ mark('history.snapshot_start');
13
16
  await watcher.writeSnapshot(root,next,{backend});
17
+ mark('history.snapshot_complete');
14
18
  const changes=previous?await watcher.getEventsSince(root,previous,{backend}):[];
19
+ mark('history.events_complete',{events:changes.length});
15
20
  process.send({ok:true,changes});
16
21
  } catch(error) {
17
22
  process.send({ok:false,error:error.message,code:error.code||error.name});
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import {mark,traceEnvironment} from './decision-profile.mjs';
2
3
  import path from 'node:path';
3
4
  import {randomUUID} from 'node:crypto';
4
5
  import {fork} from 'node:child_process';
@@ -11,11 +12,12 @@ const workerFile=path.join(path.dirname(fileURLToPath(import.meta.url)),'history
11
12
  export function history(root,cursor,backend,deadlineMs,faultFile) {
12
13
  const next=cursor+'.'+randomUUID()+'.next';
13
14
  return new Promise((resolve,reject)=>{
15
+ mark('history.submit',{deadlineMs});
14
16
  const worker=fork(workerFile,[root,fs.existsSync(cursor)?cursor:'',next,backend,
15
- faultFile||''],{stdio:['ignore','ignore','ignore','ipc']});
17
+ faultFile||''],{stdio:['ignore','ignore','ignore','ipc'],env:traceEnvironment()});
16
18
  let settled=false,abandoned=false;
17
19
  const finish=(error,value)=>{
18
- if(settled)return;settled=true;clearTimeout(timer);
20
+ if(settled)return;settled=true;mark('history.complete',{childPid:worker.pid,outcome:error?.code||error?.name||'ok',events:value?.length});clearTimeout(timer);
19
21
  worker.kill('SIGKILL');
20
22
  if(error){abandoned=true;try{fs.unlinkSync(next);}catch{}
21
23
  reject(error);return;}
@@ -7,8 +7,9 @@ import {InputIndex} from './index.mjs';
7
7
  import {directProject,directQualification} from './direct-typescript.mjs';
8
8
  import {queriesMatch} from './typescript-list.mjs';
9
9
  import {planGuard,inputKey} from './decision-validation.mjs';
10
- import {timing} from './decision-profile.mjs';
10
+ import {timing,traceBase} from './decision-profile.mjs';
11
11
 
12
+ traceBase(workerData.diagnostic);
12
13
  try {
13
14
  const totalStarted=performance.now();
14
15
  const configStarted=performance.now();