@redue/cli 0.0.0-stage → 0.1.0-alpha.10

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +180 -0
  3. package/README.md +162 -2
  4. package/bin/redue.mjs +355 -0
  5. package/bin/vstate.mjs +3 -0
  6. package/docs/README.md +21 -0
  7. package/docs/user/agents.md +207 -0
  8. package/docs/user/configuration.md +60 -0
  9. package/docs/user/direct-typescript.md +96 -0
  10. package/docs/user/getting-started.md +148 -0
  11. package/docs/user/json.md +59 -0
  12. package/docs/user/npm-launcher-notice.md +42 -0
  13. package/docs/user/pilot.md +142 -0
  14. package/docs/user/receipt-selection-notice.md +60 -0
  15. package/docs/user/shell.md +25 -0
  16. package/docs/user/supported-projects.md +90 -0
  17. package/examples/redue.config.json +8 -0
  18. package/package.json +47 -4
  19. package/src/agent-integration.mjs +248 -0
  20. package/src/agent-policy.md +70 -0
  21. package/src/cached-state.mjs +42 -0
  22. package/src/cli.mjs +459 -0
  23. package/src/control-endpoint.mjs +11 -0
  24. package/src/daemon.mjs +1082 -0
  25. package/src/decision-profile.mjs +32 -0
  26. package/src/decision-validation.mjs +41 -0
  27. package/src/declared-plan.mjs +125 -0
  28. package/src/direct-run-certificate.mjs +40 -0
  29. package/src/direct-typescript-probe.mjs +56 -0
  30. package/src/direct-typescript.mjs +76 -0
  31. package/src/executable-lookup.mjs +18 -0
  32. package/src/glob.mjs +9 -0
  33. package/src/history-worker.mjs +23 -0
  34. package/src/index.mjs +306 -0
  35. package/src/installed-inputs.mjs +110 -0
  36. package/src/linux-inotify.py +228 -0
  37. package/src/linux-observer.mjs +79 -0
  38. package/src/node-onboarding.mjs +274 -0
  39. package/src/npm-typecheck-environment.mjs +36 -0
  40. package/src/npm-typecheck-probe.mjs +120 -0
  41. package/src/observation.mjs +39 -0
  42. package/src/owned-state.mjs +32 -0
  43. package/src/path-identity.mjs +43 -0
  44. package/src/plan-worker.mjs +140 -0
  45. package/src/plan.mjs +10 -0
  46. package/src/platform-observation.mjs +17 -0
  47. package/src/platform-state.mjs +14 -0
  48. package/src/pnpm-inputs.mjs +355 -0
  49. package/src/pnpm-typecheck-probe.mjs +47 -0
  50. package/src/presentation.mjs +53 -0
  51. package/src/probe-result.mjs +21 -0
  52. package/src/process-liveness.mjs +11 -0
  53. package/src/project-location.mjs +34 -0
  54. package/src/run-lock.mjs +77 -0
  55. package/src/state-store.mjs +73 -0
  56. package/src/toolchain-probe.mjs +21 -0
  57. package/src/typescript-contract-probe.mjs +5 -0
  58. package/src/typescript-input-facts.mjs +65 -0
  59. package/src/typescript-list.mjs +72 -0
  60. package/src/typescript-qualification.mjs +77 -0
  61. package/src/windows-command.mjs +16 -0
  62. package/src/windows-process.mjs +15 -0
  63. package/src/yarn-workspace-inputs.mjs +148 -0
  64. package/src/yarn-workspace-typecheck-probe.mjs +107 -0
@@ -0,0 +1,32 @@
1
+ // Opt-in, bounded local diagnostics. Never include paths, command arguments,
2
+ // environment values, source, or probe output in a timing record.
3
+ import fs from 'node:fs';
4
+ import {AsyncLocalStorage} from 'node:async_hooks';
5
+ import {randomUUID} from 'node:crypto';
6
+
7
+ const file=process.env.REDUE_DECISION_PROFILE_FILE;
8
+ export const profiling=process.env.REDUE_DECISION_PROFILE==='1'&&!!file;
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
+
22
+ export function timing(phase,started,counts={}) {
23
+ if(!profiling)return;
24
+ const memory=process.memoryUsage();
25
+ try{fs.appendFileSync(file,'REDUE_TIMING '+JSON.stringify({
26
+ at:Date.now(),monoNs:process.hrtime.bigint().toString(),pid:process.pid,phase,...traceContext(),
27
+ ms:Math.round((performance.now()-started)*10)/10,
28
+ rssMiB:Math.round(memory.rss/1048576*10)/10,
29
+ ...counts
30
+ })+'\n');}
31
+ catch{/* Diagnostics must never change probe or verification behavior. */}
32
+ }
@@ -0,0 +1,41 @@
1
+ // Deterministic reuse keys for already-resolved plans. These are not receipts
2
+ // or a second content cache: the existing InputIndex still reads current bytes
3
+ // and membership. A changed key falls back to provider discovery.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import {execFileSync} from 'node:child_process';
7
+ import micromatch from './glob.mjs';
8
+ import {sha} from './plan.mjs';
9
+
10
+ export function inputKey(row) {
11
+ return sha(JSON.stringify([row.plan.id,row.files.size,row.sum.toString('hex')]));
12
+ }
13
+ export function planGuard(bundle) {
14
+ const root=bundle.root;
15
+ const paths=new Set(['package.json','package-lock.json','.npmrc','.yarnrc.yml',
16
+ 'pnpm-workspace.yaml','.pnpmfile.cjs','.pnpmfile.mjs',
17
+ ...bundle.workspaceManifests||[],...bundle.configurationInputs||[],
18
+ ...Object.values(bundle.plans).flatMap(plan=>plan.resolutionTriggers||[])]);
19
+ const patterns=bundle.workspacePatterns?.length?bundle.workspacePatterns:
20
+ (()=>{const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json')));
21
+ return Array.isArray(pkg.workspaces)?pkg.workspaces:pkg.workspaces?.packages||[];})();
22
+ if(patterns.length){
23
+ const files=execFileSync('git',['ls-files','--cached','--others','--exclude-standard',
24
+ '-z','--',':(glob)**/package.json'],{cwd:root,
25
+ env:{...process.env,GIT_OPTIONAL_LOCKS:'0'},timeout:5000,maxBuffer:32*1024*1024})
26
+ .toString().split('\0').filter(Boolean);
27
+ for(const file of files)if(micromatch.isMatch(path.posix.dirname(file),patterns,{dot:true}))
28
+ paths.add(file);
29
+ }
30
+ const values=[...paths].sort().map(rel=>{
31
+ const file=path.isAbsolute(rel)?rel:path.join(root,rel);
32
+ try{const st=fs.lstatSync(file,{bigint:true});
33
+ if(st.isSymbolicLink())return [rel,'link',fs.readlinkSync(file)];
34
+ if(st.isDirectory())return [rel,'directory',String(st.dev),String(st.ino)];
35
+ if(!st.isFile())throw Error('unobservable plan dependency');
36
+ return [rel,sha(fs.readFileSync(file))];
37
+ }catch(e){if(e.code==='ENOENT')return [rel,'missing'];throw e;}
38
+ });
39
+ const rootStat=fs.statSync(root,{bigint:true});
40
+ return sha(JSON.stringify([String(rootStat.dev),String(rootStat.ino),values]));
41
+ }
@@ -0,0 +1,125 @@
1
+ // A single-project plan. Declarations are explicit;
2
+ // this provider does not infer complete coverage from a successful command.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import {createHash} from 'node:crypto';
6
+ import {permittedRoots,resolveDeclaredInstalled} from './installed-inputs.mjs';
7
+ import {qualifyTypeScript} from './typescript-qualification.mjs';
8
+ import {yarnWorkspaceTypecheckInputs} from './yarn-workspace-inputs.mjs';
9
+ import {pnpmTypecheckInputs} from './pnpm-inputs.mjs';
10
+ import {observedRelative,withinPath,realObservedPath} from './path-identity.mjs';
11
+ import {timing} from './decision-profile.mjs';
12
+
13
+ const identity=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
14
+ const safePattern=value=>{
15
+ if(typeof value!=='string'||!value)throw Error('input pattern must be nonempty');
16
+ const raw=value.startsWith('!')?value.slice(1):value;
17
+ if(!raw||path.isAbsolute(raw)||raw.split(/[\\/]/).includes('..'))
18
+ throw Error(`unsafe repository-relative input pattern: ${value}`);
19
+ return value;
20
+ };
21
+
22
+ export function discoverDeclared(config) {
23
+ const root=realObservedPath(config.root),plans={},configurationInputs=new Set(),
24
+ workspaceManifests=new Set(),workspacePatterns=new Set();
25
+ if(!Array.isArray(config.checks)||!config.checks.length)throw Error('selected checks required');
26
+ for(const selected of config.checks){
27
+ if(typeof selected.name!=='string'||!selected.name||plans[selected.name])
28
+ throw Error('check names must be unique nonempty strings');
29
+ if(!Array.isArray(selected.command)||!selected.command.length||
30
+ !selected.command.every(item=>typeof item==='string'&&item))
31
+ throw Error(`exact command required for ${selected.name}`);
32
+ const cwd=path.resolve(root,selected.cwd||'.');
33
+ if(!withinPath(cwd,root))throw Error('check cwd escapes checkout');
34
+ for(const key of ['inputs','generatedInputs','installedInputs']){
35
+ if(!Array.isArray(selected[key]||[]))throw Error(`${key} must be a path-pattern array`);
36
+ for(const value of selected[key]||[])safePattern(value);
37
+ }
38
+ if(!selected.inputs?.some(value=>!value.startsWith('!')))
39
+ throw Error(`positive source inputs required for ${selected.name}`);
40
+ for(const argv of selected.probes||[])if(!Array.isArray(argv)||!argv.length||
41
+ !argv.every(value=>typeof value==='string'&&value))
42
+ throw Error('state probes must be exact nonempty argv arrays');
43
+ if(!Array.isArray(selected.environment?.variables||[])||
44
+ !(selected.environment?.variables||[]).every(value=>typeof value==='string'&&value))
45
+ throw Error('environment variables must be a string array');
46
+ if(!Array.isArray(selected.environment?.prefixes||[])||
47
+ !(selected.environment?.prefixes||[]).every(value=>
48
+ typeof value==='string'&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)))
49
+ throw Error('environment prefixes must be simple nonempty names');
50
+ if(!Array.isArray(selected.environment?.pathExecutables||[])||
51
+ !(selected.environment?.pathExecutables||[]).every(value=>
52
+ typeof value==='string'&&/^[A-Za-z_][A-Za-z0-9_-]*$/.test(value)))
53
+ throw Error('PATH executables must be simple command names');
54
+ const allowedRoots=permittedRoots(root,selected.allowedExternalRoots||[]);
55
+ let workspace=null,workspaceIssue=null;
56
+ const workspaceStarted=performance.now();
57
+ if(selected.qualification==='yarn-workspace-tsc-v1'){
58
+ try{workspace=yarnWorkspaceTypecheckInputs(root,selected.workspace,
59
+ selected.command[4]);}
60
+ catch(e){workspaceIssue=e.message;}
61
+ }
62
+ if(selected.qualification==='pnpm-tsc-v1'){
63
+ try{workspace=pnpmTypecheckInputs(root,selected.workspace||'.',
64
+ selected.script||selected.command.at(-1));}
65
+ catch(e){workspaceIssue=e.message;}
66
+ }
67
+ timing('plan.workspace_inputs',workspaceStarted,{available:workspace?1:0});
68
+ const installedStarted=performance.now();
69
+ const installed=resolveDeclaredInstalled(root,
70
+ [...(selected.installedInputs||[]),...(workspace?.installed||[])],allowedRoots);
71
+ timing('plan.installed_resolution',installedStarted,{mappings:installed.mappings.length});
72
+ const automatic=qualifyTypeScript(root,selected,
73
+ {workspace,issue:workspaceIssue});
74
+ const sourcePatterns=[...new Set([...selected.inputs,...(workspace?.source||[]),
75
+ ...(workspace?.generated||[])])].sort();
76
+ const additivePatterns=[...new Set([...(selected.generatedInputs||[]),
77
+ ...installed.internalPatterns])].sort();
78
+ const unresolved=[...installed.unresolved,...(automatic?.issues||[]),
79
+ ...(workspace?.limitations||[]),
80
+ ...(workspaceIssue?[workspaceIssue]:[])];
81
+ for(const item of workspace?.configurationInputs||[])configurationInputs.add(item);
82
+ for(const item of workspace?.workspacePatterns||[])workspacePatterns.add(item);
83
+ for(const item of workspace?.closure||[])workspaceManifests.add(item.manifest);
84
+ if(!automatic)for(const category of ['source','generated','installedDependencies',
85
+ 'environment','toolchain','runtime'])if(!selected.coverage?.[category])
86
+ unresolved.push(selected.coverageReasons?.[category]||
87
+ `${category} coverage not reviewed for this command`);
88
+ if((selected.probes||[]).length&&!automatic&&!selected.coverage?.probeContinuity)
89
+ unresolved.push('probe state continuity during execution not established');
90
+ const installedPhysicalRoots=[...new Set(installed.physicalRoots
91
+ .filter(file=>withinPath(file,root)&&observedRelative(root,file)!=='')
92
+ .map(file=>observedRelative(root,file)))].sort();
93
+ const externalPatterns=installed.externalPatterns;
94
+ const plan={name:selected.name,provider:'declared-project@1',inputResolutionVersion:1,
95
+ root,cwd,selectedConfigHash:identity(selected),command:selected.command,
96
+ target:selected.name,taskCommand:null,taskGraph:[],
97
+ patterns:[...new Set([...sourcePatterns,...additivePatterns])].sort(),
98
+ sourcePatterns,additivePatterns,generated:[],
99
+ declaredGeneratedInputs:[...new Set([...(selected.generatedInputs||[]),
100
+ ...(workspace?.generated||[])])].sort(),
101
+ declaredInstalledInputs:[...new Set([...(selected.installedInputs||[]),
102
+ ...(workspace?.installed||[])])].sort(),
103
+ installation:workspace?.installation||null,
104
+ ...(workspace?.synchronizedInstalledRead?{synchronizedInstalledRead:true}:{}),
105
+ installedInstances:workspace?.installedInstances||[],
106
+ resolutionCandidates:[],resolutionFindings:workspace?.absences||[],
107
+ installedPhysicalRoots,externalPatterns,
108
+ externalObservationRoots:Object.keys(externalPatterns).sort(),
109
+ resolutionLinks:installed.links,
110
+ resolutionTriggers:[...new Set([...installed.triggers,
111
+ ...(workspace?.linkTriggers||[]),
112
+ ...(workspace?.resolutionCandidates||[]).map(row=>row.path)])],
113
+ installedMappings:installed.mappings,effectiveEnvironmentGlobs:{},
114
+ probes:selected.probes||[],environment:selected.environment||{},
115
+ qualification:selected.qualification||null,
116
+ executionProvenance:'declared-command',
117
+ unresolved:[...new Set(unresolved)].sort()};
118
+ plan.resolutionCandidates=workspace?.resolutionCandidates||[];
119
+ plan.id=identity(plan);plans[selected.name]=plan;
120
+ }
121
+ return {schema:1,provider:'declared-project@1',root,plans,discoveredAt:Date.now(),
122
+ workspaceCount:1,workspacePatterns:[...workspacePatterns],
123
+ configurationInputs:[...configurationInputs].sort(),
124
+ workspaceManifests:[...workspaceManifests].sort()};
125
+ }
@@ -0,0 +1,40 @@
1
+ // A caller validates discovery, never an execution outcome. No persistent cache.
2
+ import {createHash} from 'node:crypto';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {inflateSync} from 'node:zlib';
5
+ import {fileURLToPath} from 'node:url';
6
+ import {timing} from './decision-profile.mjs';
7
+ const hash=value=>createHash('sha256').update(value).digest('hex');
8
+ export function directRunCertificateAllowed(selected) {
9
+ return process.platform==='darwin'&&selected.qualification==='npm-typescript-direct-v1'&&
10
+ selected.probes?.length===1&&
11
+ selected.probes[0][1]===fileURLToPath(new URL('direct-typescript-probe.mjs',import.meta.url))&&
12
+ !(process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_RUN_PROBES==='1');
13
+ }
14
+ export function validateDirectRunCertificate(plan,selected,snapshot) {
15
+ const started=performance.now(),c=snapshot?.discoveryCertificate;
16
+ const fallback=reason=>{timing('cli.run_certificate',started,{outcome:'fallback',reason});return null;};
17
+ if(!directRunCertificateAllowed(selected))return fallback('unsupported');
18
+ if(!snapshot?.observationHealthy||!c||c.schema!==1||c.planId!==plan.id||
19
+ c.planId!==snapshot.planId||c.cwd!==plan.cwd||
20
+ c.probeDefinition!==hash(JSON.stringify(selected.probes))||
21
+ ![c.output,c.context,c.stderrHash].every(s=>typeof s==='string'&&/^[a-f0-9]{64}$/.test(s))||
22
+ typeof c.queryData!=='string'||c.queryData.length>512*1024)
23
+ return fallback('certificate_unavailable');
24
+ try{
25
+ const queries=JSON.parse(inflateSync(Buffer.from(c.queryData,'base64'),{maxOutputLength:8*1024*1024}));
26
+ const argv=selected.probes[0];
27
+ const value=hash(JSON.stringify([[argv,0,hash(c.output),c.stderrHash]]));
28
+ if(value!==snapshot.probeHash)return fallback('probe_identity_changed');
29
+ // The built-in child checks the current implementation, actual caller
30
+ // environment, context, content, membership and previously absent queries.
31
+ // The daemon environment is never substituted for the caller's.
32
+ const out=spawnSync(argv[0],[...argv.slice(1),'--redue-validate-queries'],{
33
+ cwd:plan.cwd,env:process.env,timeout:10000,maxBuffer:1024*1024,
34
+ input:JSON.stringify({schema:1,context:c.context,queries})});
35
+ if(out.error||out.signal||out.status!==0||out.stdout.toString()!==c.context||
36
+ hash(out.stderr)!==c.stderrHash)return fallback('caller_validation_failed');
37
+ timing('cli.run_certificate',started,{outcome:'reused',queries:queries.length});
38
+ return value;
39
+ }catch{return fallback('certificate_invalid');}
40
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import {createHash} from 'node:crypto';
3
+ import {directProject,directInterpretation} from './direct-typescript.mjs';
4
+ import {typeScriptInputFacts} from './typescript-input-facts.mjs';
5
+ import {contextOnly,probeResult} from './probe-result.mjs';
6
+ import {realObservedPath} from './path-identity.mjs';
7
+ import path from 'node:path';
8
+ import {queriesMatch} from './typescript-list.mjs';
9
+ import {timing,mark} from './decision-profile.mjs';
10
+ const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
11
+ try{
12
+ const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
13
+ // Verify all installed implementation bytes before loading compiler API/code,
14
+ // including the optimized context-only path. A version label is insufficient.
15
+ const implementationStarted=performance.now();
16
+ const compiler=directProject(root,script);
17
+ timing('direct_probe.implementation',implementationStarted,{version:compiler.version});
18
+ const context={interpretation:directInterpretation,compiler:compiler.digest,
19
+ entry:compiler.entry,node:[realObservedPath(process.execPath),process.version]};
20
+ const contextHash=hash(context);
21
+ if(process.argv.includes('--redue-validate-queries')){
22
+ // Internal, process-local discovery certificate; never a verification receipt.
23
+ // Verify the current implementation above before queryAnswer can load its API.
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});
34
+ const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
35
+ 'fileExists','directoryExists','realpath','directories','entries']);
36
+ if(certificate.schema!==1||certificate.context!==contextHash||
37
+ !Array.isArray(certificate.queries)||!certificate.queries.length||
38
+ certificate.queries.some(q=>!Array.isArray(q)||q.length<3||q.length>4||
39
+ !kinds.has(q[0])||typeof q[1]!=='string'||!path.isAbsolute(q[1])||
40
+ q[0]==='readDirectory'&&(q[3]?.tsRoot!==compiler.tsRoot||!Array.isArray(q[3]?.args)||
41
+ q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
42
+ q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
43
+ throw Error('certificate unavailable');
44
+ mark('certificate.queries_start',{queries:certificate.queries.length});
45
+ const matched=queriesMatch(certificate.queries);
46
+ mark('certificate.queries_complete',{matched});
47
+ timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
48
+ if(!matched)throw Error('compiler queries changed');
49
+ process.stdout.write(contextHash);
50
+ process.exit(0);
51
+ }
52
+ if(!contextOnly(contextHash)){
53
+ const {queries,...inputs}=typeScriptInputFacts(root,compiler.tsRoot,compiler.entry);
54
+ probeResult(hash({root,script,...inputs,...context}),contextHash,queries);
55
+ }
56
+ }catch(e){console.error(`VSTATE_REASON:${e.code&&/^[a-z-]+$/.test(e.code)?e.code:'typescript-contract-unavailable'}`);process.exitCode=2;}
@@ -0,0 +1,76 @@
1
+ // Versioned, explicit offline compiler recipe. No package manager is executed.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {createHash} from 'node:crypto';
5
+ import {createRequire} from 'node:module';
6
+ import {realObservedPath,withinPath} from './path-identity.mjs';
7
+ export const directInterpretation='npm-direct-typescript@1';
8
+ export const directQualification='npm-typescript-direct-v1';
9
+ const reviewed={
10
+ '5.5.2':{entry:'lib/tsc.js',digest:'953b4844b5f6edc745be844b6c367c227b59029b73c4182f508f90cc47e21816'},
11
+ '5.6.3':{entry:'lib/tsc.js',digest:'e674b5dc4dac50f6e91be30094aeea983a1f340231717e62b0b6199ae3049eb2'},
12
+ '5.9.3':{entry:'lib/_tsc.js',digest:'d1b32e75475710fdc85102b1365254d84ceb192d3639d962c53fe1355ce274ca'},
13
+ };
14
+ const fail=code=>{throw Object.assign(Error(code),{code});};
15
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ export const directEnvironment={variables:['NODE_ENV','NODE_OPTIONS','NODE_PATH','NODE_COMPILE_CACHE','VSCODE_INSPECTOR_OPTIONS'],
17
+ prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_','TS_ETW_'],executableIdentity:true};
18
+ export function directEnvironmentIssue(env=process.env){
19
+ if(![22,24].includes(Number(process.versions.node.split('.')[0])))return 'direct-node-version-unsupported';
20
+ if(env.VSCODE_INSPECTOR_OPTIONS)return 'direct-execution-environment-unsupported';
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';
25
+ if(/^NODE_/i.test(key)){
26
+ // Node 22.13.0 predates env-proxy initialization. This injected flag is
27
+ // inert there, but remains strictly fingerprinted as NODE_ context.
28
+ if(key==='NODE_USE_ENV_PROXY'&&value==='1'&&process.version==='v22.13.0'&&
29
+ !process.allowedNodeEnvironmentFlags.has('--use-env-proxy'))continue;
30
+ if(key==='NODE_ENV'&&['','production','test'].includes(value))continue;
31
+ if(value!=='')return 'direct-execution-environment-unsupported';
32
+ }
33
+ if(/^(DYLD_|LD_|TSGO_|TSC_)/i.test(key)&&value!=='')return 'direct-execution-environment-unsupported';
34
+ }
35
+ return null;
36
+ }
37
+ export function directCompiler(root,{verify=true}={}){
38
+ root=realObservedPath(root);
39
+ const manifest=createRequire(path.join(root,'package.json')).resolve('typescript/package.json');
40
+ const tsRoot=path.dirname(realObservedPath(manifest));
41
+ if(!withinPath(tsRoot,root)||tsRoot!==realObservedPath(path.join(root,'node_modules/typescript')))
42
+ fail('direct-typescript-layout-unsupported');
43
+ const version=JSON.parse(fs.readFileSync(manifest)).version,spec=reviewed[version];
44
+ if(!spec)fail('direct-typescript-version-unsupported');
45
+ if(verify){
46
+ const files=[];
47
+ const walk=(dir,relative='')=>{
48
+ for(const entry of fs.readdirSync(dir,{withFileTypes:true})){
49
+ const name=relative?relative+'/'+entry.name:entry.name,full=path.join(dir,entry.name);
50
+ if(entry.isDirectory())walk(full,name);
51
+ else if(entry.isFile())files.push([name,full]);
52
+ else fail('direct-typescript-implementation-unreviewed');
53
+ }
54
+ };walk(tsRoot);
55
+ const digest=createHash('sha256');
56
+ for(const [name,file] of files.sort(([a],[b])=>a<b?-1:a>b?1:0))digest.update(name+'\0'+hash(fs.readFileSync(file))+'\0');
57
+ if(digest.digest('hex')!==spec.digest)fail('direct-typescript-implementation-unreviewed');
58
+ }
59
+ return {tsRoot,version,entry:path.join(tsRoot,spec.entry),digest:spec.digest};
60
+ }
61
+ export function directProject(root,script,{verify=true}={}){
62
+ const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json')));
63
+ if(pkg.workspaces||pkg.packageManager&&!/^npm@/.test(pkg.packageManager)||
64
+ !['package-lock.json','npm-shrinkwrap.json'].some(file=>fs.existsSync(path.join(root,file)))||
65
+ ['yarn.lock','pnpm-lock.yaml','bun.lock','bun.lockb'].some(file=>fs.existsSync(path.join(root,file))))
66
+ fail('direct-npm-project-unsupported');
67
+ if(pkg.scripts?.[script]?.trim()!=='tsc --noEmit'||pkg.scripts?.['pre'+script]||pkg.scripts?.['post'+script])
68
+ fail('typecheck-script-changed');
69
+ const issue=directEnvironmentIssue();if(issue)fail(issue);
70
+ return directCompiler(root,{verify});
71
+ }
72
+ // Provenance only: retain a digest, never proxy URLs/credentials or a raw dump.
73
+ // No npm process runs in this recipe; these keys are not compiler applicability.
74
+ export function observedNpmContext(env=process.env){
75
+ return hash(JSON.stringify(Object.entries(env).filter(([key])=>/^npm_/i.test(key)||/^(?:https?_proxy|all_proxy|no_proxy)$/i.test(key)).sort(([a],[b])=>a<b?-1:a>b?1:0)));
76
+ }
@@ -0,0 +1,18 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // Resolve the same project-local PATH that will be used for execution. Windows
5
+ // command shims have no extension in package scripts but normally end in .cmd.
6
+ export function findExecutable(name,{platform=process.platform,env=process.env,
7
+ cwd=process.cwd(),paths=path,access=fs.accessSync,realpath=fs.realpathSync}={}) {
8
+ if(!name||paths.basename(name)!==name)return null;
9
+ // An extensionless POSIX shell shim can coexist with npm.cmd on Windows.
10
+ // CreateProcess cannot run the shell shim, so prefer native PATHEXT files.
11
+ const suffixes=platform==='win32'&&!paths.extname(name)?
12
+ [...(env.PATHEXT||'.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean),'']:[''];
13
+ for(const folder of (env.PATH||'').split(paths.delimiter))for(const suffix of suffixes) {
14
+ const candidate=paths.join(folder||cwd,name+suffix);
15
+ try{access(candidate,fs.constants.X_OK);return realpath(candidate);}catch{}
16
+ }
17
+ return null;
18
+ }
package/src/glob.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import picomatch from 'picomatch';
2
+
3
+ // These are the exact delegates used by micromatch 4.0.8 matcher/isMatch,
4
+ // with the same pinned picomatch 2.3.2. REDUE does not use brace expansion;
5
+ // importing the smaller dependency avoids shipping its unused vulnerable parser.
6
+ export default {
7
+ matcher(pattern,options){return picomatch(pattern,options);},
8
+ isMatch(file,patterns,options){return picomatch(patterns,options)(file);}
9
+ };
@@ -0,0 +1,23 @@
1
+ // Native history is an optional acceleration. Keep its operations off the
2
+ // control thread so a wedged native call cannot block status or shutdown.
3
+ import fs from 'node:fs';
4
+ import {mark} from './decision-profile.mjs';
5
+ mark('history.worker_loaded');
6
+ import watcher from '@parcel/watcher';
7
+
8
+ try {
9
+ const [root,previous,next,backend,faultFile]=process.argv.slice(2);
10
+ let fault;
11
+ try {fault=faultFile&&fs.readFileSync(faultFile,'utf8').trim();}
12
+ catch(e){if(e.code!=='ENOENT')throw e;}
13
+ if(fault==='hang')Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);
14
+ if(fault==='fail')throw Error('injected history failure');
15
+ mark('history.snapshot_start');
16
+ await watcher.writeSnapshot(root,next,{backend});
17
+ mark('history.snapshot_complete');
18
+ const changes=previous?await watcher.getEventsSince(root,previous,{backend}):[];
19
+ mark('history.events_complete',{events:changes.length});
20
+ process.send({ok:true,changes});
21
+ } catch(error) {
22
+ process.send({ok:false,error:error.message,code:error.code||error.name});
23
+ }