lazycodex-ai 5.0.0-beta.48 → 5.0.0-beta.49

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 (118) hide show
  1. package/README.ja.md +35 -27
  2. package/README.ko.md +35 -27
  3. package/README.md +19 -34
  4. package/README.ru.md +33 -28
  5. package/README.zh-cn.md +31 -27
  6. package/dist/cli/index.js +19 -15
  7. package/dist/cli-node/index.js +19 -15
  8. package/package.json +1 -1
  9. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +2 -2
  10. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  11. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  12. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  13. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  14. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  15. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  17. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  19. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  20. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  22. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  26. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +11 -10
  28. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  31. package/packages/omo-codex/plugin/components/ulw-execute-continuation/AGENTS.md +3 -3
  32. package/packages/omo-codex/plugin/components/ulw-execute-continuation/README.md +3 -3
  33. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +4 -4
  34. package/packages/omo-codex/plugin/components/ulw-execute-continuation/dist/cli.js +1 -1
  35. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -13
  36. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/ulw-execute-continuation/src/codex-hook.ts +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-execute-continuation/src/types.ts +1 -1
  39. package/packages/omo-codex/plugin/components/ulw-execute-continuation/test/cli.test.ts +3 -7
  40. package/packages/omo-codex/plugin/components/ulw-execute-continuation/test/codex-hook.test.ts +16 -0
  41. package/packages/omo-codex/plugin/components/ulw-loop/README.md +2 -2
  42. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +11 -10
  43. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +8 -15
  44. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -2
  45. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
  46. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -1
  47. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +88 -67
  48. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +3 -4
  49. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +1 -1
  50. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -1
  51. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +13 -16
  52. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +2 -0
  53. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +47 -26
  54. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +2 -2
  55. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +1 -0
  56. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +14 -4
  57. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +17 -4
  58. package/packages/omo-codex/plugin/components/ulw-loop/package.json +2 -2
  59. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +8 -14
  61. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +2 -2
  62. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -1
  63. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +5 -1
  64. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +3 -4
  65. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +1 -1
  66. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +4 -1
  67. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +21 -16
  68. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +52 -25
  69. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +2 -2
  70. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +16 -4
  71. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +11 -12
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-fields-messages.test.ts +98 -0
  73. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-lazycodex-surface.test.ts +93 -0
  74. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +9 -4
  75. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +10 -0
  76. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +70 -7
  77. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +18 -0
  85. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  95. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  98. package/packages/omo-codex/plugin/package-lock.json +12 -12
  99. package/packages/omo-codex/plugin/package.json +1 -1
  100. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +61 -33
  101. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +4 -2
  102. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +4 -2
  103. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +4 -2
  104. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +13 -3
  105. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +11 -10
  106. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +11 -6
  107. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +24 -0
  108. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  109. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +4 -2
  110. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +4 -2
  111. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +8 -4
  112. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +62 -0
  113. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +3 -3
  114. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +23 -1
  115. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +36 -16
  116. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  117. package/packages/prompts-core/prompts/ultrawork/codex.md +11 -10
  118. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +0 -17
@@ -21,7 +21,7 @@ function gateTemplate(surface, base) {
21
21
  },
22
22
  ];
23
23
  const manualQa = {
24
- by: surface === "omo-senpi" ? "main-session" : "lazycodex-qa-executor",
24
+ by: "main-session",
25
25
  status: "passed",
26
26
  evidence: "<replace:manual QA evidence>",
27
27
  surfaceEvidence: [
@@ -58,7 +58,7 @@ function gateTemplate(surface, base) {
58
58
  const common = {
59
59
  manualQa,
60
60
  gateReview: {
61
- by: surface === "omo-senpi" ? "category:deep" : "lazycodex-gate-reviewer",
61
+ by: surface === "omo-senpi" ? "category:deep" : "main-session",
62
62
  recommendation: "APPROVE",
63
63
  reportPath: artifactPath(base, "gate-review.md"),
64
64
  evidence: "<replace:gate review evidence>",
@@ -80,19 +80,7 @@ function gateTemplate(surface, base) {
80
80
  adversarialClassesCovered: ["<replace:adversarial class>"],
81
81
  },
82
82
  };
83
- if (surface === "omo-senpi")
84
- return common;
85
- return {
86
- codeReview: {
87
- by: "lazycodex-code-reviewer",
88
- recommendation: "APPROVE",
89
- codeQualityStatus: "CLEAR",
90
- reportPath: artifactPath(base, "code-review.md"),
91
- evidence: "<replace:code review evidence>",
92
- blockers: [],
93
- },
94
- ...common,
95
- };
83
+ return common;
96
84
  }
97
85
  export async function checkpointTemplate(repoRoot, scope, goalId) {
98
86
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -107,6 +95,11 @@ export async function checkpointTemplate(repoRoot, scope, goalId) {
107
95
  "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
108
96
  'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
109
97
  'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
98
+ ...(resolveToolkitSurface() === "lazycodex"
99
+ ? [
100
+ "Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session.",
101
+ ]
102
+ : []),
110
103
  ...(hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]),
111
104
  ].join(" ");
112
105
  return {
@@ -79,14 +79,14 @@ function ledgerKind(status, goal, aggregateCompletion) {
79
79
  return status === "blocked" ? "goal_blocked" : "goal_failed";
80
80
  }
81
81
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
82
- const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview.codeQualityStatus === "WATCH";
82
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
83
83
  const entry = {
84
84
  at: now,
85
85
  kind: ledgerKind(args.status, goal, aggregateCompletion),
86
86
  goalId: goal.id,
87
87
  status: goal.status,
88
88
  evidence: watch && qualityGate.surface === "lazycodex"
89
- ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}`
89
+ ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}`
90
90
  : args.evidence,
91
91
  };
92
92
  if (codexGoal !== undefined)
@@ -1,5 +1,5 @@
1
1
  import type { UlwLoopCodexGoalMode, UlwLoopPlan } from "./types.js";
2
- export declare const ULW_LOOP_HELP = "Usage:\n omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)\n omo-agent-toolkit help | --help | -h (this message)\n omo-agent-toolkit ulw-loop help\n omo-agent-toolkit ulw-loop create-goals --brief \"...\" [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]\n omo-agent-toolkit ulw-loop status [--json]\n omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]\n omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]\n omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence \"...\" [--notes \"...\"] [--json]\n omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]\n omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence \"...\" --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]\n omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence \"...\" --rationale \"...\" [--proposals-json <json-or-path>] [--json]\n omo-agent-toolkit ulw-loop add-goal --title \"...\" --objective \"...\" [--json]\n omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title \"...\" --objective \"...\" --evidence \"...\" --codex-goal-json <...> [--json]\n\nEvery state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly.\nEvery subcommand accepts --help | -h to print its own usage line.";
2
+ export declare const ULW_LOOP_HELP = "Usage:\n omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)\n omo-agent-toolkit help | --help | -h (this message)\n omo-agent-toolkit ulw-loop help\n omo-agent-toolkit ulw-loop create-goals --brief \"...\" [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]\n omo-agent-toolkit ulw-loop status [--json]\n omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]\n omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]\n omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence \"...\" [--notes \"...\"] [--json]\n omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]\n omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence \"...\" --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]\n omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence \"...\" --rationale \"...\" [--proposals-json <json-or-path>] [--json]\n omo-agent-toolkit ulw-loop add-goal --title \"...\" --objective \"...\" [--json]\n omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title \"...\" --objective \"...\" --evidence \"...\" --codex-goal-json <...> [--json]\n\nEvery state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.\nEvery subcommand accepts --help | -h to print its own usage line.";
3
3
  export declare function subcommandHelp(subcommand: string): string;
4
4
  export declare function printJson(value: unknown): void;
5
5
  export declare function printJsonError(error: unknown): void;
@@ -14,7 +14,7 @@ export const ULW_LOOP_HELP = `Usage:
14
14
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
15
15
  omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
16
16
 
17
- Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly.
17
+ Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.
18
18
  Every subcommand accepts --help | -h to print its own usage line.`;
19
19
  export function subcommandHelp(subcommand) {
20
20
  const lines = ULW_LOOP_HELP.split("\n").filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
@@ -946,7 +946,7 @@ function isRecord(value) {
946
946
  function section(value, field) {
947
947
  if (isRecord(value))
948
948
  return value;
949
- invalid(`Final quality gate is missing ${field} evidence.`, field);
949
+ invalid(`Final quality gate is missing ${field} evidence (accepted input: a bare object with manualQa/gateReview/iteration/criteriaCoverage and optional codeReview, or the same object under a top-level "qualityGate" key).`, field);
950
950
  return {};
951
951
  }
952
952
  function textField(value, field) {
@@ -1129,11 +1129,25 @@ var REVIEWER_ROLES_BY_SURFACE = {
1129
1129
  }
1130
1130
  };
1131
1131
  var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
1132
+ var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
1133
+ lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
1134
+ "omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
1135
+ };
1136
+ var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
1137
+ lazycodex: ["codeReview"],
1138
+ "omo-senpi": []
1139
+ };
1132
1140
  var GATE_SECTION_BY_ACCEPTOR = {
1133
1141
  lazycodex: {
1134
- codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview],
1135
- manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa],
1136
- gateReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview]
1142
+ codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
1143
+ manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
1144
+ gateReview: [
1145
+ REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
1146
+ "category:deep",
1147
+ "category:unspecified-high",
1148
+ "category:unspecified-low",
1149
+ "main-session"
1150
+ ]
1137
1151
  },
1138
1152
  "omo-senpi": {
1139
1153
  manualQa: ["main-session"],
@@ -1213,16 +1227,6 @@ function clearGoalBlockerFields(goal) {
1213
1227
  }
1214
1228
 
1215
1229
  // components/ulw-loop/src/quality-gate.ts
1216
- function reviewerRoleField(value, expected, field) {
1217
- if (typeof value !== "string" || value.trim() === "") {
1218
- textField(value, field);
1219
- return expected;
1220
- }
1221
- const actual = textField(value, field);
1222
- if (actual !== expected)
1223
- invalid(`${field} must be ${expected}.`, field);
1224
- return expected;
1225
- }
1226
1230
  function reviewerAcceptorField(value, surface, sectionName) {
1227
1231
  const field = `${sectionName}.by`;
1228
1232
  const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
@@ -1240,20 +1244,25 @@ function validateQualityGate(input, opts) {
1240
1244
  }
1241
1245
  function validateQualityGateUncollected(input, opts) {
1242
1246
  const surface = opts?.reviewerSurface ?? "lazycodex";
1243
- const gate = section(input, "qualityGate");
1247
+ const raw = input;
1248
+ const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
1249
+ for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
1250
+ section(gate[name], name);
1251
+ for (const name of Object.keys(gate)) {
1252
+ if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
1253
+ invalid("omo-senpi gate has no codeReview lane.", name);
1254
+ }
1244
1255
  if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
1245
1256
  invalid("omo-senpi gate has no codeReview lane.", "codeReview");
1246
1257
  const manualQa = section(gate["manualQa"], "manualQa");
1247
1258
  const gateReview = section(gate["gateReview"], "gateReview");
1248
1259
  const iteration = section(gate["iteration"], "iteration");
1249
1260
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
1250
- const codeReview = surface === "lazycodex" ? section(gate["codeReview"], "codeReview") : {};
1261
+ const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
1251
1262
  const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
1252
1263
  const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
1253
1264
  const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
1254
1265
  const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
1255
- if (surface === "lazycodex")
1256
- reviewerRoleField(codeReview?.["by"], "lazycodex-code-reviewer", "codeReview.by");
1257
1266
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
1258
1267
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
1259
1268
  if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
@@ -1298,13 +1307,15 @@ function validateQualityGateUncollected(input, opts) {
1298
1307
  };
1299
1308
  if (surface === "omo-senpi")
1300
1309
  return { surface, ...common };
1310
+ if (codeReview === undefined)
1311
+ return { surface, ...common };
1301
1312
  const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
1302
1313
  checkFile(codeReportPath, "codeReview.reportPath", opts);
1303
1314
  return {
1304
1315
  surface,
1305
1316
  ...common,
1306
1317
  codeReview: {
1307
- by: "lazycodex-code-reviewer",
1318
+ by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
1308
1319
  recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
1309
1320
  codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
1310
1321
  reportPath: codeReportPath,
@@ -1640,13 +1651,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
1640
1651
  return status === "blocked" ? "goal_blocked" : "goal_failed";
1641
1652
  }
1642
1653
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
1643
- const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview.codeQualityStatus === "WATCH";
1654
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
1644
1655
  const entry = {
1645
1656
  at: now,
1646
1657
  kind: ledgerKind2(args.status, goal, aggregateCompletion),
1647
1658
  goalId: goal.id,
1648
1659
  status: goal.status,
1649
- evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1660
+ evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
1650
1661
  };
1651
1662
  if (codexGoal !== undefined)
1652
1663
  entry.codexGoal = codexGoal;
@@ -1763,7 +1774,7 @@ function gateTemplate(surface, base) {
1763
1774
  }
1764
1775
  ];
1765
1776
  const manualQa = {
1766
- by: surface === "omo-senpi" ? "main-session" : "lazycodex-qa-executor",
1777
+ by: "main-session",
1767
1778
  status: "passed",
1768
1779
  evidence: "<replace:manual QA evidence>",
1769
1780
  surfaceEvidence: [
@@ -1800,7 +1811,7 @@ function gateTemplate(surface, base) {
1800
1811
  const common = {
1801
1812
  manualQa,
1802
1813
  gateReview: {
1803
- by: surface === "omo-senpi" ? "category:deep" : "lazycodex-gate-reviewer",
1814
+ by: surface === "omo-senpi" ? "category:deep" : "main-session",
1804
1815
  recommendation: "APPROVE",
1805
1816
  reportPath: artifactPath(base, "gate-review.md"),
1806
1817
  evidence: "<replace:gate review evidence>",
@@ -1822,19 +1833,7 @@ function gateTemplate(surface, base) {
1822
1833
  adversarialClassesCovered: ["<replace:adversarial class>"]
1823
1834
  }
1824
1835
  };
1825
- if (surface === "omo-senpi")
1826
- return common;
1827
- return {
1828
- codeReview: {
1829
- by: "lazycodex-code-reviewer",
1830
- recommendation: "APPROVE",
1831
- codeQualityStatus: "CLEAR",
1832
- reportPath: artifactPath(base, "code-review.md"),
1833
- evidence: "<replace:code review evidence>",
1834
- blockers: []
1835
- },
1836
- ...common
1837
- };
1836
+ return common;
1838
1837
  }
1839
1838
  async function checkpointTemplate(repoRoot, scope, goalId) {
1840
1839
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -1849,6 +1848,9 @@ async function checkpointTemplate(repoRoot, scope, goalId) {
1849
1848
  "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
1850
1849
  'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
1851
1850
  'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
1851
+ ...resolveToolkitSurface() === "lazycodex" ? [
1852
+ "Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
1853
+ ] : [],
1852
1854
  ...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
1853
1855
  ].join(" ");
1854
1856
  return {
@@ -1877,7 +1879,7 @@ var ULW_LOOP_HELP = `Usage:
1877
1879
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
1878
1880
  omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
1879
1881
 
1880
- Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly.
1882
+ Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.
1881
1883
  Every subcommand accepts --help | -h to print its own usage line.`;
1882
1884
  function subcommandHelp(subcommand) {
1883
1885
  const lines = ULW_LOOP_HELP.split(`
@@ -2042,12 +2044,11 @@ function finalSection(plan, goal, isFinal, aggregate, surface) {
2042
2044
  const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
2043
2045
  const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
2044
2046
  return joinLines([
2045
- "Final story — run mandatory quality gate before update_goal:",
2047
+ "Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
2046
2048
  "- Run targeted verification for changed behavior.",
2047
2049
  "- Confirm every manualQa artifact path exists and has non-zero size.",
2048
- `- First spawn ${roles.codeReview} and ${roles.manualQa} in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).`,
2049
- `- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
2050
- "- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
2050
+ `- Run manual QA yourself and write its artifact under currentAttemptDir. Only if the user explicitly demands strict, rigorous, or high-accuracy review, spawn ${roles.gateReview}, optionally also ${roles.codeReview} and ${roles.manualQa}; otherwise set manualQa.by and gateReview.by to "main-session".`,
2051
+ "- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage; include codeReview only when strict review was requested. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
2051
2052
  "- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
2052
2053
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
2053
2054
  "- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
@@ -2773,10 +2774,10 @@ function activeGoalActions(plan, active) {
2773
2774
  ];
2774
2775
  if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
2775
2776
  return [
2776
- `${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate.`
2777
+ `${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate. Use status --json's currentAttemptDir for all quality-gate artifacts.`
2777
2778
  ];
2778
2779
  return [
2779
- `${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`.`
2780
+ `${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`. Put quality-gate artifacts under the currentAttemptDir shown by status --json.`
2780
2781
  ];
2781
2782
  }
2782
2783
 
@@ -3767,10 +3768,10 @@ function readAll(stdin) {
3767
3768
 
3768
3769
  // components/ulw-loop/src/spawn-guard.ts
3769
3770
  import { randomBytes } from "node:crypto";
3770
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
3771
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
3771
3772
  import { dirname as dirname3, join as join3 } from "node:path";
3772
3773
  var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
3773
- var DEFAULT_FANOUT_LIMIT = 60;
3774
+ var DEFAULT_FANOUT_LIMIT = 24;
3774
3775
  var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
3775
3776
  var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
3776
3777
  var REVIEW_AGENT_TYPES = [
@@ -3782,6 +3783,9 @@ var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
3782
3783
  function applySpawnGuards(payload, options = {}) {
3783
3784
  if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3784
3785
  return "";
3786
+ const breaker = readAdmissionBreaker(payload.session_id);
3787
+ if (breaker !== null)
3788
+ return deny(`Subagent admission failed earlier in this session (${breaker}). Do not spawn more workers or reviewers; report the capacity block and wait for the user.`);
3785
3789
  const scope = { sessionId: payload.session_id };
3786
3790
  const stateDir = ulwLoopDir(payload.cwd, scope);
3787
3791
  const plan = readPlan(join3(stateDir, "goals.json"));
@@ -3802,7 +3806,7 @@ function evaluateGuards(payload, plan, stateDir) {
3802
3806
  return deny(fanOutPeek);
3803
3807
  const missingArtifact = missingGateArtifact(payload, plan);
3804
3808
  if (missingArtifact !== null)
3805
- return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
3809
+ return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
3806
3810
  const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
3807
3811
  if (reviewDenial !== null)
3808
3812
  return deny(reviewDenial);
@@ -3811,6 +3815,26 @@ function evaluateGuards(payload, plan, stateDir) {
3811
3815
  return deny(fanOutDenial);
3812
3816
  return "";
3813
3817
  }
3818
+ async function runSpawnAdmissionRecorderCli(stdin, stdout) {
3819
+ const chunks = [];
3820
+ for await (const chunk of stdin)
3821
+ chunks.push(Buffer.from(chunk));
3822
+ try {
3823
+ const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3824
+ const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
3825
+ if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
3826
+ return;
3827
+ const dataDir = process.env["PLUGIN_DATA"];
3828
+ if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
3829
+ return;
3830
+ const markerDir = join3(dataDir, "spawn-breaker");
3831
+ mkdirSync2(markerDir, { recursive: true });
3832
+ atomicWriteJson(join3(markerDir, `${payload["session_id"]}.json`), {
3833
+ reason: response,
3834
+ at: new Date().toISOString()
3835
+ });
3836
+ } catch {}
3837
+ }
3814
3838
  async function runSpawnGuardCli(stdin, stdout) {
3815
3839
  try {
3816
3840
  const chunks = [];
@@ -3871,8 +3895,7 @@ function missingGateArtifact(payload, plan) {
3871
3895
  if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
3872
3896
  return null;
3873
3897
  const scope = { sessionId: payload.session_id };
3874
- const surface = resolveToolkitSurface();
3875
- const requiredArtifacts = surface === "omo-senpi" ? [`${goal3.id}-manual-qa.md`] : [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`];
3898
+ const requiredArtifacts = [`${goal3.id}-manual-qa.md`];
3876
3899
  if (plan.evidenceLayoutVersion === 2) {
3877
3900
  const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
3878
3901
  for (const name of requiredArtifacts) {
@@ -3882,16 +3905,8 @@ function missingGateArtifact(payload, plan) {
3882
3905
  }
3883
3906
  return null;
3884
3907
  }
3885
- const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
3886
- if (surface !== "omo-senpi" && !isNonEmptyFile(join3(payload.cwd, flatReport)))
3887
- return flatReport;
3888
- if (surface === "omo-senpi") {
3889
- const manualQa = `.omo/evidence/${goal3.id}-manual-qa.md`;
3890
- return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
3891
- }
3892
- if (!hasOtherEvidenceFile(join3(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
3893
- return `.omo/evidence/${goal3.id}-manual-qa.md`;
3894
- return null;
3908
+ const manualQa = `.omo/evidence/${goal3.id}-manual-qa.md`;
3909
+ return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
3895
3910
  }
3896
3911
  function isGateReviewerSpawn(toolInput) {
3897
3912
  const agentType = reviewAgentType(toolInput);
@@ -3954,6 +3969,17 @@ function deny(reason) {
3954
3969
  })}
3955
3970
  `;
3956
3971
  }
3972
+ function readAdmissionBreaker(sessionId) {
3973
+ const dataDir = process.env["PLUGIN_DATA"];
3974
+ if (typeof dataDir !== "string")
3975
+ return null;
3976
+ try {
3977
+ const value = JSON.parse(readFileSync5(join3(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
3978
+ return typeof value.reason === "string" ? value.reason : "capacity limit";
3979
+ } catch {
3980
+ return null;
3981
+ }
3982
+ }
3957
3983
  function fanOutLimit() {
3958
3984
  const raw = process.env["OMO_SPAWN_FANOUT_LIMIT"];
3959
3985
  if (raw === undefined)
@@ -3977,15 +4003,6 @@ function isNonEmptyFile(path) {
3977
4003
  throw error;
3978
4004
  }
3979
4005
  }
3980
- function hasOtherEvidenceFile(evidenceDir, excludedName) {
3981
- try {
3982
- return readdirSync2(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join3(evidenceDir, name)));
3983
- } catch (error) {
3984
- if (error instanceof Error)
3985
- return false;
3986
- throw error;
3987
- }
3988
- }
3989
4006
  function readCount(counterPath) {
3990
4007
  try {
3991
4008
  const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
@@ -4245,6 +4262,10 @@ async function main() {
4245
4262
  await runStopResumeHookCli(process.stdin, process.stdout);
4246
4263
  return 0;
4247
4264
  }
4265
+ if (sub === "post-tool-use-spawn") {
4266
+ await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
4267
+ return 0;
4268
+ }
4248
4269
  if (sub === "pre-tool-use-spawn") {
4249
4270
  await runSpawnGuardCli(process.stdin, process.stdout);
4250
4271
  return 0;
@@ -92,12 +92,11 @@ function finalSection(plan, goal, isFinal, aggregate, surface) {
92
92
  const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
93
93
  const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
94
94
  return joinLines([
95
- "Final story — run mandatory quality gate before update_goal:",
95
+ "Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
96
96
  "- Run targeted verification for changed behavior.",
97
97
  "- Confirm every manualQa artifact path exists and has non-zero size.",
98
- `- First spawn ${roles.codeReview} and ${roles.manualQa} in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).`,
99
- `- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
100
- "- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
98
+ `- Run manual QA yourself and write its artifact under currentAttemptDir. Only if the user explicitly demands strict, rigorous, or high-accuracy review, spawn ${roles.gateReview}, optionally also ${roles.codeReview} and ${roles.manualQa}; otherwise set manualQa.by and gateReview.by to "main-session".`,
99
+ "- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage; include codeReview only when strict review was requested. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
101
100
  "- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
102
101
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
103
102
  "- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
@@ -122,7 +122,7 @@ interface UlwLoopQualityGateCommon {
122
122
  }
123
123
  export interface UlwLoopQualityGateLazycodex extends UlwLoopQualityGateCommon {
124
124
  readonly surface: "lazycodex";
125
- readonly codeReview: {
125
+ readonly codeReview?: {
126
126
  readonly by: string;
127
127
  readonly recommendation: "APPROVE";
128
128
  readonly codeQualityStatus: "CLEAR" | "WATCH";
@@ -55,7 +55,7 @@ export function isRecord(value) {
55
55
  export function section(value, field) {
56
56
  if (isRecord(value))
57
57
  return value;
58
- invalid(`Final quality gate is missing ${field} evidence.`, field);
58
+ invalid(`Final quality gate is missing ${field} evidence (accepted input: a bare object with manualQa/gateReview/iteration/criteriaCoverage and optional codeReview, or the same object under a top-level "qualityGate" key).`, field);
59
59
  return {};
60
60
  }
61
61
  export function textField(value, field) {
@@ -1,18 +1,8 @@
1
1
  import { artifactCompatible, artifactMap, checkFile, compatibleKindsFor, parseArtifactRefs, referencedArtifacts, surfaceField, } from "./quality-gate-artifacts.js";
2
2
  import { emptyBlockers, invalid, isPoisoned, isPoisonedArtifactKind, literal, numberField, section, stringArray, textField, withQualityGateCollector, } from "./quality-gate-fields.js";
3
3
  import { adversarialVerdict, codeQualityStatusField, passedVerdict } from "./quality-gate-verdicts.js";
4
- import { GATE_SECTION_BY_ACCEPTOR } from "./surface.js";
4
+ import { GATE_SECTION_BY_ACCEPTOR, OPTIONAL_GATE_SECTIONS_BY_SURFACE, REQUIRED_GATE_SECTIONS_BY_SURFACE, } from "./surface.js";
5
5
  export { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, normalizeBlockerEvidence, sameBlockerOccurrences, } from "./quality-gate-blockers.js";
6
- function reviewerRoleField(value, expected, field) {
7
- if (typeof value !== "string" || value.trim() === "") {
8
- textField(value, field);
9
- return expected;
10
- }
11
- const actual = textField(value, field);
12
- if (actual !== expected)
13
- invalid(`${field} must be ${expected}.`, field);
14
- return expected;
15
- }
16
6
  function reviewerAcceptorField(value, surface, sectionName) {
17
7
  const field = `${sectionName}.by`;
18
8
  const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
@@ -30,20 +20,25 @@ export function validateQualityGate(input, opts) {
30
20
  }
31
21
  function validateQualityGateUncollected(input, opts) {
32
22
  const surface = opts?.reviewerSurface ?? "lazycodex";
33
- const gate = section(input, "qualityGate");
23
+ const raw = input;
24
+ const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
25
+ for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
26
+ section(gate[name], name);
27
+ for (const name of Object.keys(gate)) {
28
+ if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
29
+ invalid("omo-senpi gate has no codeReview lane.", name);
30
+ }
34
31
  if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
35
32
  invalid("omo-senpi gate has no codeReview lane.", "codeReview");
36
33
  const manualQa = section(gate["manualQa"], "manualQa");
37
34
  const gateReview = section(gate["gateReview"], "gateReview");
38
35
  const iteration = section(gate["iteration"], "iteration");
39
36
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
40
- const codeReview = surface === "lazycodex" ? section(gate["codeReview"], "codeReview") : {};
37
+ const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
41
38
  const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
42
39
  const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
43
40
  const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
44
41
  const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
45
- if (surface === "lazycodex")
46
- reviewerRoleField(codeReview?.["by"], "lazycodex-code-reviewer", "codeReview.by");
47
42
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
48
43
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
49
44
  if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
@@ -88,13 +83,15 @@ function validateQualityGateUncollected(input, opts) {
88
83
  };
89
84
  if (surface === "omo-senpi")
90
85
  return { surface, ...common };
86
+ if (codeReview === undefined)
87
+ return { surface, ...common };
91
88
  const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
92
89
  checkFile(codeReportPath, "codeReview.reportPath", opts);
93
90
  return {
94
91
  surface,
95
92
  ...common,
96
93
  codeReview: {
97
- by: "lazycodex-code-reviewer",
94
+ by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
98
95
  recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
99
96
  codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
100
97
  reportPath: codeReportPath,
@@ -1,6 +1,8 @@
1
1
  import type { PreToolUsePayload } from "./codex-hook.js";
2
+ export declare const DEFAULT_FANOUT_LIMIT = 24;
2
3
  export interface SpawnGuardOptions {
3
4
  readonly lockTimeoutMs?: number;
4
5
  }
5
6
  export declare function applySpawnGuards(payload: PreToolUsePayload, options?: SpawnGuardOptions): string;
7
+ export declare function runSpawnAdmissionRecorderCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void>;
6
8
  export declare function runSpawnGuardCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void>;