@levr-one/cli 0.11.10 → 0.12.1

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.
Files changed (26) hide show
  1. package/dist/{addHandler-DR6q5U_R.js → addHandler-QVX0ltyN.js} +27 -15
  2. package/dist/{appendHandler-KVsJMxpX.js → appendHandler-DtxLpPu6.js} +9 -9
  3. package/dist/{artifact-upload-BzLxue32.js → artifact-upload-BvBSevIx.js} +2 -2
  4. package/dist/{attachHandler-DQpK_pVa.js → attachHandler-DXZi2ILT.js} +9 -9
  5. package/dist/cli.js +38 -22
  6. package/dist/{connect-BgQmrL_0.js → connect-DVjgyfls.js} +3 -3
  7. package/dist/{currentHandler-u89l068v.js → currentHandler-QhCywr3S.js} +1 -1
  8. package/dist/{find-workspace-T4xuXwtS.js → find-workspace-Bl4rfr73.js} +1 -1
  9. package/dist/{importHandler-zw5GKb-S.js → importHandler-uFdalKtw.js} +6 -6
  10. package/dist/inspect-B8tG9g0q.js +1122 -0
  11. package/dist/inspect-BzBqSBO3.js +3 -0
  12. package/dist/{listHandler-YA-0RkwS.js → listHandler-BW92JLCl.js} +5 -5
  13. package/dist/{loginHandler-DE0L1ehF.js → loginHandler-C9Oi92IY.js} +15 -11
  14. package/dist/{logoutHandler-S4TFYFwz.js → logoutHandler-Cf5aI1wq.js} +2 -2
  15. package/dist/{pushHandler-DdNyVQ5t.js → pushHandler-CXFZt3SO.js} +8 -8
  16. package/dist/{resolve-token-7xb7kG7h.js → resolve-token-ByZq05Us.js} +2 -2
  17. package/dist/{resolve-workspace-CStHisUb.js → resolve-workspace-BbK3uYQc.js} +2 -2
  18. package/dist/{runHandler-BFDjtxbt.js → runHandler-itaAVn4U.js} +215 -25
  19. package/dist/{sdk-client-DYv-0CfA.js → sdk-client-B4FMd_Fa.js} +1 -1
  20. package/dist/{selectHandler-Cejme5nY.js → selectHandler-D6cvq60S.js} +5 -5
  21. package/dist/{statusHandler-BNiWlC7e.js → statusHandler-BQXn93-r.js} +3 -3
  22. package/dist/{token-refresh-DeusLK8H.js → token-refresh-CJB7Qlxy.js} +1 -1
  23. package/package.json +1 -1
  24. /package/dist/{env-CdwyPHGV.js → env-CvjMSRd7.js} +0 -0
  25. /package/dist/{sleep-BpJ39Ypm.js → sleep-CGBHjsj5.js} +0 -0
  26. /package/dist/{workspace-store-CnrxyYRB.js → workspace-store-Bmw104Qa.js} +0 -0
@@ -0,0 +1,1122 @@
1
+ import { buildCommand } from "@stricli/core";
2
+ import { z } from "zod";
3
+ import * as fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ //#region ../shared/dist/runs/gate-op.js
7
+ const GATE_OP_CONTRACT_VERSION = 1;
8
+ const REPO_RELATIVE = "path must be repo-relative and must not contain ..";
9
+ /** Repo-relative, no `..` segment, not absolute, not home-relative. */
10
+ function isRepoRelative(value) {
11
+ if (value.length === 0 || value.includes("\0")) return false;
12
+ if (value.startsWith("/") || value.startsWith("~") || /^[A-Za-z]:[\\/]/.test(value)) return false;
13
+ return !value.split(/[\\/]/).includes("..");
14
+ }
15
+ const RepoRelativeCwd = z.string().refine(isRepoRelative, REPO_RELATIVE);
16
+ const RepoRelativePath = z.string().refine(isRepoRelative, REPO_RELATIVE);
17
+ const SandboxProfileSchema = z.object({
18
+ network_hosts: z.array(z.string()),
19
+ ports: z.array(z.number().int().nonnegative()),
20
+ read_paths: z.array(z.string()),
21
+ write_paths: z.array(z.string()),
22
+ env_allow: z.array(z.string()),
23
+ loopback: z.boolean(),
24
+ unsandboxed_on: z.array(z.enum(["linux", "wsl2"])).optional(),
25
+ unsandboxed_reason: z.string().optional()
26
+ });
27
+ const InspectPathParams = z.object({
28
+ path: RepoRelativePath,
29
+ glob: z.boolean().optional()
30
+ }).strict();
31
+ const ContainsParams = z.object({
32
+ path: RepoRelativePath,
33
+ glob: z.boolean().optional(),
34
+ pattern: z.string().optional(),
35
+ keywords: z.array(z.string()).optional(),
36
+ literal: z.boolean().optional()
37
+ }).strict().superRefine((params, ctx) => {
38
+ const hasPattern = params.pattern != null && params.pattern.length > 0;
39
+ const hasKeywords = (params.keywords?.length ?? 0) > 0;
40
+ if (!hasPattern && !hasKeywords) ctx.addIssue({
41
+ code: z.ZodIssueCode.custom,
42
+ message: "contains needs a pattern or keywords"
43
+ });
44
+ });
45
+ const DoneCriteriaParams = z.object({
46
+ path: RepoRelativePath,
47
+ glob: z.boolean().optional(),
48
+ requirements: z.array(z.union([z.string(), z.object({
49
+ pattern: z.string(),
50
+ label: z.string().optional()
51
+ }).strict()])).nonempty()
52
+ }).strict();
53
+ const JsonHasKeyParams = z.object({
54
+ path: RepoRelativePath,
55
+ glob: z.boolean().optional(),
56
+ key: z.string().min(1),
57
+ object: z.string().min(1).optional()
58
+ }).strict();
59
+ const SpecColumnsParams = z.object({
60
+ path: RepoRelativePath,
61
+ glob: z.boolean().optional(),
62
+ required_columns: z.union([z.string().min(1), z.array(z.string()).nonempty()])
63
+ }).strict();
64
+ const ArgvOpSchema = z.object({
65
+ op: z.literal("argv"),
66
+ cwd: RepoRelativeCwd,
67
+ argv: z.array(z.string()).nonempty()
68
+ }).strict();
69
+ const InspectOpSchema = z.discriminatedUnion("check", [
70
+ z.object({
71
+ op: z.literal("inspect"),
72
+ check: z.literal("exists"),
73
+ params: InspectPathParams
74
+ }).strict(),
75
+ z.object({
76
+ op: z.literal("inspect"),
77
+ check: z.literal("not_exists"),
78
+ params: InspectPathParams
79
+ }).strict(),
80
+ z.object({
81
+ op: z.literal("inspect"),
82
+ check: z.literal("contains"),
83
+ params: ContainsParams
84
+ }).strict(),
85
+ z.object({
86
+ op: z.literal("inspect"),
87
+ check: z.literal("not_contains"),
88
+ params: ContainsParams
89
+ }).strict(),
90
+ z.object({
91
+ op: z.literal("inspect"),
92
+ check: z.literal("done_criteria"),
93
+ params: DoneCriteriaParams
94
+ }).strict(),
95
+ z.object({
96
+ op: z.literal("inspect"),
97
+ check: z.literal("json_has_key"),
98
+ params: JsonHasKeyParams
99
+ }).strict(),
100
+ z.object({
101
+ op: z.literal("inspect"),
102
+ check: z.literal("spec_columns"),
103
+ params: SpecColumnsParams
104
+ }).strict(),
105
+ z.object({
106
+ op: z.literal("inspect"),
107
+ check: z.literal("migration_down_scope"),
108
+ params: InspectPathParams
109
+ }).strict()
110
+ ]);
111
+ const JudgeOpSchema = z.object({
112
+ op: z.literal("judge"),
113
+ path: RepoRelativePath,
114
+ rubric: z.string(),
115
+ output_schema: z.string(),
116
+ judge_prompt: z.string(),
117
+ model: z.string().min(1),
118
+ runner: z.literal("agy"),
119
+ max_input_bytes: z.number().int().positive()
120
+ }).strict();
121
+ const GateOpSchema = z.union([
122
+ ArgvOpSchema,
123
+ InspectOpSchema,
124
+ JudgeOpSchema
125
+ ]);
126
+ const AuthoringSourceSchema = z.object({
127
+ kind: z.enum(["binding", "definition"]),
128
+ id: z.string().uuid(),
129
+ workspace_id: z.string().uuid()
130
+ }).strict();
131
+ /**
132
+ * fail_closed: both `op` and `legacy_command`, or neither, is refused.
133
+ * A legacy carrier also keeps its authored `legacy_template` when the
134
+ * converter could not turn the template into argv.
135
+ */
136
+ const GateOpEnvelopeSchema = z.object({
137
+ contract_version: z.literal(GATE_OP_CONTRACT_VERSION),
138
+ op: GateOpSchema.optional(),
139
+ legacy_command: z.string().min(1).optional(),
140
+ legacy_template: z.string().optional(),
141
+ link_id: z.string().uuid(),
142
+ step_id: z.string().optional(),
143
+ rule_hash: z.string().optional(),
144
+ expected: z.string(),
145
+ provenance: z.enum([
146
+ "seeded",
147
+ "binding",
148
+ "workspace_template",
149
+ "legacy",
150
+ "replicated"
151
+ ]),
152
+ authored_digest: z.string().min(1),
153
+ content_digest: z.string().min(1),
154
+ authoring_source: AuthoringSourceSchema.optional(),
155
+ approval: z.enum([
156
+ "server_approved",
157
+ "confirm",
158
+ "denied"
159
+ ]),
160
+ approval_basis: z.enum([
161
+ "kind",
162
+ "default_list",
163
+ "workspace_list"
164
+ ]).optional(),
165
+ matched_pattern: z.string().optional(),
166
+ suggested_pattern: z.string().optional(),
167
+ sandbox: z.object({
168
+ mode: z.enum(["on", "off-by-gate"]),
169
+ profile: SandboxProfileSchema
170
+ }).strict(),
171
+ untested: z.boolean().optional(),
172
+ display: z.object({
173
+ posix: z.string(),
174
+ powershell: z.string()
175
+ }).strict()
176
+ }).strict().superRefine((value, ctx) => {
177
+ if (value.op !== void 0 === (value.legacy_command !== void 0)) ctx.addIssue({
178
+ code: z.ZodIssueCode.custom,
179
+ message: "fail_closed: envelope must carry exactly one of op and legacy_command"
180
+ });
181
+ });
182
+
183
+ //#endregion
184
+ //#region src/commands/gates/migration-down-scope.ts
185
+ /**
186
+ * The `migration_revert_scope` check — ENG-5106 L3a.
187
+ *
188
+ * For every path given: find the migration's `down()` and report each
189
+ * statement in it that empties a table — a `DELETE FROM` with no `WHERE`, a
190
+ * `TRUNCATE` statement, `queryRunner.clearTable()`, or TypeORM's own:
191
+ * `.clear(X)` on an entity manager, `.clear()` on a repository (one got
192
+ * from `getRepository`, or named `…repo` / `…repository`), `.delete({})` /
193
+ * `manager.delete(X, {})`, or a query-builder `.delete()` with no
194
+ * `.where` in its chain. Logs one line per path and returns the exit code
195
+ * (0 = nothing unscoped, 1 = anything else).
196
+ *
197
+ * It reads the source the way JavaScript does, not as raw text:
198
+ * - EVERY `down` with a body is read (class method, arrow or function
199
+ * property, `down: …`), not just the first, and so is every same-file
200
+ * function, method or arrow — braced or expression-bodied, `q => …`
201
+ * included — whose NAME a body mentions, transitively, however it is
202
+ * reached: `this.wipe(q)`, `M.wipe(q)`, `H.wipe(q)`, `run(wipe(q))`,
203
+ * `ids.forEach(wipe)`. Reading too much can only fail a file, never pass
204
+ * one.
205
+ * - Bodies are found by matching braces, skipping strings, template
206
+ * literals, comments and regex literals, so a `}` inside a SQL string (a
207
+ * JSON value, say) cannot end one early, and indentation does not matter.
208
+ * A `/` starts a regex after an operator, after a keyword such as
209
+ * `return`, `in` or `void`, and after the `)` of an `if`/`while`/`for`
210
+ * head; after `++`/`--` or any other `)` it is division.
211
+ * - SQL is read only from string and template literals inside those bodies,
212
+ * with their JS escapes (`\n`, `\'`, `\x57`, `\u0020`) applied, so the
213
+ * JS `delete` operator is never mistaken for SQL and a `where` in code cannot scope a DELETE.
214
+ * Within a literal the SQL is scanned left to right: quoted values and
215
+ * identifiers ('…' with '' escapes, E'…', "…") and comments (--, block)
216
+ * are opaque, a dollar- or quote-delimited PL/pgSQL body ($$…$$,
217
+ * $fn$…$fn$, DO '…') — and the string a PL/pgSQL
218
+ * `EXECUTE` / `EXECUTE format(` runs — is checked as SQL of its own (a
219
+ * TRUNCATE there may follow BEGIN / THEN / ELSE / LOOP / DO), and
220
+ * a DELETE is judged at its own bracket depth up to its own `;` or
221
+ * closing `)`, so a CTE's DELETE never borrows another statement's WHERE
222
+ * and a subquery's WHERE never scopes the outer DELETE. Literals joined by
223
+ * `+` (directly or around one identifier) are read as one.
224
+ * - A DELETE whose table is not a literal (`'DELETE FROM ' + t`, a template
225
+ * `${t}`) is reported as `(dynamic)` rather than crashing.
226
+ *
227
+ * Known limits, which fail OPEN (ENG-6316): SQL held in a constant outside
228
+ * the bodies read; a helper reached only through an alias
229
+ * (`const c = wipe`) or a factory; and PL/pgSQL `EXECUTE` of a variable,
230
+ * a parenthesised string or an `E'…'` string.
231
+ *
232
+ * WHY IT IS ONE SELF-CONTAINED FUNCTION: the seeded gate template inlines its
233
+ * compiled source into `node -e` (see `inlineNodeCheck`), so the gate runs in
234
+ * any repository with Node and needs no file from this one (ENG-6290 is what
235
+ * a repo-local runner costs). It may therefore use nothing from outside its
236
+ * own body, and no line comments inside it (the source is joined onto one
237
+ * line); `inlineNodeCheck` refuses the latter at module load.
238
+ */
239
+ function migrationRevertScopeCheck(fs$1, paths, log) {
240
+ const SQ = 39;
241
+ const DQ = 34;
242
+ const BT = 96;
243
+ const BS = 92;
244
+ const SL = 47;
245
+ const ST = 42;
246
+ const NL = 10;
247
+ const DOLLAR = 36;
248
+ const OB = 123;
249
+ const CB = 125;
250
+ const NOT_A_HELPER = /^(break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|let|new|return|super|switch|this|throw|try|typeof|var|void|while|with|yield|async|await|of|static|get|set|constructor|require|down)$/;
251
+ function unescape(raw) {
252
+ let text = "";
253
+ for (let i = 0; i < raw.length; i++) if (raw.charCodeAt(i) === BS && i + 1 < raw.length) {
254
+ const n = raw[i + 1];
255
+ const hex = n === "x" ? /^[0-9a-fA-F][0-9a-fA-F]/.exec(raw.slice(i + 2)) : n === "u" && raw.charCodeAt(i + 2) === OB ? /^.([0-9a-fA-F]+)./.exec(raw.slice(i + 2)) : n === "u" ? /^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/.exec(raw.slice(i + 2)) : null;
256
+ if (hex) {
257
+ text += String.fromCodePoint(parseInt(hex[1] || hex[0], 16));
258
+ i += 1 + hex[0].length;
259
+ } else {
260
+ text += n === "n" ? "\n" : n === "t" ? " " : n === "r" ? "\r" : n;
261
+ i++;
262
+ }
263
+ } else text += raw[i];
264
+ return text;
265
+ }
266
+ function lex(src) {
267
+ const out = src.split("");
268
+ const lits = [];
269
+ const blank = (a, b) => {
270
+ for (let k = a; k < b && k < out.length; k++) if (out[k] !== "\n") out[k] = " ";
271
+ };
272
+ const regexAllowed = (i) => {
273
+ let k = i - 1;
274
+ while (k >= 0 && /\s/.test(src[k])) k--;
275
+ if (k < 0) return true;
276
+ const c = src[k];
277
+ if (/[\w$]/.test(c)) {
278
+ let w = k;
279
+ while (w >= 0 && /[\w$]/.test(src[w])) w--;
280
+ return /^(return|typeof|instanceof|in|of|new|delete|void|throw|case|do|else|yield|await)$/.test(src.slice(w + 1, k + 1));
281
+ }
282
+ if (c === ")") {
283
+ let depth = 0;
284
+ let o = k;
285
+ for (; o >= 0; o--) if (out[o] === ")") depth++;
286
+ else if (out[o] === "(") {
287
+ depth--;
288
+ if (depth === 0) break;
289
+ }
290
+ let w = o - 1;
291
+ while (w >= 0 && /\s/.test(src[w])) w--;
292
+ let s = w;
293
+ while (s >= 0 && /\w/.test(src[s])) s--;
294
+ return /^(if|while|for|with)$/.test(src.slice(s + 1, w + 1));
295
+ }
296
+ if ((c === "+" || c === "-") && src[k - 1] === c) return false;
297
+ return "(,=:[!&|?{};+-*%<>~^".includes(c);
298
+ };
299
+ const quoted = (i, q) => {
300
+ let j = i + 1;
301
+ while (j < src.length && src.charCodeAt(j) !== q && src.charCodeAt(j) !== NL) j += src.charCodeAt(j) === BS ? 2 : 1;
302
+ lits.push({
303
+ start: i,
304
+ end: j + 1,
305
+ text: unescape(src.slice(i + 1, j))
306
+ });
307
+ blank(i, j + 1);
308
+ return j + 1;
309
+ };
310
+ const template = (i) => {
311
+ let j = i + 1;
312
+ let text = "";
313
+ blank(i, i + 1);
314
+ while (j < src.length && src.charCodeAt(j) !== BT) if (src.charCodeAt(j) === BS) {
315
+ const kind = src[j + 1];
316
+ const braced = /^.([0-9a-fA-F]+)./.exec(src.slice(j + 2, j + 12));
317
+ let len = 2;
318
+ if (kind === "x" && /^[0-9a-fA-F][0-9a-fA-F]/.test(src.slice(j + 2, j + 4))) len = 4;
319
+ else if (kind === "u" && src.charCodeAt(j + 2) === OB && braced && braced[0].charCodeAt(braced[0].length - 1) === CB) len = 2 + braced[0].length;
320
+ else if (kind === "u" && /^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/.test(src.slice(j + 2, j + 6))) len = 6;
321
+ text += unescape(src.slice(j, j + len));
322
+ blank(j, j + len);
323
+ j += len;
324
+ } else if (src.charCodeAt(j) === DOLLAR && src.charCodeAt(j + 1) === OB) {
325
+ blank(j, j + 2);
326
+ const close = code(j + 2, true);
327
+ blank(close, close + 1);
328
+ text += " ? ";
329
+ j = close + 1;
330
+ } else {
331
+ text += src[j];
332
+ blank(j, j + 1);
333
+ j++;
334
+ }
335
+ blank(j, j + 1);
336
+ lits.push({
337
+ start: i,
338
+ end: j + 1,
339
+ text
340
+ });
341
+ return j + 1;
342
+ };
343
+ function code(i, stopAtBrace) {
344
+ let depth = 0;
345
+ let j = i;
346
+ while (j < src.length) {
347
+ const c = src.charCodeAt(j);
348
+ const next = src.charCodeAt(j + 1);
349
+ if (c === SL && next === SL) {
350
+ let e = src.indexOf("\n", j);
351
+ if (e < 0) e = src.length;
352
+ blank(j, e);
353
+ j = e;
354
+ } else if (c === SL && next === ST) {
355
+ let e = src.indexOf("*/", j + 2);
356
+ e = e < 0 ? src.length : e + 2;
357
+ blank(j, e);
358
+ j = e;
359
+ } else if (c === SL && regexAllowed(j)) {
360
+ let e = j + 1;
361
+ let inClass = false;
362
+ while (e < src.length && src.charCodeAt(e) !== NL) {
363
+ const d = src.charCodeAt(e);
364
+ if (d === BS) {
365
+ e += 2;
366
+ continue;
367
+ }
368
+ if (d === 91) inClass = true;
369
+ else if (d === 93) inClass = false;
370
+ else if (d === SL && !inClass) break;
371
+ e++;
372
+ }
373
+ blank(j, e + 1);
374
+ j = e + 1;
375
+ } else if (c === SQ || c === DQ) j = quoted(j, c);
376
+ else if (c === BT) j = template(j);
377
+ else {
378
+ if (c === OB) depth++;
379
+ else if (c === CB) {
380
+ if (stopAtBrace && depth === 0) return j;
381
+ depth--;
382
+ }
383
+ j++;
384
+ }
385
+ }
386
+ return j;
387
+ }
388
+ code(0, false);
389
+ lits.sort((a, b) => a.start - b.start);
390
+ return {
391
+ masked: out.join(""),
392
+ lits
393
+ };
394
+ }
395
+ function bodyAfter(m, from) {
396
+ let j = from;
397
+ while (j < m.length && /\s/.test(m[j])) j++;
398
+ if (m[j] === ":") {
399
+ j++;
400
+ let depth$1 = 0;
401
+ let typed = false;
402
+ while (j < m.length) {
403
+ const c = m[j];
404
+ if (depth$1 === 0 && c === "=" && m[j + 1] === ">") break;
405
+ if (depth$1 === 0 && c === ";") return null;
406
+ if (depth$1 === 0 && c === "{" && typed) {
407
+ let k = j - 1;
408
+ while (k >= 0 && /\s/.test(m[k])) k--;
409
+ if (!"|&:<,".includes(m[k])) break;
410
+ }
411
+ if (c === "<" || c === "{" || c === "(" || c === "[") depth$1++;
412
+ else if (c === ">" && m[j - 1] !== "=" || c === "}" || c === ")" || c === "]") depth$1--;
413
+ if (!/\s/.test(c)) typed = true;
414
+ j++;
415
+ }
416
+ }
417
+ if (m[j] === "=" && m[j + 1] === ">") {
418
+ j += 2;
419
+ while (j < m.length && /\s/.test(m[j])) j++;
420
+ if (m.charCodeAt(j) !== OB) {
421
+ let depth$1 = 0;
422
+ let e = j;
423
+ for (; e < m.length; e++) {
424
+ const c = m[e];
425
+ if (c === "(" || c === "[" || c === "{") depth$1++;
426
+ else if (c === ")" || c === "]" || c === "}") {
427
+ if (depth$1 === 0) break;
428
+ depth$1--;
429
+ } else if (depth$1 === 0 && (c === ";" || c === ",")) break;
430
+ }
431
+ return [j - 1, e];
432
+ }
433
+ }
434
+ if (m.charCodeAt(j) !== OB) return null;
435
+ let depth = 0;
436
+ for (let e = j; e < m.length; e++) if (m.charCodeAt(e) === OB) depth++;
437
+ else if (m.charCodeAt(e) === CB) {
438
+ depth--;
439
+ if (depth === 0) return [j, e];
440
+ }
441
+ return null;
442
+ }
443
+ function bodiesOf(m, name) {
444
+ const safe = name.split("$").join("\\$");
445
+ const re = new RegExp("(?:^|[^\\w$.])" + safe + "\\s*(?:=\\s*(?:async\\s*)?(?:function\\s*\\w*\\s*)?|:\\s*(?:async\\s*)?(?:function\\s*\\w*\\s*)?)?\\(", "g");
446
+ const found = [];
447
+ for (let hit = re.exec(m); hit; hit = re.exec(m)) {
448
+ let j = hit.index + hit[0].length;
449
+ let parens = 1;
450
+ while (j < m.length && parens > 0) {
451
+ if (m[j] === "(") parens++;
452
+ else if (m[j] === ")") parens--;
453
+ j++;
454
+ }
455
+ const body = bodyAfter(m, j);
456
+ if (body) found.push(body);
457
+ }
458
+ const bare = new RegExp("(?:^|[^\\w$.])" + safe + "\\s*[=:]\\s*(?:async\\s+)?[A-Za-z_$][\\w$]*\\s*(?==>)", "g");
459
+ for (let hit = bare.exec(m); hit; hit = bare.exec(m)) {
460
+ const body = bodyAfter(m, hit.index + hit[0].length);
461
+ if (body) found.push(body);
462
+ }
463
+ return found;
464
+ }
465
+ function sqlMask(sql, nested, inBlock) {
466
+ let lastString = -1;
467
+ let m = "";
468
+ const depth = [];
469
+ let d = 0;
470
+ const blankTo = (from, to) => {
471
+ for (let k = from; k < to; k++) {
472
+ m += " ";
473
+ depth.push(d);
474
+ }
475
+ };
476
+ let i = 0;
477
+ while (i < sql.length) {
478
+ const c = sql.charCodeAt(i);
479
+ const tag = c === DOLLAR ? /^\$(?:[A-Za-z_]\w*)?\$/.exec(sql.slice(i)) : null;
480
+ if (c === SQ || c === DQ) {
481
+ const eString = c === SQ && i > 0 && /[eE]/.test(sql[i - 1]) && !(i > 1 && /\w/.test(sql[i - 2]));
482
+ let e = i + 1;
483
+ while (e < sql.length) {
484
+ const d$1 = sql.charCodeAt(e);
485
+ if (eString && d$1 === BS) e += 2;
486
+ else if (d$1 === c && sql.charCodeAt(e + 1) === c) e += 2;
487
+ else if (d$1 === c) break;
488
+ else e++;
489
+ }
490
+ const end = Math.min(e + 1, sql.length);
491
+ if (c === SQ && (inBlock || /\b(?:execute\s*(?:format\s*\(\s*)?[eE]?|do\s*(?:language\s+\w+\s*)?)(?![^])/i.test(sql.slice(0, i)))) {
492
+ const body = sql.slice(i + 1, e).split(String.fromCharCode(SQ, SQ)).join(String.fromCharCode(SQ));
493
+ if (lastString >= 0 && /^\s*\|\|\s*[eE]?(?![^])/.test(sql.slice(lastString, i))) nested[nested.length - 1] += body;
494
+ else nested.push(body);
495
+ lastString = end;
496
+ }
497
+ blankTo(i, end);
498
+ i = end;
499
+ } else if (tag) {
500
+ const open = i + tag[0].length;
501
+ const close = sql.indexOf(tag[0], open);
502
+ const end = close < 0 ? sql.length : close + tag[0].length;
503
+ nested.push(sql.slice(open, close < 0 ? sql.length : close));
504
+ blankTo(i, end);
505
+ i = end;
506
+ } else if (c === 45 && sql.charCodeAt(i + 1) === 45) {
507
+ let e = sql.indexOf("\n", i);
508
+ if (e < 0) e = sql.length;
509
+ blankTo(i, e);
510
+ i = e;
511
+ } else if (c === SL && sql.charCodeAt(i + 1) === ST) {
512
+ let e = sql.indexOf("*/", i + 2);
513
+ e = e < 0 ? sql.length : e + 2;
514
+ blankTo(i, e);
515
+ i = e;
516
+ } else {
517
+ if (c === 41 && d > 0) d--;
518
+ m += sql[i];
519
+ depth.push(d);
520
+ if (c === 40) d++;
521
+ i++;
522
+ }
523
+ }
524
+ return {
525
+ m,
526
+ depth
527
+ };
528
+ }
529
+ function unscopedIn(sql, inBlock) {
530
+ const nested = [];
531
+ const { m, depth } = sqlMask(sql, nested, inBlock);
532
+ let found = [];
533
+ const re = inBlock ? /\bdelete\s+from\b|(?:^|[;(]|\b(?:begin|then|else|loop|do)\b)\s*(truncate)\s/gi : /\bdelete\s+from\b|(?:^|[;(])\s*(truncate)\s/gi;
534
+ for (let hit = re.exec(m); hit; hit = re.exec(m)) {
535
+ const isTruncate = hit[1] !== void 0;
536
+ if (!isTruncate) {
537
+ const level = depth[hit.index];
538
+ let scope = "";
539
+ for (let e = hit.index + hit[0].length; e < m.length; e++) {
540
+ if (depth[e] < level || m[e] === ";" && depth[e] === level) break;
541
+ scope += depth[e] === level ? m[e] : " ";
542
+ }
543
+ if (/\bwhere\b/i.test(scope)) continue;
544
+ }
545
+ const name = (isTruncate ? /^\s*(?:table\s+)?(?:only\s+)?((?:"[^"]*"|[^\s,();"]+)+)/i : /^\s*(?:only\s+)?((?:"[^"]*"|[^\s,();"]+)+)/i).exec(sql.slice(hit.index + hit[0].length));
546
+ let table = name ? name[1].split("").filter((ch) => ch.charCodeAt(0) !== DQ && ch.charCodeAt(0) !== BS).join("").trim() : "";
547
+ if (table === "" || table === "?") table = "(dynamic)";
548
+ found.push((isTruncate ? "TRUNCATE " : "DELETE FROM ") + table);
549
+ }
550
+ for (const inner of nested) found = found.concat(unscopedIn(inner, true));
551
+ return found;
552
+ }
553
+ function check(p) {
554
+ if (!fs$1.existsSync(p)) {
555
+ log("no file matches: " + p);
556
+ return false;
557
+ }
558
+ if (fs$1.statSync(p).isDirectory()) {
559
+ log(p + " is a directory: name the migration file, or a glob that matches only files, such as dir/*AddFoo*.ts");
560
+ return false;
561
+ }
562
+ const lexed = lex(String(fs$1.readFileSync(p, "utf-8")));
563
+ const m = lexed.masked;
564
+ const bodies = bodiesOf(m, "down");
565
+ if (bodies.length === 0) {
566
+ log(p + ": no down method");
567
+ return false;
568
+ }
569
+ const seen = /* @__PURE__ */ new Set();
570
+ for (let b = 0; b < bodies.length && b < 500; b++) {
571
+ const text = m.slice(bodies[b][0], bodies[b][1]);
572
+ const names = /[A-Za-z_$][\w$]*/g;
573
+ for (let c = names.exec(text); c; c = names.exec(text)) {
574
+ const name = c[0];
575
+ if (seen.has(name) || NOT_A_HELPER.test(name)) continue;
576
+ seen.add(name);
577
+ for (const body of bodiesOf(m, name)) bodies.push(body);
578
+ }
579
+ }
580
+ const repos = [];
581
+ const repoDecl = /(?:\b(?:const|let|var)\s+|\bthis\s*\.\s*)([A-Za-z_$][\w$]*)\s*=\s*[^;]*?\bget(?:Tree)?Repository\s*\(/g;
582
+ for (let d = repoDecl.exec(m); d; d = repoDecl.exec(m)) repos.push(d[1]);
583
+ const inBody = (start, end) => bodies.some((b) => start > b[0] && end <= b[1]);
584
+ const unscoped = [];
585
+ for (const b of bodies) {
586
+ const text = m.slice(b[0], b[1]);
587
+ if (/\.clearTable\s*\(/.test(text)) unscoped.push("clearTable()");
588
+ if (/\.\s*clear\s*\(\s*[^\s)]/.test(text) || /\b(?:manager|em|entityManager)\s*\.\s*clear\s*\(/.test(text) || /\w*(?:repo|repository)\s*\.\s*clear\s*\(/i.test(text) || /\bget(?:Tree)?Repository\s*\([^)]*\)\s*\.\s*clear\s*\(/.test(text) || repos.some((r) => (/* @__PURE__ */ new RegExp("(?:^|[^\\w$.])" + r.split("$").join("\\$") + "\\s*\\.\\s*clear\\s*\\(")).test(text))) unscoped.push("clear()");
589
+ if (/\.\s*delete\s*\(\s*(?:[^,()]*,\s*)?\{\s*\}\s*,?\s*\)/.test(text)) unscoped.push("delete({})");
590
+ if (/\.\s*delete\s*\(\s*\)(?!(?:\s*\.\s*[\w$]+\s*\([^()]*\))*\s*\.\s*(?:where|andWhere|orWhere|whereInIds)\s*\()/.test(text)) unscoped.push("delete() with no where");
591
+ }
592
+ const mine = lexed.lits.filter((lit) => inBody(lit.start, lit.end));
593
+ const joined = /* @__PURE__ */ new Set();
594
+ for (let a = 0; a < mine.length; a++) {
595
+ let b = a;
596
+ while (b + 1 < mine.length && /^\s*,\s*(?![^])/.test(m.slice(mine[b].end, mine[b + 1].start))) b++;
597
+ if (b > a && /^\s*,?\s*\]\s*\.\s*join\s*\(/.test(m.slice(mine[b].end, mine[b].end + 80))) for (let k = a + 1; k <= b; k++) joined.add(mine[k]);
598
+ a = b;
599
+ }
600
+ const sqls = [];
601
+ let prev = null;
602
+ for (const lit of mine) {
603
+ const gap = prev ? m.slice(prev.end, lit.start) : "";
604
+ if (joined.has(lit)) sqls[sqls.length - 1] += " " + lit.text;
605
+ else if (prev && prev.end <= lit.start && /^\s*[+]\s*(?![^])/.test(gap)) sqls[sqls.length - 1] += lit.text;
606
+ else if (prev && prev.end <= lit.start && /^\s*[+]\s*[\w.]+\s*[+]\s*(?![^])/.test(gap)) sqls[sqls.length - 1] += " ? " + lit.text;
607
+ else sqls.push(lit.text);
608
+ prev = lit;
609
+ }
610
+ for (const sql of sqls) for (const hit of unscopedIn(sql, false)) unscoped.push(hit);
611
+ if (unscoped.length > 0) {
612
+ log(p + ": unscoped " + unscoped.join(","));
613
+ return false;
614
+ }
615
+ log(p + ": all scoped");
616
+ return true;
617
+ }
618
+ if (paths.length === 0) {
619
+ log("no file given");
620
+ return 1;
621
+ }
622
+ let bad = 0;
623
+ for (const p of paths) try {
624
+ if (!check(p)) bad = 1;
625
+ } catch (e) {
626
+ log(p + ": could not be checked: " + (e instanceof Error ? e.message : String(e)));
627
+ bad = 1;
628
+ }
629
+ return bad;
630
+ }
631
+
632
+ //#endregion
633
+ //#region src/commands/gates/builtins.ts
634
+ function finish(exitCode, stdout, stderr = "") {
635
+ return {
636
+ exitCode,
637
+ stdout: stdout.length === 0 || stdout.endsWith("\n") ? stdout : `${stdout}\n`,
638
+ stderr: stderr.length === 0 || stderr.endsWith("\n") ? stderr : `${stderr}\n`
639
+ };
640
+ }
641
+ function isGlobPattern(value, glob) {
642
+ if (glob === false) return false;
643
+ if (glob === true) return true;
644
+ return /[*?[]/.test(value);
645
+ }
646
+ function escapesRepo(value) {
647
+ if (value.length === 0 || value.includes("\0")) return true;
648
+ if (value.startsWith("/") || value.startsWith("~") || value.startsWith("\\\\") || value.startsWith("//") || /^[A-Za-z]:[\\/]/.test(value)) return true;
649
+ return value.split(/[\\/]/).includes("..");
650
+ }
651
+ function segmentToRegExp(segment, bracketsLiteral = false) {
652
+ let body = "";
653
+ for (let i = 0; i < segment.length; i++) {
654
+ const ch = segment[i] ?? "";
655
+ if (ch === "*") body += "[^/]*";
656
+ else if (ch === "?") body += "[^/]";
657
+ else if (ch === "[" && !bracketsLiteral) {
658
+ const end = segment.indexOf("]", i + 1);
659
+ if (end === -1) body += "\\[";
660
+ else {
661
+ body += segment.slice(i, end + 1);
662
+ i = end;
663
+ }
664
+ } else body += ch.replace(/[\\^$.|?*+(){}]/g, "\\$&");
665
+ }
666
+ return /* @__PURE__ */ new RegExp(`^${body}$`);
667
+ }
668
+ function listDir(abs) {
669
+ if (!fs.existsSync(abs)) return [];
670
+ if (!fs.statSync(abs).isDirectory()) return [];
671
+ return fs.readdirSync(abs, { withFileTypes: true });
672
+ }
673
+ /**
674
+ * Expand a repo-relative glob. `**` crosses directories. Dotfiles match only a
675
+ * leading dot. With `bracketsLiteral`, `[` and `]` are name characters and only
676
+ * `*` / `?` are patterns: the names-first reading (TR-918 #5).
677
+ */
678
+ function expandGlob(root, pattern, bracketsLiteral = false) {
679
+ const wild = bracketsLiteral ? /[*?]/ : /[*?[]/;
680
+ const segments = pattern.split(/[\\/]/).filter((segment) => segment.length > 0);
681
+ const walk = (dirRel, index) => {
682
+ if (index >= segments.length) return dirRel.length > 0 ? [dirRel] : [];
683
+ const segment = segments[index] ?? "";
684
+ const dirAbs = dirRel.length === 0 ? root : path.join(root, dirRel);
685
+ if (segment === "**") {
686
+ const here = walk(dirRel, index + 1);
687
+ const nested = [];
688
+ for (const entry of listDir(dirAbs)) {
689
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
690
+ const child = dirRel.length === 0 ? entry.name : path.join(dirRel, entry.name);
691
+ nested.push(...walk(child, index));
692
+ }
693
+ return here.concat(nested);
694
+ }
695
+ if (!wild.test(segment)) {
696
+ const rel = dirRel.length === 0 ? segment : path.join(dirRel, segment);
697
+ const abs = path.join(root, rel);
698
+ if (!fs.existsSync(abs)) return [];
699
+ if (index === segments.length - 1) return [rel];
700
+ return fs.statSync(abs).isDirectory() ? walk(rel, index + 1) : [];
701
+ }
702
+ const re = segmentToRegExp(segment, bracketsLiteral);
703
+ const hits = [];
704
+ for (const entry of listDir(dirAbs)) {
705
+ if (entry.name.startsWith(".") && !segment.startsWith(".")) continue;
706
+ if (!re.test(entry.name)) continue;
707
+ const rel = dirRel.length === 0 ? entry.name : path.join(dirRel, entry.name);
708
+ if (index === segments.length - 1) hits.push(rel);
709
+ else if (entry.isDirectory()) hits.push(...walk(rel, index + 1));
710
+ }
711
+ return hits;
712
+ };
713
+ return walk("", 0);
714
+ }
715
+ function realRoot(root) {
716
+ try {
717
+ return fs.realpathSync(root);
718
+ } catch {
719
+ return path.resolve(root);
720
+ }
721
+ }
722
+ /**
723
+ * True when `rel`, with every symlink in it followed, stays inside `root`.
724
+ * A path that does not exist reads nothing, so it is inside.
725
+ */
726
+ function staysInRepo(root, rel) {
727
+ const abs = path.join(root, rel);
728
+ let real;
729
+ try {
730
+ real = fs.realpathSync(abs);
731
+ } catch {
732
+ return !fs.existsSync(abs) && !isDanglingLink(abs);
733
+ }
734
+ const base = realRoot(root);
735
+ return real === base || real.startsWith(base + path.sep);
736
+ }
737
+ function isDanglingLink(abs) {
738
+ try {
739
+ return fs.lstatSync(abs).isSymbolicLink();
740
+ } catch {
741
+ return false;
742
+ }
743
+ }
744
+ function resolveFiles(root, check, params) {
745
+ if (escapesRepo(params.path)) return {
746
+ files: [],
747
+ failure: finish(1, `path escapes the repo: ${params.path}`)
748
+ };
749
+ const literalExists = params.glob !== true && (fs.existsSync(path.join(root, params.path)) || isDanglingLink(path.join(root, params.path)));
750
+ let files;
751
+ if (literalExists || !isGlobPattern(params.path, params.glob)) files = [params.path];
752
+ else {
753
+ files = params.glob !== true && params.path.includes("[") ? expandGlob(root, params.path, true) : [];
754
+ if (files.length === 0) files = expandGlob(root, params.path);
755
+ if (files.length === 0) {
756
+ if (check === "not_exists") return {
757
+ files,
758
+ failure: null
759
+ };
760
+ return {
761
+ files: [],
762
+ failure: finish(1, `no file matches: ${params.path}`)
763
+ };
764
+ }
765
+ }
766
+ for (const file of files) if (!staysInRepo(root, file)) return {
767
+ files: [],
768
+ failure: finish(1, `path escapes the repo: ${file}`)
769
+ };
770
+ return {
771
+ files,
772
+ failure: null
773
+ };
774
+ }
775
+ function readText(root, rel) {
776
+ return fs.readFileSync(path.join(root, rel), "utf8");
777
+ }
778
+ function escapeRegExp(value) {
779
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
780
+ }
781
+ const POSIX_CLASSES = {
782
+ alpha: "A-Za-z",
783
+ digit: "0-9",
784
+ alnum: "A-Za-z0-9",
785
+ upper: "A-Z",
786
+ lower: "a-z",
787
+ space: " \\t\\n\\r\\f\\v",
788
+ blank: " \\t",
789
+ punct: "!-\\/:-@\\[-`{-~",
790
+ xdigit: "0-9A-Fa-f",
791
+ cntrl: "\\x00-\\x1f\\x7f",
792
+ print: "\\x20-\\x7e",
793
+ graph: "\\x21-\\x7e",
794
+ word: "A-Za-z0-9_"
795
+ };
796
+ /**
797
+ * Translate a POSIX ERE (what `grep -E` read) into a JS `RegExp` source.
798
+ *
799
+ * Bracket classes become their JS ranges. Syntax that `grep` and JS read
800
+ * differently, and that has no faithful translation, is refused with a
801
+ * reason: GNU word anchors `\<` `\>`, buffer anchors, collating and
802
+ * equivalence classes, a backslash inside a bracket expression (literal to
803
+ * grep, an escape to JS), and a back-reference to a group that does not
804
+ * exist (an octal escape to JS). Refusing is the point: a `not_contains`
805
+ * whose pattern silently stopped matching is a false pass.
806
+ */
807
+ function translatePosixEre(pattern) {
808
+ let out = "";
809
+ let groups = 0;
810
+ for (let i = 0; i < pattern.length; i++) if (pattern[i] === "\\") i += 1;
811
+ else if (pattern[i] === "(" && pattern[i + 1] !== "?") groups += 1;
812
+ for (let i = 0; i < pattern.length; i++) {
813
+ const ch = pattern[i] ?? "";
814
+ if (ch === "\\") {
815
+ const next = pattern[i + 1];
816
+ if (next === void 0) return { error: "pattern ends with a backslash" };
817
+ if (next === "<" || next === ">") return { error: `\\${next} is a GNU word anchor with no JS equivalent; use \\b` };
818
+ if (next === "`" || next === "'") return { error: `\\${next} is a GNU buffer anchor; use ^ or $` };
819
+ if (/[1-9]/.test(next) && Number(next) > groups) return { error: `back-reference \\${next} names a group the pattern does not have` };
820
+ out += ch + next;
821
+ i += 1;
822
+ continue;
823
+ }
824
+ if (ch !== "[") {
825
+ out += ch;
826
+ continue;
827
+ }
828
+ let j = i + 1;
829
+ let body = "";
830
+ if (pattern[j] === "^") {
831
+ body += "^";
832
+ j += 1;
833
+ }
834
+ if (pattern[j] === "]") {
835
+ body += "\\]";
836
+ j += 1;
837
+ }
838
+ let closed = false;
839
+ while (j < pattern.length) {
840
+ const c = pattern[j] ?? "";
841
+ if (c === "]") {
842
+ closed = true;
843
+ break;
844
+ }
845
+ if (c === "[" && (pattern[j + 1] === ":" || pattern[j + 1] === "=" || pattern[j + 1] === ".")) {
846
+ const kind = pattern[j + 1];
847
+ const end = pattern.indexOf(`${kind}]`, j + 2);
848
+ if (end === -1) return { error: `unterminated [${kind} in a bracket expression` };
849
+ const name = pattern.slice(j + 2, end);
850
+ if (kind !== ":") return { error: `[${kind}${name}${kind}] has no JS equivalent` };
851
+ const range = POSIX_CLASSES[name];
852
+ if (range === void 0) return { error: `unknown class [:${name}:]` };
853
+ body += range;
854
+ j = end + 2;
855
+ continue;
856
+ }
857
+ if (c === "\\") return { error: "a backslash inside [...] is literal to grep and an escape to JS; use a POSIX class or move it outside the brackets" };
858
+ if (c === "[") body += "\\[";
859
+ else body += c;
860
+ j += 1;
861
+ }
862
+ if (!closed) return { error: "unterminated bracket expression" };
863
+ out += `[${body}]`;
864
+ i = j;
865
+ }
866
+ return out;
867
+ }
868
+ function compilePattern(source, flags) {
869
+ const translated = translatePosixEre(source);
870
+ if (typeof translated !== "string") return finish(2, `ERROR: unsupported pattern: ${translated.error}`);
871
+ try {
872
+ return new RegExp(translated, flags);
873
+ } catch (err) {
874
+ return finish(1, `ERROR: invalid pattern: ${err instanceof Error ? err.message : String(err)}`);
875
+ }
876
+ }
877
+ function matcherFor(params, insensitive) {
878
+ const keywords = params.keywords ?? [];
879
+ const flags = insensitive ? "i" : "";
880
+ if (keywords.length > 0) return new RegExp(keywords.map(escapeRegExp).join("|"), flags);
881
+ if (params.pattern != null && params.pattern.length > 0) {
882
+ if (params.literal) return new RegExp(escapeRegExp(params.pattern), flags);
883
+ return compilePattern(params.pattern, flags);
884
+ }
885
+ return finish(1, "ERROR: pattern or keywords is required");
886
+ }
887
+ function countLines(text, re) {
888
+ let count = 0;
889
+ for (const line of text.split("\n")) if (re.test(line)) count += 1;
890
+ return count;
891
+ }
892
+ const MAX_WALK_FILES = 1e5;
893
+ /**
894
+ * Files under `rel`, which may itself be a file. A directory is walked.
895
+ * `rel` itself has already been checked to stay inside the repo after its
896
+ * symlinks are followed (`resolveFiles`), so it is followed. A symlink met
897
+ * while walking is skipped, the way `grep -r` skips it: following it could
898
+ * leave the repo, and `node_modules/.bin` is full of them.
899
+ */
900
+ function collectFiles(root, rel, acc, top = true) {
901
+ if (acc.length > MAX_WALK_FILES) return finish(1, `path is too large to search: ${rel}`);
902
+ const abs = path.join(root, rel);
903
+ let stat;
904
+ try {
905
+ stat = top ? fs.statSync(abs) : fs.lstatSync(abs);
906
+ } catch {
907
+ return finish(1, `no file matches: ${rel}`);
908
+ }
909
+ if (stat.isSymbolicLink()) return null;
910
+ if (stat.isDirectory()) {
911
+ let entries;
912
+ try {
913
+ entries = fs.readdirSync(abs, { withFileTypes: true });
914
+ } catch {
915
+ return finish(1, `no file matches: ${rel}`);
916
+ }
917
+ for (const entry of entries) {
918
+ const nested = collectFiles(root, rel.length === 0 ? entry.name : path.join(rel, entry.name), acc, false);
919
+ if (nested) return nested;
920
+ }
921
+ return null;
922
+ }
923
+ if (stat.isFile()) {
924
+ acc.push(rel);
925
+ return null;
926
+ }
927
+ return null;
928
+ }
929
+ function countAcross(root, files, re) {
930
+ const expanded = [];
931
+ for (const file of files) {
932
+ const walked = collectFiles(root, file, expanded);
933
+ if (walked) return walked;
934
+ }
935
+ let total = 0;
936
+ for (const file of expanded) total += countLines(readText(root, file), re);
937
+ return total;
938
+ }
939
+ function doneCriteria(root, files, requirements) {
940
+ let corpus = "";
941
+ for (const file of files) {
942
+ const abs = path.join(root, file);
943
+ if (!fs.existsSync(abs) || fs.statSync(abs).isDirectory()) return finish(1, `no file matches: ${file}`);
944
+ corpus += readText(root, file);
945
+ corpus += "\n";
946
+ }
947
+ const lower = corpus.toLowerCase();
948
+ const missing = [];
949
+ for (const requirement of requirements) {
950
+ if (typeof requirement === "string") {
951
+ if (!lower.includes(requirement.toLowerCase())) missing.push(requirement);
952
+ continue;
953
+ }
954
+ if (!requirement || typeof requirement.pattern !== "string") return finish(2, `ERROR: requirement must be a string or an object with a string pattern: ${JSON.stringify(requirement)}`);
955
+ const re = compilePattern(requirement.pattern, "i");
956
+ if (!(re instanceof RegExp)) return re;
957
+ if (!re.test(corpus)) missing.push(requirement.label || requirement.pattern);
958
+ }
959
+ if (missing.length > 0) return finish(1, `MISSING: ${missing.join(", ")}`);
960
+ return finish(0, "all met");
961
+ }
962
+ function jsonHasKey(root, file, key, object) {
963
+ const abs = path.join(root, file);
964
+ if (!fs.existsSync(abs) || fs.statSync(abs).isDirectory()) return finish(1, `no file matches: ${file}`);
965
+ let parsed;
966
+ try {
967
+ parsed = JSON.parse(readText(root, file));
968
+ } catch {
969
+ return finish(1, `ERROR: invalid JSON in ${file}`);
970
+ }
971
+ const record = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
972
+ const parent = object === void 0 ? record : record?.[object];
973
+ const table = parent && typeof parent === "object" && !Array.isArray(parent) ? parent : null;
974
+ if (!table || !Object.prototype.hasOwnProperty.call(table, key)) return finish(1, `MISSING: ${key} is not a key of ${object === void 0 ? "the document" : object} in ${file}`);
975
+ return finish(0, "PASS");
976
+ }
977
+ function specColumns(root, file, required) {
978
+ const abs = path.join(root, file);
979
+ if (!fs.existsSync(abs) || fs.statSync(abs).isDirectory()) return finish(1, `no file matches: ${file}`);
980
+ let parsed;
981
+ try {
982
+ parsed = JSON.parse(readText(root, file));
983
+ } catch {
984
+ return finish(1, `ERROR: invalid JSON in ${file}`);
985
+ }
986
+ const fields = parsed?.fields;
987
+ if (!Array.isArray(fields)) return finish(1, `SPEC_SHAPE_ERROR: expected fields[] array, got ${fields === null ? "null" : typeof fields}`);
988
+ const cols = fields.map((field) => field && typeof field === "object" && "name" in field ? field.name : void 0).filter((name) => typeof name === "string" && name.length > 0);
989
+ const missing = (Array.isArray(required) ? required : required.split(",")).map((col) => col.trim()).filter((col) => !cols.includes(col));
990
+ if (missing.length > 0) return finish(1, `MISSING: ${missing.join(",")}`);
991
+ return finish(0, "all present");
992
+ }
993
+ const SINGLE_FILE_CHECKS = new Set([
994
+ "done_criteria",
995
+ "json_has_key",
996
+ "spec_columns"
997
+ ]);
998
+ function isRegularFile(root, rel) {
999
+ try {
1000
+ return fs.statSync(path.join(root, rel)).isFile();
1001
+ } catch {
1002
+ return false;
1003
+ }
1004
+ }
1005
+ /** Run one inspect check. `root` is the repository root; paths are relative to it. */
1006
+ function runBuiltin(request, root = process.cwd()) {
1007
+ const { files, failure } = resolveFiles(root, request.check, request.params);
1008
+ if (failure) return failure;
1009
+ if (SINGLE_FILE_CHECKS.has(request.check) && files.length > 1) return finish(1, `${request.params.path} does not resolve to exactly one file: ${files.join(", ")}`);
1010
+ if (request.check === "exists" || request.check === "not_exists") {
1011
+ if (request.check === "exists") {
1012
+ const regular = files.filter((file) => isRegularFile(root, file));
1013
+ if (regular.length > 0) return finish(0, `exists: ${regular.join(", ")}`);
1014
+ return finish(1, `MISSING: ${files[0] ?? request.params.path}`);
1015
+ }
1016
+ const present = files.filter((file) => fs.existsSync(path.join(root, file)));
1017
+ if (present.length === 0) return finish(0, `absent: ${request.params.path}`);
1018
+ return finish(1, `exists: ${present.join(", ")}`);
1019
+ }
1020
+ if (request.check === "contains" || request.check === "not_contains") {
1021
+ const matcher = matcherFor(request.params, request.check === "contains");
1022
+ if (!(matcher instanceof RegExp)) return matcher;
1023
+ const counted = countAcross(root, files, matcher);
1024
+ if (typeof counted !== "number") return counted;
1025
+ return finish((request.check === "contains" ? counted > 0 : counted === 0) ? 0 : 1, String(counted));
1026
+ }
1027
+ if (request.check === "done_criteria") {
1028
+ const requirements = request.params.requirements;
1029
+ if (!Array.isArray(requirements) || requirements.length === 0) return finish(2, "ERROR: requirements must be a non-empty array");
1030
+ return doneCriteria(root, files, requirements);
1031
+ }
1032
+ if (request.check === "json_has_key") {
1033
+ const key = request.params.key;
1034
+ if (!key) return finish(1, "ERROR: key is required");
1035
+ for (const file of files) {
1036
+ const one = jsonHasKey(root, file, key, request.params.object);
1037
+ if (one.exitCode !== 0) return one;
1038
+ }
1039
+ return finish(0, "PASS");
1040
+ }
1041
+ if (request.check === "spec_columns") {
1042
+ const required = request.params.required_columns;
1043
+ if (required == null || Array.isArray(required) && required.length === 0) return finish(1, "ERROR: required_columns is required");
1044
+ for (const file of files) {
1045
+ const one = specColumns(root, file, required);
1046
+ if (one.exitCode !== 0) return one;
1047
+ }
1048
+ return finish(0, "all present");
1049
+ }
1050
+ const lines = [];
1051
+ return finish(migrationRevertScopeCheck(fs, files.map((file) => path.resolve(root, file)), (line) => lines.push(line)), lines.join("\n"));
1052
+ }
1053
+
1054
+ //#endregion
1055
+ //#region src/commands/gates/inspect.ts
1056
+ /**
1057
+ * `levr gates inspect '<op json>'` (ENG-6179).
1058
+ *
1059
+ * Parses an inspect op with `GateOpSchema`, runs the builtin, and exits 0
1060
+ * when the check passes or 1 when it fails. An op that does not parse, or
1061
+ * that is not an inspect op, exits 2.
1062
+ */
1063
+ function executeInspect(opJson, root = process.cwd()) {
1064
+ let raw;
1065
+ try {
1066
+ raw = JSON.parse(opJson);
1067
+ } catch {
1068
+ return {
1069
+ exitCode: 2,
1070
+ stdout: "",
1071
+ stderr: "gates inspect: op does not parse\n"
1072
+ };
1073
+ }
1074
+ const parsed = GateOpSchema.safeParse(raw);
1075
+ if (!parsed.success || parsed.data.op !== "inspect") return {
1076
+ exitCode: 2,
1077
+ stdout: "",
1078
+ stderr: "gates inspect: op does not parse\n"
1079
+ };
1080
+ return runBuiltin({
1081
+ check: parsed.data.check,
1082
+ params: parsed.data.params
1083
+ }, root);
1084
+ }
1085
+ function gatesInspectHandler(_flags, opJson) {
1086
+ const result = executeInspect(opJson);
1087
+ if (result.stdout.length > 0) this.process.stdout.write(result.stdout);
1088
+ if (result.stderr.length > 0) this.process.stderr.write(result.stderr);
1089
+ this.process.exitCode = result.exitCode;
1090
+ }
1091
+ const gatesInspectCommand = buildCommand({
1092
+ docs: {
1093
+ brief: "Run one inspect op and print its result",
1094
+ fullDescription: `Parse an inspect op JSON and run it in process. Exit 0 when the
1095
+ check passes, 1 when it fails, and 2 when the op does not parse.
1096
+
1097
+ The op is the JSON object \`{ "op": "inspect", "check", "params" }\`. This is
1098
+ the command a harness runs for an inspect gate.
1099
+
1100
+ Examples:
1101
+ levr gates inspect '{"op":"inspect","check":"exists","params":{"path":"package.json"}}'`
1102
+ },
1103
+ parameters: {
1104
+ positional: {
1105
+ kind: "tuple",
1106
+ parameters: [{
1107
+ parse: String,
1108
+ brief: "Inspect op as JSON",
1109
+ placeholder: "op-json",
1110
+ optional: false
1111
+ }]
1112
+ },
1113
+ flags: {}
1114
+ },
1115
+ loader: async () => {
1116
+ const { gatesInspectHandler: handler } = await import("./inspect-BzBqSBO3.js");
1117
+ return handler;
1118
+ }
1119
+ });
1120
+
1121
+ //#endregion
1122
+ export { executeInspect, gatesInspectCommand, gatesInspectHandler };