@redue/cli 0.1.0-alpha.17 → 0.1.0-alpha.19

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,106 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.19 — 2026-10-24
4
+
5
+ A general recipe for typecheck and lint scripts that the strict recipes do not
6
+ take. Test scripts stay on the direct test recipe (Vitest, Jest).
7
+
8
+ - `redue init` tries the strict recipes first, then writes a general check
9
+ (`"qualification": "node-script-v1"`) for a root typecheck or lint script.
10
+ `redue run` runs the script through the project's package manager (npm,
11
+ pnpm 9-12 or Yarn with the `node-modules` linker) with REDUE's recorder in
12
+ every Node process, on macOS and Linux. The input is the whole checkout but
13
+ `.git`, `node_modules` and runner caches, plus every workspace
14
+ `node_modules` observed as installed files, the package manager and its
15
+ version, and the environment. See docs/user/node-script.md.
16
+ - pnpm's launchers are trusted only when their text matches a template a pnpm
17
+ release writes (pnpm 8.0 to 12.14). The pnpm executable, Corepack's
18
+ pnpm-native included, is toolchain; its configuration files are kept by
19
+ content.
20
+ - A run that starts a program REDUE does not record is UNVERIFIED and the
21
+ reason names it: native programs from installed packages (esbuild, turbo,
22
+ tsgolint, biome), git, find, and shells that read startup files.
23
+ - `status` and the hook's skip message name the recipe that made the receipt
24
+ ("general recipe"). The Claude Code hook recognizes lint commands as it does
25
+ typecheck and test commands: `npm run lint`, `pnpm lint`, and for a script
26
+ that is one plain command, that command and its `npx` form.
27
+ - In the census of 108 public TypeScript repos, the general recipe qualifies
28
+ 23 typecheck and 21 lint scripts (both recorded runs clean, CURRENT after
29
+ the second). Of the scripts whose runs start only Node and the package
30
+ manager's own launchers (35 typecheck, 37 lint, measured with strace), init
31
+ proposes and observes about 21 and 22, and 18 of each qualify. The rest:
32
+ init does not pick script names such as `tsc:build`, fails in monorepos
33
+ over 50 packages, or refuses source directory links and custom install
34
+ setups; plans with submodules or dangling links are unavailable.
35
+ - Known limits: on macOS, pnpm 12's temporary files in the project root can
36
+ arrive as an event on the root and make a run UNVERIFIED. Writes into
37
+ `node_modules` during a run make it UNVERIFIED, except pnpm's workspace
38
+ state and vue-tsc's global types, which stay inputs and are kept as the run
39
+ left them. A workspace's recording checks now leave out nested
40
+ `node_modules`, so `redue init` rewrites them once.
41
+
42
+ - Contributors: `census.mjs --product` applies the product's own rule and
43
+ lists the files an unstable run touched; the eval kills a timed-out
44
+ command's whole process group; the agent eval has lint tasks.
45
+
46
+ ## 0.1.0-alpha.18 — 2026-10-23
47
+
48
+ Typecheck in more project shapes, and pnpm test reuse on Linux. In the census
49
+ of 108 public TypeScript repos, the ones `init` makes ready went from 8 to 11
50
+ for typecheck (the census clones each repo's current head, so one or two
51
+ repos move between runs) and stayed at 22 for tests.
52
+
53
+ - pnpm test checks are reused on Linux. A recorded run accepts an installed
54
+ file hard-linked into the pnpm store the install recorded, when the store
55
+ holds it at its content address (pnpm names store files by their sha512).
56
+ Links anywhere else stay UNVERIFIED. Each file is hashed once; REDUE keeps
57
+ its identity and store path in its state and hashes it again only when the
58
+ identity changes. A 46 MB install takes about 160 ms to check the first
59
+ time. Limit: an in-place edit of an installed file in another project that
60
+ shares the store file is not seen until the next recorded run.
61
+
62
+ - Typecheck scripts in more shapes. A script of `tsc --noEmit` runs with
63
+ `-p`/`--project`, `--skipLibCheck` or `--pretty`, in any argument order,
64
+ qualifies for the direct npm recipe and the pnpm contract. A chain of such
65
+ runs joined by `&&` becomes one check per run (`typecheck.1`,
66
+ `typecheck.2`); the hook skips the script only when every run is CURRENT
67
+ and otherwise runs the stale ones through `redue run`. `check-types` joins
68
+ the recognized script names.
69
+ - The hook acts on a typecheck script's form only while the script in
70
+ package.json still has exactly the configured `tsc` runs. Before, a script
71
+ changed to `tsc --noEmit && eslint .` was rewritten to `redue run
72
+ typecheck`, which ran tsc alone.
73
+ - Without a typecheck script, `init` proposes a `tsc --noEmit` check for a
74
+ root `tsconfig.json` (direct npm recipe, pnpm) and for each pnpm workspace
75
+ package with one, unless a script runs a TypeScript checker in another form
76
+ (`tsc -b`, `vue-tsc`, ...). package.json is not changed; the check is
77
+ written only when the contract holds.
78
+ - The pnpm contract no longer needs `include: ["src"]`. Its source inputs are
79
+ the directories the config's `include` covers, as TypeScript computes them
80
+ for new files, minus `node_modules`. `exclude` entries stay inputs, since an
81
+ import can still reach an excluded file. A config extended from an installed
82
+ package is an installed input.
83
+ - Fixed: a config with three or more pnpm or test checks never got a
84
+ synchronized answer. The observer dropped control requests over 4 KB, and a
85
+ sync request carries about 1.5 KB of context digests per check; status
86
+ showed "Unexpected end of JSON input" for every check. The limit is 1 MiB.
87
+
88
+ - The direct npm TypeScript recipe takes any TypeScript 5.x or 6.0, not only
89
+ the five reviewed releases. Another release qualifies when package-lock.json
90
+ records the registry's `typescript` at that version with an integrity
91
+ (sha512, or sha1 from registries that record only sha1), npm's cache holds
92
+ the tarball under it, and the installed package is exactly the tarball's
93
+ files. Nothing is downloaded; a missing tarball
94
+ keeps the check recording-only with `npm-tarball-uncached`. The check takes
95
+ about 100 ms per full probe (TypeScript 5.x and 6.0 tarballs, 120-140 files).
96
+ CI runs the direct contract suite against 5.0.4, 5.1.6, 5.2.2, 5.3.3, 5.4.5,
97
+ 5.5.4, 5.7.3 and 5.8.3.
98
+
99
+ - Contributors: `npm test` runs up to four test files at a time, keeping two
100
+ cores free (a 3-core CI macOS runner runs one at a time), and takes about
101
+ 6 minutes on the owner's Mac (was 19.6). The two TypeScript 6 fixture
102
+ suites are separate files so they run side by side.
103
+
3
104
  ## 0.1.0-alpha.17 — 2026-10-22
4
105
 
5
106
  Workspaces and package managers beyond npm. In the census of 108 public
package/README.md CHANGED
@@ -37,7 +37,7 @@ it; relevant source edits make it stale. [Run the reproducible demo](docs/contri
37
37
 
38
38
  ## Install the alpha
39
39
 
40
- The opt-in pilot uses **0.1.0-alpha.17**. Follow the
40
+ The opt-in pilot uses **0.1.0-alpha.18**. Follow the
41
41
  [fixed-version pilot guide](docs/user/pilot.md) for supported projects, setup,
42
42
  updates and voluntary feedback. No external usage results are claimed yet.
43
43
 
@@ -76,10 +76,14 @@ Start with ordinary `status`. When it cannot establish caller context, use
76
76
  **Synchronization can cost more than rerunning a cheap check, particularly on
77
77
  Windows.** REDUE does not choose or execute that tradeoff for you.
78
78
 
79
- Since alpha.4, ordinary npm scripts are recording-only after
80
- [an npm launcher applicability gap](docs/acceptance/npm-launcher-boundary.md).
81
- A separate explicit [direct compiler recipe](docs/user/direct-typescript.md) can
82
- qualify supported npm typechecks. It does not claim that npm/lifecycle scripts ran.
79
+ `init` tries the strict recipes first: the [direct compiler recipe](docs/user/direct-typescript.md)
80
+ for npm typechecks, the pnpm and Yarn TypeScript recipes, and the
81
+ [direct test recipe](docs/user/tests-direct.md). A root typecheck or lint
82
+ script none of them takes can use the [general recipe](docs/user/node-script.md),
83
+ which runs the script through the package manager and records every Node
84
+ process; a script that starts another program (git, esbuild, turbo) stays
85
+ UNVERIFIED and the reason names it. Test scripts use the direct test recipe
86
+ (Vitest, Jest); other test scripts stay recording-only.
83
87
 
84
88
  ## Connect your agent (experimental)
85
89
 
package/bin/redue.mjs CHANGED
@@ -4,7 +4,9 @@ import {mark,traceBase,traceContext,traceEnvironment,traceId} from '../src/decis
4
4
  traceBase({...traceContext(),request:traceId(),work:'client'});
5
5
  mark('client.loaded');
6
6
  import {directCompiler,directQualification,directEnvironment,directInterpretation} from '../src/direct-typescript.mjs';
7
+ import {runArgv,tscRun} from '../src/typescript-invocation.mjs';
7
8
  import {testEnvironment,testRunnerAt,testsInterpretation,testsQualification} from '../src/test-direct.mjs';
9
+ import {scriptEnvironment,scriptInterpretation,scriptKinds,scriptQualification} from '../src/node-script.mjs';
8
10
  import os from 'node:os';
9
11
  import path from 'node:path';
10
12
  import {createHash} from 'node:crypto';
@@ -26,7 +28,7 @@ const help=`REDUE ${version}
26
28
  Usage: redue [--config FILE] [--state-dir DIR] COMMAND [OPTIONS]
27
29
 
28
30
  Commands:
29
- init [--dry-run] [--workspace NAME] [--check KIND] [--recipe typescript-direct|tests-direct]
31
+ init [--dry-run] [--workspace NAME] [--check KIND] [--recipe typescript-direct|tests-direct|node-script]
30
32
  Discover existing scripts; optionally select one workspace/check
31
33
  agent setup codex|claude [--dry-run|--apply] [--json]
32
34
  agent doctor [codex|claude] [--json]
@@ -245,6 +247,7 @@ const checks=publicConfig.checks.map(check=>{
245
247
  const npmDirect=check.qualification===directQualification;
246
248
  const pnpmTypecheck=check.qualification==='pnpm-tsc-v1';
247
249
  const testsDirect=check.qualification===testsQualification;
250
+ const generalScript=check.qualification===scriptQualification;
248
251
  if(check.script&&check.command&&!pnpmTypecheck&&!npmDirect&&!testsDirect)
249
252
  error(`${check.name}: choose script or command, not both`);
250
253
  const manager=publicConfig.packageManager;
@@ -254,10 +257,15 @@ const checks=publicConfig.checks.map(check=>{
254
257
  [`@which:${manager}`,'run',check.script]:null;
255
258
  const npmAutomatic=check.qualification==='typescript-noemit-v1';
256
259
  const yarnWorkspace=check.qualification==='yarn-workspace-tsc-v1';
257
- if(check.qualification&&!npmAutomatic&&!npmDirect&&!yarnWorkspace&&!pnpmTypecheck&&!testsDirect)
260
+ if(check.qualification&&!npmAutomatic&&!npmDirect&&!yarnWorkspace&&!pnpmTypecheck&&!testsDirect&&!generalScript)
258
261
  error(`${check.name}: unknown qualification contract`);
259
- if(npmDirect&&(manager!=='npm'||!check.script||check.cwd&&check.cwd!=='.'||
260
- JSON.stringify(check.command)!==JSON.stringify(['@node','@typescript-compiler:.','--noEmit'])))
262
+ // The general recipe runs the root package script through the package manager.
263
+ if(generalScript&&(!['npm','pnpm','yarn'].includes(manager)||!check.script||check.command||check.cwd&&check.cwd!=='.'||
264
+ check.workspace||!scriptKinds.includes(check.kind)))
265
+ error(`${check.name}: the general recipe needs a root typecheck, test or lint script, without command or cwd`);
266
+ if(npmDirect&&(manager!=='npm'||check.cwd&&check.cwd!=='.'||!Array.isArray(check.command)||
267
+ check.command[0]!=='@node'||check.command[1]!=='@typescript-compiler:.'||
268
+ !tscRun({},{script:null,args:check.command.slice(2)}).run?.noEmit))
261
269
  error(`${check.name}: direct recipe requires its explicit local compiler command`);
262
270
  // The script's own arguments follow the runner; qualification compares them with the script.
263
271
  if(testsDirect&&(!['npm','pnpm','yarn'].includes(manager)||!check.script||check.cwd&&check.cwd!=='.'&&!check.workspace||!Array.isArray(check.command)||
@@ -270,25 +278,32 @@ const checks=publicConfig.checks.map(check=>{
270
278
  check.command[1]!=='workspace'||check.command[2]!==check.workspace||
271
279
  check.command[3]!=='run'))
272
280
  error(`${check.name}: Yarn workspace qualification needs its discovered workspace command`);
273
- if(pnpmTypecheck&&(manager!=='pnpm'||!check.script||!check.command||
281
+ if(pnpmTypecheck&&(manager!=='pnpm'||!Array.isArray(check.command)||
274
282
  check.command[0]!=='@node'||
275
283
  check.command[1]!==`@typescript-bin:${check.cwd||'.'}`||
276
284
  check.workspace&&check.cwd==='.'||
277
- !check.command.slice(2).every(value=>typeof value==='string'&&value)))
285
+ !tscRun({},{script:null,args:check.command.slice(2)}).run))
278
286
  error(`${check.name}: pnpm qualification needs its discovered standalone TypeScript command`);
279
287
  const reason=check.kind==='test'?'test runtime, transforms and cache inputs need review':
280
288
  check.kind==='lint'?'lint plugins, config and resolver inputs need review':
281
289
  check.kind==='build'?'build environment and generated inputs need review':
282
290
  'check inputs need review';
283
- const autoProbes=npmDirect?[['@node','@vstate/direct-typescript-probe','@project',check.script]]:npmAutomatic?[['@node','@vstate/typescript-contract-probe',
291
+ // A chained script has one check per tsc run: each check's run counts them,
292
+ // so a run added to or removed from the script invalidates them all.
293
+ const runCount=(npmDirect||pnpmTypecheck)&&check.script?publicConfig.checks.filter(other=>
294
+ other.qualification===check.qualification&&other.script===check.script&&
295
+ (other.cwd||'.')===(check.cwd||'.')&&(other.workspace||null)===(check.workspace||null)).length:1;
296
+ if(runCount>16)error(`${check.name}: more than 16 checks for script ${check.script}`);
297
+ const run=npmDirect||pnpmTypecheck?runArgv({script:check.script??null,args:check.command.slice(2),count:runCount}):null;
298
+ const autoProbes=npmDirect?[['@node','@vstate/direct-typescript-probe','@project',...run]]:npmAutomatic?[['@node','@vstate/typescript-contract-probe',
284
299
  '@project',check.script,'@which:npm']]:yarnWorkspace?
285
300
  [['@node','@vstate/yarn-workspace-typecheck-probe','@project',
286
301
  check.workspace,check.command[4],'@which:yarn']]:pnpmTypecheck?
287
302
  [['@node','@vstate/pnpm-typecheck-probe','@project',
288
- check.workspace||'.',check.script]]:[];
303
+ check.workspace||'.',...run]]:[];
289
304
  const windowsContext=process.platform==='win32'?
290
305
  ['ComSpec','COMSPEC','PATHEXT','USERPROFILE']:[];
291
- const autoEnvironment=npmDirect?directEnvironment:testsDirect?testEnvironment:npmAutomatic?{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME',
306
+ const autoEnvironment=npmDirect?directEnvironment:testsDirect?testEnvironment:generalScript?scriptEnvironment(manager):npmAutomatic?{variables:['NODE_OPTIONS','NODE_PATH','CI','HOME',
292
307
  'NODE_ENV','BASH_ENV','ENV',...windowsContext],prefixes:['npm_config_','DYLD_','TSGO_'],
293
308
  pathExecutables:['node','npm'],executableIdentity:true}:yarnWorkspace?
294
309
  {variables:['NODE_OPTIONS','NODE_PATH','CI','HOME','NODE_ENV','BASH_ENV','ENV',...windowsContext],
@@ -310,9 +325,11 @@ const checks=publicConfig.checks.map(check=>{
310
325
  coverageReasons:check.coverageReasons||Object.fromEntries(
311
326
  categories.map(category=>[category,reason])),
312
327
  qualification:check.qualification||null,script:check.script||null,
328
+ ...(runCount>1?{runCount}:{}),
313
329
  ...(npmAutomatic?{qualificationInterpretation:'npm-launcher-withheld@1'}:{}),
314
330
  ...(npmDirect?{qualificationInterpretation:directInterpretation}:{}),
315
331
  ...(testsDirect?{qualificationInterpretation:testsInterpretation}:{}),
332
+ ...(generalScript?{qualificationInterpretation:scriptInterpretation}:{}),
316
333
  workspace:check.workspace||null,kind:check.kind||null};
317
334
  });
318
335
  const runtime={schema:1,provider:'declared-project@1',root,state,checks};
@@ -122,6 +122,17 @@ post-scripts. In every other case the command runs as typed: turbo caches its
122
122
  own results, so the hook never rewrites it. Filters (`--filter`) and nx are not
123
123
  matched.
124
124
 
125
+ For a typecheck check that runs the compiler, the script's forms act only when
126
+ the script in package.json still has exactly the configured runs: one `tsc`
127
+ run per check with the same arguments, and no pre- or post-script. Otherwise
128
+ the command runs as typed. A script that chains `tsc` runs with `&&` has one
129
+ check per run, `typecheck.1`, `typecheck.2` and so on (see
130
+ [direct TypeScript](direct-typescript.md#script-forms)); each run's own
131
+ `npx tsc ...` form matches its check. The hook skips the script when all its
132
+ runs are CURRENT, PASS and `reuse_eligible`, and otherwise rewrites it to
133
+ `redue run` of the runs that are not, in the script's order, joined by `&&`.
134
+ In a turbo or `pnpm -r` fan-out, a package's script counts only the same way.
135
+
125
136
  Each form may end with `2>&1` and then one `| tail -n N`, `| head -n N`,
126
137
  `| tail -N` or `| head -N`. It may also be the first command of a `;` or `&&`
127
138
  chain. Nothing else matches. Each form has its own `if` rule, so other Bash
@@ -30,6 +30,8 @@ out of version-controlled configuration.
30
30
 
31
31
  For the explicit direct compiler choice and generated exact argv, see
32
32
  [direct TypeScript](direct-typescript.md). Existing script receipts cannot satisfy it.
33
+ A general check (`"qualification": "node-script-v1"`) has a `script` and no
34
+ `command`; see the [general recipe](node-script.md).
33
35
 
34
36
  ## Arbitrary commands
35
37
 
@@ -56,6 +58,10 @@ Advanced explicit contracts may declare `generatedInputs`, `installedInputs`, na
56
58
  reviewed escape hatches, not onboarding requirements. A coverage rationale cannot
57
59
  override unsupported storage, unresolved links, missing inputs, or observation failure.
58
60
 
61
+ A chained typecheck script gets one generated check per `tsc` run, all with
62
+ the script's name (see [script forms](direct-typescript.md#script-forms)). A
63
+ proposed check for a project without a typecheck script has no `script`.
64
+
59
65
  Input patterns are relative to the project root. They cannot name a `.git`
60
66
  directory, because REDUE does not observe one; `!.git/**` is fine.
61
67
 
@@ -4,8 +4,10 @@ This check means: the reviewed local TypeScript compiler completed the configure
4
4
  check against declared, observed inputs and relevant execution context. It does
5
5
  not mean npm or npm lifecycle scripts completed.
6
6
 
7
- For a supported single-package npm project with an existing standalone
8
- `tsc --noEmit` script, installed dependencies, Node 22/24 and TypeScript 5.5.2/5.6.3/5.9.3/6.0.2/6.0.3:
7
+ For a supported single-package npm project with a typecheck script of `tsc`
8
+ runs (see [script forms](#script-forms)) or no typecheck script and a root
9
+ `tsconfig.json`, installed dependencies, Node 22/24 and TypeScript 5.x or 6.0
10
+ (see [compiler identity](#compiler-identity)):
9
11
 
10
12
  ```sh
11
13
  redue init --recipe typescript-direct --check typecheck --dry-run
@@ -33,9 +35,64 @@ A generated check contains:
33
35
 
34
36
  `script` identifies the reviewed source recipe; `command` is what actually runs:
35
37
  the resolved Node executable and local TypeScript implementation, with exact argv
36
- and no shell. TypeScript 5.5.2 and 5.6.3 use `lib/tsc.js`; 5.9.3, 6.0.2 and 6.0.3 use `lib/_tsc.js` directly,
37
- without its outer compile-cache-enabling shim. No compiler is fetched or bundled.
38
- The entire installed package must match reviewed official distribution bytes.
38
+ and no shell. The entry is `lib/_tsc.js` where the package has one (5.7 and
39
+ later), without its outer compile-cache-enabling shim, and `lib/tsc.js`
40
+ otherwise. No compiler is fetched or bundled.
41
+
42
+ ## Compiler identity
43
+
44
+ The entire installed `node_modules/typescript` must be an official package,
45
+ byte for byte, in one of two ways:
46
+
47
+ - TypeScript 5.5.2, 5.6.3, 5.9.3, 6.0.2 or 6.0.3: its files match the digest
48
+ REDUE ships for that release.
49
+ - Any other TypeScript 5.x or 6.0 release: package-lock.json records the
50
+ registry's `typescript` package at the installed version (no alias, an
51
+ `https` tarball URL, not a file or git source) with an integrity (sha512,
52
+ or sha1 where a registry records only sha1), npm's cache holds the tarball
53
+ under that integrity, and the installed files are exactly the tarball's
54
+ files. REDUE reads the cache only; it never downloads. The cache is the one
55
+ npm uses: `npm_config_cache`, else `cache` in the project's `.npmrc`, then
56
+ the user's, else `~/.npm` (`%LocalAppData%\npm-cache` on Windows); the
57
+ global npmrc is not read. `npm-shrinkwrap.json` is not read on this path.
58
+ `npm ci` fills the cache. When the tarball is missing the check stays
59
+ recording-only with the reason `npm-tarball-uncached`; `npm cache add
60
+ typescript@VERSION` adds it. The check's compiler identity is then the
61
+ lockfile integrity, so a lockfile change re-probes.
62
+
63
+ CI runs the same contract suite against the last release of each minor from
64
+ 5.0 to 5.8 that the reviewed releases do not cover. Later minors (6.1 and up)
65
+ wait until CI covers them. TypeScript 7 (the native compiler) is not
66
+ supported.
67
+
68
+ ## Script forms
69
+
70
+ A typecheck script qualifies when it is one or more `tsc` runs joined by `&&`,
71
+ each with `--noEmit` and only these arguments, in any order: `-p`/`--project`
72
+ with a config file or directory inside the project, `--skipLibCheck`, and
73
+ `--pretty` (optionally `true` or `false`). For example
74
+ `tsc -p tsconfig.app.json --noEmit` or
75
+ `tsc --noEmit && tsc --noEmit -p tsconfig.node.json`. `tsc -b`, `--watch`,
76
+ other options, quotes, variables and other commands keep the script
77
+ recording-only.
78
+
79
+ A chain gets one check per run, named `typecheck.1`, `typecheck.2` and so on,
80
+ each with the script's name and that run's arguments in its command. Each run
81
+ is checked as its own program against its own config, and is reused only while
82
+ the script still has as many runs as the config has checks for it, one of them
83
+ with exactly those arguments. Adding or removing a run, or a pre- or
84
+ post-script, makes every check of the script not reusable until `init` runs
85
+ again. The script as a whole
86
+ (`npm run typecheck`) is skipped only when its current runs and the configured
87
+ checks correspond one to one and all are CURRENT (see [agents](agents.md)).
88
+
89
+ With no typecheck script and a root `tsconfig.json`, init proposes a
90
+ `tsc --noEmit` check with no `script` field, unless a script already runs a
91
+ TypeScript checker in another form (`tsc -b`, `vue-tsc`, `svelte-check`,
92
+ `tsgo`, `astro check`, `nuxi typecheck`). package.json is not changed. The
93
+ check is written only when the contract holds; otherwise no typecheck check is
94
+ added. The pnpm contract does the same for each workspace package with a
95
+ `tsconfig.json` and no typecheck script.
39
96
 
40
97
  Existing configs are never overwritten. To compare choices, use a new project-local
41
98
  `--config redue.direct.config.json` with init; this explicitly selects separate
@@ -82,9 +82,10 @@ Use a check actually printed by `init`. For a workspace:
82
82
  `redue init --workspace @example/package --check typecheck`.
83
83
  Initialization previews detected commands and qualification confidence before writing
84
84
  `redue.config.json`. An existing config is never overwritten. No checks are run and
85
- no dependencies installed. A missing/unsupported boundary is explained. Npm-launcher typechecks are
86
- recording-only in alpha.4; a passed execution is historical PASS with UNVERIFIED
87
- applicability, not reusable CURRENT.
85
+ no dependencies installed. A missing/unsupported boundary is explained. `init` tries
86
+ the strict recipes first and uses the [general recipe](node-script.md) for a root
87
+ typecheck or lint script none of them takes. Test scripts use the
88
+ [direct test recipe](tests-direct.md).
88
89
 
89
90
  “Ready to qualify” means REDUE has a supported recipe whose prerequisites will be
90
91
  validated. It is not a receipt. “Recording-only” can record PASS/FAIL without claiming
@@ -92,10 +93,9 @@ reusability. Qualification can change when configuration or installation changes
92
93
 
93
94
  `run` streams normal output and preserves execution environment and cancellation.
94
95
  For the narrow pnpm TypeScript contract, `init` explicitly shows the direct installed
95
- compiler invocation it selects. Npm scripts remain recording-only;
96
- use `redue init --recipe typescript-direct --dry-run` to preview a distinct,
97
- explicit [local compiler recipe](direct-typescript.md). Other scripts run through
98
- the package manager. No existing configuration is silently migrated.
96
+ compiler invocation it selects, and for npm the
97
+ [local compiler recipe](direct-typescript.md). General checks and other scripts
98
+ run through the package manager. No existing configuration is silently migrated.
99
99
  Only use configs and probes from trusted repositories: these execute local code.
100
100
 
101
101
  `status` is cached and conservative. `status --sync` and `explain` revalidate caller
@@ -0,0 +1,153 @@
1
+ # General recipe (typecheck and lint scripts)
2
+
3
+ The general recipe records a project's own typecheck or lint script, run
4
+ through its package manager, and reports later whether that result still
5
+ applies to the files you have. It is a lower tier than the strict recipes:
6
+ `redue init` tries the [direct TypeScript recipe](direct-typescript.md), the
7
+ pnpm and Yarn TypeScript recipes and the [direct test recipe](tests-direct.md)
8
+ first, and uses the general recipe for a root typecheck or lint script none of
9
+ them takes. Test scripts use the direct test recipe; `init` does not propose
10
+ the general recipe for them.
11
+
12
+ It supports a project installed by npm, pnpm 9-12 (isolated or hoisted
13
+ `node_modules`) or Yarn with the `node-modules` linker, with one lockfile kind
14
+ matching any `packageManager` declaration, on macOS and Linux with Node 22 or
15
+ 24. Workspaces are supported for root scripts. `init --recipe node-script`
16
+ writes general checks only.
17
+
18
+ ## Setup
19
+
20
+ ```sh
21
+ redue init
22
+ redue start
23
+ redue run lint
24
+ redue status --sync
25
+ ```
26
+
27
+ The config gets one field for the recipe, written by `init`:
28
+
29
+ ```json
30
+ {"name": "lint", "script": "lint", "kind": "lint", "qualification": "node-script-v1", "inputs": ["**", "..."]}
31
+ ```
32
+
33
+ `redue run lint` runs `npm run lint` (or `pnpm run lint`, `yarn run lint`) with
34
+ REDUE's recorder loaded in every Node process, and npm's and pnpm's update
35
+ checks turned off. With the Claude Code hook, `npm run lint`, `pnpm lint` and,
36
+ for a script that is one plain command such as `eslint .`, that command itself
37
+ and `npx eslint .` are recognized like typecheck and test commands.
38
+ `status` and the hook's skip message name the recipe that made the receipt.
39
+
40
+ ## What REDUE claims
41
+
42
+ CURRENT means the last run's inputs are unchanged:
43
+
44
+ - every file in the checkout, except `.git`, every `node_modules`, the runner
45
+ caches listed below and the files the run itself wrote;
46
+ - the installed files in the root's and every workspace package's
47
+ `node_modules`, and Yarn's install state, by file identity;
48
+ - the lockfile and `package.json`, as files of the checkout;
49
+ - the package manager's executable and Node, by file identity, and the exact
50
+ script;
51
+ - every environment variable the run's JavaScript read directly, and always
52
+ `TZ`, `LANG`, `PATH`, `HOME`, `CI`, `XDG_CONFIG_HOME` and every `NODE_*`,
53
+ `LC_*`, `VITE_*`, `DYLD_*`, `LD_*`, `npm_config_*`, `pnpm_config_*`,
54
+ `PNPM_*`, `COREPACK_*` and `YARN_*` variable;
55
+ - the executables of npm, pnpm, Yarn and Corepack on PATH, by file identity,
56
+ since a script may run another package manager;
57
+ - small files read outside the checkout (up to 1 MB), by content, and lookups
58
+ outside it, asked again before each answer.
59
+
60
+ Runner caches are not inputs, and a run may read and write them: `.cache`,
61
+ `.vite`, `.vitest`, `.turbo`, `.nx`, `.swc`, `.jest-cache`, `.parcel-cache`,
62
+ `.angular/cache`, `.next/cache` and `.tmp` directories, `*.tsbuildinfo`,
63
+ `.eslintcache`, `.stylelintcache`, `.prettiercache`, and the
64
+ `_tmp_<pid>_<hex>` files pnpm writes and removes at every run. A file with
65
+ such a path that git does not ignore is source: reading it makes the run
66
+ UNVERIFIED. At the top of each `node_modules`, only `.cache`, `.vite`,
67
+ `.vite-temp`, `.vitest` and `.vitest-cache` are left out, and `*.tsbuildinfo`
68
+ anywhere in it.
69
+
70
+ Two installed files are rewritten by tools during a run: pnpm's
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.
77
+
78
+ It does not mean a rerun would give the same result: a test that depends on
79
+ the time or on randomness can pass now and fail later on the same inputs.
80
+
81
+ ## Programs
82
+
83
+ The recorder sees only Node processes. Every other program a run starts must be
84
+ one REDUE can account for:
85
+
86
+ - `sh` or `dash`, or `bash` without `BASH_ENV`, running a command that, read
87
+ as text, starts only Node programs, package managers and shell builtins
88
+ (`cd`, `echo`, `true`, `export`, `exec` and a few more). Words are looked up
89
+ on the PATH the shell got. A Node program is the Node executable, a file
90
+ whose `#!` line runs Node, or a pnpm launcher. A version manager's `node`
91
+ shim (asdf, nodenv) is a script and does not count. Other shells read
92
+ startup files and are refused. The shell must still have REDUE's recorder in
93
+ `NODE_OPTIONS`: npm's and pnpm's `node-options` setting replaces it, and a
94
+ run under such a setting is UNVERIFIED;
95
+ - a pnpm launcher in `node_modules/.bin`: the `#!/bin/sh` script pnpm writes,
96
+ which runs `sed`, `dirname`, `uname` or `readlink` before it starts Node. It
97
+ is trusted only when its text is exactly one of the templates pnpm 8.0 to
98
+ 12.14 write (`src/pnpm-shim-templates.json`), with only the bin file and
99
+ `NODE_PATH` filled in;
100
+ - the pnpm executable (pnpm 12, also the copy Corepack downloads): REDUE cannot
101
+ see what it does, so the scripts it runs are read as text the same way,
102
+ their `pre` and `post` scripts included. Filters and recursive runs
103
+ (`--filter`, `-r`) are refused. At least one Node process it starts must
104
+ have loaded the recorder, and its configuration files outside the checkout
105
+ (`pnpm/rc`, `pnpm/config.yaml`, `~/.npmrc`, the global `npmrc`) are kept by
106
+ content;
107
+ - `ps -A -o ppid,pid`, which pnpm loaded into Corepack runs.
108
+
109
+ Anything else makes the result UNVERIFIED and names the program: `git`,
110
+ `sleep`, `find`, and native programs from installed packages such as esbuild,
111
+ turbo, tsgo, tsgolint and biome, whose reads, writes and network use REDUE
112
+ cannot see. `init` reads the script the same way and keeps a script that
113
+ starts such a program recording-only, with the program in the reason.
114
+
115
+ A command line may use `&&`, `||`, `;`, `|`, quotes without expansions,
116
+ globs in arguments and redirection to `/dev/null`. Variables, command
117
+ substitution, input redirection, subshells, here-documents and `command -p`
118
+ are refused. Setting or clearing `NODE_OPTIONS`, `PATH`, `REDUE_RUN_RECORD`,
119
+ `BASH_ENV`, `ENV`, or npm's or pnpm's `node-options` or `script-shell` is
120
+ refused, since it could stop a Node process from loading the recorder.
121
+ `node --env-file` and Node's config-file options are refused: Node reads those
122
+ files natively. `process.loadEnvFile` is recorded as a read.
123
+
124
+ ## The guard
125
+
126
+ As in the [direct test recipe](tests-direct.md#the-guard), the result is kept
127
+ but marked UNVERIFIED, with the reason, when the run read a git-ignored file
128
+ REDUE does not observe and did not create first, changed an input during the
129
+ run (a formatter's `--write`, a generated `version.ts`), listed a directory or
130
+ read a large file outside the checkout, used the network, or started a program
131
+ REDUE does not account for. Reads through a workspace package's link in
132
+ `node_modules` count where the link leads.
133
+
134
+ The package managers' own installations, Corepack's downloads and npm's log
135
+ directory are toolchain, identified by the package manager's executable, not
136
+ inputs.
137
+
138
+ ## Limits
139
+
140
+ - On macOS, a file created and removed directly in the project root during a
141
+ run (pnpm 12 does this) can be reported by the file watcher as an event on
142
+ the root itself, an observation gap that makes the run UNVERIFIED. Linux
143
+ reports the file, which is not an input.
144
+ - The pnpm executable and the pnpm launchers' helpers are trusted as
145
+ toolchain: what they read is not checked.
146
+ - Code that copies the environment and reads the copy, and native code that
147
+ reads files or variables (native addons such as Rust-based resolvers), is
148
+ not seen.
149
+ - A project whose `.npmrc` sets `node-options` stays UNVERIFIED: npm replaces
150
+ `NODE_OPTIONS` with it, which drops REDUE's recorder.
151
+ - A script that runs a build first (`npm run build && tsc --noEmit`) usually
152
+ starts esbuild or another native bundler and stays UNVERIFIED.
153
+ - Windows is not supported; the check stays recording-only there.
@@ -1,4 +1,4 @@
1
- # Opt-in development pilot: alpha.17
1
+ # Opt-in development pilot: alpha.18
2
2
 
3
3
  Use one fixed build for several ordinary development sessions. The owner will
4
4
  invite two or three willing testers and approve their projects; nothing is
@@ -9,8 +9,9 @@ controlled compiler/agent trials are acceptance evidence, not independent adopti
9
9
 
10
10
  Start with a trusted, single-package npm Git checkout, normally installed locked
11
11
  dependencies, and an existing standalone `tsc --noEmit` script without pre/post
12
- scripts. The direct recipe accepts only the official, unchanged TypeScript
13
- **5.5.2, 5.6.3, 5.9.3, 6.0.2 or 6.0.3** packages and Node **22 or 24**. No workspaces, named
12
+ scripts. The direct recipe accepts only an official, unchanged TypeScript **5.x or
13
+ 6.0** package (a reviewed release, or one matching the tarball package-lock.json
14
+ names in npm's cache) and Node **22 or 24**. No workspaces, named
14
15
  tsconfig selection, emitted/incremental/composite builds, custom loaders or
15
16
  outside-root inputs are added by this pilot. Do not downgrade or change your
16
17
  project to fit; record an unsupported setup as feedback.
@@ -35,7 +36,7 @@ are **NOT RUN**. The recording shortcut is macOS/direct-recipe only.
35
36
  For a new default npm installation:
36
37
 
37
38
  ```sh
38
- npm install --global @redue/cli@0.1.0-alpha.17
39
+ npm install --global @redue/cli@0.1.0-alpha.18
39
40
  redue --version
40
41
  cd my-project
41
42
  redue --config redue.config.json init --recipe typescript-direct --check typecheck --dry-run
@@ -89,7 +90,7 @@ using it; keep the same npm prefix, config and any explicit state selection.
89
90
 
90
91
  ```sh
91
92
  redue --config redue.config.json stop
92
- npm install --global @redue/cli@0.1.0-alpha.17
93
+ npm install --global @redue/cli@0.1.0-alpha.18
93
94
  redue --config redue.config.json agent setup codex --dry-run
94
95
  redue --config redue.config.json agent setup codex --apply
95
96
  redue --config redue.config.json start
@@ -15,11 +15,17 @@ At the repository root, `init` looks for existing typecheck, test, lint, and
15
15
  build scripts. It prefers familiar script names, then unique recognizable
16
16
  `tsc`, Vitest/Jest, ESLint, or Vite commands under other names. Multiple
17
17
  matches are reported as ambiguous rather than guessed. It never creates a new
18
- project script. Automatic npm-launcher applicability was withdrawn in alpha.4;
19
- ordinary npm scripts remain recording-only. See the [maintainer notice](npm-launcher-notice.md).
20
- The direct recipe is opt-in, available since alpha.5. The opt-in `init --recipe typescript-direct` supports
21
- a standalone `tsc --noEmit` script in a single-package npm installation using
22
- reviewed TypeScript 5.5.2/5.6.3/5.9.3/6.0.2/6.0.3 and Node 22/24. It executes the local compiler,
18
+ project script. Without `--recipe`, `init` tries the strict recipes below first,
19
+ then the [general recipe](node-script.md) for a root typecheck or lint
20
+ script: the script run through the package manager with every Node process
21
+ recorded and the whole checkout as its input. `--recipe typescript-direct`,
22
+ `tests-direct` or `node-script` selects one recipe alone. The direct TypeScript recipe supports
23
+ a typecheck script of `tsc --noEmit` runs in any argument order, chained runs,
24
+ or no typecheck script at all (a proposed `tsc --noEmit`; see
25
+ [script forms](direct-typescript.md#script-forms)), in a single-package npm installation using
26
+ TypeScript 5.x or 6.0 (a reviewed release, or one whose installed files match the
27
+ tarball package-lock.json names in npm's cache; see
28
+ [compiler identity](direct-typescript.md#compiler-identity)) and Node 22/24. It executes the local compiler,
23
29
  not npm. Full installed compiler identity, config/membership, installed inputs
24
30
  and relevant Node environment are checked before reuse. See the
25
31
  [exact direct boundary](direct-typescript.md). Unsupported plugins, preloads,
@@ -37,11 +43,11 @@ When an internal dependency is consumed through built declarations, changing
37
43
  only its source does not stale the selected check until the consumed output
38
44
  changes. Unsupported custom resolution, project references, outside-root
39
45
  source links, and installation modes remain recording-only. Other Yarn
40
- typechecks, Vitest/Jest, ESLint, and builds start recording-only unless a
41
- separately justified explicit contract is provided. For a single-package npm
42
- project whose test script runs Vitest 4/5 or Jest 29 directly, the opt-in
43
- `init --recipe tests-direct` records the run and guards its inputs; see
44
- [direct tests](tests-direct.md).
46
+ typechecks, Vitest/Jest, ESLint, and builds in workspace packages start
47
+ recording-only unless a separately justified explicit contract is provided.
48
+ For a single-package project whose test script runs Vitest 4/5 or Jest 29
49
+ directly, `init` uses the direct test recipe, which records the run and guards
50
+ its inputs; see [direct tests](tests-direct.md).
45
51
  In particular, test caches and build-generated inputs are not excluded to
46
52
  make evidence green.
47
53
 
@@ -62,12 +68,17 @@ the installed one. Only the project's files are read: settings from a user or
62
68
  global rc file, or `npm_config_*` variables during install, are not. The
63
69
  supported local layouts are pnpm's standard isolated and hoisted
64
70
  `node_modules` with a project-local `.pnpm` virtual store. `init` can discover
65
- root and workspace scripts. For a standalone `tsc --noEmit` or `tsc -p ...`
66
- script in the narrow TypeScript contract, it records the exact installed
67
- compiler invocation directly. This avoids pnpm's task-run bookkeeping
71
+ root and workspace scripts. For a typecheck script of `tsc` runs
72
+ ([script forms](direct-typescript.md#script-forms); `tsc -p .` without
73
+ `--noEmit` is also accepted), or a package with a `tsconfig.json` and no
74
+ typecheck script, it records the exact installed compiler invocation directly.
75
+ The source inputs are the directories the config's `include` covers, as
76
+ TypeScript itself computes them for new files, minus `node_modules` (`exclude`
77
+ entries stay inputs: an import can still reach an excluded file); a file the
78
+ program reads outside them keeps the check recording-only. This avoids pnpm's task-run bookkeeping
68
79
  writes during `pnpm run`; it does not alter the project's script. Test, lint,
69
- build, and ambiguous TypeScript scripts still execute through `pnpm run` and
70
- start recording-only. The generated `@typescript-bin:WORKSPACE` token resolves
80
+ build, and ambiguous TypeScript scripts still execute through `pnpm run`; root
81
+ typecheck and lint ones can use the [general recipe](node-script.md). The generated `@typescript-bin:WORKSPACE` token resolves
71
82
  the local compiler when the check runs; it is not an absolute machine path.
72
83
 
73
84
  The pnpm contract indexes consumed installed package contents inside the