@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +180 -0
  3. package/README.md +126 -2
  4. package/bin/redue.mjs +329 -0
  5. package/bin/vstate.mjs +3 -0
  6. package/docs/README.md +20 -0
  7. package/docs/user/agents.md +32 -0
  8. package/docs/user/configuration.md +57 -0
  9. package/docs/user/getting-started.md +127 -0
  10. package/docs/user/json.md +59 -0
  11. package/docs/user/receipt-selection-notice.md +60 -0
  12. package/docs/user/shell.md +25 -0
  13. package/docs/user/supported-projects.md +89 -0
  14. package/examples/redue.config.json +8 -0
  15. package/package.json +46 -4
  16. package/src/cached-state.mjs +42 -0
  17. package/src/cli.mjs +401 -0
  18. package/src/control-endpoint.mjs +11 -0
  19. package/src/daemon.mjs +1004 -0
  20. package/src/decision-profile.mjs +18 -0
  21. package/src/decision-validation.mjs +41 -0
  22. package/src/declared-plan.mjs +125 -0
  23. package/src/executable-lookup.mjs +18 -0
  24. package/src/glob.mjs +9 -0
  25. package/src/history-worker.mjs +18 -0
  26. package/src/index.mjs +306 -0
  27. package/src/installed-inputs.mjs +110 -0
  28. package/src/linux-inotify.py +228 -0
  29. package/src/linux-observer.mjs +79 -0
  30. package/src/node-onboarding.mjs +263 -0
  31. package/src/npm-typecheck-probe.mjs +120 -0
  32. package/src/observation.mjs +37 -0
  33. package/src/owned-state.mjs +32 -0
  34. package/src/path-identity.mjs +43 -0
  35. package/src/plan-worker.mjs +131 -0
  36. package/src/plan.mjs +10 -0
  37. package/src/platform-observation.mjs +17 -0
  38. package/src/platform-state.mjs +14 -0
  39. package/src/pnpm-inputs.mjs +355 -0
  40. package/src/pnpm-typecheck-probe.mjs +47 -0
  41. package/src/presentation.mjs +52 -0
  42. package/src/probe-result.mjs +21 -0
  43. package/src/process-liveness.mjs +11 -0
  44. package/src/run-lock.mjs +77 -0
  45. package/src/state-store.mjs +73 -0
  46. package/src/toolchain-probe.mjs +21 -0
  47. package/src/typescript-contract-probe.mjs +188 -0
  48. package/src/typescript-list.mjs +68 -0
  49. package/src/typescript-qualification.mjs +96 -0
  50. package/src/windows-command.mjs +16 -0
  51. package/src/windows-process.mjs +15 -0
  52. package/src/yarn-workspace-inputs.mjs +148 -0
  53. 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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "schema": 1,
3
+ "checks": [{
4
+ "name": "migration",
5
+ "command": ["@node", "tools/check-migrations.mjs"],
6
+ "inputs": ["migrations/**", "tools/check-migrations.mjs"]
7
+ }]
8
+ }
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@redue/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }