/** * Goal Tracking Service * * Manages project-level goal files, current team focus, and a decisions log * with retrospective outcomes. All files are stored as Markdown under * `{projectPath}/.crewly/goals/`. * * Files managed: * - `goals.md` — Append-only log of user-stated goals * - `current_focus.md` — Overwritten each time the team focus changes * - `decisions_log.md` — Append-only decisions with mutable outcome sections * * @module services/memory/goal-tracking.service */ /** * Parameters for logging a decision via {@link GoalTrackingService.logDecision}. */ export interface DecisionParams { /** Short descriptive title for the decision */ title: string; /** The decision that was made */ decision: string; /** Reasoning behind the decision */ rationale: string; /** Agent or user who made the decision */ decidedBy: string; /** Alternative options that were considered */ alternatives?: string[]; } /** * Service for tracking project goals, team focus, and architectural decisions. * * Follows the singleton pattern for consistent state management across * the backend. Uses atomic writes for full-file overwrites and plain * `fs.appendFile` for append-only logs (goals, decisions) where partial * writes are acceptable. * * @example * ```typescript * const goalService = GoalTrackingService.getInstance(); * * // Set a project goal * await goalService.setGoal('/path/to/project', 'Ship v2.0 by March 15th', 'user'); * * // Update team focus * await goalService.updateFocus('/path/to/project', 'Completing auth module'); * * // Log a decision * const decId = await goalService.logDecision('/path/to/project', { * title: 'Use PostgreSQL over MongoDB', * decision: 'PostgreSQL for primary data store', * rationale: 'Relational data model fits our schema better', * decidedBy: 'orchestrator', * alternatives: ['MongoDB', 'DynamoDB'], * }); * * // Later, record the outcome * await goalService.updateDecisionOutcome( * '/path/to/project', * decId, * 'Successful - query performance is excellent', * 'Invest time in schema design upfront to avoid migrations', * ); * ``` */ export declare class GoalTrackingService { private static instance; /** Monotonic counter to ensure unique decision IDs even within the same millisecond */ private decisionCounter; private readonly logger; /** * Creates a new GoalTrackingService instance. * Use {@link getInstance} for the singleton. */ private constructor(); /** * Gets the singleton instance of GoalTrackingService. * * @returns The singleton GoalTrackingService instance */ static getInstance(): GoalTrackingService; /** * Clears the singleton instance. * Intended for test isolation only — never call in production. */ static clearInstance(): void; /** * Returns the absolute path to the goals directory for a project. * * @param projectPath - Absolute path to the project root * @returns Absolute path to `{projectPath}/.crewly/goals` */ private getGoalsDir; /** * Safely reads a file and returns its content, or `null` if the file * does not exist. * * Permission errors and other unexpected failures are re-thrown. * * @param filePath - Absolute path to the file * @returns File content as a UTF-8 string, or `null` if not found */ private safeReadFile; /** * Appends a new goal entry to `goals.md`. * * If the file does not yet exist it is created with a Markdown header * before the first entry is appended. * * @param projectPath - Absolute path to the project root * @param goal - The goal text to record * @param setBy - Who set the goal (defaults to `'user'`) * * @example * ```typescript * await goalService.setGoal('/projects/phoenix', 'Achieve 80% test coverage', 'user'); * ``` */ setGoal(projectPath: string, goal: string, setBy?: string): Promise; /** * Reads and returns the full contents of `goals.md`. * * @param projectPath - Absolute path to the project root * @returns The goals file content, or `null` if the file does not exist * * @example * ```typescript * const goals = await goalService.getGoals('/projects/phoenix'); * if (goals) { * console.log(goals); * } * ``` */ getGoals(projectPath: string): Promise; /** * Overwrites `current_focus.md` with the new focus description. * * Uses {@link atomicWriteFile} to ensure the file is never left in a * partial-write state. * * @param projectPath - Absolute path to the project root * @param focus - The new focus description * @param updatedBy - Who updated the focus (defaults to `'orchestrator'`) * * @example * ```typescript * await goalService.updateFocus( * '/projects/phoenix', * 'Completing the authentication module and writing integration tests', * 'orchestrator', * ); * ``` */ updateFocus(projectPath: string, focus: string, updatedBy?: string): Promise; /** * Reads and returns the full contents of `current_focus.md`. * * @param projectPath - Absolute path to the project root * @returns The current focus file content, or `null` if the file does not exist * * @example * ```typescript * const focus = await goalService.getCurrentFocus('/projects/phoenix'); * ``` */ getCurrentFocus(projectPath: string): Promise; /** * Appends a new decision entry to `decisions_log.md` and returns a * unique decision ID. * * If the file does not yet exist it is created with a Markdown header * before the first entry is appended. * * The outcome field is initially set to `_pending_` and can be updated * later via {@link updateDecisionOutcome}. * * @param projectPath - Absolute path to the project root * @param decision - Decision parameters (title, decision, rationale, etc.) * @returns The generated decision ID (format: `dec-{timestamp}-{counter}`) * * @throws {Error} If the file system write fails * * @example * ```typescript * const decisionId = await goalService.logDecision('/projects/phoenix', { * title: 'Use Redis for caching', * decision: 'Redis over Memcached', * rationale: 'Better data structure support and persistence options', * decidedBy: 'orchestrator', * alternatives: ['Memcached', 'In-memory Map'], * }); * // decisionId => 'dec-1707500000000-0' * ``` */ logDecision(projectPath: string, decision: DecisionParams): Promise; /** * Updates the outcome of a previously logged decision. * * Reads `decisions_log.md`, locates the section matching the given * `decisionId`, replaces the `**Outcome:** _pending_` line with the * actual outcome (plus optional learnings and a timestamp), and writes * the file back atomically. * * If the decision ID is not found or the file does not exist, a warning * is logged and the method returns without throwing. * * @param projectPath - Absolute path to the project root * @param decisionId - The decision ID returned by {@link logDecision} * @param outcome - Description of the actual outcome * @param learnings - Optional lessons learned from this decision * * @example * ```typescript * await goalService.updateDecisionOutcome( * '/projects/phoenix', * 'dec-1707500000000', * 'Successful - Redis reduced API latency by 40%', * 'Set explicit TTLs on all cache keys to prevent stale data', * ); * ``` */ updateDecisionOutcome(projectPath: string, decisionId: string, outcome: string, learnings?: string): Promise; /** * Reads and returns the full contents of `decisions_log.md`. * * @param projectPath - Absolute path to the project root * @returns The decisions log content, or `null` if the file does not exist * * @example * ```typescript * const log = await goalService.getDecisionsLog('/projects/phoenix'); * if (log) { * console.log(log); * } * ``` */ getDecisionsLog(projectPath: string): Promise; } //# sourceMappingURL=goal-tracking.service.d.ts.map