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

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 (66) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +180 -0
  3. package/README.md +162 -2
  4. package/bin/redue.mjs +371 -0
  5. package/bin/vstate.mjs +3 -0
  6. package/docs/README.md +21 -0
  7. package/docs/user/agents.md +253 -0
  8. package/docs/user/configuration.md +60 -0
  9. package/docs/user/direct-typescript.md +114 -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 +145 -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 +294 -0
  20. package/src/agent-policy.md +70 -0
  21. package/src/cached-state.mjs +42 -0
  22. package/src/claude-hook.mjs +115 -0
  23. package/src/cli.mjs +499 -0
  24. package/src/control-endpoint.mjs +11 -0
  25. package/src/daemon.mjs +1236 -0
  26. package/src/decision-profile.mjs +32 -0
  27. package/src/decision-validation.mjs +51 -0
  28. package/src/declared-plan.mjs +125 -0
  29. package/src/direct-run-certificate.mjs +185 -0
  30. package/src/direct-typescript-probe.mjs +22 -0
  31. package/src/direct-typescript.mjs +109 -0
  32. package/src/executable-lookup.mjs +18 -0
  33. package/src/glob.mjs +9 -0
  34. package/src/history-worker.mjs +26 -0
  35. package/src/index.mjs +306 -0
  36. package/src/installed-inputs.mjs +110 -0
  37. package/src/linux-inotify.py +228 -0
  38. package/src/linux-observer.mjs +79 -0
  39. package/src/node-onboarding.mjs +274 -0
  40. package/src/npm-typecheck-environment.mjs +36 -0
  41. package/src/npm-typecheck-probe.mjs +120 -0
  42. package/src/observation.mjs +59 -0
  43. package/src/owned-state.mjs +32 -0
  44. package/src/path-identity.mjs +43 -0
  45. package/src/plan-worker.mjs +140 -0
  46. package/src/plan.mjs +10 -0
  47. package/src/platform-observation.mjs +17 -0
  48. package/src/platform-state.mjs +14 -0
  49. package/src/pnpm-inputs.mjs +355 -0
  50. package/src/pnpm-typecheck-probe.mjs +47 -0
  51. package/src/presentation.mjs +53 -0
  52. package/src/probe-result.mjs +25 -0
  53. package/src/process-liveness.mjs +11 -0
  54. package/src/project-location.mjs +34 -0
  55. package/src/run-lock.mjs +77 -0
  56. package/src/run-recorder.cjs +78 -0
  57. package/src/state-store.mjs +73 -0
  58. package/src/toolchain-probe.mjs +21 -0
  59. package/src/typescript-contract-probe.mjs +5 -0
  60. package/src/typescript-input-facts.mjs +68 -0
  61. package/src/typescript-list.mjs +141 -0
  62. package/src/typescript-qualification.mjs +77 -0
  63. package/src/windows-command.mjs +16 -0
  64. package/src/windows-process.mjs +15 -0
  65. package/src/yarn-workspace-inputs.mjs +148 -0
  66. package/src/yarn-workspace-typecheck-probe.mjs +107 -0
@@ -0,0 +1,15 @@
1
+ import {spawn} from 'node:child_process';
2
+
3
+ // taskkill /T is the native narrow process-tree operation available without
4
+ // elevation. It cannot own deliberately detached descendants.
5
+ export function terminateWindowsTree(pid,{start=spawn,timeoutMs=5000}={}) {
6
+ if(!Number.isSafeInteger(pid)||pid<1)return Promise.reject(Error('invalid check PID'));
7
+ return new Promise((resolve,reject)=>{
8
+ const child=start('taskkill.exe',['/PID',String(pid),'/T','/F'],
9
+ {stdio:'ignore',windowsHide:true});
10
+ const timer=setTimeout(()=>{child.kill();reject(Error('taskkill deadline exceeded'));},timeoutMs);
11
+ child.once('error',error=>{clearTimeout(timer);reject(error);});
12
+ child.once('close',code=>{clearTimeout(timer);code===0?resolve():
13
+ reject(Error(`taskkill exited ${code}`));});
14
+ });
15
+ }
@@ -0,0 +1,148 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {execFileSync} from 'node:child_process';
4
+ import {createRequire} from 'node:module';
5
+ import micromatch from './glob.mjs';
6
+ import {withinPath as within,realObservedPath} from './path-identity.mjs';
7
+
8
+ export function yarnLocalTsc(root){
9
+ const expected=path.join(root,'node_modules','typescript','bin','tsc');
10
+ if(process.platform==='win32'){
11
+ const shim=path.join(root,'node_modules','.bin','tsc.cmd');
12
+ const body=fs.readFileSync(shim,'utf8').replace(/\r\n/g,'\n').trim();
13
+ const standard=`@IF EXIST "%~dp0\\node.exe" (
14
+ "%~dp0\\node.exe" "%~dp0\\..\\typescript\\bin\\tsc" %*
15
+ ) ELSE (
16
+ @SETLOCAL
17
+ @SET PATHEXT=%PATHEXT:;.JS;=;%
18
+ node "%~dp0\\..\\typescript\\bin\\tsc" %*
19
+ )`;
20
+ if(body!==standard)throw Error('local TypeScript executable is not the installed compiler');
21
+ return realObservedPath(expected);
22
+ }
23
+ const tsc=realObservedPath(path.join(root,'node_modules','.bin','tsc'));
24
+ if(tsc!==expected)throw Error('local TypeScript executable is not the installed compiler');
25
+ return tsc;
26
+ }
27
+
28
+ const posix=file=>file.split(path.sep).join('/');
29
+ const read=file=>JSON.parse(fs.readFileSync(file,'utf8'));
30
+ const relative=(root,file)=>posix(path.relative(root,file));
31
+ const safeDirectory=(root,dir)=>{
32
+ const full=path.resolve(root,dir);
33
+ if(!within(full,root))throw Error('workspace path escapes checkout');
34
+ return full;
35
+ };
36
+
37
+ export function yarnWorkspacePackages(root){
38
+ root=realObservedPath(root);
39
+ const pkg=read(path.join(root,'package.json'));
40
+ const patterns=Array.isArray(pkg.workspaces)?pkg.workspaces:pkg.workspaces?.packages;
41
+ if(!Array.isArray(patterns)||!patterns.length||patterns.some(value=>
42
+ typeof value!=='string'||path.isAbsolute(value)||value.split('/').includes('..')))
43
+ throw Error('workspace declarations are unsupported');
44
+ const files=execFileSync('git',['ls-files','--cached','--others','--exclude-standard',
45
+ '-z','--',':(glob)**/package.json'],{cwd:root,env:{...process.env,GIT_OPTIONAL_LOCKS:'0'},
46
+ maxBuffer:32*1024*1024,timeout:5000}).toString().split('\0').filter(Boolean);
47
+ const found=new Map();
48
+ for(const file of files){
49
+ const dir=path.posix.dirname(file);
50
+ if(dir==='.'||dir.split('/').includes('node_modules')||
51
+ !micromatch.isMatch(dir,patterns,{dot:true}))continue;
52
+ const member=read(path.join(root,file));
53
+ if(!member.name||found.has(member.name))throw Error('workspace package name missing or duplicated');
54
+ found.set(member.name,{name:member.name,dir,manifest:file,pkg:member});
55
+ }
56
+ if(found.size>50)throw Error('workspace count exceeds narrow Node onboarding contract');
57
+ return found;
58
+ }
59
+
60
+ function parsedTypeScript(root,dir){
61
+ const require=createRequire(path.join(root,'package.json'));
62
+ const ts=require(path.join(root,'node_modules','typescript'));
63
+ const config=path.join(root,dir,'tsconfig.json'),reads=new Set();
64
+ const host={...ts.sys,readFile(file){reads.add(path.resolve(file));return ts.sys.readFile(file);},
65
+ onUnRecoverableConfigFileDiagnostic(){}};
66
+ const parsed=ts.getParsedCommandLineOfConfigFile(config,{},host);
67
+ if(!parsed||parsed.errors?.length)throw Error('TypeScript configuration unavailable');
68
+ const raw=ts.readConfigFile(config,ts.sys.readFile);
69
+ if(raw.error||!Array.isArray(raw.config?.include)||raw.config.include.length!==1||
70
+ !['src','src/**/*','src/**'].includes(raw.config.include[0]))
71
+ throw Error('workspace TypeScript file membership is outside the supported src include');
72
+ if(parsed.options.plugins?.length||parsed.options.incremental||parsed.options.composite)
73
+ throw Error('TypeScript plugins or incremental/project builds are outside this contract');
74
+ if(parsed.options.paths||parsed.options.baseUrl||parsed.options.rootDirs?.length||
75
+ parsed.options.typeRoots?.length||parsed.options.customConditions?.length||
76
+ parsed.options.moduleSuffixes?.length||
77
+ parsed.options.moduleResolution!==undefined&&
78
+ parsed.options.moduleResolution!==ts.ModuleResolutionKind.Node10)
79
+ throw Error('custom TypeScript module resolution needs explicit input coverage');
80
+ if(parsed.projectReferences?.length)throw Error('TypeScript project references need a separate contract');
81
+ for(const file of reads)if(!within(realObservedPath(file),root))
82
+ throw Error(`TypeScript configuration escapes the checkout: ${path.basename(file)}`);
83
+ return {parsed,configs:[...reads].map(file=>relative(root,file)).sort()};
84
+ }
85
+
86
+ export function yarnWorkspaceTypecheckInputs(root,workspace,script){
87
+ root=realObservedPath(root);
88
+ const rootPkg=read(path.join(root,'package.json'));
89
+ if(!/^yarn@4\./.test(rootPkg.packageManager||''))throw Error('pinned Yarn 4 required');
90
+ const yarnrc=fs.readFileSync(path.join(root,'.yarnrc.yml'),'utf8');
91
+ const linker=yarnrc.match(/^\s*nodeLinker\s*:\s*['"]?([\w-]+)/m)?.[1];
92
+ const mode=yarnrc.match(/^\s*nmMode\s*:\s*['"]?([\w-]+)/m)?.[1]||'classic';
93
+ if(linker!=='node-modules'||mode!=='classic')
94
+ throw Error('Yarn node-modules classic installation required');
95
+ if(/^\s*(?:yarnPath|plugins)\s*:/m.test(yarnrc))
96
+ throw Error('custom Yarn executable or plugins need explicit toolchain coverage');
97
+ const members=yarnWorkspacePackages(root),selected=members.get(workspace);
98
+ if(!selected)throw Error(`workspace ${workspace} is not declared`);
99
+ if(selected.pkg.scripts?.[script]?.trim()!=='tsc -p .'||
100
+ selected.pkg.scripts?.['pre'+script]||selected.pkg.scripts?.['post'+script])
101
+ throw Error('workspace typecheck must be standalone tsc -p .');
102
+ const packageDir=safeDirectory(root,selected.dir);
103
+ if(fs.existsSync(path.join(packageDir,'node_modules','.bin','tsc'))||
104
+ fs.existsSync(path.join(packageDir,'node_modules','.bin','tsc.cmd')))
105
+ throw Error('workspace-local TypeScript executable needs separate toolchain coverage');
106
+ const {parsed,configs}=parsedTypeScript(root,selected.dir);
107
+ const outDir=parsed.options.outDir&&within(path.resolve(parsed.options.outDir),root)?
108
+ relative(root,path.resolve(parsed.options.outDir)):null;
109
+ if(!parsed.options.noEmit&&!outDir)
110
+ throw Error('emitting TypeScript check needs an in-checkout outDir');
111
+ if(outDir&&within(path.join(root,outDir),path.join(packageDir,'src')))
112
+ throw Error('TypeScript output overlaps observed source inputs');
113
+ const closure=new Map(),visit=member=>{
114
+ for(const name of Object.keys({...member.pkg.dependencies,...member.pkg.devDependencies,
115
+ ...member.pkg.peerDependencies,...member.pkg.optionalDependencies})){
116
+ const dep=members.get(name);if(!dep||dep.name===selected.name||closure.has(name))continue;
117
+ closure.set(name,dep);visit(dep);
118
+ }
119
+ };
120
+ visit(selected);
121
+ const source=[...new Set(['package.json','yarn.lock','.yarnrc.yml',
122
+ selected.manifest,...configs,`${selected.dir}/src/**`])];
123
+ const installed=['node_modules/**',`${selected.dir}/node_modules/**`];
124
+ const generated=[];
125
+ const linkTriggers=[];
126
+ for(const member of members.values()){
127
+ const logical=`node_modules/${member.name}`;
128
+ installed.push(`!${logical}`,`!${logical}/**`);
129
+ if(!closure.has(member.name))continue;
130
+ installed.push(`${member.dir}/node_modules/**`);
131
+ const declaration=member.pkg.types||member.pkg.typings;
132
+ if(!declaration||path.isAbsolute(declaration)||declaration.split('/').includes('..'))
133
+ throw Error(`${member.name} has no supported declaration-output location`);
134
+ const output=path.posix.dirname(declaration);
135
+ const outputDir=output==='.'?member.dir:`${member.dir}/${output}`;
136
+ generated.push(`${outputDir}/**`);
137
+ source.push(member.manifest);
138
+ // The logical link is part of resolution identity even though its target
139
+ // contents are indexed at the physical workspace output location.
140
+ linkTriggers.push(logical);
141
+ }
142
+ yarnLocalTsc(root);
143
+ return {workspace:selected,closure:[...closure.values()],source:[...new Set(source)],
144
+ installed,generated:[...new Set(generated)],configs,
145
+ configurationInputs:[...new Set(['package.json','yarn.lock','.yarnrc.yml',
146
+ ...configs,...members.values().map(row=>row.manifest),
147
+ 'node_modules/.yarn-state.yml'])],linkTriggers,outDir};
148
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // Read-only applicability probe. Its output is one digest or a fixed reason;
3
+ // source, configuration, environment values, and command output are not logged.
4
+ import {compilerFiles} from './typescript-list.mjs';
5
+ import {contextOnly,probeResult} from './probe-result.mjs';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import os from 'node:os';
9
+ import {createHash} from 'node:crypto';
10
+ import {spawnSync} from 'node:child_process';
11
+ import {yarnWorkspaceTypecheckInputs,yarnLocalTsc} from './yarn-workspace-inputs.mjs';
12
+ import {withinPath as within,realObservedPath} from './path-identity.mjs';
13
+ import {windowsLaunch} from './windows-command.mjs';
14
+
15
+ const [rootArg,workspace,script,yarnArg]=process.argv.slice(2);
16
+ const hash=value=>createHash('sha256').update(value).digest('hex');
17
+ const digestFile=file=>hash(fs.readFileSync(file));
18
+ const fail=code=>{throw Object.assign(Error(code),{code});};
19
+ function yarnDistribution(yarn,version){
20
+ let loader=yarn;
21
+ if(process.platform==='win32'&&/\.cmd$/i.test(yarn)){
22
+ const body=fs.readFileSync(yarn,'utf8').replace(/\r\n/g,'\n').trim();
23
+ const line=body.split('\n')[2]||'';
24
+ const match=/^ "%~dp0\\node\.exe" "%~dp0\\(.+\\corepack\\dist\\yarn\.js)" %\*$/.exec(line);
25
+ if(!match)fail('yarn-distribution-unobservable');
26
+ const relative=match[1];
27
+ const standard=`@SETLOCAL
28
+ @IF EXIST "%~dp0\\node.exe" (
29
+ "%~dp0\\node.exe" "%~dp0\\${relative}" %*
30
+ ) ELSE (
31
+ @SET PATHEXT=%PATHEXT:;.JS;=;%
32
+ node "%~dp0\\${relative}" %*
33
+ )`;
34
+ if(body!==standard)fail('yarn-distribution-unobservable');
35
+ loader=realObservedPath(path.resolve(path.dirname(yarn),relative));
36
+ }
37
+ if(!/[/\\]corepack[/\\]dist[/\\]yarn\.js$/.test(loader))
38
+ return {distribution:yarn,loader};
39
+ if(process.env.COREPACK_HOME&&!path.isAbsolute(process.env.COREPACK_HOME))
40
+ fail('yarn-distribution-unobservable');
41
+ const candidates=process.env.COREPACK_HOME?
42
+ [path.join(process.env.COREPACK_HOME,'v1/yarn',version,'yarn.js')]:[
43
+ ...(process.platform==='win32'&&path.isAbsolute(process.env.LOCALAPPDATA||'')?
44
+ [path.join(process.env.LOCALAPPDATA,'node/corepack/v1/yarn',version,'yarn.js')]:[]),
45
+ path.join(os.homedir(),'.cache/node/corepack/v1/yarn',version,'yarn.js'),
46
+ path.join(os.homedir(),'Library/Caches/node/corepack/v1/yarn',version,'yarn.js')];
47
+ const found=candidates.find(file=>fs.existsSync(file));
48
+ if(!found)fail('yarn-distribution-unobservable');
49
+ return {distribution:found,loader};
50
+ }
51
+ try{
52
+ if(!rootArg||!workspace||!script||!yarnArg)fail('probe-arguments');
53
+ const root=realObservedPath(rootArg),yarn=realObservedPath(yarnArg);
54
+ const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json')));
55
+ if(process.env.NODE_OPTIONS||process.env.NODE_PATH||process.env.BASH_ENV||
56
+ process.env.ENV||Object.entries(process.env).some(([key,value])=>{
57
+ if(/^npm_config_prefix$/i.test(key)&&process.platform==='win32'){
58
+ return !path.isAbsolute(value);
59
+ }
60
+ if(key==='COREPACK_HOME')return !path.isAbsolute(value);
61
+ return /^(YARN_|COREPACK_|npm_config_|DYLD_|TSGO_)/i.test(key);
62
+ }))
63
+ fail('workspace-execution-environment-unsupported');
64
+ const launch=windowsLaunch([yarn,'--version']);
65
+ const version=spawnSync(launch.file,launch.args,{cwd:root,env:process.env,
66
+ ...launch.options,
67
+ encoding:'utf8',timeout:3000,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe']});
68
+ if(version.error||version.signal||version.status!==0||
69
+ `yarn@${version.stdout.trim()}`!==pkg.packageManager)
70
+ fail('yarn-toolchain-unavailable');
71
+ const distribution=yarnDistribution(yarn,version.stdout.trim());
72
+ const contextFacts={yarnVersion:version.stdout.trim(),yarnShim:digestFile(yarn),
73
+ yarnLoader:digestFile(distribution.loader),
74
+ yarnDistribution:digestFile(distribution.distribution),node:process.version};
75
+ const contextHash=hash(JSON.stringify(contextFacts));
76
+ if(contextOnly(contextHash))process.exit(0);
77
+ const contract=yarnWorkspaceTypecheckInputs(root,workspace,script);
78
+ const cwd=path.join(root,contract.workspace.dir);
79
+ const tsc=yarnLocalTsc(root);
80
+ const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(path.join(root,'node_modules/typescript'),cwd):null;
81
+ 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
+ stdio:['ignore','pipe','pipe']});
84
+ if(listed.error||listed.signal||listed.status!==0)fail('typescript-input-list-unavailable');
85
+ const inputs=listed.stdout.trim().split(/\r?\n/).filter(Boolean)
86
+ .map(file=>realObservedPath(file));
87
+ if(!inputs.length)fail('typescript-input-list-unavailable');
88
+ const selectedRoot=path.join(cwd,'src'),installedRoot=path.join(root,'node_modules');
89
+ const nestedInstalled=[contract.workspace,...contract.closure]
90
+ .map(member=>path.join(root,member.dir,'node_modules'));
91
+ const generated=contract.generated.map(value=>path.join(root,value.slice(0,-3)));
92
+ for(const file of inputs){
93
+ const covered=within(file,installedRoot)||nestedInstalled.some(dir=>within(file,dir))||
94
+ within(file,selectedRoot)||
95
+ generated.some(dir=>within(file,dir));
96
+ if(!covered||!within(file,root))fail('typescript-input-outside-workspace-contract');
97
+ }
98
+ const files=[...new Set(inputs)].sort().map(file=>
99
+ [hash(path.relative(root,file)),digestFile(file)]);
100
+ const configs=contract.configs.map(file=>[hash(file),digestFile(path.join(root,file))]);
101
+ probeResult(hash(JSON.stringify({schema:1,workspace,script,
102
+ files,configs,yarnVersion:version.stdout.trim(),yarnShim:digestFile(yarn),
103
+ yarnLoader:digestFile(distribution.loader),
104
+ yarnDistribution:digestFile(distribution.distribution),
105
+ node:process.version,tsc:digestFile(tsc)})),contextHash,captured?.queries);
106
+ }catch(e){console.error(`VSTATE_REASON:${e.code&&/^[a-z-]+$/.test(e.code)?e.code:
107
+ 'workspace-typecheck-contract-unavailable'}`);process.exitCode=2;}