@webpieces/rules-config 0.4.620 → 0.4.621
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/config-error-banner.d.ts +26 -5
- package/src/config-error-banner.js +42 -11
- package/src/config-error-banner.js.map +1 -1
- package/src/config-pruner.d.ts +84 -0
- package/src/config-pruner.js +182 -0
- package/src/config-pruner.js.map +1 -0
- package/src/constants.d.ts +11 -0
- package/src/constants.js +12 -1
- package/src/constants.js.map +1 -1
- package/src/index.d.ts +3 -2
- package/src/index.js +16 -7
- package/src/index.js.map +1 -1
- package/src/retired-config-keys.d.ts +26 -5
- package/src/retired-config-keys.js +41 -13
- package/src/retired-config-keys.js.map +1 -1
- package/src/validate-config.js +34 -16
- package/src/validate-config.js.map +1 -1
- package/templates/webpieces.config-policy.md +22 -9
- package/templates/webpieces.guard-matrix.md +4 -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.621",
|
|
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",
|
|
@@ -24,13 +24,25 @@
|
|
|
24
24
|
* longer exists, and the paragraph that offered it is gone with it: the banner names ONE action, and
|
|
25
25
|
* an optional second command is how a reader comes to believe there is a choice to make.
|
|
26
26
|
*
|
|
27
|
-
* ## Why the negative
|
|
27
|
+
* ## Why the negative instruction is here
|
|
28
28
|
*
|
|
29
29
|
* The failure mode was not a missing instruction, it was a generic ordered list — which is exactly how
|
|
30
30
|
* an agent picks the wrong step. So the banner states what NOT to do as plainly as what to do: do not
|
|
31
|
-
* run `pnpm install
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* run `pnpm install`. That is the only DO-NOT left, and it is the only one there was ever evidence for.
|
|
32
|
+
*
|
|
33
|
+
* ## The delete advice used to be INVERTED, and that inversion cost a consumer a day
|
|
34
|
+
*
|
|
35
|
+
* This banner used to say "Do NOT delete a key just because it is reported unknown", steering the reader
|
|
36
|
+
* at the stale-PIN case first. That is backwards. A key the running validator has no schema for controls
|
|
37
|
+
* NOTHING; leaving it in place is dead config that reads as live config to the next reader, and for a
|
|
38
|
+
* RETIRED key deleting it is the entire fix. The sentence told the reader not to do the one thing that
|
|
39
|
+
* works, for a key (`whole-repo-build-guard`) whose own retirement instruction is "DELETE this entry".
|
|
40
|
+
*
|
|
41
|
+
* So deletion is now the PRIMARY cure and it is MECHANICAL — `PRUNE_UNKNOWN_COMMAND` strips every unknown
|
|
42
|
+
* key — while the stale pin is a secondary note. The stale pin can afford to be secondary because it is
|
|
43
|
+
* not this banner's job at all: the version-drift guard in the shim compares the pin against the installed
|
|
44
|
+
* version BEFORE exec'ing the validator, and on drift it denies every tool call with its own message and
|
|
45
|
+
* its own cure. Reaching this banner is therefore evidence that no drift was detected.
|
|
34
46
|
*
|
|
35
47
|
* ## Nothing here is conditional
|
|
36
48
|
*
|
|
@@ -42,6 +54,15 @@
|
|
|
42
54
|
* commands-section-validators.ts, validate-config.ts) embed rather than re-type, so a message and the
|
|
43
55
|
* phrase other code searches for cannot drift apart.
|
|
44
56
|
*/
|
|
57
|
+
/**
|
|
58
|
+
* The background doc this banner sends the reader to, named ONCE so the link, the installer that ships
|
|
59
|
+
* the file, and the spec that holds it to the banner's advice cannot drift apart.
|
|
60
|
+
*
|
|
61
|
+
* That drift is not hypothetical: the first cut of the delete-first rewrite fixed the banner and left
|
|
62
|
+
* this document still saying "Do not start by deleting keys" and prescribing `pnpm install` first — i.e.
|
|
63
|
+
* the message was cured and the page it cites went on teaching the thing that was removed.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CONFIG_POLICY_DOC = "webpieces.config-policy.md";
|
|
45
66
|
/** `retiredKeyError` — every RETIRED_CONFIG_KEYS entry is one `migrate()` knows how to move/rename. */
|
|
46
67
|
export declare const RETIRED_KEY_MARKER = "is a RETIRED webpieces.config.json key";
|
|
47
68
|
/** `validateCommandsSection` — the retired TOP-LEVEL pr-gate block (a shape, so it has its own text). */
|
|
@@ -49,7 +70,7 @@ export declare const RETIRED_TOP_LEVEL_MARKER = "block is RETIRED";
|
|
|
49
70
|
/** `validateSectionPlacement` — a built-in configured under the wrong section. */
|
|
50
71
|
export declare const SECTION_PLACEMENT_MARKER = "belongs in the";
|
|
51
72
|
/**
|
|
52
|
-
* Assemble the banner: the errors, then the ONE cure, then the
|
|
73
|
+
* Assemble the banner: the errors, then the ONE cure, then the warnings. Nothing else belongs here —
|
|
53
74
|
* see the module comment.
|
|
54
75
|
*/
|
|
55
76
|
export declare function formatConfigErrorsBanner(errors: readonly string[]): string;
|
|
@@ -25,13 +25,25 @@
|
|
|
25
25
|
* longer exists, and the paragraph that offered it is gone with it: the banner names ONE action, and
|
|
26
26
|
* an optional second command is how a reader comes to believe there is a choice to make.
|
|
27
27
|
*
|
|
28
|
-
* ## Why the negative
|
|
28
|
+
* ## Why the negative instruction is here
|
|
29
29
|
*
|
|
30
30
|
* The failure mode was not a missing instruction, it was a generic ordered list — which is exactly how
|
|
31
31
|
* an agent picks the wrong step. So the banner states what NOT to do as plainly as what to do: do not
|
|
32
|
-
* run `pnpm install
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* run `pnpm install`. That is the only DO-NOT left, and it is the only one there was ever evidence for.
|
|
33
|
+
*
|
|
34
|
+
* ## The delete advice used to be INVERTED, and that inversion cost a consumer a day
|
|
35
|
+
*
|
|
36
|
+
* This banner used to say "Do NOT delete a key just because it is reported unknown", steering the reader
|
|
37
|
+
* at the stale-PIN case first. That is backwards. A key the running validator has no schema for controls
|
|
38
|
+
* NOTHING; leaving it in place is dead config that reads as live config to the next reader, and for a
|
|
39
|
+
* RETIRED key deleting it is the entire fix. The sentence told the reader not to do the one thing that
|
|
40
|
+
* works, for a key (`whole-repo-build-guard`) whose own retirement instruction is "DELETE this entry".
|
|
41
|
+
*
|
|
42
|
+
* So deletion is now the PRIMARY cure and it is MECHANICAL — `PRUNE_UNKNOWN_COMMAND` strips every unknown
|
|
43
|
+
* key — while the stale pin is a secondary note. The stale pin can afford to be secondary because it is
|
|
44
|
+
* not this banner's job at all: the version-drift guard in the shim compares the pin against the installed
|
|
45
|
+
* version BEFORE exec'ing the validator, and on drift it denies every tool call with its own message and
|
|
46
|
+
* its own cure. Reaching this banner is therefore evidence that no drift was detected.
|
|
35
47
|
*
|
|
36
48
|
* ## Nothing here is conditional
|
|
37
49
|
*
|
|
@@ -44,8 +56,18 @@
|
|
|
44
56
|
* phrase other code searches for cannot drift apart.
|
|
45
57
|
*/
|
|
46
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.SECTION_PLACEMENT_MARKER = exports.RETIRED_TOP_LEVEL_MARKER = exports.RETIRED_KEY_MARKER = void 0;
|
|
59
|
+
exports.SECTION_PLACEMENT_MARKER = exports.RETIRED_TOP_LEVEL_MARKER = exports.RETIRED_KEY_MARKER = exports.CONFIG_POLICY_DOC = void 0;
|
|
48
60
|
exports.formatConfigErrorsBanner = formatConfigErrorsBanner;
|
|
61
|
+
const constants_1 = require("./constants");
|
|
62
|
+
/**
|
|
63
|
+
* The background doc this banner sends the reader to, named ONCE so the link, the installer that ships
|
|
64
|
+
* the file, and the spec that holds it to the banner's advice cannot drift apart.
|
|
65
|
+
*
|
|
66
|
+
* That drift is not hypothetical: the first cut of the delete-first rewrite fixed the banner and left
|
|
67
|
+
* this document still saying "Do not start by deleting keys" and prescribing `pnpm install` first — i.e.
|
|
68
|
+
* the message was cured and the page it cites went on teaching the thing that was removed.
|
|
69
|
+
*/
|
|
70
|
+
exports.CONFIG_POLICY_DOC = 'webpieces.config-policy.md';
|
|
49
71
|
/** `retiredKeyError` — every RETIRED_CONFIG_KEYS entry is one `migrate()` knows how to move/rename. */
|
|
50
72
|
exports.RETIRED_KEY_MARKER = 'is a RETIRED webpieces.config.json key';
|
|
51
73
|
/** `validateCommandsSection` — the retired TOP-LEVEL pr-gate block (a shape, so it has its own text). */
|
|
@@ -53,10 +75,11 @@ exports.RETIRED_TOP_LEVEL_MARKER = 'block is RETIRED';
|
|
|
53
75
|
/** `validateSectionPlacement` — a built-in configured under the wrong section. */
|
|
54
76
|
exports.SECTION_PLACEMENT_MARKER = 'belongs in the';
|
|
55
77
|
/**
|
|
56
|
-
* Assemble the banner: the errors, then the ONE cure, then the
|
|
78
|
+
* Assemble the banner: the errors, then the ONE cure, then the warnings. Nothing else belongs here —
|
|
57
79
|
* see the module comment.
|
|
58
80
|
*/
|
|
59
81
|
// webpieces-disable no-function-outside-class -- sibling string builder in this module
|
|
82
|
+
// webpieces-disable max-lines-new-methods -- one contiguous string literal; splitting it hides the message
|
|
60
83
|
function formatConfigErrorsBanner(errors) {
|
|
61
84
|
return (
|
|
62
85
|
// No "then retry". Retrying is not a fix STEP — the old banner spent one of its four numbered
|
|
@@ -70,10 +93,18 @@ function formatConfigErrorsBanner(errors) {
|
|
|
70
93
|
`node_modules already agree, so there is nothing out of date to install. (Only exception: ` +
|
|
71
94
|
`@webpieces deps pinned with a RANGE — ^ / ~ / workspace:* — which drift detection skips. This ` +
|
|
72
95
|
`repo pins exact versions.)\n` +
|
|
73
|
-
` •
|
|
74
|
-
`
|
|
75
|
-
`
|
|
76
|
-
`
|
|
77
|
-
|
|
96
|
+
` • DO delete any key reported as an unknown rule. A key the validator has no schema for controls ` +
|
|
97
|
+
`NOTHING — it is dead config that reads as live config to the next reader — and when the key is ` +
|
|
98
|
+
`RETIRED, deleting it is the WHOLE fix. Run \`${constants_1.PRUNE_UNKNOWN_COMMAND}\` to strip every unknown key ` +
|
|
99
|
+
`mechanically, or delete them by hand; either way the file then validates.\n` +
|
|
100
|
+
` • Secondary, and rare: a key can be valid-but-unlearned when package.json pins an @webpieces ` +
|
|
101
|
+
`OLDER than this config was written for. You are not in that case here — the version-drift guard ` +
|
|
102
|
+
`compares the pin against the installed version BEFORE this validator runs and denies every tool ` +
|
|
103
|
+
`call with its own message and its own cure (bump the pin), so reaching this banner means no drift ` +
|
|
104
|
+
`was detected.\n` +
|
|
105
|
+
` • A MACHINE-LOCAL setting does not belong in this file at all. Those live in ` +
|
|
106
|
+
`~/.webpieces/config.json under "experimental" — an optional file tracked by no repo, whose absence ` +
|
|
107
|
+
`is the default behaviour. Delete the key here and put it there.` +
|
|
108
|
+
`\n\nBackground: .webpieces/instruct-ai/${exports.CONFIG_POLICY_DOC}`);
|
|
78
109
|
}
|
|
79
110
|
//# sourceMappingURL=config-error-banner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-error-banner.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-error-banner.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"config-error-banner.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-error-banner.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;;;AA6BH,4DA2BC;AAtDD,2CAAoD;AAEpD;;;;;;;GAOG;AACU,QAAA,iBAAiB,GAAG,4BAA4B,CAAC;AAE9D,uGAAuG;AAC1F,QAAA,kBAAkB,GAAG,wCAAwC,CAAC;AAE3E,yGAAyG;AAC5F,QAAA,wBAAwB,GAAG,kBAAkB,CAAC;AAE3D,kFAAkF;AACrE,QAAA,wBAAwB,GAAG,gBAAgB,CAAC;AAEzD;;;GAGG;AACH,uFAAuF;AACvF,2GAA2G;AAC3G,SAAgB,wBAAwB,CAAC,MAAyB;IAC9D,OAAO;IACH,8FAA8F;IAC9F,wFAAwF;IACxF,6BAA6B,MAAM,CAAC,MAAM,6CAA6C;QACvF,MAAM,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACxD,kGAAkG;QAClG,8FAA8F;QAC9F,qDAAqD;QACrD,8FAA8F;QAC9F,2FAA2F;QAC3F,gGAAgG;QAChG,8BAA8B;QAC9B,oGAAoG;QACpG,iGAAiG;QACjG,gDAAgD,iCAAqB,gCAAgC;QACrG,6EAA6E;QAC7E,iGAAiG;QACjG,kGAAkG;QAClG,kGAAkG;QAClG,oGAAoG;QACpG,iBAAiB;QACjB,iFAAiF;QACjF,qGAAqG;QACrG,iEAAiE;QACjE,0CAA0C,yBAAiB,EAAE,CAChE,CAAC;AACN,CAAC","sourcesContent":["/**\n * THE validation-failure banner for webpieces.config.json.\n *\n * ## The invariant: there is exactly ONE cure\n *\n * Every error this banner carries came from VALIDATING webpieces.config.json, so the file is the\n * defect and editing it is the fix. That is the whole instruction. Editing it is unconditionally\n * allowed (L0 allowlist entry 2) even while it is invalid, so the cure is always reachable.\n *\n * The banner this replaces printed a FIXED four-step \"FIX ORDER\" that led with `pnpm install`, and each\n * of those steps was wrong in its own way:\n *\n * - `pnpm install` CANNOT help. The committed shim (templates/ai-hook.sh) runs the guard bin only\n * under `[ -x \"$BIN\" ] && [ -z \"$DRIFT_PKG\" ]`, and DRIFT_PKG is set by comparing every exact-pinned\n * `@webpieces/*` spec in the root package.json against the installed version (a `catalog:` spec is\n * resolved through pnpm-lock.yaml first). So the validator only ever runs when package.json and\n * node_modules ALREADY agree: nothing is out of date to install. It is not a first step, it is a\n * detour — and it is the detour that sent an agent down the wrong path in the incident that\n * produced this rewrite. The one gap: the drift check skips RANGE specs (`^`, `~`, `workspace:*`),\n * so a repo pinning with ranges is outside that guarantee. This repo pins exactly\n * (validate-versions-locked).\n * - \"Retry your command\" was not a fix step at all. Retrying is implied by the first line.\n * - A fourth step prescribed an installer flag as a competing \"edit the config\" command. That flag no\n * longer exists, and the paragraph that offered it is gone with it: the banner names ONE action, and\n * an optional second command is how a reader comes to believe there is a choice to make.\n *\n * ## Why the negative instruction is here\n *\n * The failure mode was not a missing instruction, it was a generic ordered list — which is exactly how\n * an agent picks the wrong step. So the banner states what NOT to do as plainly as what to do: do not\n * run `pnpm install`. That is the only DO-NOT left, and it is the only one there was ever evidence for.\n *\n * ## The delete advice used to be INVERTED, and that inversion cost a consumer a day\n *\n * This banner used to say \"Do NOT delete a key just because it is reported unknown\", steering the reader\n * at the stale-PIN case first. That is backwards. A key the running validator has no schema for controls\n * NOTHING; leaving it in place is dead config that reads as live config to the next reader, and for a\n * RETIRED key deleting it is the entire fix. The sentence told the reader not to do the one thing that\n * works, for a key (`whole-repo-build-guard`) whose own retirement instruction is \"DELETE this entry\".\n *\n * So deletion is now the PRIMARY cure and it is MECHANICAL — `PRUNE_UNKNOWN_COMMAND` strips every unknown\n * key — while the stale pin is a secondary note. The stale pin can afford to be secondary because it is\n * not this banner's job at all: the version-drift guard in the shim compares the pin against the installed\n * version BEFORE exec'ing the validator, and on drift it denies every tool call with its own message and\n * its own cure. Reaching this banner is therefore evidence that no drift was detected.\n *\n * ## Nothing here is conditional\n *\n * The banner is the SAME for one error and for twenty: the bullets change, the instruction does not. It\n * names no installer command at all — a unit test asserts that — because every error it can carry is\n * cured by editing the file the errors came from.\n *\n * The markers below are the phrases the message builders (retired-config-keys.ts,\n * commands-section-validators.ts, validate-config.ts) embed rather than re-type, so a message and the\n * phrase other code searches for cannot drift apart.\n */\n\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n/**\n * The background doc this banner sends the reader to, named ONCE so the link, the installer that ships\n * the file, and the spec that holds it to the banner's advice cannot drift apart.\n *\n * That drift is not hypothetical: the first cut of the delete-first rewrite fixed the banner and left\n * this document still saying \"Do not start by deleting keys\" and prescribing `pnpm install` first — i.e.\n * the message was cured and the page it cites went on teaching the thing that was removed.\n */\nexport const CONFIG_POLICY_DOC = 'webpieces.config-policy.md';\n\n/** `retiredKeyError` — every RETIRED_CONFIG_KEYS entry is one `migrate()` knows how to move/rename. */\nexport const RETIRED_KEY_MARKER = 'is a RETIRED webpieces.config.json key';\n\n/** `validateCommandsSection` — the retired TOP-LEVEL pr-gate block (a shape, so it has its own text). */\nexport const RETIRED_TOP_LEVEL_MARKER = 'block is RETIRED';\n\n/** `validateSectionPlacement` — a built-in configured under the wrong section. */\nexport const SECTION_PLACEMENT_MARKER = 'belongs in the';\n\n/**\n * Assemble the banner: the errors, then the ONE cure, then the warnings. Nothing else belongs here —\n * see the module comment.\n */\n// webpieces-disable no-function-outside-class -- sibling string builder in this module\n// webpieces-disable max-lines-new-methods -- one contiguous string literal; splitting it hides the message\nexport function formatConfigErrorsBanner(errors: readonly string[]): string {\n return (\n // No \"then retry\". Retrying is not a fix STEP — the old banner spent one of its four numbered\n // steps on it, which is how \"run the command again\" came to look like a candidate cure.\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL of them:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n👉 THE FIX: edit webpieces.config.json and apply every • above. Each bullet names the exact ` +\n `change to make, and editing this file is ALWAYS allowed through the guard — including right ` +\n `now, while it is invalid. There is no other step.\\n` +\n ` • Do NOT run \\`pnpm install\\` — it cannot help. The guards only run when package.json and ` +\n `node_modules already agree, so there is nothing out of date to install. (Only exception: ` +\n `@webpieces deps pinned with a RANGE — ^ / ~ / workspace:* — which drift detection skips. This ` +\n `repo pins exact versions.)\\n` +\n ` • DO delete any key reported as an unknown rule. A key the validator has no schema for controls ` +\n `NOTHING — it is dead config that reads as live config to the next reader — and when the key is ` +\n `RETIRED, deleting it is the WHOLE fix. Run \\`${PRUNE_UNKNOWN_COMMAND}\\` to strip every unknown key ` +\n `mechanically, or delete them by hand; either way the file then validates.\\n` +\n ` • Secondary, and rare: a key can be valid-but-unlearned when package.json pins an @webpieces ` +\n `OLDER than this config was written for. You are not in that case here — the version-drift guard ` +\n `compares the pin against the installed version BEFORE this validator runs and denies every tool ` +\n `call with its own message and its own cure (bump the pin), so reaching this banner means no drift ` +\n `was detected.\\n` +\n ` • A MACHINE-LOCAL setting does not belong in this file at all. Those live in ` +\n `~/.webpieces/config.json under \"experimental\" — an optional file tracked by no repo, whose absence ` +\n `is the default behaviour. Delete the key here and put it there.` +\n `\\n\\nBackground: .webpieces/instruct-ai/${CONFIG_POLICY_DOC}`\n );\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AtomicFile } from './atomic-file';
|
|
2
|
+
import { ConfigFile } from './config-file';
|
|
3
|
+
/**
|
|
4
|
+
* `wp-prune-unknown-config` — the MECHANICAL cure for "[x] Unknown rule".
|
|
5
|
+
*
|
|
6
|
+
* ## Why a command and not just advice
|
|
7
|
+
*
|
|
8
|
+
* An unknown key controls nothing, so deleting it is the fix, and the validator now says so. But the
|
|
9
|
+
* moment that advice is read is the worst possible moment to act on it by hand: the hook guard denies
|
|
10
|
+
* every Bash call while the config is invalid, so the reader is editing JSON blind, with no way to
|
|
11
|
+
* re-run the validator between edits. Making the cleanup one command means cleanliness is the default
|
|
12
|
+
* path rather than a judgement call taken under a total block — and it makes "the file validates" mean
|
|
13
|
+
* "every key in it is real", which is the property that keeps dead config from reading as live config.
|
|
14
|
+
*
|
|
15
|
+
* ## What it removes, and what it deliberately does NOT
|
|
16
|
+
*
|
|
17
|
+
* Removed, from the `rules` and `hookGuards` sections only:
|
|
18
|
+
* - a name with no entry in RULE_SCHEMAS and no entry in RETIRED_CONFIG_KEYS (a typo, or a rule some
|
|
19
|
+
* release deleted). Nothing reads it, by construction.
|
|
20
|
+
* - a name in RETIRED_CONFIG_KEYS whose entry is `prunable` — the retirement's own instruction is
|
|
21
|
+
* "delete this entry", because the setting left webpieces.config.json entirely.
|
|
22
|
+
*
|
|
23
|
+
* NOT removed:
|
|
24
|
+
* - a RENAME or an in-file move (`prunable: false`). Deleting those discards a value the new key still
|
|
25
|
+
* needs, so they keep their migration instruction and a human/agent applies it.
|
|
26
|
+
* - anything at all when `rulesDir` is configured. A custom rule legitimately has no built-in schema,
|
|
27
|
+
* and this command must never eat one.
|
|
28
|
+
* - top-level keys, `commands`, `excludePaths`, `match-rules`. Those are validated by shape, not by a
|
|
29
|
+
* name table, so "unknown" is not defined for them and a pruner would be guessing.
|
|
30
|
+
*
|
|
31
|
+
* ## The one destructive case, and why the drift guard already covers it
|
|
32
|
+
*
|
|
33
|
+
* A key CAN be valid-but-unlearned when package.json pins an @webpieces older than the config. Pruning
|
|
34
|
+
* then would delete live config. That case cannot reach here: the shim's version-drift guard compares
|
|
35
|
+
* the pin against the installed version and denies every tool call — including this one — before the
|
|
36
|
+
* validator or this command ever runs. It is also why `PruneResult` names every key it removed rather
|
|
37
|
+
* than reporting a count: a silent sweep is the thing that would make the rare case unrecoverable.
|
|
38
|
+
*/
|
|
39
|
+
/** One key this run removed, and the reason it was safe to remove. Data-only (per CLAUDE.md). */
|
|
40
|
+
export declare class PrunedKey {
|
|
41
|
+
/** `rules` or `hookGuards` — the section the key sat in. */
|
|
42
|
+
section: string;
|
|
43
|
+
/** The rule/guard name exactly as it appeared in the file. */
|
|
44
|
+
key: string;
|
|
45
|
+
/** Human-readable justification, printed per key so no removal is silent. */
|
|
46
|
+
reason: string;
|
|
47
|
+
constructor(section: string, key: string, reason: string);
|
|
48
|
+
}
|
|
49
|
+
/** The outcome of one prune. Data-only. */
|
|
50
|
+
export declare class PruneResult {
|
|
51
|
+
/** The webpieces.config.json that was inspected. */
|
|
52
|
+
configPath: string;
|
|
53
|
+
removed: PrunedKey[];
|
|
54
|
+
constructor(configPath: string, removed: PrunedKey[]);
|
|
55
|
+
/** True when the file was rewritten. */
|
|
56
|
+
changed(): boolean;
|
|
57
|
+
/** The report the CLI prints. Every removed key is named; nothing is summarised away. */
|
|
58
|
+
describeSelf(): string;
|
|
59
|
+
}
|
|
60
|
+
export declare class ConfigPruner {
|
|
61
|
+
private readonly configFile;
|
|
62
|
+
private readonly atomicFile;
|
|
63
|
+
constructor(configFile: ConfigFile, atomicFile: AtomicFile);
|
|
64
|
+
/**
|
|
65
|
+
* Strip every unknown key from the webpieces.config.json above `cwd` and rewrite it. Throws when no
|
|
66
|
+
* config file is found — a prune with no target is a mistake, not a no-op.
|
|
67
|
+
*/
|
|
68
|
+
pruneFrom(cwd: string): PruneResult;
|
|
69
|
+
/**
|
|
70
|
+
* Strip every unknown key from `configPath` and rewrite it in place. Reads the file directly rather
|
|
71
|
+
* than through the loader: the loader VALIDATES, and this command's whole purpose is to run on a
|
|
72
|
+
* file that fails validation.
|
|
73
|
+
*/
|
|
74
|
+
prune(configPath: string): PruneResult;
|
|
75
|
+
/** Mutates `document`, deleting every prunable name from the two rule sections. */
|
|
76
|
+
private removeUnknown;
|
|
77
|
+
/** Why `key` is safe to delete, or null when it must be kept. */
|
|
78
|
+
private reasonToRemove;
|
|
79
|
+
/**
|
|
80
|
+
* The file's own indentation, so a prune does not reformat every line it did not touch. Falls back to
|
|
81
|
+
* 4 (the shape every webpieces.config.json in the wild uses) when the document is single-line.
|
|
82
|
+
*/
|
|
83
|
+
private indentOf;
|
|
84
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigPruner = exports.PruneResult = exports.PrunedKey = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const inversify_1 = require("inversify");
|
|
7
|
+
const atomic_file_1 = require("./atomic-file");
|
|
8
|
+
const config_file_1 = require("./config-file");
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const rule_schemas_1 = require("./rule-schemas");
|
|
11
|
+
const retired_config_keys_1 = require("./retired-config-keys");
|
|
12
|
+
/**
|
|
13
|
+
* `wp-prune-unknown-config` — the MECHANICAL cure for "[x] Unknown rule".
|
|
14
|
+
*
|
|
15
|
+
* ## Why a command and not just advice
|
|
16
|
+
*
|
|
17
|
+
* An unknown key controls nothing, so deleting it is the fix, and the validator now says so. But the
|
|
18
|
+
* moment that advice is read is the worst possible moment to act on it by hand: the hook guard denies
|
|
19
|
+
* every Bash call while the config is invalid, so the reader is editing JSON blind, with no way to
|
|
20
|
+
* re-run the validator between edits. Making the cleanup one command means cleanliness is the default
|
|
21
|
+
* path rather than a judgement call taken under a total block — and it makes "the file validates" mean
|
|
22
|
+
* "every key in it is real", which is the property that keeps dead config from reading as live config.
|
|
23
|
+
*
|
|
24
|
+
* ## What it removes, and what it deliberately does NOT
|
|
25
|
+
*
|
|
26
|
+
* Removed, from the `rules` and `hookGuards` sections only:
|
|
27
|
+
* - a name with no entry in RULE_SCHEMAS and no entry in RETIRED_CONFIG_KEYS (a typo, or a rule some
|
|
28
|
+
* release deleted). Nothing reads it, by construction.
|
|
29
|
+
* - a name in RETIRED_CONFIG_KEYS whose entry is `prunable` — the retirement's own instruction is
|
|
30
|
+
* "delete this entry", because the setting left webpieces.config.json entirely.
|
|
31
|
+
*
|
|
32
|
+
* NOT removed:
|
|
33
|
+
* - a RENAME or an in-file move (`prunable: false`). Deleting those discards a value the new key still
|
|
34
|
+
* needs, so they keep their migration instruction and a human/agent applies it.
|
|
35
|
+
* - anything at all when `rulesDir` is configured. A custom rule legitimately has no built-in schema,
|
|
36
|
+
* and this command must never eat one.
|
|
37
|
+
* - top-level keys, `commands`, `excludePaths`, `match-rules`. Those are validated by shape, not by a
|
|
38
|
+
* name table, so "unknown" is not defined for them and a pruner would be guessing.
|
|
39
|
+
*
|
|
40
|
+
* ## The one destructive case, and why the drift guard already covers it
|
|
41
|
+
*
|
|
42
|
+
* A key CAN be valid-but-unlearned when package.json pins an @webpieces older than the config. Pruning
|
|
43
|
+
* then would delete live config. That case cannot reach here: the shim's version-drift guard compares
|
|
44
|
+
* the pin against the installed version and denies every tool call — including this one — before the
|
|
45
|
+
* validator or this command ever runs. It is also why `PruneResult` names every key it removed rather
|
|
46
|
+
* than reporting a count: a silent sweep is the thing that would make the rare case unrecoverable.
|
|
47
|
+
*/
|
|
48
|
+
/** One key this run removed, and the reason it was safe to remove. Data-only (per CLAUDE.md). */
|
|
49
|
+
class PrunedKey {
|
|
50
|
+
/** `rules` or `hookGuards` — the section the key sat in. */
|
|
51
|
+
section;
|
|
52
|
+
/** The rule/guard name exactly as it appeared in the file. */
|
|
53
|
+
key;
|
|
54
|
+
/** Human-readable justification, printed per key so no removal is silent. */
|
|
55
|
+
reason;
|
|
56
|
+
constructor(section, key, reason) {
|
|
57
|
+
this.section = section;
|
|
58
|
+
this.key = key;
|
|
59
|
+
this.reason = reason;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.PrunedKey = PrunedKey;
|
|
63
|
+
/** The outcome of one prune. Data-only. */
|
|
64
|
+
class PruneResult {
|
|
65
|
+
/** The webpieces.config.json that was inspected. */
|
|
66
|
+
configPath;
|
|
67
|
+
removed;
|
|
68
|
+
constructor(configPath, removed) {
|
|
69
|
+
this.configPath = configPath;
|
|
70
|
+
this.removed = removed;
|
|
71
|
+
}
|
|
72
|
+
/** True when the file was rewritten. */
|
|
73
|
+
changed() {
|
|
74
|
+
return this.removed.length > 0;
|
|
75
|
+
}
|
|
76
|
+
/** The report the CLI prints. Every removed key is named; nothing is summarised away. */
|
|
77
|
+
describeSelf() {
|
|
78
|
+
if (!this.changed()) {
|
|
79
|
+
return `[${constants_1.PRUNE_UNKNOWN_COMMAND}] No unknown keys in ${this.configPath} — nothing to remove.`;
|
|
80
|
+
}
|
|
81
|
+
const lines = this.removed.map((r) => ` • ${r.section}.${r.key} — ${r.reason}`);
|
|
82
|
+
return `[${constants_1.PRUNE_UNKNOWN_COMMAND}] Removed ${this.removed.length} unknown key(s) from ` +
|
|
83
|
+
`${this.configPath}:\n${lines.join('\n')}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.PruneResult = PruneResult;
|
|
87
|
+
/** The sections a rule/guard name may legally sit in — the only two this command touches. */
|
|
88
|
+
const PRUNABLE_SECTIONS = ['rules', 'hookGuards'];
|
|
89
|
+
let ConfigPruner = class ConfigPruner {
|
|
90
|
+
configFile;
|
|
91
|
+
atomicFile;
|
|
92
|
+
constructor(configFile, atomicFile) {
|
|
93
|
+
this.configFile = configFile;
|
|
94
|
+
this.atomicFile = atomicFile;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Strip every unknown key from the webpieces.config.json above `cwd` and rewrite it. Throws when no
|
|
98
|
+
* config file is found — a prune with no target is a mistake, not a no-op.
|
|
99
|
+
*/
|
|
100
|
+
pruneFrom(cwd) {
|
|
101
|
+
const configPath = this.configFile.findConfigFile(cwd);
|
|
102
|
+
if (configPath === null) {
|
|
103
|
+
throw new Error(`[${constants_1.PRUNE_UNKNOWN_COMMAND}] No webpieces.config.json found above ${cwd}.`);
|
|
104
|
+
}
|
|
105
|
+
return this.prune(configPath);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Strip every unknown key from `configPath` and rewrite it in place. Reads the file directly rather
|
|
109
|
+
* than through the loader: the loader VALIDATES, and this command's whole purpose is to run on a
|
|
110
|
+
* file that fails validation.
|
|
111
|
+
*/
|
|
112
|
+
prune(configPath) {
|
|
113
|
+
const text = fs.readFileSync(configPath, 'utf8');
|
|
114
|
+
// webpieces-disable no-any-unknown -- the raw config document is opaque JSON until narrowed below
|
|
115
|
+
const parsed = JSON.parse(text);
|
|
116
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
117
|
+
throw new Error(`[${constants_1.PRUNE_UNKNOWN_COMMAND}] ${configPath} is not a JSON object.`);
|
|
118
|
+
}
|
|
119
|
+
// webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above
|
|
120
|
+
const document = parsed;
|
|
121
|
+
const removed = this.removeUnknown(document);
|
|
122
|
+
if (removed.length > 0) {
|
|
123
|
+
this.atomicFile.writeAtomic(configPath, JSON.stringify(document, null, this.indentOf(text)) + '\n');
|
|
124
|
+
}
|
|
125
|
+
return new PruneResult(configPath, removed);
|
|
126
|
+
}
|
|
127
|
+
/** Mutates `document`, deleting every prunable name from the two rule sections. */
|
|
128
|
+
// webpieces-disable no-any-unknown -- opaque parsed config document
|
|
129
|
+
removeUnknown(document) {
|
|
130
|
+
// A custom rules directory means a name with no built-in schema may be entirely legitimate.
|
|
131
|
+
const rulesDir = document['rulesDir'];
|
|
132
|
+
if (Array.isArray(rulesDir) && rulesDir.length > 0)
|
|
133
|
+
return [];
|
|
134
|
+
const removed = [];
|
|
135
|
+
for (const sectionName of PRUNABLE_SECTIONS) {
|
|
136
|
+
const section = document[sectionName];
|
|
137
|
+
if (typeof section !== 'object' || section === null || Array.isArray(section))
|
|
138
|
+
continue;
|
|
139
|
+
// webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above
|
|
140
|
+
const entries = section;
|
|
141
|
+
for (const key of Object.keys(entries)) {
|
|
142
|
+
const reason = this.reasonToRemove(key);
|
|
143
|
+
if (reason === null)
|
|
144
|
+
continue;
|
|
145
|
+
delete entries[key];
|
|
146
|
+
removed.push(new PrunedKey(sectionName, key, reason));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return removed;
|
|
150
|
+
}
|
|
151
|
+
/** Why `key` is safe to delete, or null when it must be kept. */
|
|
152
|
+
reasonToRemove(key) {
|
|
153
|
+
if (rule_schemas_1.RULE_SCHEMAS[key])
|
|
154
|
+
return null;
|
|
155
|
+
const retired = retired_config_keys_1.RETIRED_CONFIG_KEYS.find(e => e.scope === retired_config_keys_1.RETIRED_SCOPE_RULE && e.key === key);
|
|
156
|
+
if (retired) {
|
|
157
|
+
if (!retired.prunable)
|
|
158
|
+
return null;
|
|
159
|
+
return retired.movedTo === ''
|
|
160
|
+
? 'RETIRED and removed with no replacement'
|
|
161
|
+
: `RETIRED — the setting now lives at ${retired.movedTo}`;
|
|
162
|
+
}
|
|
163
|
+
return 'no running validator has a schema for it, so nothing reads it';
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The file's own indentation, so a prune does not reformat every line it did not touch. Falls back to
|
|
167
|
+
* 4 (the shape every webpieces.config.json in the wild uses) when the document is single-line.
|
|
168
|
+
*/
|
|
169
|
+
indentOf(text) {
|
|
170
|
+
const match = /\n( +)"/.exec(text);
|
|
171
|
+
if (match === null)
|
|
172
|
+
return 4;
|
|
173
|
+
return match[1].length;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
exports.ConfigPruner = ConfigPruner;
|
|
177
|
+
exports.ConfigPruner = ConfigPruner = tslib_1.__decorate([
|
|
178
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
179
|
+
tslib_1.__metadata("design:paramtypes", [config_file_1.ConfigFile,
|
|
180
|
+
atomic_file_1.AtomicFile])
|
|
181
|
+
], ConfigPruner);
|
|
182
|
+
//# sourceMappingURL=config-pruner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-pruner.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-pruner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from './atomic-file';\nimport { ConfigFile } from './config-file';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\nimport { RULE_SCHEMAS } from './rule-schemas';\nimport { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_RULE } from './retired-config-keys';\n\n/**\n * `wp-prune-unknown-config` — the MECHANICAL cure for \"[x] Unknown rule\".\n *\n * ## Why a command and not just advice\n *\n * An unknown key controls nothing, so deleting it is the fix, and the validator now says so. But the\n * moment that advice is read is the worst possible moment to act on it by hand: the hook guard denies\n * every Bash call while the config is invalid, so the reader is editing JSON blind, with no way to\n * re-run the validator between edits. Making the cleanup one command means cleanliness is the default\n * path rather than a judgement call taken under a total block — and it makes \"the file validates\" mean\n * \"every key in it is real\", which is the property that keeps dead config from reading as live config.\n *\n * ## What it removes, and what it deliberately does NOT\n *\n * Removed, from the `rules` and `hookGuards` sections only:\n * - a name with no entry in RULE_SCHEMAS and no entry in RETIRED_CONFIG_KEYS (a typo, or a rule some\n * release deleted). Nothing reads it, by construction.\n * - a name in RETIRED_CONFIG_KEYS whose entry is `prunable` — the retirement's own instruction is\n * \"delete this entry\", because the setting left webpieces.config.json entirely.\n *\n * NOT removed:\n * - a RENAME or an in-file move (`prunable: false`). Deleting those discards a value the new key still\n * needs, so they keep their migration instruction and a human/agent applies it.\n * - anything at all when `rulesDir` is configured. A custom rule legitimately has no built-in schema,\n * and this command must never eat one.\n * - top-level keys, `commands`, `excludePaths`, `match-rules`. Those are validated by shape, not by a\n * name table, so \"unknown\" is not defined for them and a pruner would be guessing.\n *\n * ## The one destructive case, and why the drift guard already covers it\n *\n * A key CAN be valid-but-unlearned when package.json pins an @webpieces older than the config. Pruning\n * then would delete live config. That case cannot reach here: the shim's version-drift guard compares\n * the pin against the installed version and denies every tool call — including this one — before the\n * validator or this command ever runs. It is also why `PruneResult` names every key it removed rather\n * than reporting a count: a silent sweep is the thing that would make the rare case unrecoverable.\n */\n\n/** One key this run removed, and the reason it was safe to remove. Data-only (per CLAUDE.md). */\nexport class PrunedKey {\n /** `rules` or `hookGuards` — the section the key sat in. */\n section: string;\n /** The rule/guard name exactly as it appeared in the file. */\n key: string;\n /** Human-readable justification, printed per key so no removal is silent. */\n reason: string;\n\n constructor(section: string, key: string, reason: string) {\n this.section = section;\n this.key = key;\n this.reason = reason;\n }\n}\n\n/** The outcome of one prune. Data-only. */\nexport class PruneResult {\n /** The webpieces.config.json that was inspected. */\n configPath: string;\n removed: PrunedKey[];\n\n constructor(configPath: string, removed: PrunedKey[]) {\n this.configPath = configPath;\n this.removed = removed;\n }\n\n /** True when the file was rewritten. */\n changed(): boolean {\n return this.removed.length > 0;\n }\n\n /** The report the CLI prints. Every removed key is named; nothing is summarised away. */\n describeSelf(): string {\n if (!this.changed()) {\n return `[${PRUNE_UNKNOWN_COMMAND}] No unknown keys in ${this.configPath} — nothing to remove.`;\n }\n const lines = this.removed.map((r: PrunedKey): string => ` • ${r.section}.${r.key} — ${r.reason}`);\n return `[${PRUNE_UNKNOWN_COMMAND}] Removed ${this.removed.length} unknown key(s) from ` +\n `${this.configPath}:\\n${lines.join('\\n')}`;\n }\n}\n\n/** The sections a rule/guard name may legally sit in — the only two this command touches. */\nconst PRUNABLE_SECTIONS: readonly string[] = ['rules', 'hookGuards'];\n\n@injectable(bindingScopeValues.Singleton)\nexport class ConfigPruner {\n constructor(\n private readonly configFile: ConfigFile,\n private readonly atomicFile: AtomicFile,\n ) {}\n\n /**\n * Strip every unknown key from the webpieces.config.json above `cwd` and rewrite it. Throws when no\n * config file is found — a prune with no target is a mistake, not a no-op.\n */\n pruneFrom(cwd: string): PruneResult {\n const configPath = this.configFile.findConfigFile(cwd);\n if (configPath === null) {\n throw new Error(`[${PRUNE_UNKNOWN_COMMAND}] No webpieces.config.json found above ${cwd}.`);\n }\n return this.prune(configPath);\n }\n\n /**\n * Strip every unknown key from `configPath` and rewrite it in place. Reads the file directly rather\n * than through the loader: the loader VALIDATES, and this command's whole purpose is to run on a\n * file that fails validation.\n */\n prune(configPath: string): PruneResult {\n const text = fs.readFileSync(configPath, 'utf8');\n // webpieces-disable no-any-unknown -- the raw config document is opaque JSON until narrowed below\n const parsed: unknown = JSON.parse(text);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error(`[${PRUNE_UNKNOWN_COMMAND}] ${configPath} is not a JSON object.`);\n }\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n const document = parsed as Record<string, unknown>;\n const removed = this.removeUnknown(document);\n if (removed.length > 0) {\n this.atomicFile.writeAtomic(configPath, JSON.stringify(document, null, this.indentOf(text)) + '\\n');\n }\n return new PruneResult(configPath, removed);\n }\n\n /** Mutates `document`, deleting every prunable name from the two rule sections. */\n // webpieces-disable no-any-unknown -- opaque parsed config document\n private removeUnknown(document: Record<string, unknown>): PrunedKey[] {\n // A custom rules directory means a name with no built-in schema may be entirely legitimate.\n const rulesDir = document['rulesDir'];\n if (Array.isArray(rulesDir) && rulesDir.length > 0) return [];\n\n const removed: PrunedKey[] = [];\n for (const sectionName of PRUNABLE_SECTIONS) {\n const section = document[sectionName];\n if (typeof section !== 'object' || section === null || Array.isArray(section)) continue;\n // webpieces-disable no-any-unknown -- narrowed to a non-null, non-array object one line above\n const entries = section as Record<string, unknown>;\n for (const key of Object.keys(entries)) {\n const reason = this.reasonToRemove(key);\n if (reason === null) continue;\n delete entries[key];\n removed.push(new PrunedKey(sectionName, key, reason));\n }\n }\n return removed;\n }\n\n /** Why `key` is safe to delete, or null when it must be kept. */\n private reasonToRemove(key: string): string | null {\n if (RULE_SCHEMAS[key]) return null;\n const retired = RETIRED_CONFIG_KEYS.find(\n e => e.scope === RETIRED_SCOPE_RULE && e.key === key);\n if (retired) {\n if (!retired.prunable) return null;\n return retired.movedTo === ''\n ? 'RETIRED and removed with no replacement'\n : `RETIRED — the setting now lives at ${retired.movedTo}`;\n }\n return 'no running validator has a schema for it, so nothing reads it';\n }\n\n /**\n * The file's own indentation, so a prune does not reformat every line it did not touch. Falls back to\n * 4 (the shape every webpieces.config.json in the wild uses) when the document is single-line.\n */\n private indentOf(text: string): number {\n const match = /\\n( +)\"/.exec(text);\n if (match === null) return 4;\n return match[1].length;\n }\n}\n"]}
|
package/src/constants.d.ts
CHANGED
|
@@ -28,6 +28,17 @@ export declare const PR_REVIEW_DIR = "pr-review";
|
|
|
28
28
|
export declare const MERGE_IN_PROGRESS_FILE = "merge-in-progress.json";
|
|
29
29
|
export declare const PUSH_DEV_STATE_FILE = "push-dev-in-progress.json";
|
|
30
30
|
export declare const MERGE_EXPLANATION_FILE = "merge-explanation.md";
|
|
31
|
+
/**
|
|
32
|
+
* The command that MECHANICALLY strips every unknown key from webpieces.config.json.
|
|
33
|
+
*
|
|
34
|
+
* An unknown key controls nothing, so deleting it is the cure — but "delete it" is a judgement call made
|
|
35
|
+
* while the guard denies every Bash call, which is exactly when a reader is least able to make one. This
|
|
36
|
+
* command turns that judgement into one keystroke, and every message that reports an unknown key names it.
|
|
37
|
+
*
|
|
38
|
+
* Lives in this leaf module (no imports) so both the banner and the validators can name it without
|
|
39
|
+
* importing the pruner and re-creating an import cycle.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PRUNE_UNKNOWN_COMMAND = "pnpm wp-prune-unknown-config";
|
|
31
42
|
/**
|
|
32
43
|
* Fast predicate: does this text carry a webpieces-disable for the given rule?
|
|
33
44
|
* Line-agnostic — the caller decides which line(s) or block of text to feed it.
|
package/src/constants.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// The legacy `ai-hook-disable` alias and the `-file`/`-next`/`-all` variants and the
|
|
7
7
|
// `*`/bare (no-rule) wildcard have been removed — every disable MUST name a rule.
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.MERGE_EXPLANATION_FILE = exports.PUSH_DEV_STATE_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = void 0;
|
|
9
|
+
exports.PRUNE_UNKNOWN_COMMAND = exports.MERGE_EXPLANATION_FILE = exports.PUSH_DEV_STATE_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = void 0;
|
|
10
10
|
exports.hasDisable = hasDisable;
|
|
11
11
|
exports.WEBPIECES_DISABLE = 'webpieces-disable';
|
|
12
12
|
// Rule-name tokens as they appear AFTER `webpieces-disable` in a disable comment.
|
|
@@ -76,6 +76,17 @@ exports.PUSH_DEV_STATE_FILE = 'push-dev-in-progress.json';
|
|
|
76
76
|
// sidecar file (rather than an in-source comment) works for any file type, including comment-less
|
|
77
77
|
// ones like JSON and files resolved by deletion.
|
|
78
78
|
exports.MERGE_EXPLANATION_FILE = 'merge-explanation.md';
|
|
79
|
+
/**
|
|
80
|
+
* The command that MECHANICALLY strips every unknown key from webpieces.config.json.
|
|
81
|
+
*
|
|
82
|
+
* An unknown key controls nothing, so deleting it is the cure — but "delete it" is a judgement call made
|
|
83
|
+
* while the guard denies every Bash call, which is exactly when a reader is least able to make one. This
|
|
84
|
+
* command turns that judgement into one keystroke, and every message that reports an unknown key names it.
|
|
85
|
+
*
|
|
86
|
+
* Lives in this leaf module (no imports) so both the banner and the validators can name it without
|
|
87
|
+
* importing the pruner and re-creating an import cycle.
|
|
88
|
+
*/
|
|
89
|
+
exports.PRUNE_UNKNOWN_COMMAND = 'pnpm wp-prune-unknown-config';
|
|
79
90
|
/**
|
|
80
91
|
* Fast predicate: does this text carry a webpieces-disable for the given rule?
|
|
81
92
|
* Line-agnostic — the caller decides which line(s) or block of text to feed it.
|
package/src/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/constants.ts"],"names":[],"mappings":";AAAA,iFAAiF;AACjF,sEAAsE;AACtE,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AACrF,kFAAkF;;;
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/constants.ts"],"names":[],"mappings":";AAAA,iFAAiF;AACjF,sEAAsE;AACtE,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AACrF,kFAAkF;;;AA6FlF,gCAEC;AA7FY,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,kFAAkF;AAClF,uFAAuF;AACvF,uEAAuE;AACvE,sFAAsF;AACzE,QAAA,UAAU,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,uBAAuB,EAAE,yBAAyB;IAClD,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;IAC5C,mBAAmB,EAAE,qBAAqB;IAC1C,mBAAmB,EAAE,qBAAqB;IAC1C,2CAA2C,EAAE,6CAA6C;IAC1F,4BAA4B,EAAE,8BAA8B;IAC5D,yBAAyB,EAAE,2BAA2B;IACtD,+CAA+C,EAAE,iDAAiD;IAClG,aAAa,EAAE,eAAe;IAC9B,QAAQ,EAAE,UAAU;IACpB,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,aAAa,EAAE,eAAe;IAC9B,gBAAgB,EAAE,kBAAkB;IACpC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,kBAAkB,EAAE,oBAAoB;CAClC,CAAC;AAEX,wFAAwF;AACxF,0FAA0F;AAC1F,sFAAsF;AACtF,0DAA0D;AAC1D,EAAE;AACF,mFAAmF;AACnF,uFAAuF;AACvF,0FAA0F;AAC1F,iGAAiG;AACjG,8FAA8F;AAC9F,yFAAyF;AACzF,mGAAmG;AACnG,wGAAwG;AACxG,sGAAsG;AACtG,0CAA0C;AAC7B,QAAA,iBAAiB,GAAG,YAAY,CAAC;AACjC,QAAA,cAAc,GAAG,YAAY,CAAC;AAC3C,kGAAkG;AAClG,qGAAqG;AACrG,iCAAiC;AACpB,QAAA,aAAa,GAAG,WAAW,CAAC;AAC5B,QAAA,sBAAsB,GAAG,wBAAwB,CAAC;AAE/D,uFAAuF;AACvF,qGAAqG;AACrG,8FAA8F;AAC9F,2DAA2D;AAC3D,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,oGAAoG;AACpG,kGAAkG;AACrF,QAAA,mBAAmB,GAAG,2BAA2B,CAAC;AAE/D,2FAA2F;AAC3F,mFAAmF;AACnF,uFAAuF;AACvF,uGAAuG;AACvG,kGAAkG;AAClG,kGAAkG;AAClG,iDAAiD;AACpC,QAAA,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D;;;;;;;;;GASG;AACU,QAAA,qBAAqB,GAAG,8BAA8B,CAAC;AAEpE;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,IAAY,EAAE,QAAgB;IACrD,OAAO,IAAI,CAAC,QAAQ,CAAC,yBAAiB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACvE,CAAC","sourcesContent":["// Single source of truth for the disable-comment token and rule-name identifiers\n// shared across rules-config, ai-hook-rules, code-rules, and pr-gate.\n//\n// There is exactly ONE disable form: `// webpieces-disable <rule>[, <rule2>] -- reason`.\n// The legacy `ai-hook-disable` alias and the `-file`/`-next`/`-all` variants and the\n// `*`/bare (no-rule) wildcard have been removed — every disable MUST name a rule.\n\nexport const WEBPIECES_DISABLE = 'webpieces-disable';\n\n// Rule-name tokens as they appear AFTER `webpieces-disable` in a disable comment.\n// Values must match existing comments exactly — changing a value silently breaks every\n// disable that names that rule. Note MAX_LINES_MODIFIED is a prefix of\n// MAX_LINES_MODIFIED_FILES (a historical substring-match quirk preserved on purpose).\nexport const RULE_NAMES = {\n NO_ANY_UNKNOWN: 'no-any-unknown',\n NO_IMPLICIT_ANY: 'no-implicit-any',\n NO_DESTRUCTURE: 'no-destructure',\n NO_UNMANAGED_EXCEPTIONS: 'no-unmanaged-exceptions',\n CATCH_ERROR_PATTERN: 'catch-error-pattern',\n THROW_CAUSE_REQUIRED: 'throw-cause-required',\n REQUIRE_RETURN_TYPE: 'require-return-type',\n NO_SYMBOL_DI_TOKENS: 'no-symbol-di-tokens',\n NO_CLIENT_CREATION_OUTSIDE_SERVER_OR_CLIENT: 'no-client-creation-outside-server-or-client',\n NO_PROCESS_EXIT_OUTSIDE_MAIN: 'no-process-exit-outside-main',\n NO_FUNCTION_OUTSIDE_CLASS: 'no-function-outside-class',\n INJECT_ANNOTATION_NOT_NEEDED_FOR_CONCRETE_CLASS: 'inject-annotation-not-needed-for-concrete-class',\n FRAMEWORK_TAG: 'framework-tag',\n ROLE_TAG: 'role-tag',\n NO_INLINE_TYPES: 'no-inline-types',\n NO_DIRECT_API_RESOLVER: 'no-direct-api-resolver',\n NO_CUSTOM_CSS: 'no-custom-css',\n PRISMA_CONVERTER: 'prisma-converter',\n MAX_LINES_NEW_METHODS: 'max-lines-new-methods',\n MAX_LINES_MODIFIED_FILES: 'max-lines-modified-files',\n MAX_LINES_MODIFIED: 'max-lines-modified',\n} as const;\n\n// Merge-state convention shared by the pr-gate scripts (which WRITE the marker during a\n// conflicted 3-point merge) and the ai-hook-rules merge-in-progress-guard (which READS it\n// to block commit/push/PR until the merge is validated). Kept here so neither package\n// depends on the other — they only share this vocabulary.\n//\n// `.webpieces/` is the single working dir for all webpieces tooling: ai-hook-rules\n// bootstrap/cache, the instruct-ai docs, and the workflow state. To keep the top level\n// quiet, per-feature workflow dirs are nested one level down under `merge-info/<feature>`\n// and `pr-review/<feature>` rather than scattered as top-level `merge-<feature>`/`pr-<feature>`.\n// `.webpieces/` is gitignored; only the per-feature subdirs under those two homes are subject\n// to 30-day cleanup (the homes themselves, like hooks/ and instruct-ai/, are permanent).\n// The DIRECTORY NAME only. Never join it onto a root yourself — go through `DotWebpieces.shared()`\n// (repo-wide state) or `DotWebpieces.local()` (this worktree's own state) so the call site declares its\n// scope. In a linked worktree the two resolve to different places, and getting that silently wrong is\n// the bug those methods exist to prevent.\nexport const WEBPIECES_TMP_DIR = '.webpieces';\nexport const MERGE_INFO_DIR = 'merge-info';\n// The PR working home. Renamed from the legacy `pr-info` to `pr-review` for clarity (it holds the\n// AI's PR review + rendered body). Old `pr-info/` dirs are gitignored local state and self-clear via\n// cleanTmp's legacy `pr-` sweep.\nexport const PR_REVIEW_DIR = 'pr-review';\nexport const MERGE_IN_PROGRESS_FILE = 'merge-in-progress.json';\n\n// The dev-deploy resolve state file, written by `wp-push-dev --resolve` and cleared by\n// `wp-finish-push-dev` (or `--abort`). Named here for the same reason MERGE_IN_PROGRESS_FILE is: the\n// pr-gate commands WRITE it and things outside pr-gate READ it to decide whether a resolve is\n// half-finished, and neither side may depend on the other.\n//\n// It lives directly under `.webpieces/` local state (NOT under merge-info/), because a dev-deploy\n// resolve is not a 3-point merge: it never touches the feature branch, never produces merge-info\n// context, and must NOT be picked up by merge-in-progress-guard's marker scan — that guard's remedy\n// is `pnpm wp-finish-upsert-pr`, which is the wrong command here and would strand the tmp branch.\nexport const PUSH_DEV_STATE_FILE = 'push-dev-in-progress.json';\n\n// Proof-of-work the AI must produce for every conflicted file it resolves during a 3-point\n// merge: a short explanation written NEXT TO that file's 3-point context (the same\n// `updatemain-<safe_path>/` dir that holds A-forkpoint.txt / B-A.diff / C-A.diff). The\n// wp-finish-upsert-pr gate requires a non-empty file of this name per conflicted file before passing —\n// it is the only check on the part of the process the AI actually owns (resolving files). Using a\n// sidecar file (rather than an in-source comment) works for any file type, including comment-less\n// ones like JSON and files resolved by deletion.\nexport const MERGE_EXPLANATION_FILE = 'merge-explanation.md';\n\n/**\n * The command that MECHANICALLY strips every unknown key from webpieces.config.json.\n *\n * An unknown key controls nothing, so deleting it is the cure — but \"delete it\" is a judgement call made\n * while the guard denies every Bash call, which is exactly when a reader is least able to make one. This\n * command turns that judgement into one keystroke, and every message that reports an unknown key names it.\n *\n * Lives in this leaf module (no imports) so both the banner and the validators can name it without\n * importing the pruner and re-creating an import cycle.\n */\nexport const PRUNE_UNKNOWN_COMMAND = 'pnpm wp-prune-unknown-config';\n\n/**\n * Fast predicate: does this text carry a webpieces-disable for the given rule?\n * Line-agnostic — the caller decides which line(s) or block of text to feed it.\n * This is the cheap substring form used by code-rules detection and pr-gate's\n * dashboard grep/count. (ai-hook-rules uses a richer line-mapping parser.)\n */\nexport function hasDisable(text: string, ruleName: string): boolean {\n return text.includes(WEBPIECES_DISABLE) && text.includes(ruleName);\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { CliUsage, CliFlag, CliArgSet, CliArgsCheck, CliArgs } from './cli-args'
|
|
|
6
6
|
export { runMain } from './run-main';
|
|
7
7
|
export { toError } from './to-error';
|
|
8
8
|
export { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';
|
|
9
|
-
export { formatConfigErrorsBanner, RETIRED_KEY_MARKER, RETIRED_TOP_LEVEL_MARKER, SECTION_PLACEMENT_MARKER, } from './config-error-banner';
|
|
9
|
+
export { formatConfigErrorsBanner, CONFIG_POLICY_DOC, RETIRED_KEY_MARKER, RETIRED_TOP_LEVEL_MARKER, SECTION_PLACEMENT_MARKER, } from './config-error-banner';
|
|
10
10
|
export { findConfigFile, CONFIG_FILENAME, ConfigFile, ConfigParseAttempt, CONFIG_PARSE_ATTEMPTS, CONFIG_PARSE_RETRY_MILLIS } from './config-file';
|
|
11
11
|
export { RepoRootFinder, INSTRUCT_AI_DIR, INSTRUCT_AI_LEAF } from './repo-root';
|
|
12
12
|
export { DotWebpieces, dotWebpieces, GitDirs, WORKTREE_STATE_DIR, LOGS_STATE_DIR } from './state-dir';
|
|
@@ -25,6 +25,7 @@ export { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSecti
|
|
|
25
25
|
export { validateCommandsSection } from './commands-section-validators';
|
|
26
26
|
export { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';
|
|
27
27
|
export { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';
|
|
28
|
+
export { ConfigPruner, PruneResult, PrunedKey } from './config-pruner';
|
|
28
29
|
export { validateChecklistDocs } from './checklist-docs-validator';
|
|
29
30
|
export { HomeConfig, HomeConfigService, RetiredHomeConfigKey, RETIRED_HOME_CONFIG_KEYS, HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_EXPERIMENTAL_SECTION, HOME_KEY_BUILD_GATE_LOG_CAPTURE, } from './home-config';
|
|
30
31
|
export { MatchRuleConfig, MatchRuleViolation, findMatchRuleViolations, isMatchRuleAllowedPath, compileMatchRulePatterns, renderMatchRuleMessage, DEFAULT_MATCH_RULES, } from './match-rules-config';
|
|
@@ -36,7 +37,7 @@ export { shouldSkipRule, getCurrentBranch } from './skip-rule';
|
|
|
36
37
|
export type { SkipRuleResult } from './skip-rule';
|
|
37
38
|
export { detectBase, resolveBase, getChangedFiles, getFileDiff, getChangedLineNumbers, findNewMethodSignaturesInDiff, hasChangesInRange, isNewOrModified, DiffScope, DiffRange, ChangedFilesOptions, } from './diff-scope';
|
|
38
39
|
export { AbstractRule } from './abstract-rule';
|
|
39
|
-
export { WEBPIECES_DISABLE, RULE_NAMES, hasDisable, WEBPIECES_TMP_DIR, MERGE_INFO_DIR, PR_REVIEW_DIR, MERGE_IN_PROGRESS_FILE, MERGE_EXPLANATION_FILE, PUSH_DEV_STATE_FILE, } from './constants';
|
|
40
|
+
export { WEBPIECES_DISABLE, RULE_NAMES, hasDisable, WEBPIECES_TMP_DIR, MERGE_INFO_DIR, PR_REVIEW_DIR, MERGE_IN_PROGRESS_FILE, MERGE_EXPLANATION_FILE, PUSH_DEV_STATE_FILE, PRUNE_UNKNOWN_COMMAND, } from './constants';
|
|
40
41
|
export { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
|
|
41
42
|
export { SyncFlowGuidance, WP_START_UPDATE, WP_FINISH_UPDATE, WP_START_UPSERT_PR, WP_FINISH_UPSERT_PR, WP_PUSH_DEV, WP_FINISH_PUSH_DEV, } from './sync-flow-guidance';
|
|
42
43
|
export { MaxMethodLinesConfig, MaxFileLinesConfig, RequireReturnTypeConfig, NoInlineTypeLiteralsConfig, NoAnyUnknownConfig, NoImplicitAnyConfig, PrismaValidateDtosConfig, PrismaConverterConfig, NoDestructureConfig, NoUnmanagedExceptionsConfig, CatchErrorPatternConfig, ThrowCauseRequiredConfig, AngularNoDirectApiInResolverConfig, NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, NoFunctionOutsideClassConfig, InjectAnnotationNotNeededForConcreteClassConfig, FrameworkTagConfig, RoleTagConfig, BranchCreationGuardConfig, PrCreationOrPushGuardConfig, MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig, NoFileImportCyclesConfig, RuntimeArchitectureConfig, NxWiringConfig, DiGraphConfig, NoJsFilesConfig, ValidateTsInSrcConfig, ValidateArchitectureUnchangedConfig, ValidateNoArchitectureCyclesConfig, ValidatePackageJsonConfig, ValidateVersionsLockedConfig, ValidateEslintSyncConfig, BaseRuleConfig, } from './rule-configs';
|