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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// components/ulw-loop/src/checkpoint.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "node:fs";
|
|
5
5
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
6
|
-
import { resolve as
|
|
6
|
+
import { resolve as resolve4 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// components/ulw-loop/src/codex-goal-snapshot.ts
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
@@ -80,33 +80,58 @@ function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
|
80
80
|
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
81
81
|
const errors = [];
|
|
82
82
|
const warnings = [];
|
|
83
|
+
const nextActions = [];
|
|
83
84
|
const expected = options.expectedObjective;
|
|
84
|
-
const normalizedExpected = normalizeObjective(expected);
|
|
85
85
|
if (!effectiveSnapshot.available) {
|
|
86
|
-
|
|
87
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
86
|
+
nextActions.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
87
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, nextActions, errors };
|
|
88
88
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
].filter(Boolean));
|
|
89
|
+
const normalized = (objectives) => new Set(objectives.map(normalizeObjective).filter(Boolean));
|
|
90
|
+
const accepted = normalized([expected, ...options.acceptedObjectives ?? []]);
|
|
91
|
+
const acknowledged = normalized(options.acknowledgedObjectives ?? []);
|
|
93
92
|
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
94
|
-
|
|
93
|
+
let unacknowledgedObjective;
|
|
94
|
+
if (actual && !accepted.has(actual) && !acknowledged.has(actual)) {
|
|
95
95
|
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
96
|
+
unacknowledgedObjective = actual;
|
|
96
97
|
}
|
|
97
98
|
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
98
99
|
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
99
|
-
|
|
100
|
+
nextActions.push("/goal resume or raise the budget");
|
|
100
101
|
}
|
|
101
102
|
if (actualStatus === "complete")
|
|
102
|
-
|
|
103
|
-
return {
|
|
103
|
+
nextActions.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
104
|
+
return {
|
|
105
|
+
ok: errors.length === 0,
|
|
106
|
+
snapshot: effectiveSnapshot,
|
|
107
|
+
warnings,
|
|
108
|
+
nextActions,
|
|
109
|
+
errors,
|
|
110
|
+
...unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective }
|
|
111
|
+
};
|
|
104
112
|
}
|
|
105
113
|
function formatCodexGoalReconciliation(reconciliation) {
|
|
106
|
-
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
114
|
+
const parts = [...reconciliation.errors, ...reconciliation.nextActions, ...reconciliation.warnings];
|
|
107
115
|
return parts.join(" ");
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
// components/ulw-loop/src/driver-objective-ack.ts
|
|
119
|
+
function normalizeDriverObjective(value) {
|
|
120
|
+
return value.replace(/\s+/g, " ").trim();
|
|
121
|
+
}
|
|
122
|
+
function acknowledgedDriverObjectives(plan) {
|
|
123
|
+
return plan.acknowledgedDriverObjectives ?? [];
|
|
124
|
+
}
|
|
125
|
+
function acknowledgeDriverObjective(plan, objective) {
|
|
126
|
+
if (objective === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
const normalized = normalizeDriverObjective(objective);
|
|
129
|
+
if (!normalized || acknowledgedDriverObjectives(plan).includes(normalized))
|
|
130
|
+
return false;
|
|
131
|
+
plan.acknowledgedDriverObjectives = [...acknowledgedDriverObjectives(plan), normalized];
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
110
135
|
// components/ulw-loop/src/paths.ts
|
|
111
136
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
112
137
|
// components/ulw-loop/src/constants.ts
|
|
@@ -193,6 +218,10 @@ function repoRelative(absolutePath, repoRoot) {
|
|
|
193
218
|
return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
|
|
194
219
|
return absolutePath.split("\\").join("/");
|
|
195
220
|
}
|
|
221
|
+
function ulwLoopEvidenceRoot(scope) {
|
|
222
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
223
|
+
return sessionId === null ? ".omo/evidence" : `.omo/evidence/ulw/${sessionId}`;
|
|
224
|
+
}
|
|
196
225
|
function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
|
|
197
226
|
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
198
227
|
if (sessionId === null) {
|
|
@@ -299,14 +328,16 @@ async function validateCheckpointCodexGoal(input) {
|
|
|
299
328
|
const expected = expectedCodexObjective(input.plan, input.goal);
|
|
300
329
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
301
330
|
expectedObjective: expected,
|
|
331
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(input.plan),
|
|
302
332
|
...codexGoalMode(input.plan) === "aggregate" ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}
|
|
303
333
|
});
|
|
304
334
|
if (!reconciliation.ok)
|
|
305
335
|
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
306
336
|
return {
|
|
307
337
|
raw: snapshot?.raw,
|
|
308
|
-
nextActions: reconciliation.
|
|
309
|
-
warnings: reconciliation.warnings
|
|
338
|
+
nextActions: reconciliation.nextActions,
|
|
339
|
+
warnings: reconciliation.warnings,
|
|
340
|
+
...reconciliation.unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective: reconciliation.unacknowledgedObjective }
|
|
310
341
|
};
|
|
311
342
|
}
|
|
312
343
|
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
@@ -316,6 +347,41 @@ ${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
|
316
347
|
});
|
|
317
348
|
}
|
|
318
349
|
|
|
350
|
+
// components/ulw-loop/src/evidence-artifacts.ts
|
|
351
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
352
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
353
|
+
function fail(message, code, details) {
|
|
354
|
+
throw new UlwLoopError(message, code, { details });
|
|
355
|
+
}
|
|
356
|
+
function stored(repoRoot, absolute) {
|
|
357
|
+
const rel = relative2(repoRoot, absolute);
|
|
358
|
+
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
359
|
+
return inside ? rel.split(sep2).join("/") : absolute;
|
|
360
|
+
}
|
|
361
|
+
function resolveEvidenceArtifacts(repoRoot, artifacts) {
|
|
362
|
+
if (artifacts === undefined)
|
|
363
|
+
return;
|
|
364
|
+
const seen = new Set;
|
|
365
|
+
const resolved = [];
|
|
366
|
+
for (const [index, candidate] of artifacts.entries()) {
|
|
367
|
+
const path = typeof candidate === "string" ? candidate.trim() : "";
|
|
368
|
+
if (!path)
|
|
369
|
+
fail(`Artifact ${index + 1} must be a non-empty path.`, "ULW_LOOP_ARGUMENT_INVALID", { index });
|
|
370
|
+
const absolute = resolve2(repoRoot, path);
|
|
371
|
+
if (!existsSync2(absolute))
|
|
372
|
+
fail(`Evidence artifact does not exist: ${path} (resolved to ${absolute}).`, "ULW_LOOP_EVIDENCE_ARTIFACT_MISSING", {
|
|
373
|
+
path,
|
|
374
|
+
resolved: absolute
|
|
375
|
+
});
|
|
376
|
+
const value = stored(repoRoot, absolute);
|
|
377
|
+
if (!seen.has(value)) {
|
|
378
|
+
seen.add(value);
|
|
379
|
+
resolved.push(value);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return resolved;
|
|
383
|
+
}
|
|
384
|
+
|
|
319
385
|
// components/ulw-loop/src/plan-commit.ts
|
|
320
386
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
321
387
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -327,7 +393,7 @@ import { join as join4 } from "node:path";
|
|
|
327
393
|
import { join as join3 } from "node:path";
|
|
328
394
|
|
|
329
395
|
// components/ulw-loop/src/plan-log.ts
|
|
330
|
-
import { existsSync as
|
|
396
|
+
import { existsSync as existsSync3, readdirSync, readFileSync } from "node:fs";
|
|
331
397
|
import { join as join2 } from "node:path";
|
|
332
398
|
function hasCode(error, code) {
|
|
333
399
|
return error instanceof Error && "code" in error && error.code === code;
|
|
@@ -394,7 +460,7 @@ function reconcilePlan(dir) {
|
|
|
394
460
|
}
|
|
395
461
|
function planExists(repoRoot, scope) {
|
|
396
462
|
const dir = ulwLoopDir(repoRoot, scope);
|
|
397
|
-
return
|
|
463
|
+
return existsSync3(join2(dir, "goals.json")) || logNames(dir).length > 0;
|
|
398
464
|
}
|
|
399
465
|
|
|
400
466
|
// components/ulw-loop/src/ledger.ts
|
|
@@ -965,6 +1031,7 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
965
1031
|
const goal = findGoal(plan, args.goalId);
|
|
966
1032
|
const criterion = findCriterion(goal, args.criterionId);
|
|
967
1033
|
const evidence = nonEmptyEvidence(args.evidence);
|
|
1034
|
+
const artifacts = resolveEvidenceArtifacts(repoRoot, args.artifacts);
|
|
968
1035
|
const kind = ledgerKind(args.status);
|
|
969
1036
|
const prevStatus = criterion.status;
|
|
970
1037
|
const capturedAt = iso();
|
|
@@ -973,6 +1040,10 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
973
1040
|
criterion.capturedAt = capturedAt;
|
|
974
1041
|
if (args.notes !== undefined)
|
|
975
1042
|
criterion.notes = args.notes;
|
|
1043
|
+
if (artifacts !== undefined)
|
|
1044
|
+
criterion.artifacts = artifacts;
|
|
1045
|
+
else
|
|
1046
|
+
delete criterion.artifacts;
|
|
976
1047
|
goal.updatedAt = capturedAt;
|
|
977
1048
|
plan.updatedAt = capturedAt;
|
|
978
1049
|
const ledgerEntry = {
|
|
@@ -983,6 +1054,7 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
983
1054
|
criterionStatus: args.status,
|
|
984
1055
|
evidence,
|
|
985
1056
|
capturedEvidence: evidence,
|
|
1057
|
+
...artifacts === undefined ? {} : { artifacts },
|
|
986
1058
|
before: { status: prevStatus },
|
|
987
1059
|
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus }
|
|
988
1060
|
};
|
|
@@ -1034,7 +1106,7 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
1034
1106
|
}
|
|
1035
1107
|
|
|
1036
1108
|
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
1037
|
-
import { resolve as
|
|
1109
|
+
import { resolve as resolve3 } from "node:path";
|
|
1038
1110
|
|
|
1039
1111
|
// components/ulw-loop/src/quality-gate-fields.ts
|
|
1040
1112
|
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
@@ -1173,14 +1245,14 @@ function artifactCompatible(surface, kind) {
|
|
|
1173
1245
|
function checkFile(path, field, opts) {
|
|
1174
1246
|
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
1175
1247
|
return;
|
|
1176
|
-
const absolute =
|
|
1248
|
+
const absolute = resolve3(opts.repoRoot, path);
|
|
1177
1249
|
if (!opts.fs.existsSync(absolute)) {
|
|
1178
1250
|
invalid(`${field} must point to an existing artifact.`, field);
|
|
1179
1251
|
return;
|
|
1180
1252
|
}
|
|
1181
1253
|
if (opts.fs.statSync(absolute).size <= 0)
|
|
1182
1254
|
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
1183
|
-
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute,
|
|
1255
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve3(opts.repoRoot, opts.currentAttemptDir)))
|
|
1184
1256
|
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
1185
1257
|
}
|
|
1186
1258
|
function artifactMap(refs) {
|
|
@@ -1256,7 +1328,7 @@ function adversarialVerdict(row, field) {
|
|
|
1256
1328
|
}
|
|
1257
1329
|
|
|
1258
1330
|
// components/ulw-loop/src/surface.ts
|
|
1259
|
-
import { existsSync as
|
|
1331
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
1260
1332
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
1261
1333
|
import { fileURLToPath } from "node:url";
|
|
1262
1334
|
var REVIEWER_ROLES_BY_SURFACE = {
|
|
@@ -1316,7 +1388,7 @@ function resolveToolkitSurface(options) {
|
|
|
1316
1388
|
}
|
|
1317
1389
|
function readMarkerSurface(markerPath) {
|
|
1318
1390
|
try {
|
|
1319
|
-
if (!
|
|
1391
|
+
if (!existsSync4(markerPath))
|
|
1320
1392
|
return null;
|
|
1321
1393
|
const parsed = JSON.parse(readFileSync3(markerPath, "utf8"));
|
|
1322
1394
|
return parseSurface(parsed["surface"]);
|
|
@@ -1629,23 +1701,23 @@ async function parseValidationBatches(input, goals) {
|
|
|
1629
1701
|
if (raw === undefined)
|
|
1630
1702
|
return;
|
|
1631
1703
|
if (!Array.isArray(raw))
|
|
1632
|
-
|
|
1704
|
+
fail2("--validation-batch-json must be a JSON array.");
|
|
1633
1705
|
const batches = raw.map(batchFromObject);
|
|
1634
1706
|
validateBatches(batches, goals);
|
|
1635
1707
|
return batches;
|
|
1636
1708
|
}
|
|
1637
1709
|
function batchFromObject(value) {
|
|
1638
1710
|
if (!isObject(value))
|
|
1639
|
-
|
|
1711
|
+
fail2("validation batch entries must be objects.");
|
|
1640
1712
|
const batchId = text(value, "batchId");
|
|
1641
1713
|
const memberIds = strings(value, "memberIds");
|
|
1642
1714
|
const finalGoalId = text(value, "finalGoalId");
|
|
1643
1715
|
if (batchId === undefined)
|
|
1644
|
-
|
|
1716
|
+
fail2("validation batch requires batchId.");
|
|
1645
1717
|
if (memberIds === undefined || memberIds.length < 2)
|
|
1646
|
-
|
|
1718
|
+
fail2("validation batch requires at least two memberIds.");
|
|
1647
1719
|
if (finalGoalId === undefined)
|
|
1648
|
-
|
|
1720
|
+
fail2("validation batch requires finalGoalId.");
|
|
1649
1721
|
return { batchId, memberIds, finalGoalId };
|
|
1650
1722
|
}
|
|
1651
1723
|
function validateBatches(batches, goals) {
|
|
@@ -1654,17 +1726,17 @@ function validateBatches(batches, goals) {
|
|
|
1654
1726
|
const members = new Set;
|
|
1655
1727
|
for (const batch of batches) {
|
|
1656
1728
|
if (batchIds.has(batch.batchId))
|
|
1657
|
-
|
|
1729
|
+
fail2(`duplicate validation batch id: ${batch.batchId}.`);
|
|
1658
1730
|
batchIds.add(batch.batchId);
|
|
1659
1731
|
if (new Set(batch.memberIds).size !== batch.memberIds.length)
|
|
1660
|
-
|
|
1732
|
+
fail2(`validation batch ${batch.batchId} has duplicate memberIds.`);
|
|
1661
1733
|
if (!batch.memberIds.includes(batch.finalGoalId))
|
|
1662
|
-
|
|
1734
|
+
fail2(`validation batch ${batch.batchId} finalGoalId must be a member.`, "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
|
|
1663
1735
|
for (const memberId of batch.memberIds) {
|
|
1664
1736
|
if (!goalIds.has(memberId))
|
|
1665
|
-
|
|
1737
|
+
fail2(`validation batch ${batch.batchId} references unknown goal: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN");
|
|
1666
1738
|
if (members.has(memberId))
|
|
1667
|
-
|
|
1739
|
+
fail2(`goal appears in multiple validation batches: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_OVERLAP");
|
|
1668
1740
|
members.add(memberId);
|
|
1669
1741
|
}
|
|
1670
1742
|
}
|
|
@@ -1723,12 +1795,12 @@ function memberResolved(plan, goalId) {
|
|
|
1723
1795
|
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
|
1724
1796
|
return goal !== undefined && isMemberResolved(goal, plan);
|
|
1725
1797
|
}
|
|
1726
|
-
function
|
|
1798
|
+
function fail2(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
1727
1799
|
throw new UlwLoopError(message, code);
|
|
1728
1800
|
}
|
|
1729
1801
|
|
|
1730
1802
|
// components/ulw-loop/src/checkpoint.ts
|
|
1731
|
-
var QUALITY_GATE_FS = { existsSync:
|
|
1803
|
+
var QUALITY_GATE_FS = { existsSync: existsSync5, statSync: statSync2 };
|
|
1732
1804
|
function ulwLoopFail2(message, code) {
|
|
1733
1805
|
throw new UlwLoopError(message, code);
|
|
1734
1806
|
}
|
|
@@ -1750,8 +1822,8 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1750
1822
|
if (!(error instanceof SyntaxError))
|
|
1751
1823
|
throw error;
|
|
1752
1824
|
}
|
|
1753
|
-
const path =
|
|
1754
|
-
if (!
|
|
1825
|
+
const path = resolve4(repoRoot, trimmed);
|
|
1826
|
+
if (!existsSync5(path))
|
|
1755
1827
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1756
1828
|
try {
|
|
1757
1829
|
return JSON.parse(await readFile3(path, "utf8"));
|
|
@@ -1850,6 +1922,7 @@ async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
|
|
|
1850
1922
|
codexGoal = validation.raw;
|
|
1851
1923
|
nextActions = validation.nextActions;
|
|
1852
1924
|
warnings = validation.warnings;
|
|
1925
|
+
acknowledgeDriverObjective(plan, validation.unacknowledgedObjective);
|
|
1853
1926
|
} catch (error) {
|
|
1854
1927
|
if (!(error instanceof UlwLoopError))
|
|
1855
1928
|
throw error;
|
|
@@ -2235,6 +2308,48 @@ function joinLines(lines) {
|
|
|
2235
2308
|
`);
|
|
2236
2309
|
}
|
|
2237
2310
|
|
|
2311
|
+
// components/ulw-loop/src/success-criteria-input.ts
|
|
2312
|
+
function invalid2(message, details) {
|
|
2313
|
+
throw new UlwLoopError(`Invalid successCriteria: ${message}`, "ULW_LOOP_ARGUMENT_INVALID", { details });
|
|
2314
|
+
}
|
|
2315
|
+
function isUserModel(value) {
|
|
2316
|
+
return typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2317
|
+
}
|
|
2318
|
+
function requireText(value, field, index) {
|
|
2319
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
2320
|
+
return trimmed || invalid2(`entry ${index + 1} needs a non-empty ${field}.`, { index, field });
|
|
2321
|
+
}
|
|
2322
|
+
function criterionId(index) {
|
|
2323
|
+
return `C${String(index + 1).padStart(3, "0")}`;
|
|
2324
|
+
}
|
|
2325
|
+
function criteriaFromInput(input) {
|
|
2326
|
+
if (input.length === 0)
|
|
2327
|
+
invalid2("provide at least one criterion or omit the field for placeholders.", { count: 0 });
|
|
2328
|
+
return input.map((entry, index) => {
|
|
2329
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
2330
|
+
return invalid2(`entry ${index + 1} must be an object.`, { index });
|
|
2331
|
+
const record = { ...entry };
|
|
2332
|
+
const userModel = record["userModel"] ?? "happy";
|
|
2333
|
+
if (!isUserModel(userModel))
|
|
2334
|
+
return invalid2(`entry ${index + 1} userModel must be one of ${ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.join(", ")}.`, {
|
|
2335
|
+
index,
|
|
2336
|
+
userModel: String(userModel)
|
|
2337
|
+
});
|
|
2338
|
+
const essential = record["essential"];
|
|
2339
|
+
if (essential !== undefined && typeof essential !== "boolean")
|
|
2340
|
+
return invalid2(`entry ${index + 1} essential must be a boolean.`, { index });
|
|
2341
|
+
return {
|
|
2342
|
+
id: criterionId(index),
|
|
2343
|
+
scenario: requireText(record["scenario"], "scenario", index),
|
|
2344
|
+
userModel,
|
|
2345
|
+
expectedEvidence: requireText(record["expectedEvidence"], "expectedEvidence", index),
|
|
2346
|
+
essential: essential ?? true,
|
|
2347
|
+
capturedEvidence: null,
|
|
2348
|
+
status: "pending"
|
|
2349
|
+
};
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2238
2353
|
// components/ulw-loop/src/plan-goal-factory.ts
|
|
2239
2354
|
function cleanLine(line) {
|
|
2240
2355
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
@@ -2259,40 +2374,35 @@ function assertNonEmpty(value, label) {
|
|
|
2259
2374
|
function truncateObjective(objective) {
|
|
2260
2375
|
return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
|
|
2261
2376
|
}
|
|
2262
|
-
function
|
|
2377
|
+
function replaceVia(goalId, id, surface) {
|
|
2378
|
+
return surface === "omo-senpi" ? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)` : `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>"`;
|
|
2379
|
+
}
|
|
2380
|
+
function seedDefaultSuccessCriteria(goalIndex, objective, options = {}) {
|
|
2263
2381
|
const subject = truncateObjective(normalizeObjective2(objective) || `Goal ${goalIndex + 1}`);
|
|
2382
|
+
const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
|
|
2383
|
+
const surface = options.surface ?? "lazycodex";
|
|
2264
2384
|
const rows = [
|
|
2385
|
+
["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
|
|
2386
|
+
["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
|
|
2265
2387
|
[
|
|
2266
|
-
"C001",
|
|
2267
|
-
"happy",
|
|
2268
|
-
`happy path for: ${subject}`,
|
|
2269
|
-
`Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
|
|
2270
|
-
true
|
|
2271
|
-
],
|
|
2272
|
-
[
|
|
2273
|
-
"C002",
|
|
2274
|
-
"edge",
|
|
2275
|
-
"edge case (boundary/empty/malformed)",
|
|
2276
|
-
`Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
|
|
2277
|
-
true
|
|
2278
|
-
],
|
|
2279
|
-
[
|
|
2280
|
-
"C003",
|
|
2281
2388
|
"regression",
|
|
2282
2389
|
"regression: adjacent surface still works",
|
|
2283
|
-
`
|
|
2390
|
+
`regression proof for neighboring behavior after: ${subject}`,
|
|
2284
2391
|
false
|
|
2285
2392
|
]
|
|
2286
2393
|
];
|
|
2287
|
-
return rows.map(([
|
|
2288
|
-
id
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2394
|
+
return rows.map(([userModel, scenario, proof, essential], index) => {
|
|
2395
|
+
const id = criterionId(index);
|
|
2396
|
+
return {
|
|
2397
|
+
id,
|
|
2398
|
+
scenario,
|
|
2399
|
+
userModel,
|
|
2400
|
+
expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
|
|
2401
|
+
essential,
|
|
2402
|
+
capturedEvidence: null,
|
|
2403
|
+
status: "pending"
|
|
2404
|
+
};
|
|
2405
|
+
});
|
|
2296
2406
|
}
|
|
2297
2407
|
function deriveGoalCandidates(brief) {
|
|
2298
2408
|
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective2(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
|
|
@@ -2303,22 +2413,27 @@ function deriveGoalCandidates(brief) {
|
|
|
2303
2413
|
objective
|
|
2304
2414
|
}));
|
|
2305
2415
|
}
|
|
2306
|
-
function makeGoal(title, objective, index, now) {
|
|
2416
|
+
function makeGoal(title, objective, index, now, options = {}) {
|
|
2307
2417
|
const cleanTitle = assertNonEmpty(title, "title");
|
|
2308
2418
|
const cleanObjective = assertNonEmpty(objective, "objective");
|
|
2419
|
+
const id = normalizeGoalId(cleanTitle, index);
|
|
2420
|
+
const successCriteria = options.successCriteria === undefined ? seedDefaultSuccessCriteria(index, cleanObjective, {
|
|
2421
|
+
goalId: id,
|
|
2422
|
+
...options.surface === undefined ? {} : { surface: options.surface }
|
|
2423
|
+
}) : criteriaFromInput(options.successCriteria);
|
|
2309
2424
|
return {
|
|
2310
|
-
id
|
|
2425
|
+
id,
|
|
2311
2426
|
title: cleanTitle,
|
|
2312
2427
|
objective: cleanObjective,
|
|
2313
2428
|
status: "pending",
|
|
2314
|
-
successCriteria
|
|
2429
|
+
successCriteria,
|
|
2315
2430
|
attempt: 0,
|
|
2316
2431
|
createdAt: now,
|
|
2317
2432
|
updatedAt: now
|
|
2318
2433
|
};
|
|
2319
2434
|
}
|
|
2320
|
-
function appendGoalToPlan(plan, title, objective, now) {
|
|
2321
|
-
const goal = makeGoal(title, objective, plan.goals.length, now);
|
|
2435
|
+
function appendGoalToPlan(plan, title, objective, now, options = {}) {
|
|
2436
|
+
const goal = makeGoal(title, objective, plan.goals.length, now, options);
|
|
2322
2437
|
plan.goals.push(goal);
|
|
2323
2438
|
plan.updatedAt = now;
|
|
2324
2439
|
return goal;
|
|
@@ -2357,7 +2472,7 @@ async function createUlwLoopPlan(repoRoot, args, scope, surface = "lazycodex") {
|
|
|
2357
2472
|
throw new UlwLoopError(`Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
|
|
2358
2473
|
}
|
|
2359
2474
|
const now = iso();
|
|
2360
|
-
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
|
|
2475
|
+
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now, { surface }));
|
|
2361
2476
|
const plan = {
|
|
2362
2477
|
version: 1,
|
|
2363
2478
|
revision: existing?.revision ?? 0,
|
|
@@ -2398,11 +2513,14 @@ function completedPlanExistsError(scope, surface) {
|
|
|
2398
2513
|
]
|
|
2399
2514
|
].join(" "), "ULW_LOOP_PLAN_EXISTS_COMPLETE");
|
|
2400
2515
|
}
|
|
2401
|
-
async function addUlwLoopGoal(repoRoot, args, scope) {
|
|
2516
|
+
async function addUlwLoopGoal(repoRoot, args, scope, surface = "lazycodex") {
|
|
2402
2517
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2403
2518
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
2404
2519
|
const now = iso();
|
|
2405
|
-
const goal = appendGoalToPlan(plan, args.title, args.objective, now
|
|
2520
|
+
const goal = appendGoalToPlan(plan, args.title, args.objective, now, {
|
|
2521
|
+
surface,
|
|
2522
|
+
...args.successCriteria === undefined ? {} : { successCriteria: args.successCriteria }
|
|
2523
|
+
});
|
|
2406
2524
|
await commit(repoRoot, scope, {
|
|
2407
2525
|
plan,
|
|
2408
2526
|
entries: [{ at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }]
|
|
@@ -2570,7 +2688,7 @@ function isSource(value) {
|
|
|
2570
2688
|
function isModel(value) {
|
|
2571
2689
|
return ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2572
2690
|
}
|
|
2573
|
-
function
|
|
2691
|
+
function fail3(message, code, details) {
|
|
2574
2692
|
throw new UlwLoopError(message, code, { details });
|
|
2575
2693
|
}
|
|
2576
2694
|
function kindMessage(prefix) {
|
|
@@ -2584,15 +2702,15 @@ function text2(value, field) {
|
|
|
2584
2702
|
const trimmed = value.trim();
|
|
2585
2703
|
if (trimmed.length > 0)
|
|
2586
2704
|
return trimmed;
|
|
2587
|
-
return
|
|
2705
|
+
return fail3(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
|
|
2588
2706
|
}
|
|
2589
2707
|
function required3(argv, flag) {
|
|
2590
2708
|
const value = text2(readValue(argv, flag), flag);
|
|
2591
|
-
return value ??
|
|
2709
|
+
return value ?? fail3(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
|
|
2592
2710
|
}
|
|
2593
2711
|
function requiredGoal(argv) {
|
|
2594
2712
|
const value = text2(parseGoalArg(argv), "--goal-id");
|
|
2595
|
-
return value ??
|
|
2713
|
+
return value ?? fail3("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
|
|
2596
2714
|
}
|
|
2597
2715
|
function readObject(value, key) {
|
|
2598
2716
|
return Object.entries(value).find(([name]) => name === key)?.[1];
|
|
@@ -2616,7 +2734,7 @@ function objectChildren(value, key) {
|
|
|
2616
2734
|
for (const item of candidate) {
|
|
2617
2735
|
const next = child(item);
|
|
2618
2736
|
if (next === null)
|
|
2619
|
-
return
|
|
2737
|
+
return fail3(`${key} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { key });
|
|
2620
2738
|
parsed.push(next);
|
|
2621
2739
|
}
|
|
2622
2740
|
return parsed;
|
|
@@ -2631,13 +2749,13 @@ function parseSteeringKind(argv) {
|
|
|
2631
2749
|
const value = readValue(argv, "--kind");
|
|
2632
2750
|
if (isKind(value))
|
|
2633
2751
|
return value;
|
|
2634
|
-
return value === undefined ?
|
|
2752
|
+
return value === undefined ? fail3(kindMessage("Missing --kind."), "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind", expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP }) : fail3(kindMessage(`Invalid --kind: ${value}.`), "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP });
|
|
2635
2753
|
}
|
|
2636
2754
|
function parseSteeringSource(argv) {
|
|
2637
2755
|
const value = readValue(argv, "--source");
|
|
2638
2756
|
if (value === undefined)
|
|
2639
2757
|
return "cli";
|
|
2640
|
-
return isSource(value) ? value :
|
|
2758
|
+
return isSource(value) ? value : fail3(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
|
|
2641
2759
|
}
|
|
2642
2760
|
function child(value) {
|
|
2643
2761
|
if (!isPlain(value))
|
|
@@ -2654,12 +2772,12 @@ async function children(argv, flag, needed) {
|
|
|
2654
2772
|
return [];
|
|
2655
2773
|
const raw = await readJsonInput(input);
|
|
2656
2774
|
if (!Array.isArray(raw))
|
|
2657
|
-
return
|
|
2775
|
+
return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
|
2658
2776
|
const parsed = [];
|
|
2659
2777
|
for (const item of raw) {
|
|
2660
2778
|
const next = child(item);
|
|
2661
2779
|
if (next === null)
|
|
2662
|
-
return
|
|
2780
|
+
return fail3(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag });
|
|
2663
2781
|
parsed.push(next);
|
|
2664
2782
|
}
|
|
2665
2783
|
return parsed;
|
|
@@ -2667,11 +2785,11 @@ async function children(argv, flag, needed) {
|
|
|
2667
2785
|
async function stringArray2(argv, flag) {
|
|
2668
2786
|
const raw = await readJsonInput(required3(argv, flag));
|
|
2669
2787
|
if (!Array.isArray(raw))
|
|
2670
|
-
return
|
|
2788
|
+
return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
|
2671
2789
|
const values = [];
|
|
2672
2790
|
for (const item of raw) {
|
|
2673
2791
|
if (typeof item !== "string")
|
|
2674
|
-
return
|
|
2792
|
+
return fail3(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
|
|
2675
2793
|
values.push(text2(item, flag) ?? "");
|
|
2676
2794
|
}
|
|
2677
2795
|
return values;
|
|
@@ -2680,10 +2798,10 @@ function model(value) {
|
|
|
2680
2798
|
const trimmed = text2(value, "--user-model");
|
|
2681
2799
|
if (trimmed === undefined)
|
|
2682
2800
|
return;
|
|
2683
|
-
return isModel(trimmed) ? trimmed :
|
|
2801
|
+
return isModel(trimmed) ? trimmed : fail3(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
|
|
2684
2802
|
}
|
|
2685
2803
|
function neverKind(kind) {
|
|
2686
|
-
return
|
|
2804
|
+
return fail3(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind });
|
|
2687
2805
|
}
|
|
2688
2806
|
async function parseSteeringProposal(argv) {
|
|
2689
2807
|
const kind = parseSteeringKind(argv);
|
|
@@ -2704,7 +2822,7 @@ async function parseSteeringProposal(argv) {
|
|
|
2704
2822
|
const revisedTitle = readValue(argv, "--title");
|
|
2705
2823
|
const revisedObjective = readValue(argv, "--objective");
|
|
2706
2824
|
if (revisedTitle === undefined && revisedObjective === undefined)
|
|
2707
|
-
return
|
|
2825
|
+
return fail3("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
|
|
2708
2826
|
return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...revisedTitle === undefined ? {} : { revisedTitle }, ...revisedObjective === undefined ? {} : { revisedObjective } });
|
|
2709
2827
|
}
|
|
2710
2828
|
case "revise_criterion": {
|
|
@@ -2714,7 +2832,7 @@ async function parseSteeringProposal(argv) {
|
|
|
2714
2832
|
const expectedEvidence = readValue(argv, "--expected-evidence");
|
|
2715
2833
|
const userModel = model(readValue(argv, "--user-model"));
|
|
2716
2834
|
if (scenario === undefined && expectedEvidence === undefined && userModel === undefined)
|
|
2717
|
-
return
|
|
2835
|
+
return fail3("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
|
|
2718
2836
|
return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, criterionId, ...scenario === undefined ? {} : { scenario }, ...expectedEvidence === undefined ? {} : { expectedEvidence }, ...userModel === undefined ? {} : { userModel } });
|
|
2719
2837
|
}
|
|
2720
2838
|
case "annotate_ledger":
|
|
@@ -2764,10 +2882,10 @@ async function parseSteeringProposals(argv) {
|
|
|
2764
2882
|
if (input === undefined)
|
|
2765
2883
|
return [await parseSteeringProposal(argv)];
|
|
2766
2884
|
if (readValue(argv, "--kind") !== undefined)
|
|
2767
|
-
return
|
|
2885
|
+
return fail3("--kind and --proposals-json are mutually exclusive.", "ULW_LOOP_STEERING_BATCH_CONFLICT", { flags: ["--kind", "--proposals-json"] });
|
|
2768
2886
|
const raw = await readJsonInput(input);
|
|
2769
2887
|
if (!Array.isArray(raw) || raw.length === 0)
|
|
2770
|
-
return
|
|
2888
|
+
return fail3("--proposals-json must be a non-empty JSON array.", "ULW_LOOP_STEERING_BATCH_ARRAY_REQUIRED", { flag: "--proposals-json" });
|
|
2771
2889
|
const proposals = [];
|
|
2772
2890
|
for (const item of raw)
|
|
2773
2891
|
proposals.push(normalizeSteeringProposal(proposalFromObject(item)));
|
|
@@ -2775,13 +2893,13 @@ async function parseSteeringProposals(argv) {
|
|
|
2775
2893
|
}
|
|
2776
2894
|
function proposalFromObject(value) {
|
|
2777
2895
|
if (!isPlain(value))
|
|
2778
|
-
return
|
|
2896
|
+
return fail3("--proposals-json entries must be objects.", "ULW_LOOP_STEERING_BATCH_ITEM_INVALID", { flag: "--proposals-json" });
|
|
2779
2897
|
const kind = readObject(value, "kind");
|
|
2780
2898
|
const source = readObject(value, "source") ?? "cli";
|
|
2781
2899
|
if (!isProposalKind(kind))
|
|
2782
|
-
return
|
|
2900
|
+
return fail3(`Invalid batch steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value: kind });
|
|
2783
2901
|
if (!isProposalSource(source))
|
|
2784
|
-
return
|
|
2902
|
+
return fail3(`Invalid batch steering source: ${String(source)}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value: source });
|
|
2785
2903
|
let proposal = { kind, source, evidence: objectText(value, "evidence") ?? "", rationale: objectText(value, "rationale") ?? "" };
|
|
2786
2904
|
const goalId = objectText(value, "goalId");
|
|
2787
2905
|
const targetGoalId = objectText(value, "targetGoalId");
|
|
@@ -2867,12 +2985,13 @@ function nextGoalId(plan) {
|
|
|
2867
2985
|
}
|
|
2868
2986
|
function appendBlockerGoal(plan, args, now) {
|
|
2869
2987
|
const index = plan.goals.length;
|
|
2988
|
+
const id = nextGoalId(plan);
|
|
2870
2989
|
const goal = {
|
|
2871
|
-
id
|
|
2990
|
+
id,
|
|
2872
2991
|
title: args.title,
|
|
2873
2992
|
objective: args.objective,
|
|
2874
2993
|
status: "pending",
|
|
2875
|
-
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
|
|
2994
|
+
successCriteria: seedDefaultSuccessCriteria(index, args.objective, { goalId: id }),
|
|
2876
2995
|
attempt: 0,
|
|
2877
2996
|
createdAt: now,
|
|
2878
2997
|
updatedAt: now
|
|
@@ -2893,10 +3012,12 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2893
3012
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
2894
3013
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
2895
3014
|
expectedObjective: expectedCodexObjective(plan, goal),
|
|
2896
|
-
acceptedObjectives: compatibleCodexObjectives(plan)
|
|
3015
|
+
acceptedObjectives: compatibleCodexObjectives(plan),
|
|
3016
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(plan)
|
|
2897
3017
|
});
|
|
2898
3018
|
if (!reconciliation.ok)
|
|
2899
3019
|
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
3020
|
+
acknowledgeDriverObjective(plan, reconciliation.unacknowledgedObjective);
|
|
2900
3021
|
const now = iso();
|
|
2901
3022
|
for (const field of BLOCKER_FIELDS)
|
|
2902
3023
|
Reflect.deleteProperty(goal, field);
|
|
@@ -2920,8 +3041,8 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2920
3041
|
blockedGoal: goal,
|
|
2921
3042
|
newGoal,
|
|
2922
3043
|
ledgerEntries,
|
|
2923
|
-
nextActions: reconciliation.
|
|
2924
|
-
warnings: reconciliation.warnings
|
|
3044
|
+
nextActions: reconciliation.nextActions,
|
|
3045
|
+
warnings: reconciliation.warnings
|
|
2925
3046
|
};
|
|
2926
3047
|
});
|
|
2927
3048
|
}
|
|
@@ -2992,11 +3113,11 @@ function nextId(plan, offset) {
|
|
|
2992
3113
|
}, 0);
|
|
2993
3114
|
return `G${String(max + offset).padStart(3, "0")}`;
|
|
2994
3115
|
}
|
|
2995
|
-
function makeGoal2(plan, childGoal, evidence, now, offset) {
|
|
3116
|
+
function makeGoal2(plan, childGoal, evidence, now, offset, surface = "lazycodex") {
|
|
2996
3117
|
const id = nextId(plan, offset);
|
|
2997
3118
|
const digits = /^G(\d+)/u.exec(id)?.[1];
|
|
2998
3119
|
const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
|
|
2999
|
-
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
3120
|
+
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective, { goalId: id, surface }), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
3000
3121
|
}
|
|
3001
3122
|
function reviseWording(plan, proposal, now) {
|
|
3002
3123
|
const target = goal(plan, targets(proposal)[0]);
|
|
@@ -3008,11 +3129,11 @@ function reviseWording(plan, proposal, now) {
|
|
|
3008
3129
|
target.steeringRationale = proposal.rationale;
|
|
3009
3130
|
target.updatedAt = now;
|
|
3010
3131
|
}
|
|
3011
|
-
function splitOrBlock(plan, proposal, now) {
|
|
3132
|
+
function splitOrBlock(plan, proposal, now, surface = "lazycodex") {
|
|
3012
3133
|
const target = goal(plan, targets(proposal)[0]);
|
|
3013
3134
|
if (target === undefined)
|
|
3014
3135
|
return;
|
|
3015
|
-
const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1));
|
|
3136
|
+
const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1, surface));
|
|
3016
3137
|
target.steeringEvidence = proposal.evidence;
|
|
3017
3138
|
target.steeringRationale = proposal.rationale;
|
|
3018
3139
|
target.updatedAt = now;
|
|
@@ -3068,7 +3189,7 @@ function changedGoalIdsBetween(before, after) {
|
|
|
3068
3189
|
|
|
3069
3190
|
// components/ulw-loop/src/steering.ts
|
|
3070
3191
|
var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
|
|
3071
|
-
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
3192
|
+
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "acknowledgedDriverObjectives", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
3072
3193
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
3073
3194
|
var isPlain2 = (value) => isObject2(value) && !Array.isArray(value);
|
|
3074
3195
|
var read3 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
@@ -3226,13 +3347,13 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
3226
3347
|
if (model !== undefined && !isModel3(model))
|
|
3227
3348
|
reasons.push("invalid userModel");
|
|
3228
3349
|
}
|
|
3229
|
-
function applySteeringMutation(plan, proposal, audit) {
|
|
3350
|
+
function applySteeringMutation(plan, proposal, audit, surface = "lazycodex") {
|
|
3230
3351
|
const next = structuredClone(plan);
|
|
3231
3352
|
if (!audit.invariant.accepted)
|
|
3232
3353
|
return next;
|
|
3233
3354
|
const now = proposal.now?.toISOString() ?? iso();
|
|
3234
3355
|
if (proposal.kind === "add_subgoal")
|
|
3235
|
-
next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
|
|
3356
|
+
next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1, surface));
|
|
3236
3357
|
if (proposal.kind === "reorder_pending") {
|
|
3237
3358
|
const order = pendingOrder(proposal);
|
|
3238
3359
|
next.goals = [...order.map((id) => goal2(next, id)).filter((item) => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
|
|
@@ -3240,7 +3361,7 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
3240
3361
|
if (proposal.kind === "revise_pending_wording")
|
|
3241
3362
|
reviseWording(next, proposal, now);
|
|
3242
3363
|
if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded")
|
|
3243
|
-
splitOrBlock(next, proposal, now);
|
|
3364
|
+
splitOrBlock(next, proposal, now, surface);
|
|
3244
3365
|
if (proposal.kind === "revise_criterion")
|
|
3245
3366
|
reviseCriterion(next, proposal, now);
|
|
3246
3367
|
if (proposal.kind !== "annotate_ledger")
|
|
@@ -3263,7 +3384,7 @@ function parseUlwLoopSteeringDirective(text) {
|
|
|
3263
3384
|
throw error;
|
|
3264
3385
|
}
|
|
3265
3386
|
}
|
|
3266
|
-
async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
3387
|
+
async function steerUlwLoop(repoRoot, proposal, scope, surface = "lazycodex") {
|
|
3267
3388
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
3268
3389
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
3269
3390
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
@@ -3274,7 +3395,7 @@ async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
3274
3395
|
}
|
|
3275
3396
|
const audit = validateUlwLoopSteeringProposal(plan, proposal);
|
|
3276
3397
|
const accepted = audit.invariant.accepted;
|
|
3277
|
-
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
|
|
3398
|
+
const next = accepted ? applySteeringMutation(plan, proposal, audit, surface) : plan;
|
|
3278
3399
|
const finalAudit = { ...audit };
|
|
3279
3400
|
if (accepted) {
|
|
3280
3401
|
const changed = changedGoalIdsBetween(plan, next);
|
|
@@ -3303,20 +3424,119 @@ function ledgerEntry(proposal, audit, at) {
|
|
|
3303
3424
|
}
|
|
3304
3425
|
|
|
3305
3426
|
// components/ulw-loop/src/sdk/manifest.ts
|
|
3427
|
+
var CRITERION_INPUT = '{ scenario: string, expectedEvidence: string, userModel?: "happy" | "edge" | "regression" | "adversarial", essential?: boolean }';
|
|
3428
|
+
var STEER_KINDS = '"add_subgoal" | "split_subgoal" | "reorder_pending" | "revise_pending_wording" | "revise_criterion" | "annotate_ledger" | "mark_blocked_superseded"';
|
|
3306
3429
|
var ULW_LOOP_MANIFEST = {
|
|
3307
3430
|
version: 1,
|
|
3308
3431
|
name: "ulw-loop",
|
|
3309
3432
|
operations: [
|
|
3310
|
-
{
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
{
|
|
3318
|
-
|
|
3319
|
-
|
|
3433
|
+
{
|
|
3434
|
+
name: "help",
|
|
3435
|
+
method: "help",
|
|
3436
|
+
mutating: false,
|
|
3437
|
+
description: "Return this manifest: every operation with its method name, argument fields, and what it does.",
|
|
3438
|
+
args: {}
|
|
3439
|
+
},
|
|
3440
|
+
{
|
|
3441
|
+
name: "create-goals",
|
|
3442
|
+
method: "createGoals",
|
|
3443
|
+
mutating: true,
|
|
3444
|
+
description: "Create the session plan from a brief; one goal per bullet, each seeded with placeholder criteria until revised.",
|
|
3445
|
+
args: {
|
|
3446
|
+
brief: "string",
|
|
3447
|
+
codexGoalMode: '"aggregate" | "per_story"?',
|
|
3448
|
+
force: "boolean?",
|
|
3449
|
+
validationBatchesJson: "string?"
|
|
3450
|
+
}
|
|
3451
|
+
},
|
|
3452
|
+
{
|
|
3453
|
+
name: "status",
|
|
3454
|
+
method: "status",
|
|
3455
|
+
mutating: false,
|
|
3456
|
+
description: "Read the plan, its summary, structured nextActions, the stable evidenceRoot, and the active goal's currentAttemptDir.",
|
|
3457
|
+
args: {}
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
name: "complete-goals",
|
|
3461
|
+
method: "completeGoals",
|
|
3462
|
+
mutating: true,
|
|
3463
|
+
description: "Acquire the next eligible goal or resume the in-progress one; returns { done: true } once the aggregate is complete.",
|
|
3464
|
+
args: { retryFailed: "boolean?" }
|
|
3465
|
+
},
|
|
3466
|
+
{
|
|
3467
|
+
name: "checkpoint",
|
|
3468
|
+
method: "checkpoint",
|
|
3469
|
+
mutating: true,
|
|
3470
|
+
description: "Close the goal as complete, failed, or blocked with evidence; the final goal also needs the quality gate. printTemplate returns that gate's template instead.",
|
|
3471
|
+
args: {
|
|
3472
|
+
goalId: "string",
|
|
3473
|
+
status: '"complete" | "failed" | "blocked"',
|
|
3474
|
+
evidence: "string",
|
|
3475
|
+
codexGoalJson: "string?",
|
|
3476
|
+
qualityGateJson: "string?",
|
|
3477
|
+
printTemplate: "true? (with goalId? only)"
|
|
3478
|
+
}
|
|
3479
|
+
},
|
|
3480
|
+
{
|
|
3481
|
+
name: "steer",
|
|
3482
|
+
method: "steer",
|
|
3483
|
+
mutating: true,
|
|
3484
|
+
description: "Propose an evidence-backed plan mutation; revise_criterion replaces a criterion's scenario, expectedEvidence, or userModel.",
|
|
3485
|
+
args: {
|
|
3486
|
+
kind: STEER_KINDS,
|
|
3487
|
+
source: '"finding" | "user_prompt_submit" | "cli"',
|
|
3488
|
+
evidence: "string",
|
|
3489
|
+
rationale: "string",
|
|
3490
|
+
goalId: "string?",
|
|
3491
|
+
criterionId: "string? (revise_criterion)",
|
|
3492
|
+
scenario: "string? (revise_criterion)",
|
|
3493
|
+
expectedEvidence: "string? (revise_criterion)",
|
|
3494
|
+
userModel: '"happy" | "edge" | "regression" | "adversarial"? (revise_criterion)',
|
|
3495
|
+
title: "string? (add_subgoal)",
|
|
3496
|
+
objective: "string? (add_subgoal)",
|
|
3497
|
+
childGoals: "{ title: string, objective: string }[]? (split_subgoal)",
|
|
3498
|
+
revisedTitle: "string? (revise_pending_wording)",
|
|
3499
|
+
revisedObjective: "string? (revise_pending_wording)",
|
|
3500
|
+
pendingOrder: "string[]? (reorder_pending)",
|
|
3501
|
+
blockedReason: "string? (mark_blocked_superseded)",
|
|
3502
|
+
idempotencyKey: "string?"
|
|
3503
|
+
}
|
|
3504
|
+
},
|
|
3505
|
+
{
|
|
3506
|
+
name: "add-goal",
|
|
3507
|
+
method: "addGoal",
|
|
3508
|
+
mutating: true,
|
|
3509
|
+
description: "Append a goal; pass successCriteria to define its criteria in the same call, otherwise placeholders name the revise call.",
|
|
3510
|
+
args: { title: "string", objective: "string", successCriteria: `${CRITERION_INPUT}[]?` }
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
name: "criteria",
|
|
3514
|
+
method: "criteria",
|
|
3515
|
+
mutating: false,
|
|
3516
|
+
description: "List one goal's success criteria with their status and captured evidence.",
|
|
3517
|
+
args: { goalId: "string" }
|
|
3518
|
+
},
|
|
3519
|
+
{
|
|
3520
|
+
name: "record-evidence",
|
|
3521
|
+
method: "recordEvidence",
|
|
3522
|
+
mutating: true,
|
|
3523
|
+
description: "Record a criterion's pass, fail, or blocked evidence; artifacts must exist (resolved against the session cwd) and are stored with the criterion and the ledger entry.",
|
|
3524
|
+
args: {
|
|
3525
|
+
goalId: "string",
|
|
3526
|
+
criterionId: "string",
|
|
3527
|
+
status: '"pass" | "fail" | "blocked"',
|
|
3528
|
+
evidence: "string",
|
|
3529
|
+
notes: "string?",
|
|
3530
|
+
artifacts: "string[]?"
|
|
3531
|
+
}
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
name: "record-review-blockers",
|
|
3535
|
+
method: "recordReviewBlockers",
|
|
3536
|
+
mutating: true,
|
|
3537
|
+
description: "Mark the final goal review_blocked and append the blocker goal the reviewer verdict names.",
|
|
3538
|
+
args: { goalId: "string", title: "string", objective: "string", evidence: "string", codexGoalJson: "string?" }
|
|
3539
|
+
}
|
|
3320
3540
|
]
|
|
3321
3541
|
};
|
|
3322
3542
|
|
|
@@ -3358,10 +3578,11 @@ function isKnownRequest(request) {
|
|
|
3358
3578
|
function unreachable(value) {
|
|
3359
3579
|
throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
|
|
3360
3580
|
}
|
|
3361
|
-
function
|
|
3362
|
-
if (!(
|
|
3581
|
+
function stringsFrom(result, key) {
|
|
3582
|
+
if (!(key in result))
|
|
3363
3583
|
return [];
|
|
3364
|
-
|
|
3584
|
+
const value = Object.entries(result).find(([name]) => name === key)?.[1];
|
|
3585
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string").slice(0, 8) : [];
|
|
3365
3586
|
}
|
|
3366
3587
|
function checkpointWithValidatedSnapshot(context, scope, args) {
|
|
3367
3588
|
validateCodexGoalJson(args.codexGoalJson);
|
|
@@ -3378,8 +3599,15 @@ function createAgentToolkit(context, deps = {}) {
|
|
|
3378
3599
|
const invoke = async (operation, fn) => {
|
|
3379
3600
|
try {
|
|
3380
3601
|
const result = await fn();
|
|
3381
|
-
const nextActions = typeof result === "object" && result !== null ?
|
|
3382
|
-
|
|
3602
|
+
const nextActions = typeof result === "object" && result !== null ? stringsFrom(result, "nextActions") : [];
|
|
3603
|
+
const warnings = typeof result === "object" && result !== null ? stringsFrom(result, "warnings") : [];
|
|
3604
|
+
return await notify(operation, {
|
|
3605
|
+
ok: true,
|
|
3606
|
+
operation,
|
|
3607
|
+
result,
|
|
3608
|
+
nextActions,
|
|
3609
|
+
...warnings.length === 0 ? {} : { warnings }
|
|
3610
|
+
});
|
|
3383
3611
|
} catch (error) {
|
|
3384
3612
|
if (error instanceof UlwLoopError && error.code === "ULW_LOOP_PLAN_MISSING" && context.surface === "omo-senpi")
|
|
3385
3613
|
return notify(operation, failure(operation, planMissingError(ulwLoopGoalsRelativePath(scope), listUlwLoopSessionIds(context.cwd), context.surface)));
|
|
@@ -3425,13 +3653,14 @@ function createAgentToolkit(context, deps = {}) {
|
|
|
3425
3653
|
plan,
|
|
3426
3654
|
summary: summarizeUlwLoopPlan(plan),
|
|
3427
3655
|
nextActions: statusNextActions(plan, context.surface),
|
|
3656
|
+
evidenceRoot: ulwLoopEvidenceRoot(scope),
|
|
3428
3657
|
...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
|
|
3429
3658
|
};
|
|
3430
3659
|
}),
|
|
3431
3660
|
completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
|
|
3432
3661
|
checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointWithValidatedSnapshot(context, scope, args)),
|
|
3433
|
-
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
|
|
3434
|
-
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
|
|
3662
|
+
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope, context.surface)),
|
|
3663
|
+
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope, context.surface)),
|
|
3435
3664
|
criteria: (args) => invoke("criteria", async () => {
|
|
3436
3665
|
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
3437
3666
|
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
|
|
@@ -3798,7 +4027,7 @@ function commandScope(repoRoot, argv) {
|
|
|
3798
4027
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
3799
4028
|
|
|
3800
4029
|
// components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
3801
|
-
import { existsSync as
|
|
4030
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
3802
4031
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3803
4032
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
3804
4033
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
@@ -3839,7 +4068,7 @@ function buildUltraworkSkillPointer(skillFilePath) {
|
|
|
3839
4068
|
}
|
|
3840
4069
|
function buildUltraworkAdditionalContext(options = {}) {
|
|
3841
4070
|
const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
|
|
3842
|
-
if (skillFilePath !== null &&
|
|
4071
|
+
if (skillFilePath !== null && existsSync6(skillFilePath)) {
|
|
3843
4072
|
return buildUltraworkSkillPointer(skillFilePath);
|
|
3844
4073
|
}
|
|
3845
4074
|
return ULTRAWORK_DIRECTIVE;
|
|
@@ -4091,7 +4320,7 @@ import { join as join7 } from "node:path";
|
|
|
4091
4320
|
|
|
4092
4321
|
// components/ulw-loop/src/spawn-budget-io.ts
|
|
4093
4322
|
import { randomBytes } from "node:crypto";
|
|
4094
|
-
import { existsSync as
|
|
4323
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4095
4324
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
4096
4325
|
function readAdmissionBreaker(sessionId) {
|
|
4097
4326
|
const dataDir = process.env["PLUGIN_DATA"];
|
|
@@ -4111,7 +4340,7 @@ function atomicWriteJson(targetPath, data) {
|
|
|
4111
4340
|
}
|
|
4112
4341
|
function isNonEmptyFile(path) {
|
|
4113
4342
|
try {
|
|
4114
|
-
return
|
|
4343
|
+
return existsSync7(path) && statSync3(path).size > 0;
|
|
4115
4344
|
} catch (error) {
|
|
4116
4345
|
if (error instanceof Error)
|
|
4117
4346
|
return false;
|
|
@@ -4409,8 +4638,8 @@ function readPlan(repoRoot, sessionId) {
|
|
|
4409
4638
|
}
|
|
4410
4639
|
|
|
4411
4640
|
// components/ulw-loop/src/stop-resume-hook.ts
|
|
4412
|
-
import { existsSync as
|
|
4413
|
-
import { isAbsolute as
|
|
4641
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4642
|
+
import { isAbsolute as isAbsolute3, join as join8, resolve as resolve5, sep as sep3 } from "node:path";
|
|
4414
4643
|
var RESUME_CAP = 2;
|
|
4415
4644
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
4416
4645
|
"context compacted",
|
|
@@ -4478,8 +4707,8 @@ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
|
4478
4707
|
}
|
|
4479
4708
|
function consumeResumeBudget(stateDir, goalId) {
|
|
4480
4709
|
const ledgerLineCount = readLedgerAt(stateDir).length;
|
|
4481
|
-
const counterPath =
|
|
4482
|
-
const stuckPath =
|
|
4710
|
+
const counterPath = resolve5(stateDir, `auto-resume-${goalId}.json`);
|
|
4711
|
+
const stuckPath = resolve5(stateDir, `auto-resume-${goalId}.stuck`);
|
|
4483
4712
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
4484
4713
|
return false;
|
|
4485
4714
|
const previous = readCounter(counterPath);
|
|
@@ -4493,7 +4722,7 @@ function consumeResumeBudget(stateDir, goalId) {
|
|
|
4493
4722
|
return true;
|
|
4494
4723
|
}
|
|
4495
4724
|
function isInsideDir(dir, candidate) {
|
|
4496
|
-
return candidate.startsWith(
|
|
4725
|
+
return candidate.startsWith(resolve5(dir) + sep3);
|
|
4497
4726
|
}
|
|
4498
4727
|
function renderResumeDirective(plan, goal, sessionId) {
|
|
4499
4728
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
@@ -4519,7 +4748,7 @@ function readPlan2(repoRoot, sessionId) {
|
|
|
4519
4748
|
}
|
|
4520
4749
|
function readCounter(counterPath) {
|
|
4521
4750
|
try {
|
|
4522
|
-
if (!
|
|
4751
|
+
if (!existsSync8(counterPath))
|
|
4523
4752
|
return null;
|
|
4524
4753
|
const parsed = JSON.parse(readFileSync7(counterPath, "utf8"));
|
|
4525
4754
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
@@ -4564,9 +4793,9 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
4564
4793
|
const activePlan = entry["active_plan"];
|
|
4565
4794
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
4566
4795
|
return false;
|
|
4567
|
-
const planPath =
|
|
4796
|
+
const planPath = isAbsolute3(activePlan) ? activePlan : join8(cwd, activePlan);
|
|
4568
4797
|
const worktree = entry["worktree_path"];
|
|
4569
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !
|
|
4798
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute3(activePlan) ? [join8(isAbsolute3(worktree) ? worktree : join8(cwd, worktree), activePlan), planPath] : [planPath];
|
|
4570
4799
|
for (const candidate of candidates) {
|
|
4571
4800
|
try {
|
|
4572
4801
|
return readFileSync7(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|