@redue/cli 0.0.0-stage → 0.1.0-alpha.3
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 +34 -0
- package/LICENSE +180 -0
- package/README.md +126 -2
- package/bin/redue.mjs +329 -0
- package/bin/vstate.mjs +3 -0
- package/docs/README.md +20 -0
- package/docs/user/agents.md +32 -0
- package/docs/user/configuration.md +57 -0
- package/docs/user/getting-started.md +127 -0
- package/docs/user/json.md +59 -0
- package/docs/user/receipt-selection-notice.md +60 -0
- package/docs/user/shell.md +25 -0
- package/docs/user/supported-projects.md +89 -0
- package/examples/redue.config.json +8 -0
- package/package.json +46 -4
- package/src/cached-state.mjs +42 -0
- package/src/cli.mjs +401 -0
- package/src/control-endpoint.mjs +11 -0
- package/src/daemon.mjs +1004 -0
- package/src/decision-profile.mjs +18 -0
- package/src/decision-validation.mjs +41 -0
- package/src/declared-plan.mjs +125 -0
- package/src/executable-lookup.mjs +18 -0
- package/src/glob.mjs +9 -0
- package/src/history-worker.mjs +18 -0
- package/src/index.mjs +306 -0
- package/src/installed-inputs.mjs +110 -0
- package/src/linux-inotify.py +228 -0
- package/src/linux-observer.mjs +79 -0
- package/src/node-onboarding.mjs +263 -0
- package/src/npm-typecheck-probe.mjs +120 -0
- package/src/observation.mjs +37 -0
- package/src/owned-state.mjs +32 -0
- package/src/path-identity.mjs +43 -0
- package/src/plan-worker.mjs +131 -0
- package/src/plan.mjs +10 -0
- package/src/platform-observation.mjs +17 -0
- package/src/platform-state.mjs +14 -0
- package/src/pnpm-inputs.mjs +355 -0
- package/src/pnpm-typecheck-probe.mjs +47 -0
- package/src/presentation.mjs +52 -0
- package/src/probe-result.mjs +21 -0
- package/src/process-liveness.mjs +11 -0
- package/src/run-lock.mjs +77 -0
- package/src/state-store.mjs +73 -0
- package/src/toolchain-probe.mjs +21 -0
- package/src/typescript-contract-probe.mjs +188 -0
- package/src/typescript-list.mjs +68 -0
- package/src/typescript-qualification.mjs +96 -0
- package/src/windows-command.mjs +16 -0
- package/src/windows-process.mjs +15 -0
- package/src/yarn-workspace-inputs.mjs +148 -0
- package/src/yarn-workspace-typecheck-probe.mjs +107 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Receipt selection correction in alpha.3
|
|
2
|
+
|
|
3
|
+
**Known affected release: `@redue/cli@0.1.0-alpha.2`. Fixed in
|
|
4
|
+
`@redue/cli@0.1.0-alpha.3`.** No exploit or affected-user count is established.
|
|
5
|
+
REDUE remains an alpha.
|
|
6
|
+
|
|
7
|
+
If a wrapper persisted a newer outcome but its observer reload notification
|
|
8
|
+
failed, the observer could continue selecting an older passing receipt. Cached
|
|
9
|
+
and synchronized status could then present that older result as CURRENT and
|
|
10
|
+
eligible for reuse. A separate reproduced write-failure window left a new
|
|
11
|
+
immutable outcome recorded while replacing the selected receipt failed.
|
|
12
|
+
|
|
13
|
+
Until upgraded, do not rely solely on affected CURRENT output to skip checks.
|
|
14
|
+
Run verification when needed and inspect its actual outcome; a successful status
|
|
15
|
+
command is not proof that the latest execution passed.
|
|
16
|
+
|
|
17
|
+
## What changes
|
|
18
|
+
|
|
19
|
+
The observer refreshes REDUE-owned receipt selection before publishing status.
|
|
20
|
+
Clients check that a response represents the current readable selection. Missing,
|
|
21
|
+
unreadable, malformed, incompatible, or replaced selection cannot authorize
|
|
22
|
+
reuse. A new client connecting to an alpha.2 observer returns UNVERIFIED and
|
|
23
|
+
asks for a restart, even when the old response says CURRENT.
|
|
24
|
+
|
|
25
|
+
A small owned pending-update record also withholds applicability after an
|
|
26
|
+
incomplete receipt write. Successful recording clears only that check's pending
|
|
27
|
+
entry. It does not discard another check's incomplete outcome or automatically
|
|
28
|
+
promote an orphaned journal entry. Inspect the failed run and storage error, fix
|
|
29
|
+
the storage problem, then record that check again. An unreadable/malformed pending
|
|
30
|
+
record requires operator inspection; do not delete receipts to obtain green status.
|
|
31
|
+
|
|
32
|
+
Historical PASS/FAIL and immutable `runs-v1` files remain intact. Stable applicable
|
|
33
|
+
FAIL remains a failure and is never reuse-eligible. This guard tracks owned
|
|
34
|
+
receipt replacement; it does not replace input-content validation or promise an
|
|
35
|
+
atomic decision against concurrent edits/writes.
|
|
36
|
+
|
|
37
|
+
## Upgrade and restart
|
|
38
|
+
|
|
39
|
+
From each initialized project, stop its observer before replacing the installation.
|
|
40
|
+
After all observers for that installation have stopped, upgrade it once, then
|
|
41
|
+
restart each observer with its existing configuration and state selection:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
redue stop
|
|
45
|
+
npm install --global @redue/cli@0.1.0-alpha.3
|
|
46
|
+
redue --version
|
|
47
|
+
redue start
|
|
48
|
+
redue status --sync --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use the same npm `--prefix`, project `--config`, and `--state-dir` you already use,
|
|
52
|
+
if customized. On Windows use `npm.cmd`/`redue.cmd` where required by the existing
|
|
53
|
+
shell policy. Stop all observers belonging to the installation before replacing
|
|
54
|
+
it, then start each with the upgraded binary. For an independently installed
|
|
55
|
+
candidate, its `stop` command can stop the older observer in the same explicitly
|
|
56
|
+
selected owned state. Do not run `remove-state`; receipts need not be deleted.
|
|
57
|
+
|
|
58
|
+
Restart preserves historical receipts and reconciles inputs. It does not promise
|
|
59
|
+
CURRENT: a newer failure, unstable execution, incomplete contract, changed input,
|
|
60
|
+
or observation uncertainty remains visible under the normal rules.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Shell usage
|
|
2
|
+
|
|
3
|
+
Run `redue start` once for the project/configuration you want to observe. A shell,
|
|
4
|
+
terminal multiplexer, and agent using that same config share its backend. Closing
|
|
5
|
+
the terminal does not stop the observer. `redue stop` does.
|
|
6
|
+
|
|
7
|
+
`redue status --short` is the intended small indicator:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
REDUE UNVERIFIED
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It reads the cached snapshot, checks its expiry and observer liveness, and never
|
|
14
|
+
synchronizes, traverses installed packages, or runs probes/checks. Like ordinary
|
|
15
|
+
status, it can remain conservative when caller context or installed hardlinks
|
|
16
|
+
need a synchronized read. Do not turn that uncertainty into a green badge.
|
|
17
|
+
|
|
18
|
+
For an existing async/bounded prompt framework, use `redue status --short` from
|
|
19
|
+
the project root as the segment command and display unavailable on errors or
|
|
20
|
+
timeout. Node startup and configuration parsing still have a cost. REDUE does
|
|
21
|
+
not install a shell hook, edit global shell files, or provide its own prompt
|
|
22
|
+
scheduler. Running it manually is supported in PowerShell and POSIX shells.
|
|
23
|
+
|
|
24
|
+
Never put `status --sync` or `explain` in a prompt. Use those deliberately when
|
|
25
|
+
you need a decision and synchronization is worth its cost.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Supported Node projects
|
|
2
|
+
|
|
3
|
+
`init` detects npm from `package-lock.json`/`npm-shrinkwrap.json` or an npm
|
|
4
|
+
`packageManager` declaration. It detects Yarn from `yarn.lock` or its
|
|
5
|
+
declaration and reads `.yarnrc.yml` to distinguish node-modules/classic from
|
|
6
|
+
PnP and other modes. A Yarn v1 lockfile can identify the classic layout, but
|
|
7
|
+
without a declared Yarn version `init` does not create a runnable config:
|
|
8
|
+
Corepack can select a different version and edit `package.json`. Conflicting
|
|
9
|
+
package managers are reported without pretending that installation coverage
|
|
10
|
+
is known. When npm is not pinned in `package.json`,
|
|
11
|
+
`init` reads the local npm executable's package manifest if available; it
|
|
12
|
+
leaves the version unknown rather than guessing if that inspection fails.
|
|
13
|
+
|
|
14
|
+
At the repository root, `init` looks for existing typecheck, test, lint, and
|
|
15
|
+
build scripts. It prefers familiar script names, then unique recognizable
|
|
16
|
+
`tsc`, Vitest/Jest, ESLint, or Vite commands under other names. Multiple
|
|
17
|
+
matches are reported as ambiguous rather than guessed. It never creates a new
|
|
18
|
+
project script. It recognizes canonical
|
|
19
|
+
`tsc --noEmit` on local npm installations as a candidate for automatic
|
|
20
|
+
qualification. Before CURRENT is possible, a read-only probe checks the
|
|
21
|
+
TypeScript configuration and effective file list, local compiler, installed
|
|
22
|
+
contents, npm/Node identity, and relevant caller context. Unsupported plugins,
|
|
23
|
+
preloads, config graphs, or outside-root inputs leave the check UNVERIFIED with
|
|
24
|
+
an explanation. A discovered script can still be run and recorded in that
|
|
25
|
+
state. Root and package-level npm/Yarn workspace scripts are discovered from
|
|
26
|
+
`package.json` workspace declarations. Pinned Yarn 4 `node-modules`/classic
|
|
27
|
+
workspaces with a standalone `tsc -p .` script, local TypeScript, a conventional
|
|
28
|
+
`src` include, and Node module resolution can qualify automatically. For one
|
|
29
|
+
workspace, use `redue init --workspace NAME --check typecheck`; it writes only
|
|
30
|
+
that check and keeps derived relationships out of project configuration. The
|
|
31
|
+
contract observes selected `src` membership, root/package TypeScript config,
|
|
32
|
+
built declarations of internal workspace dependencies, and physical installed
|
|
33
|
+
files. It rechecks TypeScript's effective input list with a read-only probe.
|
|
34
|
+
A sibling workspace is not an input merely because it shares the repository.
|
|
35
|
+
When an internal dependency is consumed through built declarations, changing
|
|
36
|
+
only its source does not stale the selected check until the consumed output
|
|
37
|
+
changes. Unsupported custom resolution, project references, outside-root
|
|
38
|
+
source links, and installation modes remain recording-only. Other Yarn
|
|
39
|
+
typechecks, Vitest/Jest, ESLint, and builds start recording-only unless a
|
|
40
|
+
separately justified explicit contract is provided.
|
|
41
|
+
In particular, test caches and build-generated inputs are not excluded to
|
|
42
|
+
make evidence green.
|
|
43
|
+
|
|
44
|
+
Pinned pnpm 12 projects are read from `packageManager`, `pnpm-workspace.yaml`,
|
|
45
|
+
the lockfile, and the installed `node_modules/.modules.yaml` metadata. The
|
|
46
|
+
supported local layouts are pnpm's standard isolated and hoisted
|
|
47
|
+
`node_modules` with a project-local `.pnpm` virtual store. `init` can discover
|
|
48
|
+
root and workspace scripts. For a standalone `tsc --noEmit` or `tsc -p ...`
|
|
49
|
+
script in the narrow TypeScript contract, it records the exact installed
|
|
50
|
+
compiler invocation directly. This avoids pnpm 12's task-run bookkeeping
|
|
51
|
+
writes during `pnpm run`; it does not alter the project's script. Test, lint,
|
|
52
|
+
build, and ambiguous TypeScript scripts still execute through `pnpm run` and
|
|
53
|
+
start recording-only. The generated `@typescript-bin:WORKSPACE` token resolves
|
|
54
|
+
the local compiler when the check runs; it is not an absolute machine path.
|
|
55
|
+
|
|
56
|
+
The pnpm contract indexes consumed installed package contents inside the
|
|
57
|
+
project, selected workspace source, linked workspace declaration outputs,
|
|
58
|
+
relevant manifests, configuration, and installation metadata. The global pnpm
|
|
59
|
+
store is neither watched nor deleted. PnP, custom modules/virtual-store roots,
|
|
60
|
+
global virtual stores, injected workspace dependencies, and external links
|
|
61
|
+
without an observed boundary remain recording-only with a reason. Installed
|
|
62
|
+
files with hard-linked aliases outside the checkout remain recording-only on
|
|
63
|
+
macOS: writes through an unseen alias cannot support CURRENT there. On Linux,
|
|
64
|
+
redue watches project-installed file inodes and rehashes the selected
|
|
65
|
+
installed inputs on every synchronized read. A cached read remains UNVERIFIED
|
|
66
|
+
for these pnpm checks; a synchronized read can establish CURRENT after the
|
|
67
|
+
rehash. If an inode watch cannot be installed, observation becomes unavailable
|
|
68
|
+
instead of dropping that file. The global store is not watched. An absent
|
|
69
|
+
optional package remains unqualified when Node could later
|
|
70
|
+
resolve it from an unobserved ancestor `node_modules`; project-local candidate
|
|
71
|
+
locations are still tracked for plan changes. No configuration assertion is
|
|
72
|
+
generated to hide either gap.
|
|
73
|
+
|
|
74
|
+
An unobservable source symlink or missing Git checkout is reported by `init`
|
|
75
|
+
before writing a config. A local explicit configuration can grant a narrow
|
|
76
|
+
external observation root after review; `init` will not add machine-specific
|
|
77
|
+
paths to a project config automatically.
|
|
78
|
+
|
|
79
|
+
The generated config is project-relative and versioned. Runtime state and
|
|
80
|
+
receipts live separately. `packageManager`, `checks[].script`, `kind`,
|
|
81
|
+
`inputs`, optional `workspace`, and optional `qualification` are the generated
|
|
82
|
+
public fields. The
|
|
83
|
+
CLI also accepts `checks[].command` as exact argv for arbitrary checks;
|
|
84
|
+
`@node`, `@project`, `@which:NAME`, and the narrow
|
|
85
|
+
`@typescript-bin:WORKSPACE` token are resolved at invocation. A command
|
|
86
|
+
or input-declaration change invalidates previous evidence.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Windows supports the same qualified contracts on local NTFS, with project-visible installed hardlinks revalidated during synchronized reads. Cached hardlink-sensitive applicability is conservative. No package-manager or platform promises that synchronization is cheaper than execution.
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redue/cli",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.0-alpha.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"tag": "alpha"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"redue": "./bin/redue.mjs"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test --test-concurrency=1 test/*.test.mjs",
|
|
15
|
+
"test:package": "node test/acceptance/package-smoke.mjs",
|
|
16
|
+
"demo": "node test/acceptance/package-smoke.mjs --demo"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@parcel/watcher": "2.5.6",
|
|
20
|
+
"picomatch": "2.3.2",
|
|
21
|
+
"yaml": "2.8.3"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "5.6.3"
|
|
28
|
+
},
|
|
29
|
+
"description": "Persistent verification state for developers and coding agents.",
|
|
30
|
+
"homepage": "https://github.com/msiric/redue",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/msiric/redue.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/msiric/redue/issues"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"bin/",
|
|
40
|
+
"src/",
|
|
41
|
+
"examples/",
|
|
42
|
+
"docs/user/",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
47
|
+
"license": "Apache-2.0"
|
|
48
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The same fail-conservative cached reader serves the CLI and execution layer.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {processAlive} from './process-liveness.mjs';
|
|
5
|
+
import {readReceipts,readReceiptSnapshot,receiptRevision} from './state-store.mjs';
|
|
6
|
+
const unknown=(reason,checks)=>({schema:1,state:'unverified',current:0,stale:0,failed:0,observed_failed:0,
|
|
7
|
+
unverified:checks.length,
|
|
8
|
+
checks:checks.map(c=>({name:c.name,result:null,freshness:'UNVERIFIED',reason,
|
|
9
|
+
invocation:null,changed_inputs:[],reuse_eligible:false})),
|
|
10
|
+
observation:{healthy:false,reason}});
|
|
11
|
+
export function unavailableCached(state,reason,checks=[]) {
|
|
12
|
+
let value;try{value=JSON.parse(fs.readFileSync(path.join(state,'status.json')));}
|
|
13
|
+
catch{value=unknown(reason,checks);}
|
|
14
|
+
if(!value||!Array.isArray(value.checks))value=unknown(reason,checks);
|
|
15
|
+
let receipts=null,revision=null;
|
|
16
|
+
try{const snapshot=readReceiptSnapshot(state);receipts=snapshot.value.checks;revision=snapshot.revision;}
|
|
17
|
+
catch(error){reason+=`; latest receipt unavailable; prior outcome only: ${error.message}`;
|
|
18
|
+
try{receipts=readReceipts(state).checks;}catch{/* retain cached historical rows */}}
|
|
19
|
+
const rows=(value.checks||[]).map(row=>{
|
|
20
|
+
const latest=receipts?.[row.name];
|
|
21
|
+
return {...row,...(receipts?{result:latest?.result||null,invocation:latest?.invocation||null,
|
|
22
|
+
target_provenance:latest?.target||null,coverage_at_run:latest?.coverage||null}:{}),
|
|
23
|
+
freshness:'UNVERIFIED',reason,reuse_eligible:false,declared_inputs_match:null};
|
|
24
|
+
});
|
|
25
|
+
return {...value,receipt_revision:revision,state:'unverified',current:0,stale:0,failed:0,
|
|
26
|
+
observed_failed:rows.filter(row=>row.result==='FAIL').length,
|
|
27
|
+
unverified:Math.max(1,rows.length),checks:rows,
|
|
28
|
+
observation:{...value.observation,healthy:false,reason}};
|
|
29
|
+
}
|
|
30
|
+
export function readCachedStatus(state,checks=[]) {
|
|
31
|
+
try{const value=JSON.parse(fs.readFileSync(path.join(state,'status.json')));
|
|
32
|
+
if(typeof value.receipt_revision!=='string')
|
|
33
|
+
return unavailableCached(state,'receipt selection is unconfirmed; synchronize status or restart an older observer',checks);
|
|
34
|
+
if(value.receipt_revision!==receiptRevision(state))
|
|
35
|
+
return unavailableCached(state,'receipt selection changed; synchronize status to evaluate the latest outcome',checks);
|
|
36
|
+
if(Date.now()>=value.updated_at&&Date.now()<=value.expires_at&&
|
|
37
|
+
value.expires_at-value.updated_at<=3000){
|
|
38
|
+
if(processAlive(value.pid))return value;
|
|
39
|
+
return unavailableCached(state,'observer process unavailable',checks);}
|
|
40
|
+
}catch{}
|
|
41
|
+
return unavailableCached(state,'observer heartbeat expired or unavailable',checks);
|
|
42
|
+
}
|