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