/** * Key Result Tracking Service * * CRUD, measurement recording, progress aggregation, and WorkItem linkage * for structured Key Results attached to Missions. * * Does NOT contain LLM logic — all status derivation is pure math. * Agent reasoning happens in skills (review-mission, measure-kr). * * @module services/v3/kr-tracking.service */ import type { WorkItem } from '../../types/v2/work-item.types.js'; import { type Mission } from '../../types/v2/mission.types.js'; import { type KeyResult, type KRMeasurement, type CreateKeyResultInput, type UpdateKeyResultInput, type MissionOKRSummary, type CascadeOKRSummary } from '../../types/v2/key-result.types.js'; /** * Service for managing Key Result lifecycle, measurements, and aggregation. */ export declare class KRTrackingService { private static instance; private readonly logger; private constructor(); /** * Get the singleton instance. */ static getInstance(): KRTrackingService; /** * Reset the singleton (for testing). */ static resetInstance(): void; /** * Create a new Key Result for a mission. * * @param input - Validated creation input * @returns The created KeyResult */ create(input: CreateKeyResultInput): Promise; /** * Get a Key Result by ID. * * @param missionId - Parent mission ID * @param krId - Key Result ID * @returns The KeyResult, or null if not found */ get(missionId: string, krId: string): Promise; /** * List all Key Results for a mission. * * @param missionId - Mission ID * @returns Array of KeyResults */ listByMission(missionId: string): Promise; /** * Update a Key Result. * * @param missionId - Parent mission ID * @param krId - Key Result ID * @param update - Fields to update * @returns Updated KeyResult, or null if not found */ update(missionId: string, krId: string, update: UpdateKeyResultInput): Promise; /** * Delete a Key Result. * * @param missionId - Parent mission ID * @param krId - Key Result ID */ delete(missionId: string, krId: string): Promise; /** * Record a new measurement for a Key Result. * Automatically updates current value, recomputes status, * and appends to measurement history. * * @param missionId - Parent mission ID * @param krId - Key Result ID * @param value - Measured value * @param source - Who/what took the measurement * @param note - Optional context * @returns The recorded measurement, or null if KR not found */ recordMeasurement(missionId: string, krId: string, value: number, source: string, note?: string): Promise; /** * Link a WorkItem to a Key Result. * * @param missionId - Parent mission ID * @param krId - Key Result ID * @param workItemId - WorkItem ID to link */ linkWorkItem(missionId: string, krId: string, workItemId: string): Promise; /** * Handle WorkItem completion — auto-update KRs with task_completion source. * * For KRs measured by task completion, progress = completed linked items / total linked items. * * @param workItem - The completed WorkItem */ onWorkItemCompleted(workItem: WorkItem): Promise; /** * Compute aggregated OKR progress for a mission. * * @param missionId - Mission ID * @param staleCycles - Number of consecutive stale review cycles * @returns Aggregated summary with recommendation */ computeMissionOKRProgress(missionId: string, staleCycles?: number): Promise; /** * List the cascade-active direct children of a mission. * * Reads every mission document and keeps those whose `parentMissionId` * equals `parentId` AND that are cascade-active. A mission is cascade-active * iff its `approval` is absent (legacy missions) OR * `approval.state === 'approved'`. Draft / pending_approval / rejected * children are excluded from the roll-up per spec §4.2. * * @param parentId - Parent mission ID * @returns The approved direct-child missions (empty if none / on read error) * * @example * ```ts * const children = await KRTrackingService.getInstance().listChildMissions('team-1'); * ``` */ listChildMissions(parentId: string): Promise; /** * Compute cross-level cascade roll-up of OKR progress for a mission and its * approved descendants (company → team → project). * * Recursively walks the `parentMissionId` tree. At each node it reuses * {@link computeMissionOKRProgress} for that node's OWN KRs, then folds in the * `rolledUpProgress` of each approved child: * * - `rolledUpProgress` = equal-weight average of this node's own * `overallProgress` and each approved child's `rolledUpProgress` * (see {@link computeRolledUpProgress}); a leaf equals its own progress. * - The roll-up recommendation is derived over this node's own KR statuses * PLUS one synthetic status per child mapped from the child's * `rolledUpProgress` via {@link deriveCascadeChildStatus} — so "all children * off_track ⇒ parent escalates/replans" falls out naturally (including when * children are stalled at 0% progress). * * Orphans (a mission whose `parentMissionId` points at a missing or * non-approved parent) are treated as roots of their own subtree and never * crash. A `visited` set guards against cycles defensively (create/update * already reject cycles). * * @param missionId - Root mission of the subtree to roll up * @param staleCycles - Consecutive stale review cycles for the root node. When * omitted, the root's PERSISTED `staleCycles` is used so staleness-driven * replan/escalate is not silently suppressed at the queried node (spec §4.2). * Pass an explicit number to override the persisted value. * @returns The recursive {@link CascadeOKRSummary} for the subtree * * @example * ```ts * const summary = await KRTrackingService.getInstance().computeCascadeOKRProgress('co-1'); * console.log(summary.rolledUpProgress, summary.children.length); * ``` */ computeCascadeOKRProgress(missionId: string, staleCycles?: number): Promise; /** * Recursive worker for {@link computeCascadeOKRProgress}. * * @param missionId - Current node * @param staleCycles - Stale cycles applied to the current node only * @param byId - All missions indexed by ID * @param linkById - Lightweight parent-link map for level derivation * @param visited - Cycle-guard set of already-walked mission IDs * @returns The {@link CascadeOKRSummary} for this node's subtree */ private rollUp; /** * Whether a mission counts as cascade-active: approval absent (legacy) or * explicitly approved. * * @param mission - Mission to test * @returns `true` if the mission participates in the cascade roll-up */ private isCascadeActive; /** * Resolve a mission's cascade level: prefer its explicit `level`, else derive * it from the parent chain via {@link deriveLevel}. Missing/orphan missions * fall back to `company` (treated as a root). * * @param missionId - Mission whose level to resolve * @param byId - All missions indexed by ID * @param linkById - Lightweight parent-link map for derivation * @returns The resolved {@link MissionLevel} */ private resolveLevel; /** * Load all mission documents from disk, skipping unreadable files. * * @returns Array of missions (empty on a missing directory) */ private loadAllMissions; /** * Persist a KeyResult to disk. */ private persist; } //# sourceMappingURL=kr-tracking.service.d.ts.map