@attalabs/vinaya 0.26.0 → 0.28.0

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 (50) hide show
  1. package/README.md +6 -3
  2. package/aeg-root/enforcement.md +5 -5
  3. package/aeg-root/milestone-model.md +2 -0
  4. package/aeg-root/process.md +1 -1
  5. package/aeg-root/roles/developer.md +23 -17
  6. package/aeg-root/roles/planner.md +4 -0
  7. package/aeg-root/roles/principal.md +8 -2
  8. package/aeg-root/roles/reviewer.md +9 -5
  9. package/aeg-root/roles/security.md +9 -5
  10. package/dist/checks/bin/check-body-bare-digits.js +1038 -257
  11. package/dist/checks/bin/check-branch-topology.js +963 -199
  12. package/dist/checks/bin/check-brief-shape.js +1276 -203
  13. package/dist/checks/bin/check-changeset-coverage.js +1677 -281
  14. package/dist/checks/bin/check-closes-n.js +967 -203
  15. package/dist/checks/bin/check-coherence.js +1127 -284
  16. package/dist/checks/bin/check-dead-branch-push.js +910 -193
  17. package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
  18. package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
  19. package/dist/checks/bin/check-doc-coverage.js +1675 -279
  20. package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
  21. package/dist/checks/bin/check-doctrine-portability.js +1672 -276
  22. package/dist/checks/bin/check-evidence-fresh.js +2526 -322
  23. package/dist/checks/bin/check-exec-bits.js +1670 -274
  24. package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
  25. package/dist/checks/bin/check-issue-assignment.js +965 -201
  26. package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
  27. package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
  28. package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
  29. package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
  30. package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
  31. package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
  32. package/dist/checks/bin/check-main-branch-refusal.js +910 -193
  33. package/dist/checks/bin/check-no-disk-state.js +910 -193
  34. package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
  35. package/dist/checks/bin/check-pr-report-density.js +910 -193
  36. package/dist/checks/bin/check-quoted-command.js +1651 -274
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
  38. package/dist/checks/bin/check-registry-gates.js +997 -193
  39. package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
  40. package/dist/checks/bin/check-review-gate.js +1163 -316
  41. package/dist/checks/bin/check-single-plan-pr.js +910 -193
  42. package/dist/checks/bin/check-surface-scope.js +973 -201
  43. package/dist/checks/bin/check-test-plan.js +939 -206
  44. package/dist/checks/bin/check-token-collection-wired.js +910 -193
  45. package/dist/checks/bin/check-token-report.js +910 -193
  46. package/dist/checks/bin/check-workspace-escape.js +1668 -272
  47. package/dist/index.js +10602 -5352
  48. package/dist/lib/pre-push-changed-files.js +57 -0
  49. package/dist/lib/pre-push-select-tests.js +1473 -0
  50. package/package.json +4 -2
@@ -0,0 +1,1473 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/config.ts
4
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { z as z4 } from "zod";
8
+ // ../../packages/aeg-forge-state/src/gh.ts
9
+ import { execFile, execFileSync } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ var execFileAsync = promisify(execFile);
12
+ var systemEnv = {
13
+ ...process.env,
14
+ PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
15
+ };
16
+
17
+ // ../../packages/aeg-forge-state/src/labels.ts
18
+ var LABELS = [
19
+ {
20
+ key: "blocked",
21
+ id: "vinaya/blocked",
22
+ category: "state",
23
+ form: "literal",
24
+ carries: "Execution is halted pending an external unblock; wins over every other derived status."
25
+ },
26
+ {
27
+ key: "backlog",
28
+ id: "vinaya/backlog",
29
+ category: "state",
30
+ form: "literal",
31
+ carries: "The Issue is deliberately unplanned — belongs to no tranche and is excluded from dispatch, until a Planner promotes it."
32
+ },
33
+ {
34
+ key: "tier-0",
35
+ id: "vinaya/tier:0",
36
+ category: "tier",
37
+ form: "literal",
38
+ carries: "Lowest governance weight — typecheck, lint, tests, and a conforming PR body."
39
+ },
40
+ {
41
+ key: "tier-1",
42
+ id: "vinaya/tier:1",
43
+ category: "tier",
44
+ form: "literal",
45
+ carries: "Tier 0 plus spec/skill coverage and a passing verify-docs run."
46
+ },
47
+ {
48
+ key: "tier-3",
49
+ id: "vinaya/tier:3",
50
+ category: "tier",
51
+ form: "literal",
52
+ carries: "Tier 1 plus the reasoning for the change, stated in the pull request that makes it."
53
+ },
54
+ {
55
+ key: "tranche",
56
+ id: "vinaya/tranche:",
57
+ category: "tranche",
58
+ form: "prefix",
59
+ carries: "The tranche slug this task Issue belongs to — the forge's grouping key, matched by prefix."
60
+ },
61
+ {
62
+ key: "needs-execution-input",
63
+ id: "vinaya/needs:execution-input",
64
+ category: "needs",
65
+ form: "literal",
66
+ carries: "Waiting on a missing execution detail — a flag, a dependency, a value the brief did not carry."
67
+ },
68
+ {
69
+ key: "needs-strategy-input",
70
+ id: "vinaya/needs:strategy-input",
71
+ category: "needs",
72
+ form: "literal",
73
+ carries: "Waiting on a strategy call — the brief assumes an approach the codebase has moved away from."
74
+ },
75
+ {
76
+ key: "needs-principal-input",
77
+ id: "vinaya/needs:principal-input",
78
+ category: "needs",
79
+ form: "literal",
80
+ carries: "Waiting on the Principal — a product-level call no agent may make."
81
+ },
82
+ {
83
+ key: "needs-brief-correction",
84
+ id: "vinaya/needs:brief-correction",
85
+ category: "needs",
86
+ form: "literal",
87
+ carries: "Waiting on the Planner — the brief contradicts the surface it describes."
88
+ },
89
+ {
90
+ key: "waiver-docs",
91
+ id: "vinaya/waiver:docs",
92
+ category: "waiver",
93
+ form: "literal",
94
+ carries: "Doc-coverage gate excused for this PR — honored only when a principal applied it."
95
+ },
96
+ {
97
+ key: "waiver-review",
98
+ id: "vinaya/waiver:review",
99
+ category: "waiver",
100
+ form: "literal",
101
+ carries: "Review gate excused for this PR — honored only when a principal applied it."
102
+ },
103
+ {
104
+ key: "override-docs",
105
+ id: "vinaya/override:docs",
106
+ category: "waiver",
107
+ form: "literal",
108
+ carries: "The whole verify-docs gate suppressed for this PR — a Principal-only blunt override, wider than a waiver."
109
+ },
110
+ {
111
+ key: "incoherent",
112
+ id: "vinaya/incoherent",
113
+ category: "flag",
114
+ form: "literal",
115
+ carries: "Closed COMPLETED with no merged-PR link — done-but-unprovable, surfaced for a human."
116
+ },
117
+ {
118
+ key: "direct-main-push",
119
+ id: "vinaya/direct-main-push",
120
+ category: "flag",
121
+ form: "literal",
122
+ carries: "A commit reached main with no associated merged PR — ring-2 detection, never a mutation."
123
+ },
124
+ {
125
+ key: "dead-branch-push",
126
+ id: "vinaya/dead-branch-push",
127
+ category: "flag",
128
+ form: "literal",
129
+ carries: "Commits landed on a branch after its PR had already resolved — daily-drift detection."
130
+ },
131
+ {
132
+ key: "state-object",
133
+ id: "vinaya/state-object",
134
+ category: "kind",
135
+ form: "literal",
136
+ carries: "A permanent forge-native storage object, never actionable work — excluded from every backlog."
137
+ },
138
+ {
139
+ key: "type-build",
140
+ id: "vinaya/type:build",
141
+ category: "type",
142
+ form: "literal",
143
+ carries: "The task changes the build system, packaging, or an external dependency rather than product behavior."
144
+ },
145
+ {
146
+ key: "type-chore",
147
+ id: "vinaya/type:chore",
148
+ category: "type",
149
+ form: "literal",
150
+ carries: "The task is maintenance or repo housekeeping with no functional, user-facing change."
151
+ },
152
+ {
153
+ key: "type-docs",
154
+ id: "vinaya/type:docs",
155
+ category: "type",
156
+ form: "literal",
157
+ carries: "The task changes documentation only, with no code behavior affected."
158
+ },
159
+ {
160
+ key: "type-feat",
161
+ id: "vinaya/type:feat",
162
+ category: "type",
163
+ form: "literal",
164
+ carries: "The task adds a capability that did not exist before."
165
+ },
166
+ {
167
+ key: "type-fix",
168
+ id: "vinaya/type:fix",
169
+ category: "type",
170
+ form: "literal",
171
+ carries: "The task corrects incorrect behavior — a bug, not a missing capability."
172
+ },
173
+ {
174
+ key: "type-perf",
175
+ id: "vinaya/type:perf",
176
+ category: "type",
177
+ form: "literal",
178
+ carries: "The task improves performance or resource use without changing observable behavior."
179
+ },
180
+ {
181
+ key: "type-refactor",
182
+ id: "vinaya/type:refactor",
183
+ category: "type",
184
+ form: "literal",
185
+ carries: "The task restructures existing code without changing its external behavior."
186
+ },
187
+ {
188
+ key: "type-revert",
189
+ id: "vinaya/type:revert",
190
+ category: "type",
191
+ form: "literal",
192
+ carries: "The task undoes a previous change that shipped in error or is no longer wanted."
193
+ },
194
+ {
195
+ key: "type-style",
196
+ id: "vinaya/type:style",
197
+ category: "type",
198
+ form: "literal",
199
+ carries: "The task changes formatting, naming, or non-semantic code shape with no logic change."
200
+ },
201
+ {
202
+ key: "type-test",
203
+ id: "vinaya/type:test",
204
+ category: "type",
205
+ form: "literal",
206
+ carries: "The task adds or corrects test coverage with no production-code behavior change."
207
+ }
208
+ ];
209
+ var BY_KEY = new Map(LABELS.map((l) => [l.key, l]));
210
+ function entry(key) {
211
+ const found = BY_KEY.get(key);
212
+ if (!found)
213
+ throw new Error(`labels.ts: no LABELS entry for key '${key}'`);
214
+ return found;
215
+ }
216
+ function label(key) {
217
+ return entry(key).id;
218
+ }
219
+ // ../../packages/aeg-forge-state/src/github-token.ts
220
+ import { execFile as execFile2 } from "node:child_process";
221
+ import { promisify as promisify2 } from "node:util";
222
+ var execFileAsync2 = promisify2(execFile2);
223
+ var systemEnv2 = {
224
+ ...process.env,
225
+ PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
226
+ };
227
+ // ../../packages/aeg-forge-state/src/resolve-repo.ts
228
+ import { execFile as execFile3 } from "node:child_process";
229
+ import { promisify as promisify3 } from "node:util";
230
+ var execFileAsync3 = promisify3(execFile3);
231
+ var systemEnv3 = {
232
+ ...process.env,
233
+ PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
234
+ };
235
+ // ../../packages/aeg-core/src/state-machine-model.ts
236
+ var FORGE_FACT_INPUTS = [
237
+ {
238
+ fact: "issueState",
239
+ readsFrom: "Issue.state (OPEN | CLOSED)",
240
+ meaning: "Whether the task Issue is still open — the anchor for the honest terminal statuses."
241
+ },
242
+ {
243
+ fact: "assigned",
244
+ readsFrom: "Issue.assignees (count > 0)",
245
+ meaning: "An assignee exists. No longer affects derivation: assigned and unassigned are both todo."
246
+ },
247
+ {
248
+ fact: "branchExists",
249
+ readsFrom: "Ref refs/heads/task/<tranche>/<id>",
250
+ meaning: "A task branch has been published — the todo → in-flight transition, written by git push, not by hand."
251
+ },
252
+ {
253
+ fact: "prState",
254
+ readsFrom: "PullRequest.state (OPEN | CLOSED | MERGED)",
255
+ meaning: "Whether work is proposed or landed. CLOSED-without-merge collapses to none — AEG models open/merged/none."
256
+ },
257
+ {
258
+ fact: "reviewDecision",
259
+ readsFrom: "PullRequest.reviewDecision",
260
+ meaning: "Only 'changes_requested' flips status; approved-but-unmerged stays in-review."
261
+ },
262
+ {
263
+ fact: "blockedLabel",
264
+ readsFrom: `Issue.labels contains '${label("blocked")}'`,
265
+ meaning: "Execution halted pending an external unblock. Wins over every other rule."
266
+ },
267
+ {
268
+ fact: "stateReason",
269
+ readsFrom: "Issue.stateReason (COMPLETED | NOT_PLANNED | REOPENED | null)",
270
+ meaning: "Separates a legitimate drop from an incoherent close on a closed, never-merged Issue."
271
+ },
272
+ {
273
+ fact: "closedAt",
274
+ readsFrom: "Issue.closedAt",
275
+ meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
276
+ },
277
+ {
278
+ fact: "mergedAt",
279
+ readsFrom: "PullRequest.mergedAt",
280
+ meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
281
+ },
282
+ {
283
+ fact: "closedByActor",
284
+ readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
285
+ meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
286
+ }
287
+ ];
288
+ var DERIVED_STATUSES = [
289
+ "backlog",
290
+ "todo",
291
+ "in-flight",
292
+ "in-review",
293
+ "changes-requested",
294
+ "merged",
295
+ "blocked",
296
+ "dropped",
297
+ "incoherent"
298
+ ];
299
+ var DERIVABLE_STATUSES = DERIVED_STATUSES.filter((s) => s !== "backlog");
300
+ // ../../packages/aeg-core/src/waiver-label.ts
301
+ var WAIVER_LABEL = label("waiver-docs");
302
+ var WAIVER_LABEL_REVIEW = label("waiver-review");
303
+ // ../../packages/aeg-core/src/report-tokens.ts
304
+ var DASH_LOOKALIKES = {
305
+ "-": "‑",
306
+ "–": "‒",
307
+ "—": "―"
308
+ };
309
+ var DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES));
310
+ // ../../packages/aeg-core/src/metering-io-guard.ts
311
+ import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from "node:fs";
312
+ var GUARD_OPEN_FLAGS = fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | fsConstants.O_NOFOLLOW;
313
+ // ../../packages/aeg-core/src/doc-owners.ts
314
+ function globToRegex(pat) {
315
+ let re = "^";
316
+ let i = 0;
317
+ while (i < pat.length) {
318
+ const c = pat[i];
319
+ if (c === "*") {
320
+ if (pat[i + 1] === "*") {
321
+ re += ".*";
322
+ i += 2;
323
+ } else {
324
+ re += "[^/]*";
325
+ i += 1;
326
+ }
327
+ } else if (/[.+^$|(){}[\]\\?]/.test(c)) {
328
+ re += `\\${c}`;
329
+ i += 1;
330
+ } else {
331
+ re += c;
332
+ i += 1;
333
+ }
334
+ }
335
+ re += "$";
336
+ return new RegExp(re);
337
+ }
338
+ var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
339
+ // ../../packages/aeg-core/src/pr-tier.ts
340
+ var OVERRIDE_BODY_TOKEN = `[${label("override-docs")}]`;
341
+ // ../../packages/aeg-core/src/premise-check.ts
342
+ var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
343
+
344
+ // ../../packages/aeg-core/src/brief-validation.ts
345
+ var COMMIT_TYPES = [
346
+ "Build",
347
+ "Chore",
348
+ "Docs",
349
+ "Feat",
350
+ "Fix",
351
+ "Perf",
352
+ "Plan",
353
+ "Refactor",
354
+ "Revert",
355
+ "Style",
356
+ "Test"
357
+ ];
358
+ var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
359
+ // ../../packages/aeg-core/src/doctrine-portability.ts
360
+ var EXEMPT_LITERALS = new Set([
361
+ "path/inside/the/shipped/diff.ts",
362
+ "path/inside/the/surface.ts",
363
+ "apps/x/specs/..."
364
+ ]);
365
+ var NON_PATH_TOP_SEGMENTS = new Set(["origin", "refs", "HEAD", "vinaya", "fix"]);
366
+
367
+ // ../../packages/aeg-core/src/doctrine-no-procedures.ts
368
+ var SHELL_LANGS = new Set(["", "sh", "bash", "shell", "console"]);
369
+ // ../../packages/aeg-core/src/issue-validation.ts
370
+ var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
371
+ var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
372
+ var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
373
+
374
+ // ../../packages/aeg-core/src/coherence-checks.ts
375
+ var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
376
+ // ../../packages/aeg-core/src/diagram-model.ts
377
+ import matter from "gray-matter";
378
+ // ../../packages/aeg-core/src/reader-resolvable-prose.ts
379
+ var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
380
+
381
+ // ../../packages/aeg-core/src/quoted-command.ts
382
+ var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
383
+ // ../../packages/aeg-core/src/retired-vocabulary.ts
384
+ var RETIRED_PATTERNS = [
385
+ String.raw`\bD-[0-9]{3}\b`,
386
+ "D-(###|NNN|nnn|xxx)",
387
+ "decision (entry|logged)",
388
+ "decision-log entry",
389
+ "decision log entry",
390
+ "decision log(s|ged|ging)?([^a-z]|$)",
391
+ "decision-log",
392
+ "decision entr",
393
+ "decisions? (are |is )?logged",
394
+ "log entries",
395
+ String.raw`decisions\.md`,
396
+ "decisions-legacy",
397
+ "CONTRADICTION",
398
+ "assumes Tier 3",
399
+ "Lock: ?YES",
400
+ "Lock: ?NO",
401
+ "lock approvals?",
402
+ "approves locks",
403
+ "Conforms to lock",
404
+ "Challenges lock",
405
+ "checkDecisionNumbersFresh",
406
+ String.raw`roles/team-leader\.md`,
407
+ String.raw`\(,`,
408
+ String.raw`,\)`,
409
+ String.raw`\(\.\)`
410
+ ];
411
+ var PATTERN_EXEMPT = {
412
+ [String.raw`\bD-[0-9]{3}\b`]: ["./CLAUDE.md"],
413
+ [String.raw`decisions\.md`]: [
414
+ "docs-index.md",
415
+ "apps/vada-ai/specs/legacy/README.md",
416
+ "apps/vada-ai/specs/vada-product-spec.md",
417
+ "apps/vada-ai/web/CLAUDE.md",
418
+ "apps/vada-ai/CLAUDE.md"
419
+ ],
420
+ "decisions-legacy": [
421
+ "packages/aeg-core/src/file-classify.ts",
422
+ "packages/aeg-core/src/file-classify.test.ts",
423
+ "packages/aeg-core/src/pr-tier.test.ts",
424
+ "docs-index.md",
425
+ ".claude/skills/database/SKILL.md",
426
+ ".claude/skills/vada-architecture/SKILL.md",
427
+ "apps/vada-ai/specs/vada-byok-principles.md",
428
+ "apps/vada-ai/web/CLAUDE.md",
429
+ "apps/vada-ai/CLAUDE.md"
430
+ ],
431
+ CONTRADICTION: ["apps/vada-ai/specs/vada-reviewers-spec.md"]
432
+ };
433
+ // ../../packages/aeg-core/src/task-tools.ts
434
+ import { z } from "zod";
435
+ var TASK_TOOL_ERROR_KINDS = [
436
+ "validation",
437
+ "authority",
438
+ "precondition",
439
+ "capability",
440
+ "infrastructure",
441
+ "cancellation",
442
+ "timeout",
443
+ "uncertain_effect"
444
+ ];
445
+ var TaskToolErrorSchema = z.object({
446
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
447
+ message: z.string().min(1),
448
+ detail: z.string().optional()
449
+ });
450
+ var TaskToolRefSchema = z.union([
451
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
452
+ z.object({ issue: z.number().int().positive() })
453
+ ]);
454
+ var MAX_PAGE_LIMIT = 100;
455
+ var PageRequestSchema = z.object({
456
+ cursor: z.string().optional(),
457
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
458
+ });
459
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
460
+ var ObservedSchema = z.object({
461
+ observedAt: z.string(),
462
+ freshness: FreshnessSchema
463
+ });
464
+ var TaskStatusInputSchema = z.object({
465
+ task: TaskToolRefSchema.optional()
466
+ }).merge(PageRequestSchema);
467
+ var TaskStatusItemSchema = z.object({
468
+ task: TaskToolRefSchema,
469
+ issue: z.number().int().positive(),
470
+ pr: z.number().int().positive().nullable(),
471
+ state: z.string()
472
+ }).merge(ObservedSchema);
473
+ var TaskStatusResultSchema = z.object({
474
+ items: z.array(TaskStatusItemSchema),
475
+ nextCursor: z.string().nullable()
476
+ });
477
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
478
+ var EscalationInputsSchema = z.object({
479
+ task: z.number().int().positive(),
480
+ round: z.number().int().nonnegative(),
481
+ head: z.string(),
482
+ branch: z.string(),
483
+ prNumber: z.number().int().positive()
484
+ });
485
+ var EscalationEvidenceSchema = z.object({
486
+ round: z.number().int().nonnegative(),
487
+ reviewer: z.string().nullable(),
488
+ security: z.string().nullable()
489
+ });
490
+ var TaskEscalationReadInputSchema = z.object({
491
+ task: TaskToolRefSchema
492
+ }).merge(PageRequestSchema);
493
+ var TaskEscalationPacketSchema = z.object({
494
+ reason: z.string(),
495
+ detail: z.string().nullable(),
496
+ inputs: EscalationInputsSchema.nullable(),
497
+ evidence: EscalationEvidenceSchema.nullable(),
498
+ attemptedRecovery: z.string(),
499
+ requestedAuthority: RequestedAuthoritySchema,
500
+ permittedNextActions: z.array(z.string())
501
+ }).merge(ObservedSchema);
502
+ var TaskEscalationReadResultSchema = z.object({
503
+ items: z.array(TaskEscalationPacketSchema),
504
+ nextCursor: z.string().nullable()
505
+ }).merge(ObservedSchema);
506
+ var TaskStartInputSchema = z.object({
507
+ tranche: z.string().min(1),
508
+ id: z.string().min(1)
509
+ });
510
+ var TaskResumeInputSchema = z.object({
511
+ task: TaskToolRefSchema
512
+ });
513
+ var TaskCancelInputSchema = z.object({
514
+ task: TaskToolRefSchema,
515
+ reason: z.string().min(1)
516
+ });
517
+ var NoResultSchema = z.never();
518
+ // ../../packages/aeg-core/src/log/schema.ts
519
+ import { z as z2 } from "zod";
520
+ var ROLE_VALUES = [
521
+ "planner",
522
+ "developer",
523
+ "code-reviewer",
524
+ "security",
525
+ "principal",
526
+ "archivist",
527
+ "architect"
528
+ ];
529
+ var RoleSchema = z2.enum(ROLE_VALUES);
530
+ var HOST_VALUES = ["hook", "ci", "cli", "loop"];
531
+ var HostSchema = z2.enum(HOST_VALUES);
532
+ var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
533
+ var headerMetaCore = {
534
+ ts: z2.string(),
535
+ run_id: z2.string().regex(RUN_ID_PATTERN),
536
+ seq: z2.number().int().nonnegative(),
537
+ repo: z2.string().nullable(),
538
+ vinaya: z2.string(),
539
+ doctrine: z2.string(),
540
+ host: HostSchema,
541
+ machine: z2.string()
542
+ };
543
+ var HeaderMetaV1Schema = z2.object({
544
+ schema: z2.literal(1),
545
+ ...headerMetaCore
546
+ }).strict();
547
+ var LineageSchema = z2.object({
548
+ run: z2.string().nullable(),
549
+ attempt: z2.number().int().nullable(),
550
+ parent: z2.string().nullable()
551
+ }).strict();
552
+ var InputVersionsSchema = z2.object({
553
+ objectives_version: z2.string().nullable(),
554
+ brief_hash: z2.string().nullable(),
555
+ ruling_ordinal: z2.number().int().nullable(),
556
+ policy_digest: z2.string().nullable()
557
+ }).strict();
558
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
559
+ var HeaderMetaV2Schema = z2.object({
560
+ schema: z2.literal(2),
561
+ ...headerMetaCore,
562
+ event_id: z2.string().min(1),
563
+ process_id: z2.string().min(1),
564
+ actor_id: z2.string().nullable(),
565
+ lineage: LineageSchema,
566
+ input_versions: InputVersionsSchema,
567
+ provenance: ProvenanceSchema
568
+ }).strict();
569
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
570
+ var SubjectSchema = z2.object({
571
+ issue: z2.number().int().nullable(),
572
+ pr: z2.number().int().optional(),
573
+ sha: z2.string().optional(),
574
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
575
+ round: z2.number().int().optional(),
576
+ objectives_version: z2.string().optional()
577
+ }).strict();
578
+ var HeaderSchema = z2.object({
579
+ meta: HeaderMetaSchema,
580
+ subject: SubjectSchema
581
+ }).strict();
582
+ var envelopeTail = {
583
+ duration_ms: z2.number().nonnegative().optional(),
584
+ payload: z2.object({}).strict()
585
+ };
586
+ var ReviewFindingSchema = z2.object({
587
+ id: z2.string(),
588
+ severity: z2.string(),
589
+ state: z2.string().optional(),
590
+ severity_scale: z2.string().optional(),
591
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
592
+ confidence: z2.number().min(0).max(1).optional(),
593
+ confidence_scale: z2.string().optional(),
594
+ confidence_source: z2.string().optional()
595
+ }).strict();
596
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
597
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
598
+ z2.object({
599
+ type: z2.literal("round_pushed"),
600
+ pr: z2.number().int(),
601
+ head: z2.string(),
602
+ comment_id: z2.number().int()
603
+ }).strict(),
604
+ z2.object({
605
+ type: z2.literal("verdict"),
606
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
607
+ head: z2.string(),
608
+ comment_id: z2.number().int(),
609
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
610
+ findings: z2.array(ReviewFindingSchema)
611
+ }).strict(),
612
+ z2.object({
613
+ type: z2.literal("escalation"),
614
+ class: z2.enum(["authority", "strategy", "product"]),
615
+ comment_id: z2.number().int()
616
+ }).strict(),
617
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
618
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
619
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
620
+ ]);
621
+ var dispatchShared = {
622
+ meta: HeaderMetaSchema,
623
+ subject: SubjectSchema,
624
+ kind: z2.literal("dispatch"),
625
+ ...envelopeTail,
626
+ target_role: RoleSchema,
627
+ model: z2.string(),
628
+ round: z2.number().int().optional(),
629
+ effect_id: z2.string()
630
+ };
631
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
632
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
633
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
634
+ z2.object({
635
+ ...dispatchShared,
636
+ event: z2.literal("outcome_received"),
637
+ outcome: DispatchOutcomeSchema,
638
+ usage: dispatchUsageField
639
+ }).strict(),
640
+ z2.object({
641
+ ...dispatchShared,
642
+ event: z2.literal("dispatch_failed"),
643
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
644
+ usage: dispatchUsageField
645
+ }).strict()
646
+ ]);
647
+ var loopShared = {
648
+ meta: HeaderMetaSchema,
649
+ subject: SubjectSchema,
650
+ kind: z2.literal("dev_review_loop"),
651
+ ...envelopeTail,
652
+ loop_id: z2.string()
653
+ };
654
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
655
+ z2.object({
656
+ ...loopShared,
657
+ event: z2.literal("loop_started"),
658
+ task: z2.number().int(),
659
+ policy: z2.object({
660
+ max_rounds: z2.number().int().nonnegative(),
661
+ reviewers: z2.array(RoleSchema),
662
+ models: z2.record(RoleSchema, z2.string())
663
+ }).strict()
664
+ }).strict(),
665
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
666
+ z2.object({
667
+ ...loopShared,
668
+ event: z2.literal("gate_result_read"),
669
+ round: z2.number().int(),
670
+ head: z2.string(),
671
+ green: z2.boolean()
672
+ }).strict(),
673
+ z2.object({
674
+ ...loopShared,
675
+ event: z2.literal("verdicts_read"),
676
+ round: z2.number().int(),
677
+ head: z2.string(),
678
+ all_approve: z2.boolean(),
679
+ blockers: z2.number().int().nonnegative()
680
+ }).strict(),
681
+ z2.object({
682
+ ...loopShared,
683
+ event: z2.literal("findings_compared"),
684
+ round: z2.number().int(),
685
+ open: z2.array(z2.string()),
686
+ resolved: z2.array(z2.string()),
687
+ new: z2.array(z2.string()),
688
+ recurring: z2.array(z2.string())
689
+ }).strict(),
690
+ z2.object({
691
+ ...loopShared,
692
+ event: z2.literal("stop_condition_met"),
693
+ round: z2.number().int(),
694
+ condition: z2.enum([
695
+ "green",
696
+ "max_rounds",
697
+ "no_progress",
698
+ "escalated",
699
+ "principal_stop",
700
+ "confidence",
701
+ "reappearance"
702
+ ])
703
+ }).strict(),
704
+ z2.object({
705
+ ...loopShared,
706
+ event: z2.literal("paused"),
707
+ round: z2.number().int(),
708
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
709
+ }).strict(),
710
+ z2.object({
711
+ ...loopShared,
712
+ event: z2.literal("resumed"),
713
+ round: z2.number().int(),
714
+ by: z2.literal("principal")
715
+ }).strict(),
716
+ z2.object({
717
+ ...loopShared,
718
+ event: z2.literal("unpushed_work_resume"),
719
+ round: z2.number().int(),
720
+ branch: z2.string(),
721
+ detail: z2.string()
722
+ }).strict(),
723
+ z2.object({
724
+ ...loopShared,
725
+ event: z2.literal("round_ended"),
726
+ round: z2.number().int(),
727
+ base_head: z2.string(),
728
+ head: z2.string(),
729
+ files_changed: z2.number().int().nonnegative(),
730
+ insertions: z2.number().int().nonnegative(),
731
+ deletions: z2.number().int().nonnegative(),
732
+ wall_ms: z2.number().nonnegative(),
733
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
734
+ }).strict(),
735
+ z2.object({
736
+ ...loopShared,
737
+ event: z2.literal("journal_finalized"),
738
+ rounds: z2.number().int().nonnegative(),
739
+ total_wall_ms: z2.number().nonnegative(),
740
+ time_to_green_ms: z2.number().nonnegative().nullable(),
741
+ files_changed_total: z2.number().int().nonnegative(),
742
+ final_head: z2.string(),
743
+ result: z2.enum(["merged_ready", "stopped"])
744
+ }).strict()
745
+ ]);
746
+ var ForgeOpSchema = z2.enum([
747
+ "pr.create",
748
+ "pr.comment",
749
+ "pr.body.replace",
750
+ "pr.refreeze",
751
+ "issue.create",
752
+ "issue.edit",
753
+ "issue.comment",
754
+ "milestone.create",
755
+ "milestone.edit",
756
+ "milestone.close",
757
+ "label.add",
758
+ "label.remove"
759
+ ]);
760
+ var ForgeWriteTargetSchema = z2.object({
761
+ issue: z2.number().int().optional(),
762
+ pr: z2.number().int().optional()
763
+ }).strict();
764
+ var forgeWriteShared = {
765
+ meta: HeaderMetaSchema,
766
+ subject: SubjectSchema,
767
+ kind: z2.literal("forge_write"),
768
+ ...envelopeTail,
769
+ op: ForgeOpSchema,
770
+ target: ForgeWriteTargetSchema
771
+ };
772
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
773
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
774
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
775
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
776
+ ]);
777
+ var GateOutcomeSchema = z2.enum([
778
+ "pass",
779
+ "fail",
780
+ "wait",
781
+ "skip",
782
+ "invalid_input",
783
+ "unavailable_dependency",
784
+ "timeout",
785
+ "cancelled"
786
+ ]);
787
+ var gateShared = {
788
+ meta: HeaderMetaSchema,
789
+ subject: SubjectSchema,
790
+ kind: z2.literal("gate"),
791
+ ...envelopeTail,
792
+ check: z2.string(),
793
+ check_version: z2.string().nullable(),
794
+ policy_version: z2.string().nullable(),
795
+ input_fingerprint: z2.string().nullable()
796
+ };
797
+ var GateEventSchema = z2.discriminatedUnion("event", [
798
+ z2.object({
799
+ ...gateShared,
800
+ event: z2.literal("checked"),
801
+ outcome: GateOutcomeSchema,
802
+ reason: z2.string().optional()
803
+ }).strict()
804
+ ]);
805
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
806
+ var operationShared = {
807
+ meta: HeaderMetaSchema,
808
+ subject: SubjectSchema,
809
+ kind: z2.literal("operation"),
810
+ ...envelopeTail,
811
+ operation: z2.string(),
812
+ target: z2.string().nullable()
813
+ };
814
+ var OperationEventSchema = z2.discriminatedUnion("event", [
815
+ z2.object({
816
+ ...operationShared,
817
+ event: z2.literal("completed"),
818
+ result: OperationResultSchema,
819
+ error_class: z2.string().nullable()
820
+ }).strict()
821
+ ]);
822
+ var UsageUnitsSchema = z2.object({
823
+ input: z2.number().nonnegative().nullable(),
824
+ output: z2.number().nonnegative().nullable(),
825
+ cache: z2.number().nonnegative().nullable()
826
+ }).strict();
827
+ var usageShared = {
828
+ meta: HeaderMetaSchema,
829
+ subject: SubjectSchema,
830
+ kind: z2.literal("usage"),
831
+ ...envelopeTail,
832
+ model: z2.string().nullable(),
833
+ source: z2.string(),
834
+ semantics: z2.enum(["cumulative", "delta"])
835
+ };
836
+ var UsageEventSchema = z2.discriminatedUnion("event", [
837
+ z2.object({
838
+ ...usageShared,
839
+ event: z2.literal("observed"),
840
+ units: UsageUnitsSchema,
841
+ unknown_reason: z2.string().nullable()
842
+ }).strict()
843
+ ]);
844
+ var RoleAttemptOutcomeSchema = z2.enum([
845
+ "completed",
846
+ "incomplete",
847
+ "infrastructure_failed",
848
+ "cancelled",
849
+ "timed_out",
850
+ "capability_refused"
851
+ ]);
852
+ var roleAttemptShared = {
853
+ meta: HeaderMetaSchema,
854
+ subject: SubjectSchema,
855
+ kind: z2.literal("role_attempt"),
856
+ ...envelopeTail,
857
+ actor: z2.string().nullable(),
858
+ attempt: z2.number().int().nullable()
859
+ };
860
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
861
+ z2.object({
862
+ ...roleAttemptShared,
863
+ event: z2.literal("attempted"),
864
+ outcome: RoleAttemptOutcomeSchema,
865
+ usage: dispatchUsageField
866
+ }).strict()
867
+ ]);
868
+ var handoffShared = {
869
+ meta: HeaderMetaSchema,
870
+ subject: SubjectSchema,
871
+ kind: z2.literal("handoff"),
872
+ ...envelopeTail,
873
+ class: z2.enum(["authority", "strategy", "product"]),
874
+ reason: z2.string()
875
+ };
876
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
877
+ z2.object({
878
+ ...handoffShared,
879
+ event: z2.literal("raised"),
880
+ requested_decision: z2.string().nullable()
881
+ }).strict(),
882
+ z2.object({
883
+ ...handoffShared,
884
+ event: z2.literal("resolved"),
885
+ resolution: z2.string().nullable(),
886
+ resolved_by: z2.string().nullable()
887
+ }).strict()
888
+ ]);
889
+ var EffectTargetSchema = z2.object({
890
+ kind: z2.string(),
891
+ ref: z2.string()
892
+ }).strict();
893
+ var effectShared = {
894
+ meta: HeaderMetaSchema,
895
+ subject: SubjectSchema,
896
+ kind: z2.literal("effect"),
897
+ ...envelopeTail,
898
+ effect_id: z2.string(),
899
+ target: EffectTargetSchema
900
+ };
901
+ var EffectEventSchema = z2.discriminatedUnion("event", [
902
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
903
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
904
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
905
+ ]);
906
+ var LogEventSchema = z2.union([
907
+ DispatchEventSchema,
908
+ DevReviewLoopEventSchema,
909
+ ForgeWriteEventSchema,
910
+ GateEventSchema,
911
+ OperationEventSchema,
912
+ UsageEventSchema,
913
+ RoleAttemptEventSchema,
914
+ HandoffEventSchema,
915
+ EffectEventSchema
916
+ ]);
917
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
918
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
919
+ // ../../packages/aeg-core/src/control-store/records.ts
920
+ import { z as z3 } from "zod";
921
+ var isoTimestamp = z3.string().min(1);
922
+ var taskId = z3.number().int().positive();
923
+ var epochNumber = z3.number().int().nonnegative();
924
+ var RunRecordSchema = z3.object({
925
+ version: z3.literal(1),
926
+ kind: z3.literal("run"),
927
+ task: taskId,
928
+ runId: z3.string().min(1),
929
+ pid: z3.number().int().positive(),
930
+ host: z3.string().min(1),
931
+ startedAt: isoTimestamp
932
+ }).strict();
933
+ var InputRecordSchema = z3.object({
934
+ version: z3.literal(1),
935
+ kind: z3.literal("input"),
936
+ task: taskId,
937
+ runId: z3.string().min(1),
938
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
939
+ pr: z3.number().int().positive().nullable(),
940
+ round: z3.number().int().nonnegative(),
941
+ recordedAt: isoTimestamp
942
+ }).strict();
943
+ var OwnershipRecordSchema = z3.object({
944
+ version: z3.literal(1),
945
+ kind: z3.literal("ownership"),
946
+ task: taskId,
947
+ epoch: epochNumber,
948
+ ownerId: z3.string().min(1),
949
+ pid: z3.number().int().positive(),
950
+ host: z3.string().min(1),
951
+ acquiredAt: isoTimestamp
952
+ }).strict();
953
+ var TransitionRecordSchema = z3.object({
954
+ version: z3.literal(1),
955
+ kind: z3.literal("transition"),
956
+ task: taskId,
957
+ epoch: epochNumber,
958
+ seq: z3.number().int().nonnegative(),
959
+ from: z3.string().min(1),
960
+ to: z3.string().min(1),
961
+ detail: z3.string().optional(),
962
+ at: isoTimestamp
963
+ }).strict();
964
+ // src/lib/agent-vendors.ts
965
+ var AGENT_VENDORS = ["skills", "claude", "gemini"];
966
+
967
+ // src/lib/config.ts
968
+ var EnvEntrySchema = z4.union([
969
+ z4.literal(true),
970
+ z4.object({ optional: z4.literal(true) }),
971
+ z4.object({ anyOf: z4.array(z4.string()).min(2) }),
972
+ z4.string()
973
+ ]);
974
+ var CheckEntrySchema = z4.object({
975
+ run: z4.string(),
976
+ scope: z4.enum(["diff", "full"]),
977
+ include: z4.array(z4.string()).optional(),
978
+ args: z4.array(z4.string()).optional(),
979
+ timeoutMs: z4.number().optional(),
980
+ env: z4.record(z4.string(), EnvEntrySchema).optional(),
981
+ requiresOpenPr: z4.boolean().optional(),
982
+ ownWorkflow: z4.boolean().optional(),
983
+ principalOwed: z4.literal(true).optional(),
984
+ validates: z4.enum(["body", "issue"]).optional()
985
+ }).superRefine((entry2, ctx) => {
986
+ if (!entry2.env)
987
+ return;
988
+ for (const [key, value] of Object.entries(entry2.env)) {
989
+ if (typeof value !== "object" || value === null || !("anyOf" in value))
990
+ continue;
991
+ const members = value.anyOf;
992
+ if (new Set(members).size !== members.length) {
993
+ ctx.addIssue({
994
+ code: z4.ZodIssueCode.custom,
995
+ message: `env.${key}.anyOf has duplicate members`,
996
+ path: ["env", key, "anyOf"]
997
+ });
998
+ }
999
+ if (!members.includes(key)) {
1000
+ ctx.addIssue({
1001
+ code: z4.ZodIssueCode.custom,
1002
+ message: `env.${key}.anyOf must include "${key}" itself as a member`,
1003
+ path: ["env", key, "anyOf"]
1004
+ });
1005
+ }
1006
+ }
1007
+ });
1008
+ var RoleEntrySchema = z4.object({
1009
+ contract: z4.string().refine((p) => p.includes("/"), {
1010
+ message: 'must be a path (contain at least one "/"), not a bare filename'
1011
+ })
1012
+ });
1013
+ var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
1014
+ var BRIEF_BUILTINS = [
1015
+ "tier",
1016
+ "testPlan",
1017
+ "testPlanExclusivity",
1018
+ "principalPlaceholder",
1019
+ "surfaceMap",
1020
+ "docUpdateList",
1021
+ "worktreeStep0",
1022
+ "stopConditions",
1023
+ "autonomyClause",
1024
+ "project",
1025
+ "for",
1026
+ "closesN",
1027
+ "premiseCoverage",
1028
+ "issueRationale",
1029
+ "objectives",
1030
+ "briefSections",
1031
+ "milestoneShape"
1032
+ ];
1033
+ var BriefSectionSchema = z4.union([
1034
+ z4.object({ builtin: z4.enum(BRIEF_BUILTINS) }),
1035
+ z4.object({ heading: z4.string().min(1), name: z4.string().optional() }),
1036
+ z4.object({ field: z4.string().min(1), name: z4.string().optional() }),
1037
+ z4.object({ phrase: z4.string().min(1), name: z4.string().optional() })
1038
+ ]);
1039
+ var BriefSchemaSchema = z4.object({
1040
+ pr: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
1041
+ issue: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
1042
+ milestone: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
1043
+ ack: z4.array(z4.enum(BRIEF_BUILTINS)).optional()
1044
+ });
1045
+ function isSafeRepoRelPath(p) {
1046
+ if (p.length === 0)
1047
+ return false;
1048
+ if (p.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(p))
1049
+ return false;
1050
+ return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
1051
+ }
1052
+ var SafeRepoRelPath = z4.string().refine(isSafeRepoRelPath, {
1053
+ message: "must be a repo-root-relative path with no `..` segment or absolute root"
1054
+ });
1055
+ var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
1056
+ function isCanonicalHookBlockPath(p) {
1057
+ return CANONICAL_HOOK_BLOCK_PREFIXES.some((prefix) => p.startsWith(prefix) && p.length > prefix.length);
1058
+ }
1059
+ var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
1060
+ message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
1061
+ });
1062
+ var ManagedBlockRecordSchema = z4.object({
1063
+ path: ManagedHookBlockPath,
1064
+ marker: z4.string(),
1065
+ comment: z4.enum(["hash", "html"])
1066
+ });
1067
+ var ManagedManifestSchema = z4.object({
1068
+ version: z4.number().int().positive(),
1069
+ files: z4.array(SafeRepoRelPath),
1070
+ blocks: z4.array(ManagedBlockRecordSchema),
1071
+ labels: z4.array(z4.string()),
1072
+ agents: z4.array(z4.enum(AGENT_VENDORS)).optional()
1073
+ });
1074
+ var ProjectEntrySchema = z4.object({
1075
+ name: z4.string().min(1),
1076
+ description: z4.string().min(1).optional(),
1077
+ path: z4.string().min(1).optional()
1078
+ });
1079
+ function parseTokensCollectDeclaration(value) {
1080
+ const m = value.trim().match(/^(\S+)\s+(\S+)$/);
1081
+ if (!m)
1082
+ return null;
1083
+ const [, interpreter, script] = m;
1084
+ if (!isSafeRepoRelPath(script))
1085
+ return null;
1086
+ return { interpreter, script };
1087
+ }
1088
+ var VinayaConfigSchema = z4.object({
1089
+ rings: z4.object({
1090
+ ring1_forgeWriteInterception: z4.boolean(),
1091
+ ring2_asyncAudits: z4.boolean()
1092
+ }).optional(),
1093
+ checks: z4.record(z4.string(), CheckEntrySchema).optional(),
1094
+ roles: z4.record(z4.string(), RoleEntrySchema).optional(),
1095
+ briefSchema: BriefSchemaSchema.optional(),
1096
+ managed: ManagedManifestSchema.optional(),
1097
+ principals: z4.array(z4.string()).min(1).optional(),
1098
+ releaseActor: z4.string().min(1).optional(),
1099
+ ci: z4.object({ setup: z4.string().min(1) }).optional(),
1100
+ tokens: z4.object({
1101
+ collect: z4.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
1102
+ message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
1103
+ })
1104
+ }).optional(),
1105
+ blastRadius: z4.object({ extraDomains: z4.array(z4.string()).optional() }).optional(),
1106
+ proseGates: z4.object({
1107
+ doctrineRoot: z4.string().min(1).optional(),
1108
+ readerFacingPrefix: z4.string().min(1).optional(),
1109
+ readerFacingSuffix: z4.string().min(1).optional(),
1110
+ legacySlugDir: z4.string().min(1).optional()
1111
+ }).optional(),
1112
+ projects: z4.array(ProjectEntrySchema).optional(),
1113
+ dispatch: z4.object({
1114
+ timeoutMs: z4.number().int().positive().optional(),
1115
+ killGraceMs: z4.number().int().positive().optional(),
1116
+ agent: z4.enum(["claude", "codex", "gemini"]).optional()
1117
+ }).optional(),
1118
+ reviewPolicy: z4.object({
1119
+ codeReviewThreshold: z4.string().min(1).optional(),
1120
+ securityThreshold: z4.string().min(1).optional(),
1121
+ maxRounds: z4.number().optional()
1122
+ }).optional(),
1123
+ prePush: z4.object({
1124
+ alwaysRun: z4.array(z4.string()).optional()
1125
+ }).optional(),
1126
+ report: z4.object({
1127
+ commandTimeoutMs: z4.number().int().positive().optional()
1128
+ }).superRefine((report, ctx) => {
1129
+ const commandTimeoutMs = report.commandTimeoutMs;
1130
+ if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
1131
+ ctx.addIssue({
1132
+ code: z4.ZodIssueCode.custom,
1133
+ path: ["commandTimeoutMs"],
1134
+ message: "must be at most 3600000 (1 hour)"
1135
+ });
1136
+ }
1137
+ }).optional()
1138
+ });
1139
+ var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
1140
+ var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
1141
+ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
1142
+ function findLocalConfig() {
1143
+ let dir = process.cwd();
1144
+ while (true) {
1145
+ const candidate = join(dir, LOCAL_CONFIG_FILENAME);
1146
+ if (existsSync(candidate))
1147
+ return candidate;
1148
+ if (existsSync(join(dir, ".git")))
1149
+ return null;
1150
+ const parent = dirname(dir);
1151
+ if (parent === dir)
1152
+ return null;
1153
+ dir = parent;
1154
+ }
1155
+ }
1156
+ function configPath() {
1157
+ const local = findLocalConfig();
1158
+ if (local)
1159
+ return local;
1160
+ if (existsSync(GLOBAL_CONFIG_PATH))
1161
+ return GLOBAL_CONFIG_PATH;
1162
+ return null;
1163
+ }
1164
+ function globalChecksIgnoredWarning(path) {
1165
+ return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
1166
+ }
1167
+ function globalPrincipalsIgnoredWarning(path) {
1168
+ return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
1169
+ }
1170
+ function globalRolesIgnoredWarning(path) {
1171
+ return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
1172
+ }
1173
+ function globalReleaseActorIgnoredWarning(path) {
1174
+ return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
1175
+ }
1176
+ function globalTokensCollectIgnoredWarning(path) {
1177
+ return `${path}: "tokens" in the global config is ignored — tokens.collect may only be declared from a repo-local vinaya.config.json.`;
1178
+ }
1179
+ function stripGlobalOnlyKeys(config, path) {
1180
+ if (path !== GLOBAL_CONFIG_PATH)
1181
+ return config;
1182
+ let result = config;
1183
+ if (result.checks && Object.keys(result.checks).length > 0) {
1184
+ console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
1185
+ result = { ...result, checks: undefined };
1186
+ }
1187
+ if (result.roles && Object.keys(result.roles).length > 0) {
1188
+ console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
1189
+ result = { ...result, roles: undefined };
1190
+ }
1191
+ if (result.principals && result.principals.length > 0) {
1192
+ console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
1193
+ result = { ...result, principals: undefined };
1194
+ }
1195
+ if (result.releaseActor) {
1196
+ console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
1197
+ result = { ...result, releaseActor: undefined };
1198
+ }
1199
+ if (result.tokens) {
1200
+ console.error(`⚠ ${globalTokensCollectIgnoredWarning(path)}`);
1201
+ result = { ...result, tokens: undefined };
1202
+ }
1203
+ return result;
1204
+ }
1205
+ function loadConfig() {
1206
+ const path = configPath();
1207
+ if (!path)
1208
+ return null;
1209
+ try {
1210
+ const raw = JSON.parse(readFileSync2(path, "utf-8"));
1211
+ return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
1212
+ } catch {
1213
+ return null;
1214
+ }
1215
+ }
1216
+ var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
1217
+
1218
+ // src/lib/remote-base.ts
1219
+ import { execFileSync as execFileSync2 } from "node:child_process";
1220
+ import { join as join2 } from "node:path";
1221
+ function cleanGitEnv() {
1222
+ const env = { ...process.env };
1223
+ for (const key of Object.keys(env)) {
1224
+ if (key.startsWith("GIT_"))
1225
+ delete env[key];
1226
+ }
1227
+ return env;
1228
+ }
1229
+ function git(repoRoot, args) {
1230
+ try {
1231
+ return execFileSync2("git", args, {
1232
+ cwd: repoRoot,
1233
+ encoding: "utf8",
1234
+ stdio: ["ignore", "pipe", "ignore"],
1235
+ env: cleanGitEnv()
1236
+ }).trim();
1237
+ } catch {
1238
+ return "";
1239
+ }
1240
+ }
1241
+ function resolveRemoteBase(repoRoot) {
1242
+ const upstream = git(repoRoot, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
1243
+ if (upstream)
1244
+ return upstream;
1245
+ const originMain = git(repoRoot, ["rev-parse", "--verify", "--quiet", "origin/main"]);
1246
+ if (originMain)
1247
+ return "origin/main";
1248
+ return "HEAD~1";
1249
+ }
1250
+ function changedFilesSinceRemoteBase(repoRoot) {
1251
+ const base = resolveRemoteBase(repoRoot);
1252
+ const out = git(repoRoot, ["diff", "--name-only", "--diff-filter=ACMR", `${base}...HEAD`]);
1253
+ return out.split(`
1254
+ `).map((s) => s.trim()).filter(Boolean);
1255
+ }
1256
+ function changedFilesSinceRemoteBaseAbsolute(repoRoot) {
1257
+ return changedFilesSinceRemoteBase(repoRoot).map((f) => join2(repoRoot, f));
1258
+ }
1259
+ function addedOrRenamedFilesSinceRemoteBase(repoRoot) {
1260
+ const base = resolveRemoteBase(repoRoot);
1261
+ const out = git(repoRoot, ["diff", "--name-only", "--diff-filter=AR", `${base}...HEAD`]);
1262
+ return out.split(`
1263
+ `).map((s) => s.trim()).filter(Boolean);
1264
+ }
1265
+ function addedOrRenamedFilesSinceRemoteBaseAbsolute(repoRoot) {
1266
+ return addedOrRenamedFilesSinceRemoteBase(repoRoot).map((f) => join2(repoRoot, f));
1267
+ }
1268
+
1269
+ // src/lib/test-selector.ts
1270
+ import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
1271
+ import { dirname as dirname2, extname, isAbsolute, join as join3, resolve as resolve2 } from "node:path";
1272
+ var FROM_CLAUSE_RE = /^\s*(?:import|export)\b[^'"]*?\bfrom\s*(['"])([^'"]+)\1/gm;
1273
+ var BARE_IMPORT_RE = /^\s*import\s*(['"])([^'"]+)\1/gm;
1274
+ var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
1275
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1276
+ var SKIP_DIRS = new Set(["node_modules", "dist", ".git", "build", "coverage"]);
1277
+ function extractImportSpecifiers(source) {
1278
+ const specifiers = new Set;
1279
+ for (const re of [FROM_CLAUSE_RE, BARE_IMPORT_RE, DYNAMIC_IMPORT_RE]) {
1280
+ re.lastIndex = 0;
1281
+ let match = re.exec(source);
1282
+ while (match !== null) {
1283
+ specifiers.add(match[2]);
1284
+ match = re.exec(source);
1285
+ }
1286
+ }
1287
+ return [...specifiers];
1288
+ }
1289
+ function walkFiles(root) {
1290
+ const out = [];
1291
+ let entries;
1292
+ try {
1293
+ entries = readdirSync(root);
1294
+ } catch {
1295
+ return out;
1296
+ }
1297
+ for (const entry2 of entries) {
1298
+ if (SKIP_DIRS.has(entry2))
1299
+ continue;
1300
+ const abs = join3(root, entry2);
1301
+ let s;
1302
+ try {
1303
+ s = statSync(abs);
1304
+ } catch {
1305
+ continue;
1306
+ }
1307
+ if (s.isDirectory()) {
1308
+ out.push(...walkFiles(abs));
1309
+ } else {
1310
+ out.push(abs);
1311
+ }
1312
+ }
1313
+ return out;
1314
+ }
1315
+ function resolveRelativeImport(fromFile, specifier, knownFiles) {
1316
+ if (!specifier.startsWith("."))
1317
+ return null;
1318
+ const base = resolve2(dirname2(fromFile), specifier);
1319
+ if (knownFiles.has(base))
1320
+ return base;
1321
+ for (const ext of SOURCE_EXTENSIONS) {
1322
+ if (knownFiles.has(base + ext))
1323
+ return base + ext;
1324
+ }
1325
+ for (const ext of SOURCE_EXTENSIONS) {
1326
+ const indexed = join3(base, `index${ext}`);
1327
+ if (knownFiles.has(indexed))
1328
+ return indexed;
1329
+ }
1330
+ return null;
1331
+ }
1332
+ function isBunTestScript(script) {
1333
+ if (!script)
1334
+ return true;
1335
+ return /(^|[\s;&|])bun\s+test\b/.test(script);
1336
+ }
1337
+ function discoverWorkspacePackages(repoRoot) {
1338
+ const rootPkgPath = join3(repoRoot, "package.json");
1339
+ let workspaces = [];
1340
+ try {
1341
+ const rootPkg = JSON.parse(readFileSync3(rootPkgPath, "utf8"));
1342
+ workspaces = rootPkg.workspaces ?? [];
1343
+ } catch {
1344
+ return [];
1345
+ }
1346
+ const out = [];
1347
+ for (const glob of workspaces) {
1348
+ const m = /^([^*]+)\*$/.exec(glob);
1349
+ if (!m)
1350
+ continue;
1351
+ const parentDir = join3(repoRoot, m[1].replace(/\/$/, ""));
1352
+ let entries;
1353
+ try {
1354
+ entries = readdirSync(parentDir);
1355
+ } catch {
1356
+ continue;
1357
+ }
1358
+ for (const entry2 of entries) {
1359
+ const dir = join3(parentDir, entry2);
1360
+ const pkgPath = join3(dir, "package.json");
1361
+ try {
1362
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1363
+ if (pkg.name)
1364
+ out.push({ dir, name: pkg.name, bunTestCompatible: isBunTestScript(pkg.scripts?.test) });
1365
+ } catch {}
1366
+ }
1367
+ }
1368
+ return out;
1369
+ }
1370
+ function isTestFile(path) {
1371
+ const withoutExt = path.slice(0, -extname(path).length);
1372
+ return withoutExt.endsWith(".test");
1373
+ }
1374
+ function buildFileGraph(pkgDir) {
1375
+ const files = walkFiles(pkgDir).filter((f) => SOURCE_EXTENSIONS.includes(extname(f)));
1376
+ const knownFiles = new Set(files);
1377
+ const edges = new Map;
1378
+ for (const file of files) {
1379
+ let source;
1380
+ try {
1381
+ source = readFileSync3(file, "utf8");
1382
+ } catch {
1383
+ continue;
1384
+ }
1385
+ const deps = [];
1386
+ for (const specifier of extractImportSpecifiers(source)) {
1387
+ if (specifier.startsWith(".")) {
1388
+ const resolved = resolveRelativeImport(file, specifier, knownFiles);
1389
+ if (resolved)
1390
+ deps.push(resolved);
1391
+ } else {
1392
+ deps.push(`external:${specifier}`);
1393
+ }
1394
+ }
1395
+ edges.set(file, deps);
1396
+ }
1397
+ return { files, edges };
1398
+ }
1399
+ function selectAffectedTestFiles(repoRoot, changedFiles, options = {}) {
1400
+ const packages = discoverWorkspacePackages(repoRoot);
1401
+ const absChanged = new Set(changedFiles.map((f) => isAbsolute(f) ? f : join3(repoRoot, f)));
1402
+ const alwaysRunRegexes = (options.alwaysRun ?? []).map(globToRegex);
1403
+ const addedOrRenamed = new Set((options.addedOrRenamed ?? []).map((f) => isAbsolute(f) ? f : join3(repoRoot, f)));
1404
+ const changedPackageNames = new Set;
1405
+ for (const pkg of packages) {
1406
+ for (const f of absChanged) {
1407
+ if (f.startsWith(`${pkg.dir}/`)) {
1408
+ changedPackageNames.add(pkg.name);
1409
+ break;
1410
+ }
1411
+ }
1412
+ }
1413
+ const selected = [];
1414
+ let totalTestFiles = 0;
1415
+ for (const pkg of packages) {
1416
+ const { files, edges } = buildFileGraph(pkg.dir);
1417
+ const testFiles = files.filter(isTestFile);
1418
+ totalTestFiles += testFiles.length;
1419
+ if (!pkg.bunTestCompatible)
1420
+ continue;
1421
+ const packageChanged = new Set([...absChanged].filter((f) => f.startsWith(`${pkg.dir}/`)));
1422
+ const relevant = packageChanged.size > 0 || changedPackageNames.size > 0;
1423
+ for (const test of testFiles) {
1424
+ const forced = alwaysRunRegexes.some((re) => re.test(test.slice(repoRoot.length + 1))) || addedOrRenamed.has(test);
1425
+ if (forced || relevant && reachesChangedFile(test, edges, packageChanged, changedPackageNames, pkg.name)) {
1426
+ selected.push(test);
1427
+ }
1428
+ }
1429
+ }
1430
+ return { selected, totalTestFiles };
1431
+ }
1432
+ function reachesChangedFile(start, edges, changedInPackage, changedOtherPackages, ownPackageName) {
1433
+ const visited = new Set;
1434
+ const stack = [start];
1435
+ while (stack.length > 0) {
1436
+ const current = stack.pop();
1437
+ if (visited.has(current))
1438
+ continue;
1439
+ visited.add(current);
1440
+ if (changedInPackage.has(current))
1441
+ return true;
1442
+ for (const dep of edges.get(current) ?? []) {
1443
+ if (dep.startsWith("external:")) {
1444
+ const specifier = dep.slice("external:".length);
1445
+ for (const otherPkg of changedOtherPackages) {
1446
+ if (otherPkg === ownPackageName)
1447
+ continue;
1448
+ if (specifier === otherPkg || specifier.startsWith(`${otherPkg}/`))
1449
+ return true;
1450
+ }
1451
+ continue;
1452
+ }
1453
+ if (!visited.has(dep))
1454
+ stack.push(dep);
1455
+ }
1456
+ }
1457
+ return false;
1458
+ }
1459
+
1460
+ // src/lib/pre-push-select-tests.ts
1461
+ function main() {
1462
+ const repoRoot = process.cwd();
1463
+ const changed = changedFilesSinceRemoteBaseAbsolute(repoRoot);
1464
+ const addedOrRenamed = addedOrRenamedFilesSinceRemoteBaseAbsolute(repoRoot);
1465
+ const alwaysRun = loadConfig()?.prePush?.alwaysRun ?? [];
1466
+ const { selected, totalTestFiles } = selectAffectedTestFiles(repoRoot, changed, { alwaysRun, addedOrRenamed });
1467
+ for (const file of selected)
1468
+ process.stdout.write(`${file}
1469
+ `);
1470
+ process.stderr.write(`vinaya pre-push: selected ${selected.length} of ${totalTestFiles} test file(s)
1471
+ `);
1472
+ }
1473
+ main();