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