@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.
- package/README.md +6 -3
- package/aeg-root/enforcement.md +5 -5
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/developer.md +23 -17
- package/aeg-root/roles/planner.md +4 -0
- package/aeg-root/roles/principal.md +8 -2
- package/aeg-root/roles/reviewer.md +9 -5
- package/aeg-root/roles/security.md +9 -5
- package/dist/checks/bin/check-body-bare-digits.js +1038 -257
- package/dist/checks/bin/check-branch-topology.js +963 -199
- package/dist/checks/bin/check-brief-shape.js +1276 -203
- package/dist/checks/bin/check-changeset-coverage.js +1677 -281
- package/dist/checks/bin/check-closes-n.js +967 -203
- package/dist/checks/bin/check-coherence.js +1127 -284
- package/dist/checks/bin/check-dead-branch-push.js +910 -193
- package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
- package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
- package/dist/checks/bin/check-doc-coverage.js +1675 -279
- package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
- package/dist/checks/bin/check-doctrine-portability.js +1672 -276
- package/dist/checks/bin/check-evidence-fresh.js +2526 -322
- package/dist/checks/bin/check-exec-bits.js +1670 -274
- package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
- package/dist/checks/bin/check-issue-assignment.js +965 -201
- package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
- package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
- package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
- package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
- package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
- package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
- package/dist/checks/bin/check-main-branch-refusal.js +910 -193
- package/dist/checks/bin/check-no-disk-state.js +910 -193
- package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
- package/dist/checks/bin/check-pr-report-density.js +910 -193
- package/dist/checks/bin/check-quoted-command.js +1651 -274
- package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
- package/dist/checks/bin/check-registry-gates.js +997 -193
- package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
- package/dist/checks/bin/check-review-gate.js +1163 -316
- package/dist/checks/bin/check-single-plan-pr.js +910 -193
- package/dist/checks/bin/check-surface-scope.js +973 -201
- package/dist/checks/bin/check-test-plan.js +939 -206
- package/dist/checks/bin/check-token-collection-wired.js +910 -193
- package/dist/checks/bin/check-token-report.js +910 -193
- package/dist/checks/bin/check-workspace-escape.js +1668 -272
- package/dist/index.js +10602 -5352
- package/dist/lib/pre-push-changed-files.js +57 -0
- package/dist/lib/pre-push-select-tests.js +1473 -0
- 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();
|