@redue/cli 0.1.0-alpha.2 → 0.1.0-alpha.20
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 +467 -0
- package/README.md +107 -2
- package/bin/redue.mjs +130 -49
- package/docs/README.md +2 -1
- package/docs/user/agents.md +299 -15
- package/docs/user/configuration.md +28 -2
- package/docs/user/direct-typescript.md +209 -0
- package/docs/user/getting-started.md +23 -2
- package/docs/user/json.md +2 -0
- package/docs/user/node-script.md +153 -0
- package/docs/user/npm-launcher-notice.md +42 -0
- package/docs/user/pilot.md +146 -0
- package/docs/user/receipt-selection-notice.md +60 -0
- package/docs/user/supported-projects.md +49 -21
- package/docs/user/tests-direct.md +132 -0
- package/package.json +4 -3
- package/src/agent-integration.mjs +388 -0
- package/src/agent-policy.md +28 -0
- package/src/cached-state.mjs +46 -8
- package/src/claude-hook.mjs +449 -0
- package/src/cli.mjs +263 -40
- package/src/daemon.mjs +433 -94
- package/src/decision-profile.mjs +15 -1
- package/src/decision-validation.mjs +10 -0
- package/src/declared-plan.mjs +29 -6
- package/src/direct-run-certificate.mjs +252 -0
- package/src/direct-typescript-probe.mjs +24 -0
- package/src/direct-typescript.mjs +185 -0
- package/src/glob.mjs +8 -0
- package/src/history-worker.mjs +21 -13
- package/src/index.mjs +72 -19
- package/src/installed-inputs.mjs +7 -1
- package/src/linux-inotify.py +39 -10
- package/src/node-onboarding.mjs +157 -48
- package/src/node-script.mjs +362 -0
- package/src/npm-integrity.mjs +124 -0
- package/src/npm-typecheck-environment.mjs +36 -0
- package/src/npmrc.mjs +16 -0
- package/src/observation.mjs +34 -12
- package/src/path-identity.mjs +8 -0
- package/src/plan-worker.mjs +17 -17
- package/src/pnpm-inputs.mjs +114 -54
- package/src/pnpm-shim-templates.json +180 -0
- package/src/pnpm-shims.mjs +54 -0
- package/src/pnpm-typecheck-probe.mjs +11 -6
- package/src/presentation.mjs +9 -1
- package/src/probe-deadline.mjs +37 -0
- package/src/probe-result.mjs +7 -3
- package/src/project-location.mjs +34 -0
- package/src/run-recorder.cjs +91 -0
- package/src/state-store.mjs +37 -1
- package/src/test-direct.mjs +560 -0
- package/src/test-recorder.cjs +406 -0
- package/src/typescript-contract-probe.mjs +4 -187
- package/src/typescript-input-facts.mjs +92 -0
- package/src/typescript-invocation.mjs +92 -0
- package/src/typescript-list.mjs +108 -13
- package/src/typescript-qualification.mjs +29 -49
- package/src/workspace-manifests.mjs +22 -0
- package/src/yarn-workspace-typecheck-probe.mjs +2 -1
package/bin/redue.mjs
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
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');
|
|
6
|
+
import {directCompiler,directQualification,directEnvironment,directInterpretation} from '../src/direct-typescript.mjs';
|
|
7
|
+
import {runArgv,tscRun} from '../src/typescript-invocation.mjs';
|
|
8
|
+
import {testEnvironment,testRunnerAt,testsInterpretation,testsQualification} from '../src/test-direct.mjs';
|
|
9
|
+
import {scriptEnvironment,scriptInterpretation,scriptKinds,scriptQualification} from '../src/node-script.mjs';
|
|
3
10
|
import os from 'node:os';
|
|
4
11
|
import path from 'node:path';
|
|
5
12
|
import {createHash} from 'node:crypto';
|
|
6
13
|
import {spawn,spawnSync} from 'node:child_process';
|
|
7
14
|
import {createRequire} from 'node:module';
|
|
8
|
-
import {fileURLToPath} from 'node:url';
|
|
9
|
-
import {discoverNodeProject} from '../src/node-onboarding.mjs';
|
|
15
|
+
import {fileURLToPath,pathToFileURL} from 'node:url';
|
|
10
16
|
import {processAlive} from '../src/process-liveness.mjs';
|
|
11
17
|
import {samePath,withinPath,realObservedPath} from '../src/path-identity.mjs';
|
|
12
|
-
import {
|
|
18
|
+
import {discoverConfig,projectState,canonicalConfig} from '../src/project-location.mjs';
|
|
13
19
|
import {findExecutable} from '../src/executable-lookup.mjs';
|
|
14
20
|
import {readCachedStatus} from '../src/cached-state.mjs';
|
|
15
21
|
import {assertOwnedStatePlacement} from '../src/owned-state.mjs';
|
|
@@ -22,8 +28,12 @@ const help=`REDUE ${version}
|
|
|
22
28
|
Usage: redue [--config FILE] [--state-dir DIR] COMMAND [OPTIONS]
|
|
23
29
|
|
|
24
30
|
Commands:
|
|
25
|
-
init [--dry-run] [--workspace NAME] [--check KIND]
|
|
31
|
+
init [--dry-run] [--workspace NAME] [--check KIND] [--recipe typescript-direct|tests-direct|node-script]
|
|
26
32
|
Discover existing scripts; optionally select one workspace/check
|
|
33
|
+
agent setup codex|claude [--dry-run|--apply] [--json]
|
|
34
|
+
agent doctor [codex|claude] [--json]
|
|
35
|
+
agent remove codex|claude [--dry-run|--apply] [--json]
|
|
36
|
+
Connect project instructions; explicit setup only
|
|
27
37
|
start Start the local observer
|
|
28
38
|
stop Stop this observer
|
|
29
39
|
status [--json|--short] Read conservative cached status
|
|
@@ -36,13 +46,20 @@ Commands:
|
|
|
36
46
|
No checks run in the background. Direct commands outside redue create no receipts.`;
|
|
37
47
|
|
|
38
48
|
function error(message){console.error(`redue: ${message}`);process.exit(2);}
|
|
49
|
+
// Exits once queued output is written. On macOS, output to a pipe is written
|
|
50
|
+
// asynchronously; exiting with it queued cuts it (at 8 KB through the pipe a
|
|
51
|
+
// Node program, such as an agent's shell tool, reads).
|
|
52
|
+
async function exit(code){await new Promise(resolve=>process.stdout.write('',resolve));process.exit(code);}
|
|
39
53
|
const args=process.argv.slice(2);
|
|
54
|
+
if(args.length===3&&args[0]==='agent'&&args[1]==='hook'&&args[2]==='claude'){
|
|
55
|
+
// Runs before config discovery: the hook must exit 0 on every failure,
|
|
56
|
+
// because Claude Code treats exit 2 as a block.
|
|
57
|
+
const {runClaudeHook}=await import('../src/claude-hook.mjs');
|
|
58
|
+
await runClaudeHook(fileURLToPath(import.meta.url));process.exit(0);
|
|
59
|
+
}
|
|
40
60
|
if(args.length===1&&args[0]==='--version'){console.log(version);process.exit(0);}
|
|
41
|
-
if(args.includes('--help')||args.includes('-h')||!args.length){console.log(help);
|
|
42
|
-
|
|
43
|
-
const legacyConfig=path.resolve('vstate.config.json');
|
|
44
|
-
let configFile=fs.existsSync(preferredConfig)||!fs.existsSync(legacyConfig)?
|
|
45
|
-
preferredConfig:legacyConfig,stateOverride=null;
|
|
61
|
+
if(args.includes('--help')||args.includes('-h')||!args.length){console.log(help);await exit(0);}
|
|
62
|
+
let configFile=null,stateOverride=null;
|
|
46
63
|
for(let i=0;i<args.length;){
|
|
47
64
|
if(args[i]==='--config'||args[i]==='--state-dir'){
|
|
48
65
|
const flag=args[i],value=args[i+1];if(!value)error(`${flag} requires a path`);
|
|
@@ -51,17 +68,28 @@ for(let i=0;i<args.length;){
|
|
|
51
68
|
}else i++;
|
|
52
69
|
}
|
|
53
70
|
const command=args.shift();
|
|
71
|
+
try{configFile??=command==='init'||stateOverride&&['stop','remove-state'].includes(command)?
|
|
72
|
+
path.resolve(fs.existsSync('redue.config.json')||!fs.existsSync('vstate.config.json')?'redue.config.json':'vstate.config.json'):discoverConfig();}
|
|
73
|
+
catch(e){error(e.message);}
|
|
74
|
+
if(fs.existsSync(path.dirname(configFile)))configFile=canonicalConfig(configFile);
|
|
75
|
+
if(command==='agent'){
|
|
76
|
+
// Loaded on demand: onboarding and agent setup are not on the run/status path.
|
|
77
|
+
const {agentCommand}=await import('../src/agent-integration.mjs');
|
|
78
|
+
try{await agentCommand(args,{configFile,stateOverride,entry:fileURLToPath(import.meta.url)});}
|
|
79
|
+
catch(e){error(e.message);}
|
|
80
|
+
await exit(0);
|
|
81
|
+
}
|
|
54
82
|
if(!['init','start','stop','status','detail','explain','run','remove-state'].includes(command))
|
|
55
83
|
error(`unknown command ${command||'<none>'}; use --help`);
|
|
56
84
|
const json=args.includes('--json');
|
|
57
85
|
const sync=args.includes('--sync');
|
|
58
86
|
const dryRun=args.includes('--dry-run');
|
|
59
87
|
const short=args.includes('--short');
|
|
60
|
-
let workspaceSelection=null,checkSelection=null;
|
|
88
|
+
let workspaceSelection=null,checkSelection=null,recipeSelection=null;
|
|
61
89
|
if(command==='init')for(let i=0;i<args.length;){
|
|
62
|
-
if(args[i]==='--workspace'||args[i]==='--check'){
|
|
90
|
+
if(args[i]==='--workspace'||args[i]==='--check'||args[i]==='--recipe'){
|
|
63
91
|
const flag=args[i],value=args[i+1];if(!value||value.startsWith('--'))error(`${flag} requires a name`);
|
|
64
|
-
if(flag==='--workspace')workspaceSelection=value;else checkSelection=value;
|
|
92
|
+
if(flag==='--workspace')workspaceSelection=value;else if(flag==='--recipe')recipeSelection=value;else checkSelection=value;
|
|
65
93
|
args.splice(i,2);
|
|
66
94
|
}else i++;
|
|
67
95
|
}
|
|
@@ -73,7 +101,8 @@ if(short&&(command!=='status'||json||sync))error('--short is a cached status opt
|
|
|
73
101
|
if(dryRun&&command!=='init')error('--dry-run is for init');
|
|
74
102
|
|
|
75
103
|
if(command==='init'){
|
|
76
|
-
|
|
104
|
+
const {discoverNodeProject}=await import('../src/node-onboarding.mjs');
|
|
105
|
+
let discovery;try{discovery=discoverNodeProject(process.cwd(),{recipe:recipeSelection});}
|
|
77
106
|
catch(e){error(e.message);}
|
|
78
107
|
if(workspaceSelection||checkSelection){
|
|
79
108
|
const chosen=discovery.checks.filter(row=>
|
|
@@ -104,7 +133,8 @@ if(command==='init'){
|
|
|
104
133
|
console.log(` ${row.name} (${row.script||row.command?.join(' ')}) — `+
|
|
105
134
|
`${{ready:'ready to qualify',recording:'recording-only',unsupported:'unsupported'}[row.level]||row.level}: ${row.reason}`);
|
|
106
135
|
if(row.script&&row.command)console.log(` Recorded invocation: ${row.command.map(value=>
|
|
107
|
-
value==='@node'?'node':value
|
|
136
|
+
value==='@node'?'node':value==='@typescript-compiler:.'?'[reviewed local TypeScript compiler implementation]':
|
|
137
|
+
value.startsWith('@test-runner:')?`[local ${value.slice('@test-runner:'.length)}]`:value.startsWith('@typescript-bin:')?
|
|
108
138
|
`[installed TypeScript in ${value.slice('@typescript-bin:'.length)}]`:value).join(' ')}`);
|
|
109
139
|
}
|
|
110
140
|
for(const row of summary.ambiguous)
|
|
@@ -121,10 +151,11 @@ if(command==='init'){
|
|
|
121
151
|
if(!json){console.log(`Created ${configFile}. No checks were executed.`);
|
|
122
152
|
console.log('Ready to qualify means the supported contract will be validated; it is not verification evidence.');
|
|
123
153
|
console.log(`Next: review the config, then redue start and redue run ${JSON.stringify(discovery.checks[0].config.name)}.`);
|
|
154
|
+
console.log('Connect your agent: redue agent setup codex (or claude). Preview with --dry-run.');
|
|
124
155
|
console.log('Use redue status first. --sync can establish applicability, but may cost more than rerunning a cheap check.');}
|
|
125
156
|
} else if(unsupported&&!json)console.log(`No config written: ${summary.issue||'unsupported discovery boundary'}.`);
|
|
126
157
|
if(json)console.log(JSON.stringify({...summary,proposed_config:discovery.config}));
|
|
127
|
-
|
|
158
|
+
await exit(0);
|
|
128
159
|
}
|
|
129
160
|
function printLifecycle(output,action,machine){
|
|
130
161
|
if(machine){process.stdout.write(output);return;}
|
|
@@ -141,11 +172,12 @@ function printLifecycle(output,action,machine){
|
|
|
141
172
|
if(stateOverride&&['stop','remove-state'].includes(command)){
|
|
142
173
|
const runtime=path.join(stateOverride,'project-runtime-v1.json');
|
|
143
174
|
if(!fs.existsSync(runtime)){console.log('No REDUE state exists at this path.');process.exit(0);}
|
|
175
|
+
mark('client.engine_submit');
|
|
144
176
|
const result=spawnSync(process.execPath,[engine,runtime,command==='stop'?'stop':'uninstall'],
|
|
145
177
|
{cwd:os.tmpdir(),stdio:['inherit','pipe','inherit'],encoding:'utf8',env:process.env});
|
|
146
178
|
if(result.error)error(result.error.message);
|
|
147
179
|
if(result.stdout)printLifecycle(result.stdout,command,json);
|
|
148
|
-
|
|
180
|
+
await exit(result.status??2);
|
|
149
181
|
}
|
|
150
182
|
let publicConfig;
|
|
151
183
|
try{publicConfig=JSON.parse(fs.readFileSync(configFile,'utf8'));}
|
|
@@ -157,10 +189,7 @@ for(const key of Object.keys(publicConfig))
|
|
|
157
189
|
if(!['schema','root','checks','packageManager'].includes(key))error(`unsupported config field ${key}`);
|
|
158
190
|
if(check&&!publicConfig.checks.some(row=>row.name===check))error(`unknown check ${check}; use redue status to list configured checks`);
|
|
159
191
|
const root=realObservedPath(path.resolve(path.dirname(configFile),publicConfig.root||'.'));
|
|
160
|
-
const
|
|
161
|
-
const state=stateOverride||path.join(stateBase,'vstate',
|
|
162
|
-
`vstate-${createHash('sha256').update(stateIdentity(root)+'\0'+
|
|
163
|
-
stateIdentity(configFile)).digest('hex').slice(0,16)}`);
|
|
192
|
+
const state=stateOverride||projectState(root,configFile);
|
|
164
193
|
if(!/^(?:vstate|redue)-/.test(path.basename(state)))
|
|
165
194
|
error('--state-dir must name a dedicated redue-* directory outside the checkout (legacy vstate-* is also supported)');
|
|
166
195
|
const runtimeFile=path.join(state,'project-runtime-v1.json');
|
|
@@ -169,9 +198,18 @@ function which(name){
|
|
|
169
198
|
if(found)return found;
|
|
170
199
|
error(`cannot resolve executable ${name} on PATH`);
|
|
171
200
|
}
|
|
172
|
-
function token(value){
|
|
201
|
+
function token(value,cwd='.'){
|
|
173
202
|
if(value==='@node')return process.execPath;
|
|
174
203
|
if(value==='@project')return root;
|
|
204
|
+
if(value==='@typescript-compiler:.'){
|
|
205
|
+
try{return directCompiler(root,{verify:false}).entry;}catch(e){error(`direct compiler unavailable: ${e.code||e.message}`);}
|
|
206
|
+
}
|
|
207
|
+
if(value.startsWith('@test-runner:')){
|
|
208
|
+
// Qualification reports a missing runner; status and stop still work.
|
|
209
|
+
try{return testRunnerAt(root,cwd,value.slice('@test-runner:'.length)).entry;}
|
|
210
|
+
catch{return path.join(root,cwd,'node_modules',value.slice('@test-runner:'.length),'<unavailable>');}
|
|
211
|
+
}
|
|
212
|
+
if(value==='@vstate/direct-typescript-probe')return path.join(productRoot,'src','direct-typescript-probe.mjs');
|
|
175
213
|
if(value.startsWith('@typescript-bin:')){
|
|
176
214
|
const dir=value.slice('@typescript-bin:'.length);
|
|
177
215
|
if(!dir||path.isAbsolute(dir)||dir.split('/').includes('..'))
|
|
@@ -192,10 +230,10 @@ function token(value){
|
|
|
192
230
|
if(value.startsWith('@which:'))return which(value.slice(7));
|
|
193
231
|
return value;
|
|
194
232
|
}
|
|
195
|
-
function argv(values,label){
|
|
233
|
+
function argv(values,label,cwd){
|
|
196
234
|
if(!Array.isArray(values)||!values.length||values.some(v=>typeof v!=='string'||!v))
|
|
197
235
|
error(`${label} must be nonempty exact argv`);
|
|
198
|
-
return values.map(token);
|
|
236
|
+
return values.map(value=>token(value,cwd));
|
|
199
237
|
}
|
|
200
238
|
const categories=['source','generated','installedDependencies','environment','toolchain','runtime',
|
|
201
239
|
'probeContinuity'];
|
|
@@ -208,8 +246,11 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
208
246
|
for(const category of categories)if(check.coverage?.[category]&&
|
|
209
247
|
typeof check.coverageReview?.[category]!=='string')
|
|
210
248
|
error(`${check.name}: reviewed coverage ${category} needs a rationale`);
|
|
249
|
+
const npmDirect=check.qualification===directQualification;
|
|
211
250
|
const pnpmTypecheck=check.qualification==='pnpm-tsc-v1';
|
|
212
|
-
|
|
251
|
+
const testsDirect=check.qualification===testsQualification;
|
|
252
|
+
const generalScript=check.qualification===scriptQualification;
|
|
253
|
+
if(check.script&&check.command&&!pnpmTypecheck&&!npmDirect&&!testsDirect)
|
|
213
254
|
error(`${check.name}: choose script or command, not both`);
|
|
214
255
|
const manager=publicConfig.packageManager;
|
|
215
256
|
if(check.script&&!['npm','yarn','pnpm'].includes(manager))
|
|
@@ -218,8 +259,20 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
218
259
|
[`@which:${manager}`,'run',check.script]:null;
|
|
219
260
|
const npmAutomatic=check.qualification==='typescript-noemit-v1';
|
|
220
261
|
const yarnWorkspace=check.qualification==='yarn-workspace-tsc-v1';
|
|
221
|
-
if(check.qualification&&!npmAutomatic&&!yarnWorkspace&&!pnpmTypecheck)
|
|
262
|
+
if(check.qualification&&!npmAutomatic&&!npmDirect&&!yarnWorkspace&&!pnpmTypecheck&&!testsDirect&&!generalScript)
|
|
222
263
|
error(`${check.name}: unknown qualification contract`);
|
|
264
|
+
// The general recipe runs the root package script through the package manager.
|
|
265
|
+
if(generalScript&&(!['npm','pnpm','yarn'].includes(manager)||!check.script||check.command||check.cwd&&check.cwd!=='.'||
|
|
266
|
+
check.workspace||!scriptKinds.includes(check.kind)))
|
|
267
|
+
error(`${check.name}: the general recipe needs a root typecheck, test or lint script, without command or cwd`);
|
|
268
|
+
if(npmDirect&&(manager!=='npm'||check.cwd&&check.cwd!=='.'||!Array.isArray(check.command)||
|
|
269
|
+
check.command[0]!=='@node'||check.command[1]!=='@typescript-compiler:.'||
|
|
270
|
+
!tscRun({},{script:null,args:check.command.slice(2)}).run?.noEmit))
|
|
271
|
+
error(`${check.name}: direct recipe requires its explicit local compiler command`);
|
|
272
|
+
// The script's own arguments follow the runner; qualification compares them with the script.
|
|
273
|
+
if(testsDirect&&(!['npm','pnpm','yarn'].includes(manager)||!check.script||check.cwd&&check.cwd!=='.'&&!check.workspace||!Array.isArray(check.command)||
|
|
274
|
+
check.command[0]!=='@node'||!/^@test-runner:(?:vitest|jest)$/.test(check.command[1]||'')))
|
|
275
|
+
error(`${check.name}: test recipe requires its explicit local test runner command`);
|
|
223
276
|
if(npmAutomatic&&(manager!=='npm'||check.cwd&&check.cwd!=='.'))
|
|
224
277
|
error(`${check.name}: automatic TypeScript qualification currently needs root npm script`);
|
|
225
278
|
if(yarnWorkspace&&(manager!=='yarn'||!check.workspace||check.cwd&&check.cwd!=='.'||
|
|
@@ -227,25 +280,32 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
227
280
|
check.command[1]!=='workspace'||check.command[2]!==check.workspace||
|
|
228
281
|
check.command[3]!=='run'))
|
|
229
282
|
error(`${check.name}: Yarn workspace qualification needs its discovered workspace command`);
|
|
230
|
-
if(pnpmTypecheck&&(manager!=='pnpm'||!
|
|
283
|
+
if(pnpmTypecheck&&(manager!=='pnpm'||!Array.isArray(check.command)||
|
|
231
284
|
check.command[0]!=='@node'||
|
|
232
285
|
check.command[1]!==`@typescript-bin:${check.cwd||'.'}`||
|
|
233
286
|
check.workspace&&check.cwd==='.'||
|
|
234
|
-
!check.command.slice(2).
|
|
287
|
+
!tscRun({},{script:null,args:check.command.slice(2)}).run))
|
|
235
288
|
error(`${check.name}: pnpm qualification needs its discovered standalone TypeScript command`);
|
|
236
289
|
const reason=check.kind==='test'?'test runtime, transforms and cache inputs need review':
|
|
237
290
|
check.kind==='lint'?'lint plugins, config and resolver inputs need review':
|
|
238
291
|
check.kind==='build'?'build environment and generated inputs need review':
|
|
239
292
|
'check inputs need review';
|
|
240
|
-
|
|
293
|
+
// A chained script has one check per tsc run: each check's run counts them,
|
|
294
|
+
// so a run added to or removed from the script invalidates them all.
|
|
295
|
+
const runCount=(npmDirect||pnpmTypecheck)&&check.script?publicConfig.checks.filter(other=>
|
|
296
|
+
other.qualification===check.qualification&&other.script===check.script&&
|
|
297
|
+
(other.cwd||'.')===(check.cwd||'.')&&(other.workspace||null)===(check.workspace||null)).length:1;
|
|
298
|
+
if(runCount>16)error(`${check.name}: more than 16 checks for script ${check.script}`);
|
|
299
|
+
const run=npmDirect||pnpmTypecheck?runArgv({script:check.script??null,args:check.command.slice(2),count:runCount}):null;
|
|
300
|
+
const autoProbes=npmDirect?[['@node','@vstate/direct-typescript-probe','@project',...run]]:npmAutomatic?[['@node','@vstate/typescript-contract-probe',
|
|
241
301
|
'@project',check.script,'@which:npm']]:yarnWorkspace?
|
|
242
302
|
[['@node','@vstate/yarn-workspace-typecheck-probe','@project',
|
|
243
303
|
check.workspace,check.command[4],'@which:yarn']]:pnpmTypecheck?
|
|
244
304
|
[['@node','@vstate/pnpm-typecheck-probe','@project',
|
|
245
|
-
check.workspace||'.'
|
|
305
|
+
check.workspace||'.',...run]]:[];
|
|
246
306
|
const windowsContext=process.platform==='win32'?
|
|
247
307
|
['ComSpec','COMSPEC','PATHEXT','USERPROFILE']:[];
|
|
248
|
-
const autoEnvironment=npmAutomatic?{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME',
|
|
308
|
+
const autoEnvironment=npmDirect?directEnvironment:testsDirect?testEnvironment:generalScript?scriptEnvironment(manager):npmAutomatic?{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME',
|
|
249
309
|
'NODE_ENV','BASH_ENV','ENV',...windowsContext],prefixes:['npm_config_','DYLD_','TSGO_'],
|
|
250
310
|
pathExecutables:['node','npm'],executableIdentity:true}:yarnWorkspace?
|
|
251
311
|
{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME','NODE_ENV','BASH_ENV','ENV',...windowsContext],
|
|
@@ -254,10 +314,11 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
254
314
|
{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME','NODE_ENV','BASH_ENV','ENV',...windowsContext],
|
|
255
315
|
prefixes:['PNPM_','COREPACK_','npm_config_','DYLD_','TSGO_'],
|
|
256
316
|
pathExecutables:['node'],executableIdentity:true}:{};
|
|
257
|
-
return {name:check.name,command:argv(scriptCommand||check.command,`${check.name}.command
|
|
317
|
+
return {name:check.name,command:argv(scriptCommand||check.command,`${check.name}.command`,
|
|
318
|
+
typeof check.cwd==='string'&&check.cwd&&!path.isAbsolute(check.cwd)&&!check.cwd.split('/').includes('..')?check.cwd:'.'),
|
|
258
319
|
cwd:check.cwd,inputs:check.inputs||[],generatedInputs:check.generatedInputs||[],
|
|
259
320
|
installedInputs:check.installedInputs||(
|
|
260
|
-
npmAutomatic?['node_modules/**']:[]),
|
|
321
|
+
npmAutomatic||npmDirect||testsDirect?['node_modules/**']:[]),
|
|
261
322
|
allowedExternalRoots:check.allowedExternalRoots||[],
|
|
262
323
|
probes:[...(check.probes||[]),...autoProbes]
|
|
263
324
|
.map((p,i)=>argv(p,`${check.name}.probes[${i}]`)),
|
|
@@ -266,6 +327,11 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
266
327
|
coverageReasons:check.coverageReasons||Object.fromEntries(
|
|
267
328
|
categories.map(category=>[category,reason])),
|
|
268
329
|
qualification:check.qualification||null,script:check.script||null,
|
|
330
|
+
...(runCount>1?{runCount}:{}),
|
|
331
|
+
...(npmAutomatic?{qualificationInterpretation:'npm-launcher-withheld@1'}:{}),
|
|
332
|
+
...(npmDirect?{qualificationInterpretation:directInterpretation}:{}),
|
|
333
|
+
...(testsDirect?{qualificationInterpretation:testsInterpretation}:{}),
|
|
334
|
+
...(generalScript?{qualificationInterpretation:scriptInterpretation}:{}),
|
|
269
335
|
workspace:check.workspace||null,kind:check.kind||null};
|
|
270
336
|
});
|
|
271
337
|
const runtime={schema:1,provider:'declared-project@1',root,state,checks};
|
|
@@ -277,7 +343,13 @@ if(maintenance&&!fs.existsSync(runtimeFile)){
|
|
|
277
343
|
process.exit(0);
|
|
278
344
|
}
|
|
279
345
|
if(!maintenance)fs.mkdirSync(state,{recursive:true,mode:0o700});
|
|
280
|
-
|
|
346
|
+
// Two commands can meet on fresh state (SessionStart's start and the first
|
|
347
|
+
// status): whichever creates the runtime file first wins, and the other
|
|
348
|
+
// treats it as existing.
|
|
349
|
+
let created=false;
|
|
350
|
+
if(!maintenance&&!fs.existsSync(runtimeFile))
|
|
351
|
+
try{fs.writeFileSync(runtimeFile,serialized,{flag:'wx',mode:0o600});created=true;}catch(e){if(e.code!=='EEXIST')throw e;}
|
|
352
|
+
if(!maintenance&&!created){
|
|
281
353
|
const previous=fs.readFileSync(runtimeFile,'utf8');
|
|
282
354
|
if(previous!==serialized){
|
|
283
355
|
const pidFile=path.join(state,'observer.lock','pid');
|
|
@@ -287,18 +359,24 @@ if(!maintenance&&fs.existsSync(runtimeFile)){
|
|
|
287
359
|
}
|
|
288
360
|
fs.writeFileSync(runtimeFile,serialized,{mode:0o600});
|
|
289
361
|
}
|
|
290
|
-
}
|
|
362
|
+
}
|
|
291
363
|
|
|
292
364
|
if(command==='status'&&!sync){
|
|
293
365
|
const value=readCachedStatus(state,publicConfig.checks);
|
|
294
366
|
console.log(json?JSON.stringify(value):renderStatus(value,{short}));
|
|
295
|
-
|
|
367
|
+
await exit(0);
|
|
296
368
|
}
|
|
297
369
|
|
|
298
370
|
const details=command==='detail'||command==='explain';
|
|
299
371
|
const internal=command==='remove-state'?'uninstall':
|
|
300
372
|
details?'sync':command==='status'&&sync?'sync':command;
|
|
301
|
-
|
|
373
|
+
// On macOS and Linux the engine runs in this process, which saves a Node startup
|
|
374
|
+
// on every run; its own handlers receive SIGINT and SIGTERM. Windows keeps a
|
|
375
|
+
// child engine because cancellation reaches it over IPC.
|
|
376
|
+
if(command==='run'&&process.platform!=='win32'){
|
|
377
|
+
process.argv=[process.argv[0],engine,runtimeFile,internal,check,...(!json?['--human']:[])];
|
|
378
|
+
await import(pathToFileURL(engine).href);
|
|
379
|
+
}else if(command==='run'){
|
|
302
380
|
const child=spawn(process.execPath,[engine,runtimeFile,internal,check,...(!json?['--human']:[])],
|
|
303
381
|
{stdio:['inherit','inherit','inherit','ipc'],env:process.env});
|
|
304
382
|
const forward=signal=>{if(child.exitCode!==null)return;
|
|
@@ -313,17 +391,20 @@ if(command==='run'){
|
|
|
313
391
|
process.off('SIGINT',interrupt);process.off('SIGTERM',terminate);
|
|
314
392
|
if(outcome.error)error(outcome.error.message);
|
|
315
393
|
process.exit(outcome.status??(outcome.signal?128+os.constants.signals[outcome.signal]:1));
|
|
394
|
+
}else{
|
|
395
|
+
mark('client.engine_submit');
|
|
396
|
+
const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
|
|
397
|
+
{stdio:['inherit','pipe','inherit'],
|
|
398
|
+
encoding:'utf8',env:traceEnvironment()});
|
|
399
|
+
mark('client.engine_complete',{exit:result.status,signal:result.signal});
|
|
400
|
+
if(result.error)error(result.error.message);
|
|
401
|
+
if(result.stdout){
|
|
402
|
+
if(['status','detail','explain'].includes(command)){
|
|
403
|
+
let value;try{value=JSON.parse(result.stdout);}catch{error('status response could not be read; applicability is unknown');}
|
|
404
|
+
if(details&&check)value={...value,checks:value.checks.filter(row=>row.name===check)};
|
|
405
|
+
if(json)console.log(JSON.stringify(value));
|
|
406
|
+
else console.log(details?renderExplain(value,check):renderStatus(value,{short}));
|
|
407
|
+
}else printLifecycle(result.stdout,command,json);
|
|
408
|
+
}
|
|
409
|
+
await exit(result.status??1);
|
|
316
410
|
}
|
|
317
|
-
const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
|
|
318
|
-
{stdio:['inherit','pipe','inherit'],
|
|
319
|
-
encoding:'utf8',env:process.env});
|
|
320
|
-
if(result.error)error(result.error.message);
|
|
321
|
-
if(result.stdout){
|
|
322
|
-
if(['status','detail','explain'].includes(command)){
|
|
323
|
-
let value;try{value=JSON.parse(result.stdout);}catch{error('status response could not be read; applicability is unknown');}
|
|
324
|
-
if(details&&check)value={...value,checks:value.checks.filter(row=>row.name===check)};
|
|
325
|
-
if(json)console.log(JSON.stringify(value));
|
|
326
|
-
else console.log(details?renderExplain(value,check):renderStatus(value,{short}));
|
|
327
|
-
}else printLifecycle(result.stdout,command,json);
|
|
328
|
-
}
|
|
329
|
-
process.exit(result.status??1);
|
package/docs/README.md
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
For users:
|
|
4
4
|
|
|
5
5
|
- [Install, initialize, run, and remove](user/getting-started.md)
|
|
6
|
+
- [Fixed-build opt-in development pilot](user/pilot.md)
|
|
6
7
|
- [Supported projects and conservative boundaries](user/supported-projects.md)
|
|
7
8
|
- [Configuration](user/configuration.md)
|
|
8
|
-
- [
|
|
9
|
+
- [Connect coding agents and apply the decision policy](user/agents.md)
|
|
9
10
|
- [JSON contract](user/json.md)
|
|
10
11
|
- [Shell usage](user/shell.md)
|
|
11
12
|
|