@attalabs/vinaya 0.24.0 → 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 +11 -7
  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 +584 -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 +517 -69
  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 +582 -129
  46. package/dist/index.js +7740 -4283
  47. package/package.json +2 -2
  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
@@ -1950,13 +1950,18 @@ function parseInlineFieldList(section) {
1950
1950
  }
1951
1951
  // ../../packages/aeg-core/src/verdict-extraction.ts
1952
1952
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1953
- function firstThreeLines(comment) {
1953
+ var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1954
+ function firstFiveLines(comment) {
1954
1955
  return comment.split(`
1955
- `).slice(0, 3).join(`
1956
+ `).slice(0, 5).join(`
1956
1957
  `);
1957
1958
  }
1958
1959
  function extractHeadSha(comment) {
1959
- const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1960
+ const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
1961
+ return m ? m[1].toLowerCase() : null;
1962
+ }
1963
+ function extractObjectivesVersion(comment) {
1964
+ const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1960
1965
  return m ? m[1].toLowerCase() : null;
1961
1966
  }
1962
1967
  function extractVerdict(comments, valuePattern, missingLabel) {
@@ -1965,21 +1970,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1965
1970
  return {
1966
1971
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1967
1972
  headSha: null,
1973
+ objectivesVersion: null,
1968
1974
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1969
1975
  };
1970
1976
  }
1971
1977
  const latest = candidates[candidates.length - 1];
1972
- const m = firstThreeLines(latest).match(valuePattern);
1978
+ const m = firstFiveLines(latest).match(valuePattern);
1973
1979
  if (!m) {
1974
1980
  return {
1975
- value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1981
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1976
1982
  headSha: null,
1977
- danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1983
+ objectivesVersion: null,
1984
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1978
1985
  };
1979
1986
  }
1980
1987
  return {
1981
1988
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1982
1989
  headSha: extractHeadSha(latest),
1990
+ objectivesVersion: extractObjectivesVersion(latest),
1983
1991
  danglingNote: null
1984
1992
  };
1985
1993
  }
@@ -2336,8 +2344,105 @@ function overrideActive(opts) {
2336
2344
  return true;
2337
2345
  return false;
2338
2346
  }
2339
- // ../../packages/aeg-core/src/premise-check.ts
2347
+ // ../../packages/aeg-core/src/objectives.ts
2340
2348
  import { createHash as createHash2 } from "node:crypto";
2349
+ var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2350
+ var NEXT_HEADING_RE = /^##[ \t]/m;
2351
+ var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2352
+ function hasBacktickedPath(text) {
2353
+ let i = 0;
2354
+ while (i < text.length) {
2355
+ const start = text.indexOf("`", i);
2356
+ if (start === -1)
2357
+ return false;
2358
+ const end = text.indexOf("`", start + 1);
2359
+ if (end === -1)
2360
+ return false;
2361
+ if (text.slice(start + 1, end).includes("/"))
2362
+ return true;
2363
+ i = end + 1;
2364
+ }
2365
+ return false;
2366
+ }
2367
+ var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
2368
+ function stripObjectiveBackticks(text) {
2369
+ return text.replace(/`[^`\n]*`/g, " ");
2370
+ }
2371
+ function wordCount(text) {
2372
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2373
+ }
2374
+ function objectivesSectionText(body) {
2375
+ const heading = HEADING_RE.exec(body);
2376
+ if (!heading)
2377
+ return null;
2378
+ const afterHeading = body.slice(heading.index + heading[0].length);
2379
+ const next = NEXT_HEADING_RE.exec(afterHeading);
2380
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
2381
+ }
2382
+ function hasObjectivesHeading(body) {
2383
+ return HEADING_RE.test(body);
2384
+ }
2385
+ function objectivesOf(body) {
2386
+ const section = objectivesSectionText(body);
2387
+ if (section === null) {
2388
+ return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
2389
+ }
2390
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
2391
+ if (lines.length === 0) {
2392
+ return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
2393
+ }
2394
+ const objectives = [];
2395
+ const errors = [];
2396
+ for (const line of lines) {
2397
+ const m = OBJECTIVE_LINE_RE.exec(line);
2398
+ if (!m) {
2399
+ errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
2400
+ continue;
2401
+ }
2402
+ const n = m[1];
2403
+ const text = m[2].trim();
2404
+ if (text.length === 0) {
2405
+ errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
2406
+ continue;
2407
+ }
2408
+ if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
2409
+ 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).`);
2410
+ continue;
2411
+ }
2412
+ objectives.push({ id: `O${n}`, text });
2413
+ }
2414
+ for (let i = 0;i < objectives.length; i++) {
2415
+ const expected = i + 1;
2416
+ const actual = Number.parseInt(objectives[i].id.slice(1), 10);
2417
+ if (actual !== expected) {
2418
+ errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
2419
+ break;
2420
+ }
2421
+ }
2422
+ if (errors.length > 0)
2423
+ return { ok: false, errors };
2424
+ return { ok: true, objectives };
2425
+ }
2426
+ function normalizeText(text) {
2427
+ return text.trim().replace(/\s+/g, " ");
2428
+ }
2429
+ function objectivesVersion(objectives) {
2430
+ const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
2431
+ `);
2432
+ return createHash2("sha256").update(normalized).digest("hex");
2433
+ }
2434
+ function isIssueNotFoundError(err) {
2435
+ const stderr = err?.stderr;
2436
+ const haystack = [
2437
+ err?.message ?? "",
2438
+ typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
2439
+ ].join(`
2440
+ `);
2441
+ return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2442
+ }
2443
+
2444
+ // ../../packages/aeg-core/src/premise-check.ts
2445
+ import { createHash as createHash3 } from "node:crypto";
2341
2446
  var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
2342
2447
  function isPremiseHeader(line) {
2343
2448
  const stripped = line.replace(/[*#]/g, "").trim();
@@ -2408,7 +2513,7 @@ function checkPremises(assertions, fileReader) {
2408
2513
  } else if (a.kind === "absent" && content.includes(a.value)) {
2409
2514
  failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
2410
2515
  } else if (a.kind === "sha256") {
2411
- const actual = createHash2("sha256").update(content).digest("hex");
2516
+ const actual = createHash3("sha256").update(content).digest("hex");
2412
2517
  if (actual !== a.value.toLowerCase()) {
2413
2518
  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.`);
2414
2519
  }
@@ -2769,8 +2874,55 @@ function checkDefeatCases(prBody) {
2769
2874
  ]
2770
2875
  };
2771
2876
  }
2877
+ var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
2878
+ var OBJECTIVE_REF_RE = /O(\d+)/g;
2879
+ function citedObjectiveIds(section6) {
2880
+ const ids = new Set;
2881
+ for (const m of section6.matchAll(PART_CITATION_RE)) {
2882
+ for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
2883
+ ids.add(Number.parseInt(r[1], 10));
2884
+ }
2885
+ return ids;
2886
+ }
2887
+ function checkObjectivesCopy(prBody, issueObjectives) {
2888
+ const parsed = objectivesOf(prBody);
2889
+ if (!parsed.ok) {
2890
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
2891
+ }
2892
+ if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
2893
+ return {
2894
+ status: "fail",
2895
+ errors: [
2896
+ "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)."
2897
+ ]
2898
+ };
2899
+ }
2900
+ return { status: "pass", errors: [] };
2901
+ }
2902
+ function checkObjectivesCoverage(prBody) {
2903
+ const parsed = objectivesOf(prBody);
2904
+ if (!parsed.ok) {
2905
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
2906
+ }
2907
+ const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
2908
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2909
+ const cited = citedObjectiveIds(section6);
2910
+ const maxDeclared = Math.max(...declaredIds, 0);
2911
+ const errors = [];
2912
+ for (const id of declaredIds) {
2913
+ if (!cited.has(id)) {
2914
+ 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.`);
2915
+ }
2916
+ }
2917
+ for (const id of cited) {
2918
+ if (!declaredIds.has(id)) {
2919
+ errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
2920
+ }
2921
+ }
2922
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2923
+ }
2772
2924
  function checkBriefSections(prBody, readTier, options = {}) {
2773
- const { requireClosesN = true, consumersOf = () => [] } = options;
2925
+ const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
2774
2926
  const results = [
2775
2927
  checkTierField(prBody, readTier),
2776
2928
  checkTestPlan(prBody),
@@ -2788,6 +2940,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
2788
2940
  checkCommandsCarryOutput(prBody),
2789
2941
  checkConsumerTests(prBody, consumersOf),
2790
2942
  checkDefeatCases(prBody),
2943
+ ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2791
2944
  ...requireClosesN ? [checkClosesN(prBody)] : []
2792
2945
  ];
2793
2946
  return { errors: results.flatMap((r) => r.errors) };
@@ -2986,14 +3139,14 @@ function checkSectionDensity(prBody, heading) {
2986
3139
  ]
2987
3140
  };
2988
3141
  }
2989
- function checkSummaryDensity(prBody) {
2990
- return checkSectionDensity(prBody, "Summary");
3142
+ function checkDecisionsDensity(prBody) {
3143
+ return checkSectionDensity(prBody, "Decisions");
2991
3144
  }
2992
3145
  function checkScopeDensity(prBody) {
2993
3146
  return checkSectionDensity(prBody, "Scope");
2994
3147
  }
2995
3148
  function checkPrReportDensity(prBody) {
2996
- const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
3149
+ const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
2997
3150
  return { errors: results.flatMap((r) => r.errors) };
2998
3151
  }
2999
3152
  // ../../packages/aeg-core/src/issue-validation.ts
@@ -3025,6 +3178,15 @@ function checkIssueRationale(body) {
3025
3178
  }
3026
3179
  return { status: errors.length > 0 ? "fail" : "pass", errors };
3027
3180
  }
3181
+ var OBJECTIVES_SINCE_ISSUE = 404;
3182
+ function checkIssueObjectives(body, issueNumber) {
3183
+ if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
3184
+ return { status: "pass", errors: [] };
3185
+ const result = objectivesOf(body);
3186
+ if (result.ok)
3187
+ return { status: "pass", errors: [] };
3188
+ return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
3189
+ }
3028
3190
  function isTaskIssueLabelSet(labels) {
3029
3191
  return hasLabel("tranche", labels);
3030
3192
  }
@@ -3286,7 +3448,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3286
3448
  continue;
3287
3449
  const errors = [
3288
3450
  ...checkIssueRationale(issue.body).errors,
3289
- ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
3451
+ ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3452
+ ...checkIssueObjectives(issue.body, issue.number).errors
3290
3453
  ];
3291
3454
  if (errors.length === 0)
3292
3455
  continue;
@@ -3401,6 +3564,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3401
3564
  }
3402
3565
  return { ok: true, expectedIssue };
3403
3566
  }
3567
+ // ../../packages/aeg-core/src/archive-task.ts
3568
+ function closesRefs(text) {
3569
+ return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
3570
+ }
3571
+ function extractIssue(body) {
3572
+ const stripped = stripCode(body);
3573
+ const bodyNums = closesRefs(stripped);
3574
+ const anchored = anchoredRegion(stripped, "CLOSES");
3575
+ if (anchored !== null) {
3576
+ const anchorNums = closesRefs(anchored);
3577
+ if (anchorNums.length === 0)
3578
+ return { issue: null, extraIssues: bodyNums, outsideHeader: false };
3579
+ const issue2 = anchorNums[0];
3580
+ return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
3581
+ }
3582
+ const headerNums = closesRefs(headerRegion(stripped));
3583
+ if (bodyNums.length === 0)
3584
+ return { issue: null, extraIssues: [], outsideHeader: false };
3585
+ const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3586
+ return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3587
+ }
3404
3588
  // ../../packages/aeg-core/src/review-gate.ts
3405
3589
  function isBoundToHead(extraction, headSha) {
3406
3590
  if (!extraction.headSha)
@@ -3419,6 +3603,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3419
3603
  function isBoundToPatch(extraction, headSha, patchIdOf) {
3420
3604
  return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3421
3605
  }
3606
+ function isBoundToObjectives(extraction, currentVersion) {
3607
+ if (currentVersion === null)
3608
+ return true;
3609
+ return extraction.objectivesVersion === currentVersion;
3610
+ }
3422
3611
  function checkReviewGate(input) {
3423
3612
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3424
3613
  const waived = isWaiverLabelActorVerified({
@@ -3445,7 +3634,9 @@ function checkReviewGate(input) {
3445
3634
  const securityClean = security.value === "PASS";
3446
3635
  const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3447
3636
  const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3448
- if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
3637
+ const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3638
+ const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3639
+ if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
3449
3640
  return {
3450
3641
  verdict: "pass",
3451
3642
  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.`,
@@ -3457,11 +3648,15 @@ function checkReviewGate(input) {
3457
3648
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
3458
3649
  } else if (!codeReviewBound) {
3459
3650
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3651
+ } else if (!codeReviewObjectivesBound) {
3652
+ problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3460
3653
  }
3461
3654
  if (!securityClean) {
3462
3655
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
3463
3656
  } else if (!securityBound) {
3464
3657
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3658
+ } else if (!securityObjectivesBound) {
3659
+ problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3465
3660
  }
3466
3661
  if (!mechanicalChecksClean) {
3467
3662
  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(", ")}`);
@@ -3697,55 +3892,20 @@ function checkG6(rows, registeredCheckNames) {
3697
3892
  }
3698
3893
  return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
3699
3894
  }
3700
- // ../../packages/aeg-core/src/dead-branch-push-guard.ts
3701
- function checkDeadBranchPush(input) {
3702
- const { branch, prState, prNumber } = input;
3703
- if (prState === "MERGED") {
3704
- return {
3705
- verdict: "refuse",
3706
- 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.`
3707
- };
3708
- }
3709
- if (prState === "CLOSED") {
3710
- return {
3711
- verdict: "refuse",
3712
- 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.`
3713
- };
3714
- }
3715
- if (prState === "OPEN") {
3716
- return {
3717
- verdict: "allow",
3718
- reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
3719
- };
3720
- }
3721
- if (prState === "NONE") {
3722
- return {
3723
- verdict: "allow",
3724
- reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
3725
- };
3726
- }
3727
- return {
3728
- verdict: "allow",
3729
- reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
3730
- };
3731
- }
3732
- // ../../packages/aeg-core/src/baseline-capture.ts
3733
- function captureBaseline(counts, capturedAt) {
3734
- return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
3895
+ // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3896
+ var PRODUCT_SLUG_SCOPE = [
3897
+ "apps/cli/src",
3898
+ ".github/workflows",
3899
+ ".vinaya",
3900
+ "apps/cli/README.md",
3901
+ "packages/sources/README.md"
3902
+ ];
3903
+ function isUnderOrEqual(path, prefix) {
3904
+ return path === prefix || path.startsWith(`${prefix}/`);
3735
3905
  }
3736
- function compareToBaseline(current, baseline) {
3737
- const perTool = current.map((c) => {
3738
- const baselineEntry = baseline.find((b) => b.tool === c.tool);
3739
- const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
3740
- return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
3741
- });
3742
- return {
3743
- withinBudget: perTool.every((t) => t.delta <= 0),
3744
- delta: perTool.reduce((sum, t) => sum + t.delta, 0),
3745
- perTool
3746
- };
3906
+ function isProductScopeFile(path) {
3907
+ return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
3747
3908
  }
3748
- // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3749
3909
  var SHIPS_PREFIX = "aeg-root/";
3750
3910
  function shipsArchivePrefix(shipsPrefix) {
3751
3911
  return `${shipsPrefix}tranches/completed/`;
@@ -3765,6 +3925,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
3765
3925
  }
3766
3926
  if (isSpecFile2(path) || isClaudeMdFile(path))
3767
3927
  return "internal";
3928
+ if (isProductScopeFile(path))
3929
+ return "product";
3768
3930
  return null;
3769
3931
  }
3770
3932
  var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3777,6 +3939,9 @@ function stripNonProse(path, content) {
3777
3939
  }
3778
3940
  return content;
3779
3941
  }
3942
+ function stripNonProseForProduct(path, content) {
3943
+ return path.endsWith(".md") ? stripNonProse(path, content) : content;
3944
+ }
3780
3945
  function lineAt(content, index) {
3781
3946
  let line = 1;
3782
3947
  for (let i = 0;i < index; i++) {
@@ -3801,12 +3966,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3801
3966
  { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
3802
3967
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
3803
3968
  ];
3969
+ const productPatterns = [
3970
+ { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
3971
+ ];
3804
3972
  for (const file of files) {
3805
3973
  const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
3806
- if (!cls || !SWEPT_CLASSES.has(cls))
3974
+ if (!cls || cls === "internal")
3807
3975
  continue;
3808
- const scrubbed = stripNonProse(file.path, file.content);
3809
- for (const { pattern, what, group } of patterns) {
3976
+ if (cls !== "product" && !SWEPT_CLASSES.has(cls))
3977
+ continue;
3978
+ const blocking = cls === "product";
3979
+ const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
3980
+ const patternsToRun = blocking ? productPatterns : patterns;
3981
+ for (const { pattern, what, group } of patternsToRun) {
3810
3982
  pattern.lastIndex = 0;
3811
3983
  let match = pattern.exec(scrubbed);
3812
3984
  while (match !== null) {
@@ -3814,7 +3986,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3814
3986
  findings.push({
3815
3987
  file: file.path,
3816
3988
  line: lineAt(scrubbed, match.index),
3817
- message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`
3989
+ message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
3990
+ blocking
3818
3991
  });
3819
3992
  match = pattern.exec(scrubbed);
3820
3993
  }
@@ -3864,7 +4037,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
3864
4037
  findings.push({
3865
4038
  file: file.path,
3866
4039
  line,
3867
- message: `uses coined term "${term}" without defining it inline or linking the glossary`
4040
+ message: `uses coined term "${term}" without defining it inline or linking the glossary`,
4041
+ blocking: false
3868
4042
  });
3869
4043
  }
3870
4044
  }
@@ -3887,6 +4061,7 @@ function parseGlossaryTerms(glossaryContent) {
3887
4061
  }
3888
4062
  return terms;
3889
4063
  }
4064
+
3890
4065
  // ../../packages/aeg-core/src/quoted-command.ts
3891
4066
  var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
3892
4067
  var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3981,6 +4156,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
3981
4156
  }
3982
4157
  return findings;
3983
4158
  }
4159
+ // ../../packages/aeg-core/src/dead-branch-push-guard.ts
4160
+ function checkDeadBranchPush(input) {
4161
+ const { branch, prState, prNumber } = input;
4162
+ if (prState === "MERGED") {
4163
+ return {
4164
+ verdict: "refuse",
4165
+ 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.`
4166
+ };
4167
+ }
4168
+ if (prState === "CLOSED") {
4169
+ return {
4170
+ verdict: "refuse",
4171
+ 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.`
4172
+ };
4173
+ }
4174
+ if (prState === "OPEN") {
4175
+ return {
4176
+ verdict: "allow",
4177
+ reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
4178
+ };
4179
+ }
4180
+ if (prState === "NONE") {
4181
+ return {
4182
+ verdict: "allow",
4183
+ reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
4184
+ };
4185
+ }
4186
+ return {
4187
+ verdict: "allow",
4188
+ reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
4189
+ };
4190
+ }
4191
+ // ../../packages/aeg-core/src/baseline-capture.ts
4192
+ function captureBaseline(counts, capturedAt) {
4193
+ return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
4194
+ }
4195
+ function compareToBaseline(current, baseline) {
4196
+ const perTool = current.map((c) => {
4197
+ const baselineEntry = baseline.find((b) => b.tool === c.tool);
4198
+ const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
4199
+ return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
4200
+ });
4201
+ return {
4202
+ withinBudget: perTool.every((t) => t.delta <= 0),
4203
+ delta: perTool.reduce((sum, t) => sum + t.delta, 0),
4204
+ perTool
4205
+ };
4206
+ }
3984
4207
  // ../../packages/aeg-core/src/retired-vocabulary.ts
3985
4208
  var RETIRED_PATTERNS = [
3986
4209
  String.raw`\bD-[0-9]{3}\b`,
@@ -4450,32 +4673,248 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4450
4673
  }
4451
4674
  return findings;
4452
4675
  }
4676
+ // ../../packages/aeg-core/src/log/schema.ts
4677
+ import { z } from "zod";
4678
+ var ROLE_VALUES = [
4679
+ "planner",
4680
+ "brief-author",
4681
+ "developer",
4682
+ "code-reviewer",
4683
+ "security",
4684
+ "principal",
4685
+ "archivist",
4686
+ "architect"
4687
+ ];
4688
+ var RoleSchema = z.enum(ROLE_VALUES);
4689
+ var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4690
+ var HostSchema = z.enum(HOST_VALUES);
4691
+ var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4692
+ var HeaderMetaSchema = z.object({
4693
+ schema: z.literal(1),
4694
+ ts: z.string(),
4695
+ run_id: z.string().regex(RUN_ID_PATTERN),
4696
+ seq: z.number().int().nonnegative(),
4697
+ repo: z.string().nullable(),
4698
+ vinaya: z.string(),
4699
+ doctrine: z.string(),
4700
+ host: HostSchema,
4701
+ machine: z.string()
4702
+ }).strict();
4703
+ var SubjectSchema = z.object({
4704
+ issue: z.number().int().nullable(),
4705
+ pr: z.number().int().optional(),
4706
+ sha: z.string().optional(),
4707
+ role: z.union([RoleSchema, z.literal("unattributed")]),
4708
+ round: z.number().int().optional(),
4709
+ objectives_version: z.string().optional()
4710
+ }).strict();
4711
+ var HeaderSchema = z.object({
4712
+ meta: HeaderMetaSchema,
4713
+ subject: SubjectSchema
4714
+ }).strict();
4715
+ var envelopeTail = {
4716
+ duration_ms: z.number().nonnegative().optional(),
4717
+ payload: z.object({}).strict()
4718
+ };
4719
+ var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4720
+ z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4721
+ z.object({
4722
+ type: z.literal("round_pushed"),
4723
+ pr: z.number().int(),
4724
+ head: z.string(),
4725
+ comment_id: z.number().int()
4726
+ }).strict(),
4727
+ z.object({
4728
+ type: z.literal("verdict"),
4729
+ verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4730
+ head: z.string(),
4731
+ comment_id: z.number().int(),
4732
+ objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4733
+ findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
4734
+ }).strict(),
4735
+ z.object({
4736
+ type: z.literal("escalation"),
4737
+ class: z.enum(["authority", "strategy", "product"]),
4738
+ comment_id: z.number().int()
4739
+ }).strict(),
4740
+ z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4741
+ z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4742
+ z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
4743
+ ]);
4744
+ var dispatchShared = {
4745
+ meta: HeaderMetaSchema,
4746
+ subject: SubjectSchema,
4747
+ kind: z.literal("dispatch"),
4748
+ ...envelopeTail,
4749
+ target_role: RoleSchema,
4750
+ model: z.string(),
4751
+ round: z.number().int().optional(),
4752
+ effect_id: z.string()
4753
+ };
4754
+ var DispatchEventSchema = z.discriminatedUnion("event", [
4755
+ z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4756
+ z.object({
4757
+ ...dispatchShared,
4758
+ event: z.literal("outcome_received"),
4759
+ outcome: DispatchOutcomeSchema,
4760
+ usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
4761
+ }).strict(),
4762
+ z.object({
4763
+ ...dispatchShared,
4764
+ event: z.literal("dispatch_failed"),
4765
+ reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
4766
+ }).strict()
4767
+ ]);
4768
+ var loopShared = {
4769
+ meta: HeaderMetaSchema,
4770
+ subject: SubjectSchema,
4771
+ kind: z.literal("dev_review_loop"),
4772
+ ...envelopeTail,
4773
+ loop_id: z.string()
4774
+ };
4775
+ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4776
+ z.object({
4777
+ ...loopShared,
4778
+ event: z.literal("loop_started"),
4779
+ task: z.number().int(),
4780
+ policy: z.object({
4781
+ max_rounds: z.number().int().nonnegative(),
4782
+ reviewers: z.array(RoleSchema),
4783
+ models: z.record(RoleSchema, z.string())
4784
+ }).strict()
4785
+ }).strict(),
4786
+ z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4787
+ z.object({
4788
+ ...loopShared,
4789
+ event: z.literal("gate_result_read"),
4790
+ round: z.number().int(),
4791
+ head: z.string(),
4792
+ green: z.boolean()
4793
+ }).strict(),
4794
+ z.object({
4795
+ ...loopShared,
4796
+ event: z.literal("verdicts_read"),
4797
+ round: z.number().int(),
4798
+ head: z.string(),
4799
+ all_approve: z.boolean(),
4800
+ blockers: z.number().int().nonnegative()
4801
+ }).strict(),
4802
+ z.object({
4803
+ ...loopShared,
4804
+ event: z.literal("findings_compared"),
4805
+ round: z.number().int(),
4806
+ open: z.array(z.string()),
4807
+ resolved: z.array(z.string()),
4808
+ new: z.array(z.string()),
4809
+ recurring: z.array(z.string())
4810
+ }).strict(),
4811
+ z.object({
4812
+ ...loopShared,
4813
+ event: z.literal("stop_condition_met"),
4814
+ round: z.number().int(),
4815
+ condition: z.enum([
4816
+ "green",
4817
+ "max_rounds",
4818
+ "no_progress",
4819
+ "escalated",
4820
+ "principal_stop",
4821
+ "confidence",
4822
+ "reappearance"
4823
+ ])
4824
+ }).strict(),
4825
+ z.object({
4826
+ ...loopShared,
4827
+ event: z.literal("paused"),
4828
+ round: z.number().int(),
4829
+ reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
4830
+ }).strict(),
4831
+ z.object({
4832
+ ...loopShared,
4833
+ event: z.literal("resumed"),
4834
+ round: z.number().int(),
4835
+ by: z.literal("principal")
4836
+ }).strict(),
4837
+ z.object({
4838
+ ...loopShared,
4839
+ event: z.literal("round_ended"),
4840
+ round: z.number().int(),
4841
+ base_head: z.string(),
4842
+ head: z.string(),
4843
+ files_changed: z.number().int().nonnegative(),
4844
+ insertions: z.number().int().nonnegative(),
4845
+ deletions: z.number().int().nonnegative(),
4846
+ wall_ms: z.number().nonnegative(),
4847
+ outcome: z.enum(["green", "changes_requested", "escalated"])
4848
+ }).strict(),
4849
+ z.object({
4850
+ ...loopShared,
4851
+ event: z.literal("journal_finalized"),
4852
+ rounds: z.number().int().nonnegative(),
4853
+ total_wall_ms: z.number().nonnegative(),
4854
+ time_to_green_ms: z.number().nonnegative().nullable(),
4855
+ files_changed_total: z.number().int().nonnegative(),
4856
+ final_head: z.string(),
4857
+ result: z.enum(["merged_ready", "stopped"])
4858
+ }).strict()
4859
+ ]);
4860
+ var ForgeOpSchema = z.enum([
4861
+ "pr.create",
4862
+ "pr.comment",
4863
+ "pr.body.replace",
4864
+ "pr.refreeze",
4865
+ "issue.create",
4866
+ "issue.edit",
4867
+ "issue.comment",
4868
+ "milestone.create",
4869
+ "milestone.edit",
4870
+ "milestone.close",
4871
+ "label.add",
4872
+ "label.remove"
4873
+ ]);
4874
+ var ForgeWriteTargetSchema = z.object({
4875
+ issue: z.number().int().optional(),
4876
+ pr: z.number().int().optional()
4877
+ }).strict();
4878
+ var forgeWriteShared = {
4879
+ meta: HeaderMetaSchema,
4880
+ subject: SubjectSchema,
4881
+ kind: z.literal("forge_write"),
4882
+ ...envelopeTail,
4883
+ op: ForgeOpSchema,
4884
+ target: ForgeWriteTargetSchema
4885
+ };
4886
+ var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4887
+ z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4888
+ z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4889
+ z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
4890
+ ]);
4891
+ var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
4453
4892
  // src/lib/config.ts
4454
4893
  import { execFileSync as execFileSync2 } from "node:child_process";
4455
4894
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
4456
4895
  import { homedir } from "node:os";
4457
4896
  import { dirname, join, resolve } from "node:path";
4458
- import { z } from "zod";
4897
+ import { z as z2 } from "zod";
4459
4898
 
4460
4899
  // src/lib/agent-vendors.ts
4461
4900
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
4462
4901
 
4463
4902
  // src/lib/config.ts
4464
- var EnvEntrySchema = z.union([
4465
- z.literal(true),
4466
- z.object({ optional: z.literal(true) }),
4467
- z.object({ anyOf: z.array(z.string()).min(2) }),
4468
- z.string()
4903
+ var EnvEntrySchema = z2.union([
4904
+ z2.literal(true),
4905
+ z2.object({ optional: z2.literal(true) }),
4906
+ z2.object({ anyOf: z2.array(z2.string()).min(2) }),
4907
+ z2.string()
4469
4908
  ]);
4470
- var CheckEntrySchema = z.object({
4471
- run: z.string(),
4472
- scope: z.enum(["diff", "full"]),
4473
- include: z.array(z.string()).optional(),
4474
- args: z.array(z.string()).optional(),
4475
- timeoutMs: z.number().optional(),
4476
- env: z.record(z.string(), EnvEntrySchema).optional(),
4477
- requiresOpenPr: z.boolean().optional(),
4478
- ownWorkflow: z.boolean().optional()
4909
+ var CheckEntrySchema = z2.object({
4910
+ run: z2.string(),
4911
+ scope: z2.enum(["diff", "full"]),
4912
+ include: z2.array(z2.string()).optional(),
4913
+ args: z2.array(z2.string()).optional(),
4914
+ timeoutMs: z2.number().optional(),
4915
+ env: z2.record(z2.string(), EnvEntrySchema).optional(),
4916
+ requiresOpenPr: z2.boolean().optional(),
4917
+ ownWorkflow: z2.boolean().optional()
4479
4918
  }).superRefine((entry2, ctx) => {
4480
4919
  if (!entry2.env)
4481
4920
  return;
@@ -4485,22 +4924,22 @@ var CheckEntrySchema = z.object({
4485
4924
  const members = value.anyOf;
4486
4925
  if (new Set(members).size !== members.length) {
4487
4926
  ctx.addIssue({
4488
- code: z.ZodIssueCode.custom,
4927
+ code: z2.ZodIssueCode.custom,
4489
4928
  message: `env.${key}.anyOf has duplicate members`,
4490
4929
  path: ["env", key, "anyOf"]
4491
4930
  });
4492
4931
  }
4493
4932
  if (!members.includes(key)) {
4494
4933
  ctx.addIssue({
4495
- code: z.ZodIssueCode.custom,
4934
+ code: z2.ZodIssueCode.custom,
4496
4935
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
4497
4936
  path: ["env", key, "anyOf"]
4498
4937
  });
4499
4938
  }
4500
4939
  }
4501
4940
  });
4502
- var RoleEntrySchema = z.object({
4503
- contract: z.string().refine((p) => p.includes("/"), {
4941
+ var RoleEntrySchema = z2.object({
4942
+ contract: z2.string().refine((p) => p.includes("/"), {
4504
4943
  message: 'must be a path (contain at least one "/"), not a bare filename'
4505
4944
  })
4506
4945
  });
@@ -4519,19 +4958,21 @@ var BRIEF_BUILTINS = [
4519
4958
  "closesN",
4520
4959
  "premiseCoverage",
4521
4960
  "issueRationale",
4961
+ "objectives",
4962
+ "briefSections",
4522
4963
  "milestoneShape"
4523
4964
  ];
4524
- var BriefSectionSchema = z.union([
4525
- z.object({ builtin: z.enum(BRIEF_BUILTINS) }),
4526
- z.object({ heading: z.string().min(1), name: z.string().optional() }),
4527
- z.object({ field: z.string().min(1), name: z.string().optional() }),
4528
- z.object({ phrase: z.string().min(1), name: z.string().optional() })
4965
+ var BriefSectionSchema = z2.union([
4966
+ z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
4967
+ z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
4968
+ z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
4969
+ z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
4529
4970
  ]);
4530
- var BriefSchemaSchema = z.object({
4531
- pr: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4532
- issue: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4533
- milestone: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
4534
- ack: z.array(z.enum(BRIEF_BUILTINS)).optional()
4971
+ var BriefSchemaSchema = z2.object({
4972
+ pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4973
+ issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4974
+ milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
4975
+ ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
4535
4976
  });
4536
4977
  function isSafeRepoRelPath(p) {
4537
4978
  if (p.length === 0)
@@ -4540,7 +4981,7 @@ function isSafeRepoRelPath(p) {
4540
4981
  return false;
4541
4982
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
4542
4983
  }
4543
- var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
4984
+ var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
4544
4985
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
4545
4986
  });
4546
4987
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -4550,22 +4991,22 @@ function isCanonicalHookBlockPath(p) {
4550
4991
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
4551
4992
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
4552
4993
  });
4553
- var ManagedBlockRecordSchema = z.object({
4994
+ var ManagedBlockRecordSchema = z2.object({
4554
4995
  path: ManagedHookBlockPath,
4555
- marker: z.string(),
4556
- comment: z.enum(["hash", "html"])
4996
+ marker: z2.string(),
4997
+ comment: z2.enum(["hash", "html"])
4557
4998
  });
4558
- var ManagedManifestSchema = z.object({
4559
- version: z.number().int().positive(),
4560
- files: z.array(SafeRepoRelPath),
4561
- blocks: z.array(ManagedBlockRecordSchema),
4562
- labels: z.array(z.string()),
4563
- agents: z.array(z.enum(AGENT_VENDORS)).optional()
4999
+ var ManagedManifestSchema = z2.object({
5000
+ version: z2.number().int().positive(),
5001
+ files: z2.array(SafeRepoRelPath),
5002
+ blocks: z2.array(ManagedBlockRecordSchema),
5003
+ labels: z2.array(z2.string()),
5004
+ agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
4564
5005
  });
4565
- var ProjectEntrySchema = z.object({
4566
- name: z.string().min(1),
4567
- description: z.string().min(1).optional(),
4568
- path: z.string().min(1).optional()
5006
+ var ProjectEntrySchema = z2.object({
5007
+ name: z2.string().min(1),
5008
+ description: z2.string().min(1).optional(),
5009
+ path: z2.string().min(1).optional()
4569
5010
  });
4570
5011
  function parseTokensCollectDeclaration(value) {
4571
5012
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -4576,31 +5017,35 @@ function parseTokensCollectDeclaration(value) {
4576
5017
  return null;
4577
5018
  return { interpreter, script };
4578
5019
  }
4579
- var VinayaConfigSchema = z.object({
4580
- rings: z.object({
4581
- ring1_forgeWriteInterception: z.boolean(),
4582
- ring2_asyncAudits: z.boolean()
5020
+ var VinayaConfigSchema = z2.object({
5021
+ rings: z2.object({
5022
+ ring1_forgeWriteInterception: z2.boolean(),
5023
+ ring2_asyncAudits: z2.boolean()
4583
5024
  }).optional(),
4584
- checks: z.record(z.string(), CheckEntrySchema).optional(),
4585
- roles: z.record(z.string(), RoleEntrySchema).optional(),
5025
+ checks: z2.record(z2.string(), CheckEntrySchema).optional(),
5026
+ roles: z2.record(z2.string(), RoleEntrySchema).optional(),
4586
5027
  briefSchema: BriefSchemaSchema.optional(),
4587
5028
  managed: ManagedManifestSchema.optional(),
4588
- principals: z.array(z.string()).min(1).optional(),
4589
- releaseActor: z.string().min(1).optional(),
4590
- ci: z.object({ setup: z.string().min(1) }).optional(),
4591
- tokens: z.object({
4592
- collect: z.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5029
+ principals: z2.array(z2.string()).min(1).optional(),
5030
+ releaseActor: z2.string().min(1).optional(),
5031
+ ci: z2.object({ setup: z2.string().min(1) }).optional(),
5032
+ tokens: z2.object({
5033
+ collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
4593
5034
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
4594
5035
  })
4595
5036
  }).optional(),
4596
- blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
4597
- proseGates: z.object({
4598
- doctrineRoot: z.string().min(1).optional(),
4599
- readerFacingPrefix: z.string().min(1).optional(),
4600
- readerFacingSuffix: z.string().min(1).optional(),
4601
- legacySlugDir: z.string().min(1).optional()
5037
+ blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
5038
+ proseGates: z2.object({
5039
+ doctrineRoot: z2.string().min(1).optional(),
5040
+ readerFacingPrefix: z2.string().min(1).optional(),
5041
+ readerFacingSuffix: z2.string().min(1).optional(),
5042
+ legacySlugDir: z2.string().min(1).optional()
4602
5043
  }).optional(),
4603
- projects: z.array(ProjectEntrySchema).optional()
5044
+ projects: z2.array(ProjectEntrySchema).optional(),
5045
+ dispatch: z2.object({
5046
+ timeoutMs: z2.number().int().positive().optional(),
5047
+ agent: z2.enum(["claude", "codex", "gemini"]).optional()
5048
+ }).optional()
4604
5049
  });
4605
5050
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
4606
5051
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");