@redue/cli 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +29 -0
- package/bin/redue.mjs +7 -1
- package/docs/user/agents.md +64 -19
- package/docs/user/direct-typescript.md +2 -2
- package/docs/user/getting-started.md +5 -3
- package/docs/user/supported-projects.md +1 -1
- package/package.json +3 -2
- package/src/agent-integration.mjs +9 -5
- package/src/agent-policy.md +25 -7
- package/src/cli.mjs +7 -6
- package/src/daemon.mjs +24 -12
- package/src/decision-profile.mjs +15 -1
- package/src/direct-typescript-probe.mjs +13 -4
- package/src/direct-typescript.mjs +5 -1
- package/src/history-worker.mjs +5 -0
- package/src/observation.mjs +4 -2
- package/src/plan-worker.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.8 — 2026-10-08
|
|
4
|
+
|
|
5
|
+
- Consume compiler-certificate input asynchronously while preserving the input-size
|
|
6
|
+
limit, parent deadline, complete-input validation and conservative fallbacks.
|
|
7
|
+
- Support the exact reviewed TypeScript 5.5.2 distribution in the explicitly selected
|
|
8
|
+
single-package npm direct-compiler recipe. Enforce implementation membership and
|
|
9
|
+
reject ETW module overrides before compiler API loading.
|
|
10
|
+
- Real independent Codex/macOS sessions reused compiler evidence on the pinned
|
|
11
|
+
vscode-js-debug project. Cross-platform CLI/package acceptance is separate;
|
|
12
|
+
Claude behavior and cross-host handoff remain untested.
|
|
13
|
+
- The measured eight-case workflow avoided four compiler executions but cost
|
|
14
|
+
20.01 seconds versus 16.97 seconds for eight ordinary compiler runs. No net
|
|
15
|
+
workflow or whole-agent speedup is claimed. This verifies compiler inputs only,
|
|
16
|
+
not npm lifecycle behavior or application correctness.
|
|
17
|
+
- Stop observers before upgrading, retain the same configuration/state, then
|
|
18
|
+
restart. Do not delete receipts or automatically convert existing checks.
|
|
19
|
+
|
|
20
|
+
## 0.1.0-alpha.7 — 2026-10-07
|
|
21
|
+
|
|
22
|
+
- Guide explicitly configured direct-compiler reuse through one caller-aware
|
|
23
|
+
synchronized query; skip reuse queries when fresh execution is already required.
|
|
24
|
+
- Update intact project-local agent instructions safely; installation alone does
|
|
25
|
+
not update them. See the [existing-user procedure](docs/user/agents.md#update-an-existing-connection).
|
|
26
|
+
- In the finite Codex/macOS comparison, revised guidance reached eligible evidence
|
|
27
|
+
and reused it in 4/4 opportunities versus 1/4; verification tool calls fell
|
|
28
|
+
from 28 to 14. Evidence semantics are unchanged. No universal compliance,
|
|
29
|
+
tail-latency or causal agent-task speedup is claimed. Claude behavior and
|
|
30
|
+
cross-host handoff remain untested.
|
|
31
|
+
|
|
3
32
|
## 0.1.0-alpha.6 — 2026-10-07
|
|
4
33
|
|
|
5
34
|
- Reuse guarded compiler discovery for warm direct-TypeScript decisions on macOS
|
package/bin/redue.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import {mark,traceBase,traceContext,traceEnvironment,traceId} from '../src/decision-profile.mjs';
|
|
4
|
+
traceBase({...traceContext(),request:traceId(),work:'client'});
|
|
5
|
+
mark('client.loaded');
|
|
3
6
|
import {directCompiler,directQualification,directEnvironment,directInterpretation} from '../src/direct-typescript.mjs';
|
|
4
7
|
import os from 'node:os';
|
|
5
8
|
import path from 'node:path';
|
|
@@ -154,6 +157,7 @@ function printLifecycle(output,action,machine){
|
|
|
154
157
|
if(stateOverride&&['stop','remove-state'].includes(command)){
|
|
155
158
|
const runtime=path.join(stateOverride,'project-runtime-v1.json');
|
|
156
159
|
if(!fs.existsSync(runtime)){console.log('No REDUE state exists at this path.');process.exit(0);}
|
|
160
|
+
mark('client.engine_submit');
|
|
157
161
|
const result=spawnSync(process.execPath,[engine,runtime,command==='stop'?'stop':'uninstall'],
|
|
158
162
|
{cwd:os.tmpdir(),stdio:['inherit','pipe','inherit'],encoding:'utf8',env:process.env});
|
|
159
163
|
if(result.error)error(result.error.message);
|
|
@@ -334,9 +338,11 @@ if(command==='run'){
|
|
|
334
338
|
if(outcome.error)error(outcome.error.message);
|
|
335
339
|
process.exit(outcome.status??(outcome.signal?128+os.constants.signals[outcome.signal]:1));
|
|
336
340
|
}
|
|
341
|
+
mark('client.engine_submit');
|
|
337
342
|
const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
|
|
338
343
|
{stdio:['inherit','pipe','inherit'],
|
|
339
|
-
encoding:'utf8',env:
|
|
344
|
+
encoding:'utf8',env:traceEnvironment()});
|
|
345
|
+
mark('client.engine_complete',{exit:result.status,signal:result.signal});
|
|
340
346
|
if(result.error)error(result.error.message);
|
|
341
347
|
if(result.stdout){
|
|
342
348
|
if(['status','detail','explain'].includes(command)){
|
package/docs/user/agents.md
CHANGED
|
@@ -3,18 +3,27 @@
|
|
|
3
3
|
Use the CLI with explicit project-local activation below, or add this policy to a
|
|
4
4
|
generic agent's instructions with the project owner's permission.
|
|
5
5
|
|
|
6
|
-
1.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
1. If fresh execution is already required, execute it; do not synchronize merely
|
|
7
|
+
to confirm an irrelevant reuse opportunity. Use `redue run CHECK` when recording
|
|
8
|
+
the configured check. An explicit npm-script request still requires npm.
|
|
9
|
+
2. For reuse of the explicitly configured direct compiler recipe
|
|
10
|
+
(`qualification: "npm-typescript-direct-v1"`, direct compiler command; receipt
|
|
11
|
+
`verification_recipe: "npm-direct-typescript@1"`), query once:
|
|
12
|
+
`redue --config CONFIG status --sync --json`. Do not infer a recipe from its name.
|
|
13
|
+
Cached status cannot establish its caller context. In mixed configurations,
|
|
14
|
+
assess each check's contract separately; this is not a global sync-first policy.
|
|
15
|
+
3. For other/unknown recipes, query ordinary `status --json` first and decide whether
|
|
16
|
+
synchronization is worthwhile. Ordinary status and `--short` remain cheap inspection.
|
|
17
|
+
4. Reuse only schema 1, healthy observation and the selected check's CURRENT/PASS
|
|
18
|
+
with `reuse_eligible: true`. Missing eligibility means false. A historical FAIL
|
|
19
|
+
is never green evidence. Do not rerun eligible evidence merely for freshness.
|
|
20
|
+
5. STALE requires execution when verification is needed. UNVERIFIED requires an
|
|
21
|
+
honest choice of synchronization, permitted execution or reporting the limitation.
|
|
22
|
+
Recording-only success can remain UNVERIFIED. A deliberate cheap-check rerun is
|
|
23
|
+
permitted; there are no universal platform/package-manager timing thresholds.
|
|
24
|
+
6. Use a successful synchronized response for the current decision without repeated
|
|
25
|
+
sync/explain calls unless edits, context changes or missing information require it.
|
|
26
|
+
Do not loop until green or use an earlier response after a later query fails.
|
|
18
27
|
|
|
19
28
|
`redue explain CHECK --json` gives synchronized detail, including a known changed
|
|
20
29
|
input when available. It has the same cost caveat as synchronized status.
|
|
@@ -37,9 +46,9 @@ CURRENT. Do not delete state or reset receipts to make the output green.
|
|
|
37
46
|
REDUE tells you what evidence exists and whether it still applies. It does not
|
|
38
47
|
promise that establishing applicability is cheaper than every possible check.
|
|
39
48
|
|
|
40
|
-
## Connect once (
|
|
49
|
+
## Connect once (experimental)
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
Alpha.5 and later support opt-in project-local activation and direct
|
|
43
52
|
TypeScript evidence, with fresh-session reuse demonstrated for Codex CLI 0.160.1
|
|
44
53
|
on macOS under the documented owner-approved checkout/state/socket route. CLI
|
|
45
54
|
mechanics are tested on macOS/Linux/Windows; model behavior is not claimed for
|
|
@@ -47,7 +56,7 @@ every host/platform or sandbox policy. Claude setup/removal mechanics are tested
|
|
|
47
56
|
but Claude behavior and cross-host reuse remain untested. Reuse is not guaranteed,
|
|
48
57
|
and the existing subsecond example establishes no meaningful net time saving.
|
|
49
58
|
|
|
50
|
-
After installing
|
|
59
|
+
After installing REDUE and running `redue init` in a trusted project:
|
|
51
60
|
|
|
52
61
|
```sh
|
|
53
62
|
redue agent setup codex --dry-run
|
|
@@ -62,6 +71,42 @@ setup requires `--apply`. `--dry-run` never writes. Installing REDUE or running
|
|
|
62
71
|
`init` does not connect an agent automatically. Setup does not start an observer
|
|
63
72
|
or execute verification. Use `redue start` when you want observation.
|
|
64
73
|
|
|
74
|
+
## Update an existing connection
|
|
75
|
+
|
|
76
|
+
Installing a newer package does **not** rewrite project instructions. From the
|
|
77
|
+
same project and installation, stop every observer using that installation before
|
|
78
|
+
replacing it. Keep the original config and state selection; do not create a second
|
|
79
|
+
config or delete receipts. Example for a default npm installation/config:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
redue --config redue.config.json stop
|
|
83
|
+
npm install --global @redue/cli@alpha
|
|
84
|
+
redue --config redue.config.json agent setup codex --dry-run
|
|
85
|
+
redue --config redue.config.json agent setup codex --apply
|
|
86
|
+
redue --config redue.config.json start
|
|
87
|
+
redue --config redue.config.json status --sync --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use the **same `--prefix`** as the existing npm installation when it has a custom
|
|
91
|
+
prefix, and invoke its corresponding `redue` binary. Keep your actual config path
|
|
92
|
+
on every command. For Claude, select `claude` in both setup commands. Preserve any
|
|
93
|
+
existing `--state-dir` on lifecycle/status commands; setup does not embed custom
|
|
94
|
+
state locations, so retain the documented generic instruction path if your agent
|
|
95
|
+
workflow needs an explicit custom state. PowerShell may use the existing
|
|
96
|
+
`npm.cmd`/`redue.cmd` shims without changing script policy.
|
|
97
|
+
|
|
98
|
+
Review the preview before applying. Intact owned content updates; unrelated text
|
|
99
|
+
is preserved. Edited managed content is refused, not overwritten. If refused,
|
|
100
|
+
review/reconcile those edits deliberately; do not remove ownership records to force
|
|
101
|
+
an update. Restart the observer with its original selection even if you postpone
|
|
102
|
+
updating instructions, then start a **fresh agent session** after a successful
|
|
103
|
+
update. Setup creates no verification receipt.
|
|
104
|
+
|
|
105
|
+
Managed project metadata can itself be an observed input. Prior receipts remain
|
|
106
|
+
historical evidence, but setup may make them STALE or UNVERIFIED. Reassess normally
|
|
107
|
+
and run required checks; an upgrade is not a promise that existing receipts remain
|
|
108
|
+
CURRENT. Do not suppress that invalidation or reset state.
|
|
109
|
+
|
|
65
110
|
Codex gets a short managed section in project `AGENTS.md` and
|
|
66
111
|
`.agents/skills/redue-verification/SKILL.md`. Claude Code gets a managed section
|
|
67
112
|
in `CLAUDE.md` and `.claude/skills/redue-verification/SKILL.md`. Each uses the same
|
|
@@ -107,7 +152,7 @@ proven installation mismatch.
|
|
|
107
152
|
Codex CLI 0.160.1 on macOS can use the approved exact observer-socket route while
|
|
108
153
|
other sockets and direct network access remain denied. Its independent sessions
|
|
109
154
|
receive different proxy values. Ordinary npm invocation is **not** equivalent
|
|
110
|
-
across arbitrary notifier state:
|
|
155
|
+
across arbitrary notifier state: REDUE withholds automatic npm-launcher
|
|
111
156
|
reuse. Do not change proxy settings or permissions to get green evidence.
|
|
112
157
|
|
|
113
158
|
The separate opt-in [direct local compiler recipe](direct-typescript.md) does not
|
|
@@ -124,7 +169,7 @@ performs the same bounded, read-only decision check and exits 2 when reuse is no
|
|
|
124
169
|
eligible. It neither executes verification nor grants access; synchronization
|
|
125
170
|
may run configured probes. A successful outside-shell result is insufficient.
|
|
126
171
|
|
|
127
|
-
Use the same setup command after
|
|
172
|
+
Use the same setup command after an upgrade; it updates intact managed
|
|
128
173
|
content idempotently. Changed skill text or a changed managed block causes a
|
|
129
174
|
conflict instead of an overwrite. Review your edits before retrying.
|
|
130
175
|
|
|
@@ -154,8 +199,8 @@ paths. Other worktrees and remote containers have separate local evidence.
|
|
|
154
199
|
|
|
155
200
|
For another agent, put a short instruction in its documented project mechanism:
|
|
156
201
|
“Before deciding whether configured verification needs repeating, read REDUE's
|
|
157
|
-
agent policy,
|
|
158
|
-
|
|
202
|
+
agent policy, identify the selected recipe and use its decision query for the intended config.
|
|
203
|
+
Apply the schema/health/reuse rules.” Supply this document and the
|
|
159
204
|
[JSON contract](json.md) as the detailed policy. Review the agent's actual tool
|
|
160
205
|
trace before calling activation verified. A local npm installation does not install
|
|
161
206
|
REDUE in a remote container; that environment needs its own CLI, checkout,
|
|
@@ -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.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:
|
|
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.6.3
|
|
36
|
+
and no shell. TypeScript 5.5.2 and 5.6.3 use `lib/tsc.js`; 5.9.3 uses `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
|
|
|
@@ -57,11 +57,13 @@ prefix. Uninstalling the package does not remove local verification evidence;
|
|
|
57
57
|
`redue remove-state` is a separate, explicit action. Alpha.1 remains available as
|
|
58
58
|
historical software and is superseded for package identity, not a runtime defect.
|
|
59
59
|
|
|
60
|
-
## Upgrading
|
|
60
|
+
## Upgrading REDUE
|
|
61
61
|
|
|
62
62
|
Stop every observer using the installation before upgrading, then restart each
|
|
63
63
|
with the same config/state selection. See [the npm launcher notice](npm-launcher-notice.md)
|
|
64
|
-
for exact commands. Do not remove state or delete receipts.
|
|
64
|
+
for exact commands. Do not remove state or delete receipts. Existing agent connections require a separate
|
|
65
|
+
[preview/apply policy update](agents.md#update-an-existing-connection); installing
|
|
66
|
+
the package alone does not update project instructions.
|
|
65
67
|
|
|
66
68
|
## First project
|
|
67
69
|
|
|
@@ -90,7 +92,7 @@ reusability. Qualification can change when configuration or installation changes
|
|
|
90
92
|
|
|
91
93
|
`run` streams normal output and preserves execution environment and cancellation.
|
|
92
94
|
For the narrow pnpm TypeScript contract, `init` explicitly shows the direct installed
|
|
93
|
-
compiler invocation it selects.
|
|
95
|
+
compiler invocation it selects. Npm scripts remain recording-only;
|
|
94
96
|
use `redue init --recipe typescript-direct --dry-run` to preview a distinct,
|
|
95
97
|
explicit [local compiler recipe](direct-typescript.md). Other scripts run through
|
|
96
98
|
the package manager. No existing configuration is silently migrated.
|
|
@@ -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 following direct recipe is opt-in and new in the alpha.5 candidate. 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.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 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,
|
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.
|
|
4
|
+
"version": "0.1.0-alpha.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"tag": "alpha"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test --test-concurrency=1 test/*.test.mjs",
|
|
15
15
|
"test:package": "node test/acceptance/package-smoke.mjs",
|
|
16
|
-
"demo": "node test/acceptance/package-smoke.mjs --demo"
|
|
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
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"@parcel/watcher": "2.5.6",
|
|
@@ -45,15 +45,19 @@ function saved(root,host){const text=read(root,marker(host));if(text===null)retu
|
|
|
45
45
|
const value=JSON.parse(text),h=hosts[host];
|
|
46
46
|
if(value.schema!==1||value.host!==host||value.instruction!==h.instruction||value.skill!==h.skill||
|
|
47
47
|
typeof value.config!=='string'||path.isAbsolute(value.config)||value.config.includes('/')||value.config.includes('\\')||
|
|
48
|
-
value.block!==blockFor(host,value.config)||
|
|
48
|
+
(value.block!==blockFor(host,value.config)&&value.block!==blockFor(host,value.config,true))||
|
|
49
49
|
typeof value.skillHash!=='string'||typeof value.instructionCreated!=='boolean')
|
|
50
50
|
throw Error('unrecognized agent ownership record; inspect it without deleting user content');
|
|
51
51
|
return value;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
// Accept only these exact owned template versions; never arbitrary stored text.
|
|
54
|
+
function blockFor(host,relative,legacy=false){return `\n\n<!-- REDUE:${host}:begin -->\n`+
|
|
54
55
|
`Before deciding whether configured verification needs repeating, use REDUE.\n`+
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
(legacy?`Read ${hosts[host].skill} for the decision policy, then query ordinary status\n`+
|
|
57
|
+
`before choosing reuse or execution. Only healthy schema-1 CURRENT/PASS with\n`:
|
|
58
|
+
`Read ${hosts[host].skill}: verify the selected recipe, use caller-aware sync\n`+
|
|
59
|
+
`for direct-compiler reuse, and execute fresh obligations without a reuse query.\n`+
|
|
60
|
+
`Only healthy schema-1 CURRENT/PASS with\n`)+
|
|
57
61
|
`explicit reuse_eligible=true permits reuse; honor fresh-run requests and other\n`+
|
|
58
62
|
`project verification obligations. Reassess after edits or failed queries.\n`+
|
|
59
63
|
`Selected config (JSON path relative to this file's directory): ${JSON.stringify(relative)}.\n`+
|
|
@@ -63,7 +67,7 @@ function blockFor(host,relative){return `\n\n<!-- REDUE:${host}:begin -->\n`+
|
|
|
63
67
|
function skillFor(relative){return policy+`\nSelected configuration\n\n`+
|
|
64
68
|
`The configuration path, relative to the project instruction file's directory, is\n`+
|
|
65
69
|
`${JSON.stringify(relative)} (a JSON string, not shell code). From that directory use\n`+
|
|
66
|
-
|
|
70
|
+
`the policy-selected \`redue --config CONFIG ...\` command, replacing CONFIG with one quoted\n`+
|
|
67
71
|
`path argument. Apply the same --config to run, explain, start and stop. From a\n`+
|
|
68
72
|
`nested directory use the corresponding relative config path. Do not guess among\n`+
|
|
69
73
|
`multiple configs. Receipts are local to the selected checkout/configuration.\n`;}
|
package/src/agent-policy.md
CHANGED
|
@@ -3,11 +3,30 @@ name: redue-verification
|
|
|
3
3
|
description: Decide whether configured verification needs running, using REDUE evidence before verification and completion decisions.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
Apply the selected configuration below to every command. First distinguish an
|
|
7
|
+
execution obligation from a reuse decision:
|
|
8
|
+
|
|
9
|
+
- If fresh execution is explicitly requested or independently required, execute
|
|
10
|
+
it without a reuse query that cannot change that decision. Use `redue run CHECK`
|
|
11
|
+
to record the configured check. An explicit npm-script request requires npm,
|
|
12
|
+
not a direct compiler substitute.
|
|
13
|
+
- To evaluate reuse for a check whose selected config has
|
|
14
|
+
`qualification: "npm-typescript-direct-v1"` and the direct compiler command
|
|
15
|
+
(or whose receipt metadata has `verification_recipe: "npm-direct-typescript@1"`
|
|
16
|
+
consistent with that config), use one `redue --config CONFIG status --sync --json`
|
|
17
|
+
directly. Cached status cannot establish this recipe's caller eligibility.
|
|
18
|
+
Do not infer the recipe from a check's name or precede sync with cached status
|
|
19
|
+
solely as a ritual. Evaluate each check separately in mixed configurations.
|
|
20
|
+
- For other or unidentified recipes, start with ordinary `status --json` and
|
|
21
|
+
choose sync or execution from its evidence. Cached status and `--short` remain
|
|
22
|
+
cheap inspection, not caller-aware proof. A deliberate inexpensive rerun is
|
|
23
|
+
still permitted; there are no universal OS/package-manager cost thresholds.
|
|
24
|
+
|
|
25
|
+
Use one successful, still-applicable synchronized response for the current
|
|
26
|
+
decision; do not repeat sync/explain without edits, context changes or missing
|
|
27
|
+
information. Do not loop until green. Reassess after changes, observation loss or
|
|
28
|
+
a later failed query; earlier CURRENT is not continuing authority. There is no
|
|
29
|
+
time-based authorization or atomic guarantee against concurrent edits.
|
|
11
30
|
|
|
12
31
|
Read schema 1 JSON. Reuse only when the read succeeds, `schema` is 1,
|
|
13
32
|
`observation.healthy` is true, and the selected check has all three:
|
|
@@ -29,8 +48,7 @@ checks merely for freshness.
|
|
|
29
48
|
lifecycle scripts. Inspect the configuration/verification_recipe before claiming
|
|
30
49
|
what ran. An explicit request for `npm run typecheck` requires that npm invocation;
|
|
31
50
|
direct compiler evidence does not satisfy it.
|
|
32
|
-
-
|
|
33
|
-
verification obligations still apply. REDUE covers only configured checks.
|
|
51
|
+
- REDUE covers only configured checks, not every task-specific verification obligation.
|
|
34
52
|
|
|
35
53
|
Use `redue explain CHECK --json` for synchronized detail (also potentially costly).
|
|
36
54
|
`changed_inputs` can be incomplete; an empty list does not establish no changes.
|
package/src/cli.mjs
CHANGED
|
@@ -17,10 +17,11 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
|
|
|
17
17
|
import {permittedRoots} from './installed-inputs.mjs';
|
|
18
18
|
import {acquireRunLock,inspectRunLock,recoverRunLock} from './run-lock.mjs';
|
|
19
19
|
import {atomicJson,commitReceipt,receiptRevision} from './state-store.mjs';
|
|
20
|
-
import {timing,profiling} from './decision-profile.mjs';
|
|
20
|
+
import {timing,profiling,mark,traceContext} from './decision-profile.mjs';
|
|
21
21
|
import {readCachedStatus,unavailableCached} from './cached-state.mjs';
|
|
22
22
|
import {renderRun} from './presentation.mjs';
|
|
23
23
|
|
|
24
|
+
mark('cli.loaded');
|
|
24
25
|
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
const [configArg,action,name]=process.argv.slice(2);
|
|
26
27
|
if(!configArg||!action)throw Error('internal usage: cli.mjs CONFIG ACTION [check]');
|
|
@@ -93,13 +94,13 @@ function owner(){
|
|
|
93
94
|
}
|
|
94
95
|
function call(message,timeout=5000) {
|
|
95
96
|
return new Promise((resolve,reject)=>{
|
|
96
|
-
let data='',done=false;const client=net.createConnection(socket);
|
|
97
|
-
const timer=setTimeout(()=>{client.destroy();reject(Error('observer synchronization timeout'));},timeout);
|
|
98
|
-
client.on('connect',()=>client.write(JSON.stringify(message)+'\n'));
|
|
97
|
+
let data='',done=false;mark('client.control_connect',{deadlineMs:timeout});const client=net.createConnection(socket);
|
|
98
|
+
const timer=setTimeout(()=>{mark('client.control_timeout');client.destroy();reject(Error('observer synchronization timeout'));},timeout);
|
|
99
|
+
client.on('connect',()=>{mark('client.control_submit');client.write(JSON.stringify({...message,...(profiling?{diagnostic:traceContext()}: {})})+'\n');});
|
|
99
100
|
client.on('data',chunk=>data+=chunk);
|
|
100
|
-
client.on('end',()=>{clearTimeout(timer);if(done)return;done=true;
|
|
101
|
+
client.on('end',()=>{mark('client.control_complete',{bytes:data.length});clearTimeout(timer);if(done)return;done=true;
|
|
101
102
|
try{resolve(JSON.parse(data));}catch(e){reject(e);}});
|
|
102
|
-
client.on('error',e=>{clearTimeout(timer);reject(e);});
|
|
103
|
+
client.on('error',e=>{mark('client.control_error',{code:e.code});clearTimeout(timer);reject(e);});
|
|
103
104
|
});
|
|
104
105
|
}
|
|
105
106
|
async function decisionCall(message,timeout) {
|
package/src/daemon.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
|
|
|
17
17
|
import {atomicJson,readReceipts,readReceiptSnapshot,receiptRevision} from './state-store.mjs';
|
|
18
18
|
import {history} from './observation.mjs';
|
|
19
19
|
import {inputKey,planGuard} from './decision-validation.mjs';
|
|
20
|
-
import {timing} from './decision-profile.mjs';
|
|
20
|
+
import {timing,mark,traceBase,traceContext,traceScope,traceEnvironment,traceId} from './decision-profile.mjs';
|
|
21
21
|
|
|
22
22
|
const configFile=path.resolve(process.argv[2]);
|
|
23
23
|
const config=JSON.parse(fs.readFileSync(configFile));
|
|
@@ -31,6 +31,7 @@ const endpoint=controlEndpoint(state),socket=endpoint.address,
|
|
|
31
31
|
fs.mkdirSync(lock); // Existing lock requires operator inspection; never steal it.
|
|
32
32
|
fs.writeFileSync(path.join(lock,'pid'),String(process.pid),{mode:0o600});
|
|
33
33
|
const generation=randomUUID(), startedAt=Date.now();
|
|
34
|
+
traceBase({generation,work:'startup'});
|
|
34
35
|
let index, receipts={}, watcher, healthy=false, reason='initial reconciliation', stopping=false;
|
|
35
36
|
let receiptStateError=null;
|
|
36
37
|
let receiptStateRevision=null;
|
|
@@ -136,10 +137,12 @@ function probe(row,allowReuse=false) {
|
|
|
136
137
|
const forceFull=process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_PROBES==='1';
|
|
137
138
|
if(cacheable&&allowReuse&&macGuarded&&!forceFull&&cache?.input===key){
|
|
138
139
|
const argv=row.plan.probes[0];
|
|
140
|
+
mark('daemon.probe_launch',{mode:'certificate',deadlineMs:10000});
|
|
139
141
|
const out=spawnSync(argv[0],[...argv.slice(1),macDirect?'--redue-validate-queries':'--redue-context'],{
|
|
140
|
-
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
|
|
142
|
+
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,env:traceEnvironment(),
|
|
141
143
|
...(macDirect?{input:JSON.stringify({schema:cache.schema,context:cache.context,queries:cache.queries})}:
|
|
142
144
|
{stdio:['ignore','pipe','pipe']})});
|
|
145
|
+
mark('daemon.probe_complete',{mode:'certificate',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
|
|
143
146
|
metrics.probeContextProcesses=(metrics.probeContextProcesses||0)+1;
|
|
144
147
|
let stillValid=!macDirect;
|
|
145
148
|
if(macDirect)try{stillValid=macProbeBarrier()&&inputKey(row)===key&&planGuard(index.bundle)===guard;}catch{}
|
|
@@ -170,8 +173,10 @@ function probe(row,allowReuse=false) {
|
|
|
170
173
|
const timeout=row.plan.qualification==='pnpm-tsc-v1'||
|
|
171
174
|
(process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
|
|
172
175
|
.includes(row.plan.qualification))?10000:5000;
|
|
176
|
+
mark('daemon.probe_launch',{mode:'full',reason:fullReason,deadlineMs:timeout});
|
|
173
177
|
const out=spawnSync(argv[0],[...argv.slice(1),...(cacheable?['--redue-checkpoint']:[])],{cwd:row.plan.cwd,
|
|
174
|
-
timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe']});
|
|
178
|
+
timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe'],env:traceEnvironment()});
|
|
179
|
+
mark('daemon.probe_complete',{mode:'full',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
|
|
175
180
|
if(out.error||out.signal||out.status!==0) {
|
|
176
181
|
if(!row.probeError){row.revision++;row.lastReason='declared state probe became unavailable';}
|
|
177
182
|
row.probeHash=null;row.probeAt=0;
|
|
@@ -180,7 +185,7 @@ function probe(row,allowReuse=false) {
|
|
|
180
185
|
/VSTATE_REASON:([a-z-]+)/.exec(out.stderr?.toString()||'')?.[1]:null;
|
|
181
186
|
const explanations={
|
|
182
187
|
'direct-typescript-implementation-unreviewed':'installed TypeScript bytes or membership differ from the reviewed compiler',
|
|
183
|
-
'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.6.3 and 5.9.3 only',
|
|
188
|
+
'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.5.2, 5.6.3 and 5.9.3 only',
|
|
184
189
|
'direct-execution-environment-unsupported':'direct compiler rejects development helpers, Node cache/preloads, and unreviewed runtime overrides',
|
|
185
190
|
'direct-node-version-unsupported':'direct compiler recipe supports Node 22 and 24',
|
|
186
191
|
'npm-launcher-unobserved-inputs':'npm launcher uses unobserved notifier inputs; execution is recording-only',
|
|
@@ -420,7 +425,7 @@ function buildInWorker(forceCold=false,reuse=null) {
|
|
|
420
425
|
return new Promise((resolve,reject)=>{
|
|
421
426
|
const workerStarted=performance.now();
|
|
422
427
|
const file=path.join(path.dirname(fileURLToPath(import.meta.url)),'plan-worker.mjs');
|
|
423
|
-
const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,
|
|
428
|
+
const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,diagnostic:traceContext(),
|
|
424
429
|
reconcileFaultFile:process.env.VSTATE_TEST_RECONCILE_FAULT_FILE}});
|
|
425
430
|
planningWorker=worker;let settled=false;
|
|
426
431
|
const deadline=setTimeout(()=>{
|
|
@@ -794,7 +799,7 @@ async function catchUp() {
|
|
|
794
799
|
lastObservation=Date.now();return;
|
|
795
800
|
}
|
|
796
801
|
if(historyDisabled)return;
|
|
797
|
-
if(synchronizing)return synchronizing;
|
|
802
|
+
if(synchronizing){mark('daemon.catch_up_join');return synchronizing;}
|
|
798
803
|
synchronizing=(async()=>{
|
|
799
804
|
if(!verifyRoot())throw Error('checkout identity changed');
|
|
800
805
|
if(!watcher)throw Error('subscription unavailable');
|
|
@@ -825,7 +830,8 @@ async function catchUp() {
|
|
|
825
830
|
function respond(client,value) {
|
|
826
831
|
if(client.destroyed){event('control_reply_unavailable',{classification:'client_destroyed'});return;}
|
|
827
832
|
try{const payload=JSON.stringify(value)+'\n';
|
|
828
|
-
|
|
833
|
+
mark('server.response_constructed',{bytes:payload.length});
|
|
834
|
+
client.end(payload,()=>mark('server.response_delivered'));client.vstateReplied=true;
|
|
829
835
|
if(process.env.VSTATE_DEBUG_CONTROL==='1')event('control_reply_sent',{bytes:payload.length});}
|
|
830
836
|
catch(e){event('control_reply_unavailable',{classification:e.name||'serialization_error'});
|
|
831
837
|
if(!client.destroyed)client.end('{"error":"control response unavailable"}\n');}
|
|
@@ -843,6 +849,7 @@ async function request(message) {
|
|
|
843
849
|
if(message.action==='sync'||message.action==='snapshot'||message.action==='prelaunch') {
|
|
844
850
|
const totalStarted=performance.now();
|
|
845
851
|
const decision=++decisionSequence;
|
|
852
|
+
mark('server.decision_start',{decision,pending:pending.size,planDirty,planning,recovering,healthy,historyDisabled,decisionRequests,historyDeadlineMs,reconciliationDeadlineMs});
|
|
846
853
|
timing('daemon.decision_reason',totalStarted,{decision,action:message.action,
|
|
847
854
|
plan:process.platform==='win32'?'validate_dependencies_before_reuse':'existing_plan',
|
|
848
855
|
probe:'reuse_only_with_identical_content_and_context',installed:'current_content_required',
|
|
@@ -970,9 +977,11 @@ const server=net.createServer(client=>{
|
|
|
970
977
|
try{const message=JSON.parse(text);
|
|
971
978
|
const decision=message.action==='sync'||message.action==='snapshot';
|
|
972
979
|
if(decision)decisionRequests++;
|
|
973
|
-
|
|
974
|
-
.
|
|
975
|
-
.
|
|
980
|
+
traceScope({request:message.diagnostic?.request||traceId(),work:'request'},()=>{
|
|
981
|
+
mark('server.request_received',{activeDecisions:decisionRequests});
|
|
982
|
+
Promise.resolve(request(message)).then(v=>respond(client,v))
|
|
983
|
+
.catch(e=>respond(client,{error:e.message}))
|
|
984
|
+
.finally(()=>{mark('server.request_complete');if(decision)decisionRequests--;});});}
|
|
976
985
|
catch(e){respond(client,{error:e.message});}
|
|
977
986
|
});
|
|
978
987
|
});
|
|
@@ -1032,7 +1041,8 @@ const heartbeat=setInterval(()=>{
|
|
|
1032
1041
|
}
|
|
1033
1042
|
publish();
|
|
1034
1043
|
},1000);
|
|
1035
|
-
const probeTimer=setInterval(async()=>{
|
|
1044
|
+
const probeTimer=setInterval(()=>traceScope({request:traceId(),work:'periodic'},async()=>{
|
|
1045
|
+
mark('server.periodic_start',{pending:pending.size,planDirty,planning,decisionRequests});
|
|
1036
1046
|
// Windows cached reads are conservatively UNVERIFIED until a full decision
|
|
1037
1047
|
// reconciliation. Periodic synchronous probes cannot strengthen that cache,
|
|
1038
1048
|
// but can block control requests behind several seconds of child work.
|
|
@@ -1045,7 +1055,9 @@ const probeTimer=setInterval(async()=>{
|
|
|
1045
1055
|
}
|
|
1046
1056
|
publish();}
|
|
1047
1057
|
catch(e){observationGap('periodic_query',e);}
|
|
1048
|
-
}
|
|
1058
|
+
}
|
|
1059
|
+
mark('server.periodic_complete');
|
|
1060
|
+
}),5000);
|
|
1049
1061
|
process.on('uncaughtExceptionMonitor',error=>{
|
|
1050
1062
|
try{event('observer_uncaught_exception',{pid:process.pid,code:error.code||null,
|
|
1051
1063
|
message:error.message,stack:error.stack});}catch{}
|
package/src/decision-profile.mjs
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
// Opt-in, bounded local diagnostics. Never include paths, command arguments,
|
|
2
2
|
// environment values, source, or probe output in a timing record.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import {AsyncLocalStorage} from 'node:async_hooks';
|
|
5
|
+
import {randomUUID} from 'node:crypto';
|
|
4
6
|
|
|
5
7
|
const file=process.env.REDUE_DECISION_PROFILE_FILE;
|
|
6
8
|
export const profiling=process.env.REDUE_DECISION_PROFILE==='1'&&!!file;
|
|
7
9
|
|
|
10
|
+
const traces=new AsyncLocalStorage();
|
|
11
|
+
const safe=value=>typeof value==='string'&&/^[a-zA-Z0-9_-]{1,64}$/.test(value)?value:undefined;
|
|
12
|
+
const clean=value=>Object.fromEntries(['request','generation','work'].map(key=>[key,safe(value?.[key])]).filter(([,v])=>v));
|
|
13
|
+
let base={};
|
|
14
|
+
try{base=clean(JSON.parse(process.env.REDUE_PROFILE_CONTEXT||'{}'));}catch{}
|
|
15
|
+
export const traceContext=()=>profiling?{...base,...traces.getStore()}:{};
|
|
16
|
+
export function traceBase(value){if(profiling)base=clean(value);}
|
|
17
|
+
export const traceId=()=>randomUUID();
|
|
18
|
+
export const traceScope=(value,fn)=>profiling?traces.run({...traceContext(),...clean(value)},fn):fn();
|
|
19
|
+
export const traceEnvironment=()=>profiling?{...process.env,REDUE_PROFILE_CONTEXT:JSON.stringify(traceContext())}:process.env;
|
|
20
|
+
export function mark(phase,fields={}){timing(phase,performance.now(),fields);}
|
|
21
|
+
|
|
8
22
|
export function timing(phase,started,counts={}) {
|
|
9
23
|
if(!profiling)return;
|
|
10
24
|
const memory=process.memoryUsage();
|
|
11
25
|
try{fs.appendFileSync(file,'REDUE_TIMING '+JSON.stringify({
|
|
12
|
-
at:Date.now(),pid:process.pid,phase,
|
|
26
|
+
at:Date.now(),monoNs:process.hrtime.bigint().toString(),pid:process.pid,phase,...traceContext(),
|
|
13
27
|
ms:Math.round((performance.now()-started)*10)/10,
|
|
14
28
|
rssMiB:Math.round(memory.rss/1048576*10)/10,
|
|
15
29
|
...counts
|
|
@@ -4,10 +4,9 @@ import {directProject,directInterpretation} from './direct-typescript.mjs';
|
|
|
4
4
|
import {typeScriptInputFacts} from './typescript-input-facts.mjs';
|
|
5
5
|
import {contextOnly,probeResult} from './probe-result.mjs';
|
|
6
6
|
import {realObservedPath} from './path-identity.mjs';
|
|
7
|
-
import fs from 'node:fs';
|
|
8
7
|
import path from 'node:path';
|
|
9
8
|
import {queriesMatch} from './typescript-list.mjs';
|
|
10
|
-
import {timing} from './decision-profile.mjs';
|
|
9
|
+
import {timing,mark} from './decision-profile.mjs';
|
|
11
10
|
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
12
11
|
try{
|
|
13
12
|
const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
|
|
@@ -22,8 +21,16 @@ try{
|
|
|
22
21
|
if(process.argv.includes('--redue-validate-queries')){
|
|
23
22
|
// Internal, process-local discovery certificate; never a verification receipt.
|
|
24
23
|
// Verify the current implementation above before queryAnswer can load its API.
|
|
25
|
-
const started=performance.now()
|
|
26
|
-
|
|
24
|
+
const started=performance.now();mark('certificate.input_start');
|
|
25
|
+
// A synchronous fd-0 read can stall with spawnSync's piped input on macOS
|
|
26
|
+
// Node 22.13.0. Drain the stream without blocking its input delivery. The
|
|
27
|
+
// parent's existing deadline and the same size limit remain authoritative.
|
|
28
|
+
const chunks=[];let bytes=0;
|
|
29
|
+
for await(const chunk of process.stdin){
|
|
30
|
+
bytes+=chunk.length;if(bytes>8*1024*1024)throw Error('certificate too large');
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
}
|
|
33
|
+
const raw=Buffer.concat(chunks).toString('utf8');mark('certificate.input_complete',{bytes});
|
|
27
34
|
const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
|
|
28
35
|
'fileExists','directoryExists','realpath','directories','entries']);
|
|
29
36
|
if(certificate.schema!==1||certificate.context!==contextHash||
|
|
@@ -34,7 +41,9 @@ try{
|
|
|
34
41
|
q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
|
|
35
42
|
q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
|
|
36
43
|
throw Error('certificate unavailable');
|
|
44
|
+
mark('certificate.queries_start',{queries:certificate.queries.length});
|
|
37
45
|
const matched=queriesMatch(certificate.queries);
|
|
46
|
+
mark('certificate.queries_complete',{matched});
|
|
38
47
|
timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
|
|
39
48
|
if(!matched)throw Error('compiler queries changed');
|
|
40
49
|
process.stdout.write(contextHash);
|
|
@@ -7,17 +7,21 @@ import {realObservedPath,withinPath} from './path-identity.mjs';
|
|
|
7
7
|
export const directInterpretation='npm-direct-typescript@1';
|
|
8
8
|
export const directQualification='npm-typescript-direct-v1';
|
|
9
9
|
const reviewed={
|
|
10
|
+
'5.5.2':{entry:'lib/tsc.js',digest:'953b4844b5f6edc745be844b6c367c227b59029b73c4182f508f90cc47e21816'},
|
|
10
11
|
'5.6.3':{entry:'lib/tsc.js',digest:'e674b5dc4dac50f6e91be30094aeea983a1f340231717e62b0b6199ae3049eb2'},
|
|
11
12
|
'5.9.3':{entry:'lib/_tsc.js',digest:'d1b32e75475710fdc85102b1365254d84ceb192d3639d962c53fe1355ce274ca'},
|
|
12
13
|
};
|
|
13
14
|
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
14
15
|
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
15
16
|
export const directEnvironment={variables:['NODE_ENV','NODE_OPTIONS','NODE_PATH','NODE_COMPILE_CACHE','VSCODE_INSPECTOR_OPTIONS'],
|
|
16
|
-
prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_'],executableIdentity:true};
|
|
17
|
+
prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_','TS_ETW_'],executableIdentity:true};
|
|
17
18
|
export function directEnvironmentIssue(env=process.env){
|
|
18
19
|
if(![22,24].includes(Number(process.versions.node.split('.')[0])))return 'direct-node-version-unsupported';
|
|
19
20
|
if(env.VSCODE_INSPECTOR_OPTIONS)return 'direct-execution-environment-unsupported';
|
|
20
21
|
for(const [key,value] of Object.entries(env)){
|
|
22
|
+
// The 5.5.2 API can require this unreviewed module during probe loading.
|
|
23
|
+
// Reject even empty/case aliases; do not mutate the caller's environment.
|
|
24
|
+
if(/^TS_ETW_MODULE_PATH$/i.test(key))return 'direct-execution-environment-unsupported';
|
|
21
25
|
if(/^NODE_/i.test(key)){
|
|
22
26
|
// Node 22.13.0 predates env-proxy initialization. This injected flag is
|
|
23
27
|
// inert there, but remains strictly fingerprinted as NODE_ context.
|
package/src/history-worker.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Native history is an optional acceleration. Keep its operations off the
|
|
2
2
|
// control thread so a wedged native call cannot block status or shutdown.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import {mark} from './decision-profile.mjs';
|
|
5
|
+
mark('history.worker_loaded');
|
|
4
6
|
import watcher from '@parcel/watcher';
|
|
5
7
|
|
|
6
8
|
try {
|
|
@@ -10,8 +12,11 @@ try {
|
|
|
10
12
|
catch(e){if(e.code!=='ENOENT')throw e;}
|
|
11
13
|
if(fault==='hang')Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);
|
|
12
14
|
if(fault==='fail')throw Error('injected history failure');
|
|
15
|
+
mark('history.snapshot_start');
|
|
13
16
|
await watcher.writeSnapshot(root,next,{backend});
|
|
17
|
+
mark('history.snapshot_complete');
|
|
14
18
|
const changes=previous?await watcher.getEventsSince(root,previous,{backend}):[];
|
|
19
|
+
mark('history.events_complete',{events:changes.length});
|
|
15
20
|
process.send({ok:true,changes});
|
|
16
21
|
} catch(error) {
|
|
17
22
|
process.send({ok:false,error:error.message,code:error.code||error.name});
|
package/src/observation.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import {mark,traceEnvironment} from './decision-profile.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {randomUUID} from 'node:crypto';
|
|
4
5
|
import {fork} from 'node:child_process';
|
|
@@ -11,11 +12,12 @@ const workerFile=path.join(path.dirname(fileURLToPath(import.meta.url)),'history
|
|
|
11
12
|
export function history(root,cursor,backend,deadlineMs,faultFile) {
|
|
12
13
|
const next=cursor+'.'+randomUUID()+'.next';
|
|
13
14
|
return new Promise((resolve,reject)=>{
|
|
15
|
+
mark('history.submit',{deadlineMs});
|
|
14
16
|
const worker=fork(workerFile,[root,fs.existsSync(cursor)?cursor:'',next,backend,
|
|
15
|
-
faultFile||''],{stdio:['ignore','ignore','ignore','ipc']});
|
|
17
|
+
faultFile||''],{stdio:['ignore','ignore','ignore','ipc'],env:traceEnvironment()});
|
|
16
18
|
let settled=false,abandoned=false;
|
|
17
19
|
const finish=(error,value)=>{
|
|
18
|
-
if(settled)return;settled=true;clearTimeout(timer);
|
|
20
|
+
if(settled)return;settled=true;mark('history.complete',{childPid:worker.pid,outcome:error?.code||error?.name||'ok',events:value?.length});clearTimeout(timer);
|
|
19
21
|
worker.kill('SIGKILL');
|
|
20
22
|
if(error){abandoned=true;try{fs.unlinkSync(next);}catch{}
|
|
21
23
|
reject(error);return;}
|
package/src/plan-worker.mjs
CHANGED
|
@@ -7,8 +7,9 @@ import {InputIndex} from './index.mjs';
|
|
|
7
7
|
import {directProject,directQualification} from './direct-typescript.mjs';
|
|
8
8
|
import {queriesMatch} from './typescript-list.mjs';
|
|
9
9
|
import {planGuard,inputKey} from './decision-validation.mjs';
|
|
10
|
-
import {timing} from './decision-profile.mjs';
|
|
10
|
+
import {timing,traceBase} from './decision-profile.mjs';
|
|
11
11
|
|
|
12
|
+
traceBase(workerData.diagnostic);
|
|
12
13
|
try {
|
|
13
14
|
const totalStarted=performance.now();
|
|
14
15
|
const configStarted=performance.now();
|