Files
oh-my-opencode/packages/omo-claude/plugin/components/ultragoal/src/goal-status.ts
T
YeonGyu-Kim 71e4e2a6d0 feat(omo-claude): port ultragoal to per-session file-based goals
Goal content under ./.omo/ultragoal/sessions/claude-<id>/ keyed by Claude Code
session_id (claude: prefix); UltragoalScope struct threaded; plan version 2 +
index.json registry with read-only v1 forward-migration (never deletes v1);
create_goal/get_goal/update_goal dependency dropped (file/steering-based); the
PreToolUse create_goal budget guard kept compiled+unit-tested but its hooks.json
registration removed (inert in CC). 26 tests pass. ultragoal removed from
sync-components HANDLED set — it is a hand-fork, not patch-synced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:40:11 +09:00

85 lines
3.1 KiB
TypeScript

import type {
UltragoalGoalMode,
UltragoalItem,
UltragoalPlan,
UltragoalStatus,
UltragoalSuccessCriterion,
} from "./types.js";
export const ULTRAGOAL_AGGREGATE_OBJECTIVE: string =
"Complete the durable ultragoal plan in .omo/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omo/ultragoal/ledger.jsonl as the audit trail.";
export function goalMode(plan: UltragoalPlan): UltragoalGoalMode {
return plan.goalMode ?? "per_story";
}
function isResolvedStatus(status: UltragoalStatus): boolean {
return status === "complete";
}
function isSupersededResolved(goal: UltragoalItem, plan: UltragoalPlan): boolean {
if (goal.steeringStatus !== "superseded") return false;
const replacements = goal.supersededBy ?? [];
if (replacements.length === 0) return false;
return replacements.every((id) => {
const replacement = plan.goals.find((candidate) => candidate.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
function isCompletionBlocking(goal: UltragoalItem, plan: UltragoalPlan): boolean {
if (goal.steeringStatus === "superseded") return !isSupersededResolved(goal, plan);
if (goal.steeringStatus === "blocked") return true;
return !isResolvedStatus(goal.status);
}
function isCompletionBlockingForFinalCandidate(
candidate: UltragoalItem,
finalCandidate: UltragoalItem,
plan: UltragoalPlan,
): boolean {
if (candidate.id === finalCandidate.id) return false;
if (candidate.steeringStatus === "superseded") {
const replacements = candidate.supersededBy ?? [];
if (replacements.length === 0) return true;
return !replacements.every((id) => {
if (id === finalCandidate.id) return true;
const replacement = plan.goals.find((goal) => goal.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
return isCompletionBlocking(candidate, plan);
}
export function isUltragoalDone(plan: UltragoalPlan): boolean {
if (plan.aggregateCompletion?.status === "complete") return true;
return plan.goals.every((goal) => !isCompletionBlocking(goal, plan));
}
export function isFinalRunCompletionCandidate(plan: UltragoalPlan, goal: UltragoalItem): boolean {
return (
isCompletionBlocking(goal, plan) &&
plan.goals.every((candidate) => !isCompletionBlockingForFinalCandidate(candidate, goal, plan))
);
}
export function aggregateObjective(plan: UltragoalPlan): string {
return plan.objective ?? ULTRAGOAL_AGGREGATE_OBJECTIVE;
}
export function expectedObjective(plan: UltragoalPlan, goal: UltragoalItem): string {
return goalMode(plan) === "aggregate" ? aggregateObjective(plan) : goal.objective;
}
export function compatibleObjectives(plan: UltragoalPlan): readonly string[] {
return [aggregateObjective(plan), ...(plan.objectiveAliases ?? [])];
}
export function hasAllCriteriaPass(goal: UltragoalItem): boolean {
return goal.successCriteria.length > 0 && goal.successCriteria.every((criterion) => criterion.status === "pass");
}
export function firstUnresolvedCriterion(goal: UltragoalItem): UltragoalSuccessCriterion | undefined {
return goal.successCriteria.find((criterion) => criterion.status !== "pass");
}