@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.
- package/CHANGELOG.md +128 -0
- package/LICENSE +180 -0
- package/README.md +162 -2
- package/bin/redue.mjs +355 -0
- package/bin/vstate.mjs +3 -0
- package/docs/README.md +21 -0
- package/docs/user/agents.md +207 -0
- package/docs/user/configuration.md +60 -0
- package/docs/user/direct-typescript.md +96 -0
- package/docs/user/getting-started.md +148 -0
- package/docs/user/json.md +59 -0
- package/docs/user/npm-launcher-notice.md +42 -0
- package/docs/user/pilot.md +142 -0
- package/docs/user/receipt-selection-notice.md +60 -0
- package/docs/user/shell.md +25 -0
- package/docs/user/supported-projects.md +90 -0
- package/examples/redue.config.json +8 -0
- package/package.json +47 -4
- package/src/agent-integration.mjs +248 -0
- package/src/agent-policy.md +70 -0
- package/src/cached-state.mjs +42 -0
- package/src/cli.mjs +459 -0
- package/src/control-endpoint.mjs +11 -0
- package/src/daemon.mjs +1082 -0
- package/src/decision-profile.mjs +32 -0
- package/src/decision-validation.mjs +41 -0
- package/src/declared-plan.mjs +125 -0
- package/src/direct-run-certificate.mjs +40 -0
- package/src/direct-typescript-probe.mjs +56 -0
- package/src/direct-typescript.mjs +76 -0
- package/src/executable-lookup.mjs +18 -0
- package/src/glob.mjs +9 -0
- package/src/history-worker.mjs +23 -0
- package/src/index.mjs +306 -0
- package/src/installed-inputs.mjs +110 -0
- package/src/linux-inotify.py +228 -0
- package/src/linux-observer.mjs +79 -0
- package/src/node-onboarding.mjs +274 -0
- package/src/npm-typecheck-environment.mjs +36 -0
- package/src/npm-typecheck-probe.mjs +120 -0
- package/src/observation.mjs +39 -0
- package/src/owned-state.mjs +32 -0
- package/src/path-identity.mjs +43 -0
- package/src/plan-worker.mjs +140 -0
- package/src/plan.mjs +10 -0
- package/src/platform-observation.mjs +17 -0
- package/src/platform-state.mjs +14 -0
- package/src/pnpm-inputs.mjs +355 -0
- package/src/pnpm-typecheck-probe.mjs +47 -0
- package/src/presentation.mjs +53 -0
- package/src/probe-result.mjs +21 -0
- package/src/process-liveness.mjs +11 -0
- package/src/project-location.mjs +34 -0
- package/src/run-lock.mjs +77 -0
- package/src/state-store.mjs +73 -0
- package/src/toolchain-probe.mjs +21 -0
- package/src/typescript-contract-probe.mjs +5 -0
- package/src/typescript-input-facts.mjs +65 -0
- package/src/typescript-list.mjs +72 -0
- package/src/typescript-qualification.mjs +77 -0
- package/src/windows-command.mjs +16 -0
- package/src/windows-process.mjs +15 -0
- package/src/yarn-workspace-inputs.mjs +148 -0
- package/src/yarn-workspace-typecheck-probe.mjs +107 -0
package/bin/redue.mjs
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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 os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import {createHash} from 'node:crypto';
|
|
10
|
+
import {spawn,spawnSync} from 'node:child_process';
|
|
11
|
+
import {createRequire} from 'node:module';
|
|
12
|
+
import {fileURLToPath} from 'node:url';
|
|
13
|
+
import {discoverNodeProject} from '../src/node-onboarding.mjs';
|
|
14
|
+
import {processAlive} from '../src/process-liveness.mjs';
|
|
15
|
+
import {samePath,withinPath,realObservedPath} from '../src/path-identity.mjs';
|
|
16
|
+
import {discoverConfig,projectState,canonicalConfig} from '../src/project-location.mjs';
|
|
17
|
+
import {agentCommand} from '../src/agent-integration.mjs';
|
|
18
|
+
import {findExecutable} from '../src/executable-lookup.mjs';
|
|
19
|
+
import {readCachedStatus} from '../src/cached-state.mjs';
|
|
20
|
+
import {assertOwnedStatePlacement} from '../src/owned-state.mjs';
|
|
21
|
+
import {renderStatus,renderExplain} from '../src/presentation.mjs';
|
|
22
|
+
|
|
23
|
+
const productRoot=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
24
|
+
const engine=path.join(productRoot,'src','cli.mjs');
|
|
25
|
+
const version=JSON.parse(fs.readFileSync(path.join(productRoot,'package.json'))).version;
|
|
26
|
+
const help=`REDUE ${version}
|
|
27
|
+
Usage: redue [--config FILE] [--state-dir DIR] COMMAND [OPTIONS]
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
init [--dry-run] [--workspace NAME] [--check KIND] [--recipe typescript-direct]
|
|
31
|
+
Discover existing scripts; optionally select one workspace/check
|
|
32
|
+
agent setup codex|claude [--dry-run|--apply] [--json]
|
|
33
|
+
agent doctor [codex|claude] [--json]
|
|
34
|
+
agent remove codex|claude [--dry-run|--apply] [--json]
|
|
35
|
+
Connect project instructions; explicit setup only
|
|
36
|
+
start Start the local observer
|
|
37
|
+
stop Stop this observer
|
|
38
|
+
status [--json|--short] Read conservative cached status
|
|
39
|
+
status --sync Reconcile inputs and caller context before answering
|
|
40
|
+
explain [CHECK] [--json] Explain applicability (synchronized; may cost more than a rerun)
|
|
41
|
+
detail [CHECK] [--json] Alias for explain
|
|
42
|
+
run CHECK Execute one configured check and record its outcome
|
|
43
|
+
remove-state Stop and remove only this configuration's owned local state
|
|
44
|
+
|
|
45
|
+
No checks run in the background. Direct commands outside redue create no receipts.`;
|
|
46
|
+
|
|
47
|
+
function error(message){console.error(`redue: ${message}`);process.exit(2);}
|
|
48
|
+
const args=process.argv.slice(2);
|
|
49
|
+
if(args.length===1&&args[0]==='--version'){console.log(version);process.exit(0);}
|
|
50
|
+
if(args.includes('--help')||args.includes('-h')||!args.length){console.log(help);process.exit(0);}
|
|
51
|
+
let configFile=null,stateOverride=null;
|
|
52
|
+
for(let i=0;i<args.length;){
|
|
53
|
+
if(args[i]==='--config'||args[i]==='--state-dir'){
|
|
54
|
+
const flag=args[i],value=args[i+1];if(!value)error(`${flag} requires a path`);
|
|
55
|
+
if(flag==='--config')configFile=path.resolve(value);else stateOverride=path.resolve(value);
|
|
56
|
+
args.splice(i,2);
|
|
57
|
+
}else i++;
|
|
58
|
+
}
|
|
59
|
+
const command=args.shift();
|
|
60
|
+
try{configFile??=command==='init'||stateOverride&&['stop','remove-state'].includes(command)?
|
|
61
|
+
path.resolve(fs.existsSync('redue.config.json')||!fs.existsSync('vstate.config.json')?'redue.config.json':'vstate.config.json'):discoverConfig();}
|
|
62
|
+
catch(e){error(e.message);}
|
|
63
|
+
if(fs.existsSync(path.dirname(configFile)))configFile=canonicalConfig(configFile);
|
|
64
|
+
if(command==='agent'){
|
|
65
|
+
try{await agentCommand(args,{configFile,stateOverride,entry:fileURLToPath(import.meta.url)});}
|
|
66
|
+
catch(e){error(e.message);}
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
if(!['init','start','stop','status','detail','explain','run','remove-state'].includes(command))
|
|
70
|
+
error(`unknown command ${command||'<none>'}; use --help`);
|
|
71
|
+
const json=args.includes('--json');
|
|
72
|
+
const sync=args.includes('--sync');
|
|
73
|
+
const dryRun=args.includes('--dry-run');
|
|
74
|
+
const short=args.includes('--short');
|
|
75
|
+
let workspaceSelection=null,checkSelection=null,recipeSelection=null;
|
|
76
|
+
if(command==='init')for(let i=0;i<args.length;){
|
|
77
|
+
if(args[i]==='--workspace'||args[i]==='--check'||args[i]==='--recipe'){
|
|
78
|
+
const flag=args[i],value=args[i+1];if(!value||value.startsWith('--'))error(`${flag} requires a name`);
|
|
79
|
+
if(flag==='--workspace')workspaceSelection=value;else if(flag==='--recipe')recipeSelection=value;else checkSelection=value;
|
|
80
|
+
args.splice(i,2);
|
|
81
|
+
}else i++;
|
|
82
|
+
}
|
|
83
|
+
const check=['run','detail','explain'].includes(command)&&args[0]&&!args[0].startsWith('--')?args.shift():null;
|
|
84
|
+
if(command==='run'&&!check)error('run requires a check name');
|
|
85
|
+
if(args.some(value=>!['--json','--sync','--dry-run','--short'].includes(value)))error('unknown option; use --help');
|
|
86
|
+
if(sync&&!['status','detail','explain'].includes(command))error('--sync is for status or detail');
|
|
87
|
+
if(short&&(command!=='status'||json||sync))error('--short is a cached status option; do not combine it with --sync or --json');
|
|
88
|
+
if(dryRun&&command!=='init')error('--dry-run is for init');
|
|
89
|
+
|
|
90
|
+
if(command==='init'){
|
|
91
|
+
let discovery;try{discovery=discoverNodeProject(process.cwd(),{recipe:recipeSelection});}
|
|
92
|
+
catch(e){error(e.message);}
|
|
93
|
+
if(workspaceSelection||checkSelection){
|
|
94
|
+
const chosen=discovery.checks.filter(row=>
|
|
95
|
+
(!workspaceSelection||row.config.workspace===workspaceSelection||
|
|
96
|
+
row.config.command?.[2]===workspaceSelection)&&
|
|
97
|
+
(!checkSelection||row.config.kind===checkSelection));
|
|
98
|
+
if(!chosen.length)error('no discovered check matches the selected workspace and kind');
|
|
99
|
+
discovery.checks=chosen;discovery.config.checks=chosen.map(row=>row.config);
|
|
100
|
+
}
|
|
101
|
+
const configured=fs.existsSync(configFile),unsupported=Boolean(discovery.observationIssue||
|
|
102
|
+
discovery.manager.issue&&!['npm','yarn','pnpm'].includes(discovery.manager.name)||
|
|
103
|
+
discovery.manager.name==='yarn'&&!discovery.manager.version);
|
|
104
|
+
const willWrite=!dryRun&&!configured&&!unsupported&&discovery.checks.length>0;
|
|
105
|
+
const summary={schema:1,repository:discovery.root,node:discovery.node,
|
|
106
|
+
package_manager:discovery.manager.name,version:discovery.manager.version,
|
|
107
|
+
installation_layout:discovery.manager.layout,
|
|
108
|
+
issue:discovery.observationIssue||discovery.manager.issue,workspaces:discovery.workspaces,
|
|
109
|
+
ambiguous:discovery.ambiguous,
|
|
110
|
+
checks:discovery.checks.map(({config:check,level,reason})=>({name:check.name,
|
|
111
|
+
script:check.script||null,command:check.command||null,
|
|
112
|
+
workspace:check.workspace||null,level,reason})),config:configFile,
|
|
113
|
+
written:willWrite,existing:configured};
|
|
114
|
+
if(!json) {
|
|
115
|
+
console.log(`Node ${summary.node}; ${summary.package_manager||'unknown package manager'}`+
|
|
116
|
+
`${summary.version?' '+summary.version:''}; ${summary.installation_layout} installation.`);
|
|
117
|
+
if(summary.issue)console.log(`Boundary: ${summary.issue}`);
|
|
118
|
+
for(const row of summary.checks){
|
|
119
|
+
console.log(` ${row.name} (${row.script||row.command?.join(' ')}) — `+
|
|
120
|
+
`${{ready:'ready to qualify',recording:'recording-only',unsupported:'unsupported'}[row.level]||row.level}: ${row.reason}`);
|
|
121
|
+
if(row.script&&row.command)console.log(` Recorded invocation: ${row.command.map(value=>
|
|
122
|
+
value==='@node'?'node':value==='@typescript-compiler:.'?'[reviewed local TypeScript compiler implementation]':value.startsWith('@typescript-bin:')?
|
|
123
|
+
`[installed TypeScript in ${value.slice('@typescript-bin:'.length)}]`:value).join(' ')}`);
|
|
124
|
+
}
|
|
125
|
+
for(const row of summary.ambiguous)
|
|
126
|
+
console.log(` ${row.kind}: ambiguous scripts (${row.scripts.join(', ')}); choose one explicitly`);
|
|
127
|
+
if(!summary.checks.length)console.log('No useful existing verification scripts found.');
|
|
128
|
+
console.log(configured?`Existing ${configFile} left unchanged.`:
|
|
129
|
+
dryRun?`Preview only; ${configFile} was not written.`:
|
|
130
|
+
willWrite?`Writing ${configFile}; inspect it before running checks.`:'No configuration will be written.');
|
|
131
|
+
}
|
|
132
|
+
if(willWrite){
|
|
133
|
+
if(!samePath(realObservedPath(path.dirname(configFile)),discovery.root))
|
|
134
|
+
error('init writes only at the repository root; use --config there or --dry-run');
|
|
135
|
+
fs.writeFileSync(configFile,JSON.stringify(discovery.config,null,2)+'\n',{flag:'wx'});
|
|
136
|
+
if(!json){console.log(`Created ${configFile}. No checks were executed.`);
|
|
137
|
+
console.log('Ready to qualify means the supported contract will be validated; it is not verification evidence.');
|
|
138
|
+
console.log(`Next: review the config, then redue start and redue run ${JSON.stringify(discovery.checks[0].config.name)}.`);
|
|
139
|
+
console.log('Connect your agent: redue agent setup codex (or claude). Preview with --dry-run.');
|
|
140
|
+
console.log('Use redue status first. --sync can establish applicability, but may cost more than rerunning a cheap check.');}
|
|
141
|
+
} else if(unsupported&&!json)console.log(`No config written: ${summary.issue||'unsupported discovery boundary'}.`);
|
|
142
|
+
if(json)console.log(JSON.stringify({...summary,proposed_config:discovery.config}));
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
function printLifecycle(output,action,machine){
|
|
146
|
+
if(machine){process.stdout.write(output);return;}
|
|
147
|
+
let value;try{value=JSON.parse(output);}catch{process.stdout.write(output);return;}
|
|
148
|
+
if(action==='start'){
|
|
149
|
+
console.log(value.ready?'Observer ready.':'Observer started; checking inputs. Applicability stays UNVERIFIED until ready.');
|
|
150
|
+
console.log('Use redue status. Stop with redue stop.');
|
|
151
|
+
}else if(action==='remove-state')console.log(`Removed only REDUE-owned state: ${value.removed}`);
|
|
152
|
+
else console.log(value.ok?'Observer stopped. Historical evidence is retained.':
|
|
153
|
+
`Observer unavailable: ${value.reason||'not running'}. Previous evidence is not CURRENT.`);
|
|
154
|
+
}
|
|
155
|
+
// Owned-state maintenance must not need the checkout, its package manager,
|
|
156
|
+
// or installed compiler to remain present. No command is run from this path.
|
|
157
|
+
if(stateOverride&&['stop','remove-state'].includes(command)){
|
|
158
|
+
const runtime=path.join(stateOverride,'project-runtime-v1.json');
|
|
159
|
+
if(!fs.existsSync(runtime)){console.log('No REDUE state exists at this path.');process.exit(0);}
|
|
160
|
+
mark('client.engine_submit');
|
|
161
|
+
const result=spawnSync(process.execPath,[engine,runtime,command==='stop'?'stop':'uninstall'],
|
|
162
|
+
{cwd:os.tmpdir(),stdio:['inherit','pipe','inherit'],encoding:'utf8',env:process.env});
|
|
163
|
+
if(result.error)error(result.error.message);
|
|
164
|
+
if(result.stdout)printLifecycle(result.stdout,command,json);
|
|
165
|
+
process.exit(result.status??2);
|
|
166
|
+
}
|
|
167
|
+
let publicConfig;
|
|
168
|
+
try{publicConfig=JSON.parse(fs.readFileSync(configFile,'utf8'));}
|
|
169
|
+
catch(e){error(e.code==='ENOENT'?`no configuration at ${configFile}; run redue init from the project root, or use --config FILE`:`cannot read ${configFile}: ${e.message}`);}
|
|
170
|
+
if(publicConfig.schema!==1)error('config schema must be 1');
|
|
171
|
+
if(!Array.isArray(publicConfig.checks)||!publicConfig.checks.length)
|
|
172
|
+
error('config needs at least one explicitly defined check');
|
|
173
|
+
for(const key of Object.keys(publicConfig))
|
|
174
|
+
if(!['schema','root','checks','packageManager'].includes(key))error(`unsupported config field ${key}`);
|
|
175
|
+
if(check&&!publicConfig.checks.some(row=>row.name===check))error(`unknown check ${check}; use redue status to list configured checks`);
|
|
176
|
+
const root=realObservedPath(path.resolve(path.dirname(configFile),publicConfig.root||'.'));
|
|
177
|
+
const state=stateOverride||projectState(root,configFile);
|
|
178
|
+
if(!/^(?:vstate|redue)-/.test(path.basename(state)))
|
|
179
|
+
error('--state-dir must name a dedicated redue-* directory outside the checkout (legacy vstate-* is also supported)');
|
|
180
|
+
const runtimeFile=path.join(state,'project-runtime-v1.json');
|
|
181
|
+
function which(name){
|
|
182
|
+
const found=findExecutable(name);
|
|
183
|
+
if(found)return found;
|
|
184
|
+
error(`cannot resolve executable ${name} on PATH`);
|
|
185
|
+
}
|
|
186
|
+
function token(value){
|
|
187
|
+
if(value==='@node')return process.execPath;
|
|
188
|
+
if(value==='@project')return root;
|
|
189
|
+
if(value==='@typescript-compiler:.'){
|
|
190
|
+
try{return directCompiler(root,{verify:false}).entry;}catch(e){error(`direct compiler unavailable: ${e.code||e.message}`);}
|
|
191
|
+
}
|
|
192
|
+
if(value==='@vstate/direct-typescript-probe')return path.join(productRoot,'src','direct-typescript-probe.mjs');
|
|
193
|
+
if(value.startsWith('@typescript-bin:')){
|
|
194
|
+
const dir=value.slice('@typescript-bin:'.length);
|
|
195
|
+
if(!dir||path.isAbsolute(dir)||dir.split('/').includes('..'))
|
|
196
|
+
error('TypeScript executable workspace path is unsafe');
|
|
197
|
+
const manifest=path.join(root,dir,'package.json');
|
|
198
|
+
let resolved;try{resolved=createRequire(manifest).resolve('typescript/package.json');}
|
|
199
|
+
catch{error('installed TypeScript compiler cannot be resolved');}
|
|
200
|
+
const compiler=path.join(path.dirname(realObservedPath(resolved)),'bin','tsc');
|
|
201
|
+
if(!withinPath(compiler,root)||!fs.existsSync(compiler))
|
|
202
|
+
error('TypeScript executable is outside the observed project');
|
|
203
|
+
return compiler;
|
|
204
|
+
}
|
|
205
|
+
if(value==='@vstate/npm-typecheck-probe')return path.join(productRoot,'src','npm-typecheck-probe.mjs');
|
|
206
|
+
if(value==='@vstate/typescript-contract-probe')return path.join(productRoot,'src','typescript-contract-probe.mjs');
|
|
207
|
+
if(value==='@vstate/yarn-workspace-typecheck-probe')return path.join(productRoot,'src','yarn-workspace-typecheck-probe.mjs');
|
|
208
|
+
if(value==='@vstate/pnpm-typecheck-probe')return path.join(productRoot,'src','pnpm-typecheck-probe.mjs');
|
|
209
|
+
if(value==='@vstate/toolchain-probe')return path.join(productRoot,'src','toolchain-probe.mjs');
|
|
210
|
+
if(value.startsWith('@which:'))return which(value.slice(7));
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
function argv(values,label){
|
|
214
|
+
if(!Array.isArray(values)||!values.length||values.some(v=>typeof v!=='string'||!v))
|
|
215
|
+
error(`${label} must be nonempty exact argv`);
|
|
216
|
+
return values.map(token);
|
|
217
|
+
}
|
|
218
|
+
const categories=['source','generated','installedDependencies','environment','toolchain','runtime',
|
|
219
|
+
'probeContinuity'];
|
|
220
|
+
const checks=publicConfig.checks.map(check=>{
|
|
221
|
+
const allowed=['name','command','cwd','inputs','generatedInputs','installedInputs',
|
|
222
|
+
'allowedExternalRoots','probes','environment','coverage','coverageReview','coverageReasons',
|
|
223
|
+
'script','kind','qualification','workspace'];
|
|
224
|
+
for(const key of Object.keys(check))if(!allowed.includes(key))
|
|
225
|
+
error(`unsupported check field ${check.name}.${key}`);
|
|
226
|
+
for(const category of categories)if(check.coverage?.[category]&&
|
|
227
|
+
typeof check.coverageReview?.[category]!=='string')
|
|
228
|
+
error(`${check.name}: reviewed coverage ${category} needs a rationale`);
|
|
229
|
+
const npmDirect=check.qualification===directQualification;
|
|
230
|
+
const pnpmTypecheck=check.qualification==='pnpm-tsc-v1';
|
|
231
|
+
if(check.script&&check.command&&!pnpmTypecheck&&!npmDirect)
|
|
232
|
+
error(`${check.name}: choose script or command, not both`);
|
|
233
|
+
const manager=publicConfig.packageManager;
|
|
234
|
+
if(check.script&&!['npm','yarn','pnpm'].includes(manager))
|
|
235
|
+
error(`${check.name}: script checks need supported npm, Yarn or pnpm packageManager`);
|
|
236
|
+
const scriptCommand=check.script&&!check.command?
|
|
237
|
+
[`@which:${manager}`,'run',check.script]:null;
|
|
238
|
+
const npmAutomatic=check.qualification==='typescript-noemit-v1';
|
|
239
|
+
const yarnWorkspace=check.qualification==='yarn-workspace-tsc-v1';
|
|
240
|
+
if(check.qualification&&!npmAutomatic&&!npmDirect&&!yarnWorkspace&&!pnpmTypecheck)
|
|
241
|
+
error(`${check.name}: unknown qualification contract`);
|
|
242
|
+
if(npmDirect&&(manager!=='npm'||!check.script||check.cwd&&check.cwd!=='.'||
|
|
243
|
+
JSON.stringify(check.command)!==JSON.stringify(['@node','@typescript-compiler:.','--noEmit'])))
|
|
244
|
+
error(`${check.name}: direct recipe requires its explicit local compiler command`);
|
|
245
|
+
if(npmAutomatic&&(manager!=='npm'||check.cwd&&check.cwd!=='.'))
|
|
246
|
+
error(`${check.name}: automatic TypeScript qualification currently needs root npm script`);
|
|
247
|
+
if(yarnWorkspace&&(manager!=='yarn'||!check.workspace||check.cwd&&check.cwd!=='.'||
|
|
248
|
+
check.command?.length!==5||check.command[0]!=='@which:yarn'||
|
|
249
|
+
check.command[1]!=='workspace'||check.command[2]!==check.workspace||
|
|
250
|
+
check.command[3]!=='run'))
|
|
251
|
+
error(`${check.name}: Yarn workspace qualification needs its discovered workspace command`);
|
|
252
|
+
if(pnpmTypecheck&&(manager!=='pnpm'||!check.script||!check.command||
|
|
253
|
+
check.command[0]!=='@node'||
|
|
254
|
+
check.command[1]!==`@typescript-bin:${check.cwd||'.'}`||
|
|
255
|
+
check.workspace&&check.cwd==='.'||
|
|
256
|
+
!check.command.slice(2).every(value=>typeof value==='string'&&value)))
|
|
257
|
+
error(`${check.name}: pnpm qualification needs its discovered standalone TypeScript command`);
|
|
258
|
+
const reason=check.kind==='test'?'test runtime, transforms and cache inputs need review':
|
|
259
|
+
check.kind==='lint'?'lint plugins, config and resolver inputs need review':
|
|
260
|
+
check.kind==='build'?'build environment and generated inputs need review':
|
|
261
|
+
'check inputs need review';
|
|
262
|
+
const autoProbes=npmDirect?[['@node','@vstate/direct-typescript-probe','@project',check.script]]:npmAutomatic?[['@node','@vstate/typescript-contract-probe',
|
|
263
|
+
'@project',check.script,'@which:npm']]:yarnWorkspace?
|
|
264
|
+
[['@node','@vstate/yarn-workspace-typecheck-probe','@project',
|
|
265
|
+
check.workspace,check.command[4],'@which:yarn']]:pnpmTypecheck?
|
|
266
|
+
[['@node','@vstate/pnpm-typecheck-probe','@project',
|
|
267
|
+
check.workspace||'.',check.script]]:[];
|
|
268
|
+
const windowsContext=process.platform==='win32'?
|
|
269
|
+
['ComSpec','COMSPEC','PATHEXT','USERPROFILE']:[];
|
|
270
|
+
const autoEnvironment=npmDirect?directEnvironment:npmAutomatic?{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME',
|
|
271
|
+
'NODE_ENV','BASH_ENV','ENV',...windowsContext],prefixes:['npm_config_','DYLD_','TSGO_'],
|
|
272
|
+
pathExecutables:['node','npm'],executableIdentity:true}:yarnWorkspace?
|
|
273
|
+
{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME','NODE_ENV','BASH_ENV','ENV',...windowsContext],
|
|
274
|
+
prefixes:['YARN_','COREPACK_','npm_config_','DYLD_','TSGO_'],
|
|
275
|
+
pathExecutables:['node','yarn'],executableIdentity:true}:pnpmTypecheck?
|
|
276
|
+
{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME','NODE_ENV','BASH_ENV','ENV',...windowsContext],
|
|
277
|
+
prefixes:['PNPM_','COREPACK_','npm_config_','DYLD_','TSGO_'],
|
|
278
|
+
pathExecutables:['node'],executableIdentity:true}:{};
|
|
279
|
+
return {name:check.name,command:argv(scriptCommand||check.command,`${check.name}.command`),
|
|
280
|
+
cwd:check.cwd,inputs:check.inputs||[],generatedInputs:check.generatedInputs||[],
|
|
281
|
+
installedInputs:check.installedInputs||(
|
|
282
|
+
npmAutomatic||npmDirect?['node_modules/**']:[]),
|
|
283
|
+
allowedExternalRoots:check.allowedExternalRoots||[],
|
|
284
|
+
probes:[...(check.probes||[]),...autoProbes]
|
|
285
|
+
.map((p,i)=>argv(p,`${check.name}.probes[${i}]`)),
|
|
286
|
+
environment:check.environment||autoEnvironment,coverage:check.coverage||{},
|
|
287
|
+
coverageReview:check.coverageReview||{},
|
|
288
|
+
coverageReasons:check.coverageReasons||Object.fromEntries(
|
|
289
|
+
categories.map(category=>[category,reason])),
|
|
290
|
+
qualification:check.qualification||null,script:check.script||null,
|
|
291
|
+
...(npmAutomatic?{qualificationInterpretation:'npm-launcher-withheld@1'}:{}),
|
|
292
|
+
...(npmDirect?{qualificationInterpretation:directInterpretation}:{}),
|
|
293
|
+
workspace:check.workspace||null,kind:check.kind||null};
|
|
294
|
+
});
|
|
295
|
+
const runtime={schema:1,provider:'declared-project@1',root,state,checks};
|
|
296
|
+
try{assertOwnedStatePlacement(runtime);}catch(e){error(e.message);}
|
|
297
|
+
const serialized=JSON.stringify(runtime,null,2)+'\n';
|
|
298
|
+
const maintenance=command==='stop'||command==='remove-state';
|
|
299
|
+
if(maintenance&&!fs.existsSync(runtimeFile)){
|
|
300
|
+
console.log(`No REDUE state exists for ${configFile}.`);
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
if(!maintenance)fs.mkdirSync(state,{recursive:true,mode:0o700});
|
|
304
|
+
if(!maintenance&&fs.existsSync(runtimeFile)){
|
|
305
|
+
const previous=fs.readFileSync(runtimeFile,'utf8');
|
|
306
|
+
if(previous!==serialized){
|
|
307
|
+
const pidFile=path.join(state,'observer.lock','pid');
|
|
308
|
+
if(fs.existsSync(pidFile)){
|
|
309
|
+
const pid=Number(fs.readFileSync(pidFile));
|
|
310
|
+
if(processAlive(pid))error('config changed while observer is running; stop it before restarting with the new plan');
|
|
311
|
+
}
|
|
312
|
+
fs.writeFileSync(runtimeFile,serialized,{mode:0o600});
|
|
313
|
+
}
|
|
314
|
+
}else if(!maintenance)fs.writeFileSync(runtimeFile,serialized,{flag:'wx',mode:0o600});
|
|
315
|
+
|
|
316
|
+
if(command==='status'&&!sync){
|
|
317
|
+
const value=readCachedStatus(state,publicConfig.checks);
|
|
318
|
+
console.log(json?JSON.stringify(value):renderStatus(value,{short}));
|
|
319
|
+
process.exit(0);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const details=command==='detail'||command==='explain';
|
|
323
|
+
const internal=command==='remove-state'?'uninstall':
|
|
324
|
+
details?'sync':command==='status'&&sync?'sync':command;
|
|
325
|
+
if(command==='run'){
|
|
326
|
+
const child=spawn(process.execPath,[engine,runtimeFile,internal,check,...(!json?['--human']:[])],
|
|
327
|
+
{stdio:['inherit','inherit','inherit','ipc'],env:process.env});
|
|
328
|
+
const forward=signal=>{if(child.exitCode!==null)return;
|
|
329
|
+
if(process.platform==='win32'&&child.connected)child.send({action:'cancel',signal});
|
|
330
|
+
else child.kill(signal);};
|
|
331
|
+
const interrupt=()=>forward('SIGINT'),terminate=()=>forward('SIGTERM');
|
|
332
|
+
process.on('SIGINT',interrupt);process.on('SIGTERM',terminate);
|
|
333
|
+
const outcome=await new Promise(resolve=>{
|
|
334
|
+
child.once('error',error=>resolve({error}));
|
|
335
|
+
child.once('close',(status,signal)=>resolve({status,signal}));
|
|
336
|
+
});
|
|
337
|
+
process.off('SIGINT',interrupt);process.off('SIGTERM',terminate);
|
|
338
|
+
if(outcome.error)error(outcome.error.message);
|
|
339
|
+
process.exit(outcome.status??(outcome.signal?128+os.constants.signals[outcome.signal]:1));
|
|
340
|
+
}
|
|
341
|
+
mark('client.engine_submit');
|
|
342
|
+
const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
|
|
343
|
+
{stdio:['inherit','pipe','inherit'],
|
|
344
|
+
encoding:'utf8',env:traceEnvironment()});
|
|
345
|
+
mark('client.engine_complete',{exit:result.status,signal:result.signal});
|
|
346
|
+
if(result.error)error(result.error.message);
|
|
347
|
+
if(result.stdout){
|
|
348
|
+
if(['status','detail','explain'].includes(command)){
|
|
349
|
+
let value;try{value=JSON.parse(result.stdout);}catch{error('status response could not be read; applicability is unknown');}
|
|
350
|
+
if(details&&check)value={...value,checks:value.checks.filter(row=>row.name===check)};
|
|
351
|
+
if(json)console.log(JSON.stringify(value));
|
|
352
|
+
else console.log(details?renderExplain(value,check):renderStatus(value,{short}));
|
|
353
|
+
}else printLifecycle(result.stdout,command,json);
|
|
354
|
+
}
|
|
355
|
+
process.exit(result.status??1);
|
package/bin/vstate.mjs
ADDED
package/docs/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
For users:
|
|
4
|
+
|
|
5
|
+
- [Install, initialize, run, and remove](user/getting-started.md)
|
|
6
|
+
- [Fixed-build opt-in development pilot](user/pilot.md)
|
|
7
|
+
- [Supported projects and conservative boundaries](user/supported-projects.md)
|
|
8
|
+
- [Configuration](user/configuration.md)
|
|
9
|
+
- [Connect coding agents and apply the decision policy](user/agents.md)
|
|
10
|
+
- [JSON contract](user/json.md)
|
|
11
|
+
- [Shell usage](user/shell.md)
|
|
12
|
+
|
|
13
|
+
For contributors:
|
|
14
|
+
|
|
15
|
+
- [Architecture](contributor/architecture.md)
|
|
16
|
+
- [Observation and recovery](contributor/observation-recovery.md)
|
|
17
|
+
- [Reproducible terminal demo](contributor/demo.md)
|
|
18
|
+
- [Release procedure](contributor/releasing.md)
|
|
19
|
+
|
|
20
|
+
[Historical acceptance evidence](acceptance/README.md) is retained separately.
|
|
21
|
+
It records measured environments and limitations; it is not the getting-started guide.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Agent decision policy
|
|
2
|
+
|
|
3
|
+
Use the CLI with explicit project-local activation below, or add this policy to a
|
|
4
|
+
generic agent's instructions with the project owner's permission.
|
|
5
|
+
|
|
6
|
+
1. If fresh execution is already required, execute it; do not synchronize merely
|
|
7
|
+
to confirm an irrelevant reuse opportunity. Use `redue run CHECK` when recording
|
|
8
|
+
the configured check. An explicit npm-script request still requires npm.
|
|
9
|
+
2. For reuse of the explicitly configured direct compiler recipe
|
|
10
|
+
(`qualification: "npm-typescript-direct-v1"`, direct compiler command; receipt
|
|
11
|
+
`verification_recipe: "npm-direct-typescript@1"`), query once:
|
|
12
|
+
`redue --config CONFIG status --sync --json`. Do not infer a recipe from its name.
|
|
13
|
+
Cached status cannot establish its caller context. In mixed configurations,
|
|
14
|
+
assess each check's contract separately; this is not a global sync-first policy.
|
|
15
|
+
3. For other/unknown recipes, query ordinary `status --json` first and decide whether
|
|
16
|
+
synchronization is worthwhile. Ordinary status and `--short` remain cheap inspection.
|
|
17
|
+
4. Reuse only schema 1, healthy observation and the selected check's CURRENT/PASS
|
|
18
|
+
with `reuse_eligible: true`. Missing eligibility means false. A historical FAIL
|
|
19
|
+
is never green evidence. Do not rerun eligible evidence merely for freshness.
|
|
20
|
+
5. STALE requires execution when verification is needed. UNVERIFIED requires an
|
|
21
|
+
honest choice of synchronization, permitted execution or reporting the limitation.
|
|
22
|
+
Recording-only success can remain UNVERIFIED. A deliberate cheap-check rerun is
|
|
23
|
+
permitted; there are no universal platform/package-manager timing thresholds.
|
|
24
|
+
6. Use a successful synchronized response for the current decision without repeated
|
|
25
|
+
sync/explain calls unless edits, context changes or missing information require it.
|
|
26
|
+
Do not loop until green or use an earlier response after a later query fails.
|
|
27
|
+
|
|
28
|
+
`redue explain CHECK --json` gives synchronized detail, including a known changed
|
|
29
|
+
input when available. It has the same cost caveat as synchronized status.
|
|
30
|
+
|
|
31
|
+
No previous conversation history is needed to read a persisted receipt. Still
|
|
32
|
+
consider independent task-specific reasons to run verification: the configured
|
|
33
|
+
check is not universal product correctness. Report additional uncaptured checks
|
|
34
|
+
separately. Do not infer that an outer invocation's success proves a specific
|
|
35
|
+
check ran or that an upstream cache was hit; inspect `target_provenance`.
|
|
36
|
+
|
|
37
|
+
Reassess at the decision point after edits or context changes; previous CURRENT is
|
|
38
|
+
not continuing authorization. A status exit code of zero is not proof of CURRENT.
|
|
39
|
+
An empty changed-input list does not prove nothing changed. `run CHECK --json`
|
|
40
|
+
streams check output; use a subsequent status/explain query for structured evidence.
|
|
41
|
+
Explicit fresh-execution requests still require execution.
|
|
42
|
+
|
|
43
|
+
On a CLI error, unhealthy observation, or unavailable context, do not infer
|
|
44
|
+
CURRENT. Do not delete state or reset receipts to make the output green.
|
|
45
|
+
|
|
46
|
+
REDUE tells you what evidence exists and whether it still applies. It does not
|
|
47
|
+
promise that establishing applicability is cheaper than every possible check.
|
|
48
|
+
|
|
49
|
+
## Connect once (experimental)
|
|
50
|
+
|
|
51
|
+
Alpha.5 and later support opt-in project-local activation and direct
|
|
52
|
+
TypeScript evidence, with fresh-session reuse demonstrated for Codex CLI 0.160.1
|
|
53
|
+
and subsequent 0.162.0 trials on macOS under the documented owner-approved checkout/state/socket route. CLI
|
|
54
|
+
mechanics are tested on macOS/Linux/Windows; model behavior is not claimed for
|
|
55
|
+
every host/platform or sandbox policy. Claude setup/removal mechanics are tested,
|
|
56
|
+
but Claude behavior and cross-host reuse remain untested. Reuse is not guaranteed,
|
|
57
|
+
and the existing subsecond example establishes no meaningful net time saving.
|
|
58
|
+
|
|
59
|
+
After installing REDUE and running `redue init` in a trusted project:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
redue agent setup codex --dry-run
|
|
63
|
+
redue agent setup codex --apply
|
|
64
|
+
# Or: redue agent setup claude --apply
|
|
65
|
+
redue agent doctor
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Start a fresh agent session in this checkout, then give it your ordinary task.
|
|
69
|
+
Interactive setup shows the changes and asks before applying; noninteractive
|
|
70
|
+
setup requires `--apply`. `--dry-run` never writes. Installing REDUE or running
|
|
71
|
+
`init` does not connect an agent automatically. Setup does not start an observer
|
|
72
|
+
or execute verification. Use `redue start` when you want observation.
|
|
73
|
+
|
|
74
|
+
## Update an existing connection
|
|
75
|
+
|
|
76
|
+
Installing a newer package does **not** rewrite project instructions. From the
|
|
77
|
+
same project and installation, stop every observer using that installation before
|
|
78
|
+
replacing it. Keep the original config and state selection; do not create a second
|
|
79
|
+
config or delete receipts. Example for a default npm installation/config:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
redue --config redue.config.json stop
|
|
83
|
+
npm install --global @redue/cli@alpha
|
|
84
|
+
redue --config redue.config.json agent setup codex --dry-run
|
|
85
|
+
redue --config redue.config.json agent setup codex --apply
|
|
86
|
+
redue --config redue.config.json start
|
|
87
|
+
redue --config redue.config.json status --sync --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use the **same `--prefix`** as the existing npm installation when it has a custom
|
|
91
|
+
prefix, and invoke its corresponding `redue` binary. Keep your actual config path
|
|
92
|
+
on every command. For Claude, select `claude` in both setup commands. Preserve any
|
|
93
|
+
existing `--state-dir` on lifecycle/status commands; setup does not embed custom
|
|
94
|
+
state locations, so retain the documented generic instruction path if your agent
|
|
95
|
+
workflow needs an explicit custom state. PowerShell may use the existing
|
|
96
|
+
`npm.cmd`/`redue.cmd` shims without changing script policy.
|
|
97
|
+
|
|
98
|
+
Review the preview before applying. Intact owned content updates; unrelated text
|
|
99
|
+
is preserved. Edited managed content is refused, not overwritten. If refused,
|
|
100
|
+
review/reconcile those edits deliberately; do not remove ownership records to force
|
|
101
|
+
an update. Restart the observer with its original selection even if you postpone
|
|
102
|
+
updating instructions, then start a **fresh agent session** after a successful
|
|
103
|
+
update. Setup creates no verification receipt.
|
|
104
|
+
|
|
105
|
+
Managed project metadata can itself be an observed input. Prior receipts remain
|
|
106
|
+
historical evidence, but setup may make them STALE or UNVERIFIED. Reassess normally
|
|
107
|
+
and run required checks; an upgrade is not a promise that existing receipts remain
|
|
108
|
+
CURRENT. Do not suppress that invalidation or reset state.
|
|
109
|
+
|
|
110
|
+
Codex gets a short managed section in project `AGENTS.md` and
|
|
111
|
+
`.agents/skills/redue-verification/SKILL.md`. Claude Code gets a managed section
|
|
112
|
+
in `CLAUDE.md` and `.claude/skills/redue-verification/SKILL.md`. Each uses the same
|
|
113
|
+
policy source; detailed guidance is loaded when verification is relevant. Small
|
|
114
|
+
ownership records in `.redue/agents/` support safe updates/removal. These are
|
|
115
|
+
portable project files, not receipts; review and commit them if the team wants
|
|
116
|
+
this integration. Do not ignore just the ownership records while committing the
|
|
117
|
+
managed files.
|
|
118
|
+
|
|
119
|
+
Existing instruction text is retained. Review it in the preview: setup cannot
|
|
120
|
+
prove arbitrary natural-language policies are consistent. Conflicts such as a
|
|
121
|
+
root `AGENTS.override.md` or an alternative `.claude/CLAUDE.md` are reported
|
|
122
|
+
instead of silently outranked. Global/managed policies, nested instructions,
|
|
123
|
+
Codex instruction-size limits, skill disabling, and host-specific exclusions
|
|
124
|
+
still apply. Complete the host's ordinary trust/permission prompts yourself;
|
|
125
|
+
REDUE never grants tool permission. Sandboxed hosts may need owner-approved access
|
|
126
|
+
to the checkout, owned state directory and local observer socket. A healthy daemon
|
|
127
|
+
does not establish that the agent can reach it; doctor reports control access
|
|
128
|
+
separately. Do not disable sandbox protections to make this work. Do not use a mode that disables project
|
|
129
|
+
instructions and expect automatic activation.
|
|
130
|
+
|
|
131
|
+
Control access is not simply file-read permission. The observer can reconcile and
|
|
132
|
+
write its owned index, run the trusted configuration's probes in its own process
|
|
133
|
+
context, reload receipts, and stop. Approve only the intended checkout/state and
|
|
134
|
+
endpoint, accounting for that authority. Doctor sends only a bounded metrics
|
|
135
|
+
request; it does not synchronize or run these probes.
|
|
136
|
+
|
|
137
|
+
`agent doctor [codex|claude] --json` is read-only. It distinguishes command lookup,
|
|
138
|
+
config resolution, cached observer health, a bounded read-only control-access probe,
|
|
139
|
+
managed-file integrity, detected host
|
|
140
|
+
version, and loading guidance. `behavior_verified: false` is deliberate: intact
|
|
141
|
+
files do not prove the agent followed them. No hooks are installed. Doctor neither
|
|
142
|
+
executes checks nor starts observers, reconciles inputs, or installs a host.
|
|
143
|
+
|
|
144
|
+
Doctor separates missing/unreadable state, a missing or refused endpoint, an
|
|
145
|
+
explicit EPERM/EACCES access denial, and timeout/invalid response. A denial does
|
|
146
|
+
not identify whether OS permissions or host policy caused it. A live PID and
|
|
147
|
+
healthy cached observation do not prove control access. An older cached response
|
|
148
|
+
without receipt-selection validation gets stop/start guidance. Windows command
|
|
149
|
+
shims whose target has not been established are reported as unknown, not a
|
|
150
|
+
proven installation mismatch.
|
|
151
|
+
|
|
152
|
+
Codex CLI 0.160.1 on macOS can use the approved exact observer-socket route while
|
|
153
|
+
other sockets and direct network access remain denied. Its independent sessions
|
|
154
|
+
receive different proxy values. Ordinary npm invocation is **not** equivalent
|
|
155
|
+
across arbitrary notifier state: REDUE withholds automatic npm-launcher
|
|
156
|
+
reuse. Do not change proxy settings or permissions to get green evidence.
|
|
157
|
+
|
|
158
|
+
The separate opt-in [direct local compiler recipe](direct-typescript.md) does not
|
|
159
|
+
run npm. Its reviewed compiler implementation does not consume npm/proxy settings;
|
|
160
|
+
a digest preserves their observed provenance without treating them as compiler
|
|
161
|
+
inputs. Other execution context remains strict, and cached status still cannot
|
|
162
|
+
assert caller eligibility. An explicit user request for `npm run typecheck` still
|
|
163
|
+
requires npm execution; direct compiler evidence is not a substitute.
|
|
164
|
+
|
|
165
|
+
Before model trials, run `redue agent doctor --json` and
|
|
166
|
+
`redue status --sync --json` inside the agent's actual command boundary. The
|
|
167
|
+
contributor helper `test/acceptance/codex-preflight.mjs --config FILE --check CHECK`
|
|
168
|
+
performs the same bounded, read-only decision check and exits 2 when reuse is not
|
|
169
|
+
eligible. It neither executes verification nor grants access; synchronization
|
|
170
|
+
may run configured probes. A successful outside-shell result is insufficient.
|
|
171
|
+
|
|
172
|
+
Use the same setup command after an upgrade; it updates intact managed
|
|
173
|
+
content idempotently. Changed skill text or a changed managed block causes a
|
|
174
|
+
conflict instead of an overwrite. Review your edits before retrying.
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
redue agent remove codex --dry-run
|
|
178
|
+
redue agent remove codex --apply
|
|
179
|
+
redue agent remove claude --apply
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Removal deletes only intact REDUE-owned content for that host. Unrelated text,
|
|
183
|
+
other integrations, project config, receipts and installed dependencies remain.
|
|
184
|
+
User edits outside the managed section survive. Modified managed content is left
|
|
185
|
+
intact with a diagnostic; compare it with a preview and resolve manually. Empty
|
|
186
|
+
parent directories may remain. If the selected config has been deleted, restore
|
|
187
|
+
it or manually remove only the marked REDUE section, that host's skill, and its
|
|
188
|
+
`.redue/agents/HOST.json` ownership record after review. Do not delete an entire
|
|
189
|
+
instruction file containing user content.
|
|
190
|
+
|
|
191
|
+
## Project identity and generic agents
|
|
192
|
+
|
|
193
|
+
Commands from nested directories search toward the nearest Git root. If more than
|
|
194
|
+
one configuration applies, select one with `--config FILE`; REDUE refuses to guess.
|
|
195
|
+
The generated instruction names its project-relative configuration and applies it
|
|
196
|
+
to status/run/explain. External configs or custom machine-local `--state-dir`
|
|
197
|
+
setups should use a reviewed generic instruction instead of committing absolute
|
|
198
|
+
paths. Other worktrees and remote containers have separate local evidence.
|
|
199
|
+
|
|
200
|
+
For another agent, put a short instruction in its documented project mechanism:
|
|
201
|
+
“Before deciding whether configured verification needs repeating, read REDUE's
|
|
202
|
+
agent policy, identify the selected recipe and use its decision query for the intended config.
|
|
203
|
+
Apply the schema/health/reuse rules.” Supply this document and the
|
|
204
|
+
[JSON contract](json.md) as the detailed policy. Review the agent's actual tool
|
|
205
|
+
trace before calling activation verified. A local npm installation does not install
|
|
206
|
+
REDUE in a remote container; that environment needs its own CLI, checkout,
|
|
207
|
+
dependencies and state. There is no cloud/team receipt synchronization.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Prefer `redue init` over hand-authoring a contract. An ordinary generated npm script (recording-only in this candidate)
|
|
4
|
+
configuration is concise and portable:
|
|
5
|
+
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"schema": 1,
|
|
9
|
+
"root": ".",
|
|
10
|
+
"packageManager": "npm",
|
|
11
|
+
"checks": [{
|
|
12
|
+
"name": "typecheck",
|
|
13
|
+
"script": "typecheck",
|
|
14
|
+
"kind": "typecheck",
|
|
15
|
+
"qualification": "typescript-noemit-v1",
|
|
16
|
+
"inputs": ["package.json", "package-lock.json", "tsconfig*.json", "src/**"]
|
|
17
|
+
}]
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The exact inputs are derived from the detected project; use the emitted config,
|
|
22
|
+
not this example to assert your project's coverage. Generated Yarn/pnpm workspace
|
|
23
|
+
checks also identify the owning workspace and actual command. Large dependency
|
|
24
|
+
relationships stay in local runtime state. `qualification` selects a supported
|
|
25
|
+
validation recipe; it is not a blanket coverage assertion.
|
|
26
|
+
|
|
27
|
+
Use `--config FILE` to select an external configuration. `root` is relative to
|
|
28
|
+
that file. Runtime paths, resolved executables, local evidence, and sockets stay
|
|
29
|
+
out of version-controlled configuration.
|
|
30
|
+
|
|
31
|
+
For the explicit direct compiler choice and generated exact argv, see
|
|
32
|
+
[direct TypeScript](direct-typescript.md). Existing script receipts cannot satisfy it.
|
|
33
|
+
|
|
34
|
+
## Arbitrary commands
|
|
35
|
+
|
|
36
|
+
An unrecognized ecosystem can still record ordinary execution:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"schema": 1,
|
|
41
|
+
"checks": [{
|
|
42
|
+
"name": "migration",
|
|
43
|
+
"command": ["@node", "tools/check-migrations.mjs"],
|
|
44
|
+
"inputs": ["migrations/**", "tools/check-migrations.mjs"]
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Commands are exact argv, not shell text. Use `cwd` for a project-relative working
|
|
50
|
+
directory. `@node`, `@project`, and `@which:NAME` resolve explicit invocation facts.
|
|
51
|
+
Manual checks start recording-only: a plausible list of files is not proof of a
|
|
52
|
+
complete input boundary. Do not copy coverage assertions from unrelated projects.
|
|
53
|
+
|
|
54
|
+
Advanced explicit contracts may declare `generatedInputs`, `installedInputs`, narrow
|
|
55
|
+
`allowedExternalRoots`, read-only `probes`, and relevant `environment`. These are
|
|
56
|
+
reviewed escape hatches, not onboarding requirements. A coverage rationale cannot
|
|
57
|
+
override unsupported storage, unresolved links, missing inputs, or observation failure.
|
|
58
|
+
|
|
59
|
+
Changing commands, relevant configuration, installed material, or consumed inputs
|
|
60
|
+
invalidates prior applicability. It does not erase historical execution outcomes.
|