lazycodex-ai 5.0.0-beta.63 → 5.0.0-beta.64
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 +21 -21
- package/dist/cli-node/index.js +21 -21
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +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/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 +7 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +7 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +364 -135
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +20 -12
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.d.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.js +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.js +37 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.js +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +4 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +6 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.d.ts +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.js +38 -31
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +8 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +17 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +335 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +109 -10
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-types.d.ts +9 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +6 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.js +41 -0
- 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 +15 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/driver-objective-ack.ts +20 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence-artifacts.ts +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +9 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts +63 -31
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +8 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +17 -7
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +124 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-types.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +7 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/success-criteria-input.ts +56 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +37 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-driver-states.test.ts +33 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-dx.test.ts +219 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/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 +4 -4
|
@@ -8,12 +8,18 @@ export interface CodexGoalSnapshot {
|
|
|
8
8
|
export interface CodexGoalReconciliation {
|
|
9
9
|
ok: boolean;
|
|
10
10
|
snapshot: CodexGoalSnapshot;
|
|
11
|
+
/** Facts to know (a differing driver objective); never something to do. */
|
|
11
12
|
warnings: string[];
|
|
13
|
+
/** Things to do next (create, re-create, or resume the driver goal). */
|
|
14
|
+
nextActions: string[];
|
|
12
15
|
errors: string[];
|
|
16
|
+
/** The differing objective this reconciliation reported for the first time, for the caller to acknowledge. */
|
|
17
|
+
unacknowledgedObjective?: string;
|
|
13
18
|
}
|
|
14
19
|
export interface ReconcileCodexGoalOptions {
|
|
15
20
|
expectedObjective: string;
|
|
16
21
|
readonly acceptedObjectives?: readonly string[];
|
|
22
|
+
readonly acknowledgedObjectives?: readonly string[];
|
|
17
23
|
}
|
|
18
24
|
export declare class CodexGoalSnapshotError extends Error {
|
|
19
25
|
}
|
|
@@ -73,29 +73,37 @@ export function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
|
73
73
|
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
74
74
|
const errors = [];
|
|
75
75
|
const warnings = [];
|
|
76
|
+
const nextActions = [];
|
|
76
77
|
const expected = options.expectedObjective;
|
|
77
|
-
const normalizedExpected = normalizeObjective(expected);
|
|
78
78
|
if (!effectiveSnapshot.available) {
|
|
79
|
-
|
|
80
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
79
|
+
nextActions.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
80
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, nextActions, errors };
|
|
81
81
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
].filter(Boolean));
|
|
82
|
+
const normalized = (objectives) => new Set(objectives.map(normalizeObjective).filter(Boolean));
|
|
83
|
+
const accepted = normalized([expected, ...(options.acceptedObjectives ?? [])]);
|
|
84
|
+
const acknowledged = normalized(options.acknowledgedObjectives ?? []);
|
|
86
85
|
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
87
|
-
|
|
86
|
+
let unacknowledgedObjective;
|
|
87
|
+
if (actual && !accepted.has(actual) && !acknowledged.has(actual)) {
|
|
88
88
|
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
89
|
+
unacknowledgedObjective = actual;
|
|
89
90
|
}
|
|
90
91
|
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
91
92
|
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
92
|
-
|
|
93
|
+
nextActions.push("/goal resume or raise the budget");
|
|
93
94
|
}
|
|
94
95
|
if (actualStatus === "complete")
|
|
95
|
-
|
|
96
|
-
return {
|
|
96
|
+
nextActions.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
97
|
+
return {
|
|
98
|
+
ok: errors.length === 0,
|
|
99
|
+
snapshot: effectiveSnapshot,
|
|
100
|
+
warnings,
|
|
101
|
+
nextActions,
|
|
102
|
+
errors,
|
|
103
|
+
...(unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective }),
|
|
104
|
+
};
|
|
97
105
|
}
|
|
98
106
|
export function formatCodexGoalReconciliation(reconciliation) {
|
|
99
|
-
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
107
|
+
const parts = [...reconciliation.errors, ...reconciliation.nextActions, ...reconciliation.warnings];
|
|
100
108
|
return parts.join(" ");
|
|
101
109
|
}
|
|
@@ -10,6 +10,7 @@ export interface UlwLoopSuccessCriterion {
|
|
|
10
10
|
status: UlwLoopCriterionStatus;
|
|
11
11
|
capturedAt?: string;
|
|
12
12
|
notes?: string;
|
|
13
|
+
artifacts?: string[];
|
|
13
14
|
}
|
|
14
15
|
export interface UlwLoopItem {
|
|
15
16
|
id: string;
|
|
@@ -62,6 +63,7 @@ export interface UlwLoopPlan {
|
|
|
62
63
|
codexGoalMode?: UlwLoopCodexGoalMode;
|
|
63
64
|
codexObjective?: string;
|
|
64
65
|
codexObjectiveAliases?: string[];
|
|
66
|
+
acknowledgedDriverObjectives?: string[];
|
|
65
67
|
aggregateCompletion?: UlwLoopAggregateCompletion;
|
|
66
68
|
activeGoalId?: string;
|
|
67
69
|
validationBatches?: readonly UlwLoopValidationBatch[];
|
|
@@ -151,6 +153,7 @@ export interface UlwLoopLedgerEntry {
|
|
|
151
153
|
codexGoal?: unknown;
|
|
152
154
|
evidence?: string;
|
|
153
155
|
capturedEvidence?: string;
|
|
156
|
+
artifacts?: string[];
|
|
154
157
|
qualityGate?: unknown;
|
|
155
158
|
steering?: UlwLoopSteeringAudit;
|
|
156
159
|
before?: unknown;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UlwLoopPlan } from "./types.js";
|
|
2
|
+
export declare function normalizeDriverObjective(value: string): string;
|
|
3
|
+
export declare function acknowledgedDriverObjectives(plan: UlwLoopPlan): readonly string[];
|
|
4
|
+
export declare function acknowledgeDriverObjective(plan: UlwLoopPlan, objective: string | undefined): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function normalizeDriverObjective(value) {
|
|
2
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3
|
+
}
|
|
4
|
+
export function acknowledgedDriverObjectives(plan) {
|
|
5
|
+
return plan.acknowledgedDriverObjectives ?? [];
|
|
6
|
+
}
|
|
7
|
+
// A differing driver objective is reported once; the plan remembers the exact text so every later
|
|
8
|
+
// checkpoint under the same driver stays quiet, while a driver rewritten to a third objective is
|
|
9
|
+
// reported again.
|
|
10
|
+
export function acknowledgeDriverObjective(plan, objective) {
|
|
11
|
+
if (objective === undefined)
|
|
12
|
+
return false;
|
|
13
|
+
const normalized = normalizeDriverObjective(objective);
|
|
14
|
+
if (!normalized || acknowledgedDriverObjectives(plan).includes(normalized))
|
|
15
|
+
return false;
|
|
16
|
+
plan.acknowledgedDriverObjectives = [...acknowledgedDriverObjectives(plan), normalized];
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveEvidenceArtifacts(repoRoot: string, artifacts: readonly unknown[] | undefined): string[] | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { UlwLoopError } from "./types.js";
|
|
4
|
+
function fail(message, code, details) {
|
|
5
|
+
throw new UlwLoopError(message, code, { details });
|
|
6
|
+
}
|
|
7
|
+
function stored(repoRoot, absolute) {
|
|
8
|
+
const rel = relative(repoRoot, absolute);
|
|
9
|
+
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
10
|
+
return inside ? rel.split(sep).join("/") : absolute;
|
|
11
|
+
}
|
|
12
|
+
// Artifacts are bound at record time: a cited path that does not exist is a claim without a file,
|
|
13
|
+
// so the whole record is refused before any state changes. Paths inside the session cwd persist
|
|
14
|
+
// repo-relative (portable across machines); paths outside persist absolute.
|
|
15
|
+
export function resolveEvidenceArtifacts(repoRoot, artifacts) {
|
|
16
|
+
if (artifacts === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const resolved = [];
|
|
20
|
+
for (const [index, candidate] of artifacts.entries()) {
|
|
21
|
+
const path = typeof candidate === "string" ? candidate.trim() : "";
|
|
22
|
+
if (!path)
|
|
23
|
+
fail(`Artifact ${index + 1} must be a non-empty path.`, "ULW_LOOP_ARGUMENT_INVALID", { index });
|
|
24
|
+
const absolute = resolve(repoRoot, path);
|
|
25
|
+
if (!existsSync(absolute))
|
|
26
|
+
fail(`Evidence artifact does not exist: ${path} (resolved to ${absolute}).`, "ULW_LOOP_EVIDENCE_ARTIFACT_MISSING", {
|
|
27
|
+
path,
|
|
28
|
+
resolved: absolute,
|
|
29
|
+
});
|
|
30
|
+
const value = stored(repoRoot, absolute);
|
|
31
|
+
if (!seen.has(value)) {
|
|
32
|
+
seen.add(value);
|
|
33
|
+
resolved.push(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
@@ -7,6 +7,7 @@ type RecordEvidenceArgs = {
|
|
|
7
7
|
readonly status: EvidenceStatus;
|
|
8
8
|
readonly evidence: string;
|
|
9
9
|
readonly notes?: string;
|
|
10
|
+
readonly artifacts?: readonly string[];
|
|
10
11
|
};
|
|
11
12
|
export declare function recordEvidence(repoRoot: string, args: RecordEvidenceArgs, scope?: UlwLoopScope): Promise<{
|
|
12
13
|
plan: UlwLoopPlan;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveEvidenceArtifacts } from "./evidence-artifacts.js";
|
|
1
2
|
import { essentialCriteriaOf, hasAllCriteriaPass, hasEssentialCriteriaPass } from "./goal-status.js";
|
|
2
3
|
import { commit } from "./plan-commit.js";
|
|
3
4
|
import { readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
|
|
@@ -39,6 +40,7 @@ export async function recordEvidence(repoRoot, args, scope) {
|
|
|
39
40
|
const goal = findGoal(plan, args.goalId);
|
|
40
41
|
const criterion = findCriterion(goal, args.criterionId);
|
|
41
42
|
const evidence = nonEmptyEvidence(args.evidence);
|
|
43
|
+
const artifacts = resolveEvidenceArtifacts(repoRoot, args.artifacts);
|
|
42
44
|
const kind = ledgerKind(args.status);
|
|
43
45
|
const prevStatus = criterion.status;
|
|
44
46
|
const capturedAt = iso();
|
|
@@ -47,6 +49,10 @@ export async function recordEvidence(repoRoot, args, scope) {
|
|
|
47
49
|
criterion.capturedAt = capturedAt;
|
|
48
50
|
if (args.notes !== undefined)
|
|
49
51
|
criterion.notes = args.notes;
|
|
52
|
+
if (artifacts !== undefined)
|
|
53
|
+
criterion.artifacts = artifacts;
|
|
54
|
+
else
|
|
55
|
+
delete criterion.artifacts;
|
|
50
56
|
goal.updatedAt = capturedAt;
|
|
51
57
|
plan.updatedAt = capturedAt;
|
|
52
58
|
const ledgerEntry = {
|
|
@@ -57,6 +63,7 @@ export async function recordEvidence(repoRoot, args, scope) {
|
|
|
57
63
|
criterionStatus: args.status,
|
|
58
64
|
evidence,
|
|
59
65
|
capturedEvidence: evidence,
|
|
66
|
+
...(artifacts === undefined ? {} : { artifacts }),
|
|
60
67
|
before: { status: prevStatus },
|
|
61
68
|
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus },
|
|
62
69
|
};
|
|
@@ -80,6 +87,7 @@ export async function markCriteriaPendingResetForGoal(repoRoot, goalId, scope) {
|
|
|
80
87
|
criterion.capturedEvidence = null;
|
|
81
88
|
delete criterion.capturedAt;
|
|
82
89
|
delete criterion.notes;
|
|
90
|
+
delete criterion.artifacts;
|
|
83
91
|
}
|
|
84
92
|
goal.updatedAt = now;
|
|
85
93
|
plan.updatedAt = now;
|
|
@@ -14,6 +14,7 @@ export declare function ulwLoopGoalsPath(repoRoot: string, scope?: UlwLoopScope)
|
|
|
14
14
|
export declare function ulwLoopLedgerPath(repoRoot: string, scope?: UlwLoopScope): string;
|
|
15
15
|
export declare function ulwLoopStateLockPath(repoRoot: string, scope?: UlwLoopScope): string;
|
|
16
16
|
export declare function repoRelative(absolutePath: string, repoRoot: string): string;
|
|
17
|
+
export declare function ulwLoopEvidenceRoot(scope?: UlwLoopScope): string;
|
|
17
18
|
export declare function ulwLoopAttemptEvidenceDir(goalId: string, attempt: number, scope?: UlwLoopScope): string;
|
|
18
19
|
interface AttemptPathApi {
|
|
19
20
|
relative(from: string, to: string): string;
|
|
@@ -62,6 +62,12 @@ export function repoRelative(absolutePath, repoRoot) {
|
|
|
62
62
|
return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
|
|
63
63
|
return absolutePath.split("\\").join("/");
|
|
64
64
|
}
|
|
65
|
+
// The plan-level evidence root stays fixed for the whole run, unlike the per-goal attempt dir, so
|
|
66
|
+
// long lanes that span goals have one stable place to write artifacts.
|
|
67
|
+
export function ulwLoopEvidenceRoot(scope) {
|
|
68
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
69
|
+
return sessionId === null ? ".omo/evidence" : `.omo/evidence/ulw/${sessionId}`;
|
|
70
|
+
}
|
|
65
71
|
// Both the status --json emitter and the checkpoint enforcement resolve the attempt dir through
|
|
66
72
|
// this function from the scope alone; a second resolution path (env, a literal placeholder)
|
|
67
73
|
// would let the gate reject its own advertised directory.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type UlwLoopScope } from "./paths.js";
|
|
2
|
+
import type { SuccessCriterionInput } from "./success-criteria-input.js";
|
|
2
3
|
import type { UlwLoopToolkitSurface } from "./surface.js";
|
|
3
4
|
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
4
5
|
export { deriveGoalCandidates, seedDefaultSuccessCriteria } from "./plan-goal-factory.js";
|
|
6
|
+
export type { SuccessCriterionInput } from "./success-criteria-input.js";
|
|
5
7
|
export type UlwLoopPlanSummary = {
|
|
6
8
|
readonly total: number;
|
|
7
9
|
readonly pending: number;
|
|
@@ -29,7 +31,8 @@ export declare function createUlwLoopPlan(repoRoot: string, args: {
|
|
|
29
31
|
export declare function addUlwLoopGoal(repoRoot: string, args: {
|
|
30
32
|
title: string;
|
|
31
33
|
objective: string;
|
|
32
|
-
|
|
34
|
+
successCriteria?: readonly SuccessCriterionInput[];
|
|
35
|
+
}, scope?: UlwLoopScope, surface?: UlwLoopToolkitSurface): Promise<{
|
|
33
36
|
plan: UlwLoopPlan;
|
|
34
37
|
goal: UlwLoopItem;
|
|
35
38
|
}>;
|
|
@@ -39,7 +39,7 @@ export async function createUlwLoopPlan(repoRoot, args, scope, surface = "lazyco
|
|
|
39
39
|
throw new UlwLoopError(`Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
|
|
40
40
|
}
|
|
41
41
|
const now = iso();
|
|
42
|
-
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
|
|
42
|
+
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now, { surface }));
|
|
43
43
|
const plan = {
|
|
44
44
|
version: 1,
|
|
45
45
|
revision: existing?.revision ?? 0,
|
|
@@ -80,11 +80,14 @@ function completedPlanExistsError(scope, surface) {
|
|
|
80
80
|
]),
|
|
81
81
|
].join(" "), "ULW_LOOP_PLAN_EXISTS_COMPLETE");
|
|
82
82
|
}
|
|
83
|
-
export async function addUlwLoopGoal(repoRoot, args, scope) {
|
|
83
|
+
export async function addUlwLoopGoal(repoRoot, args, scope, surface = "lazycodex") {
|
|
84
84
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
85
85
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
86
86
|
const now = iso();
|
|
87
|
-
const goal = appendGoalToPlan(plan, args.title, args.objective, now
|
|
87
|
+
const goal = appendGoalToPlan(plan, args.title, args.objective, now, {
|
|
88
|
+
surface,
|
|
89
|
+
...(args.successCriteria === undefined ? {} : { successCriteria: args.successCriteria }),
|
|
90
|
+
});
|
|
88
91
|
await commit(repoRoot, scope, {
|
|
89
92
|
plan,
|
|
90
93
|
entries: [{ at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }],
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { type SuccessCriterionInput } from "./success-criteria-input.js";
|
|
2
|
+
import type { UlwLoopToolkitSurface } from "./surface.js";
|
|
1
3
|
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
|
|
2
|
-
export
|
|
4
|
+
export interface GoalSeedOptions {
|
|
5
|
+
readonly goalId?: string;
|
|
6
|
+
readonly surface?: UlwLoopToolkitSurface;
|
|
7
|
+
readonly successCriteria?: readonly SuccessCriterionInput[];
|
|
8
|
+
}
|
|
9
|
+
export declare function seedDefaultSuccessCriteria(goalIndex: number, objective: string, options?: Pick<GoalSeedOptions, "goalId" | "surface">): UlwLoopSuccessCriterion[];
|
|
3
10
|
export declare function deriveGoalCandidates(brief: string): Array<{
|
|
4
11
|
title: string;
|
|
5
12
|
objective: string;
|
|
6
13
|
}>;
|
|
7
|
-
export declare function makeGoal(title: string, objective: string, index: number, now: string): UlwLoopItem;
|
|
8
|
-
export declare function appendGoalToPlan(plan: UlwLoopPlan, title: string, objective: string, now: string): UlwLoopItem;
|
|
14
|
+
export declare function makeGoal(title: string, objective: string, index: number, now: string, options?: GoalSeedOptions): UlwLoopItem;
|
|
15
|
+
export declare function appendGoalToPlan(plan: UlwLoopPlan, title: string, objective: string, now: string, options?: GoalSeedOptions): UlwLoopItem;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { criteriaFromInput, criterionId } from "./success-criteria-input.js";
|
|
1
2
|
import { UlwLoopError } from "./types.js";
|
|
2
3
|
function cleanLine(line) {
|
|
3
4
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
@@ -30,40 +31,39 @@ function assertNonEmpty(value, label) {
|
|
|
30
31
|
function truncateObjective(objective) {
|
|
31
32
|
return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
// The placeholder must name the exact call that replaces it on the surface the plan is driven from,
|
|
35
|
+
// so a goal seeded without criteria costs one documented call per criterion instead of a doc hunt.
|
|
36
|
+
function replaceVia(goalId, id, surface) {
|
|
37
|
+
return surface === "omo-senpi"
|
|
38
|
+
? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)`
|
|
39
|
+
: `Replace via omo-agent-toolkit ulw-loop steer --kind revise_criterion --goal-id ${goalId} --criterion-id ${id} --scenario "<scenario>" --expected-evidence "<proof>" --evidence "<why>" --rationale "<why>"`;
|
|
40
|
+
}
|
|
41
|
+
export function seedDefaultSuccessCriteria(goalIndex, objective, options = {}) {
|
|
34
42
|
const subject = truncateObjective(normalizeObjective(objective) || `Goal ${goalIndex + 1}`);
|
|
43
|
+
const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
|
|
44
|
+
const surface = options.surface ?? "lazycodex";
|
|
35
45
|
const rows = [
|
|
46
|
+
["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
|
|
47
|
+
["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
|
|
36
48
|
[
|
|
37
|
-
"C001",
|
|
38
|
-
"happy",
|
|
39
|
-
`happy path for: ${subject}`,
|
|
40
|
-
`Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
|
|
41
|
-
true,
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
"C002",
|
|
45
|
-
"edge",
|
|
46
|
-
"edge case (boundary/empty/malformed)",
|
|
47
|
-
`Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
|
|
48
|
-
true,
|
|
49
|
-
],
|
|
50
|
-
[
|
|
51
|
-
"C003",
|
|
52
49
|
"regression",
|
|
53
50
|
"regression: adjacent surface still works",
|
|
54
|
-
`
|
|
51
|
+
`regression proof for neighboring behavior after: ${subject}`,
|
|
55
52
|
false,
|
|
56
53
|
],
|
|
57
54
|
];
|
|
58
|
-
return rows.map(([
|
|
59
|
-
id
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
return rows.map(([userModel, scenario, proof, essential], index) => {
|
|
56
|
+
const id = criterionId(index);
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
scenario,
|
|
60
|
+
userModel,
|
|
61
|
+
expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
|
|
62
|
+
essential,
|
|
63
|
+
capturedEvidence: null,
|
|
64
|
+
status: "pending",
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
67
|
}
|
|
68
68
|
export function deriveGoalCandidates(brief) {
|
|
69
69
|
const bulletGoals = brief
|
|
@@ -87,22 +87,29 @@ export function deriveGoalCandidates(brief) {
|
|
|
87
87
|
objective,
|
|
88
88
|
}));
|
|
89
89
|
}
|
|
90
|
-
export function makeGoal(title, objective, index, now) {
|
|
90
|
+
export function makeGoal(title, objective, index, now, options = {}) {
|
|
91
91
|
const cleanTitle = assertNonEmpty(title, "title");
|
|
92
92
|
const cleanObjective = assertNonEmpty(objective, "objective");
|
|
93
|
+
const id = normalizeGoalId(cleanTitle, index);
|
|
94
|
+
const successCriteria = options.successCriteria === undefined
|
|
95
|
+
? seedDefaultSuccessCriteria(index, cleanObjective, {
|
|
96
|
+
goalId: id,
|
|
97
|
+
...(options.surface === undefined ? {} : { surface: options.surface }),
|
|
98
|
+
})
|
|
99
|
+
: criteriaFromInput(options.successCriteria);
|
|
93
100
|
return {
|
|
94
|
-
id
|
|
101
|
+
id,
|
|
95
102
|
title: cleanTitle,
|
|
96
103
|
objective: cleanObjective,
|
|
97
104
|
status: "pending",
|
|
98
|
-
successCriteria
|
|
105
|
+
successCriteria,
|
|
99
106
|
attempt: 0,
|
|
100
107
|
createdAt: now,
|
|
101
108
|
updatedAt: now,
|
|
102
109
|
};
|
|
103
110
|
}
|
|
104
|
-
export function appendGoalToPlan(plan, title, objective, now) {
|
|
105
|
-
const goal = makeGoal(title, objective, plan.goals.length, now);
|
|
111
|
+
export function appendGoalToPlan(plan, title, objective, now, options = {}) {
|
|
112
|
+
const goal = makeGoal(title, objective, plan.goals.length, now, options);
|
|
106
113
|
plan.goals.push(goal);
|
|
107
114
|
plan.updatedAt = now;
|
|
108
115
|
return goal;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// biome-ignore-all format: compact port must stay within the requested pure LOC budget.
|
|
2
2
|
import { CodexGoalSnapshotError, formatCodexGoalReconciliation, readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot, } from "./codex-goal-snapshot.js";
|
|
3
|
+
import { acknowledgeDriverObjective, acknowledgedDriverObjectives } from "./driver-objective-ack.js";
|
|
3
4
|
import { compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
4
5
|
import { commit } from "./plan-commit.js";
|
|
5
6
|
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
|
|
@@ -18,12 +19,13 @@ function nextGoalId(plan) {
|
|
|
18
19
|
}
|
|
19
20
|
function appendBlockerGoal(plan, args, now) {
|
|
20
21
|
const index = plan.goals.length;
|
|
22
|
+
const id = nextGoalId(plan);
|
|
21
23
|
const goal = {
|
|
22
|
-
id
|
|
24
|
+
id,
|
|
23
25
|
title: args.title,
|
|
24
26
|
objective: args.objective,
|
|
25
27
|
status: "pending",
|
|
26
|
-
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
|
|
28
|
+
successCriteria: seedDefaultSuccessCriteria(index, args.objective, { goalId: id }),
|
|
27
29
|
attempt: 0,
|
|
28
30
|
createdAt: now,
|
|
29
31
|
updatedAt: now,
|
|
@@ -45,9 +47,11 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
45
47
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
46
48
|
expectedObjective: expectedCodexObjective(plan, goal),
|
|
47
49
|
acceptedObjectives: compatibleCodexObjectives(plan),
|
|
50
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(plan),
|
|
48
51
|
});
|
|
49
52
|
if (!reconciliation.ok)
|
|
50
53
|
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
54
|
+
acknowledgeDriverObjective(plan, reconciliation.unacknowledgedObjective);
|
|
51
55
|
const now = iso();
|
|
52
56
|
for (const field of BLOCKER_FIELDS)
|
|
53
57
|
Reflect.deleteProperty(goal, field);
|
|
@@ -71,8 +75,8 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
71
75
|
blockedGoal: goal,
|
|
72
76
|
newGoal,
|
|
73
77
|
ledgerEntries,
|
|
74
|
-
nextActions: reconciliation.
|
|
75
|
-
warnings: reconciliation.warnings
|
|
78
|
+
nextActions: reconciliation.nextActions,
|
|
79
|
+
warnings: reconciliation.warnings,
|
|
76
80
|
};
|
|
77
81
|
});
|
|
78
82
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { checkpointUlwLoop } from "../checkpoint.js";
|
|
2
2
|
import { checkpointTemplate } from "../checkpoint-template.js";
|
|
3
3
|
import { recordEvidence } from "../evidence.js";
|
|
4
|
-
import { normalizeUlwLoopSessionId, ulwLoopAttemptEvidenceDir, ulwLoopGoalsRelativePath, } from "../paths.js";
|
|
4
|
+
import { normalizeUlwLoopSessionId, ulwLoopAttemptEvidenceDir, ulwLoopEvidenceRoot, ulwLoopGoalsRelativePath, } from "../paths.js";
|
|
5
5
|
import { addUlwLoopGoal, createUlwLoopPlan, startNextUlwLoop, summarizeUlwLoopPlan } from "../plan-crud.js";
|
|
6
6
|
import { listUlwLoopSessionIds, readUlwLoopPlan } from "../plan-io.js";
|
|
7
7
|
import { planMissingError } from "../plan-missing-recovery.js";
|
|
@@ -54,10 +54,11 @@ function isKnownRequest(request) {
|
|
|
54
54
|
function unreachable(value) {
|
|
55
55
|
throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
|
|
56
56
|
}
|
|
57
|
-
function
|
|
58
|
-
if (!(
|
|
57
|
+
function stringsFrom(result, key) {
|
|
58
|
+
if (!(key in result))
|
|
59
59
|
return [];
|
|
60
|
-
|
|
60
|
+
const value = Object.entries(result).find(([name]) => name === key)?.[1];
|
|
61
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string").slice(0, 8) : [];
|
|
61
62
|
}
|
|
62
63
|
function checkpointWithValidatedSnapshot(context, scope, args) {
|
|
63
64
|
validateCodexGoalJson(args.codexGoalJson);
|
|
@@ -74,8 +75,15 @@ export function createAgentToolkit(context, deps = {}) {
|
|
|
74
75
|
const invoke = async (operation, fn) => {
|
|
75
76
|
try {
|
|
76
77
|
const result = await fn();
|
|
77
|
-
const nextActions = typeof result === "object" && result !== null ?
|
|
78
|
-
|
|
78
|
+
const nextActions = typeof result === "object" && result !== null ? stringsFrom(result, "nextActions") : [];
|
|
79
|
+
const warnings = typeof result === "object" && result !== null ? stringsFrom(result, "warnings") : [];
|
|
80
|
+
return await notify(operation, {
|
|
81
|
+
ok: true,
|
|
82
|
+
operation,
|
|
83
|
+
result,
|
|
84
|
+
nextActions,
|
|
85
|
+
...(warnings.length === 0 ? {} : { warnings }),
|
|
86
|
+
});
|
|
79
87
|
}
|
|
80
88
|
catch (error) {
|
|
81
89
|
if (error instanceof UlwLoopError && error.code === "ULW_LOOP_PLAN_MISSING" && context.surface === "omo-senpi")
|
|
@@ -122,6 +130,7 @@ export function createAgentToolkit(context, deps = {}) {
|
|
|
122
130
|
plan,
|
|
123
131
|
summary: summarizeUlwLoopPlan(plan),
|
|
124
132
|
nextActions: statusNextActions(plan, context.surface),
|
|
133
|
+
evidenceRoot: ulwLoopEvidenceRoot(scope),
|
|
125
134
|
// Attempt directories are an evidence-layout v2 concept; a v1 plan must not advertise one.
|
|
126
135
|
...(active === undefined || plan.evidenceLayoutVersion !== 2
|
|
127
136
|
? {}
|
|
@@ -132,8 +141,8 @@ export function createAgentToolkit(context, deps = {}) {
|
|
|
132
141
|
checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true
|
|
133
142
|
? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface })
|
|
134
143
|
: checkpointWithValidatedSnapshot(context, scope, args)),
|
|
135
|
-
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
|
|
136
|
-
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
|
|
144
|
+
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope, context.surface)),
|
|
145
|
+
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope, context.surface)),
|
|
137
146
|
criteria: (args) => invoke("criteria", async () => {
|
|
138
147
|
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
139
148
|
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
|