@redue/cli 0.1.0-alpha.20 → 0.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.21 — 2026-10-25
4
+
5
+ `init` finds more typecheck scripts and no longer stops in large monorepos.
6
+ In the census of 108 public TypeScript repos, the general recipe qualifies 26
7
+ typecheck scripts (was 23) and 20 lint scripts.
8
+
9
+ - `init` prefers typecheck scripts named `tsc`, `types` or `lint:types`, then
10
+ takes one script named under a typecheck name (`typecheck:test`,
11
+ `tsc:build`, ...), watch scripts aside. Two such scripts are reported as
12
+ ambiguous unless a command alone decides. Scripts named `build` or `dev`
13
+ that run tsc are not taken: they write output.
14
+ - A workspace of more than 50 packages no longer stops `init`. It reads up to
15
+ 1000 packages, proposes the root's checks, and past 50 packages skips
16
+ per-package checks and says so (`skipped` in `--json`, a "Skipped:" line
17
+ otherwise). Five census repos got no check at all before.
18
+ - General recipe: pnpm 12 writes its workspace state through a temporary
19
+ `node_modules/.tmpXXXXXX` file. Like the state file, it counts as the run's
20
+ output when the pnpm executable ran. Known limit: the agent eval's seed lint
21
+ run on one pnpm 12 project is still UNVERIFIED for an input event REDUE does
22
+ not yet name.
23
+
24
+ - Contributors: the agent eval records each run's seed and final receipt,
25
+ and the files a session changed when its seed run is not stable.
26
+
3
27
  ## 0.1.0-alpha.20 — 2026-10-24
4
28
 
5
29
  A fix for agents on macOS.
package/bin/redue.mjs CHANGED
@@ -120,7 +120,7 @@ if(command==='init'){
120
120
  package_manager:discovery.manager.name,version:discovery.manager.version,
121
121
  installation_layout:discovery.manager.layout,
122
122
  issue:discovery.observationIssue||discovery.manager.issue,workspaces:discovery.workspaces,
123
- ambiguous:discovery.ambiguous,
123
+ ambiguous:discovery.ambiguous,skipped:discovery.skipped||[],
124
124
  checks:discovery.checks.map(({config:check,level,reason})=>({name:check.name,
125
125
  script:check.script||null,command:check.command||null,
126
126
  workspace:check.workspace||null,level,reason})),config:configFile,
@@ -139,6 +139,7 @@ if(command==='init'){
139
139
  }
140
140
  for(const row of summary.ambiguous)
141
141
  console.log(` ${row.kind}: ambiguous scripts (${row.scripts.join(', ')}); choose one explicitly`);
142
+ for(const row of summary.skipped)console.log(` Skipped: ${row.reason}`);
142
143
  if(!summary.checks.length)console.log('No useful existing verification scripts found.');
143
144
  console.log(configured?`Existing ${configFile} left unchanged.`:
144
145
  dryRun?`Preview only; ${configFile} was not written.`:
@@ -67,13 +67,14 @@ UNVERIFIED. At the top of each `node_modules`, only `.cache`, `.vite`,
67
67
  `.vite-temp`, `.vitest` and `.vitest-cache` are left out, and `*.tsbuildinfo`
68
68
  anywhere in it.
69
69
 
70
- Two installed files are rewritten by tools during a run: pnpm's
70
+ Two installed files are rewritten by tools during a run. pnpm rewrites
71
71
  `node_modules/.pnpm-workspace-state-v1.json` at the first run after an
72
- install, and the `node_modules/.vue-global-types/*.d.ts` files vue-tsc writes
73
- before it reads them. They stay inputs. When they are the only files a run
74
- changed, and the run wrote them (a recorded process wrote the file, or the
75
- pnpm executable ran, for its state), the result is kept with the files as the
76
- run left them, and a later change to them makes it STALE.
72
+ install, through a temporary `node_modules/.tmpXXXXXX` file. vue-tsc writes
73
+ `node_modules/.vue-global-types/*.d.ts` before it reads them. These files stay
74
+ inputs. When they are the only files a run changed, and the run wrote them,
75
+ the result is kept with the files as the run left them. A recorded process
76
+ counts as writing a file it wrote; for pnpm's files, the pnpm executable must
77
+ have run. A later change to them makes the result STALE.
77
78
 
78
79
  It does not mean a rerun would give the same result: a test that depends on
79
80
  the time or on randomness can pass now and fail later on the same inputs.
@@ -12,7 +12,10 @@ is known. When npm is not pinned in `package.json`,
12
12
  leaves the version unknown rather than guessing if that inspection fails.
13
13
 
14
14
  At the repository root, `init` looks for existing typecheck, test, lint, and
15
- build scripts. It prefers familiar script names, then unique recognizable
15
+ build scripts. It prefers familiar script names (for typecheck: `typecheck`,
16
+ `type-check`, `check-types`, `check:types`, `types:check`, `tsc`, `types`,
17
+ `lint:types`), then for typecheck one script named under such a name
18
+ (`typecheck:test`, `tsc:build`; not a watch script), then unique recognizable
16
19
  `tsc`, Vitest/Jest, ESLint, or Vite commands under other names. Multiple
17
20
  matches are reported as ambiguous rather than guessed. It never creates a new
18
21
  project script. Without `--recipe`, `init` tries the strict recipes below first,
@@ -30,7 +33,9 @@ not npm. Full installed compiler identity, config/membership, installed inputs
30
33
  and relevant Node environment are checked before reuse. See the
31
34
  [exact direct boundary](direct-typescript.md). Unsupported plugins, preloads,
32
35
  configuration or outside-root inputs remain UNVERIFIED. Root and package-level npm/Yarn workspace scripts are discovered from
33
- `package.json` workspace declarations. Pinned Yarn 4 `node-modules`/classic
36
+ `package.json` workspace declarations. In a workspace of more than 50
37
+ packages, `init` proposes the root's checks only and names what it skipped;
38
+ add package checks to the config yourself. Up to 1000 packages are read. Pinned Yarn 4 `node-modules`/classic
34
39
  workspaces with a standalone `tsc -p .` script, local TypeScript, a conventional
35
40
  `src` include, and Node module resolution can qualify automatically. For one
36
41
  workspace, use `redue init --workspace NAME --check typecheck`; it writes only
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redue/cli",
3
3
  "private": false,
4
- "version": "0.1.0-alpha.20",
4
+ "version": "0.1.0-alpha.21",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
@@ -4,6 +4,7 @@ import {commandLineIssue,scriptCheckInputs,scriptPath,scriptProject,scriptQualif
4
4
  // Init proposes the general recipe for typecheck and lint scripts; test
5
5
  // scripts use the direct test recipe (Vitest, Jest).
6
6
  const generalKinds=['typecheck','lint'];
7
+ const perPackageLimit=50;
7
8
  import fs from 'node:fs';
8
9
  import path from 'node:path';
9
10
  import {spawnSync} from 'node:child_process';
@@ -17,7 +18,7 @@ import {partName,tscRuns} from './typescript-invocation.mjs';
17
18
  const exists=file=>{try{fs.accessSync(file);return true;}catch{return false;}};
18
19
  const readJson=file=>JSON.parse(fs.readFileSync(file,'utf8'));
19
20
  const scriptNames={
20
- typecheck:['typecheck','type-check','check:types','types:check','check-types'],
21
+ typecheck:['typecheck','type-check','check:types','types:check','check-types','tsc','types','lint:types'],
21
22
  test:['test','test:unit','test:check'],
22
23
  lint:['lint','lint:check'],
23
24
  build:['build'],
@@ -160,10 +161,16 @@ function classify(kind,scriptName,script,pkg,managerInfo,root) {
160
161
  'Vite detected; build environment and generated inputs need review':
161
162
  'build script detected; generated and runtime inputs need review'};
162
163
  }
164
+ // Typecheck scripts named under a typecheck name (`typecheck:test`,
165
+ // `tsc:build`), watch scripts aside.
166
+ const typecheckFamily=/^(?:typecheck|type-check|check-types|check:types|types:check|tsc):(?!.*watch)/;
163
167
  function selectScript(kind,names,scripts) {
164
168
  const preferred=names.find(name=>typeof scripts[name]==='string'&&
165
169
  scripts[name].trim()&&!/^echo\s+["']?Error: no test specified/.test(scripts[name]));
166
170
  if(preferred)return {script:preferred};
171
+ const family=kind==='typecheck'?Object.keys(scripts).filter(name=>typecheckFamily.test(name)&&
172
+ typeof scripts[name]==='string'&&scripts[name].trim()&&!/(?:^|\s)(?:--watch|-w)(?:\s|$)/.test(scripts[name])):[];
173
+ if(family.length===1)return {script:family[0]};
167
174
  const candidates=Object.keys(scripts).filter(name=>{
168
175
  const value=scripts[name]?.trim();
169
176
  if(typeof value!=='string')return false;
@@ -177,7 +184,7 @@ function selectScript(kind,names,scripts) {
177
184
  const exact=kind==='typecheck'?candidates.filter(name=>exactTypeScript(scripts[name])):[];
178
185
  if(exact.length===1)return {script:exact[0]};
179
186
  return candidates.length===1?{script:candidates[0]}:
180
- candidates.length>1?{ambiguous:candidates}:{};
187
+ candidates.length>1?{ambiguous:candidates}:family.length>1?{ambiguous:family}:{};
181
188
  }
182
189
  // A pnpm workspace package's TypeScript run, as its check's command, cwd and inputs.
183
190
  function pnpmCheck(root,name,dir,manifest,run){
@@ -233,7 +240,7 @@ export function discoverNodeProject(root,{recipe=null}={}) {
233
240
  workspacePatterns.some(value=>typeof value!=='string'||path.isAbsolute(value)||
234
241
  value.split('/').includes('..'))))
235
242
  throw Error('workspace patterns need a supported project-relative package list');
236
- const checks=[],ambiguous=[];
243
+ const checks=[],ambiguous=[],skipped=[];
237
244
  for(const [kind,names] of Object.entries(scriptNames)) {
238
245
  const selected=selectScript(kind,names,scripts),script=selected.script;
239
246
  if(selected.ambiguous)ambiguous.push({kind,scripts:selected.ambiguous});
@@ -304,8 +311,11 @@ export function discoverNodeProject(root,{recipe=null}={}) {
304
311
  checks.push({config:check,...classification});
305
312
  }
306
313
  if(workspaces&&!observationIssue){
307
- const seen=new Set();
308
- for(const {file,pkg:member} of workspaceManifests(root,workspacePatterns)){
314
+ const seen=new Set(),members=workspaceManifests(root,workspacePatterns);
315
+ // Past 50 packages, init proposes the root's checks only and says so.
316
+ if(members.length>perPackageLimit)skipped.push({kind:'workspace',packages:members.length,
317
+ reason:`${members.length} workspace packages, more than ${perPackageLimit}: per-package checks were not proposed; add the ones you need to the config`});
318
+ for(const {file,pkg:member} of members.length>perPackageLimit?[]:members){
309
319
  const dir=path.posix.dirname(file),name=member.name;
310
320
  if(typeof name!=='string'||!name||seen.has(name)){
311
321
  ambiguous.push({kind:'workspace',scripts:[dir],reason:'missing or duplicate package name'});
@@ -367,6 +377,6 @@ export function discoverNodeProject(root,{recipe=null}={}) {
367
377
  }
368
378
  }
369
379
  return {schema:1,root,manager:pm,observationIssue,workspaces,node:process.version,
370
- checks,ambiguous,
380
+ checks,ambiguous,skipped,
371
381
  config:{schema:1,packageManager:pm.name||'unknown',checks:checks.map(row=>row.config)}};
372
382
  }
@@ -42,19 +42,21 @@ export function scriptCheckInputs(dirs=[]){
42
42
  installedInputs:[...modules.flatMap(at=>[`${at}/**`,`!${at}/.cache/**`,`!${at}/.vite/**`,`!${at}/.vite-temp/**`,
43
43
  `!${at}/.vitest/**`,`!${at}/.vitest-cache/**`,`!${at}/**/*.tsbuildinfo`]),...installState]};
44
44
  }
45
- // Installed files a tool rewrites during a run: the workspace state pnpm
46
- // writes at the first run after an install, and the global types vue-tsc
47
- // writes before it reads them, in a workspace node_modules. They stay inputs;
48
- // when they are a run's only change and the run wrote them, the run is kept
49
- // with the files as the run left them.
45
+ // Installed files a tool rewrites during a run. pnpm writes its workspace
46
+ // state in the root node_modules at the first run after an install, through
47
+ // a temporary .tmpXXXXXX file it renames. vue-tsc writes its global types in
48
+ // a workspace node_modules before it reads them. They stay inputs. When they
49
+ // are a run's only change and the run wrote them, the run is kept with the
50
+ // files as the run left them.
50
51
  const pnpmState=rel=>/^node_modules\/\.pnpm-workspace-state(?:-v\d+)?\.json$/.test(rel);
52
+ const pnpmTemp=rel=>/^node_modules\/\.tmp[A-Za-z0-9]{6}$/.test(rel);
51
53
  const vueTypes=rel=>{const parts=rel.split('/');
52
54
  return /^[^/]+\.d\.ts$/.test(parts.at(-1))&&parts.at(-2)==='.vue-global-types'&&parts.at(-3)==='node_modules'&&
53
55
  !parts.slice(0,-3).includes('node_modules')&&!rel.startsWith('external:');};
54
- export const toolOutputPath=rel=>pnpmState(rel)||vueTypes(rel);
55
- // The run wrote it: a recorded process did, or the pnpm executable ran, which
56
- // writes its state natively.
57
- export const toolOutput=(rel,guard)=>toolOutputPath(rel)&&(guard.wroteInstalled(rel)||pnpmState(rel)&&guard.opaque);
56
+ export const toolOutputPath=rel=>pnpmState(rel)||pnpmTemp(rel)||vueTypes(rel);
57
+ // The run wrote it: a recorded process did, or, for pnpm's files, the pnpm
58
+ // executable ran, which writes them natively.
59
+ export const toolOutput=(rel,guard)=>toolOutputPath(rel)&&(guard.wroteInstalled(rel)||(pnpmState(rel)||pnpmTemp(rel))&&guard.opaque);
58
60
  // The installed paths the recorder treats as observed by identity.
59
61
  export const scriptInstalledPaths=(root,dirs=[])=>[...modulesDirs(dirs),...installState].map(at=>path.join(root,at));
60
62
  // Variables a recorded run cannot be seen reading: Node's and libc's (as in
@@ -5,7 +5,9 @@ import path from 'node:path';
5
5
  import {spawnSync} from 'node:child_process';
6
6
  import {patternMatcher} from './glob.mjs';
7
7
 
8
- export function workspaceManifests(root,patterns) {
8
+ // Up to 1000 packages: every workspace node_modules is an installed input of
9
+ // a root check, so all of them are listed.
10
+ export function workspaceManifests(root,patterns,limit=1000) {
9
11
  const listing=spawnSync('git',['ls-files','--cached','--others','--exclude-standard',
10
12
  '-z','--',':(glob)**/package.json'],{cwd:root,encoding:'utf8',timeout:5000,
11
13
  maxBuffer:32*1024*1024,stdio:['ignore','pipe','ignore'],
@@ -17,6 +19,6 @@ export function workspaceManifests(root,patterns) {
17
19
  const dir=path.posix.dirname(file);
18
20
  return dir!=='.'&&!dir.split('/').includes('node_modules')&&included(dir);
19
21
  }).sort();
20
- if(matched.length>50)throw Error('more than 50 workspace packages found; select checks explicitly');
22
+ if(matched.length>limit)throw Error(`more than ${limit} workspace packages found; select checks explicitly`);
21
23
  return matched.map(file=>({file,pkg:JSON.parse(fs.readFileSync(path.join(root,file),'utf8'))}));
22
24
  }