@redue/cli 0.1.0-alpha.11 → 0.1.0-alpha.13

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,78 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.13 — 2026-10-19
4
+
5
+ Bench numbers below are from one 800-file project on macOS arm64, Node 22.13.0.
6
+ Released alpha.12 and this build ran interleaved on the same machine; the
7
+ decision path is unchanged within noise (warm `status --sync` 192/197 ms on
8
+ alpha.12, 174/198 ms on alpha.13; states correct, no idle probe launches).
9
+ On the CI screen of 38 public TypeScript projects, 22 qualify for the direct
10
+ recipe, up from 19 at alpha.12: worldmonitor, Recordly and open-cowork, which
11
+ the fixed 5 s probe deadline blocked.
12
+
13
+ - A full state probe may run longer on large projects. Its deadline was a
14
+ fixed 5 s (10 s on Windows and for pnpm); now it is the larger of twice the
15
+ last successful probe and the last completed run, at least that floor and at
16
+ most 60 s, doubling after a timeout. A probe that never finishes is still
17
+ killed at its deadline and the check stays UNVERIFIED. `redue status --sync`
18
+ and `redue run` wait for the observer up to the sum of its checks' probe
19
+ deadlines plus 10 s (`probe_deadline_ms` in status JSON), never less than
20
+ before. While a full probe runs the observer does not answer, so with a hung
21
+ probe a decision can wait up to 60 s per check before it is UNVERIFIED.
22
+
23
+ ## 0.1.0-alpha.12 — 2026-10-19
24
+
25
+ Bench numbers below are from one 800-file project on macOS arm64, Node 22.13.0.
26
+ Released alpha.11 and this build ran interleaved on the same machine; the
27
+ decision path is unchanged within noise (warm `status --sync` 183/410 ms on
28
+ alpha.11, 298/174 ms on alpha.12; states correct, no idle probe launches).
29
+ On a screen of 38 public TypeScript projects in CI, 10 qualified for the
30
+ direct recipe before the run-facts change, 17 after it and 19 after incremental
31
+ and references support. worldmonitor and Recordly now stop at the 5 s probe
32
+ timeout, and open-cowork at its end-checkpoint probe.
33
+
34
+ - Large installations reach ready quickly. Installed files are observed by
35
+ identity (inode, size, mode, link count, times), not by hashing their contents;
36
+ the compiler's own reads stay content-checked by the probe. On macOS:
37
+ Recordly (75k files in `node_modules`) ready in 7 s instead of failing after
38
+ 120 s; worldmonitor (143k) in 30 s instead of not ready after 15 min.
39
+ Behavior change: reinstalling identical packages, restoring an installed
40
+ file's bytes or copying the installation now makes the check STALE once.
41
+ - The direct TypeScript recipe supports TypeScript 6.0.2 and 6.0.3.
42
+ - Claude Code: the typecheck hook also matches a typecheck that starts a `;` or
43
+ `&&` chain. When it skips one, the message names the rest of the chain. The
44
+ skill lists the exact commands the hook matches for the selected config.
45
+ - Shorter agent instructions: the CLAUDE.md block is 235 bytes instead of 739,
46
+ the skill 1.8 KB instead of 5 KB. The reuse rules are unchanged. When the
47
+ Claude Code hook matches a typecheck, the block no longer points to the
48
+ skill; the skill stays for agents that look for it.
49
+ - Claude Code hook: it waits for status up to half the last completed run's
50
+ duration, at most 10 s, instead of a fixed 2 s. Run `redue agent setup claude
51
+ --apply` again to raise the hook's Claude Code timeout from 5 s to 15 s.
52
+ - Linux: the observer no longer watches `.git` directories. Before, each new
53
+ directory git created there (on most commits) was an observation gap: status
54
+ was UNVERIFIED until a full reindex and probe finished, about 5 s on a
55
+ 26k-file project. Input patterns that name a `.git` directory are now
56
+ refused; REDUE never observed changes there.
57
+ - Direct TypeScript recipe: runs with `declaration: true` are reusable. tsc's
58
+ declaration checks look up files that building the program does not, and
59
+ read dependencies' `package.json`; before, any such lookup made the run
60
+ UNVERIFIED. The receipt now keeps those existence, `realpath` and
61
+ `package.json` answers as run facts, and status asks them again before every
62
+ CURRENT answer, including when a cached status is read. Every direct run is
63
+ now recorded, also one checked by full probes. The probe also records
64
+ `realpath` while it parses a tsconfig `extends` from a package.
65
+ Behavior change: a direct receipt recorded by an earlier version is
66
+ UNVERIFIED until the check runs again (macOS and Linux). Limit: Windows does
67
+ not record direct runs, so declaration-only lookups are not checked there.
68
+ - Direct TypeScript recipe: a root tsconfig with `incremental: true` is
69
+ supported. Its build-info file is left out of the inputs, and the run may
70
+ write only that file. A root tsconfig that references a composite config
71
+ (such as Vite's `tsconfig.node.json`) is supported when the program does not
72
+ use the referenced project's files. A `composite` root is still refused.
73
+ - Releases are published to the `latest` npm tag as well as `alpha`, so
74
+ `npm install -g @redue/cli` installs the newest release.
75
+
3
76
  ## 0.1.0-alpha.11 — 2026-10-18
4
77
 
5
78
  Bench numbers below are from one 800-file project on macOS arm64, Node 22.13.0.
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.11**. Follow the
40
+ The opt-in pilot uses **0.1.0-alpha.13**. 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
 
@@ -96,9 +96,42 @@ Setup is explicit, reversible, and does not change tool permissions or run check
96
96
 
97
97
  The experimental claim is **opt-in project-local agent activation and direct TypeScript
98
98
  evidence, with fresh-session reuse demonstrated in the tested Codex/macOS
99
- configuration**. Claude setup/removal mechanics are tested; Claude behavior and
100
- cross-host reuse are not. This is experimental guidance, not guaranteed agent
101
- compliance, general sandbox compatibility, or a promise of time savings.
99
+ configuration and measured for Claude Code below**. Cross-host reuse is not
100
+ tested. This is experimental guidance, not guaranteed agent compliance, general
101
+ sandbox compatibility, or a promise of time savings.
102
+
103
+ ### Measured with Claude Code
104
+
105
+ Four public TypeScript projects, four tasks each, three sessions per task with
106
+ and without REDUE: 96 headless Claude Code sessions (Sonnet 5.5, CI Linux
107
+ runner, alpha.12 code, October 2026). Plain `tsc --noEmit` takes 10–19 s on
108
+ these projects. Medians per task, summed over the four projects:
109
+
110
+ | Task | Session time, without → with REDUE | Input tokens | Typechecks skipped |
111
+ |---|---|---|---|
112
+ | Docs edit, then "make sure it still typechecks" | 97 → 45 s | 169k → 162k | 12 of 12 |
113
+ | "Does it still typecheck?" after a pull | 74 → 22 s | 117k → 89k | 12 of 12 |
114
+ | Source change, typecheck, then a docs edit | 141 → 112 s | 210k → 200k | 9 of 12 |
115
+ | Source change with tests | 152 → 151 s | 269k → 234k | 0 of 12 |
116
+ | **All tasks** | **464 → 330 s** | **765k → 685k** | 33 |
117
+
118
+ - **Correctness:** every typecheck REDUE skipped was checked afterwards with
119
+ plain tsc on the exact tree it was skipped on. All 33 agreed, and all 96
120
+ final trees passed plain tsc and the task checks in both arms.
121
+ - **Cost:** API cost was the same ($0.65 without, $0.66 with). One-time setup
122
+ per checkout was 3–5 s, plus one recorded typecheck; the CLAUDE.md section
123
+ REDUE adds is about 60 tokens.
124
+ - **How:** each session gets a fresh clone at a pinned commit; the REDUE arm
125
+ first runs `redue init`, `agent setup claude`, `start` and one
126
+ `redue run typecheck`, standing for an earlier session's passing check.
127
+ The harness, tasks and raw results are in [`eval/`](eval/README.md).
128
+ - **Limits:** four projects and three runs per cell show direction, not a
129
+ general rate. Savings come only when an earlier pass still applies: tasks
130
+ that change source code save nothing, and on one project the docs edit came
131
+ after source edits that made the result STALE, so nothing was skipped. The
132
+ projects were chosen because they qualify for the direct TypeScript recipe;
133
+ of 38 screened projects, 19 qualified at alpha.12 and 22 at alpha.13. Times are agent session
134
+ time only, on one runner type.
102
135
 
103
136
  ## What the states mean
104
137
 
@@ -83,20 +83,30 @@ The hook skips an exact rerun of a configured typecheck: a check with
83
83
  `kind: "typecheck"` or named `typecheck`. It matches these commands, run from the
84
84
  project root:
85
85
 
86
- - `redue run CHECK`
86
+ - `redue run CHECK`, or `redue --config FILE run CHECK` when the config is not
87
+ `redue.config.json`
87
88
  - `npm run SCRIPT`, when the check has an npm script
88
89
  - `npx tsc --noEmit`, for the direct-TypeScript recipe
89
90
  - the check's `command`, when it has no `@` tokens
90
91
 
91
92
  Each form may end with `2>&1` and then one `| tail -n N`, `| head -n N`,
92
- `| tail -N` or `| head -N`. Nothing else matches. Each form has its own `if`
93
- rule, so other Bash commands never start the hook.
94
-
95
- On a match, the hook runs `redue status --sync --json` with a 2-second timeout.
93
+ `| tail -N` or `| head -N`. It may also be the first command of a `;` or `&&`
94
+ chain. Nothing else matches. Each form has its own `if` rule, so other Bash
95
+ commands never start the hook. The installed skill lists these exact commands,
96
+ so the agent uses forms the hook recognizes.
97
+
98
+ On a match, the hook runs `redue status --sync --json`. It waits up to half the
99
+ duration of the check's last completed run, at most 10 seconds, so waiting never
100
+ costs more than half a run. With no completed run it waits 2 seconds. A check
101
+ that takes a few hundred milliseconds is therefore rarely skipped; skipping it
102
+ would save almost nothing, and the hook does not rewrite it to `redue run`
103
+ either. Claude Code's own limit for the hook is 15 seconds.
96
104
  It blocks the command only when the check is CURRENT, PASS and
97
105
  `reuse_eligible: true`. The message names the check and the receipt's run ID. Every
98
106
  other result allows the command: STALE, UNVERIFIED, FAILED, a timeout, an error, or
99
- a missing `redue`. A command that starts with `REDUE_FORCE=1` always runs.
107
+ a missing `redue`. A command that starts with `REDUE_FORCE=1` always runs. A
108
+ blocked chain runs none of its commands; the message names the rest of the chain
109
+ so the agent can run it on its own.
100
110
 
101
111
  When the check is not CURRENT but the observer answered for it, the hook rewrites
102
112
  the command to `redue run CHECK` and allows it, keeping any `2>&1` and
@@ -104,13 +114,17 @@ the command to `redue run CHECK` and allows it, keeping any `2>&1` and
104
114
  `redue.config.json`. The run is recorded, so the next identical command can be
105
115
  skipped. Without a healthy answer (timeout, error, no observer) the command runs
106
116
  as typed. Claude Code still applies deny and ask rules to the rewritten command.
117
+ A chain is never rewritten: the rewrite is allowed without a prompt, and that
118
+ would also skip the prompt for the rest of the chain.
107
119
 
108
120
  Limits:
109
121
 
110
122
  - The hook judges its own environment, which Claude Code passes to it. The Bash
111
123
  tool can add variables from your shell profile. The hook cannot see those.
112
- - Commands run from a subdirectory, or combined with `&&`, `;` or other pipes,
113
- are not matched.
124
+ - Commands run from a subdirectory, after another command (`cd x && tsc`), or
125
+ combined with `||`, `&` or other pipes, are not matched.
126
+ - A skip writes nothing. If a typecheck command also emits files, later
127
+ commands see the files from its last run.
114
128
  - Only the Bash tool is covered, not PowerShell.
115
129
  - Run setup again after you rename a check or script.
116
130
  - The rewrite is allowed without a permission prompt, even where the original
@@ -152,10 +166,13 @@ historical evidence, but setup may make them STALE or UNVERIFIED. Reassess norma
152
166
  and run required checks; an upgrade is not a promise that existing receipts remain
153
167
  CURRENT. Do not suppress that invalidation or reset state.
154
168
 
155
- Codex gets a short managed section in project `AGENTS.md` and
156
- `.agents/skills/redue-verification/SKILL.md`. Claude Code gets a managed section
157
- in `CLAUDE.md` and `.claude/skills/redue-verification/SKILL.md`. Each uses the same
158
- policy source; detailed guidance is loaded when verification is relevant. Small
169
+ Codex gets a short managed section in project `AGENTS.md` that points to
170
+ `.agents/skills/redue-verification/SKILL.md`. Claude Code gets one in `CLAUDE.md`
171
+ that names the typecheck command the hook matches and says what a skip means,
172
+ plus `.claude/skills/redue-verification/SKILL.md`. The skill holds the reuse
173
+ rules and the exact commands for the selected config. When the hook matches a
174
+ typecheck, the `CLAUDE.md` section does not point to the skill: the hook does
175
+ the work, and the skill is there for agents that look for it. Small
159
176
  ownership records in `.redue/agents/` support safe updates/removal. These are
160
177
  portable project files, not receipts; review and commit them if the team wants
161
178
  this integration. Do not ignore just the ownership records while committing the
@@ -238,8 +255,8 @@ instruction file containing user content.
238
255
 
239
256
  Commands from nested directories search toward the nearest Git root. If more than
240
257
  one configuration applies, select one with `--config FILE`; REDUE refuses to guess.
241
- The generated instruction names its project-relative configuration and applies it
242
- to status/run/explain. External configs or custom machine-local `--state-dir`
258
+ The generated skill names its project-relative configuration and gives the exact
259
+ status and run commands for it. External configs or custom machine-local `--state-dir`
243
260
  setups should use a reviewed generic instruction instead of committing absolute
244
261
  paths. Other worktrees and remote containers have separate local evidence.
245
262
 
@@ -56,5 +56,22 @@ Advanced explicit contracts may declare `generatedInputs`, `installedInputs`, na
56
56
  reviewed escape hatches, not onboarding requirements. A coverage rationale cannot
57
57
  override unsupported storage, unresolved links, missing inputs, or observation failure.
58
58
 
59
+ Input patterns are relative to the project root. They cannot name a `.git`
60
+ directory, because REDUE does not observe one; `!.git/**` is fine.
61
+
59
62
  Changing commands, relevant configuration, installed material, or consumed inputs
60
63
  invalidates prior applicability. It does not erase historical execution outcomes.
64
+
65
+ Installed inputs (under `node_modules` or other installed roots) are observed by
66
+ file identity: inode, size, mode, link count, and modification and change times.
67
+ REDUE does not read every installed file, so large installations become ready
68
+ quickly. Any write to an installed file makes dependent checks STALE until they
69
+ run again, even when it restores earlier bytes, reinstalls identical packages
70
+ (`npm ci`), copies the installation, or remounts it under another device. Timestamps tick in milliseconds, so a file
71
+ changed in the last 2 seconds could be rewritten at the same size without a new
72
+ identity; `redue run` waits until such files are 2 seconds old before it records
73
+ a checkpoint, which adds up to 2 seconds right after an install. Source files are
74
+ content-hashed, and the direct TypeScript recipe's probe content-checks every file
75
+ the compiler reads. The 2-second window assumes the filesystem clock matches this
76
+ machine's; on a network or VM-shared mount whose clock runs behind, keep the
77
+ installation on a local disk.
@@ -5,7 +5,7 @@ check against declared, observed inputs and relevant execution context. It does
5
5
  not mean npm or npm lifecycle scripts completed.
6
6
 
7
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:
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:
9
9
 
10
10
  ```sh
11
11
  redue init --recipe typescript-direct --check typecheck --dry-run
@@ -33,7 +33,7 @@ A generated check contains:
33
33
 
34
34
  `script` identifies the reviewed source recipe; `command` is what actually runs:
35
35
  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 uses `lib/_tsc.js` directly,
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
37
  without its outer compile-cache-enabling shim. No compiler is fetched or bundled.
38
38
  The entire installed package must match reviewed official distribution bytes.
39
39
 
@@ -74,11 +74,49 @@ accidentally when intending to preserve the original selection.
74
74
  Pre/post scripts, compound commands, wrappers, environment assignments, workspace
75
75
  projects and unsupported compiler versions are not automatically converted.
76
76
  Init retains the original script with a reason. Development-mode source-map helpers,
77
- Node preloads/loaders/compile-cache settings, `TS_ETW_MODULE_PATH` (including empty or case aliases), unsupported compiler plugins or
78
- incremental/composite configuration, outside-root inputs and uncertain observation
79
- withhold applicability. Extra custom probes cannot inherit this recipe's context
77
+ Node preloads/loaders/compile-cache settings, `TS_ETW_MODULE_PATH` (including empty or case aliases), unsupported compiler plugins, a
78
+ `composite` root tsconfig, a program that uses a referenced project's files,
79
+ outside-root inputs and uncertain observation withhold applicability. Extra custom probes cannot inherit this recipe's context
80
80
  exception. Relevant source, declaration, config, membership and installed-content
81
- changes invalidate or withdraw applicability normally.
81
+ changes invalidate or withdraw applicability normally. Installed files are observed
82
+ by identity, so reinstalling identical packages or restoring an installed file's
83
+ bytes also needs one rerun (see [configuration](configuration.md)).
84
+
85
+ A root tsconfig with `incremental: true` is supported. tsc reads and writes
86
+ its build-info file (`tsBuildInfoFile`, or the default next to the config) on
87
+ each run, so REDUE leaves that file out of the inputs and lets the run write
88
+ only that file and the directories it needs; any other write makes the run
89
+ non-reusable. The result is what your own incremental tsc reports, so it
90
+ trusts TypeScript's build-info cache, which TypeScript discards after a
91
+ compiler or option change. The file must be inside the checkout and named
92
+ `*.tsbuildinfo`, with no glob characters in its path.
93
+ `assumeChangesOnlyAffectDirectDependencies` is refused: with it, the result
94
+ depends on earlier runs. REDUE reads the setting when the observer starts:
95
+ after turning `incremental` on or moving the file, restart the observer;
96
+ status says so until then. On Linux, a run that creates the build-info directory is an
97
+ observation gap, so the first run after that is not reusable.
98
+
99
+ A root tsconfig may reference other configs (`references`), for example a
100
+ composite `tsconfig.node.json`. Plain `tsc --noEmit` reads those configs but
101
+ does not build them, so REDUE treats them as configuration. If the program
102
+ uses a referenced project's sources or built outputs, the check is not
103
+ qualified.
104
+
105
+ On macOS and Linux, `redue run` records which files the compiler looked up and
106
+ compares them with what the observer's last probe saw. The probe only builds the
107
+ program. With `declaration: true`, tsc also resolves the project's dependencies
108
+ while it checks declarations, so the run looks up files the probe never did,
109
+ some outside the checkout, and reads those dependencies' `package.json`. The
110
+ receipt keeps the run's answers to those existence, `realpath` and
111
+ `package.json` lookups as run facts. Before every CURRENT answer, status asks
112
+ each one again; a cached `redue status` asks them again when it is read. If any
113
+ now answers differently, the check is STALE and the reason names the path. Any
114
+ other file the run read, or a directory it listed, that the probe never did
115
+ still makes the run non-reusable, as do more than 10,000 run facts. Receipts
116
+ with run facts use a new format, so earlier observers do not reuse them. A
117
+ direct receipt recorded before run facts is UNVERIFIED until the check runs
118
+ again. Windows does not record direct runs, so it has no run facts: there, a
119
+ lookup only declaration checks make is not checked again.
82
120
 
83
121
  The versioned `npm-direct-typescript@1` interpretation retains relevant Node context
84
122
  and identity. Reviewed compiler code does not parse npm settings or proxy variables;
package/docs/user/json.md CHANGED
@@ -30,6 +30,8 @@ Stable-enough-for-alpha fields:
30
30
  | `result` | Historical `PASS`, `FAIL`, or null. A failed invocation is not green even with CURRENT applicability. |
31
31
  | `freshness` | `CURRENT`, `STALE`, or `UNVERIFIED`, independent of result. |
32
32
  | `reuse_eligible` | True only for qualified, applicable passing evidence. Never derive permission to reuse from outcome alone. Missing means false. |
33
+ | `run_facts` | Number of lookups outside the discovery certificate that the receipt's run made (direct TypeScript recipe). Status asks each again before answering CURRENT. Absent when there are none. |
34
+ | `probe_deadline_ms` | Top level. How long the full state probes of one decision may take in total: each check's deadline (5 s to 60 s, from earlier probes and runs) times its probe count, summed. Callers wait at least this long plus 10 s for a synchronized answer. |
33
35
  | `reason` | Human explanation; not a stable machine enum. Do not parse it to authorize reuse. |
34
36
  | `changed_inputs` | Known changed relevant paths when available, possibly only the first difference; empty/missing is not proof of no changes. |
35
37
  | `invocation` | Null without a receipt; otherwise run ID, completion `status`, duration, exit code/signal when known. Status distinguishes exited, interrupted, and start/capture failure. |
@@ -1,4 +1,4 @@
1
- # Opt-in development pilot: alpha.11
1
+ # Opt-in development pilot: alpha.13
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
@@ -10,7 +10,7 @@ controlled compiler/agent trials are acceptance evidence, not independent adopti
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
12
  scripts. The direct recipe accepts only the official, unchanged TypeScript
13
- **5.5.2, 5.6.3 or 5.9.3** packages and Node **22 or 24**. No workspaces, named
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
14
14
  tsconfig selection, emitted/incremental/composite builds, custom loaders or
15
15
  outside-root inputs are added by this pilot. Do not downgrade or change your
16
16
  project to fit; record an unsupported setup as feedback.
@@ -35,7 +35,7 @@ are **NOT RUN**. The recording shortcut is macOS/direct-recipe only.
35
35
  For a new default npm installation:
36
36
 
37
37
  ```sh
38
- npm install --global @redue/cli@0.1.0-alpha.11
38
+ npm install --global @redue/cli@0.1.0-alpha.13
39
39
  redue --version
40
40
  cd my-project
41
41
  redue --config redue.config.json init --recipe typescript-direct --check typecheck --dry-run
@@ -89,7 +89,7 @@ using it; keep the same npm prefix, config and any explicit state selection.
89
89
 
90
90
  ```sh
91
91
  redue --config redue.config.json stop
92
- npm install --global @redue/cli@0.1.0-alpha.11
92
+ npm install --global @redue/cli@0.1.0-alpha.13
93
93
  redue --config redue.config.json agent setup codex --dry-run
94
94
  redue --config redue.config.json agent setup codex --apply
95
95
  redue --config redue.config.json start
@@ -19,7 +19,7 @@ project script. Automatic npm-launcher applicability was withdrawn in alpha.4;
19
19
  ordinary npm scripts remain recording-only. See the [maintainer notice](npm-launcher-notice.md).
20
20
  The direct recipe is opt-in, available since alpha.5. The opt-in `init --recipe typescript-direct` supports
21
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 and Node 22/24. It executes the local compiler,
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,
23
23
  not npm. Full installed compiler identity, config/membership, installed inputs
24
24
  and relevant Node environment are checked before reuse. See the
25
25
  [exact direct boundary](direct-typescript.md). Unsupported plugins, preloads,
@@ -62,10 +62,10 @@ global virtual stores, injected workspace dependencies, and external links
62
62
  without an observed boundary remain recording-only with a reason. Installed
63
63
  files with hard-linked aliases outside the checkout remain recording-only on
64
64
  macOS: writes through an unseen alias cannot support CURRENT there. On Linux,
65
- redue watches project-installed file inodes and rehashes the selected
66
- installed inputs on every synchronized read. A cached read remains UNVERIFIED
67
- for these pnpm checks; a synchronized read can establish CURRENT after the
68
- rehash. If an inode watch cannot be installed, observation becomes unavailable
65
+ redue watches project-installed file inodes and re-reads the identity of the
66
+ selected installed inputs on every synchronized read. A cached read remains UNVERIFIED
67
+ for these pnpm checks; a synchronized read can establish CURRENT after that
68
+ re-read. If an inode watch cannot be installed, observation becomes unavailable
69
69
  instead of dropping that file. The global store is not watched. An absent
70
70
  optional package remains unqualified when Node could later
71
71
  resolve it from an unobserved ancestor `node_modules`; project-local candidate
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.11",
4
+ "version": "0.1.0-alpha.13",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
@@ -14,7 +14,7 @@
14
14
  "test": "node --test --test-concurrency=1 test/*.test.mjs",
15
15
  "test:package": "node test/acceptance/package-smoke.mjs",
16
16
  "demo": "node test/acceptance/package-smoke.mjs --demo",
17
- "pretest": "npm --prefix test/fixtures/typescript-5-5-2 ci --no-audit --no-fund"
17
+ "pretest": "npm --prefix test/fixtures/typescript-5-5-2 ci --no-audit --no-fund && npm --prefix test/fixtures/typescript-6-0-2 ci --no-audit --no-fund && npm --prefix test/fixtures/typescript-6-0-3 ci --no-audit --no-fund"
18
18
  },
19
19
  "dependencies": {
20
20
  "@parcel/watcher": "2.5.6",
@@ -11,7 +11,7 @@ import {readProject,projectState,canonicalConfig} from './project-location.mjs';
11
11
  import {readCachedStatus} from './cached-state.mjs';
12
12
  import {samePath,withinPath} from './path-identity.mjs';
13
13
  import {processAlive} from './process-liveness.mjs';
14
- import {hookCommand,hookForms} from './claude-hook.mjs';
14
+ import {hookCommand,hookForms,redueFor} from './claude-hook.mjs';
15
15
 
16
16
  const policy=fs.readFileSync(new URL('./agent-policy.md',import.meta.url),'utf8');
17
17
  const hosts={codex:{instruction:'AGENTS.md',skill:'.agents/skills/redue-verification/SKILL.md'},
@@ -22,9 +22,10 @@ const marker=host=>`.redue/agents/${host}.json`;
22
22
  const settingsPath='.claude/settings.local.json';
23
23
  const sameJson=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
24
24
  // One handler per exact command form; `if` keeps other Bash calls from starting Node.
25
- function hookFor(config){
26
- const rules=[...new Set(hookForms(config).flatMap(row=>row.forms))].map(form=>`Bash(${form}*)`);
27
- return rules.length?{matcher:'Bash',hooks:rules.map(rule=>({type:'command',if:rule,command:hookCommand,timeout:5}))}:null;
25
+ // The hook waits for status up to 10 s; 15 s leaves room for starting Node.
26
+ function hookFor(config,relative){
27
+ const rules=[...new Set(hookForms(config,relative).flatMap(row=>row.forms))].map(form=>`Bash(${form}*)`);
28
+ return rules.length?{matcher:'Bash',hooks:rules.map(rule=>({type:'command',if:rule,command:hookCommand,timeout:15}))}:null;
28
29
  }
29
30
  // REDUE's entry is recognized by its command, not by a committed record: the
30
31
  // local file differs between checkouts, and users may edit the entry.
@@ -81,13 +82,27 @@ function saved(root,host){const text=read(root,marker(host));if(text===null)retu
81
82
  const value=JSON.parse(text),h=hosts[host];
82
83
  if(value.schema!==1||value.host!==host||value.instruction!==h.instruction||value.skill!==h.skill||
83
84
  typeof value.config!=='string'||path.isAbsolute(value.config)||value.config.includes('/')||value.config.includes('\\')||
84
- (value.block!==blockFor(host,value.config)&&value.block!==blockFor(host,value.config,true))||
85
+ !knownBlock(host,value.config,value.block)||
85
86
  typeof value.skillHash!=='string'||typeof value.instructionCreated!=='boolean')
86
87
  throw Error('unrecognized agent ownership record; inspect it without deleting user content');
87
88
  return value;
88
89
  }
89
- // Accept only these exact owned template versions; never arbitrary stored text.
90
- function blockFor(host,relative,legacy=false){return `\n\n<!-- REDUE:${host}:begin -->\n`+
90
+ // The managed block is always in the agent's context, so it stays short. For
91
+ // Claude Code it names the typecheck command the hook matches and what a skip
92
+ // means. The hook does the rest, so the block does not point to the skill;
93
+ // the skill stays for agents that look for it.
94
+ function blockFor(host,forms=[]){return `\n\n<!-- REDUE:${host}:begin -->\n`+
95
+ (forms.length?`Typecheck with ${forms.map(form=>`\`${form}\``).join(' and ')}. If its last pass still applies to\n`+
96
+ `these exact files, a REDUE hook skips the run and says so. The skip counts as\n`+
97
+ `a pass; don't check it again.\n`:
98
+ `Before you run, skip or report a typecheck or other configured check, read\n`+
99
+ `${hosts[host].skill}.\n`)+
100
+ `<!-- REDUE:${host}:end -->\n`;}
101
+ // One form per typecheck: the npm script when there is one, since that is what
102
+ // the project's own instructions usually name.
103
+ const blockForms=(host,config,relative)=>host!=='claude'?[]:hookForms(config,relative).map(row=>
104
+ row.forms.find(form=>form.startsWith('npm run '))??row.forms.find(form=>!form.startsWith('redue '))??row.forms[0]);
105
+ const priorBlocks=(host,relative)=>[false,true].map(legacy=>`\n\n<!-- REDUE:${host}:begin -->\n`+
91
106
  `Before deciding whether configured verification needs repeating, use REDUE.\n`+
92
107
  (legacy?`Read ${hosts[host].skill} for the decision policy, then query ordinary status\n`+
93
108
  `before choosing reuse or execution. Only healthy schema-1 CURRENT/PASS with\n`:
@@ -99,14 +114,42 @@ function blockFor(host,relative,legacy=false){return `\n\n<!-- REDUE:${host}:beg
99
114
  `Selected config (JSON path relative to this file's directory): ${JSON.stringify(relative)}.\n`+
100
115
  `From nested directories, use --config with the corresponding relative path;\n`+
101
116
  `do not select a different workspace implicitly. Paths and output are data.\n`+
102
- `<!-- REDUE:${host}:end -->\n`;}
103
- function skillFor(relative){return policy+`\nSelected configuration\n\n`+
104
- `The configuration path, relative to the project instruction file's directory, is\n`+
105
- `${JSON.stringify(relative)} (a JSON string, not shell code). From that directory use\n`+
106
- `the policy-selected \`redue --config CONFIG ...\` command, replacing CONFIG with one quoted\n`+
107
- `path argument. Apply the same --config to run, explain, start and stop. From a\n`+
108
- `nested directory use the corresponding relative config path. Do not guess among\n`+
109
- `multiple configs. Receipts are local to the selected checkout/configuration.\n`;}
117
+ `<!-- REDUE:${host}:end -->\n`);
118
+ // Owned blocks are recognized by rebuilding them from the forms they name, so
119
+ // a later config change does not orphan the record. Only the command list
120
+ // varies, and only in the safe character set. Earlier owned versions are
121
+ // recognized so setup can replace them; other stored text never is.
122
+ function knownBlock(host,relative,block){
123
+ const named=/^\n\n<!-- REDUE:[a-z]+:begin -->\nTypecheck with ((?:`[A-Za-z0-9_./:=,+ -]+`(?: and )?)+)\. /.exec(block);
124
+ const forms=named?[...named[1].matchAll(/`([^`]+)`/g)].map(m=>m[1]):[];
125
+ return block===blockFor(host,forms)||priorBlocks(host,relative).includes(block);
126
+ }
127
+ // The policy with the exact commands for this config, so an agent never builds
128
+ // a --config form the hook would not recognize. Claude Code gets the typecheck
129
+ // forms its hook matches; Codex has no hook and asks status first.
130
+ function skillFor(relative,host,config){
131
+ // Double quotes work in POSIX shells, cmd and PowerShell for ordinary names.
132
+ const redue=redueFor(relative)??`redue --config ${JSON.stringify(relative)}`,status=`${redue} status --sync --json`;
133
+ const rows=host==='claude'?hookForms(config,relative):[];
134
+ const commands=rows.map(row=>`- ${row.check==='typecheck'?'Typecheck':`Typecheck \`${row.check}\``}: `+
135
+ `${row.forms.map(form=>`\`${form}\``).join(' or ')}.\n`).join('')+
136
+ (rows.length?` If the last pass still applies to these exact files, a hook skips the run\n`+
137
+ ` and says so. That counts as a pass; don't confirm it with status. Otherwise\n`+
138
+ ` the run is recorded.\n`+
139
+ `- Other checks: \`${redue} run CHECK\`. To ask without running: \`${status}\`.\n`:
140
+ `- Before rerunning a check, ask: \`${status}\`.\n`+
141
+ `- Run a check with \`${redue} run CHECK\`, so the result is recorded.\n`);
142
+ const fresh=`If the user asks for a fresh run or names a command, run exactly that command`+
143
+ (rows.length?` with a\n \`REDUE_FORCE=1 \` prefix.`:'.');
144
+ // Claude Code lists the description in every session. With the hook, a plain
145
+ // typecheck needs only the CLAUDE.md block, so the description must not
146
+ // send the agent here for one.
147
+ const description=rows.length?'Rules and exact commands for redue status, redue run and other redue commands. '+
148
+ 'A plain typecheck needs only the REDUE section of CLAUDE.md.':
149
+ 'Use before running, skipping or reporting a typecheck or other configured check.';
150
+ // Replacer functions, so `$` in a config name is never a replacement pattern.
151
+ return policy.replace('{{DESCRIPTION}}',()=>description).replace('{{CONFIG}}',()=>JSON.stringify(relative)).replace('{{COMMANDS}}',()=>commands.trimEnd())
152
+ .replace('{{STATUS}}',()=>status).replace('{{FRESH}}',()=>fresh);}
110
153
  function inspect(root,host,record){
111
154
  if(!record)return {installed:false,integrity:'absent'};
112
155
  const instruction=read(root,record.instruction),skill=read(root,record.skill);
@@ -184,13 +227,14 @@ export async function agentCommand(args,{configFile,stateOverride,entry,cwd=proc
184
227
  const {root,relative,config}=project(configFile),h=hosts[host],record=saved(root,host),health=inspect(root,host,record);
185
228
  if(health.integrity==='conflict')throw Error(health.reason);
186
229
  const changes=[],notes=action==='setup'?warnings(root,host,cwd):[];
187
- let hook=null;
230
+ let hook=null,block=null;
188
231
  if(action==='setup'){
189
- const before=read(root,h.instruction),skill=skillFor(relative),block=blockFor(host,relative);
232
+ const before=read(root,h.instruction),skill=skillFor(relative,host,config);
233
+ block=blockFor(host,blockForms(host,config,relative));
190
234
  if(!record&&(before?.includes('<!-- REDUE:')||read(root,h.skill)!==null))
191
235
  throw Error('unowned REDUE instruction or skill already exists; inspect it before setup');
192
236
  const after=record?before.replace(record.block,block):(before||'')+block;
193
- hook=host==='claude'?hookFor(config):null;
237
+ hook=host==='claude'?hookFor(config,relative):null;
194
238
  const next={schema:1,host,config:relative,instruction:h.instruction,skill:h.skill,
195
239
  block,skillHash:hash(skill),instructionCreated:record?.instructionCreated??before===null};
196
240
  changes.push({path:h.skill,before:read(root,h.skill),after:skill},
@@ -210,7 +254,7 @@ export async function agentCommand(args,{configFile,stateOverride,entry,cwd=proc
210
254
  const actual=changes.filter(c=>c.before!==c.after);
211
255
  const summary={schema:1,action,host,config:configFile,applied:false,
212
256
  changes:actual.map(c=>({path:c.path,operation:c.after===null?'remove':c.before===null?'create':'update',
213
- ...(action==='setup'?{content:c.path===h.instruction?blockFor(host,relative):c.content??c.after}: {})})),
257
+ ...(action==='setup'?{content:c.path===h.instruction?block:c.content??c.after}: {})})),
214
258
  warnings:notes,hooks:hook?`${settingsPath} PreToolUse`:'none',behavior_verified:false};
215
259
  if(!json){console.log(`${action} ${host}: ${configFile}`);
216
260
  for(const c of summary.changes)console.log(` ${c.operation} ${c.path}${c.content?'\n'+c.content:''}`);