@webpieces/rules-config 0.4.573 → 0.4.575

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.4.573",
3
+ "version": "0.4.575",
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/skip-rule.js CHANGED
@@ -5,11 +5,27 @@ exports.shouldSkipRule = shouldSkipRule;
5
5
  const child_process_1 = require("child_process");
6
6
  const inform_ai_error_1 = require("./inform-ai-error");
7
7
  const to_error_1 = require("./to-error");
8
- // The actual checked-out branch. Always uses git directly env vars (BRANCH_NAME, GIT_BRANCH,
9
- // GITHUB_*, CI_COMMIT_BRANCH, …) were intentionally REMOVED: a stray GIT_BRANCH=main locally made
10
- // this return "main" on a feature branch, which (a) mislabeled the main-sync cache and (b) silently
11
- // disabled merged-PR detection (detectMergedPr skips "main"). git rev-parse is the source of truth.
8
+ // The actual checked-out branch. The grab bag of ambient env vars (BRANCH_NAME, GIT_BRANCH,
9
+ // CI_COMMIT_BRANCH, …) was intentionally REMOVED and must stay removed: a stray GIT_BRANCH=main
10
+ // locally made this return "main" on a feature branch, which (a) mislabeled the main-sync cache and
11
+ // (b) silently disabled merged-PR detection (detectMergedPr skips "main").
12
+ //
13
+ // The two vars below are NOT that. They are consulted BEFORE git because git cannot answer at all in
14
+ // the case they cover — a `pull_request` checkout leaves HEAD detached on refs/pull/<N>/merge, where
15
+ // `git rev-parse --abbrev-ref HEAD` returns the literal string "HEAD" and no branch hatch can match.
16
+ // Neither can go stale the way GIT_BRANCH did:
17
+ // GITHUB_HEAD_REF — set by the GitHub runner ONLY on pull_request/pull_request_target, and it IS
18
+ // the source branch name. Absent on push, so the fallthrough stays safe. (Not
19
+ // GITHUB_REF_NAME: on pull_request that is "<N>/merge", not a branch.)
20
+ // WEBPIECES_BRANCH — one documented opt-in override for CI systems not special-cased here
21
+ // (GitLab, CircleCI, Buildkite). Nobody sets it by accident.
12
22
  function getCurrentBranch() {
23
+ const prBranch = process.env['GITHUB_HEAD_REF'];
24
+ if (prBranch)
25
+ return prBranch;
26
+ const override = process.env['WEBPIECES_BRANCH'];
27
+ if (override)
28
+ return override;
13
29
  // webpieces-disable no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI
14
30
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI
15
31
  try {
@@ -26,6 +42,23 @@ function shouldSkipRule(epoch,
26
42
  branchPattern) {
27
43
  if (branchPattern) {
28
44
  const current = getCurrentBranch();
45
+ // ONLY inside `if (branchPattern)`. A detached HEAD with turnOffRuleWhileOnBranch=null (the
46
+ // overwhelmingly common value) is a non-event and must stay silent, or every tag build,
47
+ // bisect and `gh pr checkout --detach` starts failing for no reason. The fault reported here
48
+ // is "you asked for branch scoping where no branch exists", never "HEAD is detached".
49
+ if (current === 'HEAD' || current === '') {
50
+ throw new inform_ai_error_1.InformAiError(`turnOffRuleWhileOnBranch: "${branchPattern}" is configured, but the current branch ` +
51
+ `cannot be determined — HEAD is detached, which is what a CI checkout of a merge ref ` +
52
+ `looks like. The hatch would silently NOT apply, so this fails now rather than passing ` +
53
+ `on your machine and failing in CI with an unrelated-looking error.\n\n` +
54
+ `WORKAROUNDS, in order of preference:\n` +
55
+ ` 1. Upgrade to a webpieces that reads GITHUB_HEAD_REF (no workflow change needed).\n` +
56
+ ` 2. Set WEBPIECES_BRANCH in the workflow so the branch can be resolved.\n` +
57
+ ` 3. Use turnOffRuleUntilEpoch instead — it is TIME based, so it survives any checkout ` +
58
+ `including a detached one, and it is the only hatch that works in CI today. Set it to a ` +
59
+ `SHORT date (a few days): unlike branch scoping it is repo-wide while it lasts, so it ` +
60
+ `also shelters unrelated work that lands in the same window.`);
61
+ }
29
62
  if (current === branchPattern) {
30
63
  return { skip: true, reason: `on branch "${branchPattern}"` };
31
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skip-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/skip-rule.ts"],"names":[],"mappings":";;AAmBA,4CASC;AAED,wCAoBC;AAlDD,iDAAyC;AAEzC,uDAAkD;AAClD,yCAAqC;AAYrC,+FAA+F;AAC/F,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,SAAgB,gBAAgB;IAC5B,wHAAwH;IACxH,0IAA0I;IAC1I,IAAI,CAAC;QACD,OAAO,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpF,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CAAC,2CAA2C,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1G,CAAC;AACL,CAAC;AAED,SAAgB,cAAc,CAC1B,KAAyB;AACzB,+FAA+F;AAC/F,0FAA0F;AAC1F,aAAwC;IAExC,IAAI,aAAa,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;QACnC,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC5B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,aAAa,GAAG,EAAE,CAAC;QAClE,CAAC;IACL,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,IAAI,UAAU,GAAG,KAAK,EAAE,CAAC;YACrB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,0CAA0C,WAAW,EAAE,EAAE,CAAC;QAC3F,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// Universal \"should this rule be skipped right now?\" logic, shared by code-rules,\n// ai-hook-rules and the Nx executors so every rule honors the same two escape\n// hatches: turnOffRuleWhileOnBranch (skip while on a named branch) and\n// turnOffRuleUntilEpoch (skip until an epoch passes).\n\nexport interface SkipRuleResult {\n skip: boolean;\n reason?: string;\n}\n\n// The actual checked-out branch. Always uses git directly env vars (BRANCH_NAME, GIT_BRANCH,\n// GITHUB_*, CI_COMMIT_BRANCH, …) were intentionally REMOVED: a stray GIT_BRANCH=main locally made\n// this return \"main\" on a feature branch, which (a) mislabeled the main-sync cache and (b) silently\n// disabled merged-PR detection (detectMergedPr skips \"main\"). git rev-parse is the source of truth.\nexport function getCurrentBranch(): string {\n // webpieces-disable no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Failed to determine current git branch: ${error.message}`, { cause: error });\n }\n}\n\nexport function shouldSkipRule(\n epoch: number | undefined,\n // null (the \"no branch / always on\" value of turnOffRuleWhileOnBranch) is treated exactly like\n // undefined — no branch scoping. Only a non-empty branch name activates the branch hatch.\n branchPattern: string | undefined | null\n): SkipRuleResult {\n if (branchPattern) {\n const current = getCurrentBranch();\n if (current === branchPattern) {\n return { skip: true, reason: `on branch \"${branchPattern}\"` };\n }\n }\n if (epoch !== undefined) {\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n return { skip: true, reason: `turnOffRuleUntilEpoch active, expires: ${expiresDate}` };\n }\n }\n return { skip: false };\n}\n"]}
1
+ {"version":3,"file":"skip-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/skip-rule.ts"],"names":[],"mappings":";;AA6BA,4CAeC;AAED,wCAuCC;AArFD,iDAAyC;AAEzC,uDAAkD;AAClD,yCAAqC;AAYrC,4FAA4F;AAC5F,gGAAgG;AAChG,oGAAoG;AACpG,2EAA2E;AAC3E,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,qGAAqG;AACrG,+CAA+C;AAC/C,oGAAoG;AACpG,mGAAmG;AACnG,4FAA4F;AAC5F,4FAA4F;AAC5F,kFAAkF;AAClF,SAAgB,gBAAgB;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAChD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACjD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,wHAAwH;IACxH,0IAA0I;IAC1I,IAAI,CAAC;QACD,OAAO,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpF,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CAAC,2CAA2C,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1G,CAAC;AACL,CAAC;AAED,SAAgB,cAAc,CAC1B,KAAyB;AACzB,+FAA+F;AAC/F,0FAA0F;AAC1F,aAAwC;IAExC,IAAI,aAAa,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;QACnC,4FAA4F;QAC5F,wFAAwF;QACxF,6FAA6F;QAC7F,sFAAsF;QACtF,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACvC,MAAM,IAAI,+BAAa,CACnB,8BAA8B,aAAa,0CAA0C;gBACjF,sFAAsF;gBACtF,wFAAwF;gBACxF,wEAAwE;gBACxE,wCAAwC;gBACxC,uFAAuF;gBACvF,4EAA4E;gBAC5E,yFAAyF;gBACzF,yFAAyF;gBACzF,uFAAuF;gBACvF,6DAA6D,CACpE,CAAC;QACN,CAAC;QACD,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC5B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,aAAa,GAAG,EAAE,CAAC;QAClE,CAAC;IACL,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,IAAI,UAAU,GAAG,KAAK,EAAE,CAAC;YACrB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,0CAA0C,WAAW,EAAE,EAAE,CAAC;QAC3F,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// Universal \"should this rule be skipped right now?\" logic, shared by code-rules,\n// ai-hook-rules and the Nx executors so every rule honors the same two escape\n// hatches: turnOffRuleWhileOnBranch (skip while on a named branch) and\n// turnOffRuleUntilEpoch (skip until an epoch passes).\n\nexport interface SkipRuleResult {\n skip: boolean;\n reason?: string;\n}\n\n// The actual checked-out branch. The grab bag of ambient env vars (BRANCH_NAME, GIT_BRANCH,\n// CI_COMMIT_BRANCH, …) was intentionally REMOVED and must stay removed: a stray GIT_BRANCH=main\n// locally made this return \"main\" on a feature branch, which (a) mislabeled the main-sync cache and\n// (b) silently disabled merged-PR detection (detectMergedPr skips \"main\").\n//\n// The two vars below are NOT that. They are consulted BEFORE git because git cannot answer at all in\n// the case they cover — a `pull_request` checkout leaves HEAD detached on refs/pull/<N>/merge, where\n// `git rev-parse --abbrev-ref HEAD` returns the literal string \"HEAD\" and no branch hatch can match.\n// Neither can go stale the way GIT_BRANCH did:\n// GITHUB_HEAD_REF — set by the GitHub runner ONLY on pull_request/pull_request_target, and it IS\n// the source branch name. Absent on push, so the fallthrough stays safe. (Not\n// GITHUB_REF_NAME: on pull_request that is \"<N>/merge\", not a branch.)\n// WEBPIECES_BRANCH — one documented opt-in override for CI systems not special-cased here\n// (GitLab, CircleCI, Buildkite). Nobody sets it by accident.\nexport function getCurrentBranch(): string {\n const prBranch = process.env['GITHUB_HEAD_REF'];\n if (prBranch) return prBranch;\n\n const override = process.env['WEBPIECES_BRANCH'];\n if (override) return override;\n\n // webpieces-disable no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Failed to determine current git branch: ${error.message}`, { cause: error });\n }\n}\n\nexport function shouldSkipRule(\n epoch: number | undefined,\n // null (the \"no branch / always on\" value of turnOffRuleWhileOnBranch) is treated exactly like\n // undefined — no branch scoping. Only a non-empty branch name activates the branch hatch.\n branchPattern: string | undefined | null\n): SkipRuleResult {\n if (branchPattern) {\n const current = getCurrentBranch();\n // ONLY inside `if (branchPattern)`. A detached HEAD with turnOffRuleWhileOnBranch=null (the\n // overwhelmingly common value) is a non-event and must stay silent, or every tag build,\n // bisect and `gh pr checkout --detach` starts failing for no reason. The fault reported here\n // is \"you asked for branch scoping where no branch exists\", never \"HEAD is detached\".\n if (current === 'HEAD' || current === '') {\n throw new InformAiError(\n `turnOffRuleWhileOnBranch: \"${branchPattern}\" is configured, but the current branch ` +\n `cannot be determined — HEAD is detached, which is what a CI checkout of a merge ref ` +\n `looks like. The hatch would silently NOT apply, so this fails now rather than passing ` +\n `on your machine and failing in CI with an unrelated-looking error.\\n\\n` +\n `WORKAROUNDS, in order of preference:\\n` +\n ` 1. Upgrade to a webpieces that reads GITHUB_HEAD_REF (no workflow change needed).\\n` +\n ` 2. Set WEBPIECES_BRANCH in the workflow so the branch can be resolved.\\n` +\n ` 3. Use turnOffRuleUntilEpoch instead — it is TIME based, so it survives any checkout ` +\n `including a detached one, and it is the only hatch that works in CI today. Set it to a ` +\n `SHORT date (a few days): unlike branch scoping it is repo-wide while it lasts, so it ` +\n `also shelters unrelated work that lands in the same window.`\n );\n }\n if (current === branchPattern) {\n return { skip: true, reason: `on branch \"${branchPattern}\"` };\n }\n }\n if (epoch !== undefined) {\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n return { skip: true, reason: `turnOffRuleUntilEpoch active, expires: ${expiresDate}` };\n }\n }\n return { skip: false };\n}\n"]}
@@ -6,7 +6,7 @@ the array the guard actually consults, not a description of it.
6
6
 
7
7
  L0 is the OUTERMOST guard layer. It blocks work while `node_modules`, the committed shim, or
8
8
  `webpieces.config.json` are in a state that makes every other guard untrustworthy. If you are
9
- reading this, one of the six faults below fired and named this file.
9
+ reading this, one of the faults below fired and named this file.
10
10
 
11
11
  ## The faults
12
12
 
@@ -14,12 +14,13 @@ reading this, one of the six faults below fired and named this file.
14
14
  |---|---|---|---|
15
15
  | `D` | version drift — root package.json pin != installed version | sh, before the bin runs | sh |
16
16
  | `X` | guard bin missing (fresh clone / new worktree / package removed) | sh, before the bin runs | sh |
17
+ | `U` | guard bin missing AND @webpieces/ai-hook-rules is not declared in package.json | sh, before the bin runs | sh |
17
18
  | `K` | guard bin present but CRASHED (exit code not 0 or 2 — corrupt node_modules) | sh, before the bin runs | sh |
18
19
  | `S` | committed .claude/webpieces/ai-hook.sh != renderShim() | the guard bin | JS |
19
20
  | `C` | webpieces.config.json missing | the guard bin | JS |
20
21
  | `Y` | a loaded rule has no webpieces.config.json key | the guard bin | JS |
21
22
 
22
- First match wins. `D`/`X`/`K` are decided in POSIX `sh` inside the committed shim, BEFORE the
23
+ First match wins. `D`/`X`/`U`/`K` are decided in POSIX `sh` inside the committed shim, BEFORE the
23
24
  guard bin runs — a stale, missing or broken validator cannot be trusted to validate itself.
24
25
 
25
26
  ## The fix, per fault
@@ -37,6 +38,10 @@ the option you pick EXACTLY as written and run nothing else on that line.
37
38
 
38
39
  - **Option 1 (preferred)**: `pnpm install` ← pick this when this fault fires at all — nothing is installed in THIS tree, and a new git worktree copies no node_modules
39
40
 
41
+ ### `U` — guard bin missing AND @webpieces/ai-hook-rules is not declared in package.json
42
+
43
+ - **Option 1 (preferred)**: `pnpm add -D @webpieces/ai-hook-rules` ← pick this when this fault fires at all — package.json asks for nothing, so pnpm install reports "Lockfile is up to date" and leaves the tree exactly as broken as it found it
44
+
40
45
  ### `K` — guard bin present but CRASHED (exit code not 0 or 2 — corrupt node_modules)
41
46
 
42
47
  - **Option 1 (preferred)**: `rm -rf node_modules && pnpm install` ← pick this when this fault fires at all — a BARE pnpm install SKIPS the corrupt package, because pnpm sees the right version on disk and considers it installed; only the delete forces a rewrite
@@ -69,7 +74,7 @@ The tool is not a dimension either: "any Read" is an allowlist ENTRY, not a tool
69
74
 
70
75
  ## The allowlist
71
76
 
72
- ONE list, consulted identically by all six faults. A cure that cannot help a given fault also
77
+ ONE list, consulted identically by every fault. A cure that cannot help a given fault also
73
78
  cannot hurt it, and gating each entry on a fault is what produced four real defects (a stale
74
79
  shim that denied `pnpm install` and `git pull`; faults that denied every Read; a config fault
75
80
  that denied `rm -rf node_modules && pnpm install` while allowing a bare `pnpm install`).
@@ -84,7 +89,8 @@ that denied `rm -rf node_modules && pnpm install` while allowing a bare `pnpm in
84
89
  | 6 | pnpm exec wp-upgrade-shim | ALLOW |
85
90
  | 7 | cp node_modules/@webpieces/ai-hook-rules/templates/ai-hook.sh .claude/webpieces/ai-hook.sh | ALLOW |
86
91
  | 8 | pnpm exec wp-install-ai-hooks (flags allowed, e.g. --target=project) | ALLOW |
87
- | 9 | read-only orientation: pwd, git status/log/diff/show/branch/rev-parse, git worktree list | ALLOW |
92
+ | 9 | pnpm add -D @webpieces/ai-hook-rules (an @version and extra flags allowed) | ALLOW |
93
+ | 10 | read-only orientation: pwd, git status/log/diff/show/branch/rev-parse, git worktree list | ALLOW |
88
94
 
89
95
  - **PASS** — L0 has no objection; the call falls THROUGH so the downstream guards still judge it.
90
96
  - **ALLOW** — terminal; bypasses everything, because a cure must stay reachable even when a
@@ -100,7 +106,7 @@ a DIFFERENT command and it is rejected again — that is not the guard refusing
100
106
  ## Known asymmetry
101
107
 
102
108
  Under `S`/`C`/`Y` the guard bin IS running, so a PASS really does fall through to the downstream
103
- guards. Under `D`/`X`/`K` the bin is never executed, so there is nothing to fall through to and a
109
+ guards. Under `D`/`X`/`U`/`K` the bin is never executed, so there is nothing to fall through to and a
104
110
  PASS degenerates into a terminal allow — reads are unguarded during those three faults.
105
111
 
106
112
  ## Widening L0