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.
- package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
- package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/THIRD-PARTY-NOTICES.md +27 -99
- package/dist/cli/index.js +48350 -48559
- package/dist/cli-node/index.js +48480 -48689
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/config/schema/oh-my-opencode-config.d.ts +0 -10
- package/dist/config/schema.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.js +11675 -13742
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/types.d.ts +0 -1
- package/dist/oh-my-opencode.schema.json +1 -45
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/shared/omo-process-sweep.d.ts +2 -4
- package/dist/skills/init-deep/SKILL.md +13 -13
- package/dist/skills/ulw-plan/SKILL.md +0 -1
- package/dist/tui.js +44809 -44910
- package/package.json +16 -18
- package/packages/omo-codex/THIRD-PARTY-NOTICES.md +0 -17
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -3
- package/packages/omo-codex/plugin/.mcp.json +0 -6
- package/packages/omo-codex/plugin/AGENTS.md +5 -5
- package/packages/omo-codex/plugin/README.md +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -15
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +0 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +11 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +35 -38
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +278 -193
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +7 -51
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +39 -22
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +64 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +37 -18
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +32 -63
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +7 -53
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +50 -31
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +67 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +36 -17
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +26 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +47 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -146
- package/packages/omo-codex/plugin/package.json +1 -2
- package/packages/omo-codex/plugin/scripts/AGENTS.md +0 -1
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
- package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -8
- package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
- package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +0 -1
- package/packages/omo-codex/plugin/test/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +2 -21
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -2
- package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +0 -25
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
- package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +0 -50
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +0 -1
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
- package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +238 -431
- package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
- package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +0 -1
- package/dist/config/schema/codegraph.d.ts +0 -12
- package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
- package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
- package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
- package/dist/mcp/codegraph.d.ts +0 -16
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
- package/packages/omo-codex/plugin/components/codegraph/LICENSE +0 -21
- package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
- package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12326
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9908
- package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
- package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -310
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -92
- package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -22
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
- package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
- package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
- package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
- package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
- package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +0 -47
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +0 -101
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
- package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
- package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { ValidateQualityGateOptions } from "./quality-gate.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compatibility entry point for callers that used the former pre-pass API.
|
|
4
|
+
* Validation is performed by the real parser, which collects defects across
|
|
5
|
+
* every nested field before throwing one aggregate error.
|
|
6
|
+
*/
|
|
2
7
|
export declare function aggregateQualityGateDefects(input: unknown, opts: ValidateQualityGateOptions | undefined): void;
|
|
@@ -1,53 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
function text(value, field, defects) {
|
|
9
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
10
|
-
add(defects, field, `Final quality gate requires non-empty ${field}.`);
|
|
11
|
-
else if (PLACEHOLDER.test(value.trim()))
|
|
12
|
-
add(defects, field, `Final quality gate rejects placeholder ${field}.`);
|
|
13
|
-
}
|
|
14
|
-
function section(value, field, defects) {
|
|
15
|
-
if (!isRecord(value)) {
|
|
16
|
-
add(defects, field, `Final quality gate is missing ${field} evidence.`);
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
1
|
+
import { validateQualityGate } from "./quality-gate.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compatibility entry point for callers that used the former pre-pass API.
|
|
4
|
+
* Validation is performed by the real parser, which collects defects across
|
|
5
|
+
* every nested field before throwing one aggregate error.
|
|
6
|
+
*/
|
|
21
7
|
export function aggregateQualityGateDefects(input, opts) {
|
|
22
|
-
|
|
23
|
-
return;
|
|
24
|
-
const defects = [];
|
|
25
|
-
const gate = input;
|
|
26
|
-
const manual = section(gate["manualQa"], "manualQa", defects);
|
|
27
|
-
const review = section(gate["gateReview"], "gateReview", defects);
|
|
28
|
-
text(manual["evidence"], "manualQa.evidence", defects);
|
|
29
|
-
text(review["evidence"], "gateReview.evidence", defects);
|
|
30
|
-
if (review["recommendation"] !== "APPROVE")
|
|
31
|
-
add(defects, "gateReview.recommendation", "gateReview.recommendation must be APPROVE.");
|
|
32
|
-
const artifacts = Array.isArray(manual["artifactRefs"]) ? manual["artifactRefs"] : [];
|
|
33
|
-
for (const [index, item] of artifacts.entries()) {
|
|
34
|
-
if (!isRecord(item))
|
|
35
|
-
continue;
|
|
36
|
-
const path = item["path"];
|
|
37
|
-
if (typeof path !== "string" || path.trim() === "")
|
|
38
|
-
continue;
|
|
39
|
-
if (opts?.repoRoot !== undefined && opts.fs !== undefined && !opts.fs.existsSync(resolve(opts.repoRoot, path)))
|
|
40
|
-
add(defects, `manualQa.artifactRefs[${index}].path`, `manualQa.artifactRefs[${index}].path must point to an existing artifact.`);
|
|
41
|
-
}
|
|
42
|
-
if (defects.length === 0)
|
|
43
|
-
return;
|
|
44
|
-
const truncated = defects.length > 25;
|
|
45
|
-
const fields = defects.slice(0, 25).map(({ field, message }) => ({ field, message }));
|
|
46
|
-
const message = [
|
|
47
|
-
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
48
|
-
...fields.map((item) => `- ${item.field}: ${item.message}`),
|
|
49
|
-
].join("\n");
|
|
50
|
-
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
51
|
-
details: { field: fields[0]?.field, fields, ...(truncated ? { truncated: true } : {}) },
|
|
52
|
-
});
|
|
8
|
+
validateQualityGate(input, opts);
|
|
53
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { isWithinAttemptDir } from "./paths.js";
|
|
3
|
-
import { invalid, section, stringArray, textField } from "./quality-gate-fields.js";
|
|
3
|
+
import { invalid, isPoisoned, markPoisonedArtifactKind, section, stringArray, textField, } from "./quality-gate-fields.js";
|
|
4
4
|
export function surfaceField(value, field) {
|
|
5
5
|
if (value === "cli" ||
|
|
6
6
|
value === "http" ||
|
|
@@ -10,6 +10,7 @@ export function surfaceField(value, field) {
|
|
|
10
10
|
value === "data")
|
|
11
11
|
return value;
|
|
12
12
|
invalid(`${field} must be a supported manual QA surface.`, field);
|
|
13
|
+
return "cli";
|
|
13
14
|
}
|
|
14
15
|
export function kindField(value, field) {
|
|
15
16
|
if (value === "cli-transcript" ||
|
|
@@ -20,6 +21,7 @@ export function kindField(value, field) {
|
|
|
20
21
|
value === "data-diff")
|
|
21
22
|
return value;
|
|
22
23
|
invalid(`${field} must be a supported artifact kind.`, field);
|
|
24
|
+
return "log";
|
|
23
25
|
}
|
|
24
26
|
export function artifactCompatible(surface, kind) {
|
|
25
27
|
switch (surface) {
|
|
@@ -35,21 +37,22 @@ export function artifactCompatible(surface, kind) {
|
|
|
35
37
|
return kind === "data-diff";
|
|
36
38
|
default:
|
|
37
39
|
invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
|
|
40
|
+
return false;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
export function checkFile(path, field, opts) {
|
|
41
|
-
if (opts?.repoRoot === undefined || opts.fs === undefined)
|
|
44
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
42
45
|
return;
|
|
43
46
|
const absolute = resolve(opts.repoRoot, path);
|
|
44
|
-
if (!opts.fs.existsSync(absolute))
|
|
47
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
45
48
|
invalid(`${field} must point to an existing artifact.`, field);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
46
51
|
if (opts.fs.statSync(absolute).size <= 0)
|
|
47
52
|
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
48
|
-
if (opts.currentAttemptDir !== undefined &&
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
52
|
-
}
|
|
53
|
+
if (opts.currentAttemptDir !== undefined &&
|
|
54
|
+
!isWithinAttemptDir(absolute, resolve(opts.repoRoot, opts.currentAttemptDir)))
|
|
55
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
53
56
|
}
|
|
54
57
|
export function artifactMap(refs) {
|
|
55
58
|
const byId = new Map();
|
|
@@ -61,25 +64,39 @@ export function artifactMap(refs) {
|
|
|
61
64
|
return byId;
|
|
62
65
|
}
|
|
63
66
|
export function parseArtifactRefs(value, opts) {
|
|
64
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
67
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
65
68
|
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return value.flatMap((item, index) => {
|
|
72
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
73
|
+
const ref = section(item, prefix);
|
|
74
|
+
if (isPoisoned(prefix))
|
|
75
|
+
return [];
|
|
76
|
+
const pathField = `${prefix}.path`;
|
|
77
|
+
const idField = `${prefix}.id`;
|
|
78
|
+
const kindFieldName = `${prefix}.kind`;
|
|
79
|
+
const descriptionField = `${prefix}.description`;
|
|
80
|
+
const path = textField(ref["path"], pathField);
|
|
81
|
+
const id = textField(ref["id"], idField);
|
|
82
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
83
|
+
if (isPoisoned(kindFieldName))
|
|
84
|
+
markPoisonedArtifactKind(id);
|
|
85
|
+
const description = textField(ref["description"], descriptionField);
|
|
86
|
+
checkFile(path, pathField, opts);
|
|
87
|
+
return [{ id, kind, description, path }];
|
|
76
88
|
});
|
|
77
89
|
}
|
|
78
90
|
export function referencedArtifacts(value, field, byId) {
|
|
79
|
-
|
|
91
|
+
const ids = stringArray(value, field);
|
|
92
|
+
if (isPoisoned(field))
|
|
93
|
+
return [];
|
|
94
|
+
return ids.flatMap((id) => {
|
|
80
95
|
const artifact = byId.get(id);
|
|
81
|
-
if (artifact === undefined)
|
|
96
|
+
if (artifact === undefined) {
|
|
82
97
|
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
83
|
-
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return [artifact];
|
|
84
101
|
});
|
|
85
102
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
type QualityGateDefect = {
|
|
2
|
+
readonly field: string;
|
|
3
|
+
readonly message: string;
|
|
4
|
+
};
|
|
5
|
+
/** A recorded defect poisons only its field; consumers skip dependent checks while preserving valid siblings and entry identity. */
|
|
6
|
+
export declare function withQualityGateCollector<T>(operation: () => T): T;
|
|
7
|
+
export declare function throwQualityGateDefects(defects: readonly QualityGateDefect[]): never;
|
|
8
|
+
export declare function invalid(message: string, field: string): undefined;
|
|
9
|
+
export declare function isPoisoned(field: string): boolean;
|
|
10
|
+
export declare function poisonField(field: string): void;
|
|
11
|
+
export declare function markPoisonedArtifactKind(id: string): void;
|
|
12
|
+
export declare function isPoisonedArtifactKind(id: string): boolean;
|
|
2
13
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
14
|
export declare function section(value: unknown, field: string): Record<string, unknown>;
|
|
4
15
|
export declare function textField(value: unknown, field: string): string;
|
|
@@ -6,3 +17,4 @@ export declare function numberField(value: unknown, field: string): number;
|
|
|
6
17
|
export declare function stringArray(value: unknown, field: string): readonly string[];
|
|
7
18
|
export declare function emptyBlockers(value: unknown, field: string): readonly [];
|
|
8
19
|
export declare function literal<T extends string | boolean>(value: unknown, expected: T, field: string): T;
|
|
20
|
+
export {};
|
|
@@ -1,39 +1,95 @@
|
|
|
1
1
|
import { UlwLoopError } from "./types.js";
|
|
2
2
|
const PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
3
|
+
let activeCollector;
|
|
4
|
+
/** A recorded defect poisons only its field; consumers skip dependent checks while preserving valid siblings and entry identity. */
|
|
5
|
+
export function withQualityGateCollector(operation) {
|
|
6
|
+
const previous = activeCollector;
|
|
7
|
+
const collector = { defects: [], poisonedFields: new Set(), poisonedArtifactKinds: new Set() };
|
|
8
|
+
activeCollector = collector;
|
|
9
|
+
try {
|
|
10
|
+
const result = operation();
|
|
11
|
+
if (collector.defects.length === 0)
|
|
12
|
+
return result;
|
|
13
|
+
throwQualityGateDefects(collector.defects);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
activeCollector = previous;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function throwQualityGateDefects(defects) {
|
|
20
|
+
const uniqueDefects = [
|
|
21
|
+
...new Map(defects.map((defect) => [`${defect.field}\u0000${defect.message}`, defect])).values(),
|
|
22
|
+
];
|
|
23
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
24
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
25
|
+
const message = [
|
|
26
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
27
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`),
|
|
28
|
+
].join("\n");
|
|
29
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
30
|
+
details: { field: fields[0]?.field, fields, ...(truncated ? { truncated: true } : {}) },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
3
33
|
export function invalid(message, field) {
|
|
4
|
-
|
|
34
|
+
if (activeCollector === undefined)
|
|
35
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
36
|
+
activeCollector.defects.push({ field, message });
|
|
37
|
+
activeCollector.poisonedFields.add(field);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export function isPoisoned(field) {
|
|
41
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
42
|
+
}
|
|
43
|
+
export function poisonField(field) {
|
|
44
|
+
activeCollector?.poisonedFields.add(field);
|
|
45
|
+
}
|
|
46
|
+
export function markPoisonedArtifactKind(id) {
|
|
47
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
48
|
+
}
|
|
49
|
+
export function isPoisonedArtifactKind(id) {
|
|
50
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
5
51
|
}
|
|
6
52
|
export function isRecord(value) {
|
|
7
53
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
54
|
}
|
|
9
55
|
export function section(value, field) {
|
|
10
|
-
|
|
56
|
+
if (isRecord(value))
|
|
57
|
+
return value;
|
|
58
|
+
invalid(`Final quality gate is missing ${field} evidence.`, field);
|
|
59
|
+
return {};
|
|
11
60
|
}
|
|
12
61
|
export function textField(value, field) {
|
|
13
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
62
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
14
63
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
15
66
|
const trimmed = value.trim();
|
|
16
67
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
17
68
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
18
69
|
return trimmed;
|
|
19
70
|
}
|
|
20
71
|
export function numberField(value, field) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
73
|
+
return value;
|
|
74
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
75
|
+
return 0;
|
|
24
76
|
}
|
|
25
77
|
export function stringArray(value, field) {
|
|
26
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
27
|
-
|
|
78
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
79
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
28
82
|
return value.map((item) => textField(item, field));
|
|
29
83
|
}
|
|
30
84
|
export function emptyBlockers(value, field) {
|
|
31
85
|
if (Array.isArray(value) && value.length === 0)
|
|
32
86
|
return [];
|
|
33
87
|
invalid(`${field} must be empty.`, field);
|
|
88
|
+
return [];
|
|
34
89
|
}
|
|
35
90
|
export function literal(value, expected, field) {
|
|
36
91
|
if (value === expected)
|
|
37
92
|
return expected;
|
|
38
93
|
invalid(`${field} must be ${String(expected)}.`, field);
|
|
94
|
+
return expected;
|
|
39
95
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { invalid, literal, textField } from "./quality-gate-fields.js";
|
|
2
2
|
export function passedVerdict(value, field) {
|
|
3
|
-
if (value === "not_applicable")
|
|
3
|
+
if (value === "not_applicable") {
|
|
4
4
|
invalid(`${field} must not be not_applicable.`, field);
|
|
5
|
+
return "passed";
|
|
6
|
+
}
|
|
5
7
|
return literal(value, "passed", field);
|
|
6
8
|
}
|
|
7
9
|
export function codeQualityStatusField(value, field) {
|
|
8
10
|
if (value === "CLEAR" || value === "WATCH")
|
|
9
11
|
return value;
|
|
10
12
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
13
|
+
return "CLEAR";
|
|
11
14
|
}
|
|
12
15
|
export function adversarialVerdict(row, field) {
|
|
13
16
|
const value = row["verdict"];
|
|
@@ -17,4 +20,5 @@ export function adversarialVerdict(row, field) {
|
|
|
17
20
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
18
21
|
}
|
|
19
22
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
23
|
+
return { verdict: "passed" };
|
|
20
24
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { aggregateQualityGateDefects } from "./quality-gate-aggregate.js";
|
|
2
1
|
import { artifactCompatible, artifactMap, checkFile, parseArtifactRefs, referencedArtifacts, surfaceField, } from "./quality-gate-artifacts.js";
|
|
3
|
-
import { emptyBlockers, invalid, literal, numberField, section, stringArray, textField, } from "./quality-gate-fields.js";
|
|
2
|
+
import { emptyBlockers, invalid, isPoisoned, isPoisonedArtifactKind, literal, numberField, section, stringArray, textField, withQualityGateCollector, } from "./quality-gate-fields.js";
|
|
4
3
|
import { adversarialVerdict, codeQualityStatusField, passedVerdict } from "./quality-gate-verdicts.js";
|
|
5
|
-
import { GATE_SECTION_BY_ACCEPTOR
|
|
4
|
+
import { GATE_SECTION_BY_ACCEPTOR } from "./surface.js";
|
|
6
5
|
export { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, normalizeBlockerEvidence, sameBlockerOccurrences, } from "./quality-gate-blockers.js";
|
|
7
6
|
function reviewerRoleField(value, expected, field) {
|
|
7
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
8
|
+
textField(value, field);
|
|
9
|
+
return expected;
|
|
10
|
+
}
|
|
8
11
|
const actual = textField(value, field);
|
|
9
12
|
if (actual !== expected)
|
|
10
13
|
invalid(`${field} must be ${expected}.`, field);
|
|
@@ -12,44 +15,51 @@ function reviewerRoleField(value, expected, field) {
|
|
|
12
15
|
}
|
|
13
16
|
function reviewerAcceptorField(value, surface, sectionName) {
|
|
14
17
|
const field = `${sectionName}.by`;
|
|
15
|
-
const actual = textField(value, field);
|
|
16
18
|
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
19
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
20
|
+
textField(value, field);
|
|
21
|
+
return accepted?.[0] ?? "";
|
|
22
|
+
}
|
|
23
|
+
const actual = textField(value, field);
|
|
17
24
|
if (accepted === undefined || !accepted.includes(actual))
|
|
18
25
|
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
19
26
|
return actual;
|
|
20
27
|
}
|
|
21
28
|
export function validateQualityGate(input, opts) {
|
|
29
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
30
|
+
}
|
|
31
|
+
function validateQualityGateUncollected(input, opts) {
|
|
22
32
|
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
23
|
-
aggregateQualityGateDefects(input, opts);
|
|
24
33
|
const gate = section(input, "qualityGate");
|
|
25
|
-
const requiredSections = REQUIRED_GATE_SECTIONS_BY_SURFACE[surface];
|
|
26
|
-
for (const sectionName of requiredSections)
|
|
27
|
-
section(gate[sectionName], sectionName);
|
|
28
34
|
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
29
35
|
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
30
36
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
31
37
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
32
38
|
const iteration = section(gate["iteration"], "iteration");
|
|
33
39
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
40
|
+
const codeReview = surface === "lazycodex" ? section(gate["codeReview"], "codeReview") : {};
|
|
34
41
|
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
35
42
|
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
43
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
44
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
36
45
|
if (surface === "lazycodex")
|
|
37
|
-
reviewerRoleField(
|
|
46
|
+
reviewerRoleField(codeReview?.["by"], "lazycodex-code-reviewer", "codeReview.by");
|
|
38
47
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
39
48
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
40
|
-
if (passCount < totalCriteria)
|
|
49
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
41
50
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
42
51
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
43
52
|
const byId = artifactMap(artifactRefs);
|
|
44
53
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
45
54
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
46
55
|
const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
|
|
47
|
-
|
|
56
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
57
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
48
58
|
const common = {
|
|
49
59
|
manualQa: {
|
|
50
60
|
by: manualQaBy,
|
|
51
61
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
52
|
-
evidence:
|
|
62
|
+
evidence: manualQaEvidence,
|
|
53
63
|
surfaceEvidence,
|
|
54
64
|
adversarialCases,
|
|
55
65
|
artifactRefs,
|
|
@@ -58,7 +68,7 @@ export function validateQualityGate(input, opts) {
|
|
|
58
68
|
by: gateReviewBy,
|
|
59
69
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
60
70
|
reportPath: gateReportPath,
|
|
61
|
-
evidence:
|
|
71
|
+
evidence: gateReviewEvidence,
|
|
62
72
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers"),
|
|
63
73
|
},
|
|
64
74
|
iteration: {
|
|
@@ -78,7 +88,6 @@ export function validateQualityGate(input, opts) {
|
|
|
78
88
|
};
|
|
79
89
|
if (surface === "omo-senpi")
|
|
80
90
|
return { surface, ...common };
|
|
81
|
-
const codeReview = section(gate["codeReview"], "codeReview");
|
|
82
91
|
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
83
92
|
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
84
93
|
return {
|
|
@@ -95,13 +104,19 @@ export function validateQualityGate(input, opts) {
|
|
|
95
104
|
};
|
|
96
105
|
}
|
|
97
106
|
function parseSurfaceEvidence(value, byId) {
|
|
98
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
107
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
99
108
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
100
|
-
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
return value.flatMap((item, index) => {
|
|
101
112
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
113
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
114
|
+
return [];
|
|
102
115
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
103
116
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
104
117
|
for (const artifact of artifacts) {
|
|
118
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
119
|
+
continue;
|
|
105
120
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
106
121
|
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
107
122
|
}
|
|
@@ -117,10 +132,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
117
132
|
});
|
|
118
133
|
}
|
|
119
134
|
function parseAdversarialCases(value, byId) {
|
|
120
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
135
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
121
136
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
122
|
-
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
return value.flatMap((item, index) => {
|
|
123
140
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
141
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
142
|
+
return [];
|
|
124
143
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
125
144
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
126
145
|
return {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit --with-ultrawork",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
10
|
+
"statusMessage": "(OmO 5.0.0-beta.35) Checking Ulw-Loop Steering"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "command",
|
|
21
21
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
|
|
22
22
|
"timeout": 5,
|
|
23
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
23
|
+
"statusMessage": "(OmO 5.0.0-beta.35) Enforcing Unlimited Ulw-Loop Budget"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"type": "command",
|
|
32
32
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use-spawn",
|
|
33
33
|
"timeout": 5,
|
|
34
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
34
|
+
"statusMessage": "(OmO 5.0.0-beta.35) Guarding Ulw-Loop Spawns"
|
|
35
35
|
}
|
|
36
36
|
]
|
|
37
37
|
}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "command",
|
|
44
44
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook stop",
|
|
45
45
|
"timeout": 10,
|
|
46
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
46
|
+
"statusMessage": "(OmO 5.0.0-beta.35) Checking Ulw-Loop Resume"
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ulw-loop",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.35",
|
|
4
4
|
"description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md
CHANGED
|
@@ -190,6 +190,7 @@ omo-agent-toolkit ulw-loop record-evidence --goal-id <g> --criterion-id <c> --st
|
|
|
190
190
|
# Then use the harness update_goal tool with status complete.
|
|
191
191
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <g> --print-template --json
|
|
192
192
|
# Fill the printed template: replace every placeholder and use real artifact paths under currentAttemptDir.
|
|
193
|
+
# codex-goal-json.goal.objective must equal the plan's codexObjective verbatim.
|
|
193
194
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <g> --status complete --evidence "..." --codex-goal-json <path> --quality-gate-json <path>
|
|
194
195
|
omo-agent-toolkit ulw-loop complete-goals
|
|
195
196
|
```
|
|
@@ -214,7 +215,7 @@ omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete --evidenc
|
|
|
214
215
|
{
|
|
215
216
|
"codeReview":{"by":"lazycodex-code-reviewer","recommendation":"APPROVE","codeQualityStatus":"CLEAR","reportPath":"test/fixtures/artifacts/code-review.md","evidence":"Diff review passed.","blockers":[]},
|
|
216
217
|
"manualQa":{"by":"lazycodex-qa-executor","status":"passed","evidence":"CLI and data surfaces passed.","surfaceEvidence":[{"id":"surface-cli-pass","criterionRef":"C1","surface":"cli","invocation":"omo-agent-toolkit ulw-loop checkpoint --quality-gate-json sample-quality-gate.json --json","verdict":"passed","artifactRefs":["artifact-cli-pass"]},{"id":"surface-data-pass","criterionRef":"C2","surface":"data","invocation":"diff -u before-ledger.json after-ledger.json","verdict":"passed","artifactRefs":["artifact-data-diff"]}],"adversarialCases":[{"id":"adv-malformed-input","criterionRef":"C3","scenario":"malformed gate input omits manual QA evidence","expectedBehavior":"validator rejects ULW_LOOP_QUALITY_GATE_INVALID","verdict":"passed","artifactRefs":["artifact-cli-reject"]}],"artifactRefs":[{"id":"artifact-cli-pass","kind":"cli-transcript","description":"CLI pass artifact.","path":"test/fixtures/artifacts/cli-pass.txt"},{"id":"artifact-cli-reject","kind":"log","description":"Reject log artifact.","path":"test/fixtures/artifacts/rejection.txt"},{"id":"artifact-data-diff","kind":"data-diff","description":"Data diff artifact.","path":"test/fixtures/artifacts/data-diff.txt"}]},
|
|
217
|
-
"gateReview":{"by":"lazycodex-gate-reviewer","recommendation":"APPROVE","reportPath":"test/fixtures/artifacts/gate-review.md","evidence":"Gate review passed.","blockers":[]},
|
|
218
|
+
"gateReview":{"by":"lazycodex-gate-reviewer","recommendation":"APPROVE","reportPath":"test/fixtures/artifacts/gate-review.md","evidence":"Gate review passed.","blockers":[],"notes":[]},
|
|
218
219
|
"iteration":{"fullRerun":true,"status":"passed","rerunCommands":["bunx vitest run packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts"],"evidence":"Focused rerun passed."},
|
|
219
220
|
"criteriaCoverage":{"totalCriteria":3,"passCount":3,"originalIntent":"User wanted artifact-backed completion.","desiredOutcome":"Behavior ships with review and QA evidence.","userOutcomeReview":"Result matches brief and goals.","adversarialClassesCovered":["malformed_input","stale_state"]}
|
|
220
221
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canReconcileActiveFinalTaskScopedAggregateSnapshot,
|
|
3
|
+
canReconcileCompletedTaskScopedAggregateSnapshot,
|
|
4
|
+
codexSnapshotMismatchError,
|
|
5
|
+
} from "./checkpoint-reconciliation.js";
|
|
6
|
+
import { readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
|
|
7
|
+
import {
|
|
8
|
+
codexGoalMode,
|
|
9
|
+
compatibleCodexObjectives,
|
|
10
|
+
expectedCodexObjective,
|
|
11
|
+
isFinalRunCompletionCandidate,
|
|
12
|
+
} from "./goal-status.js";
|
|
13
|
+
import type { UlwLoopScope } from "./paths.js";
|
|
14
|
+
import type { UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
15
|
+
import { UlwLoopError } from "./types.js";
|
|
16
|
+
|
|
17
|
+
function normalizeObjective(value: string): string {
|
|
18
|
+
return value.replace(/\s+/g, " ").trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function validateCheckpointCodexGoal(input: {
|
|
22
|
+
readonly repoRoot: string;
|
|
23
|
+
readonly plan: UlwLoopPlan;
|
|
24
|
+
readonly goal: UlwLoopItem;
|
|
25
|
+
readonly raw: string | undefined;
|
|
26
|
+
readonly evidence: string;
|
|
27
|
+
readonly scope?: UlwLoopScope;
|
|
28
|
+
}): Promise<unknown> {
|
|
29
|
+
const aggregate = codexGoalMode(input.plan) === "aggregate";
|
|
30
|
+
const final = isFinalRunCompletionCandidate(input.plan, input.goal);
|
|
31
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
32
|
+
const expectedObjective = expectedCodexObjective(input.plan, input.goal);
|
|
33
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
34
|
+
expectedObjective,
|
|
35
|
+
...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}),
|
|
36
|
+
allowedStatuses: aggregate ? (final ? ["complete"] : ["active"]) : ["complete"],
|
|
37
|
+
requireSnapshot: true,
|
|
38
|
+
requireComplete: !aggregate || final,
|
|
39
|
+
});
|
|
40
|
+
if (reconciliation.ok) return reconciliation.snapshot.raw;
|
|
41
|
+
const objective = snapshot?.objective;
|
|
42
|
+
const mismatchedTaskObjective =
|
|
43
|
+
snapshot?.available === true &&
|
|
44
|
+
objective !== undefined &&
|
|
45
|
+
normalizeObjective(objective) !== normalizeObjective(expectedObjective);
|
|
46
|
+
const completedTaskScoped =
|
|
47
|
+
mismatchedTaskObjective &&
|
|
48
|
+
snapshot.status === "complete" &&
|
|
49
|
+
(await canReconcileCompletedTaskScopedAggregateSnapshot(
|
|
50
|
+
input.repoRoot,
|
|
51
|
+
input.plan,
|
|
52
|
+
input.goal,
|
|
53
|
+
objective,
|
|
54
|
+
input.evidence,
|
|
55
|
+
input.scope,
|
|
56
|
+
));
|
|
57
|
+
const activeFinalTaskScoped =
|
|
58
|
+
mismatchedTaskObjective &&
|
|
59
|
+
snapshot.status === "active" &&
|
|
60
|
+
(await canReconcileActiveFinalTaskScopedAggregateSnapshot(
|
|
61
|
+
input.repoRoot,
|
|
62
|
+
input.plan,
|
|
63
|
+
input.goal,
|
|
64
|
+
objective,
|
|
65
|
+
input.evidence,
|
|
66
|
+
input.scope,
|
|
67
|
+
));
|
|
68
|
+
if (completedTaskScoped || activeFinalTaskScoped) return reconciliation.snapshot.raw;
|
|
69
|
+
throw codexSnapshotMismatchError({
|
|
70
|
+
reconciliation,
|
|
71
|
+
snapshot,
|
|
72
|
+
expectedObjective,
|
|
73
|
+
taskScopedHint: { goal: input.goal, aggregate, final },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function combineCheckpointValidationErrors(codexError: UlwLoopError, gateError: UlwLoopError): UlwLoopError {
|
|
78
|
+
return new UlwLoopError(`${codexError.message}\n${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
79
|
+
details: { ...(codexError.details ?? {}), ...(gateError.details ?? {}) },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -16,6 +16,12 @@ export interface CodexSnapshotMismatchInput {
|
|
|
16
16
|
readonly taskScopedHint?: { readonly goal: UlwLoopItem; readonly aggregate: boolean; readonly final: boolean };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function combineCheckpointValidationErrors(codexError: UlwLoopError, gateError: UlwLoopError): UlwLoopError {
|
|
20
|
+
return new UlwLoopError(`${codexError.message}\n${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
21
|
+
details: { ...(codexError.details ?? {}), ...(gateError.details ?? {}) },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export function codexSnapshotMismatchError(input: CodexSnapshotMismatchInput): UlwLoopError {
|
|
20
26
|
const { reconciliation, snapshot, taskScopedHint } = input;
|
|
21
27
|
const hint =
|