oh-my-opencode 5.0.0-beta.33 → 5.0.0-beta.35
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/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
- package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/THIRD-PARTY-NOTICES.md +27 -99
- package/dist/cli/index.js +48350 -48559
- package/dist/cli-node/index.js +48480 -48689
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/config/schema/oh-my-opencode-config.d.ts +0 -10
- package/dist/config/schema.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.js +11675 -13742
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/types.d.ts +0 -1
- package/dist/oh-my-opencode.schema.json +1 -45
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/shared/omo-process-sweep.d.ts +2 -4
- package/dist/skills/init-deep/SKILL.md +13 -13
- package/dist/skills/ulw-plan/SKILL.md +0 -1
- package/dist/tui.js +44809 -44910
- package/package.json +16 -18
- package/packages/omo-codex/THIRD-PARTY-NOTICES.md +0 -17
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -3
- package/packages/omo-codex/plugin/.mcp.json +0 -6
- package/packages/omo-codex/plugin/AGENTS.md +5 -5
- package/packages/omo-codex/plugin/README.md +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -15
- 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/lcx/skills/lcx-doctor/SKILL.md +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/teammode/test/thread-title-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +0 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +11 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +35 -38
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +278 -193
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +7 -51
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +39 -22
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +64 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +37 -18
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +32 -63
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +7 -53
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +50 -31
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +67 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +36 -17
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +26 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +47 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -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/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-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -146
- package/packages/omo-codex/plugin/package.json +1 -2
- package/packages/omo-codex/plugin/scripts/AGENTS.md +0 -1
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
- package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -8
- package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
- package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +0 -1
- package/packages/omo-codex/plugin/test/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +2 -21
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -2
- package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +0 -25
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
- package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +0 -50
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +0 -1
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
- package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +238 -431
- package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
- package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +0 -1
- package/dist/config/schema/codegraph.d.ts +0 -12
- package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
- package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
- package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
- package/dist/mcp/codegraph.d.ts +0 -16
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
- package/packages/omo-codex/plugin/components/codegraph/LICENSE +0 -21
- package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
- package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12326
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9908
- package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
- package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -310
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -92
- package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -22
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
- package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
- package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
- package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
- package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
- package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +0 -47
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +0 -101
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
- package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
- package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// components/ulw-loop/src/checkpoint.ts
|
|
4
4
|
import { existsSync as existsSync4, statSync } from "node:fs";
|
|
5
5
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
6
|
-
import { resolve as
|
|
6
|
+
import { resolve as resolve3 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// components/ulw-loop/src/checkpoint-reconciliation.ts
|
|
9
9
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -379,6 +379,44 @@ function buildTaskScopedAggregateReconciliationHint(goal, final) {
|
|
|
379
379
|
return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// components/ulw-loop/src/checkpoint-codex-validation.ts
|
|
383
|
+
function normalizeObjective3(value) {
|
|
384
|
+
return value.replace(/\s+/g, " ").trim();
|
|
385
|
+
}
|
|
386
|
+
async function validateCheckpointCodexGoal(input) {
|
|
387
|
+
const aggregate = codexGoalMode(input.plan) === "aggregate";
|
|
388
|
+
const final = isFinalRunCompletionCandidate(input.plan, input.goal);
|
|
389
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
390
|
+
const expectedObjective = expectedCodexObjective(input.plan, input.goal);
|
|
391
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
392
|
+
expectedObjective,
|
|
393
|
+
...aggregate ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {},
|
|
394
|
+
allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
|
|
395
|
+
requireSnapshot: true,
|
|
396
|
+
requireComplete: !aggregate || final
|
|
397
|
+
});
|
|
398
|
+
if (reconciliation.ok)
|
|
399
|
+
return reconciliation.snapshot.raw;
|
|
400
|
+
const objective = snapshot?.objective;
|
|
401
|
+
const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedObjective);
|
|
402
|
+
const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
403
|
+
const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
404
|
+
if (completedTaskScoped || activeFinalTaskScoped)
|
|
405
|
+
return reconciliation.snapshot.raw;
|
|
406
|
+
throw codexSnapshotMismatchError({
|
|
407
|
+
reconciliation,
|
|
408
|
+
snapshot,
|
|
409
|
+
expectedObjective,
|
|
410
|
+
taskScopedHint: { goal: input.goal, aggregate, final }
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
414
|
+
return new UlwLoopError(`${codexError.message}
|
|
415
|
+
${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
416
|
+
details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
382
420
|
// components/ulw-loop/src/plan-io.ts
|
|
383
421
|
import { createReadStream, readdirSync } from "node:fs";
|
|
384
422
|
import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
|
|
@@ -634,111 +672,113 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
634
672
|
});
|
|
635
673
|
}
|
|
636
674
|
|
|
637
|
-
// components/ulw-loop/src/quality-gate-
|
|
675
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
638
676
|
import { resolve as resolve2 } from "node:path";
|
|
639
677
|
|
|
640
678
|
// components/ulw-loop/src/quality-gate-fields.ts
|
|
641
679
|
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
680
|
+
var activeCollector;
|
|
681
|
+
function withQualityGateCollector(operation) {
|
|
682
|
+
const previous = activeCollector;
|
|
683
|
+
const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
|
|
684
|
+
activeCollector = collector;
|
|
685
|
+
try {
|
|
686
|
+
const result = operation();
|
|
687
|
+
if (collector.defects.length === 0)
|
|
688
|
+
return result;
|
|
689
|
+
throwQualityGateDefects(collector.defects);
|
|
690
|
+
} finally {
|
|
691
|
+
activeCollector = previous;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function throwQualityGateDefects(defects) {
|
|
695
|
+
const uniqueDefects = [
|
|
696
|
+
...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
|
|
697
|
+
];
|
|
698
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
699
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
700
|
+
const message = [
|
|
701
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
702
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
703
|
+
].join(`
|
|
704
|
+
`);
|
|
705
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
706
|
+
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
642
709
|
function invalid(message, field) {
|
|
643
|
-
|
|
710
|
+
if (activeCollector === undefined)
|
|
711
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
712
|
+
activeCollector.defects.push({ field, message });
|
|
713
|
+
activeCollector.poisonedFields.add(field);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
function isPoisoned(field) {
|
|
717
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
718
|
+
}
|
|
719
|
+
function markPoisonedArtifactKind(id) {
|
|
720
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
721
|
+
}
|
|
722
|
+
function isPoisonedArtifactKind(id) {
|
|
723
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
644
724
|
}
|
|
645
725
|
function isRecord(value) {
|
|
646
726
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
647
727
|
}
|
|
648
728
|
function section(value, field) {
|
|
649
|
-
|
|
729
|
+
if (isRecord(value))
|
|
730
|
+
return value;
|
|
731
|
+
invalid(`Final quality gate is missing ${field} evidence.`, field);
|
|
732
|
+
return {};
|
|
650
733
|
}
|
|
651
734
|
function textField(value, field) {
|
|
652
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
735
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
653
736
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
737
|
+
return "";
|
|
738
|
+
}
|
|
654
739
|
const trimmed = value.trim();
|
|
655
740
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
656
741
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
657
742
|
return trimmed;
|
|
658
743
|
}
|
|
659
744
|
function numberField(value, field) {
|
|
660
|
-
|
|
745
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
746
|
+
return value;
|
|
747
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
748
|
+
return 0;
|
|
661
749
|
}
|
|
662
750
|
function stringArray(value, field) {
|
|
663
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
664
|
-
|
|
751
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
752
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
665
755
|
return value.map((item) => textField(item, field));
|
|
666
756
|
}
|
|
667
757
|
function emptyBlockers(value, field) {
|
|
668
758
|
if (Array.isArray(value) && value.length === 0)
|
|
669
759
|
return [];
|
|
670
760
|
invalid(`${field} must be empty.`, field);
|
|
761
|
+
return [];
|
|
671
762
|
}
|
|
672
763
|
function literal(value, expected, field) {
|
|
673
764
|
if (value === expected)
|
|
674
765
|
return expected;
|
|
675
766
|
invalid(`${field} must be ${String(expected)}.`, field);
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
// components/ulw-loop/src/quality-gate-aggregate.ts
|
|
679
|
-
var PLACEHOLDER = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
680
|
-
function add(defects, field, message) {
|
|
681
|
-
defects.push({ field, message });
|
|
682
|
-
}
|
|
683
|
-
function text(value, field, defects) {
|
|
684
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
685
|
-
add(defects, field, `Final quality gate requires non-empty ${field}.`);
|
|
686
|
-
else if (PLACEHOLDER.test(value.trim()))
|
|
687
|
-
add(defects, field, `Final quality gate rejects placeholder ${field}.`);
|
|
688
|
-
}
|
|
689
|
-
function section2(value, field, defects) {
|
|
690
|
-
if (!isRecord(value)) {
|
|
691
|
-
add(defects, field, `Final quality gate is missing ${field} evidence.`);
|
|
692
|
-
return {};
|
|
693
|
-
}
|
|
694
|
-
return value;
|
|
695
|
-
}
|
|
696
|
-
function aggregateQualityGateDefects(input, opts) {
|
|
697
|
-
if (!isRecord(input))
|
|
698
|
-
return;
|
|
699
|
-
const defects = [];
|
|
700
|
-
const gate = input;
|
|
701
|
-
const manual = section2(gate["manualQa"], "manualQa", defects);
|
|
702
|
-
const review = section2(gate["gateReview"], "gateReview", defects);
|
|
703
|
-
text(manual["evidence"], "manualQa.evidence", defects);
|
|
704
|
-
text(review["evidence"], "gateReview.evidence", defects);
|
|
705
|
-
if (review["recommendation"] !== "APPROVE")
|
|
706
|
-
add(defects, "gateReview.recommendation", "gateReview.recommendation must be APPROVE.");
|
|
707
|
-
const artifacts = Array.isArray(manual["artifactRefs"]) ? manual["artifactRefs"] : [];
|
|
708
|
-
for (const [index, item] of artifacts.entries()) {
|
|
709
|
-
if (!isRecord(item))
|
|
710
|
-
continue;
|
|
711
|
-
const path = item["path"];
|
|
712
|
-
if (typeof path !== "string" || path.trim() === "")
|
|
713
|
-
continue;
|
|
714
|
-
if (opts?.repoRoot !== undefined && opts.fs !== undefined && !opts.fs.existsSync(resolve2(opts.repoRoot, path)))
|
|
715
|
-
add(defects, `manualQa.artifactRefs[${index}].path`, `manualQa.artifactRefs[${index}].path must point to an existing artifact.`);
|
|
716
|
-
}
|
|
717
|
-
if (defects.length === 0)
|
|
718
|
-
return;
|
|
719
|
-
const truncated = defects.length > 25;
|
|
720
|
-
const fields = defects.slice(0, 25).map(({ field, message: message2 }) => ({ field, message: message2 }));
|
|
721
|
-
const message = [
|
|
722
|
-
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
723
|
-
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
724
|
-
].join(`
|
|
725
|
-
`);
|
|
726
|
-
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
727
|
-
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
728
|
-
});
|
|
767
|
+
return expected;
|
|
729
768
|
}
|
|
730
769
|
|
|
731
770
|
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
732
|
-
import { resolve as resolve3 } from "node:path";
|
|
733
771
|
function surfaceField(value, field) {
|
|
734
772
|
if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
|
|
735
773
|
return value;
|
|
736
774
|
invalid(`${field} must be a supported manual QA surface.`, field);
|
|
775
|
+
return "cli";
|
|
737
776
|
}
|
|
738
777
|
function kindField(value, field) {
|
|
739
778
|
if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
|
|
740
779
|
return value;
|
|
741
780
|
invalid(`${field} must be a supported artifact kind.`, field);
|
|
781
|
+
return "log";
|
|
742
782
|
}
|
|
743
783
|
function artifactCompatible(surface, kind) {
|
|
744
784
|
switch (surface) {
|
|
@@ -754,21 +794,21 @@ function artifactCompatible(surface, kind) {
|
|
|
754
794
|
return kind === "data-diff";
|
|
755
795
|
default:
|
|
756
796
|
invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
|
|
797
|
+
return false;
|
|
757
798
|
}
|
|
758
799
|
}
|
|
759
800
|
function checkFile(path, field, opts) {
|
|
760
|
-
if (opts?.repoRoot === undefined || opts.fs === undefined)
|
|
801
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
761
802
|
return;
|
|
762
|
-
const absolute =
|
|
763
|
-
if (!opts.fs.existsSync(absolute))
|
|
803
|
+
const absolute = resolve2(opts.repoRoot, path);
|
|
804
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
764
805
|
invalid(`${field} must point to an existing artifact.`, field);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
765
808
|
if (opts.fs.statSync(absolute).size <= 0)
|
|
766
809
|
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
767
|
-
if (opts.currentAttemptDir !== undefined && opts.repoRoot
|
|
768
|
-
|
|
769
|
-
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
770
|
-
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
771
|
-
}
|
|
810
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
|
|
811
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
772
812
|
}
|
|
773
813
|
function artifactMap(refs) {
|
|
774
814
|
const byId = new Map;
|
|
@@ -780,39 +820,56 @@ function artifactMap(refs) {
|
|
|
780
820
|
return byId;
|
|
781
821
|
}
|
|
782
822
|
function parseArtifactRefs(value, opts) {
|
|
783
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
823
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
784
824
|
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
return value.flatMap((item, index) => {
|
|
828
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
829
|
+
const ref = section(item, prefix);
|
|
830
|
+
if (isPoisoned(prefix))
|
|
831
|
+
return [];
|
|
832
|
+
const pathField = `${prefix}.path`;
|
|
833
|
+
const idField = `${prefix}.id`;
|
|
834
|
+
const kindFieldName = `${prefix}.kind`;
|
|
835
|
+
const descriptionField = `${prefix}.description`;
|
|
836
|
+
const path = textField(ref["path"], pathField);
|
|
837
|
+
const id = textField(ref["id"], idField);
|
|
838
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
839
|
+
if (isPoisoned(kindFieldName))
|
|
840
|
+
markPoisonedArtifactKind(id);
|
|
841
|
+
const description = textField(ref["description"], descriptionField);
|
|
842
|
+
checkFile(path, pathField, opts);
|
|
843
|
+
return [{ id, kind, description, path }];
|
|
795
844
|
});
|
|
796
845
|
}
|
|
797
846
|
function referencedArtifacts(value, field, byId) {
|
|
798
|
-
|
|
847
|
+
const ids = stringArray(value, field);
|
|
848
|
+
if (isPoisoned(field))
|
|
849
|
+
return [];
|
|
850
|
+
return ids.flatMap((id) => {
|
|
799
851
|
const artifact = byId.get(id);
|
|
800
|
-
if (artifact === undefined)
|
|
852
|
+
if (artifact === undefined) {
|
|
801
853
|
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
802
|
-
|
|
854
|
+
return [];
|
|
855
|
+
}
|
|
856
|
+
return [artifact];
|
|
803
857
|
});
|
|
804
858
|
}
|
|
805
859
|
|
|
806
860
|
// components/ulw-loop/src/quality-gate-verdicts.ts
|
|
807
861
|
function passedVerdict(value, field) {
|
|
808
|
-
if (value === "not_applicable")
|
|
862
|
+
if (value === "not_applicable") {
|
|
809
863
|
invalid(`${field} must not be not_applicable.`, field);
|
|
864
|
+
return "passed";
|
|
865
|
+
}
|
|
810
866
|
return literal(value, "passed", field);
|
|
811
867
|
}
|
|
812
868
|
function codeQualityStatusField(value, field) {
|
|
813
869
|
if (value === "CLEAR" || value === "WATCH")
|
|
814
870
|
return value;
|
|
815
871
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
872
|
+
return "CLEAR";
|
|
816
873
|
}
|
|
817
874
|
function adversarialVerdict(row, field) {
|
|
818
875
|
const value = row["verdict"];
|
|
@@ -822,6 +879,7 @@ function adversarialVerdict(row, field) {
|
|
|
822
879
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
823
880
|
}
|
|
824
881
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
882
|
+
return { verdict: "passed" };
|
|
825
883
|
}
|
|
826
884
|
|
|
827
885
|
// components/ulw-loop/src/surface.ts
|
|
@@ -841,10 +899,6 @@ var REVIEWER_ROLES_BY_SURFACE = {
|
|
|
841
899
|
}
|
|
842
900
|
};
|
|
843
901
|
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
844
|
-
var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
|
|
845
|
-
lazycodex: ["codeReview", "manualQa", "gateReview", "iteration", "criteriaCoverage"],
|
|
846
|
-
"omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
|
|
847
|
-
};
|
|
848
902
|
var GATE_SECTION_BY_ACCEPTOR = {
|
|
849
903
|
lazycodex: {
|
|
850
904
|
codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview],
|
|
@@ -930,6 +984,10 @@ function clearGoalBlockerFields(goal) {
|
|
|
930
984
|
|
|
931
985
|
// components/ulw-loop/src/quality-gate.ts
|
|
932
986
|
function reviewerRoleField(value, expected, field) {
|
|
987
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
988
|
+
textField(value, field);
|
|
989
|
+
return expected;
|
|
990
|
+
}
|
|
933
991
|
const actual = textField(value, field);
|
|
934
992
|
if (actual !== expected)
|
|
935
993
|
invalid(`${field} must be ${expected}.`, field);
|
|
@@ -937,44 +995,51 @@ function reviewerRoleField(value, expected, field) {
|
|
|
937
995
|
}
|
|
938
996
|
function reviewerAcceptorField(value, surface, sectionName) {
|
|
939
997
|
const field = `${sectionName}.by`;
|
|
940
|
-
const actual = textField(value, field);
|
|
941
998
|
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
999
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1000
|
+
textField(value, field);
|
|
1001
|
+
return accepted?.[0] ?? "";
|
|
1002
|
+
}
|
|
1003
|
+
const actual = textField(value, field);
|
|
942
1004
|
if (accepted === undefined || !accepted.includes(actual))
|
|
943
1005
|
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
944
1006
|
return actual;
|
|
945
1007
|
}
|
|
946
1008
|
function validateQualityGate(input, opts) {
|
|
1009
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
1010
|
+
}
|
|
1011
|
+
function validateQualityGateUncollected(input, opts) {
|
|
947
1012
|
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
948
|
-
aggregateQualityGateDefects(input, opts);
|
|
949
1013
|
const gate = section(input, "qualityGate");
|
|
950
|
-
const requiredSections = REQUIRED_GATE_SECTIONS_BY_SURFACE[surface];
|
|
951
|
-
for (const sectionName of requiredSections)
|
|
952
|
-
section(gate[sectionName], sectionName);
|
|
953
1014
|
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
954
1015
|
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
955
1016
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
956
1017
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
957
1018
|
const iteration = section(gate["iteration"], "iteration");
|
|
958
1019
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
1020
|
+
const codeReview = surface === "lazycodex" ? section(gate["codeReview"], "codeReview") : {};
|
|
959
1021
|
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
960
1022
|
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
1023
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
1024
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
961
1025
|
if (surface === "lazycodex")
|
|
962
|
-
reviewerRoleField(
|
|
1026
|
+
reviewerRoleField(codeReview?.["by"], "lazycodex-code-reviewer", "codeReview.by");
|
|
963
1027
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
964
1028
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
965
|
-
if (passCount < totalCriteria)
|
|
1029
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
966
1030
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
967
1031
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
968
1032
|
const byId = artifactMap(artifactRefs);
|
|
969
1033
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
970
1034
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
971
1035
|
const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
|
|
972
|
-
|
|
1036
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
1037
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
973
1038
|
const common = {
|
|
974
1039
|
manualQa: {
|
|
975
1040
|
by: manualQaBy,
|
|
976
1041
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
977
|
-
evidence:
|
|
1042
|
+
evidence: manualQaEvidence,
|
|
978
1043
|
surfaceEvidence,
|
|
979
1044
|
adversarialCases,
|
|
980
1045
|
artifactRefs
|
|
@@ -983,7 +1048,7 @@ function validateQualityGate(input, opts) {
|
|
|
983
1048
|
by: gateReviewBy,
|
|
984
1049
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
985
1050
|
reportPath: gateReportPath,
|
|
986
|
-
evidence:
|
|
1051
|
+
evidence: gateReviewEvidence,
|
|
987
1052
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
|
|
988
1053
|
},
|
|
989
1054
|
iteration: {
|
|
@@ -1003,7 +1068,6 @@ function validateQualityGate(input, opts) {
|
|
|
1003
1068
|
};
|
|
1004
1069
|
if (surface === "omo-senpi")
|
|
1005
1070
|
return { surface, ...common };
|
|
1006
|
-
const codeReview = section(gate["codeReview"], "codeReview");
|
|
1007
1071
|
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
1008
1072
|
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
1009
1073
|
return {
|
|
@@ -1020,13 +1084,19 @@ function validateQualityGate(input, opts) {
|
|
|
1020
1084
|
};
|
|
1021
1085
|
}
|
|
1022
1086
|
function parseSurfaceEvidence(value, byId) {
|
|
1023
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1087
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1024
1088
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
1025
|
-
|
|
1089
|
+
return [];
|
|
1090
|
+
}
|
|
1091
|
+
return value.flatMap((item, index) => {
|
|
1026
1092
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
1093
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
1094
|
+
return [];
|
|
1027
1095
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
1028
1096
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
1029
1097
|
for (const artifact of artifacts) {
|
|
1098
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
1099
|
+
continue;
|
|
1030
1100
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
1031
1101
|
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
1032
1102
|
}
|
|
@@ -1042,10 +1112,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
1042
1112
|
});
|
|
1043
1113
|
}
|
|
1044
1114
|
function parseAdversarialCases(value, byId) {
|
|
1045
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1115
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1046
1116
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
1047
|
-
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
return value.flatMap((item, index) => {
|
|
1048
1120
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
1121
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
1122
|
+
return [];
|
|
1049
1123
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
1050
1124
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
1051
1125
|
return {
|
|
@@ -1158,7 +1232,7 @@ function isObject(value) {
|
|
|
1158
1232
|
function read(value, key) {
|
|
1159
1233
|
return Object.entries(value).find(([name]) => name === key)?.[1];
|
|
1160
1234
|
}
|
|
1161
|
-
function
|
|
1235
|
+
function text(value, key) {
|
|
1162
1236
|
const candidate = read(value, key);
|
|
1163
1237
|
return typeof candidate === "string" && candidate.trim().length > 0 ? candidate.trim() : undefined;
|
|
1164
1238
|
}
|
|
@@ -1179,9 +1253,9 @@ async function parseValidationBatches(input, goals) {
|
|
|
1179
1253
|
function batchFromObject(value) {
|
|
1180
1254
|
if (!isObject(value))
|
|
1181
1255
|
fail("validation batch entries must be objects.");
|
|
1182
|
-
const batchId =
|
|
1256
|
+
const batchId = text(value, "batchId");
|
|
1183
1257
|
const memberIds = strings(value, "memberIds");
|
|
1184
|
-
const finalGoalId =
|
|
1258
|
+
const finalGoalId = text(value, "finalGoalId");
|
|
1185
1259
|
if (batchId === undefined)
|
|
1186
1260
|
fail("validation batch requires batchId.");
|
|
1187
1261
|
if (memberIds === undefined || memberIds.length < 2)
|
|
@@ -1274,9 +1348,6 @@ var QUALITY_GATE_FS = { existsSync: existsSync4, statSync };
|
|
|
1274
1348
|
function ulwLoopFail2(message, code) {
|
|
1275
1349
|
throw new UlwLoopError(message, code);
|
|
1276
1350
|
}
|
|
1277
|
-
function normalizeObjective3(value) {
|
|
1278
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1279
|
-
}
|
|
1280
1351
|
function nonEmptyEvidence2(value) {
|
|
1281
1352
|
const trimmed = value.trim();
|
|
1282
1353
|
return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
|
|
@@ -1295,7 +1366,7 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1295
1366
|
if (!(error instanceof SyntaxError))
|
|
1296
1367
|
throw error;
|
|
1297
1368
|
}
|
|
1298
|
-
const path =
|
|
1369
|
+
const path = resolve3(repoRoot, trimmed);
|
|
1299
1370
|
if (!existsSync4(path))
|
|
1300
1371
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1301
1372
|
try {
|
|
@@ -1380,23 +1451,20 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1380
1451
|
requireEssentialCriteriaPass(goal);
|
|
1381
1452
|
else
|
|
1382
1453
|
requireAllCriteriaPass(goal);
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const taskScoped = completedTaskScoped || activeFinalTaskScoped;
|
|
1398
|
-
if (!taskScoped)
|
|
1399
|
-
throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expectedCodexObjective(plan, goal), taskScopedHint: { goal, aggregate, final } });
|
|
1454
|
+
let codexValidationError;
|
|
1455
|
+
try {
|
|
1456
|
+
codexGoal = await validateCheckpointCodexGoal({
|
|
1457
|
+
repoRoot,
|
|
1458
|
+
plan,
|
|
1459
|
+
goal,
|
|
1460
|
+
raw: args.codexGoalJson,
|
|
1461
|
+
evidence,
|
|
1462
|
+
...scope === undefined ? {} : { scope }
|
|
1463
|
+
});
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
if (!(error instanceof UlwLoopError))
|
|
1466
|
+
throw error;
|
|
1467
|
+
codexValidationError = error;
|
|
1400
1468
|
}
|
|
1401
1469
|
if (closesBatch)
|
|
1402
1470
|
requireBatchFinalReady(plan, goal);
|
|
@@ -1405,14 +1473,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1405
1473
|
if (final)
|
|
1406
1474
|
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
|
|
1407
1475
|
if (final || aggregateCompletion !== undefined || closesBatch) {
|
|
1408
|
-
|
|
1409
|
-
repoRoot,
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1476
|
+
try {
|
|
1477
|
+
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1478
|
+
repoRoot,
|
|
1479
|
+
fs: QUALITY_GATE_FS,
|
|
1480
|
+
reviewerSurface: resolveToolkitSurface(),
|
|
1481
|
+
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1482
|
+
});
|
|
1483
|
+
requireBatchGate(plan, goal, qualityGate);
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
|
|
1486
|
+
throw error;
|
|
1487
|
+
throw combineCheckpointValidationErrors(codexValidationError, error);
|
|
1488
|
+
}
|
|
1415
1489
|
}
|
|
1490
|
+
if (codexValidationError !== undefined)
|
|
1491
|
+
throw codexValidationError;
|
|
1416
1492
|
goal.status = "complete";
|
|
1417
1493
|
goal.completedAt = now;
|
|
1418
1494
|
goal.evidence = evidence;
|
|
@@ -1498,7 +1574,8 @@ function gateTemplate(surface, base) {
|
|
|
1498
1574
|
recommendation: "APPROVE",
|
|
1499
1575
|
reportPath: artifactPath(base, "gate-review.md"),
|
|
1500
1576
|
evidence: "<replace:gate review evidence>",
|
|
1501
|
-
blockers: []
|
|
1577
|
+
blockers: [],
|
|
1578
|
+
notes: []
|
|
1502
1579
|
},
|
|
1503
1580
|
iteration: {
|
|
1504
1581
|
fullRerun: true,
|
|
@@ -1537,12 +1614,20 @@ async function checkpointTemplate(repoRoot, scope, goalId) {
|
|
|
1537
1614
|
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
1538
1615
|
const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
|
|
1539
1616
|
const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
|
|
1617
|
+
const guidance = [
|
|
1618
|
+
"codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
|
|
1619
|
+
"Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
|
|
1620
|
+
'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
|
|
1621
|
+
'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
|
|
1622
|
+
...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
|
|
1623
|
+
].join(" ");
|
|
1540
1624
|
return {
|
|
1541
1625
|
qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
|
|
1542
1626
|
codexGoalTemplate: {
|
|
1543
1627
|
goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
|
|
1544
1628
|
},
|
|
1545
|
-
|
|
1629
|
+
guidance,
|
|
1630
|
+
...hasAttempt ? { attemptDir } : {}
|
|
1546
1631
|
};
|
|
1547
1632
|
}
|
|
1548
1633
|
|
|
@@ -2094,7 +2179,7 @@ function kindMessage(prefix) {
|
|
|
2094
2179
|
|
|
2095
2180
|
${STEERING_KIND_HELP}`;
|
|
2096
2181
|
}
|
|
2097
|
-
function
|
|
2182
|
+
function text2(value, field) {
|
|
2098
2183
|
if (value === undefined)
|
|
2099
2184
|
return;
|
|
2100
2185
|
const trimmed = value.trim();
|
|
@@ -2103,11 +2188,11 @@ function text3(value, field) {
|
|
|
2103
2188
|
return fail2(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
|
|
2104
2189
|
}
|
|
2105
2190
|
function required3(argv, flag) {
|
|
2106
|
-
const value =
|
|
2191
|
+
const value = text2(readValue(argv, flag), flag);
|
|
2107
2192
|
return value ?? fail2(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
|
|
2108
2193
|
}
|
|
2109
2194
|
function requiredGoal(argv) {
|
|
2110
|
-
const value =
|
|
2195
|
+
const value = text2(parseGoalArg(argv), "--goal-id");
|
|
2111
2196
|
return value ?? fail2("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
|
|
2112
2197
|
}
|
|
2113
2198
|
function readObject(value, key) {
|
|
@@ -2158,14 +2243,14 @@ function parseSteeringSource(argv) {
|
|
|
2158
2243
|
function child(value) {
|
|
2159
2244
|
if (!isPlain(value))
|
|
2160
2245
|
return null;
|
|
2161
|
-
const title =
|
|
2162
|
-
const objective =
|
|
2246
|
+
const title = text2(objectText(value, "title"), "title");
|
|
2247
|
+
const objective = text2(objectText(value, "objective"), "objective");
|
|
2163
2248
|
if (title === undefined || objective === undefined)
|
|
2164
2249
|
return null;
|
|
2165
2250
|
return { title, objective };
|
|
2166
2251
|
}
|
|
2167
2252
|
async function children(argv, flag, needed) {
|
|
2168
|
-
const input = needed ? required3(argv, flag) :
|
|
2253
|
+
const input = needed ? required3(argv, flag) : text2(readValue(argv, flag), flag);
|
|
2169
2254
|
if (input === undefined)
|
|
2170
2255
|
return [];
|
|
2171
2256
|
const raw = await readJsonInput(input);
|
|
@@ -2188,12 +2273,12 @@ async function stringArray2(argv, flag) {
|
|
|
2188
2273
|
for (const item of raw) {
|
|
2189
2274
|
if (typeof item !== "string")
|
|
2190
2275
|
return fail2(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
|
|
2191
|
-
values.push(
|
|
2276
|
+
values.push(text2(item, flag) ?? "");
|
|
2192
2277
|
}
|
|
2193
2278
|
return values;
|
|
2194
2279
|
}
|
|
2195
2280
|
function model(value) {
|
|
2196
|
-
const trimmed =
|
|
2281
|
+
const trimmed = text2(value, "--user-model");
|
|
2197
2282
|
if (trimmed === undefined)
|
|
2198
2283
|
return;
|
|
2199
2284
|
return isModel(trimmed) ? trimmed : fail2(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
|
|
@@ -2204,7 +2289,7 @@ function neverKind(kind) {
|
|
|
2204
2289
|
async function parseSteeringProposal(argv) {
|
|
2205
2290
|
const kind = parseSteeringKind(argv);
|
|
2206
2291
|
const source = parseSteeringSource(argv);
|
|
2207
|
-
const idempotencyKey =
|
|
2292
|
+
const idempotencyKey = text2(readValue(argv, "--idempotency-key"), "--idempotency-key");
|
|
2208
2293
|
const base = { kind, source, evidence: required3(argv, "--evidence"), rationale: required3(argv, "--rationale"), ...idempotencyKey === undefined ? {} : { idempotencyKey } };
|
|
2209
2294
|
switch (kind) {
|
|
2210
2295
|
case "add_subgoal":
|
|
@@ -2247,36 +2332,36 @@ async function parseSteeringProposal(argv) {
|
|
|
2247
2332
|
function normalizedChildren(values) {
|
|
2248
2333
|
if (values === undefined)
|
|
2249
2334
|
return;
|
|
2250
|
-
return values.map((item) => ({ title:
|
|
2335
|
+
return values.map((item) => ({ title: text2(item.title, "child.title") ?? "", objective: text2(item.objective, "child.objective") ?? "" }));
|
|
2251
2336
|
}
|
|
2252
2337
|
function normalizedStrings(values, field) {
|
|
2253
2338
|
if (values === undefined)
|
|
2254
2339
|
return;
|
|
2255
|
-
return values.map((value) =>
|
|
2340
|
+
return values.map((value) => text2(value, field) ?? "");
|
|
2256
2341
|
}
|
|
2257
2342
|
function normalizeSteeringProposal(proposal) {
|
|
2258
|
-
const evidence =
|
|
2259
|
-
const rationale =
|
|
2260
|
-
const goalId =
|
|
2261
|
-
const targetGoalId =
|
|
2343
|
+
const evidence = text2(proposal.evidence, "evidence") ?? "";
|
|
2344
|
+
const rationale = text2(proposal.rationale, "rationale") ?? "";
|
|
2345
|
+
const goalId = text2(proposal.goalId, "goalId");
|
|
2346
|
+
const targetGoalId = text2(proposal.targetGoalId, "targetGoalId");
|
|
2262
2347
|
const targetGoalIds = normalizedStrings(proposal.targetGoalIds, "targetGoalIds");
|
|
2263
|
-
const criterionId =
|
|
2264
|
-
const title =
|
|
2265
|
-
const objective =
|
|
2266
|
-
const revisedTitle =
|
|
2267
|
-
const revisedObjective =
|
|
2268
|
-
const blockedReason =
|
|
2269
|
-
const directiveText =
|
|
2270
|
-
const promptSignature =
|
|
2271
|
-
const idempotencyKey =
|
|
2272
|
-
const scenario =
|
|
2273
|
-
const expectedEvidence =
|
|
2348
|
+
const criterionId = text2(proposal.criterionId, "criterionId");
|
|
2349
|
+
const title = text2(proposal.title, "title");
|
|
2350
|
+
const objective = text2(proposal.objective, "objective");
|
|
2351
|
+
const revisedTitle = text2(proposal.revisedTitle, "revisedTitle");
|
|
2352
|
+
const revisedObjective = text2(proposal.revisedObjective, "revisedObjective");
|
|
2353
|
+
const blockedReason = text2(proposal.blockedReason, "blockedReason");
|
|
2354
|
+
const directiveText = text2(proposal.directiveText, "directiveText");
|
|
2355
|
+
const promptSignature = text2(proposal.promptSignature, "promptSignature");
|
|
2356
|
+
const idempotencyKey = text2(proposal.idempotencyKey, "idempotencyKey");
|
|
2357
|
+
const scenario = text2(proposal.scenario, "scenario");
|
|
2358
|
+
const expectedEvidence = text2(proposal.expectedEvidence, "expectedEvidence");
|
|
2274
2359
|
const childGoals = normalizedChildren(proposal.childGoals);
|
|
2275
2360
|
const pendingOrder = normalizedStrings(proposal.pendingOrder, "pendingOrder");
|
|
2276
2361
|
return { kind: proposal.kind, source: proposal.source, evidence, rationale, ...goalId === undefined ? {} : { goalId }, ...targetGoalId === undefined ? {} : { targetGoalId }, ...targetGoalIds === undefined ? {} : { targetGoalIds }, ...criterionId === undefined ? {} : { criterionId }, ...title === undefined ? {} : { title }, ...objective === undefined ? {} : { objective }, ...childGoals === undefined ? {} : { childGoals }, ...revisedTitle === undefined ? {} : { revisedTitle }, ...revisedObjective === undefined ? {} : { revisedObjective }, ...pendingOrder === undefined ? {} : { pendingOrder }, ...blockedReason === undefined ? {} : { blockedReason }, ...proposal.after === undefined ? {} : { after: proposal.after }, ...directiveText === undefined ? {} : { directiveText }, ...promptSignature === undefined ? {} : { promptSignature }, ...idempotencyKey === undefined ? {} : { idempotencyKey }, ...proposal.now === undefined ? {} : { now: proposal.now }, ...scenario === undefined ? {} : { scenario }, ...expectedEvidence === undefined ? {} : { expectedEvidence }, ...proposal.userModel === undefined ? {} : { userModel: proposal.userModel } };
|
|
2277
2362
|
}
|
|
2278
2363
|
async function parseSteeringProposals(argv) {
|
|
2279
|
-
const input =
|
|
2364
|
+
const input = text2(readValue(argv, "--proposals-json"), "--proposals-json");
|
|
2280
2365
|
if (input === undefined)
|
|
2281
2366
|
return [await parseSteeringProposal(argv)];
|
|
2282
2367
|
if (readValue(argv, "--kind") !== undefined)
|
|
@@ -2468,7 +2553,7 @@ function activeGoalActions(plan, active) {
|
|
|
2468
2553
|
// components/ulw-loop/src/steering-mutations.ts
|
|
2469
2554
|
var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2470
2555
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2471
|
-
var
|
|
2556
|
+
var text3 = (value, key) => {
|
|
2472
2557
|
const candidate = read2(value, key);
|
|
2473
2558
|
return isText(candidate) ? candidate.trim() : undefined;
|
|
2474
2559
|
};
|
|
@@ -2477,13 +2562,13 @@ var after = (proposal) => {
|
|
|
2477
2562
|
const candidate = read2(proposal, "after");
|
|
2478
2563
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
|
|
2479
2564
|
};
|
|
2480
|
-
var revised = (proposal, direct, nested) =>
|
|
2481
|
-
var targets = (proposal) => proposal.targetGoalIds ?? [proposal.targetGoalId ??
|
|
2565
|
+
var revised = (proposal, direct, nested) => text3(proposal, direct) ?? text3(after(proposal) ?? proposal, nested);
|
|
2566
|
+
var targets = (proposal) => proposal.targetGoalIds ?? [proposal.targetGoalId ?? text3(proposal, "goalId") ?? ""].filter(Boolean);
|
|
2482
2567
|
var child2 = (value) => {
|
|
2483
2568
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2484
2569
|
return null;
|
|
2485
|
-
const title =
|
|
2486
|
-
const objective =
|
|
2570
|
+
const title = text3(value, "title");
|
|
2571
|
+
const objective = text3(value, "objective");
|
|
2487
2572
|
return title === undefined || objective === undefined ? null : { title, objective };
|
|
2488
2573
|
};
|
|
2489
2574
|
var children2 = (proposal) => {
|
|
@@ -2548,7 +2633,7 @@ function reviseCriterion(plan, proposal, now) {
|
|
|
2548
2633
|
if (target === undefined || current === undefined)
|
|
2549
2634
|
return;
|
|
2550
2635
|
const model2 = read2(proposal, "userModel");
|
|
2551
|
-
target.successCriteria[index] = { ...current, scenario:
|
|
2636
|
+
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model2) ? model2 : current.userModel };
|
|
2552
2637
|
target.updatedAt = now;
|
|
2553
2638
|
}
|
|
2554
2639
|
|
|
@@ -2583,7 +2668,7 @@ var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
|
2583
2668
|
var isPlain2 = (value) => isObject2(value) && !Array.isArray(value);
|
|
2584
2669
|
var read3 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2585
2670
|
var isText2 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2586
|
-
var
|
|
2671
|
+
var text4 = (value, key) => {
|
|
2587
2672
|
const candidate = read3(value, key);
|
|
2588
2673
|
return isText2(candidate) ? candidate.trim() : undefined;
|
|
2589
2674
|
};
|
|
@@ -2596,19 +2681,19 @@ var texts = (value, key) => {
|
|
|
2596
2681
|
};
|
|
2597
2682
|
function targets2(proposal) {
|
|
2598
2683
|
const many = texts(proposal, "targetGoalIds");
|
|
2599
|
-
const one =
|
|
2684
|
+
const one = text4(proposal, "targetGoalId") ?? text4(proposal, "goalId");
|
|
2600
2685
|
return many.length > 0 ? many : one === undefined ? [] : [one];
|
|
2601
2686
|
}
|
|
2602
2687
|
var after2 = (proposal) => {
|
|
2603
2688
|
const candidate = read3(proposal, "after");
|
|
2604
2689
|
return isPlain2(candidate) ? candidate : undefined;
|
|
2605
2690
|
};
|
|
2606
|
-
var revised2 = (proposal, direct, nested) =>
|
|
2691
|
+
var revised2 = (proposal, direct, nested) => text4(proposal, direct) ?? text4(after2(proposal) ?? proposal, nested);
|
|
2607
2692
|
function child3(value) {
|
|
2608
2693
|
if (!isPlain2(value))
|
|
2609
2694
|
return null;
|
|
2610
|
-
const title =
|
|
2611
|
-
const objective =
|
|
2695
|
+
const title = text4(value, "title");
|
|
2696
|
+
const objective = text4(value, "objective");
|
|
2612
2697
|
if (title === undefined || objective === undefined)
|
|
2613
2698
|
return null;
|
|
2614
2699
|
return { title, objective };
|
|
@@ -2647,15 +2732,15 @@ function auditFor(proposal, reasons) {
|
|
|
2647
2732
|
const object = isPlain2(proposal) ? proposal : undefined;
|
|
2648
2733
|
const kindRaw = object === undefined ? undefined : read3(object, "kind");
|
|
2649
2734
|
const sourceRaw = object === undefined ? undefined : read3(object, "source");
|
|
2650
|
-
const evidence = object === undefined ? "" :
|
|
2651
|
-
const rationale = object === undefined ? "" :
|
|
2735
|
+
const evidence = object === undefined ? "" : text4(object, "evidence") ?? "";
|
|
2736
|
+
const rationale = object === undefined ? "" : text4(object, "rationale") ?? "";
|
|
2652
2737
|
const audit = { kind: isKind2(kindRaw) ? kindRaw : "annotate_ledger", source: isSource2(sourceRaw) ? sourceRaw : "cli", targetGoalIds: object === undefined ? [] : targets2(object), evidence, rationale, invariant: { accepted: reasons.length === 0, structuralInvariantAccepted: reasons.length === 0, evidenceBackedNecessity: evidence.length > 0 && rationale.length > 0, noEasierCompletion: !weakens(proposal), rejectedReasons: reasons, reasons } };
|
|
2653
2738
|
if (object === undefined)
|
|
2654
2739
|
return audit;
|
|
2655
|
-
const criterionId =
|
|
2656
|
-
const directiveText =
|
|
2657
|
-
const promptSignature =
|
|
2658
|
-
const idempotencyKey =
|
|
2740
|
+
const criterionId = text4(object, "criterionId");
|
|
2741
|
+
const directiveText = text4(object, "directiveText");
|
|
2742
|
+
const promptSignature = text4(object, "promptSignature");
|
|
2743
|
+
const idempotencyKey = text4(object, "idempotencyKey");
|
|
2659
2744
|
if (criterionId !== undefined)
|
|
2660
2745
|
audit.criterionId = criterionId;
|
|
2661
2746
|
if (directiveText !== undefined)
|
|
@@ -2676,9 +2761,9 @@ function validateUlwLoopSteeringProposal(plan, proposal) {
|
|
|
2676
2761
|
reasons.push(`invalid kind: ${String(kind)}`);
|
|
2677
2762
|
if (!isSource2(read3(object, "source")))
|
|
2678
2763
|
reasons.push(`invalid source: ${String(read3(object, "source"))}`);
|
|
2679
|
-
if (
|
|
2764
|
+
if (text4(object, "evidence") === undefined)
|
|
2680
2765
|
reasons.push("missing evidence");
|
|
2681
|
-
if (
|
|
2766
|
+
if (text4(object, "rationale") === undefined)
|
|
2682
2767
|
reasons.push("missing rationale");
|
|
2683
2768
|
if (hasProtected(proposal))
|
|
2684
2769
|
reasons.push("protected payload");
|
|
@@ -2695,7 +2780,7 @@ function goal2(plan, id) {
|
|
|
2695
2780
|
}
|
|
2696
2781
|
function validateKind(plan, proposal, kind, reasons) {
|
|
2697
2782
|
const target = goal2(plan, targets2(proposal)[0]);
|
|
2698
|
-
if (kind === "add_subgoal" && (
|
|
2783
|
+
if (kind === "add_subgoal" && (text4(proposal, "title") === undefined || text4(proposal, "objective") === undefined))
|
|
2699
2784
|
reasons.push("add_subgoal requires title/objective");
|
|
2700
2785
|
if ((kind === "split_subgoal" || kind === "revise_pending_wording" || kind === "mark_blocked_superseded") && target === undefined)
|
|
2701
2786
|
reasons.push(`${kind} requires target`);
|
|
@@ -2725,7 +2810,7 @@ function validateOrder(plan, proposal, reasons) {
|
|
|
2725
2810
|
}
|
|
2726
2811
|
function validateCriterion(plan, proposal, reasons) {
|
|
2727
2812
|
const target = goal2(plan, targets2(proposal)[0]);
|
|
2728
|
-
const criterionId =
|
|
2813
|
+
const criterionId = text4(proposal, "criterionId");
|
|
2729
2814
|
if (target === undefined)
|
|
2730
2815
|
reasons.push("revise_criterion requires goalId");
|
|
2731
2816
|
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
|
|
@@ -2760,8 +2845,8 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
2760
2845
|
function isProposal(value) {
|
|
2761
2846
|
return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
|
|
2762
2847
|
}
|
|
2763
|
-
function parseUlwLoopSteeringDirective(
|
|
2764
|
-
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(
|
|
2848
|
+
function parseUlwLoopSteeringDirective(text5) {
|
|
2849
|
+
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text5);
|
|
2765
2850
|
if (match?.[1] === undefined)
|
|
2766
2851
|
return null;
|
|
2767
2852
|
try {
|
|
@@ -3435,14 +3520,14 @@ function optionalString(value) {
|
|
|
3435
3520
|
return value === undefined || typeof value === "string";
|
|
3436
3521
|
}
|
|
3437
3522
|
function readAll(stdin) {
|
|
3438
|
-
return new Promise((
|
|
3523
|
+
return new Promise((resolve4, reject) => {
|
|
3439
3524
|
let data = "";
|
|
3440
3525
|
stdin.setEncoding("utf8");
|
|
3441
3526
|
stdin.on("data", (chunk) => {
|
|
3442
3527
|
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
3443
3528
|
});
|
|
3444
3529
|
stdin.once("error", reject);
|
|
3445
|
-
stdin.once("end", () =>
|
|
3530
|
+
stdin.once("end", () => resolve4(data));
|
|
3446
3531
|
});
|
|
3447
3532
|
}
|
|
3448
3533
|
|
|
@@ -3740,7 +3825,7 @@ function readPlan(goalsPath) {
|
|
|
3740
3825
|
|
|
3741
3826
|
// components/ulw-loop/src/stop-resume-hook.ts
|
|
3742
3827
|
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3743
|
-
import { isAbsolute as isAbsolute2, join as join4, resolve as
|
|
3828
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
|
|
3744
3829
|
var RESUME_CAP = 2;
|
|
3745
3830
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3746
3831
|
"context compacted",
|
|
@@ -3798,8 +3883,8 @@ function isResumableStatus(status2) {
|
|
|
3798
3883
|
}
|
|
3799
3884
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3800
3885
|
const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
|
|
3801
|
-
const counterPath =
|
|
3802
|
-
const stuckPath =
|
|
3886
|
+
const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
|
|
3887
|
+
const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3803
3888
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
3804
3889
|
return false;
|
|
3805
3890
|
const previous = readCounter(counterPath);
|
|
@@ -3813,7 +3898,7 @@ function consumeResumeBudget(stateDir, goalId) {
|
|
|
3813
3898
|
return true;
|
|
3814
3899
|
}
|
|
3815
3900
|
function isInsideDir(dir, candidate) {
|
|
3816
|
-
return candidate.startsWith(
|
|
3901
|
+
return candidate.startsWith(resolve4(dir) + sep2);
|
|
3817
3902
|
}
|
|
3818
3903
|
function renderResumeDirective(plan, goal3, sessionId) {
|
|
3819
3904
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|