oh-my-opencode 5.0.0-beta.30 → 5.0.0-beta.31
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/dist/cli/index.js +75 -26
- package/dist/cli-node/index.js +75 -26
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
- package/dist/features/background-agent/constants.d.ts +0 -7
- package/dist/features/background-agent/manager.d.ts +2 -1
- package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
- package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
- package/dist/features/task-toast-manager/manager.d.ts +0 -24
- package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
- package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
- package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
- package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
- package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
- package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
- package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
- package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
- package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
- package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
- package/dist/hooks/keyword-detector/hook.d.ts +7 -0
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
- package/dist/hooks/model-fallback/hook.d.ts +1 -1
- package/dist/index.js +439 -218
- package/dist/oh-my-opencode.schema.json +5 -0
- package/dist/plugin-dispose.d.ts +3 -0
- package/dist/shared/agent-tool-restrictions.d.ts +0 -1
- package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tui.js +56 -3
- package/package.json +15 -15
- package/packages/git-bash-mcp/dist/cli.js +1 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
- package/packages/lsp-core/src/lsp/constants.ts +5 -0
- package/packages/lsp-core/src/lsp/errors.ts +15 -0
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager.ts +106 -13
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
- package/packages/lsp-core/src/mcp.ts +3 -0
- package/packages/lsp-daemon/dist/cli.js +114 -18
- package/packages/lsp-daemon/dist/client.js +114 -18
- package/packages/lsp-daemon/dist/index.js +114 -18
- package/packages/lsp-daemon/dist/proxy.js +3 -0
- package/packages/lsp-tools-mcp/dist/cli.js +114 -18
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +93 -12
- package/packages/lsp-tools-mcp/dist/mcp.js +114 -18
- package/packages/lsp-tools-mcp/dist/tools.js +113 -18
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- 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/codegraph/dist/cli.js +31 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +31 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
- 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 +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -18
- 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/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +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/AGENTS.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +100 -54
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +9 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +7 -10
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +50 -0
- 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/src/checkpoint.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +16 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +11 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +73 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +44 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +45 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +20 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -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-codegraph-init-guidance.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-checking-codegraph-bootstrap.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 +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
- package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
- package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
- package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync4, statSync } from "node:fs";
|
|
5
5
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
6
6
|
import { resolve as resolve3 } from "node:path";
|
|
7
7
|
|
|
@@ -648,6 +648,53 @@ function adversarialVerdict(row, field) {
|
|
|
648
648
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
+
// packages/omo-codex/plugin/components/ulw-loop/src/surface.ts
|
|
652
|
+
import { existsSync as existsSync3, readFileSync } from "node:fs";
|
|
653
|
+
import { dirname, join as join2 } from "node:path";
|
|
654
|
+
import { fileURLToPath } from "node:url";
|
|
655
|
+
var REVIEWER_ROLES_BY_SURFACE = {
|
|
656
|
+
lazycodex: {
|
|
657
|
+
codeReview: "lazycodex-code-reviewer",
|
|
658
|
+
manualQa: "lazycodex-qa-executor",
|
|
659
|
+
gateReview: "lazycodex-gate-reviewer"
|
|
660
|
+
},
|
|
661
|
+
"omo-senpi": {
|
|
662
|
+
codeReview: "omo-senpi-code-reviewer",
|
|
663
|
+
manualQa: "omo-senpi-qa-executor",
|
|
664
|
+
gateReview: "omo-senpi-gate-reviewer"
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
668
|
+
function reviewerRolesFor(surface) {
|
|
669
|
+
return REVIEWER_ROLES_BY_SURFACE[surface];
|
|
670
|
+
}
|
|
671
|
+
var SURFACE_MARKER_FILENAME = "surface.json";
|
|
672
|
+
var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
|
|
673
|
+
function parseSurface(value) {
|
|
674
|
+
return value === "lazycodex" || value === "omo-senpi" ? value : null;
|
|
675
|
+
}
|
|
676
|
+
function resolveToolkitSurface(options) {
|
|
677
|
+
const env = options?.env ?? process.env;
|
|
678
|
+
const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
|
|
679
|
+
if (fromEnv !== null)
|
|
680
|
+
return fromEnv;
|
|
681
|
+
const entryDir = options?.entryDir ?? dirname(fileURLToPath(import.meta.url));
|
|
682
|
+
const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
|
|
683
|
+
return readMarkerSurface(markerPath) ?? "lazycodex";
|
|
684
|
+
}
|
|
685
|
+
function readMarkerSurface(markerPath) {
|
|
686
|
+
try {
|
|
687
|
+
if (!existsSync3(markerPath))
|
|
688
|
+
return null;
|
|
689
|
+
const parsed = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
690
|
+
return parseSurface(parsed["surface"]);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (error instanceof Error)
|
|
693
|
+
return null;
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
651
698
|
// packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-blockers.ts
|
|
652
699
|
var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
653
700
|
var URL_PATTERN = /https?:\/\/\S+/g;
|
|
@@ -691,11 +738,6 @@ function clearGoalBlockerFields(goal) {
|
|
|
691
738
|
}
|
|
692
739
|
|
|
693
740
|
// packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
|
|
694
|
-
var REVIEWER_ROLES = {
|
|
695
|
-
codeReview: "lazycodex-code-reviewer",
|
|
696
|
-
manualQa: "lazycodex-qa-executor",
|
|
697
|
-
gateReview: "lazycodex-gate-reviewer"
|
|
698
|
-
};
|
|
699
741
|
function reviewerRoleField(value, expected, field) {
|
|
700
742
|
const actual = textField(value, field);
|
|
701
743
|
if (actual !== expected)
|
|
@@ -729,14 +771,14 @@ function artifactCompatible(surface, kind) {
|
|
|
729
771
|
}
|
|
730
772
|
}
|
|
731
773
|
function checkFile(path, field, opts) {
|
|
732
|
-
if (opts === undefined)
|
|
774
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined)
|
|
733
775
|
return;
|
|
734
776
|
const absolute = resolve2(opts.repoRoot, path);
|
|
735
777
|
if (!opts.fs.existsSync(absolute))
|
|
736
778
|
invalid(`${field} must point to an existing artifact.`, field);
|
|
737
779
|
if (opts.fs.statSync(absolute).size <= 0)
|
|
738
780
|
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
739
|
-
if (opts.currentAttemptDir !== undefined) {
|
|
781
|
+
if (opts.currentAttemptDir !== undefined && opts.repoRoot !== undefined) {
|
|
740
782
|
const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
|
|
741
783
|
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
742
784
|
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
@@ -775,6 +817,7 @@ function referencedArtifacts(value, field, byId) {
|
|
|
775
817
|
});
|
|
776
818
|
}
|
|
777
819
|
function validateQualityGate(input, opts) {
|
|
820
|
+
const reviewerRoles = reviewerRolesFor(opts?.reviewerSurface ?? "lazycodex");
|
|
778
821
|
const gate = section(input, "qualityGate");
|
|
779
822
|
const codeReview = section(gate["codeReview"], "codeReview");
|
|
780
823
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
@@ -795,7 +838,7 @@ function validateQualityGate(input, opts) {
|
|
|
795
838
|
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
796
839
|
return {
|
|
797
840
|
codeReview: {
|
|
798
|
-
by: reviewerRoleField(codeReview["by"],
|
|
841
|
+
by: reviewerRoleField(codeReview["by"], reviewerRoles.codeReview, "codeReview.by"),
|
|
799
842
|
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
800
843
|
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
801
844
|
reportPath: codeReportPath,
|
|
@@ -803,7 +846,7 @@ function validateQualityGate(input, opts) {
|
|
|
803
846
|
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
804
847
|
},
|
|
805
848
|
manualQa: {
|
|
806
|
-
by: reviewerRoleField(manualQa["by"],
|
|
849
|
+
by: reviewerRoleField(manualQa["by"], reviewerRoles.manualQa, "manualQa.by"),
|
|
807
850
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
808
851
|
evidence: textField(manualQa["evidence"], "manualQa.evidence"),
|
|
809
852
|
surfaceEvidence,
|
|
@@ -811,7 +854,7 @@ function validateQualityGate(input, opts) {
|
|
|
811
854
|
artifactRefs
|
|
812
855
|
},
|
|
813
856
|
gateReview: {
|
|
814
|
-
by: reviewerRoleField(gateReview["by"],
|
|
857
|
+
by: reviewerRoleField(gateReview["by"], reviewerRoles.gateReview, "gateReview.by"),
|
|
815
858
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
816
859
|
reportPath: gateReportPath,
|
|
817
860
|
evidence: textField(gateReview["evidence"], "gateReview.evidence"),
|
|
@@ -1084,7 +1127,7 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
|
1084
1127
|
}
|
|
1085
1128
|
|
|
1086
1129
|
// packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
|
|
1087
|
-
var QUALITY_GATE_FS = { existsSync:
|
|
1130
|
+
var QUALITY_GATE_FS = { existsSync: existsSync4, statSync };
|
|
1088
1131
|
function ulwLoopFail2(message, code) {
|
|
1089
1132
|
throw new UlwLoopError(message, code);
|
|
1090
1133
|
}
|
|
@@ -1110,7 +1153,7 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1110
1153
|
throw error;
|
|
1111
1154
|
}
|
|
1112
1155
|
const path = resolve3(repoRoot, trimmed);
|
|
1113
|
-
if (!
|
|
1156
|
+
if (!existsSync4(path))
|
|
1114
1157
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1115
1158
|
try {
|
|
1116
1159
|
return JSON.parse(await readFile5(path, "utf8"));
|
|
@@ -1222,6 +1265,7 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1222
1265
|
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1223
1266
|
repoRoot,
|
|
1224
1267
|
fs: QUALITY_GATE_FS,
|
|
1268
|
+
reviewerSurface: resolveToolkitSurface(),
|
|
1225
1269
|
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1226
1270
|
});
|
|
1227
1271
|
requireBatchGate(plan, goal, qualityGate);
|
|
@@ -1345,12 +1389,13 @@ function buildCodexGoalInstruction(args) {
|
|
|
1345
1389
|
const mode = codexGoalMode(args.plan);
|
|
1346
1390
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
1347
1391
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
1348
|
-
|
|
1392
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
1393
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
1349
1394
|
}
|
|
1350
1395
|
function buildCreateGoalPayload(plan, goal) {
|
|
1351
1396
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
1352
1397
|
}
|
|
1353
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
1398
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
1354
1399
|
return joinLines([
|
|
1355
1400
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
1356
1401
|
`Mode: ${mode}`,
|
|
@@ -1367,7 +1412,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
1367
1412
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
1368
1413
|
...modeConstraintLines(mode, isFinal),
|
|
1369
1414
|
...evidenceLayoutLines(plan),
|
|
1370
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
1415
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
1371
1416
|
...checkpointLines(plan, mode),
|
|
1372
1417
|
"",
|
|
1373
1418
|
"create_goal payload:",
|
|
@@ -1419,7 +1464,8 @@ function evidenceLayoutLines(plan) {
|
|
|
1419
1464
|
"- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory."
|
|
1420
1465
|
];
|
|
1421
1466
|
}
|
|
1422
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
1467
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
1468
|
+
const roles = reviewerRolesFor(surface);
|
|
1423
1469
|
if (!isFinal)
|
|
1424
1470
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
1425
1471
|
const option = sessionOption(plan);
|
|
@@ -1429,8 +1475,8 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
1429
1475
|
"Final story — run mandatory quality gate before update_goal:",
|
|
1430
1476
|
"- Run targeted verification for changed behavior.",
|
|
1431
1477
|
"- Confirm every manualQa artifact path exists and has non-zero size.",
|
|
1432
|
-
|
|
1433
|
-
|
|
1478
|
+
`- First spawn ${roles.codeReview} and ${roles.manualQa} in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).`,
|
|
1479
|
+
`- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
|
|
1434
1480
|
"- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
|
|
1435
1481
|
"- 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.",
|
|
1436
1482
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
@@ -1454,7 +1500,7 @@ function joinLines(lines) {
|
|
|
1454
1500
|
}
|
|
1455
1501
|
|
|
1456
1502
|
// packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
|
|
1457
|
-
import { existsSync as
|
|
1503
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1458
1504
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1459
1505
|
|
|
1460
1506
|
// packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts
|
|
@@ -1564,7 +1610,7 @@ function clearGoalBlockerFields2(goal) {
|
|
|
1564
1610
|
}
|
|
1565
1611
|
async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
1566
1612
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1567
|
-
if (!args.force &&
|
|
1613
|
+
if (!args.force && existsSync5(ulwLoopGoalsPath(repoRoot, scope))) {
|
|
1568
1614
|
const existing = await readUlwLoopPlan(repoRoot, scope);
|
|
1569
1615
|
if (isUlwLoopDone(existing))
|
|
1570
1616
|
throw completedPlanExistsError(scope);
|
|
@@ -2802,11 +2848,11 @@ function commandScope(argv) {
|
|
|
2802
2848
|
}
|
|
2803
2849
|
|
|
2804
2850
|
// packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
|
|
2805
|
-
import { readFileSync as
|
|
2851
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2806
2852
|
|
|
2807
2853
|
// packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
2808
|
-
import { existsSync as
|
|
2809
|
-
import { fileURLToPath } from "node:url";
|
|
2854
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
2855
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2810
2856
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
2811
2857
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
2812
2858
|
|
|
@@ -2837,16 +2883,16 @@ Do not start the requested work until all three steps are complete.
|
|
|
2837
2883
|
`;
|
|
2838
2884
|
var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
|
|
2839
2885
|
var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
|
|
2840
|
-
var ULTRAWORK_DIRECTIVE =
|
|
2886
|
+
var ULTRAWORK_DIRECTIVE = readFileSync2(new URL("../directive.md", import.meta.url), "utf8");
|
|
2841
2887
|
function resolveUltraworkSkillFilePath() {
|
|
2842
|
-
return
|
|
2888
|
+
return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
|
|
2843
2889
|
}
|
|
2844
2890
|
function buildUltraworkSkillPointer(skillFilePath) {
|
|
2845
2891
|
return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
|
|
2846
2892
|
}
|
|
2847
2893
|
function buildUltraworkAdditionalContext(options = {}) {
|
|
2848
2894
|
const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
|
|
2849
|
-
if (skillFilePath !== null &&
|
|
2895
|
+
if (skillFilePath !== null && existsSync6(skillFilePath)) {
|
|
2850
2896
|
return buildUltraworkSkillPointer(skillFilePath);
|
|
2851
2897
|
}
|
|
2852
2898
|
return ULTRAWORK_DIRECTIVE;
|
|
@@ -2900,7 +2946,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
|
|
|
2900
2946
|
return false;
|
|
2901
2947
|
}
|
|
2902
2948
|
function readTranscriptTail(transcriptPath) {
|
|
2903
|
-
const rawTranscript =
|
|
2949
|
+
const rawTranscript = readFileSync3(transcriptPath);
|
|
2904
2950
|
return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
|
|
2905
2951
|
}
|
|
2906
2952
|
function isUltraworkPrompt(prompt) {
|
|
@@ -3093,16 +3139,16 @@ function readAll(stdin) {
|
|
|
3093
3139
|
}
|
|
3094
3140
|
|
|
3095
3141
|
// packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts
|
|
3096
|
-
import { existsSync as
|
|
3097
|
-
import { join as
|
|
3142
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4, statSync as statSync2, writeFileSync } from "node:fs";
|
|
3143
|
+
import { join as join3 } from "node:path";
|
|
3098
3144
|
var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
|
|
3099
3145
|
var DEFAULT_FANOUT_LIMIT = 60;
|
|
3100
|
-
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|final gate review/i;
|
|
3146
|
+
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
3101
3147
|
function applySpawnGuards(payload) {
|
|
3102
3148
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
3103
3149
|
return "";
|
|
3104
3150
|
const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
|
|
3105
|
-
const plan = readPlan(
|
|
3151
|
+
const plan = readPlan(join3(stateDir, "goals.json"));
|
|
3106
3152
|
if (plan === null)
|
|
3107
3153
|
return "";
|
|
3108
3154
|
const fanOutDenial = consumeFanOutBudget(stateDir);
|
|
@@ -3130,7 +3176,7 @@ async function runSpawnGuardCli(stdin, stdout) {
|
|
|
3130
3176
|
}
|
|
3131
3177
|
}
|
|
3132
3178
|
function consumeFanOutBudget(stateDir) {
|
|
3133
|
-
const counterPath =
|
|
3179
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3134
3180
|
const count = readCount(counterPath) + 1;
|
|
3135
3181
|
writeFileSync(counterPath, JSON.stringify({ count }));
|
|
3136
3182
|
const limit = fanOutLimit();
|
|
@@ -3151,15 +3197,15 @@ function missingGateArtifact(payload, plan) {
|
|
|
3151
3197
|
const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
|
|
3152
3198
|
for (const name of [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`]) {
|
|
3153
3199
|
const relative2 = `${attemptDir}/${name}`;
|
|
3154
|
-
if (!isNonEmptyFile(
|
|
3200
|
+
if (!isNonEmptyFile(join3(payload.cwd, relative2)))
|
|
3155
3201
|
return relative2;
|
|
3156
3202
|
}
|
|
3157
3203
|
return null;
|
|
3158
3204
|
}
|
|
3159
3205
|
const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
|
|
3160
|
-
if (!isNonEmptyFile(
|
|
3206
|
+
if (!isNonEmptyFile(join3(payload.cwd, flatReport)))
|
|
3161
3207
|
return flatReport;
|
|
3162
|
-
if (!hasOtherEvidenceFile(
|
|
3208
|
+
if (!hasOtherEvidenceFile(join3(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
|
|
3163
3209
|
return `.omo/evidence/<any manual-QA artifact besides ${goal3.id}-code-review.md>`;
|
|
3164
3210
|
return null;
|
|
3165
3211
|
}
|
|
@@ -3169,7 +3215,7 @@ function isGateReviewerSpawn(toolInput) {
|
|
|
3169
3215
|
const record = toolInput;
|
|
3170
3216
|
const agentType = record["agent_type"];
|
|
3171
3217
|
if (typeof agentType === "string")
|
|
3172
|
-
return agentType
|
|
3218
|
+
return GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
3173
3219
|
const message = record["message"];
|
|
3174
3220
|
return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
|
|
3175
3221
|
}
|
|
@@ -3193,7 +3239,7 @@ function fanOutLimit() {
|
|
|
3193
3239
|
}
|
|
3194
3240
|
function isNonEmptyFile(path) {
|
|
3195
3241
|
try {
|
|
3196
|
-
return
|
|
3242
|
+
return existsSync7(path) && statSync2(path).size > 0;
|
|
3197
3243
|
} catch (error) {
|
|
3198
3244
|
if (error instanceof Error)
|
|
3199
3245
|
return false;
|
|
@@ -3202,7 +3248,7 @@ function isNonEmptyFile(path) {
|
|
|
3202
3248
|
}
|
|
3203
3249
|
function hasOtherEvidenceFile(evidenceDir, excludedName) {
|
|
3204
3250
|
try {
|
|
3205
|
-
return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(
|
|
3251
|
+
return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join3(evidenceDir, name)));
|
|
3206
3252
|
} catch (error) {
|
|
3207
3253
|
if (error instanceof Error)
|
|
3208
3254
|
return false;
|
|
@@ -3211,7 +3257,7 @@ function hasOtherEvidenceFile(evidenceDir, excludedName) {
|
|
|
3211
3257
|
}
|
|
3212
3258
|
function readCount(counterPath) {
|
|
3213
3259
|
try {
|
|
3214
|
-
const parsed = JSON.parse(
|
|
3260
|
+
const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
|
|
3215
3261
|
return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
|
|
3216
3262
|
} catch (error) {
|
|
3217
3263
|
if (error instanceof Error)
|
|
@@ -3221,7 +3267,7 @@ function readCount(counterPath) {
|
|
|
3221
3267
|
}
|
|
3222
3268
|
function readPlan(goalsPath) {
|
|
3223
3269
|
try {
|
|
3224
|
-
return JSON.parse(
|
|
3270
|
+
return JSON.parse(readFileSync4(goalsPath, "utf8"));
|
|
3225
3271
|
} catch (error) {
|
|
3226
3272
|
if (error instanceof Error)
|
|
3227
3273
|
return null;
|
|
@@ -3230,8 +3276,8 @@ function readPlan(goalsPath) {
|
|
|
3230
3276
|
}
|
|
3231
3277
|
|
|
3232
3278
|
// packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts
|
|
3233
|
-
import { existsSync as
|
|
3234
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3279
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3280
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
|
|
3235
3281
|
var RESUME_CAP = 2;
|
|
3236
3282
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3237
3283
|
"context compacted",
|
|
@@ -3251,7 +3297,7 @@ function runStopResumeHook(input) {
|
|
|
3251
3297
|
if (boulderContinuationWillFire(payload.cwd, payload.session_id))
|
|
3252
3298
|
return "";
|
|
3253
3299
|
const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
|
|
3254
|
-
const plan = readPlan2(
|
|
3300
|
+
const plan = readPlan2(join4(stateDir, "goals.json"));
|
|
3255
3301
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
3256
3302
|
return "";
|
|
3257
3303
|
const goal3 = resumableGoal(plan);
|
|
@@ -3288,7 +3334,7 @@ function isResumableStatus(status2) {
|
|
|
3288
3334
|
return status2 === "pending" || status2 === "in_progress";
|
|
3289
3335
|
}
|
|
3290
3336
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3291
|
-
const ledgerLineCount = countLedgerLines(
|
|
3337
|
+
const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
|
|
3292
3338
|
const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
|
|
3293
3339
|
const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3294
3340
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
@@ -3321,7 +3367,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
|
|
|
3321
3367
|
}
|
|
3322
3368
|
function readPlan2(goalsPath) {
|
|
3323
3369
|
try {
|
|
3324
|
-
return JSON.parse(
|
|
3370
|
+
return JSON.parse(readFileSync5(goalsPath, "utf8"));
|
|
3325
3371
|
} catch (error) {
|
|
3326
3372
|
if (error instanceof Error)
|
|
3327
3373
|
return null;
|
|
@@ -3330,7 +3376,7 @@ function readPlan2(goalsPath) {
|
|
|
3330
3376
|
}
|
|
3331
3377
|
function countLedgerLines(ledgerPath) {
|
|
3332
3378
|
try {
|
|
3333
|
-
return
|
|
3379
|
+
return readFileSync5(ledgerPath, "utf8").split(`
|
|
3334
3380
|
`).filter(Boolean).length;
|
|
3335
3381
|
} catch (error) {
|
|
3336
3382
|
if (error instanceof Error)
|
|
@@ -3340,9 +3386,9 @@ function countLedgerLines(ledgerPath) {
|
|
|
3340
3386
|
}
|
|
3341
3387
|
function readCounter(counterPath) {
|
|
3342
3388
|
try {
|
|
3343
|
-
if (!
|
|
3389
|
+
if (!existsSync8(counterPath))
|
|
3344
3390
|
return null;
|
|
3345
|
-
const parsed = JSON.parse(
|
|
3391
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
3346
3392
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
3347
3393
|
return null;
|
|
3348
3394
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -3354,7 +3400,7 @@ function readCounter(counterPath) {
|
|
|
3354
3400
|
}
|
|
3355
3401
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
3356
3402
|
try {
|
|
3357
|
-
const raw = JSON.parse(
|
|
3403
|
+
const raw = JSON.parse(readFileSync5(join4(cwd, ".omo", "boulder.json"), "utf8"));
|
|
3358
3404
|
const works = raw["works"];
|
|
3359
3405
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
3360
3406
|
return entries.some((work) => {
|
|
@@ -3373,7 +3419,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
3373
3419
|
}
|
|
3374
3420
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
3375
3421
|
try {
|
|
3376
|
-
const transcript =
|
|
3422
|
+
const transcript = readFileSync5(transcriptPath, "utf8").toLowerCase();
|
|
3377
3423
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
3378
3424
|
} catch (error) {
|
|
3379
3425
|
if (error instanceof Error)
|
|
@@ -3385,12 +3431,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
3385
3431
|
const activePlan = entry["active_plan"];
|
|
3386
3432
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
3387
3433
|
return false;
|
|
3388
|
-
const planPath = isAbsolute2(activePlan) ? activePlan :
|
|
3434
|
+
const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
|
|
3389
3435
|
const worktree = entry["worktree_path"];
|
|
3390
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [
|
|
3436
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
|
|
3391
3437
|
for (const candidate of candidates) {
|
|
3392
3438
|
try {
|
|
3393
|
-
return
|
|
3439
|
+
return readFileSync5(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
3394
3440
|
} catch (error) {
|
|
3395
3441
|
if (!(error instanceof Error))
|
|
3396
3442
|
throw error;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UlwLoopToolkitSurface } from "./surface.js";
|
|
1
2
|
import type { UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
2
3
|
export interface CodexCreateGoalPayload {
|
|
3
4
|
readonly objective: string;
|
|
@@ -10,4 +11,5 @@ export declare function buildCodexGoalInstruction(args: {
|
|
|
10
11
|
readonly plan: UlwLoopPlan;
|
|
11
12
|
readonly goal: UlwLoopItem;
|
|
12
13
|
readonly isFinal?: boolean;
|
|
14
|
+
readonly surface?: UlwLoopToolkitSurface;
|
|
13
15
|
}): UlwLoopGoalInstruction;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// biome-ignore-all format: long agent-facing instruction strings are intentionally stable.
|
|
2
2
|
import { codexGoalMode, expectedCodexObjective, isEssentialCriterion, isFinalRunCompletionCandidate, } from "./goal-status.js";
|
|
3
|
+
import { resolveToolkitSurface, reviewerRolesFor } from "./surface.js";
|
|
3
4
|
export function buildCodexGoalInstruction(args) {
|
|
4
5
|
const mode = codexGoalMode(args.plan);
|
|
5
6
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
6
7
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
7
|
-
|
|
8
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
9
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
8
10
|
}
|
|
9
11
|
function buildCreateGoalPayload(plan, goal) {
|
|
10
12
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
11
13
|
}
|
|
12
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
14
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
13
15
|
return joinLines([
|
|
14
16
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
15
17
|
`Mode: ${mode}`,
|
|
@@ -26,7 +28,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
26
28
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
27
29
|
...modeConstraintLines(mode, isFinal),
|
|
28
30
|
...evidenceLayoutLines(plan),
|
|
29
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
31
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
30
32
|
...checkpointLines(plan, mode),
|
|
31
33
|
"",
|
|
32
34
|
"create_goal payload:",
|
|
@@ -80,7 +82,8 @@ function evidenceLayoutLines(plan) {
|
|
|
80
82
|
"- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory.",
|
|
81
83
|
];
|
|
82
84
|
}
|
|
83
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
85
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
86
|
+
const roles = reviewerRolesFor(surface);
|
|
84
87
|
if (!isFinal)
|
|
85
88
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
86
89
|
const option = sessionOption(plan);
|
|
@@ -90,8 +93,8 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
90
93
|
"Final story — run mandatory quality gate before update_goal:",
|
|
91
94
|
"- Run targeted verification for changed behavior.",
|
|
92
95
|
"- Confirm every manualQa artifact path exists and has non-zero size.",
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
`- First spawn ${roles.codeReview} and ${roles.manualQa} in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).`,
|
|
97
|
+
`- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
|
|
95
98
|
"- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
|
|
96
99
|
"- 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.",
|
|
97
100
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UlwLoopToolkitSurface } from "./surface.js";
|
|
1
2
|
import type { UlwLoopQualityGate } from "./types.js";
|
|
2
3
|
export { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, normalizeBlockerEvidence, sameBlockerOccurrences, } from "./quality-gate-blockers.js";
|
|
3
4
|
export interface QualityGateFs {
|
|
@@ -7,8 +8,9 @@ export interface QualityGateFs {
|
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
10
|
export interface ValidateQualityGateOptions {
|
|
10
|
-
readonly repoRoot
|
|
11
|
-
readonly fs
|
|
11
|
+
readonly repoRoot?: string;
|
|
12
|
+
readonly fs?: QualityGateFs;
|
|
12
13
|
readonly currentAttemptDir?: string;
|
|
14
|
+
readonly reviewerSurface?: UlwLoopToolkitSurface;
|
|
13
15
|
}
|
|
14
16
|
export declare function validateQualityGate(input: unknown, opts?: ValidateQualityGateOptions): UlwLoopQualityGate;
|
|
@@ -2,11 +2,7 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { isWithinAttemptDir } from "./paths.js";
|
|
3
3
|
import { emptyBlockers, invalid, literal, numberField, section, stringArray, textField, } from "./quality-gate-fields.js";
|
|
4
4
|
import { adversarialVerdict, codeQualityStatusField, passedVerdict } from "./quality-gate-verdicts.js";
|
|
5
|
-
|
|
6
|
-
codeReview: "lazycodex-code-reviewer",
|
|
7
|
-
manualQa: "lazycodex-qa-executor",
|
|
8
|
-
gateReview: "lazycodex-gate-reviewer",
|
|
9
|
-
};
|
|
5
|
+
import { reviewerRolesFor } from "./surface.js";
|
|
10
6
|
export { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, normalizeBlockerEvidence, sameBlockerOccurrences, } from "./quality-gate-blockers.js";
|
|
11
7
|
function reviewerRoleField(value, expected, field) {
|
|
12
8
|
const actual = textField(value, field);
|
|
@@ -51,14 +47,14 @@ function artifactCompatible(surface, kind) {
|
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
49
|
function checkFile(path, field, opts) {
|
|
54
|
-
if (opts === undefined)
|
|
50
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined)
|
|
55
51
|
return;
|
|
56
52
|
const absolute = resolve(opts.repoRoot, path);
|
|
57
53
|
if (!opts.fs.existsSync(absolute))
|
|
58
54
|
invalid(`${field} must point to an existing artifact.`, field);
|
|
59
55
|
if (opts.fs.statSync(absolute).size <= 0)
|
|
60
56
|
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
61
|
-
if (opts.currentAttemptDir !== undefined) {
|
|
57
|
+
if (opts.currentAttemptDir !== undefined && opts.repoRoot !== undefined) {
|
|
62
58
|
const attemptRoot = resolve(opts.repoRoot, opts.currentAttemptDir);
|
|
63
59
|
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
64
60
|
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
@@ -97,6 +93,7 @@ function referencedArtifacts(value, field, byId) {
|
|
|
97
93
|
});
|
|
98
94
|
}
|
|
99
95
|
export function validateQualityGate(input, opts) {
|
|
96
|
+
const reviewerRoles = reviewerRolesFor(opts?.reviewerSurface ?? "lazycodex");
|
|
100
97
|
const gate = section(input, "qualityGate");
|
|
101
98
|
const codeReview = section(gate["codeReview"], "codeReview");
|
|
102
99
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
@@ -117,7 +114,7 @@ export function validateQualityGate(input, opts) {
|
|
|
117
114
|
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
118
115
|
return {
|
|
119
116
|
codeReview: {
|
|
120
|
-
by: reviewerRoleField(codeReview["by"],
|
|
117
|
+
by: reviewerRoleField(codeReview["by"], reviewerRoles.codeReview, "codeReview.by"),
|
|
121
118
|
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
122
119
|
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
123
120
|
reportPath: codeReportPath,
|
|
@@ -125,7 +122,7 @@ export function validateQualityGate(input, opts) {
|
|
|
125
122
|
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers"),
|
|
126
123
|
},
|
|
127
124
|
manualQa: {
|
|
128
|
-
by: reviewerRoleField(manualQa["by"],
|
|
125
|
+
by: reviewerRoleField(manualQa["by"], reviewerRoles.manualQa, "manualQa.by"),
|
|
129
126
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
130
127
|
evidence: textField(manualQa["evidence"], "manualQa.evidence"),
|
|
131
128
|
surfaceEvidence,
|
|
@@ -133,7 +130,7 @@ export function validateQualityGate(input, opts) {
|
|
|
133
130
|
artifactRefs,
|
|
134
131
|
},
|
|
135
132
|
gateReview: {
|
|
136
|
-
by: reviewerRoleField(gateReview["by"],
|
|
133
|
+
by: reviewerRoleField(gateReview["by"], reviewerRoles.gateReview, "gateReview.by"),
|
|
137
134
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
138
135
|
reportPath: gateReportPath,
|
|
139
136
|
evidence: textField(gateReview["evidence"], "gateReview.evidence"),
|
|
@@ -3,12 +3,13 @@ import { join } from "node:path";
|
|
|
3
3
|
import { parsePreToolUsePayload } from "./codex-hook.js";
|
|
4
4
|
import { isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
5
5
|
import { ulwLoopAttemptEvidenceDir, ulwLoopDir } from "./paths.js";
|
|
6
|
+
import { GATE_REVIEWER_AGENT_NAMES } from "./surface.js";
|
|
6
7
|
// spawn_agent = v1; collaborationspawn_agent = the delimiter-free flattened v2
|
|
7
8
|
// hook token from codex-rs hook_names.rs; collaboration.spawn_agent = the
|
|
8
9
|
// dotted token observed live in the task-1 probe (hook-tool-tokens.txt).
|
|
9
10
|
const SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
|
|
10
11
|
const DEFAULT_FANOUT_LIMIT = 60;
|
|
11
|
-
const GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|final gate review/i;
|
|
12
|
+
const GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
12
13
|
export function applySpawnGuards(payload) {
|
|
13
14
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
14
15
|
return "";
|
|
@@ -84,7 +85,7 @@ function isGateReviewerSpawn(toolInput) {
|
|
|
84
85
|
const record = toolInput;
|
|
85
86
|
const agentType = record["agent_type"];
|
|
86
87
|
if (typeof agentType === "string")
|
|
87
|
-
return agentType
|
|
88
|
+
return GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
88
89
|
const message = record["message"];
|
|
89
90
|
return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
|
|
90
91
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type UlwLoopToolkitSurface = "lazycodex" | "omo-senpi";
|
|
2
|
+
export interface UlwLoopReviewerRoles {
|
|
3
|
+
readonly codeReview: string;
|
|
4
|
+
readonly manualQa: string;
|
|
5
|
+
readonly gateReview: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const REVIEWER_ROLES_BY_SURFACE: Readonly<Record<UlwLoopToolkitSurface, UlwLoopReviewerRoles>>;
|
|
8
|
+
export declare const GATE_REVIEWER_AGENT_NAMES: ReadonlySet<string>;
|
|
9
|
+
export declare function reviewerRolesFor(surface: UlwLoopToolkitSurface): UlwLoopReviewerRoles;
|
|
10
|
+
export declare const SURFACE_MARKER_FILENAME = "surface.json";
|
|
11
|
+
export interface ResolveToolkitSurfaceOptions {
|
|
12
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
13
|
+
readonly entryDir?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveToolkitSurface(options?: ResolveToolkitSurfaceOptions): UlwLoopToolkitSurface;
|