import { TeamMemberSessionConfig, SubordinateInfo, TeamMember, Team } from '../../types/index.js'; import type { ModuleConfig, OrgRole } from './prompt-modules/prompt-module.interface.js'; /** * Derive the organisational role for a member from team-hierarchy facts. * * Resolution cascade (first match wins): * 1. `role === 'orchestrator'` → `'orchestrator'` * 2. a lead by the team-lead rule → `'team-lead'` (`utils/team.utils` isTeamLead) * 3. `canDelegate === true` → `'team-lead'` * 4. has subordinates in the team → `'team-lead'` * 5. otherwise → `'executor'` * * This function is total — every member resolves to one of the three roles — * so it never throws. Fail-fast on misconfiguration is enforced downstream * by {@link RoleBoundaryModule} (a member with `canDelegate=true` whose * `orgRole` is undefined when the boundary is rendered indicates the * caller bypassed `buildModuleConfigFromTeamMember` — that boundary throws). * * @param member - Full TeamMember record * @param team - Full Team record (used to detect implicit subordination) * @returns The resolved organisational role */ /** Bounds for owner-declared member skill tags. */ export declare const MEMBER_SKILL_LIMITS: { readonly MAX_TAGS: 20; readonly MAX_TAG_LENGTH: 32; }; /** * Normalise owner-declared skill tags: trim, lowercase, drop empties and * duplicates, cap length and count. Returns `undefined` when nothing is left * so an absent field stays absent on the stored member. * * @param input - Raw tags from the API or a form (array or comma-separated) * @returns Clean tag list, or undefined when empty */ export declare function normalizeMemberSkills(input: unknown): string[] | undefined; /** * The `{{MEMBER_SKILLS_JSON}}` value for a prompt: the member's declared * skills, else its reported capabilities, else `[]`. Role prompts hand this * to poll-tasks so an agent polls with its own tags instead of an example. * * @param config - Module config for the member being prompted * @returns A JSON array literal */ export declare function memberSkillsJson(config: Pick): string; export declare function deriveOrgRole(member: TeamMember, team: Team): OrgRole; /** * Snapshot of the SessionConfig fields the helper needs that are NOT * carried on TeamMember/Team (runtime-host concerns: session name, project * path, runtime type, and the precomputed subordinate list). * * Splitting these out keeps {@link buildModuleConfigFromTeamMember} pure — * it can be invoked anywhere TeamMember + Team are available without * coupling to the legacy `TeamMemberSessionConfig` shape. */ export interface SessionRuntimeContext { /** Agent's session name (e.g. 'crewly-product-sam-dd2b46f7'). */ sessionName: string; /** Absolute path to the project directory. */ projectPath?: string; /** Runtime host (claude-code, gemini-cli, codex, crewly-agent). */ runtimeType?: ModuleConfig['runtimeType']; /** Optional precomputed subordinate list. When omitted, the helper * resolves it from `team.members` and `member.subordinateIds`. */ subordinates?: SubordinateInfo[]; /** Absolute path to agent skill scripts (e.g. `/config/skills/agent`). */ agentSkillsPath: string; /** Absolute path to team-leader skill scripts. */ tlSkillsPath: string; /** Absolute path to the project root (where config/ lives). */ projectRoot: string; } /** * Build a complete {@link ModuleConfig} from a TeamMember + Team pair. * * Wires every autonomy / organisation / team-context field declared on * `TeamMember` and `Team` into the prompt assembler — the gap that caused * every TL agent in production to silently render with the executor * boundary (because `orgRole` was never injected, and * `RoleBoundaryModule.build()` previously fell back to `'executor'`). * * Fields wired from `member`: * - `autonomyLevel`, `domainSOP`, `riskPolicy`, `capabilities` * - `jobTitle`, `jobDescription`, `ownershipScope`, `expertId` * * Fields wired from `team`: * - `team.mission` → `teamMission` * - `team.budget` → `teamBudget` * - `team.qualityGate` → `teamQualityGate` * - `team.serviceContract` → `serviceContract` * - `team.ownershipScope` → `teamOwnershipScope` * - `team.description` → `teamDescription` * * `orgRole` is resolved via {@link deriveOrgRole}. `canDelegate` is true for * a lead by the team-lead rule or a member flagged `canDelegate` * ({@link canMemberDelegate}), so lead-only modules (TL soul overlay, TL * skills, TL addon) follow the rule on the next wake. The subordinate list is * taken from `runtime.subordinates` when supplied, otherwise resolved from * `team.members` (for a rule lead without explicit reports: the rest of the team). * * @param member - Full TeamMember record * @param team - Full Team record * @param runtime - Runtime-host context (session name, project paths, etc.) * @returns A fully populated ModuleConfig * * @example * ```typescript * const config = buildModuleConfigFromTeamMember(member, team, { * sessionName: 'crewly-product-sam-dd2b46f7', * projectPath: '/Users/.../crewly', * runtimeType: 'claude-code', * agentSkillsPath: path.join(projectRoot, 'config/skills/agent'), * tlSkillsPath: path.join(projectRoot, 'config/skills/team-leader'), * projectRoot, * }); * const { prompt } = await new PromptAssemblyService().assemble(config); * ``` */ export declare function buildModuleConfigFromTeamMember(member: TeamMember, team: Team, runtime: SessionRuntimeContext): ModuleConfig; /** * Resolve a member's subordinate roster from the team record. * * Used when {@link buildModuleConfigFromTeamMember} is called without a * pre-computed `runtime.subordinates`. Maps `member.subordinateIds` to the * matching `team.members` entries, dropping any unresolved ids. */ export declare function resolveSubordinatesFromTeam(member: TeamMember, team: Team): SubordinateInfo[] | undefined; /** * Options for building system prompts */ export interface PromptOptions { /** Agent's role */ role?: string; /** Current task context for SOP selection */ taskContext?: string; /** Task type for SOP matching */ taskType?: string; /** Whether to include memory context (default: true) */ includeMemory?: boolean; /** Whether to include SOPs (default: true) */ includeSOPs?: boolean; /** Maximum number of SOPs to include */ sopLimit?: number; /** Maximum number of memory entries to include */ memoryLimit?: number; /** Force reload context instead of using cache */ freshContext?: boolean; } /** * Service dedicated to building and loading the various prompts used to communicate with agents. * Handles prompt templating, variable substitution, and fallback prompt generation. */ export declare class PromptBuilderService { private logger; private readonly projectRoot; private readonly rolesDirectory; /** Absolute path to agent skill scripts (used in prompts so agents can find them from any working directory) */ private readonly agentSkillsPath; /** Absolute path to team-leader skill scripts (used in TL addon template variables) */ private readonly tlSkillsPath; /** * Absolute path to orchestrator-namespaced skill scripts. Per orc-namespace * convention (see {@link composePromptWithMemory} role-branched Communication * section): orc has its own send-message wrapper that routes through * `/terminal/{session}/deliver` (readiness-aware, two-step delivery, retry) * instead of the agent-side `/terminal/{session}/write` (raw PTY buffer write, * no readiness, no Enter semantics). Spec provenance: 4-piece skill-mistake * fix dispatch piece #2 (Sam→Quinn, post-PR #446 merge). */ private readonly orchestratorSkillsPath; private memoryService; private sopService; constructor(projectRoot?: string); /** * Gets the MemoryService instance (lazy initialization) * * @returns The MemoryService singleton */ private getMemoryService; /** * Gets the SOPService instance (lazy initialization) * * @returns The SOPService singleton */ private getSOPService; /** * Build orchestrator prompt for project management */ buildOrchestratorPrompt(projectData: { projectName: string; projectPath: string; teamDetails: { name?: string; members?: Array<{ name: string; role: string; skills?: string; }>; }; requirements?: string; }): string; /** * Build system prompt for Claude Code agent * Loads prompts from config/roles/{role}/prompt.md */ buildSystemPrompt(config: TeamMemberSessionConfig): Promise; /** * Build system prompt with memory and SOP context included. * * This method builds a complete system prompt that includes: * - Base role instructions * - Project context * - Memory context (agent and project memories) * - SOP context (relevant Standard Operating Procedures) * - Agent identity information * * **WIRE-2 migration note:** When a `TeamMember` + `Team` pair is * available, prefer {@link buildSystemPromptWithTeamContext} — it * routes through {@link buildModuleConfigFromTeamMember} and wires * every autonomy / domain-SOP / risk-policy / capability / job-title / * ownership-scope / expert-id / team-mission / team-budget / * team-quality-gate / service-contract / team-ownership-scope field * declared on the TeamMember and Team records. The SessionConfig-only * surface here keeps the WIRE-1 stopgap for legacy callers (single- * agent flows, tests, and any path that doesn't have team context in * scope) so RoleBoundaryModule still resolves correctly for orc + TL * agents. * * @param config - Session configuration (SessionConfig-only) * @param options - Prompt building options * @returns Complete system prompt with memory and SOPs * * @example * ```typescript * const prompt = await promptBuilder.buildSystemPromptWithMemory(config, { * includeMemory: true, * includeSOPs: true, * taskContext: 'implementing authentication', * role: 'developer' * }); * ``` */ buildSystemPromptWithMemory(config: TeamMemberSessionConfig, options?: PromptOptions): Promise; /** * Build system prompt with full TeamMember + Team context (WIRE-2). * * The preferred entrypoint when a `TeamMember` and its enclosing `Team` * are both available — wires every autonomy / org / team-level field * declared on those records into the prompt assembler via * {@link buildModuleConfigFromTeamMember}. * * This method does NOT mutate the SessionConfig-only stopgap that * remains in {@link buildModularPrompt} — that stopgap is still the * fallback for legacy callers (single-agent flows, tests, and any path * that lacks team context in scope). Once all hot callers migrate to * this method, the stopgap becomes truly defensive (only fires for * test/legacy paths). * * @param member - Full TeamMember record * @param team - Full Team record * @param runtime - Runtime-host context (session name, project paths, * pre-resolved subordinates, etc.) * @param overlay - Optional overlay applied AFTER the helper builds the * ModuleConfig — used for fields that are NOT on TeamMember/Team * (e.g. `teamNormsPath`, registration-side `memberId` overrides). * @returns The fully-assembled prompt string * * @example * ```typescript * const prompt = await promptBuilder.buildSystemPromptWithTeamContext( * foundMember, * foundTeam, * { * sessionName, * projectPath, * runtimeType: 'claude-code', * agentSkillsPath: path.join(projectRoot, 'config/skills/agent'), * tlSkillsPath: path.join(projectRoot, 'config/skills/team-leader'), * projectRoot, * }, * { teamNormsPath: path.join(homedir, '.crewly/teams', team.id, 'norms') }, * ); * ``` */ buildSystemPromptWithTeamContext(member: TeamMember, team: Team, runtime: SessionRuntimeContext, overlay?: Partial): Promise; /** * Build system prompt using the modular PromptAssemblyService. * * Called by default (disable with CREWLY_USE_MODULAR_PROMPTS=false). The modular system handles * all prompt sections (identity, skills, communication, recovery, lifecycle, * team-reference, memory, soul, learning) with proper token budget enforcement * and priority ordering. * * @param config - Team member session configuration * @param options - Prompt options (currently unused by modular system) * @returns Assembled prompt string */ private buildModularPrompt; /** * Build memory context from agent and project memories * * @param agentId - Agent identifier * @param projectPath - Project path * @param options - Prompt options * @returns Formatted memory context string */ buildMemoryContext(agentId: string, projectPath: string, options?: PromptOptions): Promise; /** * Build SOP context from relevant SOPs for the agent's role and task * * @param role - Agent's role * @param taskContext - Current task context for SOP matching * @param taskType - Type of task being performed * @param limit - Maximum number of SOPs to include * @returns Formatted SOP context string */ buildSOPContext(role: string, taskContext?: string, taskType?: string, limit?: number): Promise; /** * Build the Team Lead responsibilities section for agents that can delegate. * * Loads the comprehensive TL management addon from `config/roles/team-leader/tl-addon.md`, * resolves template variables (WORKER_LIST, TL_SKILLS_PATH, TEAM_ID, MEMBER_ID, PROJECT_PATH), * and returns the formatted section. Falls back to a minimal inline section if the addon * file is not found. * * This enables "auto-stacking": a developer with TL hierarchy gets BOTH their dev role * prompt AND full management instructions without changing their role. * * @param config - Session config with optional TL fields * @returns Formatted TL section string, or empty string if not a TL */ buildTeamLeadSection(config: TeamMemberSessionConfig): Promise; /** * Build a formatted worker list string from subordinate info. * * @param subordinates - Array of subordinate details * @returns Formatted markdown list of workers */ private buildWorkerList; /** * Build a minimal inline TL section as fallback when tl-addon.md is not available. * * @param sessionName - The TL's session name * @param workerList - Pre-formatted worker list * @param subordinateCount - Number of subordinates * @returns Formatted inline TL section */ private buildInlineTeamLeadSection; /** * Build the team norms section for prompt injection. * * Loads norms from the team's norms directory (copied from template at team creation). * Only includes norms whose role list matches the agent's role (or '*' for all). * * @param teamId - Team ID to load norms for * @param role - Agent's role (used to filter norm applicability) * @returns Formatted norms markdown, or empty string if no norms found */ buildTeamNormsSection(teamId: string, role: string): Promise; /** * Build the memory routing rules section that tells agents where to store * different types of knowledge. * * - User prefs / team conventions → Crewly `remember` with `scope: project` * - Personal work patterns → Crewly `remember` with `scope: agent` * - Temporary task notes → project files or Claude native memory * * @returns Formatted markdown section with memory routing guidance */ buildMemoryRoutingSection(): string; /** * Compose a prompt with memory and SOP context included * * @param parts - Prompt parts to compose * @returns Composed prompt string */ private composePromptWithMemory; /** * Build the mandatory session recovery protocol section that instructs agents * to execute context recovery commands immediately after registration. * * This section is injected into ALL agent registration prompts (both regular * agents and orchestrator) to ensure agents always recover context on startup. * The agent will run `recall` and `get-my-context` to load previous knowledge, * then report any unfinished work before accepting new tasks. * * @param sessionName - Agent's session name (used as agentId in skill calls) * @param role - Agent's role * @param projectPath - Absolute path to the project * @returns Formatted markdown section with executable bash commands */ buildSessionRecoverySection(sessionName: string, role: string, projectPath: string): string; /** * Build a continuation prompt for when an agent needs to resume work * * @param agentId - Agent identifier * @param role - Agent's role * @param projectPath - Project path * @param currentTask - Current task being worked on * @param taskContext - Optional task context for SOP matching * @returns Continuation prompt string */ buildContinuationPrompt(agentId: string, role: string, projectPath: string, currentTask: { title: string; description?: string; }, taskContext?: string): Promise; /** * Load registration prompt from config files * Loads from config/roles/{role}/prompt.md */ loadRegistrationPrompt(role: string, sessionName: string, memberId?: string): Promise; /** * Build a fallback system prompt when role-specific prompt is not found */ private buildFallbackSystemPrompt; /** * Replace template variables in prompt content */ private replaceTemplateVariables; /** * Load prompt template from file * * Loads from config/roles/{roleName}/prompt.md or by full file name for backwards compatibility. * Checks for user override files in ~/.crewly/roles/{roleName}/prompt.md first. * * @param fileNameOrRole - Either a role name (e.g., 'orchestrator') or legacy file name (e.g., 'orchestrator-prompt.md') * @returns The prompt content, or null if not found */ loadPromptTemplate(fileNameOrRole: string): Promise; /** * Check if a prompt template exists for a role */ promptTemplateExists(roleName: string): Promise; /** * Get the path to the roles directory */ getRolesDirectory(): string; /** * Build a prompt for project start communication to orchestrator */ buildProjectStartPrompt(projectData: { projectName: string; projectPath: string; teamDetails: { name?: string; members?: Array<{ name: string; role: string; skills?: string; }>; }; requirements?: string; }): string; /** * Build a task assignment prompt */ buildTaskAssignmentPrompt(task: { id: string; title: string; description: string; assigneeRole: string; priority: 'high' | 'medium' | 'low'; estimatedHours?: number; }): string; /** * Build a status update request prompt */ buildStatusUpdatePrompt(sessionName: string, role: string): string; } //# sourceMappingURL=prompt-builder.service.d.ts.map