rcf-lite 0.32.4 → 0.33.0
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 +42 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/discovery/README.md +22 -0
- package/guidance/discovery/discovery-viewer-command-page-1-layout.html +191 -0
- package/guidance/discovery/discovery-viewer-command-page-2-coverage.html +193 -0
- package/guidance/discovery/discovery-viewer-command-page-3-trace-matrix.html +195 -0
- package/guidance/discovery/discovery-viewer-command-page-4-tables.html +196 -0
- package/guidance/discovery/discovery-viewer-command-page-5-subtabs.html +224 -0
- package/guidance/discovery/discovery-viewer-command-page-6-one-page.html +280 -0
- package/guidance/discovery/discovery-viewer-command-page.md +301 -0
- package/package.json +1 -1
- package/rcf/adrs/adr-4139-readiness-tables-read-only-no-commands.json +30 -0
- package/rcf/adrs/adr-4140-view-query-routes-per-pivot-matrix-no-mermaid.json +30 -0
- package/rcf/adrs/adr-4141-verify-modes.json +25 -0
- package/rcf/code-nodes/cn-771.json +3 -2
- package/rcf/code-nodes/cn-779.json +19 -0
- package/rcf/code-nodes/cn-780.json +14 -0
- package/rcf/code-nodes/cn-781.json +14 -0
- package/rcf/code-nodes/cn-782.json +15 -0
- package/rcf/code-nodes/cn-783.json +17 -0
- package/rcf/code-nodes/cn-784.json +20 -0
- package/rcf/code-nodes/cn-785.json +14 -0
- package/rcf/code-nodes/cn-786.json +14 -0
- package/rcf/code-nodes/cn-787.json +18 -0
- package/rcf/code-nodes/cn-788.json +14 -0
- package/rcf/code-nodes/cn-789.json +14 -0
- package/rcf/code-nodes/cn-790.json +15 -0
- package/rcf/code-nodes/cn-791.json +14 -0
- package/rcf/code-nodes/cn-792.json +17 -0
- package/rcf/code-nodes/cn-793.json +17 -0
- package/rcf/code-nodes/cn-794.json +18 -0
- package/rcf/code-nodes/cn-795.json +15 -0
- package/rcf/code-nodes/cn-796.json +14 -0
- package/rcf/code-nodes/cn-797.json +14 -0
- package/rcf/code-nodes/cn-798.json +14 -0
- package/rcf/define/brief-ledger.json +47 -0
- package/rcf/define/decisions-ledger.json +113 -0
- package/rcf/fbs/fbs-204.json +46 -0
- package/rcf/fbs/fbs-205.json +46 -0
- package/rcf/fbs/fbs-206.json +49 -0
- package/rcf/fbs/fbs-207.json +43 -0
- package/rcf/fbs/fbs-208.json +47 -0
- package/rcf/fbs/fbs-209.json +22 -0
- package/rcf/requirements/req-002.json +5 -2
- package/rcf/requirements/req-180.json +6 -3
- package/rcf/tacs/tac-4127-readiness-view.json +2 -2
- package/rcf/tacs/tac-4133-readiness-po-layer.json +2 -2
- package/rcf/tacs/tac-4135-readiness-command-page.json +94 -0
- package/rcf/tacs/tac-4136-view-query-routes.json +68 -0
- package/rcf/tacs/tac-4141-verify-modes.json +25 -0
- package/rcf/test-suites/ts-223.json +7 -7
- package/rcf/test-suites/ts-237.json +78 -0
- package/rcf/test-suites/ts-238.json +70 -0
- package/rcf/test-suites/ts-239.json +78 -0
- package/rcf/test-suites/ts-240.json +70 -0
- package/rcf/test-suites/ts-241.json +70 -0
- package/rcf/test-suites/ts-242.json +46 -0
- package/rcf/user-stories/us-16503.json +51 -0
- package/rcf/user-stories/us-18001.json +4 -4
- package/rcf/user-stories/us-18003.json +79 -0
- package/rcf/user-stories/us-18004.json +70 -0
- package/rcf/user-stories/us-18005.json +79 -0
- package/rcf/user-stories/us-208.json +74 -0
- package/rcf/user-stories/us-209.json +70 -0
- package/releases/releases.yaml +18 -1
- package/src/cli/finalise.js +141 -2
- package/src/cli/view.js +13 -1
- package/src/define/ledgers.js +9 -1
- package/src/finalise/spawn.js +2 -0
- package/src/server/index.js +104 -39
- package/src/server/routes.js +169 -0
- package/src/verify/cli/run.js +40 -0
- package/src/verify/engine/deterministic.js +150 -0
- package/src/verify/engine/index.js +27 -4
- package/src/view/doc-renderers/req.js +11 -9
- package/src/view/html-page.js +15 -11
- package/src/view/index.js +7 -1
- package/src/view/live-client.js +4 -0
- package/src/view/page-init.js +842 -1
- package/src/view/readiness/composite-id.js +35 -0
- package/src/view/readiness/coverage-summary.js +293 -0
- package/src/view/readiness/delta-counts.js +156 -0
- package/src/view/readiness/grid.js +175 -0
- package/src/view/readiness/po-layer.js +110 -220
- package/src/view/readiness/tables.js +500 -0
- package/src/view/readiness/thin-reqs.js +237 -0
- package/src/view/readiness/trace-matrix.js +392 -0
- package/src/view/readiness.js +359 -370
- package/src/view/style.css +594 -0
- package/src/watch/index.js +75 -17
package/src/watch/index.js
CHANGED
|
@@ -8,8 +8,17 @@
|
|
|
8
8
|
// D8: Node built-in `fs.watch` recursive - no `chokidar` dep. If dogfood
|
|
9
9
|
// surfaces a failure mode the built-in cannot handle, the swap-in is
|
|
10
10
|
// banked (same module interface, no caller changes).
|
|
11
|
+
//
|
|
12
|
+
// Issue #336 (reporter Dex, 2026-10-08): on Linux the recursive fs.watch
|
|
13
|
+
// reports the first rename over a file, then goes permanently blind to
|
|
14
|
+
// that path (next rename or write never fires). Editors that save by
|
|
15
|
+
// write-temp-then-rename (vim backupcopy=no, JetBrains safe-write) hit
|
|
16
|
+
// this on every save. Fix: on any `rename` event under a watched base,
|
|
17
|
+
// re-arm that base (close the stale handle, open a new one). The re-arm
|
|
18
|
+
// coalesces onto the debounce window, so a burst of renames yields one
|
|
19
|
+
// re-arm per flush. No dependency added.
|
|
11
20
|
|
|
12
|
-
import { watch as
|
|
21
|
+
import { watch as defaultFsWatch } from 'node:fs';
|
|
13
22
|
import { access, constants } from 'node:fs/promises';
|
|
14
23
|
import { isAbsolute, join } from 'node:path';
|
|
15
24
|
|
|
@@ -31,6 +40,10 @@ const DEFAULT_DEBOUNCE_MS = 50;
|
|
|
31
40
|
* globals. Exists as a determinism seam for tests: a manual timer lets a
|
|
32
41
|
* test decide exactly when the debounce window closes instead of racing
|
|
33
42
|
* real fs-event delivery latency. Production callers never pass this.
|
|
43
|
+
* @param {typeof defaultFsWatch} [args.fsWatchImpl] - injectable fs.watch
|
|
44
|
+
* implementation. Issue #336 seam: a test substitutes a fake that
|
|
45
|
+
* drives the Linux rename-blind delivery pattern deterministically
|
|
46
|
+
* on any host. Production callers never pass this.
|
|
34
47
|
* @returns {{ close: () => void }}
|
|
35
48
|
*/
|
|
36
49
|
export function watch({
|
|
@@ -41,6 +54,7 @@ export function watch({
|
|
|
41
54
|
onError,
|
|
42
55
|
filter,
|
|
43
56
|
timers,
|
|
57
|
+
fsWatchImpl,
|
|
44
58
|
} = {}) {
|
|
45
59
|
if (!Array.isArray(paths) || paths.length === 0) {
|
|
46
60
|
throw new TypeError('watch: paths must be a non-empty array of absolute paths');
|
|
@@ -50,7 +64,9 @@ export function watch({
|
|
|
50
64
|
}
|
|
51
65
|
const accept = typeof filter === 'function' ? filter : defaultFilter;
|
|
52
66
|
const timerHost = { setTimeout, clearTimeout, ...timers };
|
|
53
|
-
const
|
|
67
|
+
const fsWatchFn = typeof fsWatchImpl === 'function' ? fsWatchImpl : defaultFsWatch;
|
|
68
|
+
/** @type {{ base: string, handle: ReturnType<typeof defaultFsWatch> | null, needsRearm: boolean }[]} */
|
|
69
|
+
const baseWatchers = [];
|
|
54
70
|
const pending = new Map();
|
|
55
71
|
let timer = null;
|
|
56
72
|
let closed = false;
|
|
@@ -61,6 +77,13 @@ export function watch({
|
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
|
|
80
|
+
function ensureFlushScheduled() {
|
|
81
|
+
if (closed) return;
|
|
82
|
+
if (timer) return;
|
|
83
|
+
timer = timerHost.setTimeout(flush, debounceMs);
|
|
84
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
85
|
+
}
|
|
86
|
+
|
|
64
87
|
function flush() {
|
|
65
88
|
timer = null;
|
|
66
89
|
if (closed) return;
|
|
@@ -73,6 +96,18 @@ export function watch({
|
|
|
73
96
|
reportError(err);
|
|
74
97
|
}
|
|
75
98
|
}
|
|
99
|
+
// Issue #336: re-arm any base that saw a rename in this window.
|
|
100
|
+
// Closing and reopening the fs.watch handle re-registers inotify
|
|
101
|
+
// watches against current inodes, so a path that was replaced by
|
|
102
|
+
// rename starts firing again. One re-arm per flush collapses a
|
|
103
|
+
// burst of renames onto a single swap.
|
|
104
|
+
for (const rec of baseWatchers) {
|
|
105
|
+
if (!rec.needsRearm) continue;
|
|
106
|
+
rec.needsRearm = false;
|
|
107
|
+
if (closed) continue;
|
|
108
|
+
try { rec.handle?.close(); } catch { /* swallow */ }
|
|
109
|
+
rec.handle = openBaseHandle(rec.base);
|
|
110
|
+
}
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
function schedule(path, type) {
|
|
@@ -83,6 +118,16 @@ export function watch({
|
|
|
83
118
|
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
84
119
|
}
|
|
85
120
|
|
|
121
|
+
function markRearm(base) {
|
|
122
|
+
const rec = baseWatchers.find((r) => r.base === base);
|
|
123
|
+
if (!rec) return;
|
|
124
|
+
rec.needsRearm = true;
|
|
125
|
+
// Even if no path survived the filter, a rename event means the
|
|
126
|
+
// handle may be about to go blind: make sure flush fires so the
|
|
127
|
+
// re-arm actually happens.
|
|
128
|
+
ensureFlushScheduled();
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
async function classify(fullPath, eventType) {
|
|
87
132
|
if (eventType === 'change') return 'change';
|
|
88
133
|
try {
|
|
@@ -93,14 +138,16 @@ export function watch({
|
|
|
93
138
|
}
|
|
94
139
|
}
|
|
95
140
|
|
|
96
|
-
|
|
97
|
-
if (typeof base !== 'string' || !isAbsolute(base)) {
|
|
98
|
-
throw new TypeError(`watch: paths must be absolute, got ${String(base)}`);
|
|
99
|
-
}
|
|
141
|
+
function openBaseHandle(base) {
|
|
100
142
|
let w;
|
|
101
143
|
try {
|
|
102
|
-
w =
|
|
144
|
+
w = fsWatchFn(base, { recursive: true }, (eventType, filename) => {
|
|
103
145
|
if (closed) return;
|
|
146
|
+
// Issue #336: a rename anywhere under `base` can invalidate
|
|
147
|
+
// the handle's descriptors for the renamed path. Mark the base
|
|
148
|
+
// for re-arm regardless of whether the specific filename is
|
|
149
|
+
// one we would otherwise report (filtered paths still count).
|
|
150
|
+
if (eventType === 'rename') markRearm(base);
|
|
104
151
|
if (!filename) return;
|
|
105
152
|
const full = join(base, filename);
|
|
106
153
|
if (!accept(full)) return;
|
|
@@ -112,14 +159,24 @@ export function watch({
|
|
|
112
159
|
});
|
|
113
160
|
} catch (err) {
|
|
114
161
|
reportError(err);
|
|
115
|
-
|
|
162
|
+
return null;
|
|
116
163
|
}
|
|
117
|
-
w.on
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
if (typeof w.on === 'function') {
|
|
165
|
+
w.on('error', (err) => {
|
|
166
|
+
// Silently drop ENOENT (watched path deleted mid-run); surface the rest.
|
|
167
|
+
if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') return;
|
|
168
|
+
reportError(err);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return w;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const base of paths) {
|
|
175
|
+
if (typeof base !== 'string' || !isAbsolute(base)) {
|
|
176
|
+
throw new TypeError(`watch: paths must be absolute, got ${String(base)}`);
|
|
177
|
+
}
|
|
178
|
+
const handle = openBaseHandle(base);
|
|
179
|
+
if (handle) baseWatchers.push({ base, handle, needsRearm: false });
|
|
123
180
|
}
|
|
124
181
|
|
|
125
182
|
function close() {
|
|
@@ -130,10 +187,11 @@ export function watch({
|
|
|
130
187
|
timer = null;
|
|
131
188
|
}
|
|
132
189
|
pending.clear();
|
|
133
|
-
for (const
|
|
134
|
-
try {
|
|
190
|
+
for (const rec of baseWatchers) {
|
|
191
|
+
try { rec.handle?.close(); } catch { /* swallow */ }
|
|
192
|
+
rec.handle = null;
|
|
135
193
|
}
|
|
136
|
-
|
|
194
|
+
baseWatchers.length = 0;
|
|
137
195
|
if (signal && typeof signal.removeEventListener === 'function') {
|
|
138
196
|
try { signal.removeEventListener('abort', close); } catch { /* swallow */ }
|
|
139
197
|
}
|