@attalabs/vinaya 0.24.1 → 0.25.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 (117) hide show
  1. package/README.md +25 -2
  2. package/aeg-root/contracts/developer-reviewer.md +3 -0
  3. package/aeg-root/contracts/planner-brief.md +13 -0
  4. package/aeg-root/enforcement.md +10 -6
  5. package/aeg-root/process.md +4 -4
  6. package/aeg-root/roles/brief-author.md +4 -2
  7. package/aeg-root/roles/developer.md +20 -14
  8. package/aeg-root/roles/planner.md +14 -0
  9. package/aeg-root/roles/principal.md +3 -0
  10. package/aeg-root/roles/reviewer.md +23 -7
  11. package/aeg-root/roles/security.md +21 -6
  12. package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
  13. package/aeg-root/state-machine.md +2 -2
  14. package/aeg-root/templates/brief-template.md +20 -4
  15. package/aeg-root/templates/issue-rationale-template.md +34 -1
  16. package/aeg-root/templates/pr-report-template.md +5 -13
  17. package/dist/checks/bin/check-body-bare-digits.js +572 -127
  18. package/dist/checks/bin/check-branch-topology.js +629 -73
  19. package/dist/checks/bin/check-brief-shape.js +1675 -70
  20. package/dist/checks/bin/check-changeset-coverage.js +572 -127
  21. package/dist/checks/bin/check-closes-n.js +629 -73
  22. package/dist/checks/bin/check-coherence.js +696 -134
  23. package/dist/checks/bin/check-dead-branch-push.js +505 -66
  24. package/dist/checks/bin/check-dispatch-readiness.js +696 -134
  25. package/dist/checks/bin/check-doc-coverage-push.js +572 -127
  26. package/dist/checks/bin/check-doc-coverage.js +572 -127
  27. package/dist/checks/bin/check-doctrine-no-procedures.js +572 -127
  28. package/dist/checks/bin/check-doctrine-portability.js +572 -127
  29. package/dist/checks/bin/check-evidence-fresh.js +572 -127
  30. package/dist/checks/bin/check-exec-bits.js +572 -127
  31. package/dist/checks/bin/check-first-push-dispatch.js +696 -134
  32. package/dist/checks/bin/check-issue-assignment.js +629 -73
  33. package/dist/checks/bin/check-main-branch-refusal.js +505 -66
  34. package/dist/checks/bin/check-no-disk-state.js +505 -66
  35. package/dist/checks/bin/check-pr-report-density.js +505 -66
  36. package/dist/checks/bin/check-quoted-command.js +572 -127
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +610 -133
  38. package/dist/checks/bin/check-registry-gates.js +516 -68
  39. package/dist/checks/bin/check-retired-vocabulary.js +572 -127
  40. package/dist/checks/bin/check-review-gate.js +646 -130
  41. package/dist/checks/bin/check-single-plan-pr.js +505 -66
  42. package/dist/checks/bin/check-test-plan.js +505 -66
  43. package/dist/checks/bin/check-token-collection-wired.js +505 -66
  44. package/dist/checks/bin/check-token-report.js +505 -66
  45. package/dist/checks/bin/check-workspace-escape.js +572 -127
  46. package/dist/index.js +7696 -4271
  47. package/package.json +1 -1
  48. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
  49. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  52. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.js} +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
  112. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
  114. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  115. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
  116. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
  117. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
@@ -1940,13 +1940,18 @@ function parseInlineFieldList(section) {
1940
1940
  }
1941
1941
  // ../../packages/aeg-core/src/verdict-extraction.ts
1942
1942
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1943
- function firstThreeLines(comment) {
1943
+ var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1944
+ function firstFiveLines(comment) {
1944
1945
  return comment.split(`
1945
- `).slice(0, 3).join(`
1946
+ `).slice(0, 5).join(`
1946
1947
  `);
1947
1948
  }
1948
1949
  function extractHeadSha(comment) {
1949
- const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1950
+ const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
1951
+ return m ? m[1].toLowerCase() : null;
1952
+ }
1953
+ function extractObjectivesVersion(comment) {
1954
+ const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1950
1955
  return m ? m[1].toLowerCase() : null;
1951
1956
  }
1952
1957
  function extractVerdict(comments, valuePattern, missingLabel) {
@@ -1955,21 +1960,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1955
1960
  return {
1956
1961
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1957
1962
  headSha: null,
1963
+ objectivesVersion: null,
1958
1964
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1959
1965
  };
1960
1966
  }
1961
1967
  const latest = candidates[candidates.length - 1];
1962
- const m = firstThreeLines(latest).match(valuePattern);
1968
+ const m = firstFiveLines(latest).match(valuePattern);
1963
1969
  if (!m) {
1964
1970
  return {
1965
- value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1971
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1966
1972
  headSha: null,
1967
- danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1973
+ objectivesVersion: null,
1974
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1968
1975
  };
1969
1976
  }
1970
1977
  return {
1971
1978
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1972
1979
  headSha: extractHeadSha(latest),
1980
+ objectivesVersion: extractObjectivesVersion(latest),
1973
1981
  danglingNote: null
1974
1982
  };
1975
1983
  }
@@ -2326,8 +2334,105 @@ function overrideActive(opts) {
2326
2334
  return true;
2327
2335
  return false;
2328
2336
  }
2329
- // ../../packages/aeg-core/src/premise-check.ts
2337
+ // ../../packages/aeg-core/src/objectives.ts
2330
2338
  import { createHash as createHash2 } from "node:crypto";
2339
+ var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2340
+ var NEXT_HEADING_RE = /^##[ \t]/m;
2341
+ var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2342
+ function hasBacktickedPath(text) {
2343
+ let i = 0;
2344
+ while (i < text.length) {
2345
+ const start = text.indexOf("`", i);
2346
+ if (start === -1)
2347
+ return false;
2348
+ const end = text.indexOf("`", start + 1);
2349
+ if (end === -1)
2350
+ return false;
2351
+ if (text.slice(start + 1, end).includes("/"))
2352
+ return true;
2353
+ i = end + 1;
2354
+ }
2355
+ return false;
2356
+ }
2357
+ var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
2358
+ function stripObjectiveBackticks(text) {
2359
+ return text.replace(/`[^`\n]*`/g, " ");
2360
+ }
2361
+ function wordCount(text) {
2362
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2363
+ }
2364
+ function objectivesSectionText(body) {
2365
+ const heading = HEADING_RE.exec(body);
2366
+ if (!heading)
2367
+ return null;
2368
+ const afterHeading = body.slice(heading.index + heading[0].length);
2369
+ const next = NEXT_HEADING_RE.exec(afterHeading);
2370
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
2371
+ }
2372
+ function hasObjectivesHeading(body) {
2373
+ return HEADING_RE.test(body);
2374
+ }
2375
+ function objectivesOf(body) {
2376
+ const section = objectivesSectionText(body);
2377
+ if (section === null) {
2378
+ return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
2379
+ }
2380
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
2381
+ if (lines.length === 0) {
2382
+ return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
2383
+ }
2384
+ const objectives = [];
2385
+ const errors = [];
2386
+ for (const line of lines) {
2387
+ const m = OBJECTIVE_LINE_RE.exec(line);
2388
+ if (!m) {
2389
+ errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
2390
+ continue;
2391
+ }
2392
+ const n = m[1];
2393
+ const text = m[2].trim();
2394
+ if (text.length === 0) {
2395
+ errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
2396
+ continue;
2397
+ }
2398
+ if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
2399
+ errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Brief Author maps it to files).`);
2400
+ continue;
2401
+ }
2402
+ objectives.push({ id: `O${n}`, text });
2403
+ }
2404
+ for (let i = 0;i < objectives.length; i++) {
2405
+ const expected = i + 1;
2406
+ const actual = Number.parseInt(objectives[i].id.slice(1), 10);
2407
+ if (actual !== expected) {
2408
+ errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
2409
+ break;
2410
+ }
2411
+ }
2412
+ if (errors.length > 0)
2413
+ return { ok: false, errors };
2414
+ return { ok: true, objectives };
2415
+ }
2416
+ function normalizeText(text) {
2417
+ return text.trim().replace(/\s+/g, " ");
2418
+ }
2419
+ function objectivesVersion(objectives) {
2420
+ const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
2421
+ `);
2422
+ return createHash2("sha256").update(normalized).digest("hex");
2423
+ }
2424
+ function isIssueNotFoundError(err) {
2425
+ const stderr = err?.stderr;
2426
+ const haystack = [
2427
+ err?.message ?? "",
2428
+ typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
2429
+ ].join(`
2430
+ `);
2431
+ return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2432
+ }
2433
+
2434
+ // ../../packages/aeg-core/src/premise-check.ts
2435
+ import { createHash as createHash3 } from "node:crypto";
2331
2436
  var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
2332
2437
  function isPremiseHeader(line) {
2333
2438
  const stripped = line.replace(/[*#]/g, "").trim();
@@ -2398,7 +2503,7 @@ function checkPremises(assertions, fileReader) {
2398
2503
  } else if (a.kind === "absent" && content.includes(a.value)) {
2399
2504
  failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
2400
2505
  } else if (a.kind === "sha256") {
2401
- const actual = createHash2("sha256").update(content).digest("hex");
2506
+ const actual = createHash3("sha256").update(content).digest("hex");
2402
2507
  if (actual !== a.value.toLowerCase()) {
2403
2508
  failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
2404
2509
  }
@@ -2759,8 +2864,55 @@ function checkDefeatCases(prBody) {
2759
2864
  ]
2760
2865
  };
2761
2866
  }
2867
+ var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
2868
+ var OBJECTIVE_REF_RE = /O(\d+)/g;
2869
+ function citedObjectiveIds(section6) {
2870
+ const ids = new Set;
2871
+ for (const m of section6.matchAll(PART_CITATION_RE)) {
2872
+ for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
2873
+ ids.add(Number.parseInt(r[1], 10));
2874
+ }
2875
+ return ids;
2876
+ }
2877
+ function checkObjectivesCopy(prBody, issueObjectives) {
2878
+ const parsed = objectivesOf(prBody);
2879
+ if (!parsed.ok) {
2880
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
2881
+ }
2882
+ if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
2883
+ return {
2884
+ status: "fail",
2885
+ errors: [
2886
+ "brief-validation objectives copy: the brief's `## Objectives` section does not match the Issue's — copy the Issue's `## Objectives` section (aeg-root/roles/developer.md)."
2887
+ ]
2888
+ };
2889
+ }
2890
+ return { status: "pass", errors: [] };
2891
+ }
2892
+ function checkObjectivesCoverage(prBody) {
2893
+ const parsed = objectivesOf(prBody);
2894
+ if (!parsed.ok) {
2895
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
2896
+ }
2897
+ const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
2898
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2899
+ const cited = citedObjectiveIds(section6);
2900
+ const maxDeclared = Math.max(...declaredIds, 0);
2901
+ const errors = [];
2902
+ for (const id of declaredIds) {
2903
+ if (!cited.has(id)) {
2904
+ errors.push(`brief-validation objectives coverage: O${id} is not cited by any Part in §6 — every objective must be cited by at least one Part.`);
2905
+ }
2906
+ }
2907
+ for (const id of cited) {
2908
+ if (!declaredIds.has(id)) {
2909
+ errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
2910
+ }
2911
+ }
2912
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2913
+ }
2762
2914
  function checkBriefSections(prBody, readTier, options = {}) {
2763
- const { requireClosesN = true, consumersOf = () => [] } = options;
2915
+ const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
2764
2916
  const results = [
2765
2917
  checkTierField(prBody, readTier),
2766
2918
  checkTestPlan(prBody),
@@ -2778,6 +2930,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
2778
2930
  checkCommandsCarryOutput(prBody),
2779
2931
  checkConsumerTests(prBody, consumersOf),
2780
2932
  checkDefeatCases(prBody),
2933
+ ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2781
2934
  ...requireClosesN ? [checkClosesN(prBody)] : []
2782
2935
  ];
2783
2936
  return { errors: results.flatMap((r) => r.errors) };
@@ -2976,14 +3129,14 @@ function checkSectionDensity(prBody, heading) {
2976
3129
  ]
2977
3130
  };
2978
3131
  }
2979
- function checkSummaryDensity(prBody) {
2980
- return checkSectionDensity(prBody, "Summary");
3132
+ function checkDecisionsDensity(prBody) {
3133
+ return checkSectionDensity(prBody, "Decisions");
2981
3134
  }
2982
3135
  function checkScopeDensity(prBody) {
2983
3136
  return checkSectionDensity(prBody, "Scope");
2984
3137
  }
2985
3138
  function checkPrReportDensity(prBody) {
2986
- const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
3139
+ const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
2987
3140
  return { errors: results.flatMap((r) => r.errors) };
2988
3141
  }
2989
3142
  // ../../packages/aeg-core/src/issue-validation.ts
@@ -3015,6 +3168,15 @@ function checkIssueRationale(body) {
3015
3168
  }
3016
3169
  return { status: errors.length > 0 ? "fail" : "pass", errors };
3017
3170
  }
3171
+ var OBJECTIVES_SINCE_ISSUE = 404;
3172
+ function checkIssueObjectives(body, issueNumber) {
3173
+ if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
3174
+ return { status: "pass", errors: [] };
3175
+ const result = objectivesOf(body);
3176
+ if (result.ok)
3177
+ return { status: "pass", errors: [] };
3178
+ return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
3179
+ }
3018
3180
  function isTaskIssueLabelSet(labels) {
3019
3181
  return hasLabel("tranche", labels);
3020
3182
  }
@@ -3276,7 +3438,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3276
3438
  continue;
3277
3439
  const errors = [
3278
3440
  ...checkIssueRationale(issue.body).errors,
3279
- ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
3441
+ ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3442
+ ...checkIssueObjectives(issue.body, issue.number).errors
3280
3443
  ];
3281
3444
  if (errors.length === 0)
3282
3445
  continue;
@@ -3391,6 +3554,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3391
3554
  }
3392
3555
  return { ok: true, expectedIssue };
3393
3556
  }
3557
+ // ../../packages/aeg-core/src/archive-task.ts
3558
+ function closesRefs(text) {
3559
+ return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
3560
+ }
3561
+ function extractIssue(body) {
3562
+ const stripped = stripCode(body);
3563
+ const bodyNums = closesRefs(stripped);
3564
+ const anchored = anchoredRegion(stripped, "CLOSES");
3565
+ if (anchored !== null) {
3566
+ const anchorNums = closesRefs(anchored);
3567
+ if (anchorNums.length === 0)
3568
+ return { issue: null, extraIssues: bodyNums, outsideHeader: false };
3569
+ const issue2 = anchorNums[0];
3570
+ return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
3571
+ }
3572
+ const headerNums = closesRefs(headerRegion(stripped));
3573
+ if (bodyNums.length === 0)
3574
+ return { issue: null, extraIssues: [], outsideHeader: false };
3575
+ const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3576
+ return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3577
+ }
3394
3578
  // ../../packages/aeg-core/src/review-gate.ts
3395
3579
  function isBoundToHead(extraction, headSha) {
3396
3580
  if (!extraction.headSha)
@@ -3409,6 +3593,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3409
3593
  function isBoundToPatch(extraction, headSha, patchIdOf) {
3410
3594
  return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3411
3595
  }
3596
+ function isBoundToObjectives(extraction, currentVersion) {
3597
+ if (currentVersion === null)
3598
+ return true;
3599
+ return extraction.objectivesVersion === currentVersion;
3600
+ }
3412
3601
  function checkReviewGate(input) {
3413
3602
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3414
3603
  const waived = isWaiverLabelActorVerified({
@@ -3435,7 +3624,9 @@ function checkReviewGate(input) {
3435
3624
  const securityClean = security.value === "PASS";
3436
3625
  const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3437
3626
  const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3438
- if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
3627
+ const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3628
+ const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3629
+ if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
3439
3630
  return {
3440
3631
  verdict: "pass",
3441
3632
  reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
@@ -3447,11 +3638,15 @@ function checkReviewGate(input) {
3447
3638
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
3448
3639
  } else if (!codeReviewBound) {
3449
3640
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3641
+ } else if (!codeReviewObjectivesBound) {
3642
+ problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3450
3643
  }
3451
3644
  if (!securityClean) {
3452
3645
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
3453
3646
  } else if (!securityBound) {
3454
3647
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3648
+ } else if (!securityObjectivesBound) {
3649
+ problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3455
3650
  }
3456
3651
  if (!mechanicalChecksClean) {
3457
3652
  problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
@@ -3687,55 +3882,20 @@ function checkG6(rows, registeredCheckNames) {
3687
3882
  }
3688
3883
  return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
3689
3884
  }
3690
- // ../../packages/aeg-core/src/dead-branch-push-guard.ts
3691
- function checkDeadBranchPush(input) {
3692
- const { branch, prState, prNumber } = input;
3693
- if (prState === "MERGED") {
3694
- return {
3695
- verdict: "refuse",
3696
- reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
3697
- };
3698
- }
3699
- if (prState === "CLOSED") {
3700
- return {
3701
- verdict: "refuse",
3702
- reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
3703
- };
3704
- }
3705
- if (prState === "OPEN") {
3706
- return {
3707
- verdict: "allow",
3708
- reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
3709
- };
3710
- }
3711
- if (prState === "NONE") {
3712
- return {
3713
- verdict: "allow",
3714
- reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
3715
- };
3716
- }
3717
- return {
3718
- verdict: "allow",
3719
- reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
3720
- };
3721
- }
3722
- // ../../packages/aeg-core/src/baseline-capture.ts
3723
- function captureBaseline(counts, capturedAt) {
3724
- return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
3885
+ // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3886
+ var PRODUCT_SLUG_SCOPE = [
3887
+ "apps/cli/src",
3888
+ ".github/workflows",
3889
+ ".vinaya",
3890
+ "apps/cli/README.md",
3891
+ "packages/sources/README.md"
3892
+ ];
3893
+ function isUnderOrEqual(path, prefix) {
3894
+ return path === prefix || path.startsWith(`${prefix}/`);
3725
3895
  }
3726
- function compareToBaseline(current, baseline) {
3727
- const perTool = current.map((c) => {
3728
- const baselineEntry = baseline.find((b) => b.tool === c.tool);
3729
- const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
3730
- return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
3731
- });
3732
- return {
3733
- withinBudget: perTool.every((t) => t.delta <= 0),
3734
- delta: perTool.reduce((sum, t) => sum + t.delta, 0),
3735
- perTool
3736
- };
3896
+ function isProductScopeFile(path) {
3897
+ return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
3737
3898
  }
3738
- // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3739
3899
  var SHIPS_PREFIX = "aeg-root/";
3740
3900
  function shipsArchivePrefix(shipsPrefix) {
3741
3901
  return `${shipsPrefix}tranches/completed/`;
@@ -3755,6 +3915,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
3755
3915
  }
3756
3916
  if (isSpecFile2(path) || isClaudeMdFile(path))
3757
3917
  return "internal";
3918
+ if (isProductScopeFile(path))
3919
+ return "product";
3758
3920
  return null;
3759
3921
  }
3760
3922
  var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3767,6 +3929,9 @@ function stripNonProse(path, content) {
3767
3929
  }
3768
3930
  return content;
3769
3931
  }
3932
+ function stripNonProseForProduct(path, content) {
3933
+ return path.endsWith(".md") ? stripNonProse(path, content) : content;
3934
+ }
3770
3935
  function lineAt(content, index) {
3771
3936
  let line = 1;
3772
3937
  for (let i = 0;i < index; i++) {
@@ -3791,12 +3956,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3791
3956
  { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
3792
3957
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
3793
3958
  ];
3959
+ const productPatterns = [
3960
+ { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
3961
+ ];
3794
3962
  for (const file of files) {
3795
3963
  const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
3796
- if (!cls || !SWEPT_CLASSES.has(cls))
3964
+ if (!cls || cls === "internal")
3797
3965
  continue;
3798
- const scrubbed = stripNonProse(file.path, file.content);
3799
- for (const { pattern, what, group } of patterns) {
3966
+ if (cls !== "product" && !SWEPT_CLASSES.has(cls))
3967
+ continue;
3968
+ const blocking = cls === "product";
3969
+ const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
3970
+ const patternsToRun = blocking ? productPatterns : patterns;
3971
+ for (const { pattern, what, group } of patternsToRun) {
3800
3972
  pattern.lastIndex = 0;
3801
3973
  let match = pattern.exec(scrubbed);
3802
3974
  while (match !== null) {
@@ -3804,7 +3976,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3804
3976
  findings.push({
3805
3977
  file: file.path,
3806
3978
  line: lineAt(scrubbed, match.index),
3807
- message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`
3979
+ message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
3980
+ blocking
3808
3981
  });
3809
3982
  match = pattern.exec(scrubbed);
3810
3983
  }
@@ -3854,7 +4027,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
3854
4027
  findings.push({
3855
4028
  file: file.path,
3856
4029
  line,
3857
- message: `uses coined term "${term}" without defining it inline or linking the glossary`
4030
+ message: `uses coined term "${term}" without defining it inline or linking the glossary`,
4031
+ blocking: false
3858
4032
  });
3859
4033
  }
3860
4034
  }
@@ -3877,6 +4051,7 @@ function parseGlossaryTerms(glossaryContent) {
3877
4051
  }
3878
4052
  return terms;
3879
4053
  }
4054
+
3880
4055
  // ../../packages/aeg-core/src/quoted-command.ts
3881
4056
  var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
3882
4057
  var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3971,6 +4146,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
3971
4146
  }
3972
4147
  return findings;
3973
4148
  }
4149
+ // ../../packages/aeg-core/src/dead-branch-push-guard.ts
4150
+ function checkDeadBranchPush(input) {
4151
+ const { branch, prState, prNumber } = input;
4152
+ if (prState === "MERGED") {
4153
+ return {
4154
+ verdict: "refuse",
4155
+ reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
4156
+ };
4157
+ }
4158
+ if (prState === "CLOSED") {
4159
+ return {
4160
+ verdict: "refuse",
4161
+ reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
4162
+ };
4163
+ }
4164
+ if (prState === "OPEN") {
4165
+ return {
4166
+ verdict: "allow",
4167
+ reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
4168
+ };
4169
+ }
4170
+ if (prState === "NONE") {
4171
+ return {
4172
+ verdict: "allow",
4173
+ reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
4174
+ };
4175
+ }
4176
+ return {
4177
+ verdict: "allow",
4178
+ reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
4179
+ };
4180
+ }
4181
+ // ../../packages/aeg-core/src/baseline-capture.ts
4182
+ function captureBaseline(counts, capturedAt) {
4183
+ return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
4184
+ }
4185
+ function compareToBaseline(current, baseline) {
4186
+ const perTool = current.map((c) => {
4187
+ const baselineEntry = baseline.find((b) => b.tool === c.tool);
4188
+ const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
4189
+ return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
4190
+ });
4191
+ return {
4192
+ withinBudget: perTool.every((t) => t.delta <= 0),
4193
+ delta: perTool.reduce((sum, t) => sum + t.delta, 0),
4194
+ perTool
4195
+ };
4196
+ }
3974
4197
  // ../../packages/aeg-core/src/retired-vocabulary.ts
3975
4198
  var RETIRED_PATTERNS = [
3976
4199
  String.raw`\bD-[0-9]{3}\b`,
@@ -4440,6 +4663,222 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4440
4663
  }
4441
4664
  return findings;
4442
4665
  }
4666
+ // ../../packages/aeg-core/src/log/schema.ts
4667
+ import { z } from "zod";
4668
+ var ROLE_VALUES = [
4669
+ "planner",
4670
+ "brief-author",
4671
+ "developer",
4672
+ "code-reviewer",
4673
+ "security",
4674
+ "principal",
4675
+ "archivist",
4676
+ "architect"
4677
+ ];
4678
+ var RoleSchema = z.enum(ROLE_VALUES);
4679
+ var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4680
+ var HostSchema = z.enum(HOST_VALUES);
4681
+ var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4682
+ var HeaderMetaSchema = z.object({
4683
+ schema: z.literal(1),
4684
+ ts: z.string(),
4685
+ run_id: z.string().regex(RUN_ID_PATTERN),
4686
+ seq: z.number().int().nonnegative(),
4687
+ repo: z.string().nullable(),
4688
+ vinaya: z.string(),
4689
+ doctrine: z.string(),
4690
+ host: HostSchema,
4691
+ machine: z.string()
4692
+ }).strict();
4693
+ var SubjectSchema = z.object({
4694
+ issue: z.number().int().nullable(),
4695
+ pr: z.number().int().optional(),
4696
+ sha: z.string().optional(),
4697
+ role: z.union([RoleSchema, z.literal("unattributed")]),
4698
+ round: z.number().int().optional(),
4699
+ objectives_version: z.string().optional()
4700
+ }).strict();
4701
+ var HeaderSchema = z.object({
4702
+ meta: HeaderMetaSchema,
4703
+ subject: SubjectSchema
4704
+ }).strict();
4705
+ var envelopeTail = {
4706
+ duration_ms: z.number().nonnegative().optional(),
4707
+ payload: z.object({}).strict()
4708
+ };
4709
+ var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4710
+ z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4711
+ z.object({
4712
+ type: z.literal("round_pushed"),
4713
+ pr: z.number().int(),
4714
+ head: z.string(),
4715
+ comment_id: z.number().int()
4716
+ }).strict(),
4717
+ z.object({
4718
+ type: z.literal("verdict"),
4719
+ verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4720
+ head: z.string(),
4721
+ comment_id: z.number().int(),
4722
+ objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4723
+ findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
4724
+ }).strict(),
4725
+ z.object({
4726
+ type: z.literal("escalation"),
4727
+ class: z.enum(["authority", "strategy", "product"]),
4728
+ comment_id: z.number().int()
4729
+ }).strict(),
4730
+ z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4731
+ z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4732
+ z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
4733
+ ]);
4734
+ var dispatchShared = {
4735
+ meta: HeaderMetaSchema,
4736
+ subject: SubjectSchema,
4737
+ kind: z.literal("dispatch"),
4738
+ ...envelopeTail,
4739
+ target_role: RoleSchema,
4740
+ model: z.string(),
4741
+ round: z.number().int().optional(),
4742
+ effect_id: z.string()
4743
+ };
4744
+ var DispatchEventSchema = z.discriminatedUnion("event", [
4745
+ z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4746
+ z.object({
4747
+ ...dispatchShared,
4748
+ event: z.literal("outcome_received"),
4749
+ outcome: DispatchOutcomeSchema,
4750
+ usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
4751
+ }).strict(),
4752
+ z.object({
4753
+ ...dispatchShared,
4754
+ event: z.literal("dispatch_failed"),
4755
+ reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
4756
+ }).strict()
4757
+ ]);
4758
+ var loopShared = {
4759
+ meta: HeaderMetaSchema,
4760
+ subject: SubjectSchema,
4761
+ kind: z.literal("dev_review_loop"),
4762
+ ...envelopeTail,
4763
+ loop_id: z.string()
4764
+ };
4765
+ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4766
+ z.object({
4767
+ ...loopShared,
4768
+ event: z.literal("loop_started"),
4769
+ task: z.number().int(),
4770
+ policy: z.object({
4771
+ max_rounds: z.number().int().nonnegative(),
4772
+ reviewers: z.array(RoleSchema),
4773
+ models: z.record(RoleSchema, z.string())
4774
+ }).strict()
4775
+ }).strict(),
4776
+ z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4777
+ z.object({
4778
+ ...loopShared,
4779
+ event: z.literal("gate_result_read"),
4780
+ round: z.number().int(),
4781
+ head: z.string(),
4782
+ green: z.boolean()
4783
+ }).strict(),
4784
+ z.object({
4785
+ ...loopShared,
4786
+ event: z.literal("verdicts_read"),
4787
+ round: z.number().int(),
4788
+ head: z.string(),
4789
+ all_approve: z.boolean(),
4790
+ blockers: z.number().int().nonnegative()
4791
+ }).strict(),
4792
+ z.object({
4793
+ ...loopShared,
4794
+ event: z.literal("findings_compared"),
4795
+ round: z.number().int(),
4796
+ open: z.array(z.string()),
4797
+ resolved: z.array(z.string()),
4798
+ new: z.array(z.string()),
4799
+ recurring: z.array(z.string())
4800
+ }).strict(),
4801
+ z.object({
4802
+ ...loopShared,
4803
+ event: z.literal("stop_condition_met"),
4804
+ round: z.number().int(),
4805
+ condition: z.enum([
4806
+ "green",
4807
+ "max_rounds",
4808
+ "no_progress",
4809
+ "escalated",
4810
+ "principal_stop",
4811
+ "confidence",
4812
+ "reappearance"
4813
+ ])
4814
+ }).strict(),
4815
+ z.object({
4816
+ ...loopShared,
4817
+ event: z.literal("paused"),
4818
+ round: z.number().int(),
4819
+ reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
4820
+ }).strict(),
4821
+ z.object({
4822
+ ...loopShared,
4823
+ event: z.literal("resumed"),
4824
+ round: z.number().int(),
4825
+ by: z.literal("principal")
4826
+ }).strict(),
4827
+ z.object({
4828
+ ...loopShared,
4829
+ event: z.literal("round_ended"),
4830
+ round: z.number().int(),
4831
+ base_head: z.string(),
4832
+ head: z.string(),
4833
+ files_changed: z.number().int().nonnegative(),
4834
+ insertions: z.number().int().nonnegative(),
4835
+ deletions: z.number().int().nonnegative(),
4836
+ wall_ms: z.number().nonnegative(),
4837
+ outcome: z.enum(["green", "changes_requested", "escalated"])
4838
+ }).strict(),
4839
+ z.object({
4840
+ ...loopShared,
4841
+ event: z.literal("journal_finalized"),
4842
+ rounds: z.number().int().nonnegative(),
4843
+ total_wall_ms: z.number().nonnegative(),
4844
+ time_to_green_ms: z.number().nonnegative().nullable(),
4845
+ files_changed_total: z.number().int().nonnegative(),
4846
+ final_head: z.string(),
4847
+ result: z.enum(["merged_ready", "stopped"])
4848
+ }).strict()
4849
+ ]);
4850
+ var ForgeOpSchema = z.enum([
4851
+ "pr.create",
4852
+ "pr.comment",
4853
+ "pr.body.replace",
4854
+ "pr.refreeze",
4855
+ "issue.create",
4856
+ "issue.edit",
4857
+ "issue.comment",
4858
+ "milestone.create",
4859
+ "milestone.edit",
4860
+ "milestone.close",
4861
+ "label.add",
4862
+ "label.remove"
4863
+ ]);
4864
+ var ForgeWriteTargetSchema = z.object({
4865
+ issue: z.number().int().optional(),
4866
+ pr: z.number().int().optional()
4867
+ }).strict();
4868
+ var forgeWriteShared = {
4869
+ meta: HeaderMetaSchema,
4870
+ subject: SubjectSchema,
4871
+ kind: z.literal("forge_write"),
4872
+ ...envelopeTail,
4873
+ op: ForgeOpSchema,
4874
+ target: ForgeWriteTargetSchema
4875
+ };
4876
+ var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4877
+ z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4878
+ z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4879
+ z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
4880
+ ]);
4881
+ var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
4443
4882
  // src/lib/diff-evidence.ts
4444
4883
  import { execFileSync as execFileSync3 } from "node:child_process";
4445
4884
  import { join as join2, relative, resolve as resolve2 } from "node:path";
@@ -4449,27 +4888,27 @@ import { execFileSync as execFileSync2 } from "node:child_process";
4449
4888
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
4450
4889
  import { homedir } from "node:os";
4451
4890
  import { dirname, join, resolve } from "node:path";
4452
- import { z } from "zod";
4891
+ import { z as z2 } from "zod";
4453
4892
 
4454
4893
  // src/lib/agent-vendors.ts
4455
4894
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
4456
4895
 
4457
4896
  // src/lib/config.ts
4458
- var EnvEntrySchema = z.union([
4459
- z.literal(true),
4460
- z.object({ optional: z.literal(true) }),
4461
- z.object({ anyOf: z.array(z.string()).min(2) }),
4462
- z.string()
4897
+ var EnvEntrySchema = z2.union([
4898
+ z2.literal(true),
4899
+ z2.object({ optional: z2.literal(true) }),
4900
+ z2.object({ anyOf: z2.array(z2.string()).min(2) }),
4901
+ z2.string()
4463
4902
  ]);
4464
- var CheckEntrySchema = z.object({
4465
- run: z.string(),
4466
- scope: z.enum(["diff", "full"]),
4467
- include: z.array(z.string()).optional(),
4468
- args: z.array(z.string()).optional(),
4469
- timeoutMs: z.number().optional(),
4470
- env: z.record(z.string(), EnvEntrySchema).optional(),
4471
- requiresOpenPr: z.boolean().optional(),
4472
- ownWorkflow: z.boolean().optional()
4903
+ var CheckEntrySchema = z2.object({
4904
+ run: z2.string(),
4905
+ scope: z2.enum(["diff", "full"]),
4906
+ include: z2.array(z2.string()).optional(),
4907
+ args: z2.array(z2.string()).optional(),
4908
+ timeoutMs: z2.number().optional(),
4909
+ env: z2.record(z2.string(), EnvEntrySchema).optional(),
4910
+ requiresOpenPr: z2.boolean().optional(),
4911
+ ownWorkflow: z2.boolean().optional()
4473
4912
  }).superRefine((entry2, ctx) => {
4474
4913
  if (!entry2.env)
4475
4914
  return;
@@ -4479,22 +4918,22 @@ var CheckEntrySchema = z.object({
4479
4918
  const members = value.anyOf;
4480
4919
  if (new Set(members).size !== members.length) {
4481
4920
  ctx.addIssue({
4482
- code: z.ZodIssueCode.custom,
4921
+ code: z2.ZodIssueCode.custom,
4483
4922
  message: `env.${key}.anyOf has duplicate members`,
4484
4923
  path: ["env", key, "anyOf"]
4485
4924
  });
4486
4925
  }
4487
4926
  if (!members.includes(key)) {
4488
4927
  ctx.addIssue({
4489
- code: z.ZodIssueCode.custom,
4928
+ code: z2.ZodIssueCode.custom,
4490
4929
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
4491
4930
  path: ["env", key, "anyOf"]
4492
4931
  });
4493
4932
  }
4494
4933
  }
4495
4934
  });
4496
- var RoleEntrySchema = z.object({
4497
- contract: z.string().refine((p) => p.includes("/"), {
4935
+ var RoleEntrySchema = z2.object({
4936
+ contract: z2.string().refine((p) => p.includes("/"), {
4498
4937
  message: 'must be a path (contain at least one "/"), not a bare filename'
4499
4938
  })
4500
4939
  });
@@ -4513,19 +4952,21 @@ var BRIEF_BUILTINS = [
4513
4952
  "closesN",
4514
4953
  "premiseCoverage",
4515
4954
  "issueRationale",
4955
+ "objectives",
4956
+ "briefSections",
4516
4957
  "milestoneShape"
4517
4958
  ];
4518
- var BriefSectionSchema = z.union([
4519
- z.object({ builtin: z.enum(BRIEF_BUILTINS) }),
4520
- z.object({ heading: z.string().min(1), name: z.string().optional() }),
4521
- z.object({ field: z.string().min(1), name: z.string().optional() }),
4522
- z.object({ phrase: z.string().min(1), name: z.string().optional() })
4959
+ var BriefSectionSchema = z2.union([
4960
+ z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
4961
+ z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
4962
+ z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
4963
+ z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
4523
4964
  ]);
4524
- var BriefSchemaSchema = z.object({
4525
- pr: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4526
- issue: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4527
- milestone: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4528
- ack: z.array(z.enum(BRIEF_BUILTINS)).optional()
4965
+ var BriefSchemaSchema = z2.object({
4966
+ pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4967
+ issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4968
+ milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4969
+ ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
4529
4970
  });
4530
4971
  function isSafeRepoRelPath(p) {
4531
4972
  if (p.length === 0)
@@ -4534,7 +4975,7 @@ function isSafeRepoRelPath(p) {
4534
4975
  return false;
4535
4976
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
4536
4977
  }
4537
- var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
4978
+ var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
4538
4979
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
4539
4980
  });
4540
4981
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -4544,22 +4985,22 @@ function isCanonicalHookBlockPath(p) {
4544
4985
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
4545
4986
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
4546
4987
  });
4547
- var ManagedBlockRecordSchema = z.object({
4988
+ var ManagedBlockRecordSchema = z2.object({
4548
4989
  path: ManagedHookBlockPath,
4549
- marker: z.string(),
4550
- comment: z.enum(["hash", "html"])
4990
+ marker: z2.string(),
4991
+ comment: z2.enum(["hash", "html"])
4551
4992
  });
4552
- var ManagedManifestSchema = z.object({
4553
- version: z.number().int().positive(),
4554
- files: z.array(SafeRepoRelPath),
4555
- blocks: z.array(ManagedBlockRecordSchema),
4556
- labels: z.array(z.string()),
4557
- agents: z.array(z.enum(AGENT_VENDORS)).optional()
4993
+ var ManagedManifestSchema = z2.object({
4994
+ version: z2.number().int().positive(),
4995
+ files: z2.array(SafeRepoRelPath),
4996
+ blocks: z2.array(ManagedBlockRecordSchema),
4997
+ labels: z2.array(z2.string()),
4998
+ agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
4558
4999
  });
4559
- var ProjectEntrySchema = z.object({
4560
- name: z.string().min(1),
4561
- description: z.string().min(1).optional(),
4562
- path: z.string().min(1).optional()
5000
+ var ProjectEntrySchema = z2.object({
5001
+ name: z2.string().min(1),
5002
+ description: z2.string().min(1).optional(),
5003
+ path: z2.string().min(1).optional()
4563
5004
  });
4564
5005
  function parseTokensCollectDeclaration(value) {
4565
5006
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -4570,31 +5011,35 @@ function parseTokensCollectDeclaration(value) {
4570
5011
  return null;
4571
5012
  return { interpreter, script };
4572
5013
  }
4573
- var VinayaConfigSchema = z.object({
4574
- rings: z.object({
4575
- ring1_forgeWriteInterception: z.boolean(),
4576
- ring2_asyncAudits: z.boolean()
5014
+ var VinayaConfigSchema = z2.object({
5015
+ rings: z2.object({
5016
+ ring1_forgeWriteInterception: z2.boolean(),
5017
+ ring2_asyncAudits: z2.boolean()
4577
5018
  }).optional(),
4578
- checks: z.record(z.string(), CheckEntrySchema).optional(),
4579
- roles: z.record(z.string(), RoleEntrySchema).optional(),
5019
+ checks: z2.record(z2.string(), CheckEntrySchema).optional(),
5020
+ roles: z2.record(z2.string(), RoleEntrySchema).optional(),
4580
5021
  briefSchema: BriefSchemaSchema.optional(),
4581
5022
  managed: ManagedManifestSchema.optional(),
4582
- principals: z.array(z.string()).min(1).optional(),
4583
- releaseActor: z.string().min(1).optional(),
4584
- ci: z.object({ setup: z.string().min(1) }).optional(),
4585
- tokens: z.object({
4586
- collect: z.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5023
+ principals: z2.array(z2.string()).min(1).optional(),
5024
+ releaseActor: z2.string().min(1).optional(),
5025
+ ci: z2.object({ setup: z2.string().min(1) }).optional(),
5026
+ tokens: z2.object({
5027
+ collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
4587
5028
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
4588
5029
  })
4589
5030
  }).optional(),
4590
- blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
4591
- proseGates: z.object({
4592
- doctrineRoot: z.string().min(1).optional(),
4593
- readerFacingPrefix: z.string().min(1).optional(),
4594
- readerFacingSuffix: z.string().min(1).optional(),
4595
- legacySlugDir: z.string().min(1).optional()
5031
+ blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
5032
+ proseGates: z2.object({
5033
+ doctrineRoot: z2.string().min(1).optional(),
5034
+ readerFacingPrefix: z2.string().min(1).optional(),
5035
+ readerFacingSuffix: z2.string().min(1).optional(),
5036
+ legacySlugDir: z2.string().min(1).optional()
4596
5037
  }).optional(),
4597
- projects: z.array(ProjectEntrySchema).optional()
5038
+ projects: z2.array(ProjectEntrySchema).optional(),
5039
+ dispatch: z2.object({
5040
+ timeoutMs: z2.number().int().positive().optional(),
5041
+ agent: z2.enum(["claude", "codex", "gemini"]).optional()
5042
+ }).optional()
4598
5043
  });
4599
5044
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
4600
5045
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");