oh-my-opencode 5.0.0-beta.33 → 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 (221) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
  3. package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
  4. package/README.ja.md +1 -1
  5. package/README.ko.md +1 -1
  6. package/README.md +1 -1
  7. package/README.ru.md +1 -1
  8. package/README.zh-cn.md +1 -1
  9. package/THIRD-PARTY-NOTICES.md +27 -99
  10. package/dist/cli/index.js +48350 -48559
  11. package/dist/cli-node/index.js +48480 -48689
  12. package/dist/config/index.d.ts +1 -1
  13. package/dist/config/schema/hooks.d.ts +0 -1
  14. package/dist/config/schema/oh-my-opencode-config.d.ts +0 -10
  15. package/dist/config/schema.d.ts +0 -1
  16. package/dist/create-hooks.d.ts +0 -1
  17. package/dist/hooks/index.d.ts +0 -1
  18. package/dist/index.js +11675 -13742
  19. package/dist/mcp/index.d.ts +0 -4
  20. package/dist/mcp/types.d.ts +0 -1
  21. package/dist/oh-my-opencode.schema.json +1 -45
  22. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  23. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  24. package/dist/shared/omo-process-sweep.d.ts +2 -4
  25. package/dist/skills/init-deep/SKILL.md +13 -13
  26. package/dist/skills/ulw-plan/SKILL.md +0 -1
  27. package/dist/tui.js +44809 -44910
  28. package/package.json +16 -18
  29. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +0 -17
  30. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -3
  31. package/packages/omo-codex/plugin/.mcp.json +0 -6
  32. package/packages/omo-codex/plugin/AGENTS.md +5 -5
  33. package/packages/omo-codex/plugin/README.md +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -15
  35. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  40. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  42. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +1 -1
  44. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  45. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  46. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  48. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  50. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +1 -1
  52. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +2 -2
  55. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  56. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +0 -1
  58. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  59. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  60. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/ulw-loop/README.md +11 -1
  62. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +2 -2
  63. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
  64. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
  65. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +1 -0
  66. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -0
  67. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +10 -3
  68. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +35 -38
  69. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +278 -193
  70. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +5 -0
  71. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +7 -51
  72. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +39 -22
  73. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  74. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +64 -8
  75. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +37 -18
  77. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  78. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  79. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -1
  80. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
  81. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +6 -0
  82. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +10 -3
  83. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +32 -63
  84. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +7 -53
  85. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +50 -31
  86. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +67 -14
  87. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  88. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +36 -17
  89. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +26 -0
  90. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +47 -2
  91. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  92. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  93. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  94. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  95. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  116. package/packages/omo-codex/plugin/package-lock.json +12 -146
  117. package/packages/omo-codex/plugin/package.json +1 -2
  118. package/packages/omo-codex/plugin/scripts/AGENTS.md +0 -1
  119. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +0 -1
  120. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  121. package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
  122. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  123. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -8
  124. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  125. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  126. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  127. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
  128. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
  129. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +2 -2
  130. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -1
  131. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +0 -1
  132. package/packages/omo-codex/plugin/test/AGENTS.md +2 -2
  133. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +2 -21
  134. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -2
  135. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  136. package/packages/omo-codex/plugin/test/auto-update.test.mjs +0 -25
  137. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  138. package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
  139. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +0 -50
  140. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +0 -1
  141. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  142. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
  143. package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
  144. package/packages/omo-codex/scripts/install-dist/install-local.mjs +238 -431
  145. package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
  146. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
  147. package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
  148. package/packages/shared-skills/skills/ulw-plan/SKILL.md +0 -1
  149. package/dist/config/schema/codegraph.d.ts +0 -12
  150. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
  151. package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
  152. package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
  153. package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
  154. package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
  155. package/dist/mcp/codegraph.d.ts +0 -16
  156. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  157. package/packages/omo-codex/plugin/components/codegraph/LICENSE +0 -21
  158. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  159. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  160. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12326
  161. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9908
  162. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  163. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  164. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  165. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  166. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  167. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  168. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  169. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -310
  170. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -92
  171. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  172. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  173. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  174. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  175. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  176. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -22
  177. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  178. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  179. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  180. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  181. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  182. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  183. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  184. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  185. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  186. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  187. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  188. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  189. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  190. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  191. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  192. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  193. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  194. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  195. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  196. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  197. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  198. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  199. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  200. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +0 -47
  201. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  202. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  203. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  204. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  205. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  206. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  207. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  208. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  209. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  210. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  211. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  212. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  213. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +0 -101
  214. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  215. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  216. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  217. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  218. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  219. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  220. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  221. 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
+ });