@redue/cli 0.1.0-alpha.5 → 0.1.0-alpha.6
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 +13 -1
- package/package.json +1 -1
- package/src/daemon.mjs +54 -13
- package/src/direct-typescript-probe.mjs +27 -0
- package/src/typescript-list.mjs +6 -2
- package/src/typescript-qualification.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.0-alpha.
|
|
3
|
+
## 0.1.0-alpha.6 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
- Reuse guarded compiler discovery for warm direct-TypeScript decisions on macOS
|
|
6
|
+
with healthy historical observation. Membership, absence, compiler bytes,
|
|
7
|
+
caller context and receipt selection remain validated independently.
|
|
8
|
+
- Keep full discovery/reconciliation fallbacks and existing platform boundaries.
|
|
9
|
+
- On one measured project, complete warm decision median fell from roughly
|
|
10
|
+
791 to 474 ms. This is not a universal speedup, agent-task acceleration,
|
|
11
|
+
memory improvement, or a faster relevant-edit/rebuild claim.
|
|
12
|
+
- Stop observers before upgrading and restart with the same config/state.
|
|
13
|
+
Preserve receipts; no configuration migration is performed.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-alpha.5 — 2026-10-07
|
|
4
16
|
|
|
5
17
|
- Add opt-in project-local agent activation and direct TypeScript evidence, with
|
|
6
18
|
fresh-session reuse demonstrated in the tested Codex/macOS configuration.
|
package/package.json
CHANGED
package/src/daemon.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {permittedRoots} from './installed-inputs.mjs';
|
|
|
16
16
|
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
|
-
import {inputKey} from './decision-validation.mjs';
|
|
19
|
+
import {inputKey,planGuard} from './decision-validation.mjs';
|
|
20
20
|
import {timing} from './decision-profile.mjs';
|
|
21
21
|
|
|
22
22
|
const configFile=path.resolve(process.argv[2]);
|
|
@@ -110,6 +110,11 @@ function persistedPlan(bundle) {
|
|
|
110
110
|
return [name,safe];
|
|
111
111
|
}))};
|
|
112
112
|
}
|
|
113
|
+
function macProbeBarrier() {
|
|
114
|
+
return process.platform==='darwin'&&hasHistory&&!historyDisabled&&healthy&&
|
|
115
|
+
watcher&&!planning&&!recovering&&!planDirty&&!index?.unavailable&&
|
|
116
|
+
!pending.size&&!pendingChecks.size&&!external.size&&verifyRoot();
|
|
117
|
+
}
|
|
113
118
|
function probe(row,allowReuse=false) {
|
|
114
119
|
const started=performance.now();
|
|
115
120
|
const supported=new Map([
|
|
@@ -118,24 +123,45 @@ function probe(row,allowReuse=false) {
|
|
|
118
123
|
['yarn-workspace-tsc-v1','yarn-workspace-typecheck-probe.mjs'],
|
|
119
124
|
['pnpm-tsc-v1','pnpm-typecheck-probe.mjs']]);
|
|
120
125
|
const expected=supported.get(row.plan.qualification);
|
|
121
|
-
const
|
|
126
|
+
const macDirect=process.platform==='darwin'&&row.plan.qualification==='npm-typescript-direct-v1';
|
|
127
|
+
const cacheable=(process.platform==='win32'||macDirect)&&row.plan.probes.length===1&&expected&&
|
|
122
128
|
row.plan.probes[0][1]===fileURLToPath(new URL(expected,import.meta.url));
|
|
123
129
|
const key=inputKey(row);
|
|
124
|
-
|
|
130
|
+
let guard=null,guardReason=null;
|
|
131
|
+
if(macDirect&&cacheable)try{guard=planGuard(index.bundle);}catch{guardReason='plan_guard_unavailable';}
|
|
132
|
+
const cache=row.probeCache;
|
|
133
|
+
const macGuarded=!macDirect||(macProbeBarrier()&&!row.plan.unresolved.length&&
|
|
134
|
+
guard!==null&&cache?.schema===1&&cache.guard===guard&&
|
|
135
|
+
Array.isArray(cache.queries)&&cache.queries.length>0);
|
|
136
|
+
const forceFull=process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_PROBES==='1';
|
|
137
|
+
if(cacheable&&allowReuse&&macGuarded&&!forceFull&&cache?.input===key){
|
|
125
138
|
const argv=row.plan.probes[0];
|
|
126
|
-
const out=spawnSync(argv[0],[...argv.slice(1),'--redue-context'],{
|
|
127
|
-
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
|
|
139
|
+
const out=spawnSync(argv[0],[...argv.slice(1),macDirect?'--redue-validate-queries':'--redue-context'],{
|
|
140
|
+
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
|
|
141
|
+
...(macDirect?{input:JSON.stringify({schema:cache.schema,context:cache.context,queries:cache.queries})}:
|
|
142
|
+
{stdio:['ignore','pipe','pipe']})});
|
|
143
|
+
metrics.probeContextProcesses=(metrics.probeContextProcesses||0)+1;
|
|
144
|
+
let stillValid=!macDirect;
|
|
145
|
+
if(macDirect)try{stillValid=macProbeBarrier()&&inputKey(row)===key&&planGuard(index.bundle)===guard;}catch{}
|
|
128
146
|
if(!out.error&&!out.signal&&out.status===0&&
|
|
129
|
-
out.stdout.toString()===
|
|
147
|
+
out.stdout.toString()===cache.context&&stillValid){
|
|
130
148
|
row.probeHash=row.probeCache.hash;row.probeAt=Date.now();row.probeError=null;
|
|
131
149
|
metrics.probeReuses=(metrics.probeReuses||0)+1;
|
|
132
150
|
timing('daemon.probe_context_validation',started,{outcome:'reused',reason:'content_and_context_unchanged'});
|
|
133
151
|
return;
|
|
134
152
|
}
|
|
153
|
+
guardReason='context_or_compiler_queries_changed';
|
|
135
154
|
timing('daemon.probe_context_validation',started,{outcome:'changed_or_unavailable'});
|
|
136
155
|
}
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
if(macDirect&&cache?.guard&&guard!==cache.guard)planDirty=true;
|
|
157
|
+
const fullReason=forceFull?'forced_full_reference':guardReason||
|
|
158
|
+
(macDirect&&!macGuarded?'mac_validation_basis_unavailable':!cacheable?'generic_probe':
|
|
159
|
+
!row.probeCache?'no_validated_result':row.probeCache.input!==key?'inputs_changed':'context_changed');
|
|
160
|
+
timing('daemon.probe_reason',started,{reason:fullReason});
|
|
161
|
+
metrics.fullProbeReasons??={};metrics.fullProbeReasons[fullReason]=(metrics.fullProbeReasons[fullReason]||0)+1;
|
|
162
|
+
metrics.fullProbes=(metrics.fullProbes||0)+1;
|
|
163
|
+
// A failed or incomplete full probe must not leave an older certificate usable.
|
|
164
|
+
if(macDirect)row.probeCache=null;
|
|
139
165
|
const values=[];let checkpoint=null;
|
|
140
166
|
for(const argv of row.plan.probes) {
|
|
141
167
|
// Supported TypeScript probes perform bounded input/toolchain hashing.
|
|
@@ -207,7 +233,7 @@ function probe(row,allowReuse=false) {
|
|
|
207
233
|
row.revision++;row.lastReason='declared state probe changed or recovered';}
|
|
208
234
|
row.probeHash=value;row.probeAt=Date.now();row.probeError=null;
|
|
209
235
|
if(checkpoint)row.probeCache=checkpoint.queries?{input:key,context:checkpoint.context,
|
|
210
|
-
hash:value,queries:checkpoint.queries}:null;
|
|
236
|
+
hash:value,queries:checkpoint.queries,...(macDirect?{schema:1,guard}: {})}:null;
|
|
211
237
|
timing('daemon.probe',started,{outcome:'ok',commands:values.length,
|
|
212
238
|
reusable:row.probeCache?1:0,queries:row.probeCache?.queries.length||0});
|
|
213
239
|
}
|
|
@@ -820,7 +846,7 @@ async function request(message) {
|
|
|
820
846
|
timing('daemon.decision_reason',totalStarted,{decision,action:message.action,
|
|
821
847
|
plan:process.platform==='win32'?'validate_dependencies_before_reuse':'existing_plan',
|
|
822
848
|
probe:'reuse_only_with_identical_content_and_context',installed:'current_content_required',
|
|
823
|
-
|
|
849
|
+
planPhase:metrics.planPhase||'initializing',pending:pending.size,
|
|
824
850
|
notifications:metrics.notifications,planRebuilds:metrics.planRebuilds});
|
|
825
851
|
if(process.platform==='win32'&&process.env.VSTATE_TEST_FAULTS==='1'&&
|
|
826
852
|
process.env.VSTATE_TEST_WINDOWS_GAP_FILE&&
|
|
@@ -872,8 +898,18 @@ async function request(message) {
|
|
|
872
898
|
}catch(e){installedRecheck=null;observationGap('installed_recheck',e);}
|
|
873
899
|
}
|
|
874
900
|
const probeStarted=performance.now();
|
|
875
|
-
if(index&&healthy&&!validatedColdIndex&&message.action!=='prelaunch')
|
|
876
|
-
|
|
901
|
+
if(index&&healthy&&!validatedColdIndex&&message.action!=='prelaunch'){
|
|
902
|
+
const macReuse=macProbeBarrier();
|
|
903
|
+
for(const row of index.checks.values())probe(row,macReuse);
|
|
904
|
+
if(macReuse&&process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_MAC_PROBE_SIGNAL){
|
|
905
|
+
fs.writeFileSync(process.env.VSTATE_TEST_MAC_PROBE_SIGNAL,'validated');
|
|
906
|
+
await new Promise(resolve=>setTimeout(resolve,200));
|
|
907
|
+
}
|
|
908
|
+
// Child validation blocks JS event delivery. Finish with another continuity
|
|
909
|
+
// barrier so queued edits/gaps cannot be published using the pre-probe index.
|
|
910
|
+
if(macReuse)try{await catchUp();if(planDirty||pending.size||flushTimer)flush();}
|
|
911
|
+
catch(e){observationGap('post_probe_history',e);}
|
|
912
|
+
}
|
|
877
913
|
timing('daemon.request_probes',probeStarted,{checks:index?.checks.size||0});
|
|
878
914
|
lastObservation=Date.now();publish();
|
|
879
915
|
const applicabilityStarted=performance.now();
|
|
@@ -1002,7 +1038,12 @@ const probeTimer=setInterval(async()=>{
|
|
|
1002
1038
|
// but can block control requests behind several seconds of child work.
|
|
1003
1039
|
if(process.platform==='win32')return;
|
|
1004
1040
|
if(healthy&&index&&!planning&&!decisionRequests){
|
|
1005
|
-
try{await catchUp();
|
|
1041
|
+
try{await catchUp();
|
|
1042
|
+
if(healthy&&!planning&&!recovering){const macReuse=macProbeBarrier();
|
|
1043
|
+
for(const row of index.checks.values())probe(row,macReuse);
|
|
1044
|
+
if(macReuse){await catchUp();if(planDirty||pending.size||flushTimer)flush();}
|
|
1045
|
+
}
|
|
1046
|
+
publish();}
|
|
1006
1047
|
catch(e){observationGap('periodic_query',e);}
|
|
1007
1048
|
}},5000);
|
|
1008
1049
|
process.on('uncaughtExceptionMonitor',error=>{
|
|
@@ -4,15 +4,42 @@ 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
|
+
import path from 'node:path';
|
|
9
|
+
import {queriesMatch} from './typescript-list.mjs';
|
|
10
|
+
import {timing} from './decision-profile.mjs';
|
|
7
11
|
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
8
12
|
try{
|
|
9
13
|
const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
|
|
10
14
|
// Verify all installed implementation bytes before loading compiler API/code,
|
|
11
15
|
// including the optimized context-only path. A version label is insufficient.
|
|
16
|
+
const implementationStarted=performance.now();
|
|
12
17
|
const compiler=directProject(root,script);
|
|
18
|
+
timing('direct_probe.implementation',implementationStarted,{version:compiler.version});
|
|
13
19
|
const context={interpretation:directInterpretation,compiler:compiler.digest,
|
|
14
20
|
entry:compiler.entry,node:[realObservedPath(process.execPath),process.version]};
|
|
15
21
|
const contextHash=hash(context);
|
|
22
|
+
if(process.argv.includes('--redue-validate-queries')){
|
|
23
|
+
// Internal, process-local discovery certificate; never a verification receipt.
|
|
24
|
+
// 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');
|
|
27
|
+
const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
|
|
28
|
+
'fileExists','directoryExists','realpath','directories','entries']);
|
|
29
|
+
if(certificate.schema!==1||certificate.context!==contextHash||
|
|
30
|
+
!Array.isArray(certificate.queries)||!certificate.queries.length||
|
|
31
|
+
certificate.queries.some(q=>!Array.isArray(q)||q.length<3||q.length>4||
|
|
32
|
+
!kinds.has(q[0])||typeof q[1]!=='string'||!path.isAbsolute(q[1])||
|
|
33
|
+
q[0]==='readDirectory'&&(q[3]?.tsRoot!==compiler.tsRoot||!Array.isArray(q[3]?.args)||
|
|
34
|
+
q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
|
|
35
|
+
q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
|
|
36
|
+
throw Error('certificate unavailable');
|
|
37
|
+
const matched=queriesMatch(certificate.queries);
|
|
38
|
+
timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
|
|
39
|
+
if(!matched)throw Error('compiler queries changed');
|
|
40
|
+
process.stdout.write(contextHash);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
16
43
|
if(!contextOnly(contextHash)){
|
|
17
44
|
const {queries,...inputs}=typeScriptInputFacts(root,compiler.tsRoot,compiler.entry);
|
|
18
45
|
probeResult(hash({root,script,...inputs,...context}),contextHash,queries);
|
package/src/typescript-list.mjs
CHANGED
|
@@ -9,7 +9,8 @@ const require=createRequire(import.meta.url);
|
|
|
9
9
|
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
10
10
|
export function queryAnswer(kind,file,options) {
|
|
11
11
|
try{
|
|
12
|
-
if(kind==='readDirectory')return require(options.tsRoot).sys.readDirectory(file
|
|
12
|
+
if(kind==='readDirectory')return require(options.tsRoot).sys.readDirectory(file,
|
|
13
|
+
...options.args.map((value,i)=>options.undefinedArguments?.includes(i)?undefined:value)).sort();
|
|
13
14
|
if(kind==='readFile')return hash(fs.readFileSync(file,'utf8'));
|
|
14
15
|
if(kind==='fileExists')return fs.statSync(file).isFile();
|
|
15
16
|
if(kind==='directoryExists')return fs.statSync(file).isDirectory();
|
|
@@ -42,7 +43,10 @@ export function compilerFiles(tsRoot,cwd) {
|
|
|
42
43
|
const getDirectories=file=>{remember('directories',file);return ts.sys.getDirectories(file);};
|
|
43
44
|
const readDirectory=(dir,...args)=>{
|
|
44
45
|
const result=ts.sys.readDirectory(dir,...args);
|
|
45
|
-
|
|
46
|
+
// JSON turns undefined array entries into null. TypeScript distinguishes
|
|
47
|
+
// them (an omitted exclude list is not a null list), so retain that fact.
|
|
48
|
+
const full=path.resolve(dir),options={tsRoot,args,argsVersion:1,
|
|
49
|
+
undefinedArguments:args.flatMap((value,i)=>value===undefined?[i]:[])};
|
|
46
50
|
queries.set('readDirectory\0'+full+JSON.stringify(args),
|
|
47
51
|
['readDirectory',full,[...result].sort(),options]);
|
|
48
52
|
return result;
|
|
@@ -4,8 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import {realObservedPath} from './path-identity.mjs';
|
|
5
5
|
|
|
6
6
|
// This is a machine-enforced contract, not a claim inferred from a script name.
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// Runtime validation checks effective compiler membership, configuration and
|
|
8
|
+
// toolchain identity. Supported discovery certificates still validate those
|
|
9
|
+
// filesystem queries; caller context is independently compared at the decision.
|
|
9
10
|
export function qualifyTypeScript(root,selected,discovered={}) {
|
|
10
11
|
if(selected.qualification===directQualification){
|
|
11
12
|
const issues=[];
|