/** * Mission Context Service — auto-injectable mission card for agent prompts. * * Builds a compact (≤2KB) markdown card that summarizes the active * project OKR, the team's most-relevant active missions, and the team * sub-OKR (if any) so every agent wakes with the answer to "what * mission am I serving right now?". * * Per Memory Phase 1 / Fix M1: today, agents must call `recall` or * `get_goals` explicitly to find the mission they're serving — and per * the architecture audit, an agent can finish a sprint without ever * reading it. This service closes that gap by feeding a tiny mission * card into prompt assembly automatically. * * Out of scope for Phase 1 (deferred to later phases): * - Narrative summarization of mission state (no LLM round-trip here) * - Vector embedding / semantic recall * - Memory review queue / supersession * * @module services/memory/mission-context.service */ import { type Mission } from '../../types/v2/mission.types.js'; /** * Hard upper bound on the rendered mission card. The spec calls for * 1-2KB; we cap at 2KB and truncate by section priority (most-recent * status → oldest first) when over budget. */ export declare const MISSION_CONTEXT_HARD_CAP_BYTES = 2048; /** * Inputs accepted by `getContextForAgent`. * * `projectPath` is the absolute path to the project root (the directory * containing `.crewly/`). `teamSlug` is the human-readable team slug * used to resolve `-team-sub-okr.md`; if omitted, the team-sub-OKR * section is skipped. */ export interface MissionContextRequest { /** Agent session id, used for logging only. */ agentId: string; /** Absolute path to the project root that owns `.crewly/`. */ projectPath: string; /** Team id whose missions should be surfaced (matches `Mission.ownerTeamId`). */ teamId: string; /** * Optional ancestor team ids whose missions should ALSO be surfaced * to the agent — covers the OKR hierarchy case where a sub-team agent * needs to see the parent team / company-level mission they ladder up * to. Supplied by the caller (PromptBuilderService) which has team- * graph access; the service does no graph traversal of its own. */ ancestorTeamIds?: string[]; /** * Human-readable team slug used to find `-team-sub-okr.md` * under `.crewly/goals/`. If omitted, the team-sub-OKR section * is skipped. */ teamSlug?: string; /** Optional id (informational, not used for filtering today). */ projectId?: string; } /** * Builds a small markdown card describing the agent's mission context. * * Singleton, side-effect-free reads. All file IO is fail-soft: any * read or parse error skips the affected section but never throws into * the prompt-assembly path. * * @example * ```ts * const svc = MissionContextService.getInstance(); * const card = await svc.getContextForAgent({ * agentId: 'crewly-product-leo-21a5477e', * projectPath: '/Users/me/projects/crewly', * teamId: '28a4ac10-...', * teamSlug: 'product', * }); * if (card) prompt += `\n${card}`; * ``` */ export declare class MissionContextService { private static instance; private readonly logger; private readonly goalService; private constructor(); /** Get the shared singleton instance. */ static getInstance(): MissionContextService; /** Reset for tests. */ static clearInstance(): void; /** * Build the mission-context card for an agent. * * Returns the empty string when no signals are available — callers * should treat empty output as "skip the section entirely" rather * than rendering an empty heading. * * @param req - Request inputs * @returns A ≤2KB markdown card, or `''` when nothing is worth showing */ getContextForAgent(req: MissionContextRequest): Promise; /** * Read up to `GOALS_MAX_LINES` non-empty, non-comment lines from * the project's `goals.md`. Returns `[]` when the file is missing. */ private loadProjectGoalsSlice; /** * Read all `*.json` files under `/.crewly/missions/`, * filter to active missions whose `ownerTeamId` is in the supplied * `teamScope` (own team + any ancestor teams the caller passed in), * and sort by priority + recency. Returns `[]` on any error. */ private loadActiveTeamMissions; /** * Read the team sub-OKR file at * `/.crewly/goals/-team-sub-okr.md`. Returns * `[]` when no slug, no file, or any read error. */ private loadTeamSubOkrSlice; } /** * Recent mission status entry — pulled from `mission.lastReviewSummary`, * ordered by `lastReviewAt` desc. */ export interface RecentMissionStatus { missionId: string; objective: string; summary: string; reviewedAt?: string; } /** * Collect the most recent status updates across the supplied missions. * A mission contributes at most one entry. Missions with no * `lastReviewSummary` are skipped. Result is sorted by `lastReviewAt` * descending; missions with no `lastReviewAt` sort last. */ export declare function collectRecentStatusUpdates(missions: Mission[]): RecentMissionStatus[]; /** * Extract up to `maxLines` "meaningful" lines from a markdown blob. * * Rules: * - skip empty lines * - skip standalone HTML/MD comments * - keep headings, prose, list items * - trim trailing whitespace * - de-duplicate consecutive identical lines */ export declare function extractMeaningfulLines(raw: string, maxLines: number): string[]; /** * Render the final mission card as markdown using the spec template. * Sections with no data are omitted entirely (no empty headings). */ export declare function renderMissionCard(args: { goalsLines: string[]; missions: Mission[]; teamSubOkrLines: string[]; statusUpdates: RecentMissionStatus[]; }): string; /** * Enforce the hard byte cap. If `card` already fits, returns it * unchanged. Otherwise truncates line-by-line from the bottom until * it fits, then appends a single ellipsis line so the card is * obviously truncated. */ export declare function enforceHardCap(card: string, maxBytes: number): string; /** * Module-level convenience: get the singleton in one call. */ export declare const getMissionContextService: () => MissionContextService; //# sourceMappingURL=mission-context.service.d.ts.map