@webpieces/ai-hook-rules 0.4.636 → 0.4.638
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/README.md +11 -4
- package/package.json +2 -2
- package/src/adapters/hook-core.js +4 -3
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/setup.js +72 -6
- package/src/bin/setup.js.map +1 -1
- package/src/core/custom-rule-adapter.js +3 -1
- package/src/core/custom-rule-adapter.js.map +1 -1
- package/src/core/decision-log.d.ts +38 -5
- package/src/core/decision-log.js +45 -6
- package/src/core/decision-log.js.map +1 -1
- package/src/core/l1-doc.js +6 -3
- package/src/core/l1-doc.js.map +1 -1
- package/src/core/l2-doc.d.ts +7 -0
- package/src/core/l2-doc.js +274 -0
- package/src/core/l2-doc.js.map +1 -0
- package/src/core/l2-rows.d.ts +97 -0
- package/src/core/l2-rows.js +229 -0
- package/src/core/l2-rows.js.map +1 -0
- package/src/core/load-rules.d.ts +8 -2
- package/src/core/load-rules.js +51 -31
- package/src/core/load-rules.js.map +1 -1
- package/src/core/main-sync-refresh.d.ts +8 -1
- package/src/core/main-sync-refresh.js +9 -2
- package/src/core/main-sync-refresh.js.map +1 -1
- package/src/core/main-sync-timeout.d.ts +32 -0
- package/src/core/main-sync-timeout.js +93 -0
- package/src/core/main-sync-timeout.js.map +1 -0
- package/src/core/rules/branch-creation-guard.js +1 -1
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/catch-error-pattern.js +1 -1
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/feature-branch-guard.d.ts +3 -3
- package/src/core/rules/feature-branch-guard.js +17 -4
- package/src/core/rules/feature-branch-guard.js.map +1 -1
- package/src/core/rules/index.d.ts +15 -1
- package/src/core/rules/index.js +18 -10
- package/src/core/rules/index.js.map +1 -1
- package/src/core/rules/match-rule.js +4 -1
- package/src/core/rules/match-rule.js.map +1 -1
- package/src/core/rules/max-file-lines.js +1 -1
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +9 -3
- package/src/core/rules/merge-in-progress-guard.js +9 -4
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/merged-branch-bash-guard.d.ts +3 -3
- package/src/core/rules/merged-branch-bash-guard.js +17 -6
- package/src/core/rules/merged-branch-bash-guard.js.map +1 -1
- package/src/core/rules/no-any-unknown.js +1 -1
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-custom-css.js +1 -1
- package/src/core/rules/no-custom-css.js.map +1 -1
- package/src/core/rules/no-destructure.js +1 -1
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-implicit-any.js +1 -1
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.js +1 -1
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-process-exit-outside-main.js +1 -1
- package/src/core/rules/no-process-exit-outside-main.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.js +1 -1
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.js +1 -1
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/pr-creation-or-push-guard.d.ts +10 -3
- package/src/core/rules/pr-creation-or-push-guard.js +10 -4
- package/src/core/rules/pr-creation-or-push-guard.js.map +1 -1
- package/src/core/rules/pr-merge-guard.d.ts +3 -3
- package/src/core/rules/pr-merge-guard.js +2 -1
- package/src/core/rules/pr-merge-guard.js.map +1 -1
- package/src/core/rules/read-stale-guard.d.ts +3 -3
- package/src/core/rules/read-stale-guard.js +15 -6
- package/src/core/rules/read-stale-guard.js.map +1 -1
- package/src/core/rules/redirect-how-to-merge-main.d.ts +3 -3
- package/src/core/rules/redirect-how-to-merge-main.js +1 -1
- package/src/core/rules/redirect-how-to-merge-main.js.map +1 -1
- package/src/core/rules/require-return-type.js +1 -1
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/stale-main-bash-guard.d.ts +3 -3
- package/src/core/rules/stale-main-bash-guard.js +5 -4
- package/src/core/rules/stale-main-bash-guard.js.map +1 -1
- package/src/core/rules/throw-cause-required.js +1 -1
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.js +1 -1
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/rules/whole-repo-build-guard.js +6 -2
- package/src/core/rules/whole-repo-build-guard.js.map +1 -1
- package/src/core/runner.js +41 -22
- package/src/core/runner.js.map +1 -1
- package/src/core/sync-main.d.ts +9 -0
- package/src/core/sync-main.js +17 -1
- package/src/core/sync-main.js.map +1 -1
- package/src/core/types.d.ts +11 -0
- package/src/core/types.js.map +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// L2 — the BRANCH-STATE layer, as data.
|
|
4
|
+
//
|
|
5
|
+
// L2 answers one question: *may I work here, and is what I read current?* Drawn as a decision matrix
|
|
6
|
+
// that is TEN ordered rows plus one terminal fail-open row, first match wins.
|
|
7
|
+
//
|
|
8
|
+
// This module holds those rows, l2-doc.ts renders them into guards/L2-branch-state.md, and a unit test
|
|
9
|
+
// (l2-matrix.spec.ts) locks that file byte-identical to the renderer — the same mechanism that already
|
|
10
|
+
// makes L0's fault table and L1's location table undriftable. Before this existed the L2 doc was 100%
|
|
11
|
+
// hand-written and said so: *"Until that lands this text is hand-written and can drift."*
|
|
12
|
+
//
|
|
13
|
+
// ## HOW L2 JOINS TO THE ROWS TODAY — read this before assuming it works like L1
|
|
14
|
+
//
|
|
15
|
+
// L1 DISPATCHES from its array: `runner.l1LocationBlock` takes the first matching row and switches on
|
|
16
|
+
// its `blockId`, so deleting a row deletes a block. L2 does NOT, and pretending otherwise would be the
|
|
17
|
+
// drift this table exists to remove. The four L2 guard classes each own their own ladder, and those
|
|
18
|
+
// ladders diverge on purpose (guards/L2-branch-state.md, "Deliberate divergence": the two Bash guards
|
|
19
|
+
// differ in polarity, quantifier and empty-command handling all at once, so no single parameterised
|
|
20
|
+
// function serves both). Unifying them is where a wrong edit turns an allow into a session-wedging
|
|
21
|
+
// block, so it is deliberately not attempted here.
|
|
22
|
+
//
|
|
23
|
+
// What L2 does instead is a REASON→ROW join. Every exit of every L2 guard already carries a stable
|
|
24
|
+
// reason string into the decision log; `L2_ROW_FOR_REASON` maps each of those to the row it is an
|
|
25
|
+
// instance of, the guards stamp that number as `row=`, and l2-matrix.spec.ts asserts the map is
|
|
26
|
+
// EXHAUSTIVE against the guard sources — a new reason with no row fails the build. So `row=8` in
|
|
27
|
+
// `.webpieces/logs/L2-decisions` opens guards/L2-branch-state.md at row 8 and reads the state, the cure
|
|
28
|
+
// and the tools that row covers, exactly as `row=5` already does for L1.
|
|
29
|
+
//
|
|
30
|
+
// The rows the guards cannot yet honour are named in the doc's "Not done" section rather than quietly
|
|
31
|
+
// rendered as if they were live. That section is generated from NOT_DONE below, so it cannot rot.
|
|
32
|
+
//
|
|
33
|
+
// This module is deliberately import-free at runtime, so `pnpm guards:generate` can load it without the
|
|
34
|
+
// package's transitive dependencies.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.NOT_DONE = exports.L2NotDone = exports.L2_ROWS = exports.L2Row = exports.L2_FAIL_OPEN = exports.L2_BLOCK = exports.L2_EXEMPT = exports.L2_ALLOW = exports.L2Action = exports.L2_FAIL_OPEN_ROW = void 0;
|
|
38
|
+
exports.l2RowForReason = l2RowForReason;
|
|
39
|
+
exports.l2MappedReasons = l2MappedReasons;
|
|
40
|
+
/**
|
|
41
|
+
* The TERMINAL fail-open row, and the one number in this table that is not from the 1-10 design.
|
|
42
|
+
*
|
|
43
|
+
* Everything in rows 6-10 needs the main-sync cache, and the cache is written by a fire-and-forget
|
|
44
|
+
* refresher that populates it for the NEXT call — so the first tool call of every session has none.
|
|
45
|
+
* "Stop here and ALLOW" was written as a DIVIDER in the design table, i.e. as prose between two blocks
|
|
46
|
+
* of rows. Prose cannot be stamped into a log line, and this is the single most frequently taken exit
|
|
47
|
+
* in the whole layer (every session's first call, every unreadable branch, every unreachable forge), so
|
|
48
|
+
* it is a row with a number like any other.
|
|
49
|
+
*
|
|
50
|
+
* It is 11 rather than 6-with-a-renumber because row numbers are IDENTITY here: they are printed in the
|
|
51
|
+
* doc and logged as `row=`, so shifting 6-10 down would silently re-point every reference. The doc
|
|
52
|
+
* prints it in its true position, between rows 5 and 6, with its number shown — same treatment L1 gives
|
|
53
|
+
* row 8, which is printed third and numbered 8.
|
|
54
|
+
*/
|
|
55
|
+
exports.L2_FAIL_OPEN_ROW = 11;
|
|
56
|
+
/** The `act` cell: the doc's literal label, plus the machine-readable kind behind it. */
|
|
57
|
+
class L2Action {
|
|
58
|
+
label;
|
|
59
|
+
kind;
|
|
60
|
+
constructor(label, kind) {
|
|
61
|
+
this.label = label;
|
|
62
|
+
this.kind = kind;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.L2Action = L2Action;
|
|
66
|
+
exports.L2_ALLOW = new L2Action('1 allow', 'allow');
|
|
67
|
+
exports.L2_EXEMPT = new L2Action('2 exempt', 'exempt');
|
|
68
|
+
exports.L2_BLOCK = new L2Action('4 block', 'block');
|
|
69
|
+
exports.L2_FAIL_OPEN = new L2Action('1 allow (fail-open)', 'fail-open');
|
|
70
|
+
/**
|
|
71
|
+
* One row of L2's decision table.
|
|
72
|
+
*
|
|
73
|
+
* `cure` is rendered verbatim into the doc and is LITERAL by policy: L0's cure-reachability discipline
|
|
74
|
+
* says a message pointing at documentation for its own remedy cannot be tested, and it caught a fault
|
|
75
|
+
* prescribing a bin that had been renamed away. `—` is the only legal non-command cure, and only on a
|
|
76
|
+
* row that allows.
|
|
77
|
+
*/
|
|
78
|
+
class L2Row {
|
|
79
|
+
num;
|
|
80
|
+
tools;
|
|
81
|
+
state;
|
|
82
|
+
action;
|
|
83
|
+
cure;
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- the five cells of one doc row
|
|
85
|
+
constructor(num, tools,
|
|
86
|
+
/** The `state` cell, verbatim. */
|
|
87
|
+
state, action,
|
|
88
|
+
/** The `cure` cell, verbatim. `—` when the row allows. */
|
|
89
|
+
cure) {
|
|
90
|
+
this.num = num;
|
|
91
|
+
this.tools = tools;
|
|
92
|
+
this.state = state;
|
|
93
|
+
this.action = action;
|
|
94
|
+
this.cure = cure;
|
|
95
|
+
}
|
|
96
|
+
/** `B R E`, the doc's own spelling of the tool cell. */
|
|
97
|
+
toolCell() {
|
|
98
|
+
return this.tools.join(' ');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.L2Row = L2Row;
|
|
102
|
+
/**
|
|
103
|
+
* THE ELEVEN L2 ROWS, in first-match-wins order.
|
|
104
|
+
*
|
|
105
|
+
* Rows 1-5 need NO cache and fire on call #1: rows 1, 2 and 4 are text matches, row 3 is a marker-file
|
|
106
|
+
* scan, row 5 is one `git rev-parse`. Row 11 is the cache divider. Rows 6-10 all read the cache.
|
|
107
|
+
*
|
|
108
|
+
* THE ORDER OF ROW 5 IS THE MOST LOAD-BEARING THING IN THIS TABLE. Put "on main" BELOW the divider and
|
|
109
|
+
* writes on `main` are permitted for the whole first call of every session — and permanently in a
|
|
110
|
+
* multi-worktree repo, where another tree may hold the cache lock indefinitely.
|
|
111
|
+
*
|
|
112
|
+
* `B` tracks `E` everywhere; `R` is judged separately in exactly ONE place, rows 6/7 on `main`. A Read
|
|
113
|
+
* names exactly one file so the guard can evaluate it precisely; a Bash command is opaque and gets the
|
|
114
|
+
* conservative answer. Reading a CURRENT `main` is fine — the problem is that `main` is almost always
|
|
115
|
+
* behind.
|
|
116
|
+
*/
|
|
117
|
+
exports.L2_ROWS = [
|
|
118
|
+
new L2Row(1, ['B', 'R', 'E'], 'on the **global allowlist** (inert command, or a universal cure such as reading/editing `webpieces.config.json`)', exports.L2_ALLOW, '—'),
|
|
119
|
+
new L2Row(2, ['B'], 'bare `git checkout main`, with no `git pull` chained into the same command', exports.L2_BLOCK, '`git checkout main && git pull origin main`'),
|
|
120
|
+
new L2Row(3, ['B', 'R', 'E'], '**merge in progress** — L4 owns this state', exports.L2_EXEMPT, 'finish the merge: `pnpm wp-finish-upsert-pr`'),
|
|
121
|
+
new L2Row(4, ['B'], 'on the **skip list** — it gets you OUT, or tells you where you are', exports.L2_ALLOW, '—'),
|
|
122
|
+
new L2Row(5, ['B', 'E'], 'on `main`', exports.L2_BLOCK, '`git checkout -b <new> origin/main`'),
|
|
123
|
+
new L2Row(exports.L2_FAIL_OPEN_ROW, ['B', 'R', 'E'], '**the state could not be established** — branch undeterminable, no cache yet, the cache holds another branch, `origin/main` unknown, the forge unreachable, or a dirty tree whose cure is not a clean fast-forward', exports.L2_FAIL_OPEN, '— (nothing to fix; the refresher populates the cache for the next call)'),
|
|
124
|
+
new L2Row(6, ['R'], 'on `main`, behind `origin/main`', exports.L2_BLOCK, '`git pull origin main`, or `git checkout -b <new> origin/main`'),
|
|
125
|
+
new L2Row(7, ['R'], 'on `main`, current', exports.L2_ALLOW, '—'),
|
|
126
|
+
new L2Row(8, ['B', 'R', 'E'], 'on a branch whose PR is **already merged**', exports.L2_BLOCK, '`git fetch origin main && git checkout -b <new> origin/main`'),
|
|
127
|
+
new L2Row(9, ['B', 'R', 'E'], 'no fork point with `origin/main`, or `origin/main` moved and collided with your files', exports.L2_BLOCK, '`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a PR is open'),
|
|
128
|
+
new L2Row(10, ['B', 'R', 'E'], 'healthy feature branch', exports.L2_ALLOW, '—'),
|
|
129
|
+
];
|
|
130
|
+
/**
|
|
131
|
+
* REASON → ROW. The join between what a guard actually logged and the row it is an instance of.
|
|
132
|
+
*
|
|
133
|
+
* Keys are the exact `reason` strings the four L2 guards pass to their decision log. Two of them are
|
|
134
|
+
* PREFIXES because the guard interpolates a PR number or a matched segment into the reason
|
|
135
|
+
* (`already-merged PR#123`); those are matched by `l2RowForReason` on prefix, which is why they end in
|
|
136
|
+
* a space or a `(`.
|
|
137
|
+
*
|
|
138
|
+
* l2-matrix.spec.ts reads the four guard sources and asserts every reason literal in them resolves
|
|
139
|
+
* here, so a new exit with no row fails the build rather than logging `row=-` forever.
|
|
140
|
+
*/
|
|
141
|
+
const EXACT_REASON_ROWS = {
|
|
142
|
+
// Row 1 — the universal cure that must stay reachable from inside any block.
|
|
143
|
+
'webpieces-config-read (escape hatch)': 1,
|
|
144
|
+
// Row 2 — the preventive half, decided from command TEXT before any cache is read.
|
|
145
|
+
// (prefix, see PREFIX_REASON_ROWS)
|
|
146
|
+
// Row 4 — the skip list, in its two live spellings.
|
|
147
|
+
'merged-branch recovery/inspection (allowlisted)': 4,
|
|
148
|
+
'not-a-content-read (cure/build/metadata)': 4,
|
|
149
|
+
// Row 5 — never work on main.
|
|
150
|
+
'on-main': 5,
|
|
151
|
+
// Row 6/7 — the ONE place a Read is judged differently from a Bash command.
|
|
152
|
+
'on-stale-main': 6,
|
|
153
|
+
'local-main-contains-origin (up to date)': 7,
|
|
154
|
+
// Row 9 — the two unhealthy-fork states.
|
|
155
|
+
'no-fork-point': 9,
|
|
156
|
+
'main-moved-conflict': 9,
|
|
157
|
+
// Row 10 — healthy, and the state-B guard's "this is not my state" hand-off, which is the same
|
|
158
|
+
// verdict about the same tree.
|
|
159
|
+
'clean-feature-branch': 10,
|
|
160
|
+
'not-on-main (state B is another guard)': 10,
|
|
161
|
+
// Row 11 — every "could not establish", including the two dirty-tree valves the code still opens
|
|
162
|
+
// (see NOT_DONE) and the unreachable forge.
|
|
163
|
+
'branch-undeterminable': exports.L2_FAIL_OPEN_ROW,
|
|
164
|
+
'no-sync-cache': exports.L2_FAIL_OPEN_ROW,
|
|
165
|
+
'stale-cross-branch-cache': exports.L2_FAIL_OPEN_ROW,
|
|
166
|
+
'origin-main-unknown': exports.L2_FAIL_OPEN_ROW,
|
|
167
|
+
'dirty-tree-on-main': exports.L2_FAIL_OPEN_ROW,
|
|
168
|
+
'dirty-merged-branch': exports.L2_FAIL_OPEN_ROW,
|
|
169
|
+
'no-forge': exports.L2_FAIL_OPEN_ROW,
|
|
170
|
+
// The config-edit bypass logged by the hook adapter before any guard runs — row 1, same universal
|
|
171
|
+
// cure as the config READ above.
|
|
172
|
+
'config-bypass (feature-branch-guard skipped)': 1,
|
|
173
|
+
};
|
|
174
|
+
/** Reasons the guards interpolate a value into. Matched by prefix, longest first. */
|
|
175
|
+
const PREFIX_REASON_ROWS = {
|
|
176
|
+
'already-merged PR#': 8,
|
|
177
|
+
'bare checkout of main (': 2,
|
|
178
|
+
'stale-main content read (': 6,
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The row a logged reason belongs to, or null when nothing claims it.
|
|
182
|
+
*
|
|
183
|
+
* Null rather than a default row: a reason with no row is a HOLE in the table, and defaulting it to
|
|
184
|
+
* "fail-open" would hide exactly the drift the exhaustiveness spec exists to catch. The guards render
|
|
185
|
+
* null as `row=-`, so an unmapped reason is visible in the log too, not only in CI.
|
|
186
|
+
*/
|
|
187
|
+
// webpieces-disable no-function-outside-class -- the matcher over the two tables above, beside them in this data module
|
|
188
|
+
function l2RowForReason(reason) {
|
|
189
|
+
const exact = EXACT_REASON_ROWS[reason];
|
|
190
|
+
if (exact !== undefined)
|
|
191
|
+
return exact;
|
|
192
|
+
for (const prefix of Object.keys(PREFIX_REASON_ROWS)) {
|
|
193
|
+
if (reason.startsWith(prefix))
|
|
194
|
+
return PREFIX_REASON_ROWS[prefix];
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
/** Every reason string this table claims, for the exhaustiveness spec. */
|
|
199
|
+
// webpieces-disable no-function-outside-class -- pure accessor over the two tables above
|
|
200
|
+
function l2MappedReasons() {
|
|
201
|
+
return [...Object.keys(EXACT_REASON_ROWS), ...Object.keys(PREFIX_REASON_ROWS)];
|
|
202
|
+
}
|
|
203
|
+
/** One documented gap between a row and what the guards actually do today. Data-only. */
|
|
204
|
+
class L2NotDone {
|
|
205
|
+
row;
|
|
206
|
+
gap;
|
|
207
|
+
why;
|
|
208
|
+
constructor(row, gap, why) {
|
|
209
|
+
this.row = row;
|
|
210
|
+
this.gap = gap;
|
|
211
|
+
this.why = why;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.L2NotDone = L2NotDone;
|
|
215
|
+
/**
|
|
216
|
+
* WHERE THE TABLE AND THE CODE DISAGREE, stated rather than papered over.
|
|
217
|
+
*
|
|
218
|
+
* The L1 precedent is `## Not done — \`o\` is not exempt yet`: a row the runner cannot reach, named in
|
|
219
|
+
* the generated doc with the reason it has not shipped. The same treatment applies here, and it is what
|
|
220
|
+
* makes it safe to publish a table the guards do not yet dispatch from — a reader is told exactly which
|
|
221
|
+
* rows describe intent rather than behaviour, and the log's `row=` stamps land on row 11 for every one
|
|
222
|
+
* of these, so the trail never claims the strict row fired.
|
|
223
|
+
*/
|
|
224
|
+
exports.NOT_DONE = [
|
|
225
|
+
new L2NotDone(5, 'Row 5 blocks `B` as well as `E` on `main`. Only `E` is blocked today: `feature-branch-guard` blocks the write, while `stale-main-bash-guard` blocks only CONTENT-READING Bash, and only once the cache proves `main` is behind.', 'Bash on a CURRENT `main` is harmless, and the strict form needs the skip list (row 4) to be complete first — a `B`-on-main block with an incomplete skip list wedges the session on its own cure.'),
|
|
226
|
+
new L2NotDone(8, 'Row 8 blocks reads on a merged branch even when the tree is DIRTY. The code opens a dirty valve and fails open (`dirty-merged-branch`, logged at row 11).', 'The row is the ORIGINAL documented design — `git checkout -b <new> origin/main` carries uncommitted changes onto the fresh branch, so nothing is trapped — and `read-stale-guard`\'s own class comment still states it. The code drifted, and closing the valve is a behaviour change that belongs in its own PR with its own evidence, not in a config collapse.'),
|
|
227
|
+
new L2NotDone(6, 'Row 6 blocks reads on a stale `main` even when the tree is DIRTY. The code opens a dirty valve (`dirty-tree-on-main`, logged at row 11).', 'This is the one place the dirty argument has teeth: the cure is `git pull`, which genuinely is not a clean fast-forward on a dirty tree. `git stash` is on the skip list and clears it, so the strict form is reachable — but it is the same behaviour change, and the same separate PR.'),
|
|
228
|
+
];
|
|
229
|
+
//# sourceMappingURL=l2-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"l2-rows.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l2-rows.ts"],"names":[],"mappings":"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","sourcesContent":["// ---------------------------------------------------------------------------\n// L2 — the BRANCH-STATE layer, as data.\n//\n// L2 answers one question: *may I work here, and is what I read current?* Drawn as a decision matrix\n// that is TEN ordered rows plus one terminal fail-open row, first match wins.\n//\n// This module holds those rows, l2-doc.ts renders them into guards/L2-branch-state.md, and a unit test\n// (l2-matrix.spec.ts) locks that file byte-identical to the renderer — the same mechanism that already\n// makes L0's fault table and L1's location table undriftable. Before this existed the L2 doc was 100%\n// hand-written and said so: *\"Until that lands this text is hand-written and can drift.\"*\n//\n// ## HOW L2 JOINS TO THE ROWS TODAY — read this before assuming it works like L1\n//\n// L1 DISPATCHES from its array: `runner.l1LocationBlock` takes the first matching row and switches on\n// its `blockId`, so deleting a row deletes a block. L2 does NOT, and pretending otherwise would be the\n// drift this table exists to remove. The four L2 guard classes each own their own ladder, and those\n// ladders diverge on purpose (guards/L2-branch-state.md, \"Deliberate divergence\": the two Bash guards\n// differ in polarity, quantifier and empty-command handling all at once, so no single parameterised\n// function serves both). Unifying them is where a wrong edit turns an allow into a session-wedging\n// block, so it is deliberately not attempted here.\n//\n// What L2 does instead is a REASON→ROW join. Every exit of every L2 guard already carries a stable\n// reason string into the decision log; `L2_ROW_FOR_REASON` maps each of those to the row it is an\n// instance of, the guards stamp that number as `row=`, and l2-matrix.spec.ts asserts the map is\n// EXHAUSTIVE against the guard sources — a new reason with no row fails the build. So `row=8` in\n// `.webpieces/logs/L2-decisions` opens guards/L2-branch-state.md at row 8 and reads the state, the cure\n// and the tools that row covers, exactly as `row=5` already does for L1.\n//\n// The rows the guards cannot yet honour are named in the doc's \"Not done\" section rather than quietly\n// rendered as if they were live. That section is generated from NOT_DONE below, so it cannot rot.\n//\n// This module is deliberately import-free at runtime, so `pnpm guards:generate` can load it without the\n// package's transitive dependencies.\n// ---------------------------------------------------------------------------\n\n/** Which tools a row covers. `B` Bash · `R` Read · `E` Write/Edit. */\nexport type L2Tool = 'B' | 'R' | 'E';\n\n/** What L2 does with a row — the same action codebook every layer reports in (GUARD_MATRIX.md). */\nexport type L2ActionKind = 'allow' | 'exempt' | 'block' | 'fail-open';\n\n/**\n * The TERMINAL fail-open row, and the one number in this table that is not from the 1-10 design.\n *\n * Everything in rows 6-10 needs the main-sync cache, and the cache is written by a fire-and-forget\n * refresher that populates it for the NEXT call — so the first tool call of every session has none.\n * \"Stop here and ALLOW\" was written as a DIVIDER in the design table, i.e. as prose between two blocks\n * of rows. Prose cannot be stamped into a log line, and this is the single most frequently taken exit\n * in the whole layer (every session's first call, every unreadable branch, every unreachable forge), so\n * it is a row with a number like any other.\n *\n * It is 11 rather than 6-with-a-renumber because row numbers are IDENTITY here: they are printed in the\n * doc and logged as `row=`, so shifting 6-10 down would silently re-point every reference. The doc\n * prints it in its true position, between rows 5 and 6, with its number shown — same treatment L1 gives\n * row 8, which is printed third and numbered 8.\n */\nexport const L2_FAIL_OPEN_ROW = 11;\n\n/** The `act` cell: the doc's literal label, plus the machine-readable kind behind it. */\nexport class L2Action {\n constructor(readonly label: string, readonly kind: L2ActionKind) {}\n}\n\nexport const L2_ALLOW = new L2Action('1 allow', 'allow');\nexport const L2_EXEMPT = new L2Action('2 exempt', 'exempt');\nexport const L2_BLOCK = new L2Action('4 block', 'block');\nexport const L2_FAIL_OPEN = new L2Action('1 allow (fail-open)', 'fail-open');\n\n/**\n * One row of L2's decision table.\n *\n * `cure` is rendered verbatim into the doc and is LITERAL by policy: L0's cure-reachability discipline\n * says a message pointing at documentation for its own remedy cannot be tested, and it caught a fault\n * prescribing a bin that had been renamed away. `—` is the only legal non-command cure, and only on a\n * row that allows.\n */\nexport class L2Row {\n // eslint-disable-next-line @typescript-eslint/max-params -- the five cells of one doc row\n constructor(\n readonly num: number,\n readonly tools: readonly L2Tool[],\n /** The `state` cell, verbatim. */\n readonly state: string,\n readonly action: L2Action,\n /** The `cure` cell, verbatim. `—` when the row allows. */\n readonly cure: string,\n ) {}\n\n /** `B R E`, the doc's own spelling of the tool cell. */\n toolCell(): string {\n return this.tools.join(' ');\n }\n}\n\n/**\n * THE ELEVEN L2 ROWS, in first-match-wins order.\n *\n * Rows 1-5 need NO cache and fire on call #1: rows 1, 2 and 4 are text matches, row 3 is a marker-file\n * scan, row 5 is one `git rev-parse`. Row 11 is the cache divider. Rows 6-10 all read the cache.\n *\n * THE ORDER OF ROW 5 IS THE MOST LOAD-BEARING THING IN THIS TABLE. Put \"on main\" BELOW the divider and\n * writes on `main` are permitted for the whole first call of every session — and permanently in a\n * multi-worktree repo, where another tree may hold the cache lock indefinitely.\n *\n * `B` tracks `E` everywhere; `R` is judged separately in exactly ONE place, rows 6/7 on `main`. A Read\n * names exactly one file so the guard can evaluate it precisely; a Bash command is opaque and gets the\n * conservative answer. Reading a CURRENT `main` is fine — the problem is that `main` is almost always\n * behind.\n */\nexport const L2_ROWS: readonly L2Row[] = [\n new L2Row(1, ['B', 'R', 'E'], 'on the **global allowlist** (inert command, or a universal cure such as reading/editing `webpieces.config.json`)', L2_ALLOW, '—'),\n new L2Row(2, ['B'], 'bare `git checkout main`, with no `git pull` chained into the same command', L2_BLOCK, '`git checkout main && git pull origin main`'),\n new L2Row(3, ['B', 'R', 'E'], '**merge in progress** — L4 owns this state', L2_EXEMPT, 'finish the merge: `pnpm wp-finish-upsert-pr`'),\n new L2Row(4, ['B'], 'on the **skip list** — it gets you OUT, or tells you where you are', L2_ALLOW, '—'),\n new L2Row(5, ['B', 'E'], 'on `main`', L2_BLOCK, '`git checkout -b <new> origin/main`'),\n new L2Row(L2_FAIL_OPEN_ROW, ['B', 'R', 'E'], '**the state could not be established** — branch undeterminable, no cache yet, the cache holds another branch, `origin/main` unknown, the forge unreachable, or a dirty tree whose cure is not a clean fast-forward', L2_FAIL_OPEN, '— (nothing to fix; the refresher populates the cache for the next call)'),\n new L2Row(6, ['R'], 'on `main`, behind `origin/main`', L2_BLOCK, '`git pull origin main`, or `git checkout -b <new> origin/main`'),\n new L2Row(7, ['R'], 'on `main`, current', L2_ALLOW, '—'),\n new L2Row(8, ['B', 'R', 'E'], 'on a branch whose PR is **already merged**', L2_BLOCK, '`git fetch origin main && git checkout -b <new> origin/main`'),\n new L2Row(9, ['B', 'R', 'E'], 'no fork point with `origin/main`, or `origin/main` moved and collided with your files', L2_BLOCK, '`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a PR is open'),\n new L2Row(10, ['B', 'R', 'E'], 'healthy feature branch', L2_ALLOW, '—'),\n];\n\n/**\n * REASON → ROW. The join between what a guard actually logged and the row it is an instance of.\n *\n * Keys are the exact `reason` strings the four L2 guards pass to their decision log. Two of them are\n * PREFIXES because the guard interpolates a PR number or a matched segment into the reason\n * (`already-merged PR#123`); those are matched by `l2RowForReason` on prefix, which is why they end in\n * a space or a `(`.\n *\n * l2-matrix.spec.ts reads the four guard sources and asserts every reason literal in them resolves\n * here, so a new exit with no row fails the build rather than logging `row=-` forever.\n */\nconst EXACT_REASON_ROWS: Record<string, number> = {\n // Row 1 — the universal cure that must stay reachable from inside any block.\n 'webpieces-config-read (escape hatch)': 1,\n // Row 2 — the preventive half, decided from command TEXT before any cache is read.\n // (prefix, see PREFIX_REASON_ROWS)\n // Row 4 — the skip list, in its two live spellings.\n 'merged-branch recovery/inspection (allowlisted)': 4,\n 'not-a-content-read (cure/build/metadata)': 4,\n // Row 5 — never work on main.\n 'on-main': 5,\n // Row 6/7 — the ONE place a Read is judged differently from a Bash command.\n 'on-stale-main': 6,\n 'local-main-contains-origin (up to date)': 7,\n // Row 9 — the two unhealthy-fork states.\n 'no-fork-point': 9,\n 'main-moved-conflict': 9,\n // Row 10 — healthy, and the state-B guard's \"this is not my state\" hand-off, which is the same\n // verdict about the same tree.\n 'clean-feature-branch': 10,\n 'not-on-main (state B is another guard)': 10,\n // Row 11 — every \"could not establish\", including the two dirty-tree valves the code still opens\n // (see NOT_DONE) and the unreachable forge.\n 'branch-undeterminable': L2_FAIL_OPEN_ROW,\n 'no-sync-cache': L2_FAIL_OPEN_ROW,\n 'stale-cross-branch-cache': L2_FAIL_OPEN_ROW,\n 'origin-main-unknown': L2_FAIL_OPEN_ROW,\n 'dirty-tree-on-main': L2_FAIL_OPEN_ROW,\n 'dirty-merged-branch': L2_FAIL_OPEN_ROW,\n 'no-forge': L2_FAIL_OPEN_ROW,\n // The config-edit bypass logged by the hook adapter before any guard runs — row 1, same universal\n // cure as the config READ above.\n 'config-bypass (feature-branch-guard skipped)': 1,\n};\n\n/** Reasons the guards interpolate a value into. Matched by prefix, longest first. */\nconst PREFIX_REASON_ROWS: Record<string, number> = {\n 'already-merged PR#': 8,\n 'bare checkout of main (': 2,\n 'stale-main content read (': 6,\n};\n\n/**\n * The row a logged reason belongs to, or null when nothing claims it.\n *\n * Null rather than a default row: a reason with no row is a HOLE in the table, and defaulting it to\n * \"fail-open\" would hide exactly the drift the exhaustiveness spec exists to catch. The guards render\n * null as `row=-`, so an unmapped reason is visible in the log too, not only in CI.\n */\n// webpieces-disable no-function-outside-class -- the matcher over the two tables above, beside them in this data module\nexport function l2RowForReason(reason: string): number | null {\n const exact = EXACT_REASON_ROWS[reason];\n if (exact !== undefined) return exact;\n for (const prefix of Object.keys(PREFIX_REASON_ROWS)) {\n if (reason.startsWith(prefix)) return PREFIX_REASON_ROWS[prefix];\n }\n return null;\n}\n\n/** Every reason string this table claims, for the exhaustiveness spec. */\n// webpieces-disable no-function-outside-class -- pure accessor over the two tables above\nexport function l2MappedReasons(): readonly string[] {\n return [...Object.keys(EXACT_REASON_ROWS), ...Object.keys(PREFIX_REASON_ROWS)];\n}\n\n/** One documented gap between a row and what the guards actually do today. Data-only. */\nexport class L2NotDone {\n constructor(readonly row: number, readonly gap: string, readonly why: string) {}\n}\n\n/**\n * WHERE THE TABLE AND THE CODE DISAGREE, stated rather than papered over.\n *\n * The L1 precedent is `## Not done — \\`o\\` is not exempt yet`: a row the runner cannot reach, named in\n * the generated doc with the reason it has not shipped. The same treatment applies here, and it is what\n * makes it safe to publish a table the guards do not yet dispatch from — a reader is told exactly which\n * rows describe intent rather than behaviour, and the log's `row=` stamps land on row 11 for every one\n * of these, so the trail never claims the strict row fired.\n */\nexport const NOT_DONE: readonly L2NotDone[] = [\n new L2NotDone(5,\n 'Row 5 blocks `B` as well as `E` on `main`. Only `E` is blocked today: `feature-branch-guard` blocks the write, while `stale-main-bash-guard` blocks only CONTENT-READING Bash, and only once the cache proves `main` is behind.',\n 'Bash on a CURRENT `main` is harmless, and the strict form needs the skip list (row 4) to be complete first — a `B`-on-main block with an incomplete skip list wedges the session on its own cure.'),\n new L2NotDone(8,\n 'Row 8 blocks reads on a merged branch even when the tree is DIRTY. The code opens a dirty valve and fails open (`dirty-merged-branch`, logged at row 11).',\n 'The row is the ORIGINAL documented design — `git checkout -b <new> origin/main` carries uncommitted changes onto the fresh branch, so nothing is trapped — and `read-stale-guard`\\'s own class comment still states it. The code drifted, and closing the valve is a behaviour change that belongs in its own PR with its own evidence, not in a config collapse.'),\n new L2NotDone(6,\n 'Row 6 blocks reads on a stale `main` even when the tree is DIRTY. The code opens a dirty valve (`dirty-tree-on-main`, logged at row 11).',\n 'This is the one place the dirty argument has teeth: the cure is `git pull`, which genuinely is not a clean fast-forward on a dirty tree. `git stash` is on the skip list and clears it, so the strict form is reachable — but it is the same behaviour change, and the same separate PR.'),\n];\n"]}
|
package/src/core/load-rules.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { WebpiecesRulesConfig, MatchRuleConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { Rule } from './types';
|
|
3
|
-
|
|
3
|
+
/** The two gated-command strings guards print, resolved from `commands.guardHints`. Data-only. */
|
|
4
|
+
export declare class GuardHintCommands {
|
|
5
|
+
readonly upsertPr: string;
|
|
6
|
+
readonly mergeComplete: string;
|
|
7
|
+
constructor(upsertPr: string, mergeComplete: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string, guardHints: GuardHintCommands): readonly Rule[];
|
|
4
10
|
/**
|
|
5
11
|
* The EXPERIMENTAL bash guards: rules that have NO webpieces.config.json entry, are switched only from
|
|
6
12
|
* the optional machine-local `~/.webpieces/config.json`, and are therefore deliberately kept out of
|
|
7
|
-
* `
|
|
13
|
+
* `builtInConfigKeys`/`BUILT_IN_RULE_MAP` — so the config-sync check (fault Y, "every built-in rule needs
|
|
8
14
|
* an entry, or every Bash call is blocked") can never see them. That containment is the whole point:
|
|
9
15
|
* whole-repo-build-guard shipped inside the config-driven set once and took every upgrading consumer's
|
|
10
16
|
* shell down with it.
|
package/src/core/load-rules.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GuardHintCommands = void 0;
|
|
3
4
|
exports.loadRules = loadRules;
|
|
4
5
|
exports.loadExperimentalBashRules = loadExperimentalBashRules;
|
|
5
6
|
exports.loadMatchRules = loadMatchRules;
|
|
@@ -38,29 +39,45 @@ const whole_repo_build_guard_1 = require("./rules/whole-repo-build-guard");
|
|
|
38
39
|
const match_rule_1 = require("./rules/match-rule");
|
|
39
40
|
const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
|
|
40
41
|
const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
|
|
42
|
+
/** The two gated-command strings guards print, resolved from `commands.guardHints`. Data-only. */
|
|
43
|
+
class GuardHintCommands {
|
|
44
|
+
upsertPr;
|
|
45
|
+
mergeComplete;
|
|
46
|
+
constructor(upsertPr, mergeComplete) {
|
|
47
|
+
this.upsertPr = upsertPr;
|
|
48
|
+
this.mergeComplete = mergeComplete;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.GuardHintCommands = GuardHintCommands;
|
|
41
52
|
const BUILT_IN_RULE_MAP = {
|
|
42
|
-
'no-any-unknown': (c) => new no_any_unknown_1.NoAnyUnknownRule(c),
|
|
43
|
-
'no-implicit-any': (c) => new no_implicit_any_1.NoImplicitAnyRule(c),
|
|
44
|
-
'max-file-lines': (c) => new max_file_lines_1.MaxFileLinesRule(c),
|
|
45
|
-
'validate-ts-in-src': (c) => new validate_ts_in_src_1.ValidateTsInSrcRule(c),
|
|
46
|
-
'no-destructure': (c) => new no_destructure_1.NoDestructureRule(c),
|
|
47
|
-
'require-return-type': (c) => new require_return_type_1.RequireReturnTypeRule(c),
|
|
48
|
-
'no-unmanaged-exceptions': (c) => new no_unmanaged_exceptions_1.NoUnmanagedExceptionsRule(c),
|
|
49
|
-
'catch-error-pattern': (c) => new catch_error_pattern_1.CatchErrorPatternRule(c),
|
|
50
|
-
'throw-cause-required': (c) => new throw_cause_required_1.ThrowCauseRequiredRule(c),
|
|
51
|
-
'no-symbol-di-tokens': (c) => new no_symbol_di_tokens_1.NoSymbolDiTokensRule(c),
|
|
52
|
-
'no-custom-css': (c) => new no_custom_css_1.NoCustomCssRule(c),
|
|
53
|
-
'no-process-exit-outside-main': (c) => new no_process_exit_outside_main_1.NoProcessExitOutsideMainRule(c),
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
'no-any-unknown': (c) => [new no_any_unknown_1.NoAnyUnknownRule(c)],
|
|
54
|
+
'no-implicit-any': (c) => [new no_implicit_any_1.NoImplicitAnyRule(c)],
|
|
55
|
+
'max-file-lines': (c) => [new max_file_lines_1.MaxFileLinesRule(c)],
|
|
56
|
+
'validate-ts-in-src': (c) => [new validate_ts_in_src_1.ValidateTsInSrcRule(c)],
|
|
57
|
+
'no-destructure': (c) => [new no_destructure_1.NoDestructureRule(c)],
|
|
58
|
+
'require-return-type': (c) => [new require_return_type_1.RequireReturnTypeRule(c)],
|
|
59
|
+
'no-unmanaged-exceptions': (c) => [new no_unmanaged_exceptions_1.NoUnmanagedExceptionsRule(c)],
|
|
60
|
+
'catch-error-pattern': (c) => [new catch_error_pattern_1.CatchErrorPatternRule(c)],
|
|
61
|
+
'throw-cause-required': (c) => [new throw_cause_required_1.ThrowCauseRequiredRule(c)],
|
|
62
|
+
'no-symbol-di-tokens': (c) => [new no_symbol_di_tokens_1.NoSymbolDiTokensRule(c)],
|
|
63
|
+
'no-custom-css': (c) => [new no_custom_css_1.NoCustomCssRule(c)],
|
|
64
|
+
'no-process-exit-outside-main': (c) => [new no_process_exit_outside_main_1.NoProcessExitOutsideMainRule(c)],
|
|
65
|
+
'no-js-files': (c) => [new no_js_files_1.NoJsFilesRule(c)],
|
|
66
|
+
'branch-creation-guard': (c) => [new branch_creation_guard_1.BranchCreationGuardRule(c)],
|
|
67
|
+
// THE TWO COLLAPSED POLICIES. Order inside each array is the order the rules run in, and it is the
|
|
68
|
+
// same order the previous per-key registry produced.
|
|
69
|
+
'pr-lifecycle-guard': (c, hints) => [
|
|
70
|
+
new pr_creation_or_push_guard_1.PrCreationOrPushGuardRule(c, hints.upsertPr),
|
|
71
|
+
new merge_in_progress_guard_1.MergeInProgressGuardRule(c, hints.mergeComplete),
|
|
72
|
+
new pr_merge_guard_1.PrMergeGuardRule(c),
|
|
73
|
+
new redirect_how_to_merge_main_1.RedirectHowToMergeMainRule(c),
|
|
74
|
+
],
|
|
75
|
+
'branch-state-guard': (c) => [
|
|
76
|
+
new feature_branch_guard_1.FeatureBranchGuardRule(c),
|
|
77
|
+
new read_stale_guard_1.ReadStaleGuardRule(c),
|
|
78
|
+
new merged_branch_bash_guard_1.MergedBranchBashGuardRule(c),
|
|
79
|
+
new stale_main_bash_guard_1.StaleMainBashGuardRule(c),
|
|
80
|
+
],
|
|
64
81
|
};
|
|
65
82
|
// Index the typed config by rule name. Each value is the rule's *Config (a plain object from
|
|
66
83
|
// JSON), or undefined when the rule has no entry yet (the sync check reports those).
|
|
@@ -68,15 +85,16 @@ function asConfigMap(config) {
|
|
|
68
85
|
// webpieces-disable no-any-unknown -- index the typed config by dynamic rule name
|
|
69
86
|
return config;
|
|
70
87
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
88
|
+
// webpieces-disable no-function-outside-class -- the module's entry point, beside loadMatchRules/loadExperimentalBashRules; this whole loader is module-scope functions and a lone class for one of them would break the file's shape
|
|
89
|
+
function loadRules(config, workspaceRoot, guardHints) {
|
|
90
|
+
const builtIns = loadBuiltInRules(config, guardHints);
|
|
73
91
|
const custom = loadCustomRules(config, workspaceRoot);
|
|
74
92
|
return [...builtIns, ...custom];
|
|
75
93
|
}
|
|
76
94
|
/**
|
|
77
95
|
* The EXPERIMENTAL bash guards: rules that have NO webpieces.config.json entry, are switched only from
|
|
78
96
|
* the optional machine-local `~/.webpieces/config.json`, and are therefore deliberately kept out of
|
|
79
|
-
* `
|
|
97
|
+
* `builtInConfigKeys`/`BUILT_IN_RULE_MAP` — so the config-sync check (fault Y, "every built-in rule needs
|
|
80
98
|
* an entry, or every Bash call is blocked") can never see them. That containment is the whole point:
|
|
81
99
|
* whole-repo-build-guard shipped inside the config-driven set once and took every upgrading consumer's
|
|
82
100
|
* shell down with it.
|
|
@@ -95,17 +113,19 @@ function loadExperimentalBashRules(affectedBuildCommand) {
|
|
|
95
113
|
function loadMatchRules(matchRules) {
|
|
96
114
|
return matchRules.map((c) => new match_rule_1.MatchRule(c));
|
|
97
115
|
}
|
|
98
|
-
|
|
116
|
+
// Iterates CONFIG KEYS, not rule names — one entry can yield several rules (see BUILT_IN_RULE_MAP).
|
|
117
|
+
// webpieces-disable no-function-outside-class -- the body of loadRules above, in the same module of loader functions
|
|
118
|
+
function loadBuiltInRules(config, guardHints) {
|
|
99
119
|
const map = asConfigMap(config);
|
|
100
120
|
const rules = [];
|
|
101
|
-
for (const
|
|
102
|
-
const factory = BUILT_IN_RULE_MAP[
|
|
121
|
+
for (const configKey of index_1.builtInConfigKeys) {
|
|
122
|
+
const factory = BUILT_IN_RULE_MAP[configKey];
|
|
103
123
|
if (!factory) {
|
|
104
|
-
process.stderr.write(`[ai-hooks] unknown built-in
|
|
124
|
+
process.stderr.write(`[ai-hooks] unknown built-in config key: ${configKey}\n`);
|
|
105
125
|
continue;
|
|
106
126
|
}
|
|
107
|
-
const ruleConfig = map[
|
|
108
|
-
rules.push(factory(ruleConfig));
|
|
127
|
+
const ruleConfig = map[configKey] ?? new rule_base_1.EmptyRuleConfig();
|
|
128
|
+
rules.push(...factory(ruleConfig, guardHints));
|
|
109
129
|
}
|
|
110
130
|
return rules;
|
|
111
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, BranchCreationGuardConfig, PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig,\n NoJsFilesConfig, FeatureBranchGuardConfig, ReadStaleGuardConfig, MergedBranchBashGuardConfig, StaleMainBashGuardConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationOrPushGuardRule } from './rules/pr-creation-or-push-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { ReadStaleGuardRule } from './rules/read-stale-guard';\nimport { MergedBranchBashGuardRule } from './rules/merged-branch-bash-guard';\nimport { StaleMainBashGuardRule } from './rules/stale-main-bash-guard';\nimport { WholeRepoBuildGuardRule } from './rules/whole-repo-build-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'no-custom-css': (c: BaseRuleConfig) => new NoCustomCssRule(c as NoCustomCssConfig),\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => new NoProcessExitOutsideMainRule(c as NoProcessExitOutsideMainConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-or-push-guard': (c: BaseRuleConfig) => new PrCreationOrPushGuardRule(c as PrCreationOrPushGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-guard': (c: BaseRuleConfig) => new PrMergeGuardRule(c as PrMergeGuardConfig),\n 'redirect-how-to-merge-main': (c: BaseRuleConfig) => new RedirectHowToMergeMainRule(c as RedirectHowToMergeMainConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'feature-branch-guard': (c: BaseRuleConfig) => new FeatureBranchGuardRule(c as FeatureBranchGuardConfig),\n 'read-stale-guard': (c: BaseRuleConfig) => new ReadStaleGuardRule(c as ReadStaleGuardConfig),\n 'merged-branch-bash-guard': (c: BaseRuleConfig) => new MergedBranchBashGuardRule(c as MergedBranchBashGuardConfig),\n 'stale-main-bash-guard': (c: BaseRuleConfig) => new StaleMainBashGuardRule(c as StaleMainBashGuardConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n/**\n * The EXPERIMENTAL bash guards: rules that have NO webpieces.config.json entry, are switched only from\n * the optional machine-local `~/.webpieces/config.json`, and are therefore deliberately kept out of\n * `builtInRuleNames`/`BUILT_IN_RULE_MAP` — so the config-sync check (fault Y, \"every built-in rule needs\n * an entry, or every Bash call is blocked\") can never see them. That containment is the whole point:\n * whole-repo-build-guard shipped inside the config-driven set once and took every upgrading consumer's\n * shell down with it.\n *\n * Each rule here decides for itself, from the home config, whether it does anything at all.\n * `affectedBuildCommand` is the project's gate command, passed through so a refusal quotes what THIS\n * repo's gate actually runs.\n */\n// webpieces-disable no-function-outside-class -- sibling of loadRules/loadMatchRules in this module; the whole loader is module-scope functions and a lone class for this one would break the file's shape\nexport function loadExperimentalBashRules(affectedBuildCommand: string): Rule[] {\n return [new WholeRepoBuildGuardRule(affectedBuildCommand)];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, BranchCreationGuardConfig,\n PrLifecycleGuardConfig, BranchStateGuardConfig,\n NoJsFilesConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInConfigKeys } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationOrPushGuardRule } from './rules/pr-creation-or-push-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { ReadStaleGuardRule } from './rules/read-stale-guard';\nimport { MergedBranchBashGuardRule } from './rules/merged-branch-bash-guard';\nimport { StaleMainBashGuardRule } from './rules/stale-main-bash-guard';\nimport { WholeRepoBuildGuardRule } from './rules/whole-repo-build-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n/**\n * ONE CONFIG KEY → N RULES.\n *\n * Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json). The\n * config arrives as a plain object structurally typed as the *Config class, so the `as` narrows the\n * shared BaseRuleConfig param back to the concrete config the rule consumes.\n *\n * The map is keyed by CONFIG KEY and each factory returns an ARRAY, because a hookGuards key names a\n * POLICY and a policy may be implemented by several classes: `branch-state-guard` builds all four\n * branch-state guards from one entry, `pr-lifecycle-guard` all four PR-lifecycle guards. It used to be\n * `Record<string, (c) => Rule>` — one factory per key — which is precisely why four classes could not\n * share a key and why the config had to carry nine switches for three decisions.\n *\n * `guardHints` are the resolved `commands.guardHints` strings, handed to the two rules that print a\n * gated command. They arrive as a constructor argument rather than a config field, so there is exactly\n * one spelling of each command in the config (see PrLifecycleGuardConfig).\n */\ntype RuleFactory = (config: BaseRuleConfig, guardHints: GuardHintCommands) => readonly Rule[];\n\n/** The two gated-command strings guards print, resolved from `commands.guardHints`. Data-only. */\nexport class GuardHintCommands {\n constructor(readonly upsertPr: string, readonly mergeComplete: string) {}\n}\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => [new NoAnyUnknownRule(c as NoAnyUnknownConfig)],\n 'no-implicit-any': (c: BaseRuleConfig) => [new NoImplicitAnyRule(c as NoImplicitAnyConfig)],\n 'max-file-lines': (c: BaseRuleConfig) => [new MaxFileLinesRule(c as MaxFileLinesConfig)],\n 'validate-ts-in-src': (c: BaseRuleConfig) => [new ValidateTsInSrcRule(c as ValidateTsInSrcConfig)],\n 'no-destructure': (c: BaseRuleConfig) => [new NoDestructureRule(c as NoDestructureConfig)],\n 'require-return-type': (c: BaseRuleConfig) => [new RequireReturnTypeRule(c as RequireReturnTypeConfig)],\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => [new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig)],\n 'catch-error-pattern': (c: BaseRuleConfig) => [new CatchErrorPatternRule(c as CatchErrorPatternConfig)],\n 'throw-cause-required': (c: BaseRuleConfig) => [new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig)],\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => [new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig)],\n 'no-custom-css': (c: BaseRuleConfig) => [new NoCustomCssRule(c as NoCustomCssConfig)],\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => [new NoProcessExitOutsideMainRule(c as NoProcessExitOutsideMainConfig)],\n 'no-js-files': (c: BaseRuleConfig) => [new NoJsFilesRule(c as NoJsFilesConfig)],\n 'branch-creation-guard': (c: BaseRuleConfig) => [new BranchCreationGuardRule(c as BranchCreationGuardConfig)],\n // THE TWO COLLAPSED POLICIES. Order inside each array is the order the rules run in, and it is the\n // same order the previous per-key registry produced.\n 'pr-lifecycle-guard': (c: BaseRuleConfig, hints: GuardHintCommands) => [\n new PrCreationOrPushGuardRule(c as PrLifecycleGuardConfig, hints.upsertPr),\n new MergeInProgressGuardRule(c as PrLifecycleGuardConfig, hints.mergeComplete),\n new PrMergeGuardRule(c as PrLifecycleGuardConfig),\n new RedirectHowToMergeMainRule(c as PrLifecycleGuardConfig),\n ],\n 'branch-state-guard': (c: BaseRuleConfig) => [\n new FeatureBranchGuardRule(c as BranchStateGuardConfig),\n new ReadStaleGuardRule(c as BranchStateGuardConfig),\n new MergedBranchBashGuardRule(c as BranchStateGuardConfig),\n new StaleMainBashGuardRule(c as BranchStateGuardConfig),\n ],\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\n// webpieces-disable no-function-outside-class -- the module's entry point, beside loadMatchRules/loadExperimentalBashRules; this whole loader is module-scope functions and a lone class for one of them would break the file's shape\nexport function loadRules(\n config: WebpiecesRulesConfig,\n workspaceRoot: string,\n guardHints: GuardHintCommands,\n): readonly Rule[] {\n const builtIns = loadBuiltInRules(config, guardHints);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n/**\n * The EXPERIMENTAL bash guards: rules that have NO webpieces.config.json entry, are switched only from\n * the optional machine-local `~/.webpieces/config.json`, and are therefore deliberately kept out of\n * `builtInConfigKeys`/`BUILT_IN_RULE_MAP` — so the config-sync check (fault Y, \"every built-in rule needs\n * an entry, or every Bash call is blocked\") can never see them. That containment is the whole point:\n * whole-repo-build-guard shipped inside the config-driven set once and took every upgrading consumer's\n * shell down with it.\n *\n * Each rule here decides for itself, from the home config, whether it does anything at all.\n * `affectedBuildCommand` is the project's gate command, passed through so a refusal quotes what THIS\n * repo's gate actually runs.\n */\n// webpieces-disable no-function-outside-class -- sibling of loadRules/loadMatchRules in this module; the whole loader is module-scope functions and a lone class for this one would break the file's shape\nexport function loadExperimentalBashRules(affectedBuildCommand: string): Rule[] {\n return [new WholeRepoBuildGuardRule(affectedBuildCommand)];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\n// Iterates CONFIG KEYS, not rule names — one entry can yield several rules (see BUILT_IN_RULE_MAP).\n// webpieces-disable no-function-outside-class -- the body of loadRules above, in the same module of loader functions\nfunction loadBuiltInRules(config: WebpiecesRulesConfig, guardHints: GuardHintCommands): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const configKey of builtInConfigKeys) {\n const factory = BUILT_IN_RULE_MAP[configKey];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in config key: ${configKey}\\n`);\n continue;\n }\n const ruleConfig = map[configKey] ?? new EmptyRuleConfig();\n rules.push(...factory(ruleConfig, guardHints));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
|
|
@@ -20,6 +20,13 @@ export declare function refresherArgv(refresher: string, workspaceRoot: string,
|
|
|
20
20
|
* /dev/null) so a crash before the child's own logging is still captured. If the async-refresh stream shows
|
|
21
21
|
* SPAWN_ATTEMPT but never START, the detached child was killed before it ran.
|
|
22
22
|
*
|
|
23
|
+
* `hangTimeoutMinutes` IS REQUIRED, deliberately. It used to default to DEFAULT_HANG_TIMEOUT_MINUTES,
|
|
24
|
+
* and that default is the mechanism by which three of the four configured values could never reach a
|
|
25
|
+
* spawn: two callers ran before any guard and simply omitted the argument, and the at-most-once latch
|
|
26
|
+
* below then meant the guard's own call — the one carrying the configured value — was a no-op. An
|
|
27
|
+
* omitted argument that silently means "the default" is unreadable at the call site and impossible to
|
|
28
|
+
* grep. Required makes every caller say which value it is passing, and makes forgetting a compile error.
|
|
29
|
+
*
|
|
23
30
|
* THE IDENTITY GOES ON ARGV, and that is what makes the sentence above true. The child is a separate
|
|
24
31
|
* node process whose `logStream` starts unidentified, so it used to write every line to the shared
|
|
25
32
|
* `unknown-coordinator-hook.log` while this parent wrote SPAWN_ATTEMPT to its own
|
|
@@ -29,4 +36,4 @@ export declare function refresherArgv(refresher: string, workspaceRoot: string,
|
|
|
29
36
|
* session/agent/hook through (positional argv, alongside the root and timeout the child already
|
|
30
37
|
* takes) puts the whole cycle back in ONE file.
|
|
31
38
|
*/
|
|
32
|
-
export declare function triggerMainSyncRefresh(workspaceRoot: string, hangTimeoutMinutes
|
|
39
|
+
export declare function triggerMainSyncRefresh(workspaceRoot: string, hangTimeoutMinutes: number): void;
|
|
@@ -7,7 +7,6 @@ const tslib_1 = require("tslib");
|
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const fs = tslib_1.__importStar(require("fs"));
|
|
9
9
|
const path = tslib_1.__importStar(require("path"));
|
|
10
|
-
const rules_config_1 = require("@webpieces/rules-config");
|
|
11
10
|
const to_error_1 = require("./to-error");
|
|
12
11
|
const main_sync_log_1 = require("./main-sync-log");
|
|
13
12
|
const log_stream_1 = require("./log-stream");
|
|
@@ -41,6 +40,13 @@ function refresherArgv(refresher, workspaceRoot, hangTimeoutMinutes, identity) {
|
|
|
41
40
|
* /dev/null) so a crash before the child's own logging is still captured. If the async-refresh stream shows
|
|
42
41
|
* SPAWN_ATTEMPT but never START, the detached child was killed before it ran.
|
|
43
42
|
*
|
|
43
|
+
* `hangTimeoutMinutes` IS REQUIRED, deliberately. It used to default to DEFAULT_HANG_TIMEOUT_MINUTES,
|
|
44
|
+
* and that default is the mechanism by which three of the four configured values could never reach a
|
|
45
|
+
* spawn: two callers ran before any guard and simply omitted the argument, and the at-most-once latch
|
|
46
|
+
* below then meant the guard's own call — the one carrying the configured value — was a no-op. An
|
|
47
|
+
* omitted argument that silently means "the default" is unreadable at the call site and impossible to
|
|
48
|
+
* grep. Required makes every caller say which value it is passing, and makes forgetting a compile error.
|
|
49
|
+
*
|
|
44
50
|
* THE IDENTITY GOES ON ARGV, and that is what makes the sentence above true. The child is a separate
|
|
45
51
|
* node process whose `logStream` starts unidentified, so it used to write every line to the shared
|
|
46
52
|
* `unknown-coordinator-hook.log` while this parent wrote SPAWN_ATTEMPT to its own
|
|
@@ -50,7 +56,8 @@ function refresherArgv(refresher, workspaceRoot, hangTimeoutMinutes, identity) {
|
|
|
50
56
|
* session/agent/hook through (positional argv, alongside the root and timeout the child already
|
|
51
57
|
* takes) puts the whole cycle back in ONE file.
|
|
52
58
|
*/
|
|
53
|
-
function
|
|
59
|
+
// webpieces-disable no-function-outside-class -- the module's one entry point, beside refresherArgv; a detached fire-and-forget spawn has no state to inject and must stay callable from a tree too broken to build a DI container
|
|
60
|
+
function triggerMainSyncRefresh(workspaceRoot, hangTimeoutMinutes) {
|
|
54
61
|
// ONE refresher per hook process. Several call sites fire this on a single tool call — the Read
|
|
55
62
|
// fast path in hook-core AND read-stale-guard's own check(), for one — which is why the log showed
|
|
56
63
|
// two SPAWN_ATTEMPTs ~20ms apart from the same pid on every cycle. The loser only ever reached the
|