@webpieces/rules-config 0.4.668 → 0.4.669
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/package.json +1 -1
- package/src/home-config.d.ts +178 -24
- package/src/home-config.js +227 -42
- package/src/home-config.js.map +1 -1
- package/src/index.js +5 -1
- package/src/index.js.map +1 -1
- package/templates/webpieces.branch-state-matrix.md +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.669",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/home-config.d.ts
CHANGED
|
@@ -10,18 +10,92 @@
|
|
|
10
10
|
* "not opted in", and returns all-defaults silently. That is not a fallback for a wrong shape; it is the
|
|
11
11
|
* definition of "the user did not create this file".
|
|
12
12
|
*
|
|
13
|
-
* ───
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the
|
|
13
|
+
* ─── THE STANDING RULE, FOR WHOEVER EDITS THIS FILE NEXT ──────────────────────────────────────────────
|
|
14
|
+
* This file is MACHINE-GLOBAL: ONE document on the disk, read by EVERY repo on the machine, and those
|
|
15
|
+
* repos are pinned to DIFFERENT webpieces releases. Two rules follow, and they are not negotiable:
|
|
16
|
+
*
|
|
17
|
+
* (a) NO KEY MAY EVER BE REQUIRED. `readOptionalBoolean` is the ONLY reader. There is no
|
|
18
|
+
* `readRequiredBoolean`, no `RequiredHomeFlag`, no `REQUIRED_HOME_FLAGS` — those existed once and
|
|
19
|
+
* were deleted; do not reintroduce them under any name.
|
|
20
|
+
* (b) AN UNKNOWN KEY IS IGNORED, never rejected. `warnUnknownKeys` warns; nothing throws.
|
|
21
|
+
*
|
|
22
|
+
* Both halves are needed, and either one alone still leaves a file that some installed release rejects:
|
|
23
|
+
*
|
|
24
|
+
* (a) covers the OLD FILE on a NEW release — a document written before a key existed, missing it.
|
|
25
|
+
* (b) covers the NEW FILE on an OLD release — a document carrying a key that release never heard of.
|
|
26
|
+
*
|
|
27
|
+
* Break either one and every repo on the machine that is not on the newest release hard-blocks: a
|
|
28
|
+
* rejection here fails config load, and that denies every tool call in that repo until somebody upgrades
|
|
29
|
+
* all of them in lockstep. That is the outage this design exists to make impossible, and it is not
|
|
30
|
+
* hypothetical — it is the shape of the incident recorded at the bottom of this docblock.
|
|
31
|
+
*
|
|
32
|
+
* `home-config.spec.ts` pins both halves, and pins them by ENUMERATING `ALLOWED_EXPERIMENTAL` rather
|
|
33
|
+
* than by a hand-written list, so a key added later is covered by the invariant automatically instead of
|
|
34
|
+
* silently escaping it.
|
|
35
|
+
*
|
|
36
|
+
* ─── PRESENT IS STRICT ABOUT WHAT IT UNDERSTANDS, AND FORWARD-COMPATIBLE ABOUT WHAT IT DOES NOT ───────
|
|
37
|
+
* Once the bytes are readable, someone DELIBERATELY created this file, and three of the four failure
|
|
38
|
+
* modes are REJECTED exactly as webpieces.config.json rejects them (see `retired-config-keys.ts`), with
|
|
39
|
+
* an error naming the exact fix and no `??` fallback anywhere:
|
|
40
|
+
*
|
|
41
|
+
* UNPARSEABLE not JSON, or not a single JSON object → REJECT
|
|
42
|
+
* RETIRED KEY listed in RETIRED_HOME_CONFIG_KEYS → REJECT, carrying the migration
|
|
43
|
+
* WRONG TYPE a KNOWN key holding a non-boolean → REJECT
|
|
44
|
+
* UNKNOWN KEY a key no version of this validator has heard of → IGNORED, with a warning
|
|
45
|
+
*
|
|
46
|
+
* ─── WHY THE LAST ROW DIFFERS FROM webpieces.config.json, WHICH STAYS STRICT ──────────────────────────
|
|
47
|
+
* The difference is not a softening of policy; it is that the two files have different CARDINALITY.
|
|
48
|
+
*
|
|
49
|
+
* webpieces.config.json is REPO-TRACKED. One repo, one file, one pinned @webpieces release, and the
|
|
50
|
+
* file moves through git in lockstep with the code that reads it. "One version reads this document" is
|
|
51
|
+
* true by construction, so an unknown key there can only be a typo or a dead key — and rejecting it is
|
|
52
|
+
* right, because it is the delivery mechanism for the migration.
|
|
53
|
+
*
|
|
54
|
+
* THIS file is MACHINE-GLOBAL. ONE document, read by EVERY repo on the machine, and those repos pin
|
|
55
|
+
* DIFFERENT releases — deliberately, since a repo's pin is tracked and moves when its own PR lands.
|
|
56
|
+
* So "an unknown key" here has a second, entirely legitimate cause that cannot occur in the repo file:
|
|
57
|
+
* a key a NEWER release added, being read by an OLDER one. Rejecting it means adding any key to this
|
|
58
|
+
* file hard-blocks every repo on the machine that has not yet been upgraded — an outage produced by
|
|
59
|
+
* opting IN to an experimental flag, which is the same shape of failure that moved these flags out of
|
|
60
|
+
* webpieces.config.json in the first place (see the section below).
|
|
61
|
+
*
|
|
62
|
+
* This is the exact mirror of the already-settled rule that no key here may be REQUIRED. Both halves fall
|
|
63
|
+
* out of one fact: the set of valid documents must be non-empty for EVERY release on the machine at once.
|
|
64
|
+
* • omit a new key → an old release must not demand it (already true: every key is optional)
|
|
65
|
+
* • add a new key → an old release must not reject it (this change)
|
|
66
|
+
* With only the first half, the set of valid files was still empty the moment a key was added.
|
|
67
|
+
*
|
|
68
|
+
* ─── WHAT THAT COSTS, AND WHAT PAYS FOR IT ────────────────────────────────────────────────────────────
|
|
69
|
+
* The cost is real and worth stating plainly: a TYPO now silently does nothing. `"whole-repo-build-gaurd"`
|
|
70
|
+
* used to be a loud rejection; it is now a key nothing reads, so the flag keeps its default and nothing
|
|
71
|
+
* about the machine's behaviour reveals the mistake.
|
|
72
|
+
*
|
|
73
|
+
* That bites HARDEST on a key whose default is ON, which `whole-repo-build-guard` now is
|
|
74
|
+
* (WHOLE_REPO_BUILD_GUARD_DEFAULT). Misspell the OPT-OUT and the guard stays on while the author
|
|
75
|
+
* believes they turned it off — a failure that argues with them rather than one they can shrug at. It is
|
|
76
|
+
* the strongest reason the warning below is not optional decoration, and the reason `nearestKnownKey`
|
|
77
|
+
* had to get fuzzier than the case-insensitive match it replaced: `gaurd` is a transposition, exactly
|
|
78
|
+
* the class of typo an equality test cannot see.
|
|
79
|
+
*
|
|
80
|
+
* That is mitigated, not eliminated, by making the ignore VISIBLE: every unknown key is printed once per
|
|
81
|
+
* load as a `[webpieces]` warning on stderr, and `nearestKnownKey` upgrades that line with a "did you
|
|
82
|
+
* mean" whenever the key is within a two-character edit of a known one — which is what a typo is, and
|
|
83
|
+
* what a key from a newer release is not. A warning naming a close match is the strongest signal
|
|
84
|
+
* available that does not also block a colleague on an older pin.
|
|
85
|
+
*
|
|
86
|
+
* The trade was taken this way round because the two mistakes are not symmetric. A typo costs its author
|
|
87
|
+
* one flag that did not turn on, discoverable the moment they check whether the feature is doing
|
|
88
|
+
* anything, on their own machine. A rejection costs every repo on the machine every tool call, and the
|
|
89
|
+
* person it blocks is usually not the person who edited the file.
|
|
90
|
+
*
|
|
91
|
+
* An unknown TOP-LEVEL key is ignored on the identical argument, and it is the more important half: a
|
|
92
|
+
* future release adding a second section (`preferences`, say) beside `experimental` would otherwise be
|
|
93
|
+
* unreadable by every older release on the machine, which is precisely the sequencing being deleted here.
|
|
94
|
+
*
|
|
95
|
+
* ─── REJECTING IS STILL SELF-RECOVERABLE, FOR THE THREE ROWS THAT STILL REJECT ────────────────────────
|
|
96
|
+
* A Write/Edit targeting THIS path is an unconditional PASS in the hook guards (see `isHomeConfigPath`,
|
|
97
|
+
* wired into ai-hook-rules' runner beside the webpieces.config.json pass), so an agent can always repair
|
|
98
|
+
* the file the loader just rejected.
|
|
25
99
|
*
|
|
26
100
|
* ─── WHY A MACHINE-LOCAL SWITCH LIVES HERE AND NOT IN webpieces.config.json ───────────────────────────
|
|
27
101
|
* `whole-repo-build-guard` first shipped as an ordinary validated guard: `mode: 'ON'` by default AND an
|
|
@@ -65,6 +139,20 @@ export declare const HOME_KEY_ORPHAN_DIR_SWEEP = "orphan-dir-sweep";
|
|
|
65
139
|
* turns it off for a machine, with no repo config involved.
|
|
66
140
|
*/
|
|
67
141
|
export declare const WHOLE_REPO_BUILD_GUARD_DEFAULT = true;
|
|
142
|
+
/**
|
|
143
|
+
* The complete UNDERSTOOD shape. A key not on these lists is ignored with a warning rather than
|
|
144
|
+
* rejected (see the class docblock: this document is machine-global and older releases must survive
|
|
145
|
+
* meeting a newer release's key), so adding a key still means adding it here — a key absent from these
|
|
146
|
+
* lists is never read at all, and the flag it was meant to set keeps the default above.
|
|
147
|
+
*
|
|
148
|
+
* EXPORTED so `home-config.spec.ts` can ENUMERATE them rather than restate them. The cross-version
|
|
149
|
+
* invariant ("every key is independently omittable") is only as good as the list the test walks, and a
|
|
150
|
+
* hand-written copy of that list means a NEW key silently escapes the invariant on the day it is added —
|
|
151
|
+
* which is the one failure mode nobody would notice until an older release started rejecting files.
|
|
152
|
+
* Walking the real constant makes the test cover a new key the moment it appears here.
|
|
153
|
+
*/
|
|
154
|
+
export declare const ALLOWED_TOP_LEVEL: readonly string[];
|
|
155
|
+
export declare const ALLOWED_EXPERIMENTAL: readonly string[];
|
|
68
156
|
/** The parsed `~/.webpieces/config.json`. Data-only (per CLAUDE.md — classes, not interfaces, for data). */
|
|
69
157
|
export declare class HomeConfig {
|
|
70
158
|
/**
|
|
@@ -127,15 +215,22 @@ export declare class RetiredHomeConfigKey {
|
|
|
127
215
|
*/
|
|
128
216
|
export declare const RETIRED_HOME_CONFIG_KEYS: readonly RetiredHomeConfigKey[];
|
|
129
217
|
/**
|
|
130
|
-
* Loads and
|
|
131
|
-
*
|
|
218
|
+
* Loads and validates `~/.webpieces/config.json`, and resolves whether a path IS that file (for the
|
|
219
|
+
* guard carve-out that keeps a rejection repairable).
|
|
220
|
+
*
|
|
221
|
+
* Strict about everything it UNDERSTANDS (a retired key, a known key of the wrong type, and a document
|
|
222
|
+
* that is not JSON all throw); forward-compatible about everything it does not (an unknown key is
|
|
223
|
+
* ignored with a warning). The class docblock at the top of this file has the reasoning.
|
|
132
224
|
*/
|
|
133
225
|
export declare class HomeConfigService {
|
|
134
226
|
/** Absolute path to the preference file. `homeDir` is a parameter so specs never touch a real HOME. */
|
|
135
227
|
configPath(homeDir?: string): string;
|
|
136
228
|
/**
|
|
137
229
|
* The preferences. Returns all-defaults, silently and without touching anything, when the file is not
|
|
138
|
-
* there. THROWS InformAiError, naming the fix, when a file that IS there is wrong
|
|
230
|
+
* there. THROWS InformAiError, naming the fix, when a file that IS there is wrong in a way this
|
|
231
|
+
* release can be sure about — unparseable, a RETIRED key, or a KNOWN key of the wrong type. A key it
|
|
232
|
+
* simply does not recognise is ignored with a warning, because it may be a newer release's key and
|
|
233
|
+
* this file is shared by every repo on the machine.
|
|
139
234
|
*/
|
|
140
235
|
load(homeDir?: string): HomeConfig;
|
|
141
236
|
/**
|
|
@@ -163,21 +258,80 @@ export declare class HomeConfigService {
|
|
|
163
258
|
/**
|
|
164
259
|
* An absent key falls back to `whenAbsent`, which every caller states OUT LOUD — there is no implicit
|
|
165
260
|
* "absent means false" any more, because `whole-repo-build-guard` defaults ON and a hidden default
|
|
166
|
-
* would put the two halves of that fact in different files.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
261
|
+
* would put the two halves of that fact in different files.
|
|
262
|
+
*
|
|
263
|
+
* A PRESENT key of the wrong type is still an ERROR, and that is the line the unknown-key change
|
|
264
|
+
* deliberately did not move: `"whole-repo-build-guard": "yes"` is a file somebody wrote wrongly, not
|
|
265
|
+
* a file written for a different release. No release of webpieces has ever given this key a string
|
|
266
|
+
* meaning, so there is no forward-compatibility story to protect and nothing is gained by guessing —
|
|
267
|
+
* whereas guessing would turn a typed value into a silent fallback to the default, which is the very
|
|
268
|
+
* cost the unknown-key warning exists to bound.
|
|
269
|
+
*
|
|
270
|
+
* This is the ONLY reader; see the every-key-is-optional note above for why there is no required
|
|
271
|
+
* variant.
|
|
169
272
|
*/
|
|
170
273
|
private readOptionalBoolean;
|
|
171
274
|
private asBoolean;
|
|
172
275
|
private assertNotRetired;
|
|
173
276
|
private isPresentAt;
|
|
174
277
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
278
|
+
* The understood keys AT ONE LEVEL, rendered from the allow-list rather than hand-listed — and
|
|
279
|
+
* rendered at the level the reader's key was actually found, so a mistyped SECTION is answered with
|
|
280
|
+
* the sections and a mistyped FLAG with the flags. The hand-listed version named two keys and went
|
|
281
|
+
* stale the moment a third arrived, telling an agent its brand-new key was not accepted while the
|
|
282
|
+
* validator right above accepted it.
|
|
283
|
+
*/
|
|
284
|
+
private quotedKeys;
|
|
285
|
+
/**
|
|
286
|
+
* An unknown key is IGNORED — see the class docblock for why this one file cannot reject it — but it
|
|
287
|
+
* is never SILENT. The warning is the entire mitigation for the cost of ignoring, so it says both
|
|
288
|
+
* things a reader needs: that the key did nothing, and what the understood keys are.
|
|
289
|
+
*
|
|
290
|
+
* ─── WHY THIS IS NOT THE "console side channel" SHAPE ────────────────────────────────────────────
|
|
291
|
+
* That shape is a rule or a library reporting a FAILURE — or a cure for one — by printing it instead
|
|
292
|
+
* of throwing a structured value to the one top-level handler. This is the opposite case, and the
|
|
293
|
+
* distinction is the entire subject of this change: an unknown key here is NOT a failure. The load
|
|
294
|
+
* SUCCEEDS, a valid HomeConfig is returned, every caller proceeds normally, and there is no cure the
|
|
295
|
+
* reader is obliged to apply — a key from a newer release is a CORRECT file being read by an older
|
|
296
|
+
* validator. There is no throw this could be, because throwing is precisely the behaviour being
|
|
297
|
+
* deleted here; and returning it would mean inventing a warnings channel through `load()` that no
|
|
298
|
+
* caller has any reason to render.
|
|
299
|
+
*
|
|
300
|
+
* stderr, not stdout, for the usual reason: this runs inside hooks whose stdout is a JSON decision
|
|
301
|
+
* and inside `wp-*` commands whose stdout is their real output, and neither may be polluted. It is
|
|
302
|
+
* the same channel, with the same `[webpieces]` prefix, that `state-dir-migration.announce` already
|
|
303
|
+
* uses in this package for the same category of finding — something a human may want to know about
|
|
304
|
+
* and is not required to act on.
|
|
305
|
+
*/
|
|
306
|
+
private warnUnknownKeys;
|
|
307
|
+
/**
|
|
308
|
+
* A near-miss of a RETIRED key, pointed at its migration — or '' when nothing retired is close.
|
|
309
|
+
*
|
|
310
|
+
* The gap this closes: `assertNotRetired` matches a retired key EXACTLY, so `captureBuildGateLog`
|
|
311
|
+
* throws with its rename instruction while `captureBuildGateLogg` — one stray character away, and a
|
|
312
|
+
* far likelier thing to type — falls through to the generic "IGNORED, might be from a newer release"
|
|
313
|
+
* line. That is the least helpful of the three answers offered to the reader whose intent is the
|
|
314
|
+
* clearest, so the retired table is consulted here too, at the same distance-2 threshold.
|
|
315
|
+
*
|
|
316
|
+
* It only ever produces a WARNING, never a throw: this release cannot know whether the reader meant
|
|
317
|
+
* the retired key or a newer one, and guessing wrong in the throwing direction is what the whole
|
|
318
|
+
* change is about. Known keys are matched first, so a typo of a LIVE key is never answered with a
|
|
319
|
+
* dead one.
|
|
320
|
+
*/
|
|
321
|
+
private nearRetiredHint;
|
|
322
|
+
private warn;
|
|
323
|
+
/**
|
|
324
|
+
* The closest understood key within two edits, or '' when nothing is close.
|
|
325
|
+
*
|
|
326
|
+
* This used to be a case-insensitive EQUALITY test, which was adequate while an unknown key was a
|
|
327
|
+
* hard error — the error itself was the signal, and the suggestion only saved a reading. Now the
|
|
328
|
+
* suggestion IS the signal, so it has to catch the typos an equality test misses: a doubled letter,
|
|
329
|
+
* a dropped one, a transposition, a stray trailing `d` (`buildGateLogCaptured`). Two is the useful
|
|
330
|
+
* threshold — it covers every one of those and still refuses to guess for a genuinely new key, which
|
|
331
|
+
* is the case that must NOT be dressed up as a typo.
|
|
178
332
|
*/
|
|
179
|
-
private
|
|
180
|
-
|
|
181
|
-
private
|
|
333
|
+
private nearestKnownKey;
|
|
334
|
+
/** Ordinary Levenshtein distance, one row at a time — the key names are short and this runs once. */
|
|
335
|
+
private editDistance;
|
|
182
336
|
private error;
|
|
183
337
|
}
|
package/src/home-config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HomeConfigService = exports.RETIRED_HOME_CONFIG_KEYS = exports.RetiredHomeConfigKey = exports.HomeConfig = exports.WHOLE_REPO_BUILD_GUARD_DEFAULT = exports.HOME_KEY_ORPHAN_DIR_SWEEP = exports.HOME_KEY_WHOLE_REPO_BUILD_GUARD = exports.HOME_KEY_BUILD_GATE_LOG_CAPTURE = exports.HOME_EXPERIMENTAL_SECTION = exports.HOME_CONFIG_FILE = exports.HOME_CONFIG_DIR = void 0;
|
|
3
|
+
exports.HomeConfigService = exports.RETIRED_HOME_CONFIG_KEYS = exports.RetiredHomeConfigKey = exports.HomeConfig = exports.ALLOWED_EXPERIMENTAL = exports.ALLOWED_TOP_LEVEL = exports.WHOLE_REPO_BUILD_GUARD_DEFAULT = exports.HOME_KEY_ORPHAN_DIR_SWEEP = exports.HOME_KEY_WHOLE_REPO_BUILD_GUARD = exports.HOME_KEY_BUILD_GATE_LOG_CAPTURE = exports.HOME_EXPERIMENTAL_SECTION = exports.HOME_CONFIG_FILE = exports.HOME_CONFIG_DIR = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs = tslib_1.__importStar(require("fs"));
|
|
6
6
|
const os = tslib_1.__importStar(require("os"));
|
|
@@ -20,18 +20,92 @@ const to_error_1 = require("./to-error");
|
|
|
20
20
|
* "not opted in", and returns all-defaults silently. That is not a fallback for a wrong shape; it is the
|
|
21
21
|
* definition of "the user did not create this file".
|
|
22
22
|
*
|
|
23
|
-
* ───
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* unknown key, a misspelled key, a retired key or a wrong value TYPE is REJECTED with an error naming the
|
|
27
|
-
* exact fix. No `??` fallback, no alias table, no "accepted for now". Every reader of this file is a
|
|
28
|
-
* coding agent, and an accepted shape is never migrated — so a loud failure carrying the mechanical edit
|
|
29
|
-
* is strictly cheaper than duality, and it is the delivery mechanism for the migration.
|
|
23
|
+
* ─── THE STANDING RULE, FOR WHOEVER EDITS THIS FILE NEXT ──────────────────────────────────────────────
|
|
24
|
+
* This file is MACHINE-GLOBAL: ONE document on the disk, read by EVERY repo on the machine, and those
|
|
25
|
+
* repos are pinned to DIFFERENT webpieces releases. Two rules follow, and they are not negotiable:
|
|
30
26
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
27
|
+
* (a) NO KEY MAY EVER BE REQUIRED. `readOptionalBoolean` is the ONLY reader. There is no
|
|
28
|
+
* `readRequiredBoolean`, no `RequiredHomeFlag`, no `REQUIRED_HOME_FLAGS` — those existed once and
|
|
29
|
+
* were deleted; do not reintroduce them under any name.
|
|
30
|
+
* (b) AN UNKNOWN KEY IS IGNORED, never rejected. `warnUnknownKeys` warns; nothing throws.
|
|
31
|
+
*
|
|
32
|
+
* Both halves are needed, and either one alone still leaves a file that some installed release rejects:
|
|
33
|
+
*
|
|
34
|
+
* (a) covers the OLD FILE on a NEW release — a document written before a key existed, missing it.
|
|
35
|
+
* (b) covers the NEW FILE on an OLD release — a document carrying a key that release never heard of.
|
|
36
|
+
*
|
|
37
|
+
* Break either one and every repo on the machine that is not on the newest release hard-blocks: a
|
|
38
|
+
* rejection here fails config load, and that denies every tool call in that repo until somebody upgrades
|
|
39
|
+
* all of them in lockstep. That is the outage this design exists to make impossible, and it is not
|
|
40
|
+
* hypothetical — it is the shape of the incident recorded at the bottom of this docblock.
|
|
41
|
+
*
|
|
42
|
+
* `home-config.spec.ts` pins both halves, and pins them by ENUMERATING `ALLOWED_EXPERIMENTAL` rather
|
|
43
|
+
* than by a hand-written list, so a key added later is covered by the invariant automatically instead of
|
|
44
|
+
* silently escaping it.
|
|
45
|
+
*
|
|
46
|
+
* ─── PRESENT IS STRICT ABOUT WHAT IT UNDERSTANDS, AND FORWARD-COMPATIBLE ABOUT WHAT IT DOES NOT ───────
|
|
47
|
+
* Once the bytes are readable, someone DELIBERATELY created this file, and three of the four failure
|
|
48
|
+
* modes are REJECTED exactly as webpieces.config.json rejects them (see `retired-config-keys.ts`), with
|
|
49
|
+
* an error naming the exact fix and no `??` fallback anywhere:
|
|
50
|
+
*
|
|
51
|
+
* UNPARSEABLE not JSON, or not a single JSON object → REJECT
|
|
52
|
+
* RETIRED KEY listed in RETIRED_HOME_CONFIG_KEYS → REJECT, carrying the migration
|
|
53
|
+
* WRONG TYPE a KNOWN key holding a non-boolean → REJECT
|
|
54
|
+
* UNKNOWN KEY a key no version of this validator has heard of → IGNORED, with a warning
|
|
55
|
+
*
|
|
56
|
+
* ─── WHY THE LAST ROW DIFFERS FROM webpieces.config.json, WHICH STAYS STRICT ──────────────────────────
|
|
57
|
+
* The difference is not a softening of policy; it is that the two files have different CARDINALITY.
|
|
58
|
+
*
|
|
59
|
+
* webpieces.config.json is REPO-TRACKED. One repo, one file, one pinned @webpieces release, and the
|
|
60
|
+
* file moves through git in lockstep with the code that reads it. "One version reads this document" is
|
|
61
|
+
* true by construction, so an unknown key there can only be a typo or a dead key — and rejecting it is
|
|
62
|
+
* right, because it is the delivery mechanism for the migration.
|
|
63
|
+
*
|
|
64
|
+
* THIS file is MACHINE-GLOBAL. ONE document, read by EVERY repo on the machine, and those repos pin
|
|
65
|
+
* DIFFERENT releases — deliberately, since a repo's pin is tracked and moves when its own PR lands.
|
|
66
|
+
* So "an unknown key" here has a second, entirely legitimate cause that cannot occur in the repo file:
|
|
67
|
+
* a key a NEWER release added, being read by an OLDER one. Rejecting it means adding any key to this
|
|
68
|
+
* file hard-blocks every repo on the machine that has not yet been upgraded — an outage produced by
|
|
69
|
+
* opting IN to an experimental flag, which is the same shape of failure that moved these flags out of
|
|
70
|
+
* webpieces.config.json in the first place (see the section below).
|
|
71
|
+
*
|
|
72
|
+
* This is the exact mirror of the already-settled rule that no key here may be REQUIRED. Both halves fall
|
|
73
|
+
* out of one fact: the set of valid documents must be non-empty for EVERY release on the machine at once.
|
|
74
|
+
* • omit a new key → an old release must not demand it (already true: every key is optional)
|
|
75
|
+
* • add a new key → an old release must not reject it (this change)
|
|
76
|
+
* With only the first half, the set of valid files was still empty the moment a key was added.
|
|
77
|
+
*
|
|
78
|
+
* ─── WHAT THAT COSTS, AND WHAT PAYS FOR IT ────────────────────────────────────────────────────────────
|
|
79
|
+
* The cost is real and worth stating plainly: a TYPO now silently does nothing. `"whole-repo-build-gaurd"`
|
|
80
|
+
* used to be a loud rejection; it is now a key nothing reads, so the flag keeps its default and nothing
|
|
81
|
+
* about the machine's behaviour reveals the mistake.
|
|
82
|
+
*
|
|
83
|
+
* That bites HARDEST on a key whose default is ON, which `whole-repo-build-guard` now is
|
|
84
|
+
* (WHOLE_REPO_BUILD_GUARD_DEFAULT). Misspell the OPT-OUT and the guard stays on while the author
|
|
85
|
+
* believes they turned it off — a failure that argues with them rather than one they can shrug at. It is
|
|
86
|
+
* the strongest reason the warning below is not optional decoration, and the reason `nearestKnownKey`
|
|
87
|
+
* had to get fuzzier than the case-insensitive match it replaced: `gaurd` is a transposition, exactly
|
|
88
|
+
* the class of typo an equality test cannot see.
|
|
89
|
+
*
|
|
90
|
+
* That is mitigated, not eliminated, by making the ignore VISIBLE: every unknown key is printed once per
|
|
91
|
+
* load as a `[webpieces]` warning on stderr, and `nearestKnownKey` upgrades that line with a "did you
|
|
92
|
+
* mean" whenever the key is within a two-character edit of a known one — which is what a typo is, and
|
|
93
|
+
* what a key from a newer release is not. A warning naming a close match is the strongest signal
|
|
94
|
+
* available that does not also block a colleague on an older pin.
|
|
95
|
+
*
|
|
96
|
+
* The trade was taken this way round because the two mistakes are not symmetric. A typo costs its author
|
|
97
|
+
* one flag that did not turn on, discoverable the moment they check whether the feature is doing
|
|
98
|
+
* anything, on their own machine. A rejection costs every repo on the machine every tool call, and the
|
|
99
|
+
* person it blocks is usually not the person who edited the file.
|
|
100
|
+
*
|
|
101
|
+
* An unknown TOP-LEVEL key is ignored on the identical argument, and it is the more important half: a
|
|
102
|
+
* future release adding a second section (`preferences`, say) beside `experimental` would otherwise be
|
|
103
|
+
* unreadable by every older release on the machine, which is precisely the sequencing being deleted here.
|
|
104
|
+
*
|
|
105
|
+
* ─── REJECTING IS STILL SELF-RECOVERABLE, FOR THE THREE ROWS THAT STILL REJECT ────────────────────────
|
|
106
|
+
* A Write/Edit targeting THIS path is an unconditional PASS in the hook guards (see `isHomeConfigPath`,
|
|
107
|
+
* wired into ai-hook-rules' runner beside the webpieces.config.json pass), so an agent can always repair
|
|
108
|
+
* the file the loader just rejected.
|
|
35
109
|
*
|
|
36
110
|
* ─── WHY A MACHINE-LOCAL SWITCH LIVES HERE AND NOT IN webpieces.config.json ───────────────────────────
|
|
37
111
|
* `whole-repo-build-guard` first shipped as an ordinary validated guard: `mode: 'ON'` by default AND an
|
|
@@ -88,10 +162,20 @@ exports.WHOLE_REPO_BUILD_GUARD_DEFAULT = true;
|
|
|
88
162
|
// keys is byte-for-byte the behaviour of having no file at all. Named rather than written as a bare
|
|
89
163
|
// `false` at each call site so the asymmetry with the constant above is visible where it is passed.
|
|
90
164
|
const GUARD_OFF_WHEN_ABSENT = false;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The complete UNDERSTOOD shape. A key not on these lists is ignored with a warning rather than
|
|
167
|
+
* rejected (see the class docblock: this document is machine-global and older releases must survive
|
|
168
|
+
* meeting a newer release's key), so adding a key still means adding it here — a key absent from these
|
|
169
|
+
* lists is never read at all, and the flag it was meant to set keeps the default above.
|
|
170
|
+
*
|
|
171
|
+
* EXPORTED so `home-config.spec.ts` can ENUMERATE them rather than restate them. The cross-version
|
|
172
|
+
* invariant ("every key is independently omittable") is only as good as the list the test walks, and a
|
|
173
|
+
* hand-written copy of that list means a NEW key silently escapes the invariant on the day it is added —
|
|
174
|
+
* which is the one failure mode nobody would notice until an older release started rejecting files.
|
|
175
|
+
* Walking the real constant makes the test cover a new key the moment it appears here.
|
|
176
|
+
*/
|
|
177
|
+
exports.ALLOWED_TOP_LEVEL = [exports.HOME_EXPERIMENTAL_SECTION];
|
|
178
|
+
exports.ALLOWED_EXPERIMENTAL = [
|
|
95
179
|
exports.HOME_KEY_WHOLE_REPO_BUILD_GUARD, exports.HOME_KEY_BUILD_GATE_LOG_CAPTURE, exports.HOME_KEY_ORPHAN_DIR_SWEEP,
|
|
96
180
|
];
|
|
97
181
|
/**
|
|
@@ -113,9 +197,10 @@ const ALLOWED_EXPERIMENTAL = [
|
|
|
113
197
|
* and it defaults TRUE — it is no longer experimental — for the reason spelled out at
|
|
114
198
|
* WHOLE_REPO_BUILD_GUARD_DEFAULT.
|
|
115
199
|
*
|
|
116
|
-
* The other half of cross-version safety — an OLD release
|
|
117
|
-
* rejecting it — is
|
|
118
|
-
* to
|
|
200
|
+
* The other half of cross-version safety — an OLD release IGNORING a key a NEW one added, rather than
|
|
201
|
+
* rejecting it — is solved by `warnUnknownKeys` below. The two halves are one invariant: for the set of
|
|
202
|
+
* valid documents to be non-empty across every release installed on the machine, neither omitting a key
|
|
203
|
+
* nor adding one may be an error.
|
|
119
204
|
*/
|
|
120
205
|
// Read errors that mean "the file is not there / not reachable" rather than "the file is wrong". Every
|
|
121
206
|
// one of these resolves to the all-defaults config, silently. Widened deliberately past ENOENT: the
|
|
@@ -208,8 +293,12 @@ exports.RETIRED_HOME_CONFIG_KEYS = [
|
|
|
208
293
|
'carries over unchanged.'),
|
|
209
294
|
];
|
|
210
295
|
/**
|
|
211
|
-
* Loads and
|
|
212
|
-
*
|
|
296
|
+
* Loads and validates `~/.webpieces/config.json`, and resolves whether a path IS that file (for the
|
|
297
|
+
* guard carve-out that keeps a rejection repairable).
|
|
298
|
+
*
|
|
299
|
+
* Strict about everything it UNDERSTANDS (a retired key, a known key of the wrong type, and a document
|
|
300
|
+
* that is not JSON all throw); forward-compatible about everything it does not (an unknown key is
|
|
301
|
+
* ignored with a warning). The class docblock at the top of this file has the reasoning.
|
|
213
302
|
*/
|
|
214
303
|
let HomeConfigService = class HomeConfigService {
|
|
215
304
|
/** Absolute path to the preference file. `homeDir` is a parameter so specs never touch a real HOME. */
|
|
@@ -218,7 +307,10 @@ let HomeConfigService = class HomeConfigService {
|
|
|
218
307
|
}
|
|
219
308
|
/**
|
|
220
309
|
* The preferences. Returns all-defaults, silently and without touching anything, when the file is not
|
|
221
|
-
* there. THROWS InformAiError, naming the fix, when a file that IS there is wrong
|
|
310
|
+
* there. THROWS InformAiError, naming the fix, when a file that IS there is wrong in a way this
|
|
311
|
+
* release can be sure about — unparseable, a RETIRED key, or a KNOWN key of the wrong type. A key it
|
|
312
|
+
* simply does not recognise is ignored with a warning, because it may be a newer release's key and
|
|
313
|
+
* this file is shared by every repo on the machine.
|
|
222
314
|
*/
|
|
223
315
|
load(homeDir = os.homedir()) {
|
|
224
316
|
const raw = this.readIfPresent(this.configPath(homeDir));
|
|
@@ -307,22 +399,30 @@ let HomeConfigService = class HomeConfigService {
|
|
|
307
399
|
// webpieces-disable no-any-unknown -- see parse(); the document is user-authored and unvalidated
|
|
308
400
|
validate(raw, file) {
|
|
309
401
|
this.assertNotRetired(raw, file);
|
|
310
|
-
this.
|
|
402
|
+
this.warnUnknownKeys(Object.keys(raw), exports.ALLOWED_TOP_LEVEL, '');
|
|
311
403
|
const section = raw[exports.HOME_EXPERIMENTAL_SECTION];
|
|
312
404
|
if (section !== undefined && (typeof section !== 'object' || section === null || Array.isArray(section))) {
|
|
313
405
|
throw new inform_ai_error_1.InformAiError(this.error(file, `"${exports.HOME_EXPERIMENTAL_SECTION}" must be a JSON object.`));
|
|
314
406
|
}
|
|
315
407
|
// webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above
|
|
316
408
|
const experimental = (section ?? {});
|
|
317
|
-
this.
|
|
409
|
+
this.warnUnknownKeys(Object.keys(experimental), exports.ALLOWED_EXPERIMENTAL, `${exports.HOME_EXPERIMENTAL_SECTION}.`);
|
|
318
410
|
return new HomeConfig(this.readOptionalBoolean(experimental, exports.HOME_KEY_BUILD_GATE_LOG_CAPTURE, file, GUARD_OFF_WHEN_ABSENT), this.readOptionalBoolean(experimental, exports.HOME_KEY_WHOLE_REPO_BUILD_GUARD, file, exports.WHOLE_REPO_BUILD_GUARD_DEFAULT), this.readOptionalBoolean(experimental, exports.HOME_KEY_ORPHAN_DIR_SWEEP, file, GUARD_OFF_WHEN_ABSENT));
|
|
319
411
|
}
|
|
320
412
|
/**
|
|
321
413
|
* An absent key falls back to `whenAbsent`, which every caller states OUT LOUD — there is no implicit
|
|
322
414
|
* "absent means false" any more, because `whole-repo-build-guard` defaults ON and a hidden default
|
|
323
|
-
* would put the two halves of that fact in different files.
|
|
324
|
-
*
|
|
325
|
-
*
|
|
415
|
+
* would put the two halves of that fact in different files.
|
|
416
|
+
*
|
|
417
|
+
* A PRESENT key of the wrong type is still an ERROR, and that is the line the unknown-key change
|
|
418
|
+
* deliberately did not move: `"whole-repo-build-guard": "yes"` is a file somebody wrote wrongly, not
|
|
419
|
+
* a file written for a different release. No release of webpieces has ever given this key a string
|
|
420
|
+
* meaning, so there is no forward-compatibility story to protect and nothing is gained by guessing —
|
|
421
|
+
* whereas guessing would turn a typed value into a silent fallback to the default, which is the very
|
|
422
|
+
* cost the unknown-key warning exists to bound.
|
|
423
|
+
*
|
|
424
|
+
* This is the ONLY reader; see the every-key-is-optional note above for why there is no required
|
|
425
|
+
* variant.
|
|
326
426
|
*/
|
|
327
427
|
// webpieces-disable no-any-unknown -- see parse()
|
|
328
428
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
@@ -367,31 +467,116 @@ let HomeConfigService = class HomeConfigService {
|
|
|
367
467
|
return section[parts[1]] !== undefined;
|
|
368
468
|
}
|
|
369
469
|
/**
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
470
|
+
* The understood keys AT ONE LEVEL, rendered from the allow-list rather than hand-listed — and
|
|
471
|
+
* rendered at the level the reader's key was actually found, so a mistyped SECTION is answered with
|
|
472
|
+
* the sections and a mistyped FLAG with the flags. The hand-listed version named two keys and went
|
|
473
|
+
* stale the moment a third arrived, telling an agent its brand-new key was not accepted while the
|
|
474
|
+
* validator right above accepted it.
|
|
373
475
|
*/
|
|
374
|
-
|
|
375
|
-
return
|
|
476
|
+
quotedKeys(allowed, prefix) {
|
|
477
|
+
return allowed.map((key) => `"${prefix}${key}"`).join(', ');
|
|
376
478
|
}
|
|
377
|
-
|
|
479
|
+
/**
|
|
480
|
+
* An unknown key is IGNORED — see the class docblock for why this one file cannot reject it — but it
|
|
481
|
+
* is never SILENT. The warning is the entire mitigation for the cost of ignoring, so it says both
|
|
482
|
+
* things a reader needs: that the key did nothing, and what the understood keys are.
|
|
483
|
+
*
|
|
484
|
+
* ─── WHY THIS IS NOT THE "console side channel" SHAPE ────────────────────────────────────────────
|
|
485
|
+
* That shape is a rule or a library reporting a FAILURE — or a cure for one — by printing it instead
|
|
486
|
+
* of throwing a structured value to the one top-level handler. This is the opposite case, and the
|
|
487
|
+
* distinction is the entire subject of this change: an unknown key here is NOT a failure. The load
|
|
488
|
+
* SUCCEEDS, a valid HomeConfig is returned, every caller proceeds normally, and there is no cure the
|
|
489
|
+
* reader is obliged to apply — a key from a newer release is a CORRECT file being read by an older
|
|
490
|
+
* validator. There is no throw this could be, because throwing is precisely the behaviour being
|
|
491
|
+
* deleted here; and returning it would mean inventing a warnings channel through `load()` that no
|
|
492
|
+
* caller has any reason to render.
|
|
493
|
+
*
|
|
494
|
+
* stderr, not stdout, for the usual reason: this runs inside hooks whose stdout is a JSON decision
|
|
495
|
+
* and inside `wp-*` commands whose stdout is their real output, and neither may be polluted. It is
|
|
496
|
+
* the same channel, with the same `[webpieces]` prefix, that `state-dir-migration.announce` already
|
|
497
|
+
* uses in this package for the same category of finding — something a human may want to know about
|
|
498
|
+
* and is not required to act on.
|
|
499
|
+
*/
|
|
500
|
+
warnUnknownKeys(found, allowed, prefix) {
|
|
378
501
|
for (const key of found) {
|
|
379
502
|
if (allowed.includes(key))
|
|
380
503
|
continue;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
504
|
+
const near = this.nearestKnownKey(key, allowed);
|
|
505
|
+
const guess = near !== ''
|
|
506
|
+
? ` Did you mean "${prefix}${near}"?`
|
|
507
|
+
: this.nearRetiredHint(`${prefix}${key}`)
|
|
508
|
+
|| ' If it is a typo, fix the spelling; if it is from a NEWER @webpieces than this repo pins, upgrade this repo to use it.';
|
|
509
|
+
this.warn(`"${prefix}${key}" is not a key this @webpieces release understands, so it was IGNORED ` +
|
|
510
|
+
`and had NO effect.${guess} Understood here: ${this.quotedKeys(allowed, prefix)}.`);
|
|
385
511
|
}
|
|
386
512
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
513
|
+
/**
|
|
514
|
+
* A near-miss of a RETIRED key, pointed at its migration — or '' when nothing retired is close.
|
|
515
|
+
*
|
|
516
|
+
* The gap this closes: `assertNotRetired` matches a retired key EXACTLY, so `captureBuildGateLog`
|
|
517
|
+
* throws with its rename instruction while `captureBuildGateLogg` — one stray character away, and a
|
|
518
|
+
* far likelier thing to type — falls through to the generic "IGNORED, might be from a newer release"
|
|
519
|
+
* line. That is the least helpful of the three answers offered to the reader whose intent is the
|
|
520
|
+
* clearest, so the retired table is consulted here too, at the same distance-2 threshold.
|
|
521
|
+
*
|
|
522
|
+
* It only ever produces a WARNING, never a throw: this release cannot know whether the reader meant
|
|
523
|
+
* the retired key or a newer one, and guessing wrong in the throwing direction is what the whole
|
|
524
|
+
* change is about. Known keys are matched first, so a typo of a LIVE key is never answered with a
|
|
525
|
+
* dead one.
|
|
526
|
+
*/
|
|
527
|
+
nearRetiredHint(dottedKey) {
|
|
528
|
+
for (const entry of exports.RETIRED_HOME_CONFIG_KEYS) {
|
|
529
|
+
if (this.editDistance(dottedKey.toLowerCase(), entry.key.toLowerCase()) > 2)
|
|
530
|
+
continue;
|
|
531
|
+
const destination = entry.movedTo === ''
|
|
532
|
+
? 'it was removed with no replacement'
|
|
533
|
+
: `it moved to "${entry.movedTo}"`;
|
|
534
|
+
return ` Did you mean the RETIRED key "${entry.key}"? If so, ${destination}. ${entry.instruction}`;
|
|
392
535
|
}
|
|
393
536
|
return '';
|
|
394
537
|
}
|
|
538
|
+
// One shape for every non-fatal finding, matching state-dir-migration's `[webpieces] <what>:` prefix
|
|
539
|
+
// so a reader can tell at a glance which subsystem is talking.
|
|
540
|
+
warn(message) {
|
|
541
|
+
process.stderr.write(`[webpieces] ~/.webpieces/config.json: ${message}\n`);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The closest understood key within two edits, or '' when nothing is close.
|
|
545
|
+
*
|
|
546
|
+
* This used to be a case-insensitive EQUALITY test, which was adequate while an unknown key was a
|
|
547
|
+
* hard error — the error itself was the signal, and the suggestion only saved a reading. Now the
|
|
548
|
+
* suggestion IS the signal, so it has to catch the typos an equality test misses: a doubled letter,
|
|
549
|
+
* a dropped one, a transposition, a stray trailing `d` (`buildGateLogCaptured`). Two is the useful
|
|
550
|
+
* threshold — it covers every one of those and still refuses to guess for a genuinely new key, which
|
|
551
|
+
* is the case that must NOT be dressed up as a typo.
|
|
552
|
+
*/
|
|
553
|
+
nearestKnownKey(key, allowed) {
|
|
554
|
+
let best = '';
|
|
555
|
+
let bestDistance = 3;
|
|
556
|
+
for (const candidate of allowed) {
|
|
557
|
+
const distance = this.editDistance(key.toLowerCase(), candidate.toLowerCase());
|
|
558
|
+
if (distance >= bestDistance)
|
|
559
|
+
continue;
|
|
560
|
+
bestDistance = distance;
|
|
561
|
+
best = candidate;
|
|
562
|
+
}
|
|
563
|
+
return best;
|
|
564
|
+
}
|
|
565
|
+
/** Ordinary Levenshtein distance, one row at a time — the key names are short and this runs once. */
|
|
566
|
+
editDistance(a, b) {
|
|
567
|
+
let previous = [];
|
|
568
|
+
for (let j = 0; j <= b.length; j += 1)
|
|
569
|
+
previous.push(j);
|
|
570
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
571
|
+
const current = [i];
|
|
572
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
573
|
+
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
574
|
+
current.push(Math.min(substitution, previous[j] + 1, current[j - 1] + 1));
|
|
575
|
+
}
|
|
576
|
+
previous = current;
|
|
577
|
+
}
|
|
578
|
+
return previous[b.length];
|
|
579
|
+
}
|
|
395
580
|
// One shape for every rejection: what is wrong, in which file, and the fact that deleting the file is
|
|
396
581
|
// always a legal fix — because the file is optional, and "no file" is a fully supported state.
|
|
397
582
|
error(file, detail) {
|
package/src/home-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/home-config.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n/**\n * `~/.webpieces/config.json` — the MACHINE-GLOBAL preference file, and a different thing from the repo's\n * tracked `webpieces.config.json`. It is EXPERIMENTAL and entirely OPTIONAL.\n *\n * ─── ABSENT IS THE NORMAL STATE, AND IT IS NEVER AN ERROR ─────────────────────────────────────────────\n * Essentially every consumer of these packages has no such file, and for them every `wp-*` command must\n * behave byte-for-byte as it did before this file was ever read: no error, no warning, no log line, no\n * extra file, nothing. `load()` therefore treats EVERY failure to READ the bytes — the file missing, the\n * `~/.webpieces` directory missing, a permission error, a path component that is not a directory — as\n * \"not opted in\", and returns all-defaults silently. That is not a fallback for a wrong shape; it is the\n * definition of \"the user did not create this file\".\n *\n * ─── PRESENT IS STRICT, EXACTLY LIKE webpieces.config.json ────────────────────────────────────────────\n * Once the bytes are readable, someone DELIBERATELY created this file, and from that point the same\n * policy applies as to webpieces.config.json (see `retired-config-keys.ts`): an unparseable document, an\n * unknown key, a misspelled key, a retired key or a wrong value TYPE is REJECTED with an error naming the\n * exact fix. No `??` fallback, no alias table, no \"accepted for now\". Every reader of this file is a\n * coding agent, and an accepted shape is never migrated — so a loud failure carrying the mechanical edit\n * is strictly cheaper than duality, and it is the delivery mechanism for the migration.\n *\n * Rejecting is self-recoverable here for the same reason it is for webpieces.config.json: a Write/Edit\n * targeting THIS path is an unconditional PASS in the hook guards (see `isHomeConfigPath`, wired into\n * ai-hook-rules' runner beside the webpieces.config.json pass), so an agent can always repair the file\n * the loader just rejected.\n *\n * ─── WHY A MACHINE-LOCAL SWITCH LIVES HERE AND NOT IN webpieces.config.json ───────────────────────────\n * `whole-repo-build-guard` first shipped as an ordinary validated guard: `mode: 'ON'` by default AND an\n * entry required under `hookGuards`. The consequence on upgrade was an outage — a consumer repo that had\n * not yet added the entry hit fault Y, which blocks EVERY Bash call, for a feature nobody had opted into.\n *\n * The rule that buys back: a switch that lives HERE needs no file, no key and no edit to be in its\n * default state. A repo-tracked config key cannot express that — an entry there is something every\n * consumer must add, on a schedule set by whoever bumps the release.\n *\n * Note which half of that was the outage. It was the REQUIRED KEY, not the on-by-default behaviour: the\n * failure was at config LOAD, before any command was judged. `whole-repo-build-guard` defaults ON again\n * today (see WHOLE_REPO_BUILD_GUARD_DEFAULT) and cannot reproduce it, because there is nothing to add\n * anywhere — the only file that could carry it is optional, and absent is a fully supported state.\n */\nexport const HOME_CONFIG_DIR = '.webpieces';\nexport const HOME_CONFIG_FILE = 'config.json';\n\n// The `experimental` section and its keys. Named as constants because both the validator and its error\n// text must spell them identically — a validator whose message names a different key than the one it\n// checks is worse than no message.\nexport const HOME_EXPERIMENTAL_SECTION = 'experimental';\nexport const HOME_KEY_BUILD_GATE_LOG_CAPTURE = 'buildGateLogCapture';\n// The on/off switch for `whole-repo-build-guard`. Spelled with the GUARD's own name, hyphens and all,\n// so `grep -rn whole-repo-build-guard` finds the switch beside the guard — and so nobody has to learn a\n// second name for one thing. It is a DIFFERENT key from buildGateLogCapture, which is #620's build-log\n// feature and merely selects WHICH refusal this guard prints.\nexport const HOME_KEY_WHOLE_REPO_BUILD_GUARD = 'whole-repo-build-guard';\n// The on/off switch for the orphan-directory sweep `wp-checkout-clean-main` runs. Named for the thing\n// it switches, exactly as the guard key above is — one name, greppable from either end.\nexport const HOME_KEY_ORPHAN_DIR_SWEEP = 'orphan-dir-sweep';\n\n/**\n * `whole-repo-build-guard`'s value on a machine that does not name the key — including the machine with\n * no such file at all, which is essentially every machine. It is TRUE: the guard is ON by default.\n *\n * ─── Why this one key defaults ON, and why that is not the outage this file warns about ─────────────\n * The outage recorded below was a CONFIG-LOAD failure: the guard once required an entry under\n * `hookGuards` in the repo-tracked `webpieces.config.json`, so every consumer who upgraded without\n * adding it hit fault Y and had EVERY Bash call blocked, for a feature they had never asked for. The\n * shape of that failure is \"you must edit a file to get your shell back\". Nothing here reproduces it:\n * no file needs to exist, no key needs to be added, and the load path for an absent or silent file is\n * unchanged. What changes is only what the guard DOES once loaded — it refuses a command that would\n * build the whole monorepo, and hands back the scoped command in its place.\n *\n * That is the behaviour the guard was written for, and OFF-by-default is why it never delivered it: a\n * guard nobody opts into never fires. The measured consequence is a sibling repo whose `ci:local`\n * verify chain ran three whole-world passes on every inner loop for months, while the correct command\n * sat unused in `commands.pr-gate.buildCommand`.\n *\n * The escape hatch stays, and it is one line: `{\"experimental\": {\"whole-repo-build-guard\": false}}`\n * turns it off for a machine, with no repo config involved.\n */\nexport const WHOLE_REPO_BUILD_GUARD_DEFAULT = true;\n\n// Every OTHER key's value when it is not named. False — \"this machine never opted in\" — which for those\n// keys is byte-for-byte the behaviour of having no file at all. Named rather than written as a bare\n// `false` at each call site so the asymmetry with the constant above is visible where it is passed.\nconst GUARD_OFF_WHEN_ABSENT = false;\n\n// The complete accepted shape. Anything not on these lists is an error, so adding a key means adding it\n// here — there is no place for an unvalidated key to hide.\nconst ALLOWED_TOP_LEVEL: readonly string[] = [HOME_EXPERIMENTAL_SECTION];\nconst ALLOWED_EXPERIMENTAL: readonly string[] = [\n HOME_KEY_WHOLE_REPO_BUILD_GUARD, HOME_KEY_BUILD_GATE_LOG_CAPTURE, HOME_KEY_ORPHAN_DIR_SWEEP,\n];\n\n/**\n * ─── EVERY KEY IS OPTIONAL, AND THAT IS A HARD REQUIREMENT OF WHERE THIS FILE LIVES ───────────────────\n * This file is MACHINE-GLOBAL: one document, read by every repo on the machine, and those repos pin\n * DIFFERENT webpieces releases. A REQUIRED key cannot survive that, because it makes the set of valid\n * files EMPTY:\n *\n * • omit the new key → the NEW release rejects the file (\"REQUIRED and not set\")\n * • add the new key → every OLDER release rejects the file (\"not a known key\")\n *\n * There is no third option, and both rejections block. `whole-repo-build-guard` was required for the\n * reason recorded in #627 — a flag that decides whether a command RUNS should not be inferred — and that\n * reasoning was sound for a single version and wrong for a shared file. It is optional now, along with\n * every other key.\n *\n * Absent then reads as each key's declared default. For every EXPERIMENTAL key that is false, which is\n * byte-for-byte the behaviour of having no file at all. `whole-repo-build-guard` is the one exception\n * and it defaults TRUE — it is no longer experimental — for the reason spelled out at\n * WHOLE_REPO_BUILD_GUARD_DEFAULT.\n *\n * The other half of cross-version safety — an OLD release ignoring a key a NEW one added, rather than\n * rejecting it — is NOT solved here. Adding a key still requires every repo on the machine to be moved\n * to a release that knows it.\n */\n\n// Read errors that mean \"the file is not there / not reachable\" rather than \"the file is wrong\". Every\n// one of these resolves to the all-defaults config, silently. Widened deliberately past ENOENT: the\n// parent `~/.webpieces` may not exist (ENOENT), may be a file (ENOTDIR), may be unreadable (EACCES /\n// EPERM), and the path itself may be a directory (EISDIR). None of those is a user who opted in.\nconst ABSENT_ERROR_CODES: readonly string[] = ['ENOENT', 'ENOTDIR', 'EACCES', 'EPERM', 'EISDIR', 'ELOOP', 'ENAMETOOLONG'];\n\n/** The parsed `~/.webpieces/config.json`. Data-only (per CLAUDE.md — classes, not interfaces, for data). */\nexport class HomeConfig {\n /**\n * EXPERIMENTAL, under test, not a supported knob. When true, the pr-gate build gate captures its full\n * output to `.webpieces/logs/` and hands a failing build's pointer to that file to the AI instead of\n * an instruction to rebuild. Default false — i.e. the behaviour every consumer has today.\n */\n buildGateLogCapture: boolean;\n\n /**\n * NOT experimental, and ON unless this machine says otherwise: `WHOLE_REPO_BUILD_GUARD_DEFAULT`.\n * When true, `whole-repo-build-guard` BLOCKS a Bash command that would build the WHOLE monorepo and\n * hands back the repo's own scoped build command (`pnpm wp-build`). Setting it false makes the guard\n * completely inert: no block, no log, no message.\n *\n * This is the guard's ONLY switch, and it is an OPT-OUT. There is deliberately no\n * webpieces.config.json entry for it (see RETIRED_CONFIG_KEYS): a guard that every consumer must\n * ADD A KEY to avoid being blocked by is a guard that ships an outage on upgrade, which is exactly\n * what happened. Defaulting ON here is the opposite shape — nothing has to be added anywhere, and\n * see WHOLE_REPO_BUILD_GUARD_DEFAULT for why that distinction is the whole argument.\n */\n wholeRepoBuildGuard: boolean;\n\n /**\n * EXPERIMENTAL. When true, `wp-checkout-clean-main` ARCHIVES the orphan directories it finds — the\n * package directories left behind on every clone by an `nx g move`, which git cannot remove because\n * an ignored `dist/` or `node_modules/` survives the deletion of every tracked file under them.\n *\n * False — and the all-defaults value for a machine with no such file — means the sweep only REPORTS\n * what it found and moves nothing. That asymmetry is the point of shipping this behind the home\n * config at all: the author can run it live across their own clones for a release while every\n * colleague's repo is untouched, and neither state depends on a tracked key anybody must add.\n *\n * The sweep itself never deletes (see OrphanDirArchiver): directories move under `.webpieces/trash/`\n * with a printed `recover=`, so the worst case for a false positive is a `mv` somebody undoes. The\n * ARCHIVE is reaped after 30 days, which is a real deletion — of the second copy, on a timer long\n * enough that anything still wanted has been noticed.\n */\n orphanDirSweep: boolean;\n\n // ALL THREE required, no defaults. A defaulted parameter would leave `new HomeConfig(true)` compiling\n // after this class grew a second flag, silently meaning \"guard off\" — an old spelling that still\n // typechecks with a changed meaning is exactly the shim this repo does not ship. The absent-file\n // state is constructed in exactly one place — load()'s absent-file branch.\n constructor(buildGateLogCapture: boolean, wholeRepoBuildGuard: boolean, orphanDirSweep: boolean) {\n this.buildGateLogCapture = buildGateLogCapture;\n this.wholeRepoBuildGuard = wholeRepoBuildGuard;\n this.orphanDirSweep = orphanDirSweep;\n }\n}\n\n/**\n * One retired `~/.webpieces/config.json` key and the mechanical edit that replaces it. Data-only.\n *\n * This mirrors `RetiredConfigKey` rather than reusing it: that table's entries carry a `scope`\n * (rule-name vs key-in-section) that describes webpieces.config.json's two-level layout and means\n * nothing here, and its `label` convention names that file's sections. One shared class covering both\n * would be a type with fields that are dead for half its instances.\n */\nexport class RetiredHomeConfigKey {\n // Dotted path exactly as it appears in the file, e.g. `experimental.captureBuildGateLog`.\n key: string;\n // Where the value goes now. Empty when the key is deleted outright.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it verbatim.\n instruction: string;\n\n constructor(key: string, movedTo: string, instruction: string) {\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n }\n}\n\n/**\n * Every retired home-config key — the ONE place in the codebase where a dead home-config key may be\n * named, exactly as `RETIRED_CONFIG_KEYS` is for webpieces.config.json. Newest at the bottom.\n *\n * When you retire a key here, DELETE its read path in the same change. `home-config.spec.ts` asserts\n * every entry below actually FAILS the load, so a fallback that quietly accepts one turns it red.\n */\nexport const RETIRED_HOME_CONFIG_KEYS: readonly RetiredHomeConfigKey[] = [\n // `captureBuildGateLog` was the working name while this feature was being built, and it appears in\n // the branch history and in in-flight drafts, so it is exactly the spelling an agent reconstructing\n // the file from memory will type. It never shipped in a release; it is listed so that typing it\n // produces the rename instruction rather than a bare \"unknown key\".\n new RetiredHomeConfigKey(\n 'experimental.captureBuildGateLog', 'experimental.buildGateLogCapture',\n 'Rename the key to \"buildGateLogCapture\" inside the same \"experimental\" object. Its boolean value ' +\n 'carries over unchanged.',\n ),\n];\n\n/**\n * Loads and STRICTLY validates `~/.webpieces/config.json`, and resolves whether a path IS that file (for\n * the guard carve-out that keeps a rejection repairable).\n */\n@injectable(bindingScopeValues.Singleton)\nexport class HomeConfigService {\n /** Absolute path to the preference file. `homeDir` is a parameter so specs never touch a real HOME. */\n configPath(homeDir: string = os.homedir()): string {\n return path.join(homeDir, HOME_CONFIG_DIR, HOME_CONFIG_FILE);\n }\n\n /**\n * The preferences. Returns all-defaults, silently and without touching anything, when the file is not\n * there. THROWS InformAiError, naming the fix, when a file that IS there is wrong.\n */\n load(homeDir: string = os.homedir()): HomeConfig {\n const raw = this.readIfPresent(this.configPath(homeDir));\n // THE ABSENT-FILE STATE, and the ONE place it is constructed. Every EXPERIMENTAL flag is off;\n // `whole-repo-build-guard` is NOT experimental any more and takes WHOLE_REPO_BUILD_GUARD_DEFAULT\n // (true) — the same value a present file that does not name the key gets, so \"no file\" and \"file\n // that ignores this key\" can never disagree. Spelled out rather than defaulted in the\n // constructor — see the note there on why a defaulted parameter is a shim.\n if (raw === null) {\n return new HomeConfig(\n GUARD_OFF_WHEN_ABSENT, WHOLE_REPO_BUILD_GUARD_DEFAULT, GUARD_OFF_WHEN_ABSENT);\n }\n return this.validate(this.parse(raw, this.configPath(homeDir)), this.configPath(homeDir));\n }\n\n /**\n * True when `candidate` names `~/.webpieces/config.json`, in any of the forms an agent writes it:\n * an absolute path, a `~/`-prefixed path, or one still spelled `$HOME/…` / `${HOME}/…`.\n *\n * This is what the hook guards call to grant the file an unconditional Write/Edit PASS. Without it a\n * strict loader could reject the file while the guards blocked the edit that would fix it — the one\n * wedge webpieces.config.json is already immune to, and the reason its own carve-out exists.\n */\n isHomeConfigPath(candidate: string, homeDir: string = os.homedir()): boolean {\n if (candidate.trim() === '') return false;\n return path.resolve(this.expandHome(candidate.trim(), homeDir)) === path.resolve(this.configPath(homeDir));\n }\n\n // `~`, `$HOME` and `${HOME}` at the FRONT only — a home reference anywhere else is not a home path.\n private expandHome(candidate: string, homeDir: string): string {\n if (candidate === '~') return homeDir;\n for (const prefix of ['~/', '$HOME/', '${HOME}/']) {\n if (candidate.startsWith(prefix)) return path.join(homeDir, candidate.slice(prefix.length));\n }\n return candidate;\n }\n\n /**\n * The file's bytes, or null meaning \"no such file — the user did not opt in\".\n *\n * EVERY read failure is null. This is the single most important behaviour in the file: the absent\n * path is the path every consumer of these packages is on, and it may never reach an error branch.\n * An error code outside ABSENT_ERROR_CODES is genuinely exceptional (EIO, EBUSY) and is rethrown,\n * because silently disabling on a failing disk would be its own kind of lie.\n */\n private readIfPresent(file: string): string | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: this catch IS the \"you have no such file\"\n // decision, and that decision may never surface as a failure to a user who never created the file\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readFileSync(file, 'utf8');\n } catch (err: unknown) {\n const error = toError(err);\n if (this.isAbsent(error)) return null;\n throw error;\n }\n }\n\n private isAbsent(error: Error): boolean {\n // webpieces-disable no-any-unknown -- node attaches `code` to fs errors without typing it on Error\n const code = (error as unknown as Record<string, unknown>)['code'];\n return typeof code === 'string' && ABSENT_ERROR_CODES.includes(code);\n }\n\n // A readable file that is not a JSON object is a WRONG file, not an absent one — hence the throw.\n // webpieces-disable no-any-unknown -- an unvalidated user-authored document; every field is narrowed below\n private parse(raw: string, file: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: JSON.parse's own message is useless to an\n // agent on its own, so it is re-thrown as the InformAiError that names the file and the fix\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed immediately below\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new InformAiError(this.error(file, 'the file must contain a single JSON OBJECT, e.g. {}.'));\n }\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n return parsed as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof InformAiError) throw error;\n throw new InformAiError(this.error(file, `the file is not valid JSON (${error.message}).`), { cause: error });\n }\n }\n\n // webpieces-disable no-any-unknown -- see parse(); the document is user-authored and unvalidated\n private validate(raw: Record<string, unknown>, file: string): HomeConfig {\n this.assertNotRetired(raw, file);\n this.assertKnownKeys(Object.keys(raw), ALLOWED_TOP_LEVEL, '', file);\n const section = raw[HOME_EXPERIMENTAL_SECTION];\n if (section !== undefined && (typeof section !== 'object' || section === null || Array.isArray(section))) {\n throw new InformAiError(this.error(file, `\"${HOME_EXPERIMENTAL_SECTION}\" must be a JSON object.`));\n }\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n const experimental = (section ?? {}) as Record<string, unknown>;\n this.assertKnownKeys(Object.keys(experimental), ALLOWED_EXPERIMENTAL, `${HOME_EXPERIMENTAL_SECTION}.`, file);\n return new HomeConfig(\n this.readOptionalBoolean(experimental, HOME_KEY_BUILD_GATE_LOG_CAPTURE, file, GUARD_OFF_WHEN_ABSENT),\n this.readOptionalBoolean(experimental, HOME_KEY_WHOLE_REPO_BUILD_GUARD, file, WHOLE_REPO_BUILD_GUARD_DEFAULT),\n this.readOptionalBoolean(experimental, HOME_KEY_ORPHAN_DIR_SWEEP, file, GUARD_OFF_WHEN_ABSENT),\n );\n }\n\n /**\n * An absent key falls back to `whenAbsent`, which every caller states OUT LOUD — there is no implicit\n * \"absent means false\" any more, because `whole-repo-build-guard` defaults ON and a hidden default\n * would put the two halves of that fact in different files. A PRESENT key of the wrong type is still\n * an error: that is a file somebody wrote wrongly, not a file written for a different release. This\n * is the ONLY reader; see the every-key-is-optional note above for why there is no required variant.\n */\n // webpieces-disable no-any-unknown -- see parse()\n // eslint-disable-next-line @typescript-eslint/max-params\n private readOptionalBoolean(\n experimental: Record<string, unknown>, key: string, file: string, whenAbsent: boolean,\n ): boolean {\n const value = experimental[key];\n if (value === undefined) return whenAbsent;\n return this.asBoolean(value, key, file, ' Remove the quotes, or delete the key.');\n }\n\n // webpieces-disable no-any-unknown -- see parse()\n // eslint-disable-next-line @typescript-eslint/max-params\n private asBoolean(value: unknown, key: string, file: string, fix: string): boolean {\n if (typeof value !== 'boolean') {\n throw new InformAiError(this.error(file,\n `\"${HOME_EXPERIMENTAL_SECTION}.${key}\" must be the boolean ` +\n `true or false, not ${JSON.stringify(value)}.${fix}`));\n }\n return value;\n }\n\n // Retired keys are checked BEFORE unknown-key reporting: \"unknown key\" would send an agent deleting a\n // key whose value it should be MOVING, which is the whole reason the retirement table exists.\n // webpieces-disable no-any-unknown -- see parse()\n private assertNotRetired(raw: Record<string, unknown>, file: string): void {\n for (const entry of RETIRED_HOME_CONFIG_KEYS) {\n if (!this.isPresentAt(raw, entry.key)) continue;\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n throw new InformAiError(this.error(file,\n `\"${entry.key}\" is RETIRED. ${destination} ${entry.instruction}`));\n }\n }\n\n // Is `dotted` (one or two segments — the file is two levels deep by construction) actually present?\n // webpieces-disable no-any-unknown -- see parse()\n private isPresentAt(raw: Record<string, unknown>, dotted: string): boolean {\n const parts = dotted.split('.');\n if (parts.length === 1) return raw[parts[0]] !== undefined;\n const section = raw[parts[0]];\n if (typeof section !== 'object' || section === null || Array.isArray(section)) return false;\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n return (section as Record<string, unknown>)[parts[1]] !== undefined;\n }\n\n /**\n * Every accepted key, rendered from ALLOWED_EXPERIMENTAL rather than hand-listed. The hand-listed\n * version named two keys and went stale the moment a third arrived, telling an agent its brand-new\n * key was not accepted while the validator right above accepted it.\n */\n private quotedExperimentalKeys(): string {\n return ALLOWED_EXPERIMENTAL.map((key: string): string =>\n `\"${HOME_EXPERIMENTAL_SECTION}.${key}\"`).join(', ');\n }\n\n private assertKnownKeys(found: string[], allowed: readonly string[], prefix: string, file: string): void {\n for (const key of found) {\n if (allowed.includes(key)) continue;\n throw new InformAiError(this.error(file,\n `\"${prefix}${key}\" is not a known key.${this.didYouMean(key, allowed, prefix)} ` +\n `The only keys this file accepts are ` +\n `${this.quotedExperimentalKeys()}. ` +\n `Fix the spelling or delete the key.`));\n }\n }\n\n // A case-insensitive match is the overwhelmingly common typo and is worth naming outright.\n private didYouMean(key: string, allowed: readonly string[], prefix: string): string {\n for (const candidate of allowed) {\n if (candidate.toLowerCase() === key.toLowerCase()) return ` Did you mean \"${prefix}${candidate}\"?`;\n }\n return '';\n }\n\n // One shape for every rejection: what is wrong, in which file, and the fact that deleting the file is\n // always a legal fix — because the file is optional, and \"no file\" is a fully supported state.\n private error(file: string, detail: string): string {\n return `[~/.webpieces/config.json] ${detail}\\n\\n` +\n `File: ${file}\\n` +\n `This machine-local preference file is OPTIONAL and EXPERIMENTAL. Editing it is always ` +\n `permitted, even while it is invalid, and deleting it outright is a valid fix — with no such ` +\n `file every webpieces command behaves exactly as it does by default.`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"home-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/home-config.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,+CAAyB;AACzB,mDAA6B;AAC7B,yCAA2D;AAE3D,uDAAkD;AAClD,yCAAqC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgHG;AACU,QAAA,eAAe,GAAG,YAAY,CAAC;AAC/B,QAAA,gBAAgB,GAAG,aAAa,CAAC;AAE9C,uGAAuG;AACvG,qGAAqG;AACrG,mCAAmC;AACtB,QAAA,yBAAyB,GAAG,cAAc,CAAC;AAC3C,QAAA,+BAA+B,GAAG,qBAAqB,CAAC;AACrE,sGAAsG;AACtG,wGAAwG;AACxG,uGAAuG;AACvG,8DAA8D;AACjD,QAAA,+BAA+B,GAAG,wBAAwB,CAAC;AACxE,sGAAsG;AACtG,wFAAwF;AAC3E,QAAA,yBAAyB,GAAG,kBAAkB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;GAoBG;AACU,QAAA,8BAA8B,GAAG,IAAI,CAAC;AAEnD,wGAAwG;AACxG,oGAAoG;AACpG,oGAAoG;AACpG,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAEpC;;;;;;;;;;;GAWG;AACU,QAAA,iBAAiB,GAAsB,CAAC,iCAAyB,CAAC,CAAC;AACnE,QAAA,oBAAoB,GAAsB;IACnD,uCAA+B,EAAE,uCAA+B,EAAE,iCAAyB;CAC9F,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,uGAAuG;AACvG,oGAAoG;AACpG,qGAAqG;AACrG,iGAAiG;AACjG,MAAM,kBAAkB,GAAsB,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;AAE1H,4GAA4G;AAC5G,MAAa,UAAU;IACnB;;;;OAIG;IACH,mBAAmB,CAAU;IAE7B;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAU;IAE7B;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAU;IAExB,sGAAsG;IACtG,iGAAiG;IACjG,iGAAiG;IACjG,2EAA2E;IAC3E,YAAY,mBAA4B,EAAE,mBAA4B,EAAE,cAAuB;QAC3F,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACzC,CAAC;CACJ;AAhDD,gCAgDC;AAED;;;;;;;GAOG;AACH,MAAa,oBAAoB;IAC7B,0FAA0F;IAC1F,GAAG,CAAS;IACZ,oEAAoE;IACpE,OAAO,CAAS;IAChB,yEAAyE;IACzE,WAAW,CAAS;IAEpB,YAAY,GAAW,EAAE,OAAe,EAAE,WAAmB;QACzD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACnC,CAAC;CACJ;AAbD,oDAaC;AAED;;;;;;GAMG;AACU,QAAA,wBAAwB,GAAoC;IACrE,mGAAmG;IACnG,oGAAoG;IACpG,gGAAgG;IAChG,oEAAoE;IACpE,IAAI,oBAAoB,CACpB,kCAAkC,EAAE,kCAAkC,EACtE,mGAAmG;QACnG,yBAAyB,CAC5B;CACJ,CAAC;AAEF;;;;;;;GAOG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC1B,uGAAuG;IACvG,UAAU,CAAC,UAAkB,EAAE,CAAC,OAAO,EAAE;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,uBAAe,EAAE,wBAAgB,CAAC,CAAC;IACjE,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,UAAkB,EAAE,CAAC,OAAO,EAAE;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;QACzD,8FAA8F;QAC9F,iGAAiG;QACjG,iGAAiG;QACjG,sFAAsF;QACtF,2EAA2E;QAC3E,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACf,OAAO,IAAI,UAAU,CACjB,qBAAqB,EAAE,sCAA8B,EAAE,qBAAqB,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB,CAAC,SAAiB,EAAE,UAAkB,EAAE,CAAC,OAAO,EAAE;QAC9D,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/G,CAAC;IAED,oGAAoG;IAC5F,UAAU,CAAC,SAAiB,EAAE,OAAe;QACjD,IAAI,SAAS,KAAK,GAAG;YAAE,OAAO,OAAO,CAAC;QACtC,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;YAChD,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED;;;;;;;OAOG;IACK,aAAa,CAAC,IAAY;QAC9B,qGAAqG;QACrG,kGAAkG;QAClG,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACtC,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,KAAY;QACzB,mGAAmG;QACnG,MAAM,IAAI,GAAI,KAA4C,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,kGAAkG;IAClG,2GAA2G;IACnG,KAAK,CAAC,GAAW,EAAE,IAAY;QACnC,qGAAqG;QACrG,4FAA4F;QAC5F,8DAA8D;QAC9D,IAAI,CAAC;YACD,qFAAqF;YACrF,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzE,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,sDAAsD,CAAC,CAAC,CAAC;YACtG,CAAC;YACD,8FAA8F;YAC9F,OAAO,MAAiC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,KAAK,YAAY,+BAAa;gBAAE,MAAM,KAAK,CAAC;YAChD,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,+BAA+B,KAAK,CAAC,OAAO,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClH,CAAC;IACL,CAAC;IAED,iGAAiG;IACzF,QAAQ,CAAC,GAA4B,EAAE,IAAY;QACvD,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,yBAAiB,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,GAAG,CAAC,iCAAyB,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACvG,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,iCAAyB,0BAA0B,CAAC,CAAC,CAAC;QACvG,CAAC;QACD,8FAA8F;QAC9F,MAAM,YAAY,GAAG,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;QAChE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,4BAAoB,EAAE,GAAG,iCAAyB,GAAG,CAAC,CAAC;QACvG,OAAO,IAAI,UAAU,CACjB,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,uCAA+B,EAAE,IAAI,EAAE,qBAAqB,CAAC,EACpG,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,uCAA+B,EAAE,IAAI,EAAE,sCAA8B,CAAC,EAC7G,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,iCAAyB,EAAE,IAAI,EAAE,qBAAqB,CAAC,CACjG,CAAC;IACN,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,kDAAkD;IAClD,yDAAyD;IACjD,mBAAmB,CACvB,YAAqC,EAAE,GAAW,EAAE,IAAY,EAAE,UAAmB;QAErF,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;QAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,wCAAwC,CAAC,CAAC;IACtF,CAAC;IAED,kDAAkD;IAClD,yDAAyD;IACjD,SAAS,CAAC,KAAc,EAAE,GAAW,EAAE,IAAY,EAAE,GAAW;QACpE,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EACnC,IAAI,iCAAyB,IAAI,GAAG,wBAAwB;gBAC5D,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,sGAAsG;IACtG,8FAA8F;IAC9F,kDAAkD;IAC1C,gBAAgB,CAAC,GAA4B,EAAE,IAAY;QAC/D,KAAK,MAAM,KAAK,IAAI,gCAAwB,EAAE,CAAC;YAC3C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC;gBAAE,SAAS;YAChD,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,KAAK,EAAE;gBACpC,CAAC,CAAC,qCAAqC;gBACvC,CAAC,CAAC,gBAAgB,KAAK,CAAC,OAAO,IAAI,CAAC;YACxC,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EACnC,IAAI,KAAK,CAAC,GAAG,iBAAiB,WAAW,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC;IAED,oGAAoG;IACpG,kDAAkD;IAC1C,WAAW,CAAC,GAA4B,EAAE,MAAc;QAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;QAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5F,8FAA8F;QAC9F,OAAQ,OAAmC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACxE,CAAC;IAED;;;;;;OAMG;IACK,UAAU,CAAC,OAA0B,EAAE,MAAc;QACzD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAW,EAAU,EAAE,CAAC,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACK,eAAe,CAAC,KAAe,EAAE,OAA0B,EAAE,MAAc;QAC/E,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,SAAS;YACpC,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAChD,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE;gBACrB,CAAC,CAAC,kBAAkB,MAAM,GAAG,IAAI,IAAI;gBACrC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC;uBACpC,wHAAwH,CAAC;YAClI,IAAI,CAAC,IAAI,CACL,IAAI,MAAM,GAAG,GAAG,wEAAwE;gBACxF,qBAAqB,KAAK,qBAAqB,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5F,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,eAAe,CAAC,SAAiB;QACrC,KAAK,MAAM,KAAK,IAAI,gCAAwB,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC;gBAAE,SAAS;YACtF,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,KAAK,EAAE;gBACpC,CAAC,CAAC,oCAAoC;gBACtC,CAAC,CAAC,gBAAgB,KAAK,CAAC,OAAO,GAAG,CAAC;YACvC,OAAO,kCAAkC,KAAK,CAAC,GAAG,aAAa,WAAW,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC;QACvG,CAAC;QACD,OAAO,EAAE,CAAC;IACd,CAAC;IAED,qGAAqG;IACrG,+DAA+D;IACvD,IAAI,CAAC,OAAe;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,OAAO,IAAI,CAAC,CAAC;IAC/E,CAAC;IAED;;;;;;;;;OASG;IACK,eAAe,CAAC,GAAW,EAAE,OAA0B;QAC3D,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;YAC/E,IAAI,QAAQ,IAAI,YAAY;gBAAE,SAAS;YACvC,YAAY,GAAG,QAAQ,CAAC;YACxB,IAAI,GAAG,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,qGAAqG;IAC7F,YAAY,CAAC,CAAS,EAAE,CAAS;QACrC,IAAI,QAAQ,GAAa,EAAE,CAAC;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,OAAO,GAAa,CAAC,CAAC,CAAC,CAAC;YAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9E,CAAC;YACD,QAAQ,GAAG,OAAO,CAAC;QACvB,CAAC;QACD,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,sGAAsG;IACtG,+FAA+F;IACvF,KAAK,CAAC,IAAY,EAAE,MAAc;QACtC,OAAO,8BAA8B,MAAM,MAAM;YAC7C,SAAS,IAAI,IAAI;YACjB,wFAAwF;YACxF,8FAA8F;YAC9F,qEAAqE,CAAC;IAC9E,CAAC;CACJ,CAAA;AA3SY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,iBAAiB,CA2S7B","sourcesContent":["import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n/**\n * `~/.webpieces/config.json` — the MACHINE-GLOBAL preference file, and a different thing from the repo's\n * tracked `webpieces.config.json`. It is EXPERIMENTAL and entirely OPTIONAL.\n *\n * ─── ABSENT IS THE NORMAL STATE, AND IT IS NEVER AN ERROR ─────────────────────────────────────────────\n * Essentially every consumer of these packages has no such file, and for them every `wp-*` command must\n * behave byte-for-byte as it did before this file was ever read: no error, no warning, no log line, no\n * extra file, nothing. `load()` therefore treats EVERY failure to READ the bytes — the file missing, the\n * `~/.webpieces` directory missing, a permission error, a path component that is not a directory — as\n * \"not opted in\", and returns all-defaults silently. That is not a fallback for a wrong shape; it is the\n * definition of \"the user did not create this file\".\n *\n * ─── THE STANDING RULE, FOR WHOEVER EDITS THIS FILE NEXT ──────────────────────────────────────────────\n * This file is MACHINE-GLOBAL: ONE document on the disk, read by EVERY repo on the machine, and those\n * repos are pinned to DIFFERENT webpieces releases. Two rules follow, and they are not negotiable:\n *\n * (a) NO KEY MAY EVER BE REQUIRED. `readOptionalBoolean` is the ONLY reader. There is no\n * `readRequiredBoolean`, no `RequiredHomeFlag`, no `REQUIRED_HOME_FLAGS` — those existed once and\n * were deleted; do not reintroduce them under any name.\n * (b) AN UNKNOWN KEY IS IGNORED, never rejected. `warnUnknownKeys` warns; nothing throws.\n *\n * Both halves are needed, and either one alone still leaves a file that some installed release rejects:\n *\n * (a) covers the OLD FILE on a NEW release — a document written before a key existed, missing it.\n * (b) covers the NEW FILE on an OLD release — a document carrying a key that release never heard of.\n *\n * Break either one and every repo on the machine that is not on the newest release hard-blocks: a\n * rejection here fails config load, and that denies every tool call in that repo until somebody upgrades\n * all of them in lockstep. That is the outage this design exists to make impossible, and it is not\n * hypothetical — it is the shape of the incident recorded at the bottom of this docblock.\n *\n * `home-config.spec.ts` pins both halves, and pins them by ENUMERATING `ALLOWED_EXPERIMENTAL` rather\n * than by a hand-written list, so a key added later is covered by the invariant automatically instead of\n * silently escaping it.\n *\n * ─── PRESENT IS STRICT ABOUT WHAT IT UNDERSTANDS, AND FORWARD-COMPATIBLE ABOUT WHAT IT DOES NOT ───────\n * Once the bytes are readable, someone DELIBERATELY created this file, and three of the four failure\n * modes are REJECTED exactly as webpieces.config.json rejects them (see `retired-config-keys.ts`), with\n * an error naming the exact fix and no `??` fallback anywhere:\n *\n * UNPARSEABLE not JSON, or not a single JSON object → REJECT\n * RETIRED KEY listed in RETIRED_HOME_CONFIG_KEYS → REJECT, carrying the migration\n * WRONG TYPE a KNOWN key holding a non-boolean → REJECT\n * UNKNOWN KEY a key no version of this validator has heard of → IGNORED, with a warning\n *\n * ─── WHY THE LAST ROW DIFFERS FROM webpieces.config.json, WHICH STAYS STRICT ──────────────────────────\n * The difference is not a softening of policy; it is that the two files have different CARDINALITY.\n *\n * webpieces.config.json is REPO-TRACKED. One repo, one file, one pinned @webpieces release, and the\n * file moves through git in lockstep with the code that reads it. \"One version reads this document\" is\n * true by construction, so an unknown key there can only be a typo or a dead key — and rejecting it is\n * right, because it is the delivery mechanism for the migration.\n *\n * THIS file is MACHINE-GLOBAL. ONE document, read by EVERY repo on the machine, and those repos pin\n * DIFFERENT releases — deliberately, since a repo's pin is tracked and moves when its own PR lands.\n * So \"an unknown key\" here has a second, entirely legitimate cause that cannot occur in the repo file:\n * a key a NEWER release added, being read by an OLDER one. Rejecting it means adding any key to this\n * file hard-blocks every repo on the machine that has not yet been upgraded — an outage produced by\n * opting IN to an experimental flag, which is the same shape of failure that moved these flags out of\n * webpieces.config.json in the first place (see the section below).\n *\n * This is the exact mirror of the already-settled rule that no key here may be REQUIRED. Both halves fall\n * out of one fact: the set of valid documents must be non-empty for EVERY release on the machine at once.\n * • omit a new key → an old release must not demand it (already true: every key is optional)\n * • add a new key → an old release must not reject it (this change)\n * With only the first half, the set of valid files was still empty the moment a key was added.\n *\n * ─── WHAT THAT COSTS, AND WHAT PAYS FOR IT ────────────────────────────────────────────────────────────\n * The cost is real and worth stating plainly: a TYPO now silently does nothing. `\"whole-repo-build-gaurd\"`\n * used to be a loud rejection; it is now a key nothing reads, so the flag keeps its default and nothing\n * about the machine's behaviour reveals the mistake.\n *\n * That bites HARDEST on a key whose default is ON, which `whole-repo-build-guard` now is\n * (WHOLE_REPO_BUILD_GUARD_DEFAULT). Misspell the OPT-OUT and the guard stays on while the author\n * believes they turned it off — a failure that argues with them rather than one they can shrug at. It is\n * the strongest reason the warning below is not optional decoration, and the reason `nearestKnownKey`\n * had to get fuzzier than the case-insensitive match it replaced: `gaurd` is a transposition, exactly\n * the class of typo an equality test cannot see.\n *\n * That is mitigated, not eliminated, by making the ignore VISIBLE: every unknown key is printed once per\n * load as a `[webpieces]` warning on stderr, and `nearestKnownKey` upgrades that line with a \"did you\n * mean\" whenever the key is within a two-character edit of a known one — which is what a typo is, and\n * what a key from a newer release is not. A warning naming a close match is the strongest signal\n * available that does not also block a colleague on an older pin.\n *\n * The trade was taken this way round because the two mistakes are not symmetric. A typo costs its author\n * one flag that did not turn on, discoverable the moment they check whether the feature is doing\n * anything, on their own machine. A rejection costs every repo on the machine every tool call, and the\n * person it blocks is usually not the person who edited the file.\n *\n * An unknown TOP-LEVEL key is ignored on the identical argument, and it is the more important half: a\n * future release adding a second section (`preferences`, say) beside `experimental` would otherwise be\n * unreadable by every older release on the machine, which is precisely the sequencing being deleted here.\n *\n * ─── REJECTING IS STILL SELF-RECOVERABLE, FOR THE THREE ROWS THAT STILL REJECT ────────────────────────\n * A Write/Edit targeting THIS path is an unconditional PASS in the hook guards (see `isHomeConfigPath`,\n * wired into ai-hook-rules' runner beside the webpieces.config.json pass), so an agent can always repair\n * the file the loader just rejected.\n *\n * ─── WHY A MACHINE-LOCAL SWITCH LIVES HERE AND NOT IN webpieces.config.json ───────────────────────────\n * `whole-repo-build-guard` first shipped as an ordinary validated guard: `mode: 'ON'` by default AND an\n * entry required under `hookGuards`. The consequence on upgrade was an outage — a consumer repo that had\n * not yet added the entry hit fault Y, which blocks EVERY Bash call, for a feature nobody had opted into.\n *\n * The rule that buys back: a switch that lives HERE needs no file, no key and no edit to be in its\n * default state. A repo-tracked config key cannot express that — an entry there is something every\n * consumer must add, on a schedule set by whoever bumps the release.\n *\n * Note which half of that was the outage. It was the REQUIRED KEY, not the on-by-default behaviour: the\n * failure was at config LOAD, before any command was judged. `whole-repo-build-guard` defaults ON again\n * today (see WHOLE_REPO_BUILD_GUARD_DEFAULT) and cannot reproduce it, because there is nothing to add\n * anywhere — the only file that could carry it is optional, and absent is a fully supported state.\n */\nexport const HOME_CONFIG_DIR = '.webpieces';\nexport const HOME_CONFIG_FILE = 'config.json';\n\n// The `experimental` section and its keys. Named as constants because both the validator and its error\n// text must spell them identically — a validator whose message names a different key than the one it\n// checks is worse than no message.\nexport const HOME_EXPERIMENTAL_SECTION = 'experimental';\nexport const HOME_KEY_BUILD_GATE_LOG_CAPTURE = 'buildGateLogCapture';\n// The on/off switch for `whole-repo-build-guard`. Spelled with the GUARD's own name, hyphens and all,\n// so `grep -rn whole-repo-build-guard` finds the switch beside the guard — and so nobody has to learn a\n// second name for one thing. It is a DIFFERENT key from buildGateLogCapture, which is #620's build-log\n// feature and merely selects WHICH refusal this guard prints.\nexport const HOME_KEY_WHOLE_REPO_BUILD_GUARD = 'whole-repo-build-guard';\n// The on/off switch for the orphan-directory sweep `wp-checkout-clean-main` runs. Named for the thing\n// it switches, exactly as the guard key above is — one name, greppable from either end.\nexport const HOME_KEY_ORPHAN_DIR_SWEEP = 'orphan-dir-sweep';\n\n/**\n * `whole-repo-build-guard`'s value on a machine that does not name the key — including the machine with\n * no such file at all, which is essentially every machine. It is TRUE: the guard is ON by default.\n *\n * ─── Why this one key defaults ON, and why that is not the outage this file warns about ─────────────\n * The outage recorded below was a CONFIG-LOAD failure: the guard once required an entry under\n * `hookGuards` in the repo-tracked `webpieces.config.json`, so every consumer who upgraded without\n * adding it hit fault Y and had EVERY Bash call blocked, for a feature they had never asked for. The\n * shape of that failure is \"you must edit a file to get your shell back\". Nothing here reproduces it:\n * no file needs to exist, no key needs to be added, and the load path for an absent or silent file is\n * unchanged. What changes is only what the guard DOES once loaded — it refuses a command that would\n * build the whole monorepo, and hands back the scoped command in its place.\n *\n * That is the behaviour the guard was written for, and OFF-by-default is why it never delivered it: a\n * guard nobody opts into never fires. The measured consequence is a sibling repo whose `ci:local`\n * verify chain ran three whole-world passes on every inner loop for months, while the correct command\n * sat unused in `commands.pr-gate.buildCommand`.\n *\n * The escape hatch stays, and it is one line: `{\"experimental\": {\"whole-repo-build-guard\": false}}`\n * turns it off for a machine, with no repo config involved.\n */\nexport const WHOLE_REPO_BUILD_GUARD_DEFAULT = true;\n\n// Every OTHER key's value when it is not named. False — \"this machine never opted in\" — which for those\n// keys is byte-for-byte the behaviour of having no file at all. Named rather than written as a bare\n// `false` at each call site so the asymmetry with the constant above is visible where it is passed.\nconst GUARD_OFF_WHEN_ABSENT = false;\n\n/**\n * The complete UNDERSTOOD shape. A key not on these lists is ignored with a warning rather than\n * rejected (see the class docblock: this document is machine-global and older releases must survive\n * meeting a newer release's key), so adding a key still means adding it here — a key absent from these\n * lists is never read at all, and the flag it was meant to set keeps the default above.\n *\n * EXPORTED so `home-config.spec.ts` can ENUMERATE them rather than restate them. The cross-version\n * invariant (\"every key is independently omittable\") is only as good as the list the test walks, and a\n * hand-written copy of that list means a NEW key silently escapes the invariant on the day it is added —\n * which is the one failure mode nobody would notice until an older release started rejecting files.\n * Walking the real constant makes the test cover a new key the moment it appears here.\n */\nexport const ALLOWED_TOP_LEVEL: readonly string[] = [HOME_EXPERIMENTAL_SECTION];\nexport const ALLOWED_EXPERIMENTAL: readonly string[] = [\n HOME_KEY_WHOLE_REPO_BUILD_GUARD, HOME_KEY_BUILD_GATE_LOG_CAPTURE, HOME_KEY_ORPHAN_DIR_SWEEP,\n];\n\n/**\n * ─── EVERY KEY IS OPTIONAL, AND THAT IS A HARD REQUIREMENT OF WHERE THIS FILE LIVES ───────────────────\n * This file is MACHINE-GLOBAL: one document, read by every repo on the machine, and those repos pin\n * DIFFERENT webpieces releases. A REQUIRED key cannot survive that, because it makes the set of valid\n * files EMPTY:\n *\n * • omit the new key → the NEW release rejects the file (\"REQUIRED and not set\")\n * • add the new key → every OLDER release rejects the file (\"not a known key\")\n *\n * There is no third option, and both rejections block. `whole-repo-build-guard` was required for the\n * reason recorded in #627 — a flag that decides whether a command RUNS should not be inferred — and that\n * reasoning was sound for a single version and wrong for a shared file. It is optional now, along with\n * every other key.\n *\n * Absent then reads as each key's declared default. For every EXPERIMENTAL key that is false, which is\n * byte-for-byte the behaviour of having no file at all. `whole-repo-build-guard` is the one exception\n * and it defaults TRUE — it is no longer experimental — for the reason spelled out at\n * WHOLE_REPO_BUILD_GUARD_DEFAULT.\n *\n * The other half of cross-version safety — an OLD release IGNORING a key a NEW one added, rather than\n * rejecting it — is solved by `warnUnknownKeys` below. The two halves are one invariant: for the set of\n * valid documents to be non-empty across every release installed on the machine, neither omitting a key\n * nor adding one may be an error.\n */\n\n// Read errors that mean \"the file is not there / not reachable\" rather than \"the file is wrong\". Every\n// one of these resolves to the all-defaults config, silently. Widened deliberately past ENOENT: the\n// parent `~/.webpieces` may not exist (ENOENT), may be a file (ENOTDIR), may be unreadable (EACCES /\n// EPERM), and the path itself may be a directory (EISDIR). None of those is a user who opted in.\nconst ABSENT_ERROR_CODES: readonly string[] = ['ENOENT', 'ENOTDIR', 'EACCES', 'EPERM', 'EISDIR', 'ELOOP', 'ENAMETOOLONG'];\n\n/** The parsed `~/.webpieces/config.json`. Data-only (per CLAUDE.md — classes, not interfaces, for data). */\nexport class HomeConfig {\n /**\n * EXPERIMENTAL, under test, not a supported knob. When true, the pr-gate build gate captures its full\n * output to `.webpieces/logs/` and hands a failing build's pointer to that file to the AI instead of\n * an instruction to rebuild. Default false — i.e. the behaviour every consumer has today.\n */\n buildGateLogCapture: boolean;\n\n /**\n * NOT experimental, and ON unless this machine says otherwise: `WHOLE_REPO_BUILD_GUARD_DEFAULT`.\n * When true, `whole-repo-build-guard` BLOCKS a Bash command that would build the WHOLE monorepo and\n * hands back the repo's own scoped build command (`pnpm wp-build`). Setting it false makes the guard\n * completely inert: no block, no log, no message.\n *\n * This is the guard's ONLY switch, and it is an OPT-OUT. There is deliberately no\n * webpieces.config.json entry for it (see RETIRED_CONFIG_KEYS): a guard that every consumer must\n * ADD A KEY to avoid being blocked by is a guard that ships an outage on upgrade, which is exactly\n * what happened. Defaulting ON here is the opposite shape — nothing has to be added anywhere, and\n * see WHOLE_REPO_BUILD_GUARD_DEFAULT for why that distinction is the whole argument.\n */\n wholeRepoBuildGuard: boolean;\n\n /**\n * EXPERIMENTAL. When true, `wp-checkout-clean-main` ARCHIVES the orphan directories it finds — the\n * package directories left behind on every clone by an `nx g move`, which git cannot remove because\n * an ignored `dist/` or `node_modules/` survives the deletion of every tracked file under them.\n *\n * False — and the all-defaults value for a machine with no such file — means the sweep only REPORTS\n * what it found and moves nothing. That asymmetry is the point of shipping this behind the home\n * config at all: the author can run it live across their own clones for a release while every\n * colleague's repo is untouched, and neither state depends on a tracked key anybody must add.\n *\n * The sweep itself never deletes (see OrphanDirArchiver): directories move under `.webpieces/trash/`\n * with a printed `recover=`, so the worst case for a false positive is a `mv` somebody undoes. The\n * ARCHIVE is reaped after 30 days, which is a real deletion — of the second copy, on a timer long\n * enough that anything still wanted has been noticed.\n */\n orphanDirSweep: boolean;\n\n // ALL THREE required, no defaults. A defaulted parameter would leave `new HomeConfig(true)` compiling\n // after this class grew a second flag, silently meaning \"guard off\" — an old spelling that still\n // typechecks with a changed meaning is exactly the shim this repo does not ship. The absent-file\n // state is constructed in exactly one place — load()'s absent-file branch.\n constructor(buildGateLogCapture: boolean, wholeRepoBuildGuard: boolean, orphanDirSweep: boolean) {\n this.buildGateLogCapture = buildGateLogCapture;\n this.wholeRepoBuildGuard = wholeRepoBuildGuard;\n this.orphanDirSweep = orphanDirSweep;\n }\n}\n\n/**\n * One retired `~/.webpieces/config.json` key and the mechanical edit that replaces it. Data-only.\n *\n * This mirrors `RetiredConfigKey` rather than reusing it: that table's entries carry a `scope`\n * (rule-name vs key-in-section) that describes webpieces.config.json's two-level layout and means\n * nothing here, and its `label` convention names that file's sections. One shared class covering both\n * would be a type with fields that are dead for half its instances.\n */\nexport class RetiredHomeConfigKey {\n // Dotted path exactly as it appears in the file, e.g. `experimental.captureBuildGateLog`.\n key: string;\n // Where the value goes now. Empty when the key is deleted outright.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it verbatim.\n instruction: string;\n\n constructor(key: string, movedTo: string, instruction: string) {\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n }\n}\n\n/**\n * Every retired home-config key — the ONE place in the codebase where a dead home-config key may be\n * named, exactly as `RETIRED_CONFIG_KEYS` is for webpieces.config.json. Newest at the bottom.\n *\n * When you retire a key here, DELETE its read path in the same change. `home-config.spec.ts` asserts\n * every entry below actually FAILS the load, so a fallback that quietly accepts one turns it red.\n */\nexport const RETIRED_HOME_CONFIG_KEYS: readonly RetiredHomeConfigKey[] = [\n // `captureBuildGateLog` was the working name while this feature was being built, and it appears in\n // the branch history and in in-flight drafts, so it is exactly the spelling an agent reconstructing\n // the file from memory will type. It never shipped in a release; it is listed so that typing it\n // produces the rename instruction rather than a bare \"unknown key\".\n new RetiredHomeConfigKey(\n 'experimental.captureBuildGateLog', 'experimental.buildGateLogCapture',\n 'Rename the key to \"buildGateLogCapture\" inside the same \"experimental\" object. Its boolean value ' +\n 'carries over unchanged.',\n ),\n];\n\n/**\n * Loads and validates `~/.webpieces/config.json`, and resolves whether a path IS that file (for the\n * guard carve-out that keeps a rejection repairable).\n *\n * Strict about everything it UNDERSTANDS (a retired key, a known key of the wrong type, and a document\n * that is not JSON all throw); forward-compatible about everything it does not (an unknown key is\n * ignored with a warning). The class docblock at the top of this file has the reasoning.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class HomeConfigService {\n /** Absolute path to the preference file. `homeDir` is a parameter so specs never touch a real HOME. */\n configPath(homeDir: string = os.homedir()): string {\n return path.join(homeDir, HOME_CONFIG_DIR, HOME_CONFIG_FILE);\n }\n\n /**\n * The preferences. Returns all-defaults, silently and without touching anything, when the file is not\n * there. THROWS InformAiError, naming the fix, when a file that IS there is wrong in a way this\n * release can be sure about — unparseable, a RETIRED key, or a KNOWN key of the wrong type. A key it\n * simply does not recognise is ignored with a warning, because it may be a newer release's key and\n * this file is shared by every repo on the machine.\n */\n load(homeDir: string = os.homedir()): HomeConfig {\n const raw = this.readIfPresent(this.configPath(homeDir));\n // THE ABSENT-FILE STATE, and the ONE place it is constructed. Every EXPERIMENTAL flag is off;\n // `whole-repo-build-guard` is NOT experimental any more and takes WHOLE_REPO_BUILD_GUARD_DEFAULT\n // (true) — the same value a present file that does not name the key gets, so \"no file\" and \"file\n // that ignores this key\" can never disagree. Spelled out rather than defaulted in the\n // constructor — see the note there on why a defaulted parameter is a shim.\n if (raw === null) {\n return new HomeConfig(\n GUARD_OFF_WHEN_ABSENT, WHOLE_REPO_BUILD_GUARD_DEFAULT, GUARD_OFF_WHEN_ABSENT);\n }\n return this.validate(this.parse(raw, this.configPath(homeDir)), this.configPath(homeDir));\n }\n\n /**\n * True when `candidate` names `~/.webpieces/config.json`, in any of the forms an agent writes it:\n * an absolute path, a `~/`-prefixed path, or one still spelled `$HOME/…` / `${HOME}/…`.\n *\n * This is what the hook guards call to grant the file an unconditional Write/Edit PASS. Without it a\n * strict loader could reject the file while the guards blocked the edit that would fix it — the one\n * wedge webpieces.config.json is already immune to, and the reason its own carve-out exists.\n */\n isHomeConfigPath(candidate: string, homeDir: string = os.homedir()): boolean {\n if (candidate.trim() === '') return false;\n return path.resolve(this.expandHome(candidate.trim(), homeDir)) === path.resolve(this.configPath(homeDir));\n }\n\n // `~`, `$HOME` and `${HOME}` at the FRONT only — a home reference anywhere else is not a home path.\n private expandHome(candidate: string, homeDir: string): string {\n if (candidate === '~') return homeDir;\n for (const prefix of ['~/', '$HOME/', '${HOME}/']) {\n if (candidate.startsWith(prefix)) return path.join(homeDir, candidate.slice(prefix.length));\n }\n return candidate;\n }\n\n /**\n * The file's bytes, or null meaning \"no such file — the user did not opt in\".\n *\n * EVERY read failure is null. This is the single most important behaviour in the file: the absent\n * path is the path every consumer of these packages is on, and it may never reach an error branch.\n * An error code outside ABSENT_ERROR_CODES is genuinely exceptional (EIO, EBUSY) and is rethrown,\n * because silently disabling on a failing disk would be its own kind of lie.\n */\n private readIfPresent(file: string): string | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: this catch IS the \"you have no such file\"\n // decision, and that decision may never surface as a failure to a user who never created the file\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readFileSync(file, 'utf8');\n } catch (err: unknown) {\n const error = toError(err);\n if (this.isAbsent(error)) return null;\n throw error;\n }\n }\n\n private isAbsent(error: Error): boolean {\n // webpieces-disable no-any-unknown -- node attaches `code` to fs errors without typing it on Error\n const code = (error as unknown as Record<string, unknown>)['code'];\n return typeof code === 'string' && ABSENT_ERROR_CODES.includes(code);\n }\n\n // A readable file that is not a JSON object is a WRONG file, not an absent one — hence the throw.\n // webpieces-disable no-any-unknown -- an unvalidated user-authored document; every field is narrowed below\n private parse(raw: string, file: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: JSON.parse's own message is useless to an\n // agent on its own, so it is re-thrown as the InformAiError that names the file and the fix\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed immediately below\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new InformAiError(this.error(file, 'the file must contain a single JSON OBJECT, e.g. {}.'));\n }\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n return parsed as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof InformAiError) throw error;\n throw new InformAiError(this.error(file, `the file is not valid JSON (${error.message}).`), { cause: error });\n }\n }\n\n // webpieces-disable no-any-unknown -- see parse(); the document is user-authored and unvalidated\n private validate(raw: Record<string, unknown>, file: string): HomeConfig {\n this.assertNotRetired(raw, file);\n this.warnUnknownKeys(Object.keys(raw), ALLOWED_TOP_LEVEL, '');\n const section = raw[HOME_EXPERIMENTAL_SECTION];\n if (section !== undefined && (typeof section !== 'object' || section === null || Array.isArray(section))) {\n throw new InformAiError(this.error(file, `\"${HOME_EXPERIMENTAL_SECTION}\" must be a JSON object.`));\n }\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n const experimental = (section ?? {}) as Record<string, unknown>;\n this.warnUnknownKeys(Object.keys(experimental), ALLOWED_EXPERIMENTAL, `${HOME_EXPERIMENTAL_SECTION}.`);\n return new HomeConfig(\n this.readOptionalBoolean(experimental, HOME_KEY_BUILD_GATE_LOG_CAPTURE, file, GUARD_OFF_WHEN_ABSENT),\n this.readOptionalBoolean(experimental, HOME_KEY_WHOLE_REPO_BUILD_GUARD, file, WHOLE_REPO_BUILD_GUARD_DEFAULT),\n this.readOptionalBoolean(experimental, HOME_KEY_ORPHAN_DIR_SWEEP, file, GUARD_OFF_WHEN_ABSENT),\n );\n }\n\n /**\n * An absent key falls back to `whenAbsent`, which every caller states OUT LOUD — there is no implicit\n * \"absent means false\" any more, because `whole-repo-build-guard` defaults ON and a hidden default\n * would put the two halves of that fact in different files.\n *\n * A PRESENT key of the wrong type is still an ERROR, and that is the line the unknown-key change\n * deliberately did not move: `\"whole-repo-build-guard\": \"yes\"` is a file somebody wrote wrongly, not\n * a file written for a different release. No release of webpieces has ever given this key a string\n * meaning, so there is no forward-compatibility story to protect and nothing is gained by guessing —\n * whereas guessing would turn a typed value into a silent fallback to the default, which is the very\n * cost the unknown-key warning exists to bound.\n *\n * This is the ONLY reader; see the every-key-is-optional note above for why there is no required\n * variant.\n */\n // webpieces-disable no-any-unknown -- see parse()\n // eslint-disable-next-line @typescript-eslint/max-params\n private readOptionalBoolean(\n experimental: Record<string, unknown>, key: string, file: string, whenAbsent: boolean,\n ): boolean {\n const value = experimental[key];\n if (value === undefined) return whenAbsent;\n return this.asBoolean(value, key, file, ' Remove the quotes, or delete the key.');\n }\n\n // webpieces-disable no-any-unknown -- see parse()\n // eslint-disable-next-line @typescript-eslint/max-params\n private asBoolean(value: unknown, key: string, file: string, fix: string): boolean {\n if (typeof value !== 'boolean') {\n throw new InformAiError(this.error(file,\n `\"${HOME_EXPERIMENTAL_SECTION}.${key}\" must be the boolean ` +\n `true or false, not ${JSON.stringify(value)}.${fix}`));\n }\n return value;\n }\n\n // Retired keys are checked BEFORE unknown-key reporting: \"unknown key\" would send an agent deleting a\n // key whose value it should be MOVING, which is the whole reason the retirement table exists.\n // webpieces-disable no-any-unknown -- see parse()\n private assertNotRetired(raw: Record<string, unknown>, file: string): void {\n for (const entry of RETIRED_HOME_CONFIG_KEYS) {\n if (!this.isPresentAt(raw, entry.key)) continue;\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n throw new InformAiError(this.error(file,\n `\"${entry.key}\" is RETIRED. ${destination} ${entry.instruction}`));\n }\n }\n\n // Is `dotted` (one or two segments — the file is two levels deep by construction) actually present?\n // webpieces-disable no-any-unknown -- see parse()\n private isPresentAt(raw: Record<string, unknown>, dotted: string): boolean {\n const parts = dotted.split('.');\n if (parts.length === 1) return raw[parts[0]] !== undefined;\n const section = raw[parts[0]];\n if (typeof section !== 'object' || section === null || Array.isArray(section)) return false;\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n return (section as Record<string, unknown>)[parts[1]] !== undefined;\n }\n\n /**\n * The understood keys AT ONE LEVEL, rendered from the allow-list rather than hand-listed — and\n * rendered at the level the reader's key was actually found, so a mistyped SECTION is answered with\n * the sections and a mistyped FLAG with the flags. The hand-listed version named two keys and went\n * stale the moment a third arrived, telling an agent its brand-new key was not accepted while the\n * validator right above accepted it.\n */\n private quotedKeys(allowed: readonly string[], prefix: string): string {\n return allowed.map((key: string): string => `\"${prefix}${key}\"`).join(', ');\n }\n\n /**\n * An unknown key is IGNORED — see the class docblock for why this one file cannot reject it — but it\n * is never SILENT. The warning is the entire mitigation for the cost of ignoring, so it says both\n * things a reader needs: that the key did nothing, and what the understood keys are.\n *\n * ─── WHY THIS IS NOT THE \"console side channel\" SHAPE ────────────────────────────────────────────\n * That shape is a rule or a library reporting a FAILURE — or a cure for one — by printing it instead\n * of throwing a structured value to the one top-level handler. This is the opposite case, and the\n * distinction is the entire subject of this change: an unknown key here is NOT a failure. The load\n * SUCCEEDS, a valid HomeConfig is returned, every caller proceeds normally, and there is no cure the\n * reader is obliged to apply — a key from a newer release is a CORRECT file being read by an older\n * validator. There is no throw this could be, because throwing is precisely the behaviour being\n * deleted here; and returning it would mean inventing a warnings channel through `load()` that no\n * caller has any reason to render.\n *\n * stderr, not stdout, for the usual reason: this runs inside hooks whose stdout is a JSON decision\n * and inside `wp-*` commands whose stdout is their real output, and neither may be polluted. It is\n * the same channel, with the same `[webpieces]` prefix, that `state-dir-migration.announce` already\n * uses in this package for the same category of finding — something a human may want to know about\n * and is not required to act on.\n */\n private warnUnknownKeys(found: string[], allowed: readonly string[], prefix: string): void {\n for (const key of found) {\n if (allowed.includes(key)) continue;\n const near = this.nearestKnownKey(key, allowed);\n const guess = near !== ''\n ? ` Did you mean \"${prefix}${near}\"?`\n : this.nearRetiredHint(`${prefix}${key}`)\n || ' If it is a typo, fix the spelling; if it is from a NEWER @webpieces than this repo pins, upgrade this repo to use it.';\n this.warn(\n `\"${prefix}${key}\" is not a key this @webpieces release understands, so it was IGNORED ` +\n `and had NO effect.${guess} Understood here: ${this.quotedKeys(allowed, prefix)}.`);\n }\n }\n\n /**\n * A near-miss of a RETIRED key, pointed at its migration — or '' when nothing retired is close.\n *\n * The gap this closes: `assertNotRetired` matches a retired key EXACTLY, so `captureBuildGateLog`\n * throws with its rename instruction while `captureBuildGateLogg` — one stray character away, and a\n * far likelier thing to type — falls through to the generic \"IGNORED, might be from a newer release\"\n * line. That is the least helpful of the three answers offered to the reader whose intent is the\n * clearest, so the retired table is consulted here too, at the same distance-2 threshold.\n *\n * It only ever produces a WARNING, never a throw: this release cannot know whether the reader meant\n * the retired key or a newer one, and guessing wrong in the throwing direction is what the whole\n * change is about. Known keys are matched first, so a typo of a LIVE key is never answered with a\n * dead one.\n */\n private nearRetiredHint(dottedKey: string): string {\n for (const entry of RETIRED_HOME_CONFIG_KEYS) {\n if (this.editDistance(dottedKey.toLowerCase(), entry.key.toLowerCase()) > 2) continue;\n const destination = entry.movedTo === ''\n ? 'it was removed with no replacement'\n : `it moved to \"${entry.movedTo}\"`;\n return ` Did you mean the RETIRED key \"${entry.key}\"? If so, ${destination}. ${entry.instruction}`;\n }\n return '';\n }\n\n // One shape for every non-fatal finding, matching state-dir-migration's `[webpieces] <what>:` prefix\n // so a reader can tell at a glance which subsystem is talking.\n private warn(message: string): void {\n process.stderr.write(`[webpieces] ~/.webpieces/config.json: ${message}\\n`);\n }\n\n /**\n * The closest understood key within two edits, or '' when nothing is close.\n *\n * This used to be a case-insensitive EQUALITY test, which was adequate while an unknown key was a\n * hard error — the error itself was the signal, and the suggestion only saved a reading. Now the\n * suggestion IS the signal, so it has to catch the typos an equality test misses: a doubled letter,\n * a dropped one, a transposition, a stray trailing `d` (`buildGateLogCaptured`). Two is the useful\n * threshold — it covers every one of those and still refuses to guess for a genuinely new key, which\n * is the case that must NOT be dressed up as a typo.\n */\n private nearestKnownKey(key: string, allowed: readonly string[]): string {\n let best = '';\n let bestDistance = 3;\n for (const candidate of allowed) {\n const distance = this.editDistance(key.toLowerCase(), candidate.toLowerCase());\n if (distance >= bestDistance) continue;\n bestDistance = distance;\n best = candidate;\n }\n return best;\n }\n\n /** Ordinary Levenshtein distance, one row at a time — the key names are short and this runs once. */\n private editDistance(a: string, b: string): number {\n let previous: number[] = [];\n for (let j = 0; j <= b.length; j += 1) previous.push(j);\n for (let i = 1; i <= a.length; i += 1) {\n const current: number[] = [i];\n for (let j = 1; j <= b.length; j += 1) {\n const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);\n current.push(Math.min(substitution, previous[j] + 1, current[j - 1] + 1));\n }\n previous = current;\n }\n return previous[b.length];\n }\n\n // One shape for every rejection: what is wrong, in which file, and the fact that deleting the file is\n // always a legal fix — because the file is optional, and \"no file\" is a fully supported state.\n private error(file: string, detail: string): string {\n return `[~/.webpieces/config.json] ${detail}\\n\\n` +\n `File: ${file}\\n` +\n `This machine-local preference file is OPTIONAL and EXPERIMENTAL. Editing it is always ` +\n `permitted, even while it is invalid, and deleting it outright is a valid fix — with no such ` +\n `file every webpieces command behaves exactly as it does by default.`;\n }\n}\n"]}
|
package/src/index.js
CHANGED
|
@@ -146,7 +146,11 @@ Object.defineProperty(exports, "PrunedKey", { enumerable: true, get: function ()
|
|
|
146
146
|
var checklist_docs_validator_1 = require("./checklist-docs-validator");
|
|
147
147
|
Object.defineProperty(exports, "validateChecklistDocs", { enumerable: true, get: function () { return checklist_docs_validator_1.validateChecklistDocs; } });
|
|
148
148
|
// The OPTIONAL machine-local `~/.webpieces/config.json`: absent (the normal state for every consumer) means
|
|
149
|
-
// each key's declared default, silently; present means
|
|
149
|
+
// each key's declared default, silently; present means STRICT about what it understands and FORWARD-COMPATIBLE
|
|
150
|
+
// about what it does not. A retired key (its own retirement table), a known key of the wrong TYPE and an
|
|
151
|
+
// unparseable document all REJECT; a key this release simply does not recognise is IGNORED with a warning,
|
|
152
|
+
// because the file is machine-global and the repos reading it pin different releases — rejecting a newer
|
|
153
|
+
// release's key would hard-block every repo on the machine that is not yet on it. See home-config.ts.
|
|
150
154
|
// `isHomeConfigPath` is what grants the file its unconditional Write/Edit PASS in the hook guards, which is
|
|
151
155
|
// what keeps a rejection repairable. Its `experimental.*` keys are deliberately NOT advertised knobs — the one
|
|
152
156
|
// exception is `whole-repo-build-guard`, which is a supported OPT-OUT and defaults ON.
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError, renderRuleFailForAi, renderRuleFailForHuman } from './rule-fail-error';\n// THE one representation of a cure, shared by RuleFailError (build-time) and FixHint (edit-time), plus\n// the one renderer that owns the \"Fix Option N:\" numbering and the \"(preferred)\" tag.\nexport { Option, formatFixOptions } from './fix-option';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliFlag, CliArgSet, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\n// The validation-failure banner: ONE cure (edit the file), plus the marker phrases the validator\n// messages embed rather than re-type.\nexport {\n formatConfigErrorsBanner,\n CONFIG_POLICY_DOC,\n RETIRED_KEY_MARKER,\n RETIRED_TOP_LEVEL_MARKER,\n SECTION_PLACEMENT_MARKER,\n} from './config-error-banner';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile, ConfigParseAttempt, CONFIG_PARSE_ATTEMPTS, CONFIG_PARSE_RETRY_MILLIS } from './config-file';\n// The PARSED-BUT-UNVALIDATED config shape. Exported for readers that walk the file generically rather\n// than through the typed config (the pr-gate active-hatch dashboard section reads every rule's hatches).\nexport type { RawConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR, INSTRUCT_AI_LEAF } from './repo-root';\n// The scoped `.webpieces` resolver. EVERY reader/writer of `.webpieces/...` goes through one of its two\n// named methods so the call site declares whether the state is repo-wide or worktree-private.\nexport { DotWebpieces, dotWebpieces, GitDirs, WORKTREE_STATE_DIR, LOGS_STATE_DIR } from './state-dir';\nexport { StateDirMigrator, StateMigrationReport } from './state-dir-migration';\n// There is NO machine-global state root. `MachineStateHome`/`StateHome`/`WEBPIECES_STATE_HOME` and the\n// `PrBodyStore` that used them are DELETED: the one artifact that needed a scope above the clone was the\n// gated squash body, and GitHub holds it now (it IS the PR description). Every `.webpieces` path a\n// webpieces tool writes is `{repo}/.webpieces`, resolved through `DotWebpieces` above. See\n// `decisions/0005-the-pr-description-is-the-merge-body.md`.\nexport { AgedTreeSweeper, SweepCount, RETENTION_DAYS } from './aged-tree-sweep';\nexport { ClaudeEnv, claudeEnv, CLAUDE_PROJECT_DIR_ENV, CLAUDE_PROJECT_DIR_UNSET } from './claude-env';\nexport { AtomicFile } from './atomic-file';\n// The ONE formatter for a remedy that must run in a named directory: `cd '<root>' && <command>`.\n// Single-quoted so a repo path containing a space is still runnable (and still un-smuggleable).\nexport { atRoot } from './at-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateExcludePaths, validateMatchRulesSection, allRuleNames, recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './validate-config';\nexport { validateCommandsSection } from './commands-section-validators';\nexport { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';\n// The retired-key table + the no-back-compat policy it enforces. Exported so the installer can migrate\n// what the errors instruct, and so consumers can enumerate retirements.\nexport { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';\n// The MECHANICAL cure the unknown-rule error and the banner both name: strip every key no validator has a\n// schema for, so cleanliness is one command rather than a judgement call made while every Bash call is\n// blocked. `PRUNE_UNKNOWN_COMMAND` (constants.ts) is the single spelling of that command.\nexport { ConfigPruner, PruneResult, PrunedKey } from './config-pruner';\nexport { validateChecklistDocs } from './checklist-docs-validator';\n// The OPTIONAL machine-local `~/.webpieces/config.json`: absent (the normal state for every consumer) means\n// each key's declared default, silently; present means strictly validated, with its own retirement table.\n// `isHomeConfigPath` is what grants the file its unconditional Write/Edit PASS in the hook guards, which is\n// what keeps a rejection repairable. Its `experimental.*` keys are deliberately NOT advertised knobs — the one\n// exception is `whole-repo-build-guard`, which is a supported OPT-OUT and defaults ON.\nexport {\n HomeConfig, HomeConfigService, RetiredHomeConfigKey, RETIRED_HOME_CONFIG_KEYS,\n HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_EXPERIMENTAL_SECTION, HOME_KEY_BUILD_GATE_LOG_CAPTURE,\n HOME_KEY_ORPHAN_DIR_SWEEP, HOME_KEY_WHOLE_REPO_BUILD_GUARD, WHOLE_REPO_BUILD_GUARD_DEFAULT,\n} from './home-config';\n// The orphan-directory sweep: the corpse an `nx g move` leaves on every clone, which git cannot remove\n// because an ignored dist/ or node_modules/ outlives every tracked file under it. See orphan-dir-scan.ts\n// for why the predicate is git's own `clean -Xdn` answer rather than a hand-rolled ignore walk.\nexport { OrphanDirScanner, OrphanCandidate } from './orphan-dir-scan';\nexport {\n OrphanDirArchiver, ArchivedOrphan, FailedOrphan, OrphanSweepResult, OrphanSweepManifest,\n TRASH_STATE_DIR, TRASH_MANIFEST_FILE, TRASH_RETENTION_DAYS,\n} from './orphan-dir-archive';\nexport { OrphanDirSweeper, OrphanSweepReport } from './orphan-dir-sweep';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { schemaFieldNames } from './rule-schemas';\nexport { HOOK_GUARD_NAMES, BRANCH_STATE_GUARD_KEY, PR_LIFECYCLE_GUARD_KEY, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n PUSH_DEV_STATE_FILE,\n PRUNE_UNKNOWN_COMMAND,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n WP_PUSH_DEV,\n WP_FINISH_PUSH_DEV,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n BranchStateGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n DEFAULT_BUILD_COMMAND,\n LandPrConfig,\n DevDeployConfig,\n DEFAULT_DEV_BRANCH_NAMESPACE,\n DEFAULT_DEV_BRANCH,\n ReviewContextEntry,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n defaultDevDeployConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n buildDevDeployConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n ReviewerInstructionsService,\n ReviewerBriefing,\n BriefedFile,\n ContextEntry,\n READ_TRUNCATION_LINES,\n ALL_DIFF_ONE_READ_LINES,\n} from './reviewer-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ReviewerEvidence,\n ReviewerContext,\n TranscriptScan,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewProvenanceService,\n ReviewProvenance,\n ReviewerTranscript,\n ReviewerPaths,\n OfferedContext,\n ProvenanceWriteRequest,\n DEFAULT_RETENTION_DAYS,\n} from './review-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncStatusFile,\n MainSyncFileStore,\n PullRequestIndex,\n MAIN_SYNC_STATUS_VERSION,\n} from './main-sync-file';\nexport {\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n readMainSyncStatusFile,\n writeMainSyncStatus,\n writeMainSyncStatusFile,\n computeAllMainSyncStatuses,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeService,\n} from './worktrees';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport {\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n} from './worktree-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError, renderRuleFailForAi, renderRuleFailForHuman } from './rule-fail-error';\n// THE one representation of a cure, shared by RuleFailError (build-time) and FixHint (edit-time), plus\n// the one renderer that owns the \"Fix Option N:\" numbering and the \"(preferred)\" tag.\nexport { Option, formatFixOptions } from './fix-option';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliFlag, CliArgSet, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\n// The validation-failure banner: ONE cure (edit the file), plus the marker phrases the validator\n// messages embed rather than re-type.\nexport {\n formatConfigErrorsBanner,\n CONFIG_POLICY_DOC,\n RETIRED_KEY_MARKER,\n RETIRED_TOP_LEVEL_MARKER,\n SECTION_PLACEMENT_MARKER,\n} from './config-error-banner';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile, ConfigParseAttempt, CONFIG_PARSE_ATTEMPTS, CONFIG_PARSE_RETRY_MILLIS } from './config-file';\n// The PARSED-BUT-UNVALIDATED config shape. Exported for readers that walk the file generically rather\n// than through the typed config (the pr-gate active-hatch dashboard section reads every rule's hatches).\nexport type { RawConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR, INSTRUCT_AI_LEAF } from './repo-root';\n// The scoped `.webpieces` resolver. EVERY reader/writer of `.webpieces/...` goes through one of its two\n// named methods so the call site declares whether the state is repo-wide or worktree-private.\nexport { DotWebpieces, dotWebpieces, GitDirs, WORKTREE_STATE_DIR, LOGS_STATE_DIR } from './state-dir';\nexport { StateDirMigrator, StateMigrationReport } from './state-dir-migration';\n// There is NO machine-global state root. `MachineStateHome`/`StateHome`/`WEBPIECES_STATE_HOME` and the\n// `PrBodyStore` that used them are DELETED: the one artifact that needed a scope above the clone was the\n// gated squash body, and GitHub holds it now (it IS the PR description). Every `.webpieces` path a\n// webpieces tool writes is `{repo}/.webpieces`, resolved through `DotWebpieces` above. See\n// `decisions/0005-the-pr-description-is-the-merge-body.md`.\nexport { AgedTreeSweeper, SweepCount, RETENTION_DAYS } from './aged-tree-sweep';\nexport { ClaudeEnv, claudeEnv, CLAUDE_PROJECT_DIR_ENV, CLAUDE_PROJECT_DIR_UNSET } from './claude-env';\nexport { AtomicFile } from './atomic-file';\n// The ONE formatter for a remedy that must run in a named directory: `cd '<root>' && <command>`.\n// Single-quoted so a repo path containing a space is still runnable (and still un-smuggleable).\nexport { atRoot } from './at-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateExcludePaths, validateMatchRulesSection, allRuleNames, recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './validate-config';\nexport { validateCommandsSection } from './commands-section-validators';\nexport { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';\n// The retired-key table + the no-back-compat policy it enforces. Exported so the installer can migrate\n// what the errors instruct, and so consumers can enumerate retirements.\nexport { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';\n// The MECHANICAL cure the unknown-rule error and the banner both name: strip every key no validator has a\n// schema for, so cleanliness is one command rather than a judgement call made while every Bash call is\n// blocked. `PRUNE_UNKNOWN_COMMAND` (constants.ts) is the single spelling of that command.\nexport { ConfigPruner, PruneResult, PrunedKey } from './config-pruner';\nexport { validateChecklistDocs } from './checklist-docs-validator';\n// The OPTIONAL machine-local `~/.webpieces/config.json`: absent (the normal state for every consumer) means\n// each key's declared default, silently; present means STRICT about what it understands and FORWARD-COMPATIBLE\n// about what it does not. A retired key (its own retirement table), a known key of the wrong TYPE and an\n// unparseable document all REJECT; a key this release simply does not recognise is IGNORED with a warning,\n// because the file is machine-global and the repos reading it pin different releases — rejecting a newer\n// release's key would hard-block every repo on the machine that is not yet on it. See home-config.ts.\n// `isHomeConfigPath` is what grants the file its unconditional Write/Edit PASS in the hook guards, which is\n// what keeps a rejection repairable. Its `experimental.*` keys are deliberately NOT advertised knobs — the one\n// exception is `whole-repo-build-guard`, which is a supported OPT-OUT and defaults ON.\nexport {\n HomeConfig, HomeConfigService, RetiredHomeConfigKey, RETIRED_HOME_CONFIG_KEYS,\n HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_EXPERIMENTAL_SECTION, HOME_KEY_BUILD_GATE_LOG_CAPTURE,\n HOME_KEY_ORPHAN_DIR_SWEEP, HOME_KEY_WHOLE_REPO_BUILD_GUARD, WHOLE_REPO_BUILD_GUARD_DEFAULT,\n} from './home-config';\n// The orphan-directory sweep: the corpse an `nx g move` leaves on every clone, which git cannot remove\n// because an ignored dist/ or node_modules/ outlives every tracked file under it. See orphan-dir-scan.ts\n// for why the predicate is git's own `clean -Xdn` answer rather than a hand-rolled ignore walk.\nexport { OrphanDirScanner, OrphanCandidate } from './orphan-dir-scan';\nexport {\n OrphanDirArchiver, ArchivedOrphan, FailedOrphan, OrphanSweepResult, OrphanSweepManifest,\n TRASH_STATE_DIR, TRASH_MANIFEST_FILE, TRASH_RETENTION_DAYS,\n} from './orphan-dir-archive';\nexport { OrphanDirSweeper, OrphanSweepReport } from './orphan-dir-sweep';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { schemaFieldNames } from './rule-schemas';\nexport { HOOK_GUARD_NAMES, BRANCH_STATE_GUARD_KEY, PR_LIFECYCLE_GUARD_KEY, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n PUSH_DEV_STATE_FILE,\n PRUNE_UNKNOWN_COMMAND,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n WP_PUSH_DEV,\n WP_FINISH_PUSH_DEV,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n BranchStateGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n DEFAULT_BUILD_COMMAND,\n LandPrConfig,\n DevDeployConfig,\n DEFAULT_DEV_BRANCH_NAMESPACE,\n DEFAULT_DEV_BRANCH,\n ReviewContextEntry,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n defaultDevDeployConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n buildDevDeployConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n ReviewerInstructionsService,\n ReviewerBriefing,\n BriefedFile,\n ContextEntry,\n READ_TRUNCATION_LINES,\n ALL_DIFF_ONE_READ_LINES,\n} from './reviewer-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ReviewerEvidence,\n ReviewerContext,\n TranscriptScan,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewProvenanceService,\n ReviewProvenance,\n ReviewerTranscript,\n ReviewerPaths,\n OfferedContext,\n ProvenanceWriteRequest,\n DEFAULT_RETENTION_DAYS,\n} from './review-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncStatusFile,\n MainSyncFileStore,\n PullRequestIndex,\n MAIN_SYNC_STATUS_VERSION,\n} from './main-sync-file';\nexport {\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n readMainSyncStatusFile,\n writeMainSyncStatus,\n writeMainSyncStatusFile,\n computeAllMainSyncStatuses,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeService,\n} from './worktrees';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport {\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n} from './worktree-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
|
@@ -66,7 +66,7 @@ guards, meant at most one of the four values could ever reach a spawn.
|
|
|
66
66
|
| # | tools | state | act | cure |
|
|
67
67
|
|---|---|---|---|---|
|
|
68
68
|
| 1 | `B R E` | on the **global allowlist** (inert command, or a universal cure such as reading/editing `webpieces.config.json`) | 1 allow | — |
|
|
69
|
-
| 2 | `B` | bare `git checkout main`, with no `git pull` chained into the same command | 4 block | `
|
|
69
|
+
| 2 | `B` | bare `git checkout main`, with no `git pull` chained into the same command | 4 block | `pnpm wp-checkout-clean-main` |
|
|
70
70
|
| 3 | `B R E` | **merge in progress** — L4 owns this state | 2 exempt | finish the merge: `pnpm wp-finish-upsert-pr` |
|
|
71
71
|
| 4 | `B` | on the **skip list** — it gets you OUT, or tells you where you are | 1 allow | — |
|
|
72
72
|
| 5 | `B E` | on `main` | 4 block | `git checkout -b <new> origin/main` |
|
|
@@ -143,7 +143,7 @@ under. So a case whose row is wrong fails the build rather than misinforming a r
|
|
|
143
143
|
|---|---|---|---|---|
|
|
144
144
|
| 1 | You are blocked by some other L2 row, and need to turn the policy off to get anything done | any state — this row is ahead of every block | ALLOW: reading and editing `webpieces.config.json` is never blocked, so the mode-OFF cure is always reachable | Edit `webpieces.config.json` → `hookGuards` → `branch-state-guard` → `"mode": "OFF"` |
|
|
145
145
|
| 2 | A Write to `webpieces.config.json` while on `main`, which row 5 would otherwise block | on `main`, editing the one file that can disable the guard | ALLOW: the hook adapter bypasses feature-branch-guard for this path before any guard runs | None needed — the edit proceeds |
|
|
146
|
-
| 3 | `git checkout main` after a merge, to start the next piece of work | about to land on whatever local `main` you last had — 157 commits behind, in the incident | BLOCK: decided from command TEXT alone, before the checkout, because the only `main` this could measure is the one it is about to leave | `
|
|
146
|
+
| 3 | `git checkout main` after a merge, to start the next piece of work | about to land on whatever local `main` you last had — 157 commits behind, in the incident | BLOCK: decided from command TEXT alone, before the checkout, because the only `main` this could measure is the one it is about to leave | `pnpm wp-checkout-clean-main` — checkout, pull, reap dead branches/worktrees, sweep orphan directories, in one command (hand-rolled, the pull must be in the SAME command as the checkout) |
|
|
147
147
|
| 4 | The same command inside a linked worktree, where `git checkout main` fatals anyway | linked worktree — `main` is already checked out in the primary clone | BLOCK, and the message prints the worktree form rather than a cure git would refuse | `git fetch origin main`, then work off `origin/main` |
|
|
148
148
|
| 5 | Reading and editing conflicted files during a 3-point merge, on a branch row 9 would block | merge markers on disk — `pnpm wp-start-update` has run and not finished | EXEMPT: everything is permitted, which is exactly what lets row 9 be strict | Resolve the conflicts, then `pnpm wp-finish-upsert-pr` |
|
|
149
149
|
| 6 | `git status` / `gh pr view` while blocked, to work out where you are | any state — orientation is never "working here" | ALLOW: metadata tells you where you are without putting stale file CONTENT in context | None needed |
|
|
@@ -194,7 +194,7 @@ Principle: **these get you OUT or tell you where you are.** They are not "workin
|
|
|
194
194
|
| group | commands |
|
|
195
195
|
|---|---|
|
|
196
196
|
| get out | `git checkout -b <new> origin/main` · `git switch -c <new> origin/main` · `git switch <other>` · `git worktree add … -b <new> origin/main` |
|
|
197
|
-
| make `main` current | `git pull` · `git fetch` · `git checkout main && git pull origin main` *(paired only)* |
|
|
197
|
+
| make `main` current | `pnpm wp-checkout-clean-main` *(the prescribed form — also reaps dead branches/worktrees and sweeps orphan directories)* · `git pull` · `git fetch` · `git checkout main && git pull origin main` *(paired only; still allowed, and still the L0 recovery cure, where no `pnpm` bin can be trusted)* |
|
|
198
198
|
| orient | `git status\|log\|diff\|branch` · `gh pr view\|list\|status\|checks` |
|
|
199
199
|
| park work | `git stash` |
|
|
200
200
|
| repair / tooling | `pnpm wp-start-update` · `pnpm wp-start-upsert-pr` · the `wp-*` bins |
|