@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 CHANGED
@@ -1,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0-alpha.5 — unreleased candidate
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redue/cli",
3
3
  "private": false,
4
- "version": "0.1.0-alpha.5",
4
+ "version": "0.1.0-alpha.6",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
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 cacheable=process.platform==='win32'&&row.plan.probes.length===1&&expected&&
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
- if(cacheable&&allowReuse&&row.probeCache?.input===key){
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,stdio:['ignore','pipe','pipe']});
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()===row.probeCache.context){
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
- timing('daemon.probe_reason',started,{reason:!cacheable?'generic_probe':
138
- !row.probeCache?'no_validated_result':row.probeCache.input!==key?'inputs_changed':'context_changed'});
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
- phase:metrics.planPhase||'initializing',pending:pending.size,
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
- for(const row of index.checks.values())probe(row);
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();for(const row of index.checks.values())probe(row);publish();}
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);
@@ -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,...options.args).sort();
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
- const full=path.resolve(dir),options={tsRoot,args};
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
- // The read-only runtime probe separately checks the effective TypeScript file
8
- // list, config graph, npm toolchain and caller context on every sync/run.
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=[];