lazycodex-ai 5.0.0-beta.34 → 5.0.0-beta.35

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 (187) hide show
  1. package/README.ja.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.ru.md +1 -1
  5. package/README.zh-cn.md +1 -1
  6. package/dist/cli/index.js +48342 -48539
  7. package/dist/cli-node/index.js +49017 -49214
  8. package/package.json +2 -4
  9. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -3
  10. package/packages/omo-codex/plugin/.mcp.json +0 -6
  11. package/packages/omo-codex/plugin/AGENTS.md +5 -5
  12. package/packages/omo-codex/plugin/README.md +1 -1
  13. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -15
  14. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  15. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  17. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  19. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  20. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  21. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +1 -1
  23. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  24. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  25. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  27. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +1 -1
  31. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  32. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  33. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +2 -2
  34. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  35. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  36. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +0 -1
  37. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  39. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/ulw-loop/README.md +11 -1
  41. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +2 -2
  42. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
  43. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
  44. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +1 -0
  45. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -0
  46. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +10 -3
  47. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +35 -38
  48. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +278 -193
  49. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +5 -0
  50. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +7 -51
  51. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +39 -22
  52. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  53. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +64 -8
  54. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  55. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +37 -18
  56. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  57. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  58. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -1
  59. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
  60. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +6 -0
  61. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +10 -3
  62. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +32 -63
  63. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +7 -53
  64. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +50 -31
  65. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +67 -14
  66. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  67. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +36 -17
  68. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +26 -0
  69. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +47 -2
  70. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  71. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  73. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  95. package/packages/omo-codex/plugin/package-lock.json +12 -146
  96. package/packages/omo-codex/plugin/package.json +1 -2
  97. package/packages/omo-codex/plugin/scripts/AGENTS.md +0 -1
  98. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +0 -1
  99. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  100. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  101. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  102. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  103. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  104. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
  105. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
  106. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +2 -2
  107. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -1
  108. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +0 -1
  109. package/packages/omo-codex/plugin/test/AGENTS.md +2 -2
  110. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +2 -21
  111. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -2
  112. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  113. package/packages/omo-codex/plugin/test/auto-update.test.mjs +0 -25
  114. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  115. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +0 -50
  116. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +0 -1
  117. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  118. package/packages/omo-codex/scripts/install-dist/install-local.mjs +227 -435
  119. package/packages/prompts-core/prompts/ultrawork/codex.md +2 -2
  120. package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
  121. package/packages/shared-skills/skills/ulw-plan/SKILL.md +0 -1
  122. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  123. package/packages/omo-codex/plugin/components/codegraph/LICENSE +0 -21
  124. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  125. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  126. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12326
  127. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9908
  128. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  129. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  130. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  131. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  132. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  133. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  134. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  135. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -310
  136. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -92
  137. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  138. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  139. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  140. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  141. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  142. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -22
  143. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  144. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  145. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  146. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  147. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  148. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  149. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  150. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  151. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  152. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  153. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  154. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  155. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  156. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  157. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  158. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  159. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  160. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  161. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  162. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  163. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  164. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  165. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  166. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +0 -47
  167. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  168. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  169. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  170. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  171. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  172. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  173. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  174. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  175. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  176. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  177. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  178. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  179. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +0 -101
  180. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  181. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  182. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  183. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  184. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  185. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  186. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  187. package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
@@ -114,6 +114,32 @@ describe("checkpointUlwLoop final story surface resolution", () => {
114
114
  });
115
115
 
116
116
  describe("checkpointUlwLoop final story", () => {
117
+ it("#given a mismatched codex objective and multiple gate defects #when completing the final checkpoint #then reports both validation domains", async () => {
118
+ const repo = await repoWith(
119
+ plan([passGoal("G001", { status: "complete" }), passGoal("G002")], { activeGoalId: "G002" }),
120
+ );
121
+ const gate = JSON.parse(await qualityGateJson(repo)) as Record<string, Record<string, unknown>>;
122
+ const manualQa = requiredSection(gate, "manualQa");
123
+ manualQa["surfaceEvidence"] = [];
124
+ requiredSection(gate, "gateReview")["recommendation"] = "REJECT";
125
+
126
+ try {
127
+ await checkpointUlwLoop(repo, {
128
+ goalId: "G002",
129
+ status: "complete",
130
+ evidence: "final work complete and validation passed",
131
+ codexGoalJson: snapshot("complete", "wrong objective"),
132
+ qualityGateJson: JSON.stringify(gate),
133
+ });
134
+ throw new Error("expected validation failure");
135
+ } catch (error) {
136
+ expect(error).toBeInstanceOf(Error);
137
+ if (!(error instanceof Error)) throw error;
138
+ expect(error.message).toContain("Codex goal objective mismatch");
139
+ expect(error.message).toContain("manualQa.surfaceEvidence");
140
+ expect(error.message).toContain("gateReview.recommendation");
141
+ }
142
+ });
117
143
  it("requires quality-gate-json for the final goal complete", async () => {
118
144
  const repo = await repoWith(
119
145
  plan([passGoal("G001", { status: "complete" }), passGoal("G002")], { activeGoalId: "G002" }),
@@ -1,10 +1,12 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
3
  import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
+ import { join, resolve } from "node:path";
4
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
6
 
6
7
  import { ulwLoopCommand } from "../src/cli-commands.js";
7
8
  import { writePlan } from "../src/plan-io.js";
9
+ import { validateQualityGate } from "../src/quality-gate.js";
8
10
  import { goal, plan } from "./fixtures/checkpoint-builders.js";
9
11
 
10
12
  let repo: string;
@@ -32,7 +34,50 @@ async function run(args: string[]): Promise<Record<string, unknown>> {
32
34
  return JSON.parse(output.join(""));
33
35
  }
34
36
 
37
+ function fillTemplate(value: unknown): unknown {
38
+ if (typeof value === "string") {
39
+ return value.replace(/<replace:[^>]+>/g, "plausible evidence");
40
+ }
41
+ if (Array.isArray(value)) return value.map(fillTemplate);
42
+ if (value !== null && typeof value === "object") {
43
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, fillTemplate(item)]));
44
+ }
45
+ return value;
46
+ }
47
+
35
48
  describe("checkpoint --print-template", () => {
49
+ it("#given a printed template #when every placeholder is filled #then the quality gate validates", async () => {
50
+ await writePlan(
51
+ repo,
52
+ plan([goal({ id: "G001", attempt: 2 })], {
53
+ evidenceLayoutVersion: 2,
54
+ activeGoalId: "G001",
55
+ codexObjective: "Exact objective from goals.json",
56
+ }),
57
+ );
58
+ process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
59
+ const result = await run(["--print-template", "--goal-id", "G001", "--json"]);
60
+ const template = fillTemplate(result["qualityGateTemplate"]) as Record<string, unknown>;
61
+ const paths = [
62
+ ...((template["manualQa"] as Record<string, unknown>)["artifactRefs"] as Array<Record<string, unknown>>),
63
+ ];
64
+ await Promise.all(
65
+ [
66
+ ...paths.map((artifact) => String(artifact["path"])),
67
+ String((template["gateReview"] as Record<string, unknown>)["reportPath"]),
68
+ ].map(async (path) => {
69
+ const absolute = resolve(repo, path);
70
+ await mkdir(resolve(absolute, ".."), { recursive: true });
71
+ await writeFile(absolute, "evidence");
72
+ }),
73
+ );
74
+ const validated = validateQualityGate(template, {
75
+ repoRoot: repo,
76
+ fs: { existsSync, statSync },
77
+ reviewerSurface: "omo-senpi",
78
+ });
79
+ expect(validated.gateReview.recommendation).toBe("APPROVE");
80
+ });
36
81
  it("#given an active senpi v2 plan #when printed with only goal-id #then emits the senpi gate skeleton", async () => {
37
82
  await writePlan(
38
83
  repo,
@@ -0,0 +1,258 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { describe, expect, it } from "vitest";
3
+ import { validateQualityGate } from "../src/quality-gate.js";
4
+ import { UlwLoopError } from "../src/types.js";
5
+
6
+ const opts = { repoRoot: process.cwd(), fs: { existsSync, statSync }, reviewerSurface: "omo-senpi" as const };
7
+ describe("aggregated quality gate basics", () => {
8
+ it("#given rejected recommendation, empty surface evidence, and invalid artifact kind #when validating #then reports all three defects", () => {
9
+ const gate = {
10
+ manualQa: {
11
+ by: "main-session",
12
+ status: "passed",
13
+ evidence: "qa evidence",
14
+ surfaceEvidence: [],
15
+ adversarialCases: [
16
+ {
17
+ id: "adv",
18
+ criterionRef: "C001",
19
+ scenario: "scenario",
20
+ expectedBehavior: "reject",
21
+ verdict: "passed",
22
+ artifactRefs: [],
23
+ },
24
+ ],
25
+ artifactRefs: [
26
+ {
27
+ id: "artifact",
28
+ kind: "invalid-kind",
29
+ description: "artifact",
30
+ path: "test/fixtures/artifacts/cli-pass.txt",
31
+ },
32
+ ],
33
+ },
34
+ gateReview: {
35
+ by: "category:deep",
36
+ recommendation: "REJECT",
37
+ evidence: "gate evidence",
38
+ reportPath: "test/fixtures/artifacts/gate-review.md",
39
+ blockers: [],
40
+ },
41
+ iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
42
+ criteriaCoverage: {
43
+ totalCriteria: 1,
44
+ passCount: 1,
45
+ originalIntent: "intent",
46
+ desiredOutcome: "outcome",
47
+ userOutcomeReview: "review",
48
+ adversarialClassesCovered: ["none"],
49
+ },
50
+ };
51
+
52
+ try {
53
+ validateQualityGate(gate, { reviewerSurface: "omo-senpi" });
54
+ throw new Error("expected validation failure");
55
+ } catch (error) {
56
+ expect(error).toBeInstanceOf(UlwLoopError);
57
+ if (!(error instanceof UlwLoopError)) throw error;
58
+ const fields = error.details?.["fields"];
59
+ expect(Array.isArray(fields) ? fields.map((item) => item["field"]) : []).toEqual(
60
+ expect.arrayContaining([
61
+ "gateReview.recommendation",
62
+ "manualQa.surfaceEvidence",
63
+ "manualQa.artifactRefs[0].kind",
64
+ ]),
65
+ );
66
+ }
67
+ });
68
+
69
+ it("#given an empty existing artifact and another defect #when validating #then reports both defects", () => {
70
+ const gate = {
71
+ manualQa: {
72
+ by: "main-session",
73
+ status: "passed",
74
+ evidence: "qa evidence",
75
+ surfaceEvidence: [
76
+ {
77
+ id: "surface",
78
+ criterionRef: "C001",
79
+ surface: "cli",
80
+ invocation: "run",
81
+ verdict: "passed",
82
+ artifactRefs: ["empty"],
83
+ },
84
+ ],
85
+ adversarialCases: [
86
+ {
87
+ id: "adv",
88
+ criterionRef: "C001",
89
+ scenario: "scenario",
90
+ expectedBehavior: "reject",
91
+ verdict: "passed",
92
+ artifactRefs: [],
93
+ },
94
+ ],
95
+ artifactRefs: [{ id: "empty", kind: "cli-transcript", description: "empty", path: "empty.txt" }],
96
+ },
97
+ gateReview: {
98
+ by: "category:deep",
99
+ recommendation: "REJECT",
100
+ evidence: "gate evidence",
101
+ reportPath: "test/fixtures/artifacts/gate-review.md",
102
+ blockers: [],
103
+ },
104
+ iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
105
+ criteriaCoverage: {
106
+ totalCriteria: 1,
107
+ passCount: 1,
108
+ originalIntent: "intent",
109
+ desiredOutcome: "outcome",
110
+ userOutcomeReview: "review",
111
+ adversarialClassesCovered: ["none"],
112
+ },
113
+ };
114
+
115
+ try {
116
+ validateQualityGate(gate, {
117
+ repoRoot: process.cwd(),
118
+ reviewerSurface: "omo-senpi",
119
+ fs: {
120
+ existsSync: (path) => path.endsWith("empty.txt") || path.endsWith("gate-review.md"),
121
+ statSync: () => ({ size: 0 }),
122
+ },
123
+ });
124
+ throw new Error("expected validation failure");
125
+ } catch (error) {
126
+ expect(error).toBeInstanceOf(UlwLoopError);
127
+ if (!(error instanceof UlwLoopError)) throw error;
128
+ const fields = error.details?.["fields"];
129
+ expect(Array.isArray(fields) ? fields.map((item) => item["field"]) : []).toEqual(
130
+ expect.arrayContaining(["manualQa.artifactRefs[0].path", "gateReview.recommendation"]),
131
+ );
132
+ }
133
+ });
134
+ it("#given four independent defects #when validating #then reports all defects in one error", () => {
135
+ const gate = {
136
+ manualQa: {
137
+ by: "main-session",
138
+ status: "passed",
139
+ evidence: "placeholder",
140
+ surfaceEvidence: [],
141
+ adversarialCases: [],
142
+ artifactRefs: [
143
+ { id: "missing", kind: "cli-transcript", description: "artifact", path: "does-not-exist.txt" },
144
+ ],
145
+ },
146
+ gateReview: {
147
+ by: "category:deep",
148
+ recommendation: "REJECT",
149
+ reportPath: "test/fixtures/artifacts/gate-review.md",
150
+ blockers: [],
151
+ },
152
+ iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
153
+ criteriaCoverage: {
154
+ totalCriteria: 1,
155
+ passCount: 1,
156
+ originalIntent: "intent",
157
+ desiredOutcome: "outcome",
158
+ userOutcomeReview: "review",
159
+ adversarialClassesCovered: ["none"],
160
+ },
161
+ };
162
+ try {
163
+ validateQualityGate(gate, opts);
164
+ throw new Error("expected validation failure");
165
+ } catch (error) {
166
+ expect(error).toBeInstanceOf(UlwLoopError);
167
+ if (!(error instanceof UlwLoopError)) throw error;
168
+ expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
169
+ expect(error.details?.["field"]).toBe("manualQa.evidence");
170
+ expect(error.details?.["fields"]).toEqual(
171
+ expect.arrayContaining([
172
+ { field: "manualQa.evidence", message: expect.stringContaining("placeholder") },
173
+ { field: "gateReview.evidence", message: expect.stringContaining("non-empty") },
174
+ { field: "gateReview.recommendation", message: expect.stringContaining("APPROVE") },
175
+ { field: "manualQa.artifactRefs[0].path", message: expect.stringContaining("existing") },
176
+ ]),
177
+ );
178
+ }
179
+ });
180
+
181
+ it("#given missing required fields #when validating #then reports each field in one error", () => {
182
+ const gate = {
183
+ manualQa: { evidence: "qa", artifactRefs: [] },
184
+ gateReview: { by: "invalid", recommendation: "REJECT", evidence: "", reportPath: "", blockers: ["blocker"] },
185
+ iteration: { fullRerun: false, status: "failed", rerunCommands: [], evidence: "" },
186
+ criteriaCoverage: {
187
+ totalCriteria: "bad",
188
+ passCount: 0,
189
+ originalIntent: "",
190
+ desiredOutcome: "",
191
+ userOutcomeReview: "",
192
+ adversarialClassesCovered: [],
193
+ },
194
+ };
195
+ expect(() => validateQualityGate(gate, { reviewerSurface: "omo-senpi" })).toThrow(/gateReview\.by/);
196
+ try {
197
+ validateQualityGate(gate, { reviewerSurface: "omo-senpi" });
198
+ } catch (error) {
199
+ expect(error).toBeInstanceOf(UlwLoopError);
200
+ if (!(error instanceof UlwLoopError)) throw error;
201
+ const fields = error.details?.["fields"];
202
+ expect(Array.isArray(fields) ? fields.map((field) => field["field"]) : []).toEqual(
203
+ expect.arrayContaining([
204
+ "gateReview.by",
205
+ "iteration.status",
206
+ "criteriaCoverage.totalCriteria",
207
+ "manualQa.artifactRefs",
208
+ ]),
209
+ );
210
+ }
211
+ });
212
+
213
+ it("#given more than twenty-five defects #when validating #then caps the list and flags truncation", () => {
214
+ const refs = Array.from({ length: 30 }, (_, index) => ({
215
+ id: `ref-${index}`,
216
+ kind: "cli-transcript",
217
+ description: "artifact",
218
+ path: `does-not-exist-${index}.txt`,
219
+ }));
220
+ const gate = {
221
+ manualQa: {
222
+ by: "main-session",
223
+ status: "passed",
224
+ evidence: "real evidence",
225
+ surfaceEvidence: [],
226
+ adversarialCases: [],
227
+ artifactRefs: refs,
228
+ },
229
+ gateReview: {
230
+ by: "category:deep",
231
+ recommendation: "APPROVE",
232
+ reportPath: "test/fixtures/artifacts/gate-review.md",
233
+ evidence: "gate evidence",
234
+ blockers: [],
235
+ },
236
+ iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
237
+ criteriaCoverage: {
238
+ totalCriteria: 1,
239
+ passCount: 1,
240
+ originalIntent: "intent",
241
+ desiredOutcome: "outcome",
242
+ userOutcomeReview: "review",
243
+ adversarialClassesCovered: ["none"],
244
+ },
245
+ };
246
+ try {
247
+ validateQualityGate(gate, opts);
248
+ throw new Error("expected validation failure");
249
+ } catch (error) {
250
+ expect(error).toBeInstanceOf(UlwLoopError);
251
+ if (!(error instanceof UlwLoopError)) throw error;
252
+ const fields = error.details?.["fields"];
253
+ expect(Array.isArray(fields) && fields.length).toBe(25);
254
+ expect(error.details?.["truncated"]).toBe(true);
255
+ expect(error.message).toContain("25+");
256
+ }
257
+ });
258
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { throwQualityGateDefects } from "../src/quality-gate-fields.js";
3
+ import { UlwLoopError } from "../src/types.js";
4
+
5
+ describe("quality gate cap and dedupe", () => {
6
+ it("#given duplicate defects beyond the cap #when formatting #then deduplicates before capping", () => {
7
+ const defects = Array.from({ length: 30 }, (_, index) => ({
8
+ field: index < 10 ? "duplicate" : `field-${index - 10}`,
9
+ message: index < 10 ? "same defect" : `defect-${index - 10}`,
10
+ }));
11
+ try {
12
+ throwQualityGateDefects(defects);
13
+ throw new Error("expected validation failure");
14
+ } catch (error) {
15
+ expect(error).toBeInstanceOf(UlwLoopError);
16
+ if (!(error instanceof UlwLoopError)) throw error;
17
+ const fields = error.details?.["fields"];
18
+ expect(Array.isArray(fields) ? fields.length : 0).toBe(21);
19
+ expect(error.details?.["truncated"]).toBeUndefined();
20
+ }
21
+ });
22
+ });
@@ -0,0 +1,97 @@
1
+ import { existsSync } from "node:fs";
2
+ import { describe, expect, it } from "vitest";
3
+ import { validateQualityGate } from "../src/quality-gate.js";
4
+ import { UlwLoopError } from "../src/types.js";
5
+
6
+ const shared = {
7
+ gateReview: {
8
+ by: "category:deep",
9
+ recommendation: "APPROVE",
10
+ evidence: "review",
11
+ reportPath: "report.txt",
12
+ blockers: [],
13
+ },
14
+ iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
15
+ criteriaCoverage: {
16
+ totalCriteria: 1,
17
+ passCount: 1,
18
+ originalIntent: "intent",
19
+ desiredOutcome: "outcome",
20
+ userOutcomeReview: "review",
21
+ adversarialClassesCovered: ["none"],
22
+ },
23
+ };
24
+ function fields(gate: Record<string, unknown>, withFs = false): readonly string[] {
25
+ try {
26
+ validateQualityGate(gate, {
27
+ reviewerSurface: "omo-senpi",
28
+ ...(withFs ? { repoRoot: process.cwd(), fs: { existsSync, statSync: () => ({ size: 1 }) } } : {}),
29
+ });
30
+ } catch (error) {
31
+ if (error instanceof UlwLoopError) {
32
+ const list = error.details?.["fields"];
33
+ return Array.isArray(list) ? list.map((item) => `${String(item["field"])}: ${String(item["message"])}`) : [];
34
+ }
35
+ throw error;
36
+ }
37
+ throw new Error("expected validation failure");
38
+ }
39
+ function manual(overrides: Record<string, unknown> = {}): Record<string, unknown> {
40
+ return {
41
+ by: "main-session",
42
+ status: "passed",
43
+ evidence: "qa",
44
+ surfaceEvidence: [],
45
+ adversarialCases: [],
46
+ artifactRefs: [{ id: "a", kind: "log", description: "x", path: "x.txt" }],
47
+ ...overrides,
48
+ };
49
+ }
50
+ describe("quality gate poisoning contract", () => {
51
+ it.each([
52
+ [
53
+ "invalid artifact kind",
54
+ [{ id: "a", kind: "invalid-kind", description: "x", path: "x.txt" }],
55
+ (list: readonly string[]) => {
56
+ expect(list.some((x) => x.startsWith("manualQa.artifactRefs[0].kind:"))).toBe(true);
57
+ expect(list.some((x) => x.includes("references unknown artifact a"))).toBe(false);
58
+ },
59
+ ],
60
+ [
61
+ "duplicate id with invalid kind",
62
+ [
63
+ { id: "a", kind: "cli-transcript", description: "x", path: "x.txt" },
64
+ { id: "a", kind: "invalid-kind", description: "x", path: "x.txt" },
65
+ ],
66
+ (list: readonly string[]) => {
67
+ expect(list.some((x) => x.startsWith("manualQa.artifactRefs[1].kind:"))).toBe(true);
68
+ expect(list.some((x) => x.includes("duplicate a"))).toBe(true);
69
+ },
70
+ ],
71
+ ] as const)("#given %s #when validating #then preserves independent defects", (_name, artifactRefs, assertResult) =>
72
+ assertResult(fields({ ...shared, manualQa: manual({ artifactRefs }) })),
73
+ );
74
+ it("#given a poisoned numeric input #when validating #then skips its cross-field comparison", () => {
75
+ const list = fields({
76
+ ...shared,
77
+ criteriaCoverage: { ...shared.criteriaCoverage, passCount: "bad" },
78
+ manualQa: manual(),
79
+ });
80
+ expect(list.some((x) => x.includes("passCount") && x.includes("numeric"))).toBe(true);
81
+ expect(list.some((x) => x.includes("cover totalCriteria"))).toBe(false);
82
+ });
83
+ it("#given poisoned parent rows #when validating #then suppresses all child defects", () => {
84
+ const list = fields({ ...shared, manualQa: manual({ surfaceEvidence: [null], adversarialCases: [null] }) });
85
+ expect(list.some((x) => x.startsWith("manualQa.surfaceEvidence[0]:"))).toBe(true);
86
+ expect(list.some((x) => x.startsWith("manualQa.surfaceEvidence[0]."))).toBe(false);
87
+ expect(list.some((x) => x.startsWith("manualQa.adversarialCases[0]:"))).toBe(true);
88
+ });
89
+ it("#given a poisoned report path #when validating with fs options #then skips file checks", () => {
90
+ const list = fields(
91
+ { ...shared, gateReview: { ...shared.gateReview, reportPath: "tbd" }, manualQa: manual() },
92
+ true,
93
+ );
94
+ expect(list.some((x) => x.includes("reportPath") && x.includes("placeholder"))).toBe(true);
95
+ expect(list.some((x) => x.includes("gateReview.reportPath") && x.includes("existing artifact"))).toBe(false);
96
+ });
97
+ });