lazycodex-ai 5.0.0-beta.53 → 5.0.0-beta.55
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 +53 -19
- package/dist/cli-node/index.js +53 -19
- package/package.json +1 -1
- 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/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/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 +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +14 -34
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +1 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +0 -18
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +1 -88
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +86 -174
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +2 -18
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +23 -33
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +16 -9
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +18 -59
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +1 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +1 -124
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +11 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +10 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +31 -50
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +29 -10
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +0 -82
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +16 -22
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +27 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +34 -11
- package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +9 -47
- package/packages/omo-codex/plugin/components/ulw-loop/test/review-blockers.test.ts +9 -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 +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-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/scripts/install-dist/install-local.mjs +2 -2
|
@@ -7,16 +7,25 @@ function safeObject(value) {
|
|
|
7
7
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8
8
|
}
|
|
9
9
|
function safeString(value) {
|
|
10
|
+
return typeof value === "string" ? value : "";
|
|
11
|
+
}
|
|
12
|
+
function safeStatusString(value) {
|
|
10
13
|
return typeof value === "string" ? value.trim() : "";
|
|
11
14
|
}
|
|
12
15
|
function normalizeStatus(value) {
|
|
13
|
-
const status =
|
|
16
|
+
const status = safeStatusString(value).toLowerCase();
|
|
14
17
|
if (status === "complete" || status === "completed" || status === "done")
|
|
15
18
|
return "complete";
|
|
16
19
|
if (status === "cancelled" || status === "canceled")
|
|
17
20
|
return "cancelled";
|
|
18
21
|
if (status === "failed" || status === "failure")
|
|
19
22
|
return "failed";
|
|
23
|
+
if (status === "paused")
|
|
24
|
+
return "paused";
|
|
25
|
+
if (status === "usage_limited")
|
|
26
|
+
return "usage_limited";
|
|
27
|
+
if (status === "budget_limited")
|
|
28
|
+
return "budget_limited";
|
|
20
29
|
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
21
30
|
return "active";
|
|
22
31
|
return "unknown";
|
|
@@ -64,48 +73,29 @@ export function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
|
64
73
|
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
65
74
|
const errors = [];
|
|
66
75
|
const warnings = [];
|
|
76
|
+
const expected = options.expectedObjective;
|
|
77
|
+
const normalizedExpected = normalizeObjective(expected);
|
|
67
78
|
if (!effectiveSnapshot.available) {
|
|
68
|
-
|
|
69
|
-
if (options.requireSnapshot)
|
|
70
|
-
errors.push(message);
|
|
71
|
-
else
|
|
72
|
-
warnings.push(message);
|
|
79
|
+
warnings.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
73
80
|
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
74
81
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
82
|
+
const accepted = new Set([
|
|
83
|
+
normalizedExpected,
|
|
84
|
+
...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective)),
|
|
85
|
+
].filter(Boolean));
|
|
77
86
|
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
78
|
-
if (!actual) {
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
else if (!accepted.has(actual)) {
|
|
82
|
-
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
|
|
87
|
+
if (actual && !accepted.has(normalizeObjective(actual))) {
|
|
88
|
+
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
83
89
|
}
|
|
84
|
-
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
|
|
85
90
|
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
if (options.requireComplete && actualStatus !== "complete") {
|
|
90
|
-
errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
|
|
91
|
+
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
92
|
+
warnings.push("/goal resume or raise the budget");
|
|
91
93
|
}
|
|
94
|
+
if (actualStatus === "complete")
|
|
95
|
+
warnings.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
92
96
|
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
93
97
|
}
|
|
94
98
|
export function formatCodexGoalReconciliation(reconciliation) {
|
|
95
99
|
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
96
100
|
return parts.join(" ");
|
|
97
101
|
}
|
|
98
|
-
/**
|
|
99
|
-
* The reconciliation errors normalize whitespace for comparison, which makes the
|
|
100
|
-
* quoted objective unusable as a copy source. Recovery therefore carries the
|
|
101
|
-
* plan's `codexObjective` verbatim so the agent can paste it into `update_goal`.
|
|
102
|
-
*/
|
|
103
|
-
export function codexGoalMismatchRecovery(expectedObjective, snapshot) {
|
|
104
|
-
const receivedObjective = snapshot?.objective ?? "";
|
|
105
|
-
const message = [
|
|
106
|
-
"Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
|
|
107
|
-
`expected codexObjective: ${expectedObjective}`,
|
|
108
|
-
`received objective: ${receivedObjective || "(none)"}`,
|
|
109
|
-
].join("\n");
|
|
110
|
-
return { message, details: { expectedObjective, receivedObjective } };
|
|
111
|
-
}
|
|
@@ -12,5 +12,7 @@ export interface RecordFinalReviewBlockersResult {
|
|
|
12
12
|
readonly blockedGoal: UlwLoopItem;
|
|
13
13
|
readonly newGoal: UlwLoopItem;
|
|
14
14
|
readonly ledgerEntries: UlwLoopLedgerEntry[];
|
|
15
|
+
readonly nextActions: readonly string[];
|
|
16
|
+
readonly warnings: readonly string[];
|
|
15
17
|
}
|
|
16
18
|
export declare function recordFinalReviewBlockers(repoRoot: string, args: RecordFinalReviewBlockersArgs, scope?: UlwLoopScope): Promise<RecordFinalReviewBlockersResult>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// biome-ignore-all format: compact port must stay within the requested pure LOC budget.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
2
|
+
import { CodexGoalSnapshotError, formatCodexGoalReconciliation, readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot, } from "./codex-goal-snapshot.js";
|
|
3
|
+
import { compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
5
4
|
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
|
|
6
5
|
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
|
|
7
6
|
import { iso, UlwLoopError } from "./types.js";
|
|
@@ -42,11 +41,12 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
42
41
|
if (!isFinalRunCompletionCandidate(plan, goal))
|
|
43
42
|
ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
|
|
44
43
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
45
|
+
expectedObjective: expectedCodexObjective(plan, goal),
|
|
46
|
+
acceptedObjectives: compatibleCodexObjectives(plan),
|
|
47
|
+
});
|
|
48
48
|
if (!reconciliation.ok)
|
|
49
|
-
throw
|
|
49
|
+
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
50
50
|
const now = iso();
|
|
51
51
|
for (const field of BLOCKER_FIELDS)
|
|
52
52
|
Reflect.deleteProperty(goal, field);
|
|
@@ -58,7 +58,7 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
58
58
|
delete plan.activeGoalId;
|
|
59
59
|
const newGoal = appendBlockerGoal(plan, args, now);
|
|
60
60
|
plan.updatedAt = now;
|
|
61
|
-
const codexGoal =
|
|
61
|
+
const codexGoal = snapshot?.raw;
|
|
62
62
|
const blockedEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
|
|
63
63
|
const addedEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
|
|
64
64
|
const summaryEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
|
|
@@ -67,6 +67,13 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
67
67
|
await writePlan(repoRoot, plan, scope);
|
|
68
68
|
for (const entry of ledgerEntries)
|
|
69
69
|
await appendLedger(repoRoot, entry, scope);
|
|
70
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
plan,
|
|
72
|
+
blockedGoal: goal,
|
|
73
|
+
newGoal,
|
|
74
|
+
ledgerEntries,
|
|
75
|
+
nextActions: reconciliation.warnings,
|
|
76
|
+
warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs")),
|
|
77
|
+
};
|
|
71
78
|
});
|
|
72
79
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit --with-ultrawork",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
10
|
+
"statusMessage": "(OmO 5.0.0-beta.55) Checking Ulw-Loop Steering"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "command",
|
|
21
21
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use-spawn",
|
|
22
22
|
"timeout": 5,
|
|
23
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
23
|
+
"statusMessage": "(OmO 5.0.0-beta.55) Recording Spawn Admission"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"type": "command",
|
|
34
34
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
|
|
35
35
|
"timeout": 5,
|
|
36
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
36
|
+
"statusMessage": "(OmO 5.0.0-beta.55) Enforcing Unlimited Ulw-Loop Budget"
|
|
37
37
|
}
|
|
38
38
|
]
|
|
39
39
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "command",
|
|
45
45
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use-spawn",
|
|
46
46
|
"timeout": 5,
|
|
47
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
47
|
+
"statusMessage": "(OmO 5.0.0-beta.55) Guarding Ulw-Loop Spawns"
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
50
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "command",
|
|
57
57
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook stop",
|
|
58
58
|
"timeout": 10,
|
|
59
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
59
|
+
"statusMessage": "(OmO 5.0.0-beta.55) Checking Ulw-Loop Resume"
|
|
60
60
|
}
|
|
61
61
|
]
|
|
62
62
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ulw-loop",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.55",
|
|
4
4
|
"description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
codexGoalMode,
|
|
9
|
-
compatibleCodexObjectives,
|
|
10
|
-
expectedCodexObjective,
|
|
11
|
-
isFinalRunCompletionCandidate,
|
|
12
|
-
} from "./goal-status.js";
|
|
2
|
+
CodexGoalSnapshotError,
|
|
3
|
+
formatCodexGoalReconciliation,
|
|
4
|
+
readCodexGoalSnapshotInput,
|
|
5
|
+
reconcileCodexGoalSnapshot,
|
|
6
|
+
} from "./codex-goal-snapshot.js";
|
|
7
|
+
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective } from "./goal-status.js";
|
|
13
8
|
import type { UlwLoopScope } from "./paths.js";
|
|
14
9
|
import type { UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
15
10
|
import { UlwLoopError } from "./types.js";
|
|
16
11
|
|
|
17
|
-
function normalizeObjective(value: string): string {
|
|
18
|
-
return value.replace(/\s+/g, " ").trim();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
12
|
export async function validateCheckpointCodexGoal(input: {
|
|
22
13
|
readonly repoRoot: string;
|
|
23
14
|
readonly plan: UlwLoopPlan;
|
|
@@ -25,53 +16,21 @@ export async function validateCheckpointCodexGoal(input: {
|
|
|
25
16
|
readonly raw: string | undefined;
|
|
26
17
|
readonly evidence: string;
|
|
27
18
|
readonly scope?: UlwLoopScope;
|
|
28
|
-
}): Promise<unknown> {
|
|
29
|
-
const aggregate = codexGoalMode(input.plan) === "aggregate";
|
|
30
|
-
const final = isFinalRunCompletionCandidate(input.plan, input.goal);
|
|
19
|
+
}): Promise<{ readonly raw: unknown; readonly nextActions: readonly string[]; readonly warnings: readonly string[] }> {
|
|
31
20
|
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
32
|
-
const
|
|
21
|
+
const expected = expectedCodexObjective(input.plan, input.goal);
|
|
33
22
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
34
|
-
expectedObjective,
|
|
35
|
-
...(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
requireComplete: !aggregate || final,
|
|
39
|
-
});
|
|
40
|
-
if (reconciliation.ok) return reconciliation.snapshot.raw;
|
|
41
|
-
const objective = snapshot?.objective;
|
|
42
|
-
const mismatchedTaskObjective =
|
|
43
|
-
snapshot?.available === true &&
|
|
44
|
-
objective !== undefined &&
|
|
45
|
-
normalizeObjective(objective) !== normalizeObjective(expectedObjective);
|
|
46
|
-
const completedTaskScoped =
|
|
47
|
-
mismatchedTaskObjective &&
|
|
48
|
-
snapshot.status === "complete" &&
|
|
49
|
-
(await canReconcileCompletedTaskScopedAggregateSnapshot(
|
|
50
|
-
input.repoRoot,
|
|
51
|
-
input.plan,
|
|
52
|
-
input.goal,
|
|
53
|
-
objective,
|
|
54
|
-
input.evidence,
|
|
55
|
-
input.scope,
|
|
56
|
-
));
|
|
57
|
-
const activeFinalTaskScoped =
|
|
58
|
-
mismatchedTaskObjective &&
|
|
59
|
-
snapshot.status === "active" &&
|
|
60
|
-
(await canReconcileActiveFinalTaskScopedAggregateSnapshot(
|
|
61
|
-
input.repoRoot,
|
|
62
|
-
input.plan,
|
|
63
|
-
input.goal,
|
|
64
|
-
objective,
|
|
65
|
-
input.evidence,
|
|
66
|
-
input.scope,
|
|
67
|
-
));
|
|
68
|
-
if (completedTaskScoped || activeFinalTaskScoped) return reconciliation.snapshot.raw;
|
|
69
|
-
throw codexSnapshotMismatchError({
|
|
70
|
-
reconciliation,
|
|
71
|
-
snapshot,
|
|
72
|
-
expectedObjective,
|
|
73
|
-
taskScopedHint: { goal: input.goal, aggregate, final },
|
|
23
|
+
expectedObjective: expected,
|
|
24
|
+
...(codexGoalMode(input.plan) === "aggregate"
|
|
25
|
+
? { acceptedObjectives: compatibleCodexObjectives(input.plan) }
|
|
26
|
+
: {}),
|
|
74
27
|
});
|
|
28
|
+
if (!reconciliation.ok) throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
29
|
+
return {
|
|
30
|
+
raw: snapshot?.raw,
|
|
31
|
+
nextActions: reconciliation.warnings,
|
|
32
|
+
warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs")),
|
|
33
|
+
};
|
|
75
34
|
}
|
|
76
35
|
|
|
77
36
|
export function combineCheckpointValidationErrors(codexError: UlwLoopError, gateError: UlwLoopError): UlwLoopError {
|
|
@@ -42,12 +42,7 @@ export async function checkpoint(
|
|
|
42
42
|
const goalId = required(argv, "--goal-id");
|
|
43
43
|
const statusValue = checkpointStatus(required(argv, "--status"));
|
|
44
44
|
const evidence = required(argv, "--evidence");
|
|
45
|
-
const codexGoalJson = await parseCodexGoalJson(
|
|
46
|
-
statusValue === "complete" ? required(argv, "--codex-goal-json") : readValue(argv, "--codex-goal-json"),
|
|
47
|
-
);
|
|
48
|
-
if (statusValue === "complete" && codexGoalJson === undefined) {
|
|
49
|
-
throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
|
|
50
|
-
}
|
|
45
|
+
const codexGoalJson = await parseCodexGoalJson(readValue(argv, "--codex-goal-json"));
|
|
51
46
|
const qualityGateJson = readValue(argv, "--quality-gate-json");
|
|
52
47
|
const args: CheckpointUlwLoopArgs & { readonly advance: boolean } = {
|
|
53
48
|
goalId,
|
|
@@ -1,130 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
|
|
4
|
-
import type { CodexGoalReconciliation, CodexGoalSnapshot } from "./codex-goal-snapshot.js";
|
|
5
|
-
import { codexGoalMismatchRecovery, formatCodexGoalReconciliation } from "./codex-goal-snapshot.js";
|
|
6
|
-
import { codexGoalMode, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
7
|
-
import { type UlwLoopScope, ulwLoopBriefPath } from "./paths.js";
|
|
8
|
-
import type { UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
9
|
-
import { ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
|
|
10
|
-
|
|
11
|
-
export interface CodexSnapshotMismatchInput {
|
|
12
|
-
readonly reconciliation: CodexGoalReconciliation;
|
|
13
|
-
readonly snapshot: CodexGoalSnapshot | null | undefined;
|
|
14
|
-
readonly expectedObjective: string;
|
|
15
|
-
/** Only the checkpoint path can fall back to task-scoped aggregate reconciliation, so only it asks for that hint. */
|
|
16
|
-
readonly taskScopedHint?: { readonly goal: UlwLoopItem; readonly aggregate: boolean; readonly final: boolean };
|
|
17
|
-
}
|
|
1
|
+
import { UlwLoopError } from "./types.js";
|
|
18
2
|
|
|
19
3
|
export function combineCheckpointValidationErrors(codexError: UlwLoopError, gateError: UlwLoopError): UlwLoopError {
|
|
20
4
|
return new UlwLoopError(`${codexError.message}\n${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
21
5
|
details: { ...(codexError.details ?? {}), ...(gateError.details ?? {}) },
|
|
22
6
|
});
|
|
23
7
|
}
|
|
24
|
-
|
|
25
|
-
export function codexSnapshotMismatchError(input: CodexSnapshotMismatchInput): UlwLoopError {
|
|
26
|
-
const { reconciliation, snapshot, taskScopedHint } = input;
|
|
27
|
-
const hint =
|
|
28
|
-
taskScopedHint?.aggregate === true && snapshot?.status === "complete" && snapshot.objective !== undefined
|
|
29
|
-
? buildTaskScopedAggregateReconciliationHint(taskScopedHint.goal, taskScopedHint.final)
|
|
30
|
-
: "";
|
|
31
|
-
const recovery = codexGoalMismatchRecovery(input.expectedObjective, snapshot);
|
|
32
|
-
return new UlwLoopError(
|
|
33
|
-
`${formatCodexGoalReconciliation(reconciliation)}${hint}\n${recovery.message}`,
|
|
34
|
-
"ulw_loop_codex_snapshot_mismatch",
|
|
35
|
-
{ details: recovery.details },
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function normalizeObjective(value: string): string {
|
|
40
|
-
return value.replace(/\s+/g, " ").trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function textMentionsUlwLoopPlanArtifact(value: string | undefined): boolean {
|
|
44
|
-
const normalized = (value ?? "").toLowerCase();
|
|
45
|
-
return (
|
|
46
|
-
normalized.includes(ULW_LOOP_DIR.toLowerCase()) ||
|
|
47
|
-
normalized.includes(ULW_LOOP_GOALS.toLowerCase()) ||
|
|
48
|
-
normalized.includes(ULW_LOOP_LEDGER.toLowerCase())
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function textMentionsGoalId(value: string | undefined, goalId: string): boolean {
|
|
53
|
-
return (value ?? "").toLowerCase().includes(goalId.toLowerCase());
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function textHasCompletionValidationEvidence(value: string | undefined): boolean {
|
|
57
|
-
const normalized = (value ?? "").toLowerCase();
|
|
58
|
-
const done =
|
|
59
|
-
/\b(?:planned work|implementation|deliverables?|scope|task|work)\b/.test(normalized) &&
|
|
60
|
-
/\b(?:done|complete|completed|finished|shipped)\b/.test(normalized);
|
|
61
|
-
const verified =
|
|
62
|
-
/\b(?:validation|verification|tests?|build|lint|review|quality gate|code-review)\b/.test(normalized) &&
|
|
63
|
-
/\b(?:passed|complete|completed|clean|green|approve|approved|clear)\b/.test(normalized);
|
|
64
|
-
return done && verified;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function snapshotObjectiveMapsToUlwLoopPlan(
|
|
68
|
-
repoRoot: string,
|
|
69
|
-
snapshotObjective: string,
|
|
70
|
-
scope?: UlwLoopScope,
|
|
71
|
-
): Promise<boolean> {
|
|
72
|
-
const actual = normalizeObjective(snapshotObjective).toLowerCase();
|
|
73
|
-
if (textMentionsUlwLoopPlanArtifact(actual)) return true;
|
|
74
|
-
if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope))) return false;
|
|
75
|
-
try {
|
|
76
|
-
const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
|
|
77
|
-
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
|
|
78
|
-
} catch (error) {
|
|
79
|
-
if (error instanceof Error) return false;
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function canReconcileCompletedTaskScopedAggregateSnapshot(
|
|
85
|
-
repoRoot: string,
|
|
86
|
-
plan: UlwLoopPlan,
|
|
87
|
-
goal: UlwLoopItem,
|
|
88
|
-
snapshotObjective: string,
|
|
89
|
-
evidence: string,
|
|
90
|
-
scope?: UlwLoopScope,
|
|
91
|
-
): Promise<boolean> {
|
|
92
|
-
if (codexGoalMode(plan) !== "aggregate") return false;
|
|
93
|
-
if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id) return false;
|
|
94
|
-
if (isFinalRunCompletionCandidate(plan, goal)) {
|
|
95
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
96
|
-
}
|
|
97
|
-
if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id)) return false;
|
|
98
|
-
if (!textHasCompletionValidationEvidence(evidence)) return false;
|
|
99
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export async function canReconcileActiveFinalTaskScopedAggregateSnapshot(
|
|
103
|
-
repoRoot: string,
|
|
104
|
-
plan: UlwLoopPlan,
|
|
105
|
-
goal: UlwLoopItem,
|
|
106
|
-
snapshotObjective: string,
|
|
107
|
-
evidence: string,
|
|
108
|
-
scope?: UlwLoopScope,
|
|
109
|
-
): Promise<boolean> {
|
|
110
|
-
if (codexGoalMode(plan) !== "aggregate") return false;
|
|
111
|
-
if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id) return false;
|
|
112
|
-
if (!isFinalRunCompletionCandidate(plan, goal)) return false;
|
|
113
|
-
if (!textHasCompletionValidationEvidence(evidence)) return false;
|
|
114
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function buildCompletedLegacyGoalRemediation(goal: UlwLoopItem): string {
|
|
118
|
-
return [
|
|
119
|
-
"If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.",
|
|
120
|
-
`Record a non-terminal blocker with: omo-agent-toolkit ulw-loop checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
|
|
121
|
-
"Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there.",
|
|
122
|
-
].join(" ");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function buildTaskScopedAggregateReconciliationHint(goal: UlwLoopItem, final: boolean): string {
|
|
126
|
-
if (final) {
|
|
127
|
-
return ` Final task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress final OMO goal and the completed get_goal objective to map to the ulw-loop brief or artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
|
|
128
|
-
}
|
|
129
|
-
return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
|
|
130
|
-
}
|
|
@@ -40,6 +40,8 @@ export interface CheckpointUlwLoopResult {
|
|
|
40
40
|
readonly goal: UlwLoopItem;
|
|
41
41
|
readonly ledgerEntry: UlwLoopLedgerEntry;
|
|
42
42
|
readonly aggregateCompletion?: UlwLoopAggregateCompletion;
|
|
43
|
+
readonly nextActions: readonly string[];
|
|
44
|
+
readonly warnings: readonly string[];
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const QUALITY_GATE_FS = { existsSync, statSync } as const;
|
|
@@ -158,6 +160,8 @@ export async function checkpointUlwLoop(
|
|
|
158
160
|
let aggregateCompletion: UlwLoopAggregateCompletion | undefined;
|
|
159
161
|
let qualityGate: UlwLoopQualityGate | undefined;
|
|
160
162
|
let codexGoal: unknown;
|
|
163
|
+
let nextActions: readonly string[] = [];
|
|
164
|
+
let warnings: readonly string[] = [];
|
|
161
165
|
if (args.status === "complete") {
|
|
162
166
|
const aggregate = codexGoalMode(plan) === "aggregate";
|
|
163
167
|
const final = isFinalRunCompletionCandidate(plan, goal);
|
|
@@ -170,7 +174,7 @@ export async function checkpointUlwLoop(
|
|
|
170
174
|
else requireAllCriteriaPass(goal);
|
|
171
175
|
let codexValidationError: UlwLoopError | undefined;
|
|
172
176
|
try {
|
|
173
|
-
|
|
177
|
+
const validation = await validateCheckpointCodexGoal({
|
|
174
178
|
repoRoot,
|
|
175
179
|
plan,
|
|
176
180
|
goal,
|
|
@@ -178,6 +182,9 @@ export async function checkpointUlwLoop(
|
|
|
178
182
|
evidence,
|
|
179
183
|
...(scope === undefined ? {} : { scope }),
|
|
180
184
|
});
|
|
185
|
+
codexGoal = validation.raw;
|
|
186
|
+
nextActions = validation.nextActions;
|
|
187
|
+
warnings = validation.warnings;
|
|
181
188
|
} catch (error) {
|
|
182
189
|
if (!(error instanceof UlwLoopError)) throw error;
|
|
183
190
|
codexValidationError = error;
|
|
@@ -213,6 +220,7 @@ export async function checkpointUlwLoop(
|
|
|
213
220
|
} else applyBlockedOrFailed(goal, plan, args.status, evidence, now);
|
|
214
221
|
goal.updatedAt = now;
|
|
215
222
|
if (aggregateCompletion !== undefined) plan.aggregateCompletion = aggregateCompletion;
|
|
223
|
+
if (aggregateCompletion !== undefined) nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
|
|
216
224
|
plan.updatedAt = now;
|
|
217
225
|
await writePlan(repoRoot, plan, scope);
|
|
218
226
|
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
|
|
@@ -220,7 +228,7 @@ export async function checkpointUlwLoop(
|
|
|
220
228
|
const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
|
|
221
229
|
if (closedBatch !== undefined) await appendLedger(repoRoot, { at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId }, scope);
|
|
222
230
|
return aggregateCompletion === undefined
|
|
223
|
-
? { plan, goal, ledgerEntry }
|
|
224
|
-
: { plan, goal, ledgerEntry, aggregateCompletion };
|
|
231
|
+
? { plan, goal, ledgerEntry, nextActions, warnings }
|
|
232
|
+
: { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
|
|
225
233
|
});
|
|
226
234
|
}
|
|
@@ -65,11 +65,17 @@ export async function readJsonInput(value: string | undefined): Promise<unknown
|
|
|
65
65
|
|
|
66
66
|
export async function parseCodexGoalJson(value: string | undefined): Promise<string | undefined> {
|
|
67
67
|
if (value === undefined) return undefined;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
try {
|
|
69
|
+
const raw = looksLikeJson(value) ? value : await readFile(value, "utf8");
|
|
70
|
+
JSON.parse(raw);
|
|
71
|
+
return raw;
|
|
72
|
+
} catch (error) {
|
|
71
73
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
72
|
-
throw new UlwLoopError(
|
|
74
|
+
throw new UlwLoopError(
|
|
75
|
+
`Invalid --codex-goal-json: ${looksLikeJson(value) ? message : "neither valid JSON nor a readable path"}`,
|
|
76
|
+
"ULW_LOOP_CODEX_GOAL_JSON_INVALID",
|
|
77
|
+
{ cause: error },
|
|
78
|
+
);
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -203,6 +203,8 @@ export async function reviewBlockers(
|
|
|
203
203
|
blockedGoal: result.blockedGoal,
|
|
204
204
|
goal: result.newGoal,
|
|
205
205
|
ledgerEntries: result.ledgerEntries,
|
|
206
|
+
nextActions: result.nextActions,
|
|
207
|
+
warnings: result.warnings,
|
|
206
208
|
summary: summarizeUlwLoopPlan(result.plan),
|
|
207
209
|
});
|
|
208
210
|
} else {
|