import { Component, Project as Project$1, typescript, awscdk } from 'projen'; import { Project, Component as Component$1, Task } from 'projen/lib'; import { NodeProject, PnpmOptions } from 'projen/lib/javascript'; import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; import { AwsStageType, DeploymentTargetRoleType, AwsEnvironmentType, AWS_STAGE_TYPE } from '@codedrifters/utils'; import * as ts from 'typescript'; import * as spec from '@jsii/spec'; import { TypeScriptProject as TypeScriptProject$1, TypeScriptAppProject, TypeScriptProjectOptions as TypeScriptProjectOptions$1 } from 'projen/lib/typescript'; import { ValueOf } from 'type-fest'; import { BuildWorkflow, BuildWorkflowOptions } from 'projen/lib/build'; import { GithubWorkflow, GitHub } from 'projen/lib/github'; import { JobStep } from 'projen/lib/github/workflows-model'; /** * Defines the scope/activation model for an agent rule. * - ALWAYS: Rule is always active regardless of context * - FILE_PATTERN: Rule activates when the AI is working on files matching the patterns */ declare const AGENT_RULE_SCOPE: { readonly ALWAYS: "always"; readonly FILE_PATTERN: "file-pattern"; }; type AgentRuleScope = (typeof AGENT_RULE_SCOPE)[keyof typeof AGENT_RULE_SCOPE]; /** * Supported AI coding assistant platforms. */ declare const AGENT_PLATFORM: { readonly CURSOR: "cursor"; readonly CLAUDE: "claude"; readonly CODEX: "codex"; readonly COPILOT: "copilot"; }; type AgentPlatform = (typeof AGENT_PLATFORM)[keyof typeof AGENT_PLATFORM]; /** * Render target for Claude Code rules. * - SCOPED_FILE: `.claude/rules/{name}.md` (default — supports paths frontmatter for conditional activation) * - AGENTS_MD: AGENTS.md (always-active, shared with Codex) * - CLAUDE_MD: CLAUDE.md (always-active, Claude-only content not consumed by other agents) */ declare const CLAUDE_RULE_TARGET: { readonly SCOPED_FILE: "scoped-file"; readonly AGENTS_MD: "agents-md"; readonly CLAUDE_MD: "claude-md"; }; type ClaudeRuleTarget = (typeof CLAUDE_RULE_TARGET)[keyof typeof CLAUDE_RULE_TARGET]; /** * Model selection for sub-agents. Platforms map these to their own model identifiers. */ declare const AGENT_MODEL: { readonly INHERIT: "inherit"; readonly FAST: "fast"; readonly BALANCED: "balanced"; readonly POWERFUL: "powerful"; }; type AgentModel = (typeof AGENT_MODEL)[keyof typeof AGENT_MODEL]; /** * Maps abstract AGENT_MODEL values to Claude Code model aliases. * Returns undefined for "inherit" (omit the field entirely). * Cursor omits the model field to use its default model selection. */ declare function resolveModelAlias(model: string | undefined): string | undefined; /** * MCP server transport type. */ declare const MCP_TRANSPORT: { readonly STDIO: "stdio"; readonly HTTP: "http"; readonly SSE: "sse"; }; type McpTransport = (typeof MCP_TRANSPORT)[keyof typeof MCP_TRANSPORT]; /** * Platform-specific overrides for a rule. */ interface AgentPlatformOverrides { /** Cursor-specific overrides */ readonly cursor?: { /** Override the description used in Cursor's YAML frontmatter */ readonly description?: string; /** Exclude this rule from Cursor output entirely */ readonly exclude?: boolean; }; /** Claude Code-specific overrides */ readonly claude?: { /** * Where to render this rule for Claude Code. * - SCOPED_FILE: `.claude/rules/{name}.md` (default — supports paths frontmatter) * - AGENTS_MD: AGENTS.md (always-active, shared with Codex) * - CLAUDE_MD: CLAUDE.md (always-active, Claude-only) */ readonly target?: ClaudeRuleTarget; /** Exclude this rule from Claude Code output entirely */ readonly exclude?: boolean; }; /** Codex-specific overrides (future) */ readonly codex?: { /** Place this rule in a sub-directory AGENTS.md instead of root */ readonly directory?: string; /** Exclude this rule from Codex output entirely */ readonly exclude?: boolean; }; /** Copilot-specific overrides (future) */ readonly copilot?: { /** Exclude this rule from Copilot output entirely */ readonly exclude?: boolean; }; } /** * A single agent rule definition, platform-agnostic. */ interface AgentRule { /** * Unique identifier for the rule. Used as the filename stem in platforms * that use per-rule files (Cursor, Claude Code, Copilot). * @example 'typescript-conventions' */ readonly name: string; /** * Human-readable description of the rule's purpose. * Used by Cursor for AI-assisted rule selection. * @example 'TypeScript project patterns and conventions' */ readonly description: string; /** * Activation scope for this rule. * - AGENT_RULE_SCOPE.ALWAYS: Active in all contexts * - AGENT_RULE_SCOPE.FILE_PATTERN: Active only when working on matching files */ readonly scope: AgentRuleScope; /** * Glob patterns for conditional activation. * Required when scope is AGENT_RULE_SCOPE.FILE_PATTERN. * @example ['src/**\/*.ts', 'tests/**\/*.ts'] */ readonly filePatterns?: ReadonlyArray; /** * The rule content as markdown. This is the platform-agnostic body * that applies equally to all AI assistants. */ readonly content: string; /** * Optional per-platform overrides. Use sparingly — prefer platform-agnostic content. */ readonly platforms?: AgentPlatformOverrides; /** * Optional tags for categorizing and ordering rules. * Rules are ordered by tag (alphabetical), then by name within each tag. * @example ['typescript', 'testing', 'workflow'] */ readonly tags?: ReadonlyArray; } /** * A skill definition following the cross-platform Agent Skills specification. * Rendered to .claude/skills/ (Claude Code) and .cursor/skills/ (Cursor). */ interface AgentSkill { /** * Unique identifier for the skill. Becomes the /slash-command name. * @example 'commit' */ readonly name: string; /** * Human-readable description. Claude uses this to decide when to auto-invoke. */ readonly description: string; /** * Multi-line instruction content (markdown). Becomes the SKILL.md body. */ readonly instructions: string; /** * Optional tool allowlist for this skill. * @example ['Read', 'Bash(npm run *)'] */ readonly allowedTools?: ReadonlyArray; /** * Whether to prevent auto-invocation of this skill. * When true, only triggered by the user typing /skill-name. * @default false */ readonly disableModelInvocation?: boolean; /** * Whether the user can invoke this skill directly via /skill-name. * Set to false for background skills that should not appear in the / menu. * @default true */ readonly userInvocable?: boolean; /** * Model override when this skill is active. * @example 'claude-opus-4-6' */ readonly model?: string; /** * Reasoning effort level when this skill is active. * @example 'high' */ readonly effort?: string; /** * Glob patterns that limit when the skill is auto-loaded. * @example ['src/api/**\/*.ts'] */ readonly paths?: ReadonlyArray; /** * Resource directories bundled with the skill (e.g., references/, scripts/, assets/). * Documentation hint only — directory names listed here are not emitted to disk. * Prefer {@link referenceFiles} to ship actual companion file contents. * * @deprecated Use {@link referenceFiles} to emit physical companion files. * @example ['references/', 'scripts/'] */ readonly references?: ReadonlyArray; /** * Companion files shipped alongside the SKILL.md (e.g., templates, references, scripts). * Each entry is rendered as a TextFile under the skill's directory on every * platform that emits skills (Claude: `.claude/skills/{name}/{path}`, * Cursor: `.cursor/skills/{name}/{path}`). * * Use this for skill assets that benefit from being separate files rather than * inlined into the skill instructions — large templates, reference tables, * runnable scripts. * * Per-platform `platforms.{claude,cursor}.exclude` flags suppress reference * files for that platform alongside the SKILL.md. * * @example * ```ts * referenceFiles: [ * { path: '_references/templates/_template-FR.md', content: '# Functional Requirement\n...' }, * { path: '_references/standards-and-frameworks.md', content: '# Standards\n...' }, * ] * ``` */ readonly referenceFiles?: ReadonlyArray<{ /** Path relative to the skill directory (e.g., `_references/templates/_template-FR.md`). */ readonly path: string; /** File contents written verbatim. */ readonly content: string; }>; /** * Context isolation mode. Set to 'fork' to run in an isolated subagent context. */ readonly context?: string; /** * Subagent name to delegate to when context is 'fork'. * @example 'code-reviewer' */ readonly agent?: string; /** * Shell for dynamic context injection via !`command` syntax. * @default 'bash' */ readonly shell?: string; /** Per-platform overrides. Use `exclude: true` to skip a platform. */ readonly platforms?: { readonly claude?: { readonly exclude?: boolean; }; readonly cursor?: { readonly exclude?: boolean; }; }; } /** * Platform-specific overrides for a sub-agent definition. */ /** * Copilot handoff definition for sub-agent delegation. */ interface CopilotHandoff { /** Display label for the handoff action. */ readonly label: string; /** Target agent name. */ readonly agent: string; /** Optional prompt to pass to the target agent. */ readonly prompt?: string; /** Whether to auto-send the handoff without user confirmation. */ readonly send?: boolean; } interface AgentSubAgentPlatformOverrides { /** Claude Code-specific overrides */ readonly claude?: { /** Permission mode: default, acceptEdits, dontAsk, bypassPermissions, plan */ readonly permissionMode?: string; /** Run in isolated git worktree */ readonly isolation?: string; /** Run as a background task */ readonly background?: boolean; /** Reasoning effort level (Opus): low, medium, high, max */ readonly effort?: string; /** Persistent memory scope: user, project, local */ readonly memory?: string; /** Exclude this sub-agent from Claude Code output entirely */ readonly exclude?: boolean; }; /** Cursor-specific overrides */ readonly cursor?: { /** Restrict the sub-agent to read-only operations */ readonly readonly?: boolean; /** Run the sub-agent asynchronously as a background task */ readonly isBackground?: boolean; /** Exclude this sub-agent from Cursor output entirely */ readonly exclude?: boolean; }; /** Codex-specific overrides (future) */ readonly codex?: { /** Sandbox mode: read-only or full */ readonly sandboxMode?: string; /** Model reasoning effort */ readonly modelReasoningEffort?: string; /** Exclude this sub-agent from Codex output entirely */ readonly exclude?: boolean; }; /** Copilot-specific overrides (future) */ readonly copilot?: { /** Target environment: vscode or github-copilot */ readonly target?: string; /** Whether the user can invoke this sub-agent directly */ readonly userInvocable?: boolean; /** Prevent auto-invocation of this sub-agent */ readonly disableModelInvocation?: boolean; /** Handoff definitions for agent-to-agent delegation */ readonly handoffs?: ReadonlyArray; /** Exclude this sub-agent from Copilot output entirely */ readonly exclude?: boolean; }; } /** * A custom sub-agent definition, platform-agnostic. * Rendered to .cursor/agents/ (Cursor), .claude/agents/ (Claude Code). */ interface AgentSubAgent { /** * Unique identifier for the sub-agent. Used as the filename stem. * Must be lowercase letters and hyphens only. * @example 'code-reviewer' */ readonly name: string; /** Human-readable description. Used by the parent agent to decide when to delegate. */ readonly description: string; /** * System prompt / instructions for the sub-agent (markdown). * This becomes the body of the generated agent file. */ readonly prompt: string; /** * Model selection for this sub-agent. * @default AGENT_MODEL.INHERIT */ readonly model?: AgentModel; /** * Tool allowlist. When omitted, inherits all tools from parent. * @example ['Read', 'Glob', 'Grep'] */ readonly tools?: ReadonlyArray; /** * Tool denylist. Applied before the allowlist. * @example ['Bash', 'Write'] */ readonly disallowedTools?: ReadonlyArray; /** Maximum agentic turns before the sub-agent stops. */ readonly maxTurns?: number; /** * Skills to preload for this sub-agent. * @example ['commit', 'review-pr'] */ readonly skills?: ReadonlyArray; /** * MCP servers available to this sub-agent. * Rendered to the platform-specific sub-agent config. */ readonly mcpServers?: Readonly>; /** Optional per-platform overrides for this sub-agent. */ readonly platforms?: AgentSubAgentPlatformOverrides; } /** * A user-invokable slash command. Rendered to `.claude/commands/.md`. * * Slash commands appear in the Claude Code command picker (typed as * `/`) and execute the body as the user prompt. The body may * reference any rule, skill, sub-agent, or procedure registered via * `AgentConfigOptions` — bundle-shipped commands typically delegate to * an existing sub-agent or procedure rather than restating its content. * * @see https://docs.claude.com/en/docs/claude-code/slash-commands */ interface AgentCommand { /** * Slash-command name (no leading slash). Lowercase, kebab-case. * Becomes the filename stem under `.claude/commands/`. * @example 'orchestrate' */ readonly name: string; /** * One-sentence summary shown in the slash-command picker. */ readonly description: string; /** * Body content (markdown). Rendered as the command file body verbatim, * after the generated YAML frontmatter. */ readonly content: string; /** * Optional model override for the command's invocation. Maps to a * Claude Code model alias (e.g. `opus`, `sonnet`, `haiku`) via * {@link resolveModelAlias}. */ readonly model?: AgentModel; } /** * An executable procedure (shell script) that ships with a bundle. * Rendered to `.claude/procedures/{name}` as an executable file. */ interface AgentProcedure { /** * Filename for the procedure (e.g., 'check-blocked.sh'). * Used as the filename in .claude/procedures/. */ readonly name: string; /** Human-readable description of what this procedure does. */ readonly description: string; /** Script content as a single string. Lines are split on newlines for rendering. */ readonly content: string; } /** * MCP server configuration. Cross-platform — rendered to .claude/settings.json * (Claude Code) and .cursor/mcp.json (Cursor). */ interface McpServerConfig { /** * Transport type for the server connection. * @default MCP_TRANSPORT.STDIO */ readonly transport?: McpTransport; /** Command to launch a stdio server. */ readonly command?: string; /** Command arguments for stdio server. */ readonly args?: ReadonlyArray; /** URL for HTTP/SSE remote servers. */ readonly url?: string; /** HTTP headers for HTTP/SSE connections. */ readonly headers?: Readonly>; /** Environment variables for the server process. */ readonly env?: Readonly>; /** * Tool allowlist — only these tools from the server will be available. * @example ['read_file', 'search'] */ readonly enabledTools?: ReadonlyArray; /** * Tool denylist — these tools from the server will be blocked. * Applied before enabledTools. * @example ['delete_file', 'execute'] */ readonly disabledTools?: ReadonlyArray; } /** A single GitHub label definition for EndBug/label-sync. */ interface LabelDefinition { /** Label name (e.g. "priority:high"). */ readonly name: string; /** Hex color without the leading `#` (e.g. "B60205"). */ readonly color: string; /** Short description shown in the GitHub UI. */ readonly description: string; } /** Options for the sync-labels workflow and labels config file. */ interface SyncLabelsOptions { /** * Additional labels to sync alongside the standard defaults. * Merged with DEFAULT_STATUS_LABELS, DEFAULT_PRIORITY_LABELS, and * DEFAULT_TYPE_LABELS (standard labels are always included). */ readonly labels?: ReadonlyArray; /** * Remove labels from the repo that are not in the config file. * @default true */ readonly deleteOtherLabels?: boolean; /** * Workflow file name (display name in the Actions tab). * @default "sync-labels" */ readonly workflowName?: string; /** * Agent bundles whose contributed labels should be merged into * `.github/labels.yml`. Typically populated by the project type from * `AgentConfig.of(project)?.activeBundles` so bundle-supplied labels * only appear when the bundle is actually enabled. * * Merge order: Tier 1 defaults → bundle labels → user-supplied `labels` * (later entries override earlier ones on name collision). */ readonly bundles?: ReadonlyArray; } /** Default status labels. */ declare const DEFAULT_STATUS_LABELS: ReadonlyArray; /** Default priority labels. */ declare const DEFAULT_PRIORITY_LABELS: ReadonlyArray; /** Default type labels — one per conventional commit type. */ declare const DEFAULT_TYPE_LABELS: ReadonlyArray; /** * Hard limit GitHub's Labels API enforces on the `description` field. * EndBug/label-sync rejects (and the API returns an opaque error for) * any label whose description exceeds this length, which silently * breaks the sync workflow downstream. */ declare const MAX_LABEL_DESCRIPTION_LENGTH = 100; /** * Adds a labels config file and a GitHub Actions workflow that syncs * labels to the repository using EndBug/label-sync. */ declare function addSyncLabelsWorkflow(project: NodeProject, options: SyncLabelsOptions): void; /******************************************************************************* * * Agent Rule Bundle * ******************************************************************************/ /** * A context-aware bundle of rules and/or skills that ships with configulator. * Bundles are automatically selected based on project introspection (e.g., * which components are present, which libraries are in use). */ interface AgentRuleBundle { /** * Unique identifier for the bundle. * @example 'vitest', 'turborepo', 'pnpm-monorepo', 'aws-cdk' */ readonly name: string; /** * Human-readable description of when this bundle applies. * @example 'Rules for projects using Vitest as their test runner' */ readonly description: string; /** * Function that inspects the Projen project and returns true if this * bundle should be automatically included. Receives the root project * as input and can check for sibling components, dependencies, etc. * @example `(project) => Vitest.of(project) !== undefined` */ readonly appliesWhen: (project: Project) => boolean; /** * Optional. When set, returns the list of projects (root and/or * subprojects) that matched this bundle's detection predicate. Enables * AgentConfig to narrow each rule's `filePatterns` to the outdirs of * those projects instead of the bundle's original repo-wide patterns. * * Bundles that provide `FILE_PATTERN`-scoped rules about TypeScript or * TypeScript-adjacent code (e.g. typescript, aws-cdk, vitest, jest) should * implement this so their rules only activate for files in the packages * that actually use them. * @example `(project) => findProjectsWithFile(project, 'tsconfig.json')` */ readonly findApplicableProjects?: (project: Project) => ReadonlyArray; /** Rules included in this bundle. */ readonly rules: ReadonlyArray; /** Skills included in this bundle (cross-platform where supported). */ readonly skills?: ReadonlyArray; /** Sub-agents included in this bundle. */ readonly subAgents?: ReadonlyArray; /** Executable procedures (shell scripts) included in this bundle. */ readonly procedures?: ReadonlyArray; /** * Slash commands included in this bundle. Rendered to * `.claude/commands/.md`. Bundle commands are merged with * consumer-supplied commands; consumers can opt out of any default * via `AgentConfigOptions.excludeCommands` or by excluding the * whole bundle. */ readonly commands?: ReadonlyArray; /** * Claude Code permission entries contributed by this bundle. * Allow and deny entries are merged with the default and user-supplied * permissions when the bundle is active. */ readonly claudePermissions?: { readonly allow?: ReadonlyArray; readonly deny?: ReadonlyArray; }; /** * GitHub labels contributed by this bundle. When the bundle is active in a * project that also uses the sync-labels workflow, these labels are merged * into `.github/labels.yml` alongside the Tier 1 defaults. * * User-supplied labels (via `SyncLabelsOptions.labels`) override bundle * labels on name collision. */ readonly labels?: ReadonlyArray; } /******************************************************************************* * * Claude Code Settings * ******************************************************************************/ /** * A single Cursor hook action. */ interface CursorHookAction { /** Shell command to execute. */ readonly command: string; } /** * Cursor hook lifecycle events. Each event takes an array of hook actions. * * Cursor hooks use a different model than Claude Code hooks: * - Blocking hooks can return permission decisions to block actions * - Non-blocking hooks are informational only */ interface CursorHooksConfig { /** * Fires before a user prompt is submitted. Non-blocking (informational). */ readonly beforeSubmitPrompt?: ReadonlyArray; /** * Fires before a shell command executes. Blocking — can deny execution. */ readonly beforeShellExecution?: ReadonlyArray; /** * Fires after a shell command executes. Non-blocking (informational). */ readonly afterShellExecution?: ReadonlyArray; /** * Fires before an MCP tool is invoked. Blocking — can deny execution. */ readonly beforeMCPExecution?: ReadonlyArray; /** * Fires after an MCP tool is invoked. Non-blocking (informational). */ readonly afterMCPExecution?: ReadonlyArray; /** * Fires before a file is read by the AI. Blocking — can rewrite content. */ readonly beforeReadFile?: ReadonlyArray; /** * Fires before a tab file is read. Blocking — can rewrite content. */ readonly beforeTabFileRead?: ReadonlyArray; /** * Fires after a file is edited. Non-blocking (informational). */ readonly afterFileEdit?: ReadonlyArray; /** * Fires after a tab file is edited. Non-blocking (informational). */ readonly afterTabFileEdit?: ReadonlyArray; /** * Fires when the agent stops. Non-blocking (informational). */ readonly stop?: ReadonlyArray; /** * Fires when a session starts. Non-blocking (informational). */ readonly sessionStart?: ReadonlyArray; /** * Fires when a session ends. Non-blocking (informational). */ readonly sessionEnd?: ReadonlyArray; /** * Fires before context compaction. Non-blocking (informational). */ readonly preCompact?: ReadonlyArray; /** * Fires after the agent produces a response. Non-blocking (informational). */ readonly afterAgentResponse?: ReadonlyArray; /** * Fires after the agent produces a thought. Non-blocking (informational). */ readonly afterAgentThought?: ReadonlyArray; } /** * Cursor-specific configuration options. * Cursor does not support a project-level settings.json — its AI settings * are stored in an internal SQLite database. However, it does support * project-level hooks (.cursor/hooks.json) and ignore files. */ interface CursorSettingsConfig { /** * Lifecycle hooks for Cursor's agent. Generated to .cursor/hooks.json. */ readonly hooks?: CursorHooksConfig; /** * Patterns for .cursorignore — files completely invisible to Cursor. * This is a hard security boundary: matched files are not indexed, * not readable by AI, and not included in any context. * Uses .gitignore syntax. * @example ['**\/.env', '**\/secrets/**', 'dist/'] */ readonly ignorePatterns?: ReadonlyArray; /** * Patterns for .cursorindexingignore — files excluded from codebase * indexing but still accessible to AI if referenced directly. * Uses .gitignore syntax. * @example ['**\/generated/**', '**\/vendor/**', '*.min.js'] */ readonly indexingIgnorePatterns?: ReadonlyArray; } /** * Permission rule syntax supports fine-grained patterns: * - Tool name: "Bash", "Read", "Edit", "WebFetch" * - With args: "Bash(npm run *)", "Bash(git * main)" * - File paths: "Edit(/src/**\/*.ts)" * - MCP tools: "mcp__puppeteer__puppeteer_navigate" */ interface ClaudePermissionsConfig { /** Tools auto-approved without prompts. */ readonly allow?: ReadonlyArray; /** Tools completely blocked. */ readonly deny?: ReadonlyArray; /** Tools that always prompt (overrides allow). */ readonly ask?: ReadonlyArray; /** Additional directories Claude can access beyond the project root. */ readonly additionalDirectories?: ReadonlyArray; /** * Default permission entries to drop from the rendered baseline. * * After defaults + bundle + consumer entries are merged and deduped, any * entry in `allow` / `deny` / `ask` whose value exactly matches a string * listed here is removed — including `DEFAULT_CLAUDE_PATH_DENY` entries. * This is the only supported way to remove a baseline default (the merge is * otherwise append-only, and Claude Code precedence is deny-beats-allow). * Exact string match; omit or leave empty to preserve the current baseline. */ readonly excludeDefaults?: ReadonlyArray; } /** * A single hook action. Supports four types: command, http, prompt, agent. */ interface ClaudeHookAction { /** Hook type. */ readonly type: "command" | "http" | "prompt" | "agent"; /** Shell command to execute (type: 'command'). */ readonly command?: string; /** URL to POST to (type: 'http'). */ readonly url?: string; /** HTTP headers (type: 'http'). */ readonly headers?: Readonly>; /** LLM/agent prompt text (type: 'prompt' | 'agent'). */ readonly prompt?: string; /** Model override for prompt hooks (type: 'prompt'). */ readonly model?: string; /** Permission rule filter — only fire when this pattern matches. */ readonly if?: string; /** Timeout in seconds. */ readonly timeout?: number; /** Custom spinner text shown while the hook runs. */ readonly statusMessage?: string; /** Run in background without blocking (type: 'command'). */ readonly async?: boolean; /** Only execute this hook once per session. */ readonly once?: boolean; /** Shell to use (type: 'command'). */ readonly shell?: string; } /** * Hook definition for Claude Code lifecycle events. */ interface ClaudeHookEntry { /** * Tool name or regex pattern to match. * @example 'Bash', 'Edit|Write', '.*' */ readonly matcher: string; /** Array of hook actions to execute when the matcher triggers. */ readonly hooks: ReadonlyArray; } /** * All supported Claude Code hook lifecycle events. */ interface ClaudeHooksConfig { readonly PreToolUse?: ReadonlyArray; readonly PostToolUse?: ReadonlyArray; readonly PostToolUseFailure?: ReadonlyArray; readonly PermissionRequest?: ReadonlyArray; readonly PermissionDenied?: ReadonlyArray; readonly Notification?: ReadonlyArray; readonly UserPromptSubmit?: ReadonlyArray; readonly Stop?: ReadonlyArray; readonly StopFailure?: ReadonlyArray; readonly SubagentStart?: ReadonlyArray; readonly SubagentStop?: ReadonlyArray; readonly TaskCreated?: ReadonlyArray; readonly TaskCompleted?: ReadonlyArray; readonly TeammateIdle?: ReadonlyArray; readonly PreCompact?: ReadonlyArray; readonly PostCompact?: ReadonlyArray; readonly ConfigChange?: ReadonlyArray; readonly WorktreeCreate?: ReadonlyArray; readonly WorktreeRemove?: ReadonlyArray; readonly SessionStart?: ReadonlyArray; readonly SessionEnd?: ReadonlyArray; readonly Elicitation?: ReadonlyArray; readonly ElicitationResult?: ReadonlyArray; readonly InstructionsLoaded?: ReadonlyArray; readonly FileChanged?: ReadonlyArray; readonly CwdChanged?: ReadonlyArray; } /** * Sandbox configuration for Claude Code. Controls filesystem, network, * and execution restrictions. */ interface ClaudeSandboxConfig { /** Enable sandboxing. */ readonly enabled?: boolean; /** * Sandbox mode. * - 'auto-allow': Auto-approve Bash commands when sandboxed * - 'regular-permissions': Use normal permission prompts even when sandboxed */ readonly mode?: string; /** Fail if sandbox cannot be initialized. */ readonly failIfUnavailable?: boolean; /** Auto-allow Bash commands when sandboxed. */ readonly autoAllowBashIfSandboxed?: boolean; /** Commands excluded from sandbox. */ readonly excludedCommands?: ReadonlyArray; /** Filesystem access restrictions. */ readonly filesystem?: { readonly allowRead?: ReadonlyArray; readonly denyRead?: ReadonlyArray; readonly allowWrite?: ReadonlyArray; readonly denyWrite?: ReadonlyArray; }; /** Network access restrictions. */ readonly network?: { readonly allowedDomains?: ReadonlyArray; readonly denyDomains?: ReadonlyArray; }; } /** * Auto mode configuration. Provides context to Claude's permission * classifier for more intelligent auto-approval decisions. */ interface ClaudeAutoModeConfig { /** Environmental context strings about the organization/project. */ readonly environment?: ReadonlyArray; /** Actions explicitly allowed with reasoning. */ readonly allow?: ReadonlyArray; /** Actions soft-denied with reasoning. */ readonly soft_deny?: ReadonlyArray; } /** * Full Claude Code settings.json configuration. * Maps to the project-level .claude/settings.json file. */ interface ClaudeSettingsConfig { /** * Default permission mode for the project. * @example 'default', 'acceptEdits', 'plan', 'auto' */ readonly defaultMode?: string; /** Permission rules (allow, deny, ask, additionalDirectories). */ readonly permissions?: ClaudePermissionsConfig; /** Lifecycle hooks. */ readonly hooks?: ClaudeHooksConfig; /** MCP server configurations for .claude/settings.json. */ readonly mcpServers?: Readonly>; /** MCP servers to explicitly allow. */ readonly allowedMcpServers?: ReadonlyArray; /** MCP servers to explicitly deny. */ readonly deniedMcpServers?: ReadonlyArray; /** Environment variables passed to Claude's shell. */ readonly env?: Readonly>; /** Sandbox configuration. */ readonly sandbox?: ClaudeSandboxConfig; /** Auto mode configuration. */ readonly autoMode?: ClaudeAutoModeConfig; /** Set to "disable" to prevent use of bypassPermissions mode. */ readonly disableBypassPermissionsMode?: string; /** Set to "disable" to prevent use of auto mode. */ readonly disableAutoMode?: string; /** Disable all hooks (project and user-level). */ readonly disableAllHooks?: boolean; /** * Glob patterns for sensitive files to exclude from suggestions. * @example ['**\/.env', '**\/*.key', '**\/secrets/**'] */ readonly excludeSensitivePatterns?: ReadonlyArray; /** * Default model ID for the project. * @example 'claude-opus-4-6' */ readonly model?: string; /** * Default reasoning effort level. * @example 'high' */ readonly effortLevel?: string; /** Attribution configuration for commits and PRs created by Claude. */ readonly attribution?: { /** Attribution mode: 'inherit', 'always', 'never'. */ readonly mode?: string; /** Commit author details. */ readonly commits?: { readonly author?: string; readonly email?: string; }; }; /** * Glob patterns to exclude from CLAUDE.md file search. * @example ['vendor/**', 'generated/**'] */ readonly claudeMdExcludes?: ReadonlyArray; /** * Whether to respect .gitignore when indexing files. * @default true */ readonly respectGitignore?: boolean; } /******************************************************************************* * * CLAUDE.md Tuning * ******************************************************************************/ /** * Tuning knobs for the rendered `CLAUDE.md` file. * * Today this surface only carries one switch (`injectBundleHooks`), * but it exists as its own interface so future CLAUDE.md-shaping * options (size caps, section pruning, alternative table-of-contents * formats) have an obvious home. */ interface ClaudeMdConfig { /** * Whether the CLAUDE.md renderer should append the four "see also" * subsections to each phased-agent bundle's `-workflow` * rule. * * When `true` (the default), each affected `-workflow` * rule receives up to four short subsections — `## Progress File`, * `## Shared Index Editing`, `## Issue Templates`, and * `## Skill Evals` — that point readers at the matching * convention rule (`progress-file-convention`, `shared-editing-safety`, * `issue-templates-convention`, `skill-evals`). The convention * rules themselves render unconditionally as standalone top-level * CLAUDE.md sections regardless of this setting; these per-bundle * subsections are an extra "see also" pointer for readers who land * on a workflow rule directly. * * Set to `false` to drop the per-bundle pointers. The convention * rules at the top of CLAUDE.md still convey the policy. This * recovers roughly 600 lines (~6,500 tokens per turn) on a typical * phased-agent-heavy consumer where the same handful of "see the * X rule" stubs are duplicated across every workflow rule. * * The default is `true` to preserve back-compat for existing * consumers that expect the per-agent stubs. * * @default true */ readonly injectBundleHooks?: boolean; } /******************************************************************************* * * Agent Paths Config * ******************************************************************************/ /** * Consumer-facing overrides for the output-path roots used by agent * bundles. Every field is optional; unset fields cascade from their * parent root (resolved by `resolveAgentPaths()` in * `./bundles/paths.ts`). * * Bundles read these values indirectly through `DEFAULT_AGENT_PATHS` * (module-eval-time defaults) today. Per-project propagation through * `AgentConfig.resolveRules` lands in a follow-up change. */ interface AgentPathsConfig { /** * Root folder for the monorepo-wide Starlight docs site content. * @default "docs/src/content/docs" */ readonly docsRoot?: string; /** * Root folder for all research outputs (scopes, slices, * deliverables) produced by the `research-analyst` pipeline. * @default "docs/research" */ readonly researchRoot?: string; /** * Root folder for profile documents (people, companies, software, * industries). * @default "/profiles" */ readonly profilesRoot?: string; /** * Root folder for meeting notes and transcripts produced by the * `meeting-analyst` pipeline. * @default "/meetings" */ readonly meetingsRoot?: string; /** * Root folder for final requirement documents produced by the * `requirements-writer` agent. * @default "/requirements" */ readonly requirementsRoot?: string; /** * Root folder where the `requirements-analyst` writes requirement * proposals before they are promoted into the final requirements * tree. * @default "/requirements" */ readonly researchRequirementsRoot?: string; /** * Root folder for BCM (Business Capability Model) capability-model * documents produced by the `bcm-writer` agent. * @default "/concepts" */ readonly bcmRoot?: string; /** * Root folder for people profiles. * @default "/people" */ readonly peopleRoot?: string; /** * Root folder for company profiles. * @default "/companies" */ readonly companiesRoot?: string; /** * Root folder for software profiles. * @default "/software" */ readonly softwareRoot?: string; /** * Root folder for industry profiles. * @default "/industries" */ readonly industriesRoot?: string; /** * Per-category subdirectory names under the requirements root * (`requirementsRoot`). Each requirement category (business, * functional, non-functional, …) lives in its own subdirectory; the * `requirements-writer`, `requirements-analyst`, and * `requirements-reviewer` bundles cite these names in category tables * and cross-reference links. Override any subset to match a project's * existing directory convention (e.g. `functional-requirements`). * Unset entries fall back to the canonical defaults. */ readonly requirementCategoryDirs?: RequirementCategoryDirsConfig; } /** * Subdirectory names for each requirement category, relative to the * requirements root. All entries are optional; unset entries resolve to * their canonical defaults via `resolveAgentPaths()`. */ interface RequirementCategoryDirsConfig { /** @default "business" */ readonly business?: string; /** @default "functional" */ readonly functional?: string; /** @default "non-functional" */ readonly nonFunctional?: string; /** @default "technical" */ readonly technical?: string; /** @default "architectural-decisions" */ readonly architecturalDecisions?: string; /** @default "security" */ readonly security?: string; /** @default "data" */ readonly data?: string; /** @default "integration" */ readonly integration?: string; /** @default "operational" */ readonly operational?: string; /** @default "ux" */ readonly ux?: string; /** @default "multi-tenancy" */ readonly multiTenancy?: string; } /******************************************************************************* * * Priority Rules * ******************************************************************************/ /** * A single project-specific priority-detection rule. * * Rules let consuming repos declare a sector-prefix regex, label, body * regex, or explicit issue-number pin that maps to one of the five * `priority:*` tiers. The `base` bundle renders a "Project-specific * priority rules" subsection into the `issue-label-conventions` rule * when `AgentConfigOptions.priorityRules` is non-empty, in precedence * order. * * Precedence is **first match wins** — rules are evaluated in the * order supplied, and the bundle's default inference heuristics act as * the fallback when nothing matches. * * @see AgentConfigOptions.priorityRules */ interface PriorityRule { /** Target priority tier the rule maps matched issues to. */ readonly priority: "critical" | "high" | "medium" | "low" | "trivial"; /** * Match predicate. At least one sub-field should be set; multiple * sub-fields on the same rule combine as a logical OR (any one * matching fires the rule). */ readonly match: { /** Any of these labels present on the issue matches. */ readonly labels?: ReadonlyArray; /** Regex applied to the issue title, anchored as supplied. */ readonly titleRegex?: string; /** Regex applied to the issue body, anchored as supplied. */ readonly bodyRegex?: string; /** Explicit issue numbers pinned to this priority. */ readonly issueNumbers?: ReadonlyArray; }; /** * Human-readable rationale rendered into the generated rule * content so reviewers understand why the rule exists. */ readonly rationale: string; } /******************************************************************************* * * Focus Scoring * ******************************************************************************/ /** * Match predicate for a single focus area. At least one sub-field * should be set; multiple sub-fields on the same match combine as a * logical OR (any one matching contributes the focus area's weight). * * @see FocusArea */ interface FocusAreaMatch { /** Any of these labels present on the issue matches. */ readonly labels?: ReadonlyArray; /** * Case-insensitive substrings matched against the issue title. * A hit on any single keyword fires the match. */ readonly titleKeywords?: ReadonlyArray; /** * Case-insensitive substrings matched against the issue body. * A hit on any single keyword fires the match. */ readonly bodyKeywords?: ReadonlyArray; } /** * A single scored focus area — a named customer, segment, * organization, software product, regulation, or person the project * cares about right now. Matching issues receive this area's * `weight` as a priority boost at triage time. * * @see FocusConfig * @see ./bundles/focus.ts#renderFocusSection */ interface FocusArea { /** Human-readable name for the focus area (e.g. "Acme Corp"). */ readonly name: string; /** * Consuming-repo-defined vocabulary describing what kind of entity * this focus area represents (e.g. `"customer"`, `"segment"`, * `"organization"`, `"software"`, `"regulation"`, `"person"`). * The `AgentExpansionRules.allowedTypes` / * `AgentExpansionRules.forbiddenTypes` lists gate what values * agents are permitted to introduce. */ readonly type: string; /** * Provenance. `"human"` entries are curated by a maintainer; * `"agent"` entries were appended by an agent under the * agent-expansion rules and must carry a `discoveredIn` reference. */ readonly source: "human" | "agent"; /** * Issue reference (e.g. `"#123"`) that introduced the focus area. * Required when `source` is `"agent"` so the provenance of every * agent-appended entry is auditable; optional for human entries. */ readonly discoveredIn?: string; /** Match predicate that decides which issues this area applies to. */ readonly match: FocusAreaMatch; /** * Positive integer weight contributed to the issue's focus score * when the match fires. Consuming repos typically cap this with * `AgentExpansionRules.maxWeight` (default 8 in openhi's reference * implementation). */ readonly weight: number; } /** * Guard-rails on what agents are allowed to append to `focus.json` * without human review. The agent-expansion contract is append-only: * agents never modify or remove existing entries. */ interface AgentExpansionRules { /** * Hard cap on the `weight` an agent may set on a newly appended * focus area. Human curators may use higher weights. */ readonly maxWeight: number; /** * Whitelist of `FocusArea.type` values agents are permitted to * introduce. Any type outside this list must be added by a human. */ readonly allowedTypes: ReadonlyArray; /** * Explicit blocklist of `FocusArea.type` values agents must never * introduce, even if the list accidentally overlaps * `allowedTypes`. `forbiddenTypes` wins on conflict. */ readonly forbiddenTypes: ReadonlyArray; /** * Optional cap on the number of entries in each keyword array * (`match.labels`, `match.titleKeywords`, `match.bodyKeywords`) * on an agent-appended focus area. Prevents runaway keyword * expansion. Human entries are not subject to this cap. */ readonly maxKeywords?: number; } /** * Top-level configuration for the focus-scoring engine. The schema * and agent-expansion rules live in the configulator bundle; the * actual `focus.json` file is authored and curated in the consuming * repo. * * When supplied via `AgentConfigOptions.focus`, the `base` bundle * appends a "Focus scoring" subsection to the * `issue-label-conventions` rule that teaches agents how to read * `focus.json`, how focus weight interacts with the * `priority:*` taxonomy, and what they may and may not append to * the file. * * @see FocusArea * @see AgentExpansionRules * @see ./bundles/focus.ts#renderFocusSection */ interface FocusConfig { /** * Path to the focus file relative to the repo root. * @default ".claude/focus.json" */ readonly focusFilePath?: string; /** * Schema version the consuming repo's `focus.json` conforms to. * Bump when the schema changes in a backwards-incompatible way. * @default 1 */ readonly schemaVersion?: number; /** * Cumulative focus-score thresholds that map into the * `priority:*` taxonomy. An issue whose total focus score * (sum of matched focus-area weights) reaches `high` is boosted * to `priority:high`; reaching `medium` keeps it at * `priority:medium`. Scores below `medium` do not affect the * inferred priority. * * @remarks `high` and `medium` must be integers. The JSON schema * at `schemas/focus.schema.json` enforces this for `focus.json`. */ readonly thresholds?: { readonly high: number; readonly medium: number; }; /** * Guard-rails that govern what agents may append to `focus.json`. * When omitted, the rendered rule documents that agents must * **not** append to `focus.json` without human review. */ readonly agentExpansionRules?: AgentExpansionRules; } /******************************************************************************* * * Meetings Config * ******************************************************************************/ /** * Scope classifier for a meeting type. Used by the `meeting-analysis` * bundle to reason about where a given meeting belongs (internal vs * external) and, for external meetings, the nature of the other party. * * - `internal-recurring` — recurring internal meeting (weekly standup, * team sync, planning cadence). * - `internal-oneoff` — one-off internal meeting (brainstorm, kickoff, * retro). * - `external-customer` — meeting with an existing paying customer. * - `external-prospect` — meeting with a sales prospect not yet under * contract. * - `external-partner` — meeting with a partner (integration, vendor, * channel). * - `external-other` — any external meeting that does not fit the * customer / prospect / partner buckets. */ type MeetingScope = "internal-recurring" | "internal-oneoff" | "external-customer" | "external-prospect" | "external-partner" | "external-other"; /** * Generic meeting-kind taxonomy. Each recognized meeting type maps to * one of these kinds, which the `meeting-analysis` bundle uses to * apply type-specific extraction rules in phases 1–2. * * The kind captures the **family** of meeting (how it should be * extracted), independent of the concrete project-specific `id`. For * example a repo may declare `founders-weekly` and `sprint-planning` * as two distinct `id`s that both map to `kind: 'planning'`. * * - `planning` — sprint or project planning. Focus on tasks, goals, * and assignments. Produces direct sprint-plan edits and task * issues. * - `review` — sprint review, retrospective, or decision review. * Captures retro learnings, status updates, and follow-ups. * - `brainstorm` — open-ended ideation. Lower bar for Open Questions * and Future Features; higher bar for requirement/ADR creation * (only Firm items). * - `standup` — short status meeting. Action items, blockers, sprint * status. Phase 3 (Draft) is almost always skipped. * - `external` — customer, prospect, partner, or conference debrief. * Emphasizes people/company profiles, customer pain points (as BR * candidates, not FR), and competitive intel. * - `other` — general meeting with no type-specific handling; the * default workflow applies. */ type MeetingTypeKind = "planning" | "review" | "brainstorm" | "standup" | "external" | "other"; /** * A single meeting-type taxonomy entry declared by the consuming repo. * Each entry names a kind of meeting the project recognizes * (`founders-weekly`, `customer-discovery`, etc.) and supplies the * per-type metadata agents need to route, default, and template that * meeting. * * When `AgentConfigOptions.meetings.meetingTypes` is non-empty, the * `meeting-analysis` bundle renders a "Recognized meeting types" * subsection listing every declared type so agents pick from the * project's vocabulary rather than guessing. * * @see MeetingsConfig * @see ./bundles/meeting-types.ts#renderMeetingTypesSection */ interface MeetingType { /** * Stable machine identifier for the meeting type. Used as the * `meeting_type` frontmatter value on meeting notes and as the key * that the future `agenda` bundle will look up when selecting a * pre-meeting template. * @example 'founders-weekly', 'customer-discovery', 'sprint-review' */ readonly id: string; /** Human-readable label shown in generated rule content. */ readonly label: string; /** * Scope classification — whether the meeting is internal (recurring * or one-off) or external (customer / prospect / partner / other). */ readonly scope: MeetingScope; /** * Generic meeting kind this type maps to. Drives which type-specific * extraction rules (Meeting Type Handling table) the `meeting-analysis` * bundle applies in phases 1–2. Optional — when omitted, the bundle * falls back to `other` (no type-specific rules). * * Multiple concrete `id`s can share the same `kind` (e.g., both * `founders-weekly` and `sprint-planning` may map to `planning`). * * @example 'planning', 'standup', 'external' */ readonly kind?: MeetingTypeKind; /** * Default scheduled duration in minutes. Optional; agents may use * this when proposing calendar holds or when validating a * transcript's recorded duration against the type's expected * duration. */ readonly defaultDurationMinutes?: number; /** * Path to a pre-meeting agenda skeleton for this type, resolved * relative to `MeetingsConfig.agendaTemplateRoot`. Optional — a * type without a template simply has no canned agenda. * @example 'founders-weekly.md', 'customer-discovery/v2.md' */ readonly agendaTemplatePath?: string; /** * Human-readable cadence descriptor. Free-form text — agents * surface the string verbatim rather than parsing it. * @example 'weekly', 'every 2 weeks', 'one-off' */ readonly cadence?: string; } /** * A single meeting-area entry. Meeting areas provide a coarse * routing map from entries in an `areas:` frontmatter list on a * meeting note to the sub-tree of the docs root where phase-4 * direct edits should land. A single meeting may declare multiple * areas; every matching entry's `docRoot` is in-scope for direct * edits on that meeting (see the `meeting-analyst` agent's * **Areas filtering** section for the full gating contract). * * Example: declaring * `{ id: 'product-engineering', label: 'Product & Engineering', docRoot: 'product' }` * tells the `meeting-analysis` bundle that a meeting whose frontmatter * carries `areas: [product-engineering]` should have its direct edits * routed under `/product/`. * * When `AgentConfigOptions.meetings.meetingAreas` is non-empty, the * `meeting-analysis` bundle renders an "Area → doc-root mapping" * subsection documenting every declared area and its resolved * destination. * * @see MeetingsConfig * @see ./bundles/meeting-types.ts#renderMeetingTypesSection */ interface MeetingArea { /** * Stable machine identifier for the area. Matches one of the * entries in the `areas:` frontmatter list on meeting notes. * @example 'product-engineering', 'go-to-market', 'operations' */ readonly id: string; /** Human-readable label shown in generated rule content. */ readonly label: string; /** * Destination folder for phase-4 direct edits on meeting notes * carrying this area. Interpreted **relative to the resolved * docs root** (`AgentPathsConfig.docsRoot`, default * `docs/src/content/docs`). Do not include a leading slash. * @example 'product', 'gtm', 'operations' */ readonly docRoot: string; } /** * Tunes how the `meeting-analyst` Phase 4 (Link) decides whether an * extracted action item becomes a GitHub issue or stays recorded only * in the notes `## Action Items` table. * * The bundle ships a baked-in **agent-workability test**: an action * item is filed (through its dedicated downstream channel — * `req:write`, `docs:write`, `bcm:*`, `research:scope`, etc.) only when * completing it produces a documentation deliverable an automated agent * can author in this repo's docs tree. Human-owned, real-world tasks * (send/schedule/install/decide/communicate/get-access/build-elsewhere) * are recorded only in the notes table and never filed as an issue. * * Every field is optional; the defaults reproduce the baked-in * behaviour. Supplying this config lets a consumer opt out of the split * or extend the verb cues that classify an item without forking the * bundle. * * @see MeetingsConfig * @see ./bundles/meeting-types.ts#renderMeetingTypesSection */ interface ActionItemFilingConfig { /** * Master switch for the agent-workability split. When `true` * (default) human-owned action items are recorded only in the notes * `## Action Items` table and only agent-workable doc deliverables * are filed as issues. Set to `false` to restore the legacy * behaviour where Phase 4 files a generic issue for every * non-document action item. * @default true */ readonly enabled?: boolean; /** * Extra cues that mark an action item as **human-owned** (recorded in * the notes table only, never filed). Appended to the bundle's * built-in cue list. Supply short verb phrases as they would appear * in an action item, e.g. "reconcile the books", "renew the domain". */ readonly humanOwnedCues?: ReadonlyArray; /** * Extra cues that mark an action item as **agent-workable** (filed * through its dedicated downstream channel). Appended to the bundle's * built-in cue list. Supply short verb phrases, e.g. "draft the * onboarding runbook", "document the API contract". */ readonly agentWorkableCues?: ReadonlyArray; } /** * Meeting-analysis injection points — the set of typed * configurations agents consult when classifying, routing, and * templating a meeting. Every field is optional. * * - `meetingTypes` — the set of meeting types the repo recognizes. * - `meetingAreas` — maps entries in the `areas:` frontmatter list * to doc-root sub-trees. * - `agendaTemplateRoot` — where pre-meeting agenda skeletons live. * - `actionItemFiling` — tunes the Phase 4 agent-workability split that * keeps human-owned action items out of the issue queue. * * When supplied, the `meeting-analysis` bundle conditionally renders * subsections into the `meeting-processing-workflow` rule — * "Recognized meeting types" (when `meetingTypes` is non-empty), * "Area → doc-root mapping" (when `meetingAreas` is non-empty), and * "Action-item filing policy" (when `actionItemFiling` overrides a * default). * * @see MeetingType * @see MeetingArea * @see ActionItemFilingConfig * @see ./bundles/meeting-types.ts#renderMeetingTypesSection */ interface MeetingsConfig { /** * Meeting-type taxonomy. An empty or missing list means the * consuming repo does not classify meetings by type; the * `meeting-analysis` bundle falls back to its generic 4-phase * behaviour. */ readonly meetingTypes?: ReadonlyArray; /** * Meeting-area map. An empty or missing list means the consuming * repo does not route meetings by area; phase-4 direct edits * continue to land under the default meetings root. */ readonly meetingAreas?: ReadonlyArray; /** * Root folder containing pre-meeting agenda-template skeletons. * Resolved at issue-creation time by the (future) `agenda` bundle * when selecting a template for a given `meeting_type`. * * When unset, the `meeting-analysis` bundle documents the default * of `/_agenda-templates` — consumers that wire a * custom `AgentPathsConfig.meetingsRoot` inherit the override * automatically. * @default "/_agenda-templates" */ readonly agendaTemplateRoot?: string; /** * Tunes the Phase 4 (Link) agent-workability split that decides * whether an extracted action item is filed as a GitHub issue or * recorded only in the notes `## Action Items` table. Omit to accept * the bundle's baked-in split (file agent-workable doc deliverables, * keep human-owned tasks notes-only). */ readonly actionItemFiling?: ActionItemFilingConfig; } /******************************************************************************* * * Agent Features Config * ******************************************************************************/ /** * Per-tier lists of domain-specific source examples injected into the * base bundle's "Source Quality & Verification" rule under each of the * T1 / T2 / T3 / T4 headings. * * Every field is optional. When a field is set, the `base` bundle * renders a bullet list of the supplied examples beneath the * corresponding tier heading; when a field is unset, the tier heading * renders with its generic examples only. * * The examples are emitted verbatim — supply fully-formed descriptive * strings ("FHIR R4 capability statements", "SEC EDGAR filings"). * * @see AgentFeaturesConfig * @see ./bundles/features.ts#renderSourceTierExamples */ interface SourceTierExamples { /** * Primary living sources — T1. Examples that update when reality * changes (official product docs, company about/leadership pages, * live government registries). */ readonly t1?: ReadonlyArray; /** * Primary snapshot sources — T2. Examples that capture a moment in * time from the authoritative party (press releases, earnings * transcripts, regulatory filings, conference presentations). */ readonly t2?: ReadonlyArray; /** * Secondary sources — T3. Examples that interpret primary material * (news articles, trade press, analyst reports, industry databases). */ readonly t3?: ReadonlyArray; /** * Self-reported or marketing sources — T4. Examples that require * corroboration before any factual claim rides on them (vendor * comparison pages, testimonials, social posts, job listings, * Wikipedia). */ readonly t4?: ReadonlyArray; } /** * A single consumer-supplied doc-section template injected into a * bundle's rule content at rule-generation time. * * The configulator framework is deliberately generic here — it * matches by `bundleName` and appends `body` verbatim under a new * `## ` heading after the first rule in the target * bundle whose content contains `## `. Named feature * toggles (stealth-mode, consortium-model, etc.) belong in downstream * consumer repos that compose the primitive this interface exposes. * * Placeholder substitution (`{{foo}}`) is **not** performed at * rule-generation time — the `body` string is emitted verbatim. * Any templating is the agent's responsibility at document-authoring * time, not configulator's. * * @see AgentFeaturesConfig * @see ./bundles/features.ts#renderCustomDocSection */ interface CustomDocSection { /** * Target bundle id. Must match an `AgentRuleBundle.name` that is * active in the project. If no active bundle has this name, the * section is silently dropped. * @example 'company-profile', 'meeting-analysis', 'bcm-writer' */ readonly bundleName: string; /** * Heading text (without any leading `#` characters) of the existing * section this custom section is inserted after. The framework * searches every rule in the target bundle for a heading line at * any level (`#` through `######`) whose text equals `afterSection`; * the first rule that contains such a heading is the insertion site. * Matching any level lets consumers hook into single-`#` bundle-rule * titles (e.g. `# Company Profile Workflow`) as well as nested `##` / * `###` subsections. If no rule contains a matching heading, the * section is silently dropped. * * Multiple entries targeting the same heading render in supplied * order — the first entry lands directly beneath the target heading * block, the second below it, and so on. * @example 'Company Profile Workflow', 'Company Type Taxonomy', 'Output Boundaries' */ readonly afterSection: string; /** * Heading text for the injected section, rendered as * `## `. * @example 'Consortium Membership', 'Vortex Relevance' */ readonly sectionTitle: string; /** * Markdown body emitted verbatim beneath the `## ` * heading. Trailing newlines are trimmed before rendering. */ readonly body: string; } /** * Framework injection points for source-tier customization and * custom doc sections. Supplied via `AgentConfigOptions.features`. * * - `sourceTierExamples` — per-tier domain-specific examples that get * listed under each tier in the base bundle's "Source Quality & * Verification" rule. * - `customDocSections` — per-bundle section templates that render * verbatim after an existing section heading in the target bundle's * rule content. * * Named feature toggles (`consortium-model`, `stealth-mode`, etc.) * are deliberately **not** part of this config surface. They belong * in downstream consumer repos built on top of the generic * `customDocSections` primitive. * * @see SourceTierExamples * @see CustomDocSection * @see ./bundles/features.ts */ interface AgentFeaturesConfig { /** * Per-tier lists of domain-specific source examples injected under * T1 / T2 / T3 / T4 in the base bundle's "Source Quality & * Verification" rule. */ readonly sourceTierExamples?: SourceTierExamples; /** * Custom doc-section templates to inject into active bundles at * rule-generation time. Each entry names the target bundle, the * section heading to insert after, the injected section's title, * and the body to emit verbatim. */ readonly customDocSections?: ReadonlyArray; } /******************************************************************************* * * Agent Tier Config * ******************************************************************************/ /** * A single consumer-supplied agent-type → funnel-tier mapping. * * The `type` field is the GitHub `type:*` label value **without** the * `type:` prefix (e.g. supply `"company-profile"`, not * `"type:company-profile"`). The `tier` field is a funnel tier in the * range **0–4**: * * - **0 — routing.** Unblocks other work. Picked first on a priority tie. * - **1 — research.** Feeds downstream pipelines. * - **2 — profiles.** Consumes research. * - **3 — synthesis.** Produces deliverables. * - **4 — support.** Important but not pipeline-critical. * * The `orchestrator` bundle validates this value at synth time and * fails the build when the tier is outside 0–4 or when `type` is * empty/whitespace. * * @see AgentTierConfig * @see ./bundles/tiers.ts#renderAgentTierSection */ interface AgentTierEntry { /** * GitHub `type:*` label value (without the `type:` prefix). * @example 'research', 'company-profile', 'requirement' */ readonly type: string; /** * Funnel tier 0–4. Lower tiers dispatch first when priority is tied. */ readonly tier: 0 | 1 | 2 | 3 | 4; } /** * Funnel-tier configuration consumed by the `orchestrator` bundle. * * The orchestrator sorts eligible issues by * **priority desc → tier asc → issue number asc**. Lower tier numbers * win ties on priority so research work feeds downstream pipelines * before synthesis consumes attention. * * Every field is optional. When the whole config is absent the * orchestrator renders its built-in default tier table (matching the * openhi `DISPATCHER.md` source). Two override knobs are supported: * * - `tiers` — **replace** the default list wholesale. Rare; reserved * for repos that want a bespoke taxonomy end-to-end. * - `customTypes` — **extend** whichever list is in play. Entries * with a `type` that already exists in the default (or replacement) * list win — consumer overrides take precedence. * * @see AgentTierEntry * @see ./bundles/tiers.ts#resolveAgentTiers */ interface AgentTierConfig { /** * Replacement tier list. When supplied, the built-in default list * is discarded and this list is used as the base before * `customTypes` are merged in. Supply an empty array is an error — * omit the field to keep the defaults. */ readonly tiers?: ReadonlyArray; /** * Additional tier mappings merged on top of whatever list is in * play (default or replacement). Later entries override earlier * entries on `type` collision, so consumer-supplied mappings win * over the built-in defaults. */ readonly customTypes?: ReadonlyArray; } /******************************************************************************* * * Scope Gate Config * ******************************************************************************/ /** * Threshold pair describing the inclusive upper bounds of the `small` / * `medium` scope classes. Any issue above `medium` is classified `large` * and rejected by the scope gate. * * Every issue is scored on two signals read from the issue body: * * - **Acceptance criteria** — the count of checkbox lines * (`- [ ]` / `- [x]`) under the issue's `## Acceptance Criteria` * section. * - **Sources** — the count of bullet list items under the issue's * `## Inputs` / `## References` / `## Sources` sections (whichever * exists; they're summed if multiple exist). * * `small` requires **both** counts to be at or below `smallMax`. * `medium` requires **both** counts to be at or below `mediumMax`. * Anything above `mediumMax` on either axis is `large`. * * @see ScopeGateConfig * @see ./bundles/scope-gate.ts#classifyIssueScope */ interface ScopeGateThresholds { /** * Inclusive upper bound on the `acceptance-criteria` / `sources` * count for the **small** scope class. */ readonly smallMax: number; /** * Inclusive upper bound on the `acceptance-criteria` / `sources` * count for the **medium** scope class. Must be strictly greater * than `smallMax`. */ readonly mediumMax: number; } /** * Scope-gate configuration consumed by the `orchestrator` bundle. * * The scope gate rejects oversized issues at dispatch time and * instructs the orchestrator to decompose them into phased sub-issues * instead of claiming a worker session for a multi-hour task. The * gate reads the **issue body only** — it does not inspect the * repository. Heuristics are deliberately repo-agnostic so any * consuming repo can adopt the contract without customizing code. * * Two threshold dials control the classification: * * - `acceptanceCriteria` — counts of checkbox lines under the issue's * `## Acceptance Criteria` section. * - `sources` — counts of bullet list items under the issue's * `## Inputs` / `## References` / `## Sources` sections. * * An issue is classified **small** when both counts are at or below * the `small` thresholds, **medium** when both are at or below the * `medium` thresholds, and **large** otherwise. Large issues are * rejected — the orchestrator applies `status:needs-attention`, * posts a decomposition proposal comment, and (when `autoFile` is * true) files the proposed phased sub-issues automatically. * * Consumers may override the per-axis thresholds, the decomposition * comment template, and the auto-file toggle. When the whole config * is absent the orchestrator applies openhi's published defaults * (small: ≤3 AC + ≤2 sources; medium: ≤6 AC + ≤5 sources). * * Malformed configs — thresholds that are negative, non-integer, or * inverted (medium ≤ small) — fail the build at synth time via * `validateScopeGateConfig`. * * @see ScopeGateThresholds * @see ./bundles/scope-gate.ts#resolveScopeGate * @see ./bundles/scope-gate.ts#validateScopeGateConfig * @see ./bundles/scope-gate.ts#renderScopeGateSection */ interface ScopeGateConfig { /** * Master switch for the scope gate. When `false`, the orchestrator * dispatches issues of every size and no decomposition proposal is * posted. Defaults to `true` — scope gating is on by default. */ readonly enabled?: boolean; /** * Classification thresholds for **acceptance-criteria counts** * (checkbox lines under `## Acceptance Criteria`). * Defaults to `{ smallMax: 3, mediumMax: 6 }` — openhi's published * heuristic. */ readonly acceptanceCriteria?: ScopeGateThresholds; /** * Classification thresholds for **source counts** (bullet list * items under the issue's `## Inputs` / `## References` / * `## Sources` sections). Defaults to `{ smallMax: 2, mediumMax: 5 }`. */ readonly sources?: ScopeGateThresholds; /** * When `true`, the orchestrator files the proposed phased * sub-issues automatically after posting the decomposition * proposal comment. When `false` (the default), it posts the * proposal and stops — a human is expected to file the sub-issues. * * Autofile is **opt-in** because sub-issue creation permanently * modifies the project state; repos that adopt the feature should * validate the decomposition template first and graduate to * autofile once they trust the output. * @default false */ readonly autoFile?: boolean; /** * Override for the decomposition-proposal comment body posted on * a rejected `large` issue. When unset, the bundle ships a generic * template that the orchestrator fills in with the classified axis * (`acceptance-criteria` / `sources`), the observed counts, and a * boilerplate `Phase 1 / Phase 2 / Phase 3` skeleton for the agent * to refine. * * The override string may embed the following placeholders — the * orchestrator substitutes them at comment-composition time: * * - `` — observed acceptance-criteria count. * - `` — observed sources count. * - `` — the `acceptanceCriteria.mediumMax` threshold * the issue tripped. * - `` — the `sources.mediumMax` threshold the * issue tripped. * * Placeholders use the angle-bracketed uppercase-snake form — not * `{{curly-brace}}` form — because `AgentConfig`'s template * resolver claims the curly-brace namespace at rule generation * time and would rewrite `{{acCount}}` to `` before the * agent ever saw it. * * Placeholder substitution is performed by the orchestrator at * runtime — configulator emits the template verbatim. */ readonly decompositionTemplate?: string; /** * Per-phase-label threshold overrides. Issues whose `type:*` / * phase label matches one of the keys in this map are classified * against the override's thresholds instead of the global * `acceptanceCriteria` / `sources` defaults. This lets * **content-spec workflows** — issues whose AC list is the * per-section content checklist for one cohesive document, not a * phase-completion checklist that can be decomposed — clear the * gate even when their AC count is well above the global cap. * * Configulator ships seventeen opt-in defaults out of the box — * each calibrated against an observed cohort of phase-template * issues whose AC list is the per-section content checklist for * one cohesive deliverable rather than a phase-completion * checklist that can be decomposed: * * - `req:write` — formal requirement documents (ADR / TR / OPS / * SEC / NFR / UX / MT) routinely carry 12–20 ACs covering * per-section content invariants. Default override: * `{ acceptanceCriteria: { mediumMax: 20 } }` (sources unchanged). * - `bcm:scaffold` — multi-section BCM documents compress sub- * addendum requirements into coarse ACs but still land above * the global cap. Default override: * `{ acceptanceCriteria: { mediumMax: 12 } }` (sources unchanged). * - `research:verify` — fixed-shape verify template emitted by * the `research-analyst` slice phase (uniformly `ac=9 / src=2` * in the wild). Default override: * `{ acceptanceCriteria: { mediumMax: 12 } }` (sources unchanged). * - `software:profile` — single software-product profile pages * with per-section content invariants. Default override: * `{ acceptanceCriteria: { mediumMax: 14 } }` (sources unchanged). * - `regulatory:research` — single regulation pages covering * jurisdiction, scope, obligations, penalties, and effective * dates (single-document conformance scans run ac=11). Default * override: * `{ acceptanceCriteria: { mediumMax: 12 } }` (sources unchanged). * - `standards:research` — single standard-version research notes * enumerating one cohesive deliverable across per-section ACs * (candidate encodings, reconciliation, storage/query analysis, * citations, recommendation). Sibling of `regulatory:research`. * Default override: * `{ acceptanceCriteria: { mediumMax: 10 } }` (sources unchanged). * - `software:map` — single capability-mapping matrix file whose * Sources block typically cross-references the entire BCM tree * slice. Default override: * `{ acceptanceCriteria: { mediumMax: 12 }, sources: { mediumMax: 15 } }`. * - `bcm:connect` — connect-phase outputs that cross-link a * capability to upstream value streams, downstream profiles, * and adjacent capabilities. Default override: * `{ acceptanceCriteria: { mediumMax: 12 }, sources: { mediumMax: 8 } }`. * - `software:matrix` — physician-RCM-style feature matrices * (per-row/per-column requirements for one cohesive matrix * file). Mirrors `software:map` (closest peer). Default * override: * `{ acceptanceCriteria: { mediumMax: 12 }, sources: { mediumMax: 15 } }`. * - `people:research` / `company:research` — profile research * phases whose AC list is the per-section content checklist for * one cohesive research-notes file. Default override: * `{ acceptanceCriteria: { mediumMax: 12 } }` (sources unchanged). * - `people:draft` / `company:draft` — profile draft phases that * read research notes and write one cohesive structured profile * document. Default override: * `{ acceptanceCriteria: { mediumMax: 12 } }` (sources unchanged). * - `req:draft-trace` — `requirements-analyst` draft-trace phase * emitting one proposal-with-traceability document. Sibling of * `req:write`. Default override: * `{ acceptanceCriteria: { mediumMax: 20 } }` (sources unchanged). * - `meeting:notes` — single cohesive Phase-2 meeting-notes * document with per-section content ACs. Default override: * `{ acceptanceCriteria: { mediumMax: 9 } }` (sources unchanged). * - `meeting:draft` / `meeting:link` — Phase-3/Phase-4 meeting * outputs carrying a nested "file N action-item issues" * criterion and cross-referencing every session input. Default * override: * `{ acceptanceCriteria: { mediumMax: 15 }, sources: { mediumMax: 10 } }`. * * Consumer overrides **deep-merge** with the shipped defaults * with **consumer-wins-per-key**: * * - Setting an entry in the map (e.g. `'req:write': {...}`) * replaces the shipped default for that key. * - Setting an entry to `undefined` (e.g. `'req:write': undefined`) * opts out of the shipped default — that label receives no * override and the issue is classified against the global * thresholds. * - Adding a new entry (e.g. `'standards:scope': {...}`) extends * the override map without touching the shipped defaults. * * Within a single override entry, `acceptanceCriteria` and * `sources` are independent — a consumer can override one axis * and leave the other on the global default. The unspecified * axis falls through to the resolved global thresholds at * classification time. * * **Tie-breaking.** When an issue carries multiple labels that * match keys in the override map, the orchestrator selects the * **first match** in alphabetical order on the label name. This * is deterministic and easy to reason about; consumers that need * a specific label to win should rename or remove the colliding * label. * * Malformed override thresholds (negative, non-integer, or * inverted) fail the build at synth time exactly like the * top-level `acceptanceCriteria` / `sources` thresholds. */ readonly bundleOverrides?: { readonly [phaseLabel: string]: ScopeGateBundleOverride | undefined; }; } /** * Per-phase-label threshold override for the scope gate. Each axis * is independent — a consumer can override one and leave the other * on the global default. Both fields are optional; an empty object * is a no-op override (use it sparingly — `undefined` in the * `bundleOverrides` map opts out more clearly). * * @see ScopeGateConfig * @see ScopeGateThresholds * @see ./bundles/scope-gate.ts#resolveOverrideForLabels */ interface ScopeGateBundleOverride { readonly acceptanceCriteria?: ScopeGateThresholds; readonly sources?: ScopeGateThresholds; } /******************************************************************************* * * PR Review Policy Config * ******************************************************************************/ /** * `auto-merge` half of the PR review policy. Today the only * configurable knob is `pathsExemptFromSize` — a list of path globs * that exempt a PR from the `human-required.size` rule (rule #6 in * the precedence walk). * * The reviewer walks every changed path in the PR and skips rule #6 * when **every** path matches at least one glob in this list. * Doc-only PRs routinely exceed the 500-insertion threshold (large * migrations, bulk additions, refresh passes) but carry no production * risk that warrants forcing a human reviewer, so the default * carve-out exempts `docs/**` out of the box. * * @see PrReviewPolicyConfig * @see ./bundles/pr-review-policy.ts#DEFAULT_PATHS_EXEMPT_FROM_SIZE */ interface PrReviewAutoMergeConfig { /** * Path globs that exempt a PR from the `human-required.size` rule. * When **every** changed path in the PR matches at least one glob * in this list, the reviewer skips rule #6 (size threshold) and * continues with the rest of the precedence walk. * * Defaults to `["docs/**"]` — the entire Starlight docs tree every * configulator consumer ships. Override with a custom list to * exempt additional doc-only roots (e.g. `docs/research/**` for * research notes that live outside the Starlight tree): * * ```typescript * agentConfig: { * prReviewPolicy: { * autoMerge: { * pathsExemptFromSize: ["docs/**", "docs/research/**"], * }, * }, * } * ``` * * Pass `[]` to disable the carve-out entirely and apply the size * rule to every PR regardless of path. Pass non-empty entries only * — empty / whitespace-only strings fail synth. * * @default ["docs/**"] */ readonly pathsExemptFromSize?: ReadonlyArray; } /** * CI-verification half of the PR review policy. * * The reviewer confirms CI before enabling auto-merge by reading the * GitHub **check-runs** rollup (`gh pr checks` / * `statusCheckRollup`) — the canonical source GitHub itself uses for * branch protection. That read covers every check context (Actions, * third-party CI, GitHub Apps, commit statuses), so it stays the * primary path for every consumer. * * The check-runs endpoint returns HTTP 403 `Resource not accessible * by personal access token` when the reviewer authenticates with a * **fine-grained PAT** — GitHub exposes no `Checks` permission for * fine-grained tokens (it is GitHub-App-only), so such a consumer * cannot grant its way out of the 403. On that specific failure the * reviewer falls back to the **Actions runs API** * (`GET /repos/{owner}/{repo}/actions/runs?head_sha=...`), which a * fine-grained PAT with `Actions: Read-only` *can* read. The fallback * only sees GitHub Actions runs, so it needs to know which workflows * to treat as required — that is what `requiredWorkflows` configures. * * GitHub-App and classic-PAT consumers never hit the 403 and never * use the fallback; for them this config is inert. * * @see PrReviewPolicyConfig * @see ./bundles/pr-review-policy.ts#DEFAULT_REQUIRED_WORKFLOWS */ interface PrReviewCiVerificationConfig { /** * Workflow `name`s the reviewer treats as **required** when it has * to fall back to the Actions runs API (because the primary * check-runs read returned a fine-grained-PAT 403). Auto-merge is * gated on every listed workflow's latest run for the PR head SHA * concluding `success` (`skipped` / `neutral` are non-blocking; * `failure` / `cancelled` / `timed_out` / `action_required` block; * `in_progress` / `queued` / a missing run count as not-yet-green). * * Defaults to `[]`. An empty list means **every** workflow run * observed for the head SHA is treated as required — the * conservative zero-config default, so an unknown failing workflow * blocks rather than slips through. Set an explicit list to gate on * a known subset (e.g. ignore an optional or advisory workflow): * * ```typescript * agentConfig: { * prReviewPolicy: { * ciVerification: { * requiredWorkflows: ["build", "pull-request-lint"], * }, * }, * } * ``` * * The list only governs the fallback path. The primary check-runs * read derives "required" from branch protection directly, so a * GitHub-App / classic-PAT consumer is unaffected by this list. * Pass non-empty entries only — empty / whitespace-only strings * fail synth. * * @default [] */ readonly requiredWorkflows?: ReadonlyArray; } /** * PR review policy configuration consumed by the `pr-review` bundle. * * The bundle ships a declarative policy in the rendered CLAUDE.md * (under `## PR Review Policy`) that tells the `pr-reviewer` * sub-agent which PRs may auto-merge and which must wait for a * human reviewer. Most of the policy is fixed — the path globs that * force human review (`human-required.paths`), the issue types * (`release`, `hotfix`), the size thresholds (10 files / 500 * insertions), and the force-auto / force-human label sets — and * does not require per-consumer tuning. * * The one knob exposed today is the **doc-only carve-out** against * the size rule. When the consumer sets * `autoMerge.pathsExemptFromSize`, the rendered policy YAML carries * the override and the precedence walk documents the carve-out so * the reviewer applies it consistently. * * When the whole config is omitted, the bundle ships with the * carve-out enabled and `pathsExemptFromSize: ["docs/**"]` — a * doc-only PR that trips the size threshold is auto-mergeable; any * PR mixing docs and code still falls into `human-required` because * the non-docs path fails the carve-out check. * * Malformed configs — empty / whitespace-only entries in * `pathsExemptFromSize` — fail the build at synth time via * `validatePrReviewPolicyConfig`. * * @see PrReviewAutoMergeConfig * @see ./bundles/pr-review-policy.ts#resolvePrReviewPolicy * @see ./bundles/pr-review-policy.ts#validatePrReviewPolicyConfig */ interface PrReviewPolicyConfig { /** * `auto-merge` half of the policy. Currently exposes a single knob * (`pathsExemptFromSize`) that carves doc-only PRs out of the * size rule. */ readonly autoMerge?: PrReviewAutoMergeConfig; /** * CI-verification half of the policy. Exposes `requiredWorkflows`, * the workflow names the reviewer gates on when it falls back to * the Actions runs API because the primary check-runs read returned * a fine-grained-PAT 403. */ readonly ciVerification?: PrReviewCiVerificationConfig; } /******************************************************************************* * * Run Ratio Config * ******************************************************************************/ /** * Run-ratio configuration consumed by the `orchestrator` bundle. * * The orchestrator keeps a persistent run counter and interleaves * **dispatch runs** (pick the next ready issue, recommend a worker) * with **housekeeping runs** (batch PR review + maintenance scan) on * a configurable dispatch-to-housekeeping ratio. This mirrors * openhi's `DISPATCHER.md` 4:1 cadence — four dispatch runs feed the * worker queue, then one batched housekeeping run flushes the * review backlog and runs maintenance triage so the pipeline never * drifts. * * The counter persists in a small gitignored JSON state file * (default `.state/orchestrator-runs.json`). Every orchestrator * invocation ticks the counter once and classifies the run based on * `runCounter % (ratio + 1) == 0` — the `ratio + 1`th run in every * cycle is a housekeeping run, all others dispatch. * * Every field is optional. When the whole config is absent the * orchestrator ships with openhi's 4:1 defaults baked in (4 dispatch * runs, 1 housekeeping run, state file at the default path, * `opus` / `sonnet` recommended models). * * Malformed configs — non-integer or non-positive `ratio`, empty or * absolute `stateFilePath` — fail the build at synth time via * `validateRunRatioConfig`. * * @see ./bundles/run-ratio.ts#resolveRunRatio * @see ./bundles/run-ratio.ts#validateRunRatioConfig * @see ./bundles/run-ratio.ts#renderRunRatioSection */ interface RunRatioConfig { /** * Master switch for the run-ratio pipeline. When `false`, every * orchestrator run executes the full dispatch pipeline and no * housekeeping batching is performed; PR review and maintenance * remain manual invocations. Defaults to `true` — ratio-based * batching is on by default. */ readonly enabled?: boolean; /** * Number of dispatch runs per housekeeping run. With `ratio = 4`, * runs 1–4 dispatch and run 5 housekeeps; the counter then wraps. * The cycle length is therefore `ratio + 1` runs. * * Must be a positive integer (`ratio >= 1`). Defaults to `4` — * the openhi-published cadence. */ readonly ratio?: number; /** * Path to the run-counter state file, relative to the repo root. * The orchestrator reads, increments, and writes back this file on * every invocation. The file is tiny JSON (`{ "run_counter": }`) * and is expected to be gitignored — each operator's orchestrator * session maintains its own counter. * * Must be a non-empty relative path (no leading `/`). Defaults to * `.state/orchestrator-runs.json`. */ readonly stateFilePath?: string; /** * Human-readable label for the model recommended on dispatch runs. * Rendered verbatim into the orchestrator-conventions rule so the * operator knows which model to run each dispatch session against. * Configulator does **not** set `model:` frontmatter on the * sub-agent — the label is informational. * * Defaults to `"opus"`. */ readonly dispatchModel?: string; /** * Human-readable label for the model recommended on housekeeping * runs. See `dispatchModel` for the rendering contract. * Housekeeping is mechanical (batch PR review + maintenance scan) * so a cheaper model is the documented recommendation. * * Defaults to `"sonnet"`. */ readonly housekeepingModel?: string; } /******************************************************************************* * * Unblock Dependents Config * ******************************************************************************/ /** * Agent-driven unblocking configuration consumed by the `orchestrator` * bundle. * * After any agent applies `status:done` to an issue, it runs a * targeted sweep against that issue's dependents — open issues whose * body contains `Depends on: #`. Dependents whose full * dependency list is now closed flip from `status:blocked` to * `status:ready` immediately, without waiting for the next * orchestrator dispatch cycle. The sweep is implemented by the * shipped `.claude/procedures/unblock-dependents.sh` script. * * Every field is optional. When the whole config is absent the * orchestrator ships with the sweep **enabled** and a generic * "Dependencies resolved by # — unblocking." citation comment. * * Malformed configs — empty / whitespace-only `commentTemplate` or * `partialUnblockCommentTemplate` — fail the build at synth time via * `validateUnblockDependentsConfig`. * * @see ./bundles/unblock-dependents.ts#resolveUnblockDependents * @see ./bundles/unblock-dependents.ts#validateUnblockDependentsConfig * @see ./bundles/unblock-dependents.ts#renderUnblockDependentsSection */ interface UnblockDependentsConfig { /** * Master switch for the agent-driven unblock sweep. When `false`, * agents skip the targeted sweep after applying `status:done`; * dependents wait for the next orchestrator dispatch (Phase C) to * pick up the resolved dependency. * * Defaults to `true` — the sweep is on by default. Repos that * want to keep label transitions manual should set this to `false` * explicitly. */ readonly enabled?: boolean; /** * Override for the citation comment posted on each fully-unblocked * dependent. The string is interpolated at runtime by the shipped * shell script: `` is replaced with the resolving * issue's `#` reference. * * The placeholder syntax deliberately avoids `{{curly-brace}}` — * `AgentConfig`'s template resolver claims that namespace at rule * generation time. * * Defaults to `"Dependencies resolved by — unblocking."`. */ readonly commentTemplate?: string; /** * Whether to post a partial-unblock comment on a dependent whose * full dependency list is **not** yet fully closed. When `true`, * each intermediate dependency resolution leaves a trail of * "still waiting on X" comments on the dependent — useful for * operator visibility on long dependency chains. When `false`, * partial resolutions are logged to the script's stdout only and * the dependent issue stays silent until the final dep closes. * * Defaults to `false` — keeps issue threads clean by default. */ readonly flagPartialUnblockWithAttention?: boolean; /** * Override for the partial-unblock comment posted when * `flagPartialUnblockWithAttention` is `true`. The string is * interpolated at runtime: `` is replaced with the * just-resolved issue's `#` reference, and `` with * the space-separated list of remaining open dependencies * (e.g. `#45 #47`). * * Defaults to `"Dependency resolved, but still waiting on: ."`. */ readonly partialUnblockCommentTemplate?: string; } /******************************************************************************* * * Temporal Framing Config * ******************************************************************************/ /** * Temporal-framing convention consumed by the `base` bundle and every * analyst bundle that writes profile / research content. * * The convention addresses a specific failure mode: present-tense * framing of time-sensitive facts (ownership, leadership tenure, * regulatory status, litigation, dated metrics) silently goes stale, * and downstream refresh passes have no mechanical signal for which * claims to re-verify. The fix is to require an inline * `as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier on the first * occurrence of every time-sensitive claim. * * Every field is optional. When the whole config is absent the * convention ships **enabled** with profile / research path globs and * a five-category cadence table covering ownership, leadership, * regulatory status, litigation, and dated metrics. The optional * `check-temporal-framing.sh` lint is **off** by default — consumers * opt in via `emitChecker: true`. * * Malformed configs — `paths` containing empty entries, `cadences` * containing non-positive integers — fail the build at synth time via * `validateTemporalFramingConfig`. * * @see ./bundles/temporal-framing.ts#resolveTemporalFraming * @see ./bundles/temporal-framing.ts#validateTemporalFramingConfig * @see ./bundles/temporal-framing.ts#renderTemporalFramingRuleContent */ interface TemporalFramingConfig { /** * Master switch for the temporal-framing convention. When `false`, * the base bundle renders a short stub stating that the project * does not enforce explicit `as of` qualifiers; analyst-bundle * authoring phases skip the per-workflow temporal-framing * injection. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; /** * Path globs the rule applies to. Every agent that writes Markdown * matching any of these patterns is expected to add `as of` * qualifiers on first-occurrence time-sensitive claims. * * Defaults to the canonical profile / research subtrees of a * Starlight docs site (`docs/src/content/docs/profiles/**`, * `docs/src/content/docs/industry-research/**`, etc.). Consumers * with a different content layout may replace the list entirely. * * Note that meeting notes, requirement documents, and the * project-context page are deliberately out of scope — their own * dating conventions (file-name date prefix, version frontmatter, * living snapshot under direct human review) already anchor the * temporal meaning of their content. */ readonly paths?: ReadonlyArray; /** * Per-category refresh cadences in days. The five recognized * categories cover the time-sensitive claim patterns surfaced by * the May 2026 sampled drift audit. Each category carries its own * cadence so a refresh agent grepping `as of ` can apply the right * staleness threshold to the right claim. * * Defaults: * * - `ownership` — 180 days * - `company-leadership` — 90 days * - `regulatory-status` — 30 days * - `litigation` — 30 days * - `dated-metrics` — 180 days * * Consumers may override any subset; unspecified categories fall * through to the defaults above. Values must be positive integers. */ readonly cadences?: Partial<{ readonly ownership: number; readonly "company-leadership": number; readonly "regulatory-status": number; readonly litigation: number; readonly "dated-metrics": number; }>; /** * When `true`, configulator emits a * `.claude/procedures/check-temporal-framing.sh` lint helper that * fails non-zero on any covered file containing present-tense * framing of a time-sensitive category without an `as of ` * qualifier anywhere in the file. * * Disabled by default — consumers opt in when they want a hard * pre-commit or CI gate. The rule body itself renders * unconditionally regardless of this flag. * * Defaults to `false`. */ readonly emitChecker?: boolean; } /******************************************************************************* * * Progress Files Config * ******************************************************************************/ /** * Progress-file convention consumed by the `base` bundle and every * phased-agent bundle (bcm-writer, research-pipeline, etc.). * * Every phased agent session writes a small progress file to disk as * its first action after claiming an issue, updates it after each * non-trivial step, and deletes it in the final commit that closes * the issue. If the session crashes, the next session reads the * progress file, confirms on-disk state matches reality, and resumes * from the next uncompleted step rather than starting from scratch. * * The convention complements the stale-branch decision tree — clone * recovery returns the checkout to the default branch; the progress * file restores the *deliverable* to a partially-complete state so * resumed work can build on it. * * Every field is optional. When the whole config is absent the * convention ships **enabled** with JSON-formatted files stored under * `.state/-progress.json`. * * Malformed configs — empty / whitespace-only or absolute `stateDir`, * empty / whitespace-only `filenamePattern`, `filenamePattern` missing * the `` placeholder, unknown `format`, non-positive * `staleAfterHours` — fail the build at synth time via * `validateProgressFilesConfig`. * * @see ./bundles/progress-files.ts#resolveProgressFiles * @see ./bundles/progress-files.ts#validateProgressFilesConfig * @see ./bundles/progress-files.ts#renderProgressFilesRuleContent */ interface ProgressFilesConfig { /** * Master switch for the progress-file convention. When `false`, * the base bundle renders a short stub stating the project does not * enforce progress files; phased-agent bundles skip the * per-workflow "Progress File" injection. Partial-resume guidance * still renders because it applies even without a progress file. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; /** * Root directory (relative to the repo root) where progress files * are written. Must be a non-empty relative path (no leading `/`). * The directory is added to the project's `.gitignore` at synth * time so progress files never land on the default branch. * * Defaults to `.state` — a top-level repo-root directory rather * than a Claude-specific subtree, so the same progress files can * be read and written by any agent runtime (Claude Code, Cursor, * a bespoke worker). */ readonly stateDir?: string; /** * Filename pattern for an individual progress file. The * `` placeholder is substituted at runtime with the * numeric id of the issue the agent is working on (e.g. * `479-progress.json`). * * Must contain the `` placeholder and must not * contain a `/` (use `stateDir` for the directory). * * The placeholder uses the angle-bracketed uppercase-snake form — * not `{{curly-brace}}` form — because `AgentConfig`'s template * resolver claims the curly-brace namespace at rule generation * time. * * Defaults to `"-progress.json"`. */ readonly filenamePattern?: string; /** * Serialization format for the progress-file body. * * - `"json"` (default) — machine-parseable JSON with a typed * schema. Preferred when scripted resume logic reads the file. * - `"markdown"` — human-readable markdown with a YAML frontmatter * block. Preferred for projects that treat the progress file as * an operator-visible log. */ readonly format?: "json" | "markdown"; /** * Whether the final commit that closes the issue must delete the * progress file. When `true` (the default), progress files are a * working-branch artefact and never land on the default branch. * When `false`, progress files are retained as an audit trail. * * Defaults to `true`. */ readonly cleanupOnComplete?: boolean; /** * Stale-threshold in hours for the stale-branch decision tree. A * branch whose progress file's `last_updated` is older than this * many hours **and** that has no matching open PR is considered * abandoned. Rendered verbatim into the stale-branch documentation * so operators have a single authoritative number. * * Must be a positive integer. Defaults to `72` — matches the * orchestrator bundle's in-progress staleness threshold. */ readonly staleAfterHours?: number; } /******************************************************************************* * * Shared Editing Config * ******************************************************************************/ /** * Shared-editing safety convention consumed by the `base` bundle and * every phased-agent bundle that writes rows to a shared registry or * feature-matrix file. * * Multiple concurrent agent sessions frequently need to edit the same * index file — registry `README.md` / `index.md` row tables, category * landing pages, feature matrices. Without a shared contract, parallel * sessions conflict on the index even when their content contributions * are independent, and the resolver may accidentally drop a row. * * The convention covers: pre-edit read-latest, single-entry * deterministic-sort inserts, commit-path verification (read-back the * committed file, assert the new row is present exactly once), and a * scripted merge-conflict resolution recipe. * * Every field is optional. When the whole config is absent the * convention ships **enabled** with the documented default set of * shared index path patterns and the rebase-based conflict strategy. * * Malformed configs — empty `sharedIndexPaths`, empty / * whitespace-only path entry, unknown `conflictStrategy` — fail the * build at synth time via `validateSharedEditingConfig`. * * @see ./bundles/shared-editing.ts#resolveSharedEditing * @see ./bundles/shared-editing.ts#validateSharedEditingConfig * @see ./bundles/shared-editing.ts#renderSharedEditingRuleContent */ interface SharedEditingConfig { /** * Master switch for the shared-editing convention. When `false`, * the base bundle renders a short stub stating the project does not * enforce the convention; phased-agent bundles skip the * per-workflow "Shared Index Editing" injection. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; /** * Path patterns (plain glob strings) that identify shared index * files. Rendered verbatim into the rule body as a bullet list; * agents match the file they are about to edit against the patterns * to decide whether the contract applies. * * Must be a non-empty array of non-empty strings. * * Defaults to the configulator-wide set covering docs-site * `index.md` / `README.md` registry tables and feature-matrix * files. */ readonly sharedIndexPaths?: ReadonlyArray; /** * Whether the rendered convention includes the commit-path * verification protocol (read-back the committed file, assert the * new row is present exactly once). The verification step catches * staging / path bugs that would otherwise silently drop a row. * * Defaults to `true` — verification is on by default. */ readonly verifyCommit?: boolean; /** * Strategy the convention's merge-conflict recipe prescribes when * two agents both add a row to the same shared index: * * - `"rebase"` (default) — `git pull --rebase` the branch, re-insert * the new row in sort order, resolve, `git rebase --continue`. * - `"merge"` — `git pull` (fast-forward or merge commit), re-insert * the row, `git commit`. Use for projects that keep a merge-commit * history on feature branches. */ readonly conflictStrategy?: "rebase" | "merge"; /** * Whether the convention emits the * `.claude/procedures/verify-index-row.sh` helper to disk. The * helper implements the commit-path verification step in a single * shell invocation; consumers that prefer the inline * `git show HEAD:` recipe can leave it disabled. * * Defaults to `false` — the helper is opt-in. */ readonly emitHelper?: boolean; } /******************************************************************************* * * Skill Evals Config * ******************************************************************************/ /** * Skill eval harness convention consumed by the `base` bundle and * every skill-owning bundle (requirements-writer, bcm-writer, etc.). * * Each skill under `//` may ship a regression * suite at `//evals/evals.json`. Suites are * declarative prompt / expected-output fixtures parameterised by a * shared **product-context** fixture (by default * `docs/src/content/docs/project-context.md`) so the same eval shape * works across every project that depends on configulator — each * consuming repo supplies its own `project-context.md`, not its own * forked eval files. * * The convention is declarative: the optional runner script shipped * by `emitRunner: true` walks the skills root, validates every * `evals.json` against the schema, resolves the product-context * fixture, and emits a JSON execution plan to stdout. It never * invokes an LLM itself — the plan is consumed by whatever human or * model actually replays the prompts against the skill. * * Every field is optional. When the whole config is absent the * convention ships **enabled** with the documented default paths, * product-context required, and the runner script opt-in. * * Malformed configs — empty / whitespace-only or absolute * `skillsRoot`, empty / whitespace-only or absolute * `productContextPath` — fail the build at synth time via * `validateSkillEvalsConfig`. * * @see ./bundles/skill-evals.ts#resolveSkillEvals * @see ./bundles/skill-evals.ts#validateSkillEvalsConfig * @see ./bundles/skill-evals.ts#renderSkillEvalsRuleContent */ interface SkillEvalsConfig { /** * Master switch for the skill-eval harness convention. When `false`, * the base bundle renders a short stub stating the project does not * ship skill evals; skill-owning bundles skip their per-bundle * "Skill Evals" injection and the optional runner script is not * emitted even if `emitRunner: true`. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; /** * Root directory (relative to the repo root) under which skill * SKILL.md files live. The runner walks this directory to discover * eval suites at `//evals/evals.json`. * * Must be a non-empty relative path (no leading `/`). * * Defaults to `.claude/skills`, which matches the path every * configulator-managed project ships skills to on disk. */ readonly skillsRoot?: string; /** * Path (relative to the repo root) to the product-context fixture * every eval suite references by default. A per-suite * `product_context` field in an `evals.json` overrides this value * for that suite only. * * Must be a non-empty relative path (no leading `/`). * * Defaults to `docs/src/content/docs/project-context.md`, which is * the file every configulator-managed agent already loads at * session start. */ readonly productContextPath?: string; /** * Whether the runner fails fast when the product-context fixture is * missing. The default (`true`) is deliberately strict — an eval * that silently runs without its fixture produces a false-positive * pass. Projects bootstrapping a new consuming repo that has not * yet authored its `project-context.md` can set this to `false`, * at which point a missing fixture becomes a stderr warning rather * than a hard failure. * * Defaults to `true`. */ readonly requireProductContext?: boolean; /** * Whether the convention emits the * `.claude/procedures/run-skill-evals.sh` helper to disk. The * helper implements the full runner contract (discover, validate, * resolve product-context, emit JSON plan) in a single shell * invocation. Consumers that prefer to roll their own runner can * leave it disabled and follow the inline recipe documented in the * rule body. * * Defaults to `false` — the helper is opt-in. */ readonly emitRunner?: boolean; } /******************************************************************************* * * Upstream Configulator Docs Config * ******************************************************************************/ /** * Upstream-configulator-docs convention consumed by the * `upstream-configulator-docs` bundle. The bundle ships a top-level * CLAUDE.md reminder plus a `.claude/rules/` detail file that point * downstream agents at the upstream `codedrifters/packages` repo: where * configulator's docs live, how to read them via `gh api` / * `gh search code`, and how to file upstream issues for missing * features instead of working around them with downstream rule * overrides. * * Every field is optional. When the whole config is absent the * convention ships **enabled** so every consumer of * `@codedrifters/configulator` carries the upstream pointers by * default. * * Non-CodeDrifters consumers — repos that depend on configulator but * do not want to point downstream agents at this upstream — set * `enabled: false` to drop the bundle's rules entirely. * * @see ./bundles/upstream-configulator-docs.ts */ interface UpstreamConfigulatorConfig { /** * Master switch for the upstream-configulator-docs convention. When * `false`, `AgentConfig.resolveRules` filters out every rule whose * name starts with `upstream-configulator-docs`, so the bundle * contributes nothing to CLAUDE.md or `.claude/rules/`. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; } /******************************************************************************* * * Issue Templates Config * ******************************************************************************/ /** * Issue-templates convention consumed by the `base` bundle and every * phased-agent bundle that files downstream issues. Documents the * single hand-authored reference page that carries one canonical * `gh issue create` recipe per downstream phase label, and enforces * the **reference-don't-inline** rule: bundle rules and agent prompts * cite the matching section of that page instead of duplicating a * full `gh issue create --title ... --label ... --body ...` * invocation. * * The convention is deliberately content-free — configulator cannot * ship meaningful templates because each consuming repo enables a * different subset of bundles and customises phase labels, tier * taxonomies, and body conventions via the Group A injection points. * Instead the bundle renders the rule (what to author, when to cite * it) and optionally emits a starter page + a lint script. * * Every field is optional. When the whole config is absent the * convention ships **enabled** with the documented default templates * path (`docs/src/content/docs/agents/issue-templates.md`), the * default bundle-path patterns, the hard-requirement phrasing, and * both the starter page and the lint script opt-in. * * Malformed configs — empty / whitespace-only or absolute * `templatesPath`, empty `bundlePathPatterns`, empty / * whitespace-only pattern entry — fail the build at synth time via * `validateIssueTemplatesConfig`. * * @see ./bundles/issue-templates.ts#resolveIssueTemplates * @see ./bundles/issue-templates.ts#validateIssueTemplatesConfig * @see ./bundles/issue-templates.ts#renderIssueTemplatesRuleContent */ interface IssueTemplatesConfig { /** * Master switch for the issue-templates convention. When `false`, * the base bundle renders a short stub stating the project does not * enforce the convention; phased-agent bundles skip the per-workflow * "Issue Templates" injection and the optional helper script / * starter page are not emitted even if their `emit*` flags are * true. * * Defaults to `true` — the convention is on by default. */ readonly enabled?: boolean; /** * Repo-relative path to the single issue-templates reference page. * The rendered rule cites this path as the on-disk home of every * `gh issue create` recipe; the lint script uses it as the * allow-listed sentinel path that is permitted to contain full * recipes. * * Must be a non-empty relative path (no leading `/`). * * Defaults to `docs/src/content/docs/agents/issue-templates.md`, * which matches the monorepo-wide singleton `/docs` Starlight site * every configulator-managed repo ships. */ readonly templatesPath?: string; /** * Path patterns (bash-style globs) that identify "bundle files" — * the source files composing agent prompts and skill instructions. * Rendered verbatim into the rule body as a bullet list and emitted * into the lint script when `emitChecker: true`. * * Must be a non-empty array of non-empty strings. * * Defaults to the configulator-wide set covering in-tree bundle * source files, `.claude/agents/**.md` agent prompts, and * `.claude/skills/**.md` skill prompts. */ readonly bundlePathPatterns?: ReadonlyArray; /** * Whether the convention emits the * `.claude/procedures/check-issue-templates.sh` lint to disk. The * script walks a newline-separated list of changed files (from * stdin or positional arguments) and fails non-zero when any file * matches a bundle-path pattern and contains a multi-line * `gh issue create ... --title` invocation. * * Disabled by default — the lint is opt-in for repos that want a * hard CI gate or pre-commit hook. Consumers that prefer to * enforce the rule via review discipline alone can leave it off. */ readonly emitChecker?: boolean; /** * Whether the convention emits a minimal starter templates page to * disk at ``. The starter carries the expected * structure (how-to-use preamble + one example * `## Template: ` section) so consumers adopting the * convention on a green-field repo have a working template to * extend. * * Disabled by default because the page is hand-authored — an * emitted stub would conflict with existing content on repos * adopting the convention late. */ readonly emitStarterDoc?: boolean; /** * Whether the rendered rule body phrases the reference-don't-inline * rule as a **hard requirement** (`MUST`) or a **strong * recommendation** (`SHOULD`). Defaults to `true` — the * hard-requirement phrasing matches the consolidation goal the * convention is designed for. Consumers that treat consolidation * as aspirational can soften the phrasing by setting this to * `false`. */ readonly requireReference?: boolean; } /******************************************************************************* * * Scheduled Tasks Config * ******************************************************************************/ /** * Partial override for a single default scheduled-task entry keyed by * `taskId`. Every field is optional — only supplied fields replace the * default. Use `enabled: true` to opt a task in without otherwise * changing its shape. * * @see ScheduledTasksConfig * @see ./bundles/scheduled-tasks.ts#DEFAULT_SCHEDULED_TASK_ENTRIES */ interface ScheduledTaskOverride { /** Whether the task is emitted to disk. Defaults to the registry entry's value (normally `false`). */ readonly enabled?: boolean; /** * Cron expression. Set to `null` to mean manual-only. Default is * manual-only; every configulator default entry ships without a cron. */ readonly cron?: string | null; /** Recommended model label surfaced on frontmatter and the rendered table. */ readonly recommendedModel?: "opus" | "sonnet" | "haiku"; /** One-line description for the rendered table and SKILL.md frontmatter. */ readonly description?: string; /** * Exact `type:*` label values (without the `type:` prefix) the * worker should pick up. When set, replaces the default entry's * single `typeLabel` with a multi-type filter (useful for the * routing-bucket `worker-issue` which covers * `feat/fix/chore/refactor/docs/release/hotfix`). Empty array not * allowed; use `undefined` to keep the default. */ readonly typeLabels?: ReadonlyArray; /** * Exact phase-label values the worker filters on. When set, takes * precedence over `phasePrefix` (prefix-match is dropped). Empty * array not allowed; use `undefined` to keep the default. */ readonly phaseLabels?: ReadonlyArray; /** * Discriminator for the rendered `SKILL.md` shape and the rendered * registered-tasks table cell. Defaults to the registry entry's * value. See `ScheduledTaskEntry.kind` for semantics. */ readonly kind?: "issue-worker" | "pipeline"; } /** * Fully consumer-authored scheduled-task entry. Entries whose `taskId` * collides with a built-in default **replace** the default outright; * entries with a new `taskId` are appended to the registry. * * @see ScheduledTasksConfig */ interface ScheduledTaskEntry { /** * Unique task directory name. Emitted under * `//SKILL.md`. Every `taskId` must be unique within * the resolved registry. */ readonly taskId: string; /** Target sub-agent name (filename stem under `.claude/agents/`). */ readonly agent: string; /** Human-readable agent label for rendered tables / frontmatter. Defaults to `agent`. */ readonly agentLabel?: string; /** GitHub `type:*` label value (without the `type:` prefix) that gates which issues this worker picks up. Ignored when `typeLabels` is set. */ readonly typeLabel: string; /** * Exact `type:*` label values (without the `type:` prefix) the * worker picks up. When set, replaces the single `typeLabel` with * a multi-type filter (e.g. the routing-bucket `worker-issue` * covers `feat/fix/chore/refactor/docs/release/hotfix`). Empty * array not allowed. */ readonly typeLabels?: ReadonlyArray; /** Optional phase-label prefix (e.g. `research:`). When set, the worker filters on both `type:` and any `*` label. Ignored when `phaseLabels` is set. */ readonly phasePrefix?: string; /** * Exact phase-label values the worker filters on (e.g. * `["req:write"]` for a writer that only picks up issues whose * phase label is exactly `req:write`). When set, takes precedence * over `phasePrefix`. Empty array not allowed. */ readonly phaseLabels?: ReadonlyArray; /** Recommended model label surfaced on frontmatter and the rendered table. */ readonly recommendedModel: "opus" | "sonnet" | "haiku"; /** Whether the task is emitted to disk. Defaults to `false` — opt-in. */ readonly enabled?: boolean; /** Cron expression. Defaults to `null` (manual-only). */ readonly cron?: string | null; /** One-line description for the rendered table and SKILL.md frontmatter. */ readonly description?: string; /** * Discriminator that controls how the task's `SKILL.md` body and * registered-tasks table cell are rendered. * * - `"issue-worker"` (default) — the task delegates one unit of work * to the generic `issue-worker` agent. The rendered SKILL.md * instructs the worker to follow `.claude/agents/issue-worker.md` * with the `type:*` / phase-label filter applied at the * find-an-issue step. The registered-tasks table renders the * `type:*` filter normally. * - `"pipeline"` — the task runs an end-to-end pipeline (e.g. the * orchestrator's full cycle: triage / unblock, maintenance, queue * scan, delegate, cleanup). The rendered * SKILL.md skips the issue-worker contract and points the operator * at the target sub-agent for the full workflow. The * registered-tasks table renders `_(none — pipeline manager)_` * for the type-label cell because pipeline tasks orchestrate other * workers rather than filtering on a single label themselves. * * Defaults to `"issue-worker"`. */ readonly kind?: "issue-worker" | "pipeline"; } /** * Per-agent scheduled-task configuration consumed by the `orchestrator` * bundle. Drives the per-agent worker layout at * `//SKILL.md` — one task per agent type with its own * label filter, recommended model, and opt-in enablement. * * The pattern mirrors vortex-management's `.claude/scheduled-tasks/` * layout: splitting workers by type lets expensive research agents run * on a different cadence than cheap synthesis agents, and disabling a * whole category is a one-line toggle instead of label surgery. * * Every task ships **disabled by default**. Consumers opt in * explicitly; the generated repo contains no scheduled-task files * unless at least one task is enabled. * * Every field is optional. When the whole config is absent the * scheduled-tasks subsystem is enabled but no task is emitted (every * default is disabled). * * Malformed configs — unknown model value, empty `taskId` / `agent` / * `typeLabel`, duplicate `taskId` inside `tasks`, override referencing * an unknown default `taskId` — fail the build at synth time via * `validateScheduledTasksConfig`. * * @see ScheduledTaskEntry * @see ScheduledTaskOverride * @see ./bundles/scheduled-tasks.ts#resolveScheduledTasks * @see ./bundles/scheduled-tasks.ts#validateScheduledTasksConfig * @see ./bundles/scheduled-tasks.ts#renderScheduledTasksSection */ interface ScheduledTasksConfig { /** * Master switch for the scheduled-tasks subsystem. When `false`, no * `//SKILL.md` files are emitted regardless of * individual task overrides. Defaults to `true` — the subsystem is * on, but every default task is still disabled. */ readonly enabled?: boolean; /** * Root directory (relative to the repo root) for emitted * scheduled-task files. Each task renders to * `//SKILL.md`. * * Must be a non-empty relative path (no leading `/`). Defaults to * `.claude/scheduled-tasks`. */ readonly root?: string; /** * Per-task overrides keyed by the built-in `taskId`. Use this to * flip the `enabled` toggle on one or more default tasks, override * the cron expression, or change the recommended model without * re-declaring the rest of the entry. * * Every key must match a built-in default `taskId`; unknown keys * fail the build. Use `tasks` to append new entries. */ readonly overrides?: Readonly>; /** * Fully consumer-authored task entries. Entries whose `taskId` * matches a built-in default **replace** the default wholesale; * entries with a new `taskId` are appended. * * Every `taskId` must be unique within this list (duplicate * `taskId`s inside `tasks` fail the build). Use `overrides` to * tweak a single field without re-declaring the rest of the entry. */ readonly tasks?: ReadonlyArray; } /******************************************************************************* * * Issue Defaults * ******************************************************************************/ /** * Per-phase-label override for the default `status:*` and * `priority:*` labels every bundle-shipped `gh issue create` recipe * uses when filing a downstream issue. Both fields are optional — * a consumer may override `status` only, `priority` only, or both. * * - `status` rejects unknown values at synth time. Allowed values * are the canonical `status:*` taxonomy (`ready`, `blocked`, * `in-progress`, `ready-for-review`, `needs-attention`, `done`, * `deferred`). * - `priority` rejects unknown values at synth time. Allowed values * are the canonical five-level priority taxonomy (`critical`, * `high`, `medium`, `low`, `trivial`). */ interface IssueDefaultsOverride { readonly status?: "ready" | "blocked" | "in-progress" | "ready-for-review" | "needs-attention" | "done" | "deferred"; readonly priority?: "critical" | "high" | "medium" | "low" | "trivial"; } /** * Map of phase label (e.g. `people:research`, `company:draft`, * `req:write`) to the default `status` / `priority` labels every * bundle-shipped `gh issue create` recipe should carry when filing * an issue with that phase label. * * Keyed by phase label rather than by `type:*` so consumers can * discriminate per phase — a `type:company-profile` consumer might * want `company:research` deferred but `company:analyze` ready. * * Unknown phase labels (no bundle currently files them) are * accepted silently so the config does not break when a bundle is * later removed from `includeBundles`. * * @see ./bundles/issue-defaults.ts#resolveIssueDefaults * @see ./bundles/issue-defaults.ts#validateIssueDefaultsConfig * @see ./bundles/issue-defaults.ts#labelsForPhase */ type IssueDefaultsConfig = Readonly>; /******************************************************************************* * * AgentConfig Options * ******************************************************************************/ /** * Options for the AgentConfig component. */ interface AgentConfigOptions { /** * Target platforms to generate configuration for. * @default [AGENT_PLATFORM.CURSOR, AGENT_PLATFORM.CLAUDE] */ readonly platforms?: ReadonlyArray; /** * Default model tier for analyst / writer / profile / research / regulatory / * standards / business-models / customer / industry / meeting / maintenance * sub-agents that don't pin a model explicitly. Defaults to 'balanced' (sonnet). * Override to 'powerful' (opus) to restore pre-2026-05-08 behaviour during a * migration window. The four reviewer/orchestrator agents (orchestrator, * issue-worker, pr-reviewer, requirements-reviewer) ignore this knob and * stay on POWERFUL because their workload requires it. * @default 'balanced' */ readonly defaultAgentTier?: "powerful" | "balanced" | "fast"; /** * Per-bundle override for `defaultAgentTier`. Bundle name keyed * (matches the bundle's `name` field, e.g. `'meeting-analysis'`, * `'company-profile'`). Bundles not present in the map fall back * to `defaultAgentTier`. * * Only the six tier-aware bundles (`meeting-analysis`, * `company-profile`, `customer-profile`, `people-profile`, * `software-profile`, `maintenance-audit`) read this map — the * four reviewer/orchestrator agents (orchestrator, issue-worker, * pr-reviewer, requirements-reviewer) ignore both this map and * `defaultAgentTier` and stay on POWERFUL. * * Synth fails with a clear error if a key in the map does not * match one of the six tier-aware bundle names — typos or * deprecated bundle names would otherwise be silently ignored. * * @default `{}` */ readonly bundleAgentTiers?: Readonly>; /** * Additional agent rules to generate alongside auto-detected and bundled rules. * Custom rules override bundled rules of the same name. */ readonly rules?: ReadonlyArray; /** * Additional skills to generate. */ readonly skills?: ReadonlyArray; /** * Custom sub-agent definitions. */ readonly subAgents?: ReadonlyArray; /** * Custom procedure definitions (executable shell scripts). */ readonly procedures?: ReadonlyArray; /** * Slash commands rendered to `.claude/commands/.md`. Includes any * default commands shipped by an active bundle (e.g. the orchestrator * bundle's `/orchestrate`, `/check-blocked`, `/scan`) plus * consumer-supplied commands. A consumer command whose name collides * with a default wins (override semantics, mirroring `subAgents` / * `skills` / `rules`). Use `excludeCommands` to drop a specific * default by name. */ readonly commands?: ReadonlyArray; /** * Names of default commands to exclude from rendering. Each entry is * the bare command name (no leading slash). Use this to drop a single * bundle-shipped default without disabling the whole bundle. * @example ['scan'] */ readonly excludeCommands?: ReadonlyArray; /** * MCP server configurations. Cross-platform — rendered to the appropriate * config file for each platform. */ readonly mcpServers?: Readonly>; /** * Whether to automatically detect and include context-aware rule bundles * based on project introspection. * @default true */ readonly autoDetectBundles?: boolean; /** * Explicit list of bundle names to include, regardless of auto-detection. * @example ['vitest', 'aws-cdk'] */ readonly includeBundles?: ReadonlyArray; /** * Bundle names to exclude even if auto-detection would include them. * @example ['jest'] */ readonly excludeBundles?: ReadonlyArray; /** * Whether to include the base rule set (project-overview, general conventions). * @default true */ readonly includeBaseRules?: boolean; /** * Names of individual rules to exclude from any source. */ readonly excludeRules?: ReadonlyArray; /** * Additional content to append to existing rules (from bundles or custom rules). * Keys are rule names, values are markdown content appended after a horizontal rule. * Use this to supplement bundle rules with project-specific additions without * replacing the entire rule. * * Unknown keys (no matching rule in the active bundle set) are silently * ignored. * * @example * ```ts * ruleExtensions: { * 'typescript-conventions': '## Additional Conventions\n\n- Use branded types for IDs', * } * ``` */ readonly ruleExtensions?: Readonly>; /** * Additional content to append to existing sub-agent prompts (from bundles * or custom subAgents). Keys are sub-agent names, values are markdown * content appended after a horizontal rule. Mirrors `ruleExtensions` but * targets the rendered sub-agent prompt across every active platform * (Claude, Cursor, etc.). * * Unknown keys (no matching sub-agent in the active bundle set) are * silently ignored. * * @example * ```ts * subAgentExtensions: { * 'requirements-writer': '## Project-Specific Gaps\n\n- Use branded types for IDs', * } * ``` */ readonly subAgentExtensions?: Readonly>; /** * Additional content to append to existing skill `SKILL.md` bodies (from * bundles or custom skills). Keys are skill names, values are markdown * content appended after a horizontal rule. Mirrors `ruleExtensions` but * targets the rendered skill instructions across every active platform * (Claude, Cursor, etc.). * * Unknown keys (no matching skill in the active bundle set) are silently * ignored. * * @example * ```ts * skillExtensions: { * 'write-requirement': '## Project-Specific Templates\n\n- Use branded types for IDs', * } * ``` */ readonly skillExtensions?: Readonly>; /** * Append additional file-pattern globs to a rule's `filePatterns` * array. Keys are rule names, values are glob strings appended in * order. Mirrors `ruleExtensions` (which appends body content) but * targets the rule's load-trigger paths instead. * * Use this when the bundled defaults don't cover paths that are * meaningful only in **your** repo — for example, paths into * configulator's own bundle source, which only exist when * configulator is a workspace package (not a node_modules dep). * * Semantics: * * - Appends to `filePatterns` for `FILE_PATTERN`-scoped rules. * - **Silently ignored** for `ALWAYS`-scoped rules (those load * everywhere already; adding paths would have no effect and * silently flipping the scope to `FILE_PATTERN` would be * surprising). To re-scope an `ALWAYS` rule to path-loaded, drop * the bundle rule via `excludeRules` and re-add a custom rule * with the desired scope via `rules`. * - Unknown keys (no matching rule in the active bundle set) are * silently ignored, mirroring `ruleExtensions`. * * @example * ```ts * agentConfig: { * additionalRulePaths: { * // codedrifters/packages contains configulator as a workspace * // package, so it can usefully add paths into the bundle source. * 'orchestrator-conventions': [ * 'packages/@codedrifters/configulator/src/agent/bundles/orchestrator.ts', * 'packages/@codedrifters/configulator/src/agent/bundles/scope-gate.ts', * ], * 'progress-file-convention': [ * 'packages/@codedrifters/configulator/src/agent/bundles/progress-files.ts', * ], * }, * } * ``` */ readonly additionalRulePaths?: Readonly>>; /** * Claude Code settings.json configuration. * Generated to .claude/settings.json (committed, team-shared). */ readonly claudeSettings?: ClaudeSettingsConfig; /** * CLAUDE.md rendering tuning knobs. * * Currently exposes a single switch (`injectBundleHooks`) that * suppresses the four "see also" subsections each phased-agent * `-workflow` rule otherwise receives. The convention * rules themselves still render — this only drops the duplicated * per-bundle pointers. Default behaviour is unchanged for * back-compat. * * @see ClaudeMdConfig */ readonly claudeMd?: ClaudeMdConfig; /** * Cursor-specific configuration. Generates .cursor/hooks.json for * lifecycle hooks and .cursorignore / .cursorindexingignore for * file visibility control. */ readonly cursorSettings?: CursorSettingsConfig; /** * Overrides for the output-path roots used by agent bundles * (requirements, BCM, profiles, meetings, research). Unset fields * fall back to the defaults captured in * `bundles/paths.ts#DEFAULT_AGENT_PATHS`. * * This option is the first of the Group A framework injection * points from epic #414. Per-project propagation of these values * into bundle rule content lands in a follow-up — setting it today * does not yet alter generated rules. */ readonly paths?: AgentPathsConfig; /** * Project-specific priority-detection rules. Each rule declares a * match predicate (labels, title regex, body regex, or explicit * issue numbers) and a target `priority:*` tier. * * When non-empty, the `base` bundle renders a "Project-specific * priority rules" subsection into the `issue-label-conventions` * rule. Precedence is **first match wins**; the bundle's default * inference heuristics act as the fallback when no rule matches. * * @see PriorityRule * @see ./bundles/priority-rules.ts#renderPriorityRulesSection */ readonly priorityRules?: ReadonlyArray; /** * Focus-scoring configuration. Declares the path to the consuming * repo's `focus.json` file, score thresholds, and the * agent-expansion rules that govern what agents may append to the * file. * * When set, the `base` bundle appends a "Focus scoring" * subsection to the `issue-label-conventions` rule that teaches * agents (a) how to read `focus.json`, (b) how focus weight * interacts with the `priority:*` taxonomy, and (c) the * agent-driven expansion contract. The actual `focus.json` file * is authored and curated in the consuming repo — configulator * ships the schema and agent instructions only. * * @see FocusConfig * @see FocusArea * @see AgentExpansionRules * @see ./bundles/focus.ts#renderFocusSection */ readonly focus?: FocusConfig; /** * Meeting-analysis injection points — typed configs the * `meeting-analysis` bundle consults when classifying, routing, and * templating meetings. Supplies the meeting-type taxonomy, the * area → doc-root routing map, and the agenda-template root used * by the (future) `agenda` bundle. * * When `meetingTypes` is non-empty, the `meeting-analysis` bundle * appends a "Recognized meeting types" subsection to the * `meeting-processing-workflow` rule. When `meetingAreas` is * non-empty, it appends an "Area → doc-root mapping" subsection. * When both are empty or unset, the generated rule content is * unchanged from the no-config baseline. * * @see MeetingsConfig * @see MeetingType * @see MeetingArea * @see ./bundles/meeting-types.ts#renderMeetingTypesSection */ readonly meetings?: MeetingsConfig; /** * Framework injection points for source-tier customization and * custom doc sections. * * - `sourceTierExamples` — domain-specific examples injected under * T1 / T2 / T3 / T4 in the base bundle's "Source Quality & * Verification" rule. * - `customDocSections` — consumer-supplied section templates that * render verbatim after an existing section heading in a target * bundle's rule content. * * Named feature toggles (`consortium-model`, `stealth-mode`, etc.) * are intentionally out of scope here — build those on top of * `customDocSections` inside the consuming repo's projen config. * * @see AgentFeaturesConfig * @see SourceTierExamples * @see CustomDocSection * @see ./bundles/features.ts */ readonly features?: AgentFeaturesConfig; /** * Funnel-tier configuration consumed by the `orchestrator` bundle. * Drives the multi-key dispatch sort (priority desc → tier asc → * issue number asc) that keeps research feeders from starving. * * When the whole config is omitted the orchestrator ships its * built-in default tier table. Supply `customTypes` to register * additional agent types or override the tier of an existing entry; * supply `tiers` to replace the default list wholesale. * * Malformed configs — tier outside 0–4, empty `type`, duplicate * entries inside a single list — fail the build at synth time via * the orchestrator bundle's tier validator. * * @see AgentTierConfig * @see AgentTierEntry * @see ./bundles/tiers.ts#resolveAgentTiers * @see ./bundles/tiers.ts#validateAgentTierConfig */ readonly tiers?: AgentTierConfig; /** * Scope-gate configuration consumed by the `orchestrator` bundle. * Rejects oversized issues at dispatch and instructs the orchestrator * to decompose them into phased sub-issues instead of claiming a * worker session for a multi-hour task. * * When the whole config is omitted, the orchestrator ships with * openhi's published defaults baked in (small: ≤3 AC + ≤2 sources; * medium: ≤6 AC + ≤5 sources; auto-file off). * * Supply `acceptanceCriteria` / `sources` to override the * per-axis thresholds, `decompositionTemplate` to customize the * proposal comment body, or `autoFile: true` to have the * orchestrator file the proposed sub-issues automatically. * * Malformed configs — negative or non-integer thresholds, or * `mediumMax <= smallMax` — fail the build at synth time via * `validateScopeGateConfig`. * * @see ScopeGateConfig * @see ScopeGateThresholds * @see ./bundles/scope-gate.ts#resolveScopeGate * @see ./bundles/scope-gate.ts#validateScopeGateConfig */ readonly scopeGate?: ScopeGateConfig; /** * PR review policy configuration consumed by the `pr-review` * bundle. Exposes two knobs: a doc-only carve-out against the * `human-required.size` rule (rule #6 in the precedence walk) — * see `PrReviewAutoMergeConfig.pathsExemptFromSize` — and the * CI-verification fallback's required-workflow list — see * `PrReviewCiVerificationConfig.requiredWorkflows`. * * When the whole config is omitted, the bundle ships the carve-out * enabled with `pathsExemptFromSize: ["docs/**"]` so a doc-only * PR that exceeds the 500-insertion threshold remains * auto-mergeable. Override the list to exempt additional doc-only * roots (e.g. `docs/research/**`) or pass `[]` to disable the * carve-out entirely. * * `ciVerification.requiredWorkflows` defaults to `[]` (treat every * observed Actions run for the head SHA as required); it only * affects the Actions-runs fallback the reviewer uses when the * primary check-runs read returns a fine-grained-PAT 403. * * Malformed configs — empty / whitespace-only entries in * `pathsExemptFromSize` or `requiredWorkflows` — fail the build at * synth time via `validatePrReviewPolicyConfig`. * * @see PrReviewPolicyConfig * @see PrReviewAutoMergeConfig * @see ./bundles/pr-review-policy.ts#resolvePrReviewPolicy * @see ./bundles/pr-review-policy.ts#validatePrReviewPolicyConfig */ readonly prReviewPolicy?: PrReviewPolicyConfig; /** * Run-ratio configuration consumed by the `orchestrator` bundle. * Drives the dispatch-to-housekeeping cadence — every `(ratio + 1)`th * run batches PR review + maintenance scan instead of dispatching * fresh issue work, preventing review and maintenance backlog drift * on always-on agent pipelines. * * When the whole config is omitted, the orchestrator ships with * openhi's published 4:1 defaults (four dispatch runs, one * housekeeping run, state file at * `.state/orchestrator-runs.json`, `opus` / `sonnet` * recommended models). * * Supply `ratio` to change the cadence, `stateFilePath` to move * the counter file, `dispatchModel` / `housekeepingModel` to * override the recommended-model labels rendered into the * orchestrator-conventions rule, or `enabled: false` to disable * run-ratio batching entirely. * * Malformed configs — non-integer or non-positive `ratio`, empty * or absolute `stateFilePath` — fail the build at synth time via * `validateRunRatioConfig`. * * @see RunRatioConfig * @see ./bundles/run-ratio.ts#resolveRunRatio * @see ./bundles/run-ratio.ts#validateRunRatioConfig */ readonly runRatio?: RunRatioConfig; /** * Scheduled-tasks configuration consumed by the `orchestrator` * bundle. Drives the per-agent worker layout at * `//SKILL.md` — one task per agent type with its own * label filter, recommended model, and opt-in enablement. * * Every default task is **disabled by default**. Consumers opt in * explicitly via `overrides..enabled = true`; the generated * repo contains no scheduled-task files until at least one task is * enabled. * * Supply `overrides` to flip `enabled`, cron, or model on a default * task; supply `tasks` to add new entries or replace defaults * wholesale; supply `root` to change the output directory; supply * `enabled: false` on the whole config to disable the subsystem * regardless of individual overrides. * * Malformed configs — unknown model, empty `taskId` / `agent` / * `typeLabel`, duplicate `taskId` in `tasks`, override referencing * an unknown default `taskId` — fail the build at synth time via * `validateScheduledTasksConfig`. * * @see ScheduledTasksConfig * @see ScheduledTaskEntry * @see ScheduledTaskOverride * @see ./bundles/scheduled-tasks.ts#resolveScheduledTasks * @see ./bundles/scheduled-tasks.ts#validateScheduledTasksConfig */ readonly scheduledTasks?: ScheduledTasksConfig; /** * Agent-driven unblocking configuration consumed by the * `orchestrator` bundle. Controls the targeted sweep agents run * after applying `status:done` — the sweep flips downstream * `Depends on: #` issues from `status:blocked` to * `status:ready` without waiting for the next orchestrator * dispatch cycle. * * When the whole config is omitted, the orchestrator ships with * the sweep **enabled** and a generic citation comment. * * Supply `enabled: false` to disable the sweep entirely, * `commentTemplate` to override the unblock citation, or * `flagPartialUnblockWithAttention: true` to have the sweep post a * progress comment on dependents whose dependency list is not yet * fully resolved. * * Malformed configs — empty / whitespace-only `commentTemplate` or * `partialUnblockCommentTemplate` — fail the build at synth time * via `validateUnblockDependentsConfig`. * * @see UnblockDependentsConfig * @see ./bundles/unblock-dependents.ts#resolveUnblockDependents * @see ./bundles/unblock-dependents.ts#validateUnblockDependentsConfig */ readonly unblockDependents?: UnblockDependentsConfig; /** * Temporal-framing convention consumed by the `base` bundle and * every analyst bundle that writes profile / research content. * Requires every time-sensitive factual claim (ownership, leadership * tenure, regulatory status, litigation, dated metrics) to carry an * inline `as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier so * refresh agents have a mechanical signal for which claims to * re-verify. * * When the whole config is omitted, the convention ships **enabled** * with default profile / research path globs and a five-category * cadence table (90d company-leadership, 30d regulatory-status, 30d * litigation, 180d ownership, 180d dated-metrics). The optional * `.claude/procedures/check-temporal-framing.sh` lint helper is * **off** by default — flip `emitChecker: true` to opt in. * * Malformed configs — `paths` containing empty entries, `cadences` * containing non-positive integers — fail the build at synth time * via `validateTemporalFramingConfig`. * * @see TemporalFramingConfig * @see ./bundles/temporal-framing.ts#resolveTemporalFraming * @see ./bundles/temporal-framing.ts#validateTemporalFramingConfig */ readonly temporalFraming?: TemporalFramingConfig; /** * Progress-file convention consumed by the `base` bundle and every * phased-agent bundle (bcm-writer, research-pipeline, etc.). Every * phased agent writes a small progress file when it claims an * issue, updates it after each non-trivial step, and deletes it in * the final commit that closes the issue. Crashed sessions are * resumed by reading the file rather than starting over. * * When the whole config is omitted, the convention ships * **enabled** with JSON-formatted files stored under * `.state/-progress.json` and a 72-hour * staleness threshold. * * Supply `enabled: false` to disable the convention entirely, * `format: "markdown"` to use the openhi-style markdown body, * `stateDir` / `filenamePattern` to move or rename the on-disk * artefact, `cleanupOnComplete: false` to retain progress files * after the issue closes, or `staleAfterHours` to override the * stale-branch threshold rendered into the documentation. * * Malformed configs — empty / whitespace-only or absolute * `stateDir`, empty / whitespace-only `filenamePattern`, * `filenamePattern` missing the `` placeholder, * unknown `format`, non-positive `staleAfterHours` — fail the * build at synth time via `validateProgressFilesConfig`. * * @see ProgressFilesConfig * @see ./bundles/progress-files.ts#resolveProgressFiles * @see ./bundles/progress-files.ts#validateProgressFilesConfig * @see ./bundles/progress-files.ts#renderProgressFilesRuleContent */ readonly progressFiles?: ProgressFilesConfig; /** * Shared-editing safety convention consumed by the `base` bundle * and every phased-agent bundle that writes rows to a shared * registry or feature-matrix file. Documents the single-entry / * deterministic-sort / verify-commit / re-sort-on-conflict protocol * agents follow when touching an index file other sessions may also * be editing concurrently. * * When the whole config is omitted, the convention ships * **enabled** with the documented default set of shared-index path * patterns, commit-path verification on, and the `rebase` conflict * strategy. * * Supply `enabled: false` to disable the convention entirely, * `sharedIndexPaths` to replace the default pattern list, * `verifyCommit: false` to drop the commit-path verification * protocol from the rendered rule, `conflictStrategy: "merge"` to * switch the conflict recipe to the merge-commit flow, or * `emitHelper: true` to ship the * `.claude/procedures/verify-index-row.sh` helper script to disk. * * Malformed configs — empty `sharedIndexPaths`, empty / * whitespace-only path entry, unknown `conflictStrategy` — fail * the build at synth time via `validateSharedEditingConfig`. * * @see SharedEditingConfig * @see ./bundles/shared-editing.ts#resolveSharedEditing * @see ./bundles/shared-editing.ts#validateSharedEditingConfig * @see ./bundles/shared-editing.ts#renderSharedEditingRuleContent */ readonly sharedEditing?: SharedEditingConfig; /** * Skill eval harness convention consumed by the `base` bundle and * every skill-owning bundle. Each skill under * `//` may ship a regression suite at * `//evals/evals.json`. Suites are * declarative prompt / expected-output fixtures parameterised by a * shared product-context fixture so the same eval shape works * across every project that depends on configulator. * * When the whole config is omitted, the convention ships * **enabled** with skills-root `.claude/skills`, product-context * fixture `docs/src/content/docs/project-context.md`, * product-context required, and the runner helper opt-in. * * Supply `enabled: false` to disable the convention entirely, * `skillsRoot` or `productContextPath` to override the default * paths, `requireProductContext: false` to downgrade missing * product-context to a warning, or `emitRunner: true` to ship the * `.claude/procedures/run-skill-evals.sh` helper to disk. * * Malformed configs — empty / whitespace-only or absolute * `skillsRoot`, empty / whitespace-only or absolute * `productContextPath` — fail the build at synth time via * `validateSkillEvalsConfig`. * * @see SkillEvalsConfig * @see ./bundles/skill-evals.ts#resolveSkillEvals * @see ./bundles/skill-evals.ts#validateSkillEvalsConfig * @see ./bundles/skill-evals.ts#renderSkillEvalsRuleContent */ readonly skillEvals?: SkillEvalsConfig; /** * Issue-templates convention consumed by the `base` bundle and * every phased-agent bundle that files downstream issues. * Documents the single hand-authored reference page that carries * one canonical `gh issue create` recipe per downstream phase * label, and enforces the reference-don't-inline rule: bundle * rules and agent prompts cite the matching section of that page * instead of duplicating a full `gh issue create` invocation. * * When the whole config is omitted, the convention ships * **enabled** with the default templates path * (`docs/src/content/docs/agents/issue-templates.md`), the default * bundle-path patterns, the hard-requirement phrasing, and both * the starter page and the lint script opt-in. * * Supply `enabled: false` to disable the convention entirely, * `templatesPath` to move the page, `bundlePathPatterns` to * replace the default pattern list, `emitStarterDoc: true` to * seed a minimal starter page on disk, `emitChecker: true` to * ship the `.claude/procedures/check-issue-templates.sh` helper, * or `requireReference: false` to soften the rule phrasing from * MUST to SHOULD. * * Malformed configs — empty / whitespace-only or absolute * `templatesPath`, empty `bundlePathPatterns`, empty / * whitespace-only pattern entry — fail the build at synth time * via `validateIssueTemplatesConfig`. * * @see IssueTemplatesConfig * @see ./bundles/issue-templates.ts#resolveIssueTemplates * @see ./bundles/issue-templates.ts#validateIssueTemplatesConfig * @see ./bundles/issue-templates.ts#renderIssueTemplatesRuleContent */ readonly issueTemplates?: IssueTemplatesConfig; /** * Per-phase-label override for the default `status:*` and * `priority:*` labels every bundle-shipped `gh issue create` * recipe uses when filing a downstream issue. Keyed by phase * label (e.g. `people:research`, `company:draft`, `req:write`). * * When the whole config is omitted, every bundle-shipped recipe * renders with `status:ready` + `priority:medium` — the historical * hardcoded defaults. * * Each entry may set `status` only, `priority` only, or both. * Missing fields cascade from the bundle defaults. Empty entries * (neither field set) are rejected at synth time as a probable * typo on the field name. * * Example: * * ```typescript * agentConfig: { * issueDefaults: { * 'people:research': { status: 'deferred', priority: 'low' }, * 'people:refresh': { status: 'deferred', priority: 'low' }, * }, * } * ``` * * Malformed configs — empty / whitespace-only phase-label key, * unrecognised `status:` / `priority:` strings, or * an empty entry that sets neither field — fail the build at * synth time via `validateIssueDefaultsConfig`. * * @see IssueDefaultsConfig * @see ./bundles/issue-defaults.ts#resolveIssueDefaults * @see ./bundles/issue-defaults.ts#validateIssueDefaultsConfig * @see ./bundles/issue-defaults.ts#labelsForPhase */ readonly issueDefaults?: IssueDefaultsConfig; /** * Upstream-configulator-docs convention consumed by the * `upstream-configulator-docs` bundle. Ships pointers to the * upstream `codedrifters/packages` repo so downstream agents know * where configulator's docs live, how to read them, and how to file * upstream issues for missing features instead of papering over * gaps with downstream rule overrides. * * When the whole config is omitted, the convention ships * **enabled** by default. * * Supply `enabled: false` to drop the bundle's rules entirely — * appropriate for non-CodeDrifters consumers that depend on * configulator but do not want to point downstream agents at the * upstream repo. * * @see UpstreamConfigulatorConfig * @see ./bundles/upstream-configulator-docs.ts */ readonly upstreamConfigulator?: UpstreamConfigulatorConfig; } /** * Generates AI coding assistant configuration files from a common schema. * * Supports Cursor and Claude Code (initial release), with Codex and Copilot * renderers planned for future issues. Rules, skills, and sub-agents are * defined once and rendered into the correct format for each target platform. * * Follows the configulator component pattern: extends Component, static .of() * factory, options interface with JSDoc. * * @example * ```ts * new AgentConfig(project, { * rules: [{ * name: 'my-rule', * description: 'Project conventions', * scope: AGENT_RULE_SCOPE.ALWAYS, * content: '# My Rule\n\nFollow these conventions...', * }], * }); * ``` */ declare class AgentConfig extends Component { /** * Find the AgentConfig component on a project. */ static of(project: Project$1): AgentConfig | undefined; /** * Returns `true` when at least one tier array on the supplied * `SourceTierExamples` is non-empty, signalling that the consuming * repo has opted into rendering the base bundle's * `source-quality-verification` rule. Returns `false` for * `undefined`, `{}`, or a fully-empty `{ t1: [], t2: [], t3: [], t4: [] }`. */ private static hasActiveTierExamples; /** * Merges default Claude permissions and hooks with bundle and * user-supplied settings. * * Permission merge order: defaults → bundle permissions → user-supplied * entries. Both `allow` and `deny` are deduped via * `Array.from(new Set(...))`; V8 `Set` iteration preserves insertion * order, so the final ordering is defaults first, then bundle, then * user, with duplicates removed (first-occurrence wins). After the * merge+dedupe, any entry whose value exactly matches a string in * `permissions.excludeDefaults` is dropped from `allow` / `deny` / `ask` * — the only supported way to remove a baseline default, since the merge * is otherwise append-only. `excludeDefaults` is a build-time directive * and is stripped from the rendered `permissions` object. `defaultMode` * is opt-in (steering decision D7): it is set only from * `userSettings.defaultMode` and left undefined otherwise, so the * renderer omits the key for un-opted-in consumers — see the inline * comment on the literal below. * * Hooks merge: consumer-supplied entries first, then default entries * (Stop, PostToolUse), deduped by `(matcher, JSON-serialized hooks)`. * Defaults are skipped entirely when `disableAllHooks: true` is set — * the `disableAllHooks` flag passes through to the rendered file so * Claude Code suppresses every project-level hook at runtime, and the * defaults are dropped at synth time so the rendered file does not * carry orphan entries that would re-fire if the operator later flipped * the flag back off. * * Env merge: defaults from `DEFAULT_CLAUDE_ENV` (e.g. * `ENABLE_TOOL_SEARCH=1`) layer first, then consumer-supplied * `claudeSettings.env` entries override on key collision. Sibling keys * from both sources land in the rendered file. */ private static mergeClaudeDefaults; /** * Merge default lifecycle hooks (Stop, PostToolUse) with consumer- * supplied entries on `claudeSettings.hooks`. Consumer entries appear * first so a downstream override that wires a faster lint/format hook * runs ahead of the defaults; defaults are appended after. * * Returns `undefined` when neither defaults nor consumer entries * remain — the renderer skips the `hooks` key entirely in that case * so opt-out repos do not ship an empty `"hooks": {}` object. * * Defaults are gated on `disableAllHooks !== true`. When the flag is * set we still pass through any consumer-supplied entries (the * runtime-level `disableAllHooks: true` is what suppresses execution), * but we never inject the bundle defaults. That keeps the disable-all * escape hatch idempotent: flipping the flag drops the bundle's * default surface area instead of leaving the entries on disk for a * future re-enable. */ private static mergeClaudeHooks; private readonly options; private cachedBundles?; private cachedPaths?; constructor(project: Project$1, options?: AgentConfigOptions); /** * Resolved agent-path roots for this project. Consumer overrides on * `AgentConfigOptions.paths` cascade into derived roots via * `resolveAgentPaths()`. Memoized so repeated reads during synthesis * do not re-run the resolver. */ private get resolvedPaths(); /** * Path-aware built-in bundles assembled with this project's * resolved paths. Path-aware bundle factories (e.g. * `buildResearchPipelineBundle`) receive the resolved struct so * their rendered rule content reflects any consumer override. * Bundles that do not read agent paths are passed through as-is * from their default const exports. * * The build policy is auto-detected from the project's `TurboRepo` * component here rather than configured, so build guidance in the * `github-workflow` and `turborepo` rules only claims an AWS * credential requirement when a remote cache actually exists. The * getter is lazy by design — `TurboRepo` must already be attached * to the project when the bundles are first read. * * Every **config-driven convention rule** is likewise resolved here * and seeded into its owning bundle, so the rule enters the rule map * already carrying the consumer's settings. Rewriting those rules * after the map was assembled — the previous approach — silently * discarded any `ruleExtensions` append or same-name `rules` * override that had already been merged in. */ private get pathAwareBundles(); /** * Resolved settings for every config-driven convention rule, derived * from this project's options. Consumed by `buildBuiltInBundles` so * the `base` and `orchestrator` bundles seed final rule content. * * `excludeBundles` feeds two of these: the orchestrator's rendered * tier table / scope-gate overrides / scheduled-tasks registry drop * rows owned by excluded bundles, and the issue-templates rule falls * back to its disabled stub once every downstream-issue-kind bundle * has been excluded. */ private get resolvedRuleConventions(); /** * Returns the bundles that are active for this project: auto-detected * bundles (when `autoDetectBundles !== false`) plus force-included * bundles, minus explicitly excluded bundles. Deduplicated by name. * * Exposed so sibling components (e.g. the sync-labels workflow) can * consume bundle-contributed configuration. */ get activeBundles(): ReadonlyArray; preSynthesize(): void; private resolvePlatforms; private resolveRules; /** * Return a bundle's rules with `filePatterns` narrowed to the projects * that actually matched the bundle's detection predicate (when the bundle * provides `findApplicableProjects`) and any project-specified * `features.customDocSections` entries injected into the matching rule * content. Rules with `ALWAYS` scope and rules on bundles that don't * implement the hook are returned with only the custom-section * transformation (if any) applied. */ private bundleRulesFor; private resolveSkills; private resolveSubAgents; private resolveProcedures; /** * Resolves the final list of slash commands by merging bundle-shipped * defaults with consumer-supplied entries. Mirrors {@link resolveSkills} * and {@link resolveSubAgents}: auto-detected bundles contribute first, * force-included bundles overlay, and consumer commands override on * name collision. Names listed in `excludeCommands` are dropped after * the merge so consumers can opt out of a single default without * disabling the whole bundle. */ private resolveCommands; /** * Resolves template variables in rule content using project metadata. * Emits synthesis warnings for rules with unresolved variables. */ private resolveTemplates; /** * Resolves template variables in skill instructions using project metadata, * then appends any matching `skillExtensions` content after a horizontal * rule. Unknown keys in `skillExtensions` are silently ignored, mirroring * the `ruleExtensions` contract. */ private resolveSkillTemplates; /** * Resolves template variables in sub-agent prompts using project metadata, * then appends any matching `subAgentExtensions` content after a horizontal * rule. Unknown keys in `subAgentExtensions` are silently ignored, * mirroring the `ruleExtensions` contract. */ private resolveSubAgentTemplates; /** * Resolves template variables in procedure content using project metadata. */ private resolveProcedureTemplates; /** * Collects Claude permission entries from all active bundles. */ private resolveBundlePermissions; } /** * Valid `status:*` values that may appear in an * `IssueDefaultsOverride.status`. The list mirrors the canonical * status taxonomy documented in the `issue-label-conventions` rule * — every value here renders as a `status:` label on the * downstream `gh issue create` invocation. * * `deferred` is included so consumers can route low-priority * downstream byproducts (e.g. `people:research` follow-ups in a * research-heavy planning repo) into a manually-promoted backlog * instead of the auto-dispatch queue. */ declare const VALID_STATUS_VALUES: readonly ["ready", "blocked", "in-progress", "ready-for-review", "needs-attention", "done", "deferred"]; type IssueDefaultsStatus = (typeof VALID_STATUS_VALUES)[number]; /** * Valid `priority:*` values that may appear in an * `IssueDefaultsOverride.priority`. Mirrors the five-level priority * taxonomy documented in the `issue-label-conventions` rule. */ declare const VALID_PRIORITY_VALUES: readonly ["critical", "high", "medium", "low", "trivial"]; type IssueDefaultsPriority = (typeof VALID_PRIORITY_VALUES)[number]; /** * Bundle-shipped defaults used when no `issueDefaults[]` * override is configured. `ready` / `medium` matches the historical * hardcoded values every bundle carried before this knob existed. */ declare const DEFAULT_ISSUE_STATUS: IssueDefaultsStatus; declare const DEFAULT_ISSUE_PRIORITY: IssueDefaultsPriority; /** * Fully-resolved per-phase-label entry. Every field is filled in so * downstream callers can render `status:` / `priority:` * label lines without re-checking for `undefined`. */ interface ResolvedIssueDefaultsEntry { readonly status: IssueDefaultsStatus; readonly priority: IssueDefaultsPriority; } /** * Fully-resolved issue-defaults configuration. The `overrides` map * is keyed by phase label (e.g. `people:research`, `company:draft`, * `req:write`) — not by `type:*` — so consumers can discriminate per * phase. The `defaults` field carries the bundle-shipped fallback * values (`ready` / `medium`) callers fall back to when no override * is configured for a given phase. */ interface ResolvedIssueDefaults { readonly defaults: ResolvedIssueDefaultsEntry; readonly overrides: Readonly>; } /** * Default-everything resolved instance. Used by bundle factories * when the consumer supplies no override at all (the common case). */ declare const DEFAULT_RESOLVED_ISSUE_DEFAULTS: ResolvedIssueDefaults; /** * Resolve a (possibly absent) `IssueDefaultsConfig` into a canonical * `ResolvedIssueDefaults` with every override entry filled in. * * Each entry inside the consumer-supplied map may set `status`, * `priority`, or both. Missing fields cascade from the bundle * defaults (`ready` / `medium`) so a partially-specified entry * still resolves to a complete pair. * * Malformed configs throw a descriptive `Error`: * * - Empty / whitespace-only phase-label key. * - `status` set to a value outside `VALID_STATUS_VALUES`. * - `priority` set to a value outside `VALID_PRIORITY_VALUES`. * - Empty entry (neither `status` nor `priority` supplied) — the * override would be a no-op and probably indicates a typo on the * field name. */ declare function resolveIssueDefaults(config?: IssueDefaultsConfig): ResolvedIssueDefaults; /** * Synth-time validation hook. Throws a descriptive `Error` when * the supplied `IssueDefaultsConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * override fails the build instead of silently shipping unrecognised * label values into bundle prompts. Returns the resolved config so * callers can write `const id = validateIssueDefaultsConfig(config)` * in one line. */ declare function validateIssueDefaultsConfig(config?: IssueDefaultsConfig): ResolvedIssueDefaults; /** * Look up the effective `status` / `priority` pair for a phase label. * Returns the override entry when one is configured for the given * phase, otherwise the bundle defaults (`ready` / `medium`). * * The single canonical helper bundle prompt templates call when they * need to render the labels for a downstream filing — keeps every * filing site in sync with the same resolution rule. */ declare function labelsForPhase(resolved: ResolvedIssueDefaults, phaseLabel: string): ResolvedIssueDefaultsEntry; /** * The GitHub **issue type** vocabulary this convention assigns. * * An issue type is a first-class GitHub field (Epic / Feature / Bug / * Task) and is a completely different axis from the `type:*` **label** * taxonomy: * * - `type:` / `type:` — a *label*. Routing * and dedup signal. Owned by {@link BUNDLE_OWNERSHIP} and set with * `gh issue create --label`. * - GitHub issue type — a *field*. Human triage, Epic-relationship * tracking, and reporting signal. `gh issue create` cannot set it, so * it is applied immediately after creation via the * `updateIssueIssueType` GraphQL mutation. * * Conflating the two is the single most common mistake in this area, so * both this module and the prose it renders keep them explicitly apart. */ declare const GITHUB_ISSUE_TYPES: readonly ["Epic", "Feature", "Bug", "Task"]; type GithubIssueType = (typeof GITHUB_ISSUE_TYPES)[number]; /** * The issue type every title prefix maps to unless it is one of the * three explicit exceptions in {@link NON_DEFAULT_TITLE_PREFIX_TYPES}. * * Every bundle-phase prefix (`company:`, `req:`, `bcm:`, `software:`, * …) lands here, which is why agent-enqueued downstream issues are * almost always `Task` — the phased pipelines file work items, not * features or bug reports. */ declare const DEFAULT_GITHUB_ISSUE_TYPE: GithubIssueType; /** * Canonical issue-title-prefix → GitHub issue type map. * * Derived from {@link CONVENTIONAL_COMMIT_TYPE_LABELS} — the shared * conventional-commit vocabulary exported alongside the bundle * ownership registry — so the prefix list can never drift from the * label list the create-issue workflow stamps. Every conventional-commit * prefix defaults to `Task`; the three exceptions are overlaid on top. * * Prefixes carry their trailing colon (`"feat:"`) to match the way the * title conventions write them. */ declare const GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX: Readonly>; /** * Resolve an issue **title** to the GitHub issue type it must carry. * * Anything that is not one of the four recognised non-default prefixes * — including every bundle-phase prefix (`company:research: …`) and a * title with no prefix at all — resolves to * {@link DEFAULT_GITHUB_ISSUE_TYPE}. */ declare function githubIssueTypeForTitle(title: string): GithubIssueType; /** * Path to the `set-issue-type.sh` helper the `github-workflow` bundle * ships. Referenced (never assumed present) by the rendered prose — see * {@link renderGithubIssueTypeSectionLines} for the fallback that keeps * the recipe working for consumers who exclude that bundle. */ declare const SET_ISSUE_TYPE_HELPER_PATH = ".claude/procedures/set-issue-type.sh"; /** * The two-step `updateIssueIssueType` GraphQL flow, rendered as shell. * * `set-issue-type.sh` wraps exactly this flow, but that helper ships * **only** via the `github-workflow` bundle. Any recipe outside that * bundle that cited the helper unconditionally would be broken for a * consumer running `excludeBundles: ["github-workflow"]`, so the * fallback is documented inline in an always-on base rule and every * per-filing-site step points at it. */ declare function renderSetIssueTypeFallbackLines(): Array; /** * Render the **GitHub Issue Type** section of the always-on * `issue-conventions` rule. * * The section is the single canonical answer to "how does an agent set * an issue's type?", and it is rendered into an `ALWAYS`-scoped base * rule precisely so every downstream filing site can cite it in one * line regardless of which optional bundles the consumer enabled. * * It documents both paths deliberately: * * 1. The `set-issue-type.sh` one-liner, when `github-workflow` is * active. * 2. The inline GraphQL fallback, when it is not. */ declare function renderGithubIssueTypeSectionLines(): Array; /** * Render the title-prefix → issue-type mapping as a markdown bullet * list, for recipes that present it inline rather than as a table (the * interactive create-issue workflow's step 3). * * Grouping matches the table in {@link renderGithubIssueTypeSectionLines}: * one bullet per non-default prefix, then a single bullet collapsing * every prefix that maps to {@link DEFAULT_GITHUB_ISSUE_TYPE}. */ declare function renderTitlePrefixTypeBullets(indent?: string): Array; /** String form of {@link renderGithubIssueTypeSectionLines}. */ declare function renderGithubIssueTypeSection(): string; /** Options for {@link renderIssueTypeAssignmentStep}. */ interface IssueTypeAssignmentStepOptions { /** * Leading whitespace prepended to every rendered line so the step * nests correctly under the numbered/bulleted filing recipe it * follows. Defaults to the three spaces a top-level numbered list * item continues with. */ readonly indent?: string; /** * The GitHub issue type the filed issue must carry. Defaults to * {@link DEFAULT_GITHUB_ISSUE_TYPE}, which is correct for every * bundle-phase-prefixed downstream issue. */ readonly issueType?: GithubIssueType; /** * Render the step as a markdown list item (`- …` with hanging * continuation lines) instead of a paragraph. Used at the handful of * filing recipes that specify the issue with a bullet list rather * than numbered prose. */ readonly bullet?: boolean; } /** * Render the compact "now set the issue type" step appended to every * bundle-shipped downstream filing recipe. * * Kept deliberately short: it appears at ~45 filing sites across the * phased-pipeline bundles, so it names the concrete type, calls out that * the `type:*` label is a different field, gives the command, and * delegates the fallback to the always-on `issue-conventions` rule * rather than re-inlining the GraphQL flow at every site. */ declare function renderIssueTypeAssignmentStep(options?: IssueTypeAssignmentStepOptions): Array; /** * Render the phase-wide variant of {@link renderIssueTypeAssignmentStep} * for a workflow phase that files several kinds of issue across several * steps, where repeating the per-recipe step at each one would bloat the * prompt without adding information. */ declare function renderIssueTypeAssignmentBlanket(indent?: string, issueType?: GithubIssueType): Array; /** * Default master switch for the issue-templates convention. When no * config is supplied the convention ships **enabled** so every * configulator-consuming repo carries the canonical `gh issue create` * template reference in its rendered `CLAUDE.md`. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_ENABLED = true; /** * Default repo-relative path for the consolidated issue-templates * documentation page. Matches the singleton `/docs` site layout every * configulator-managed repo ships: a single Starlight docs site at * `/docs` with agent reference pages under * `docs/src/content/docs/agents/`. * * The file is never generated by configulator unless `emitStarterDoc` * is set — the canonical list of templates is repo-specific and grows * whenever a new phase label is minted, so consumers author and evolve * the page themselves. The starter doc is opt-in. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md"; /** * Default list of glob patterns that identify "bundle files" — the * source files that compose agent prompts and skill instructions. * These are the locations the optional lint walks when checking that * `gh issue create` snippets are **referenced** rather than inlined. * * The defaults cover the locations bundle-like content lives in a * generic configulator-consuming repo: * * - `.claude/agents/**.md` / `.claude/skills/**` — agent and skill * prompts in consuming repos that don't re-export configulator * bundles. * * Repos that **also** host configulator's own bundle source as a * workspace package (only `codedrifters/packages` itself) should * append `packages/@codedrifters/configulator/src/agent/bundles/**.ts` * via `IssueTemplatesConfig.bundlePathPatterns` to lint those bundle * sources too. The default omits that pattern because it is dead * weight (matches nothing) in any other consumer. * * Consumers can replace the list outright via `bundlePathPatterns` * when their agent sources live elsewhere. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS: ReadonlyArray; /** * Default for whether the convention emits the * `.claude/procedures/check-issue-templates.sh` lint to disk. The * script greps the provided files (stdin or positional args) for * inline `gh issue create` invocations and fails non-zero when any * are found outside a fenced example block that cites the canonical * templates doc. * * Disabled by default because many consumers prefer to enforce the * rule via review discipline and the rendered guidance alone; the * script is opt-in for repos that want a hard CI gate or pre-commit * hook. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false; /** * Default for whether the convention emits the issue-templates * scaffold to disk. The scaffold is two files: * * 1. A **write-once** starter page at `` (projen * `SampleFile`) carrying the expected structure — the "How to use" * preamble and one example `## Template: ` section — * which the consumer then fleshes out by hand. * 2. An **always-regenerated** companion page at * {@link issueTemplatesGeneratedPath}, carrying one label-correct * recipe stub per phase label the consumer's active bundles emit. * * The split exists because a `SampleFile` never reaches a consumer * whose page already exists: repos that adopted the convention on an * older configulator would otherwise be frozen on whatever skeleton * shipped that day. Hand-authored bodies stay in the write-once page; * the generated label sets regenerate on every `projen` run so a new * phase label reaches every consumer on their next upgrade. * * Disabled by default because the hand-authored page conflicts with * the ad-hoc notes most repos already maintain when they adopt the * convention. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false; /** * Filename suffix appended to the templates page's stem to derive the * always-regenerated companion page. Chosen so the companion can never * collide with a hand-authored router layout that splits recipes into * `/.md` sibling pages. */ declare const ISSUE_TEMPLATES_GENERATED_SUFFIX = "-generated"; /** * Repo-relative path of the always-regenerated companion page for a * given `templatesPath`: the stem gains * {@link ISSUE_TEMPLATES_GENERATED_SUFFIX} and keeps its extension * (`…/issue-templates.md` → `…/issue-templates-generated.md`). */ declare function issueTemplatesGeneratedPath(templatesPath: string): string; /** * Glob matching the sibling child pages of a router-style templates * layout (`…/issue-templates.md` → `…/issue-templates/*.md`). The * label-consistency lint walks these so a repo that split its recipes * across child pages is checked against the same map. */ declare function issueTemplatesChildGlob(templatesPath: string): string; /** * Default for whether the rendered rule body asserts that every * `gh issue create` recipe in a bundle or agent prompt **MUST** cite * the canonical templates doc rather than inline a full template. * * Defaults to `true` — the whole point of consolidation is that * templates live in one place, so the MUST phrasing is the correct * default. Consumers that treat consolidation as aspirational can * soften the phrasing by setting this to `false`. * * @see IssueTemplatesConfig */ declare const DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true; /** * Fully-resolved issue-templates settings. Every field is defaulted * so downstream renderers can reason about a single canonical shape. */ interface ResolvedIssueTemplates { readonly enabled: boolean; readonly templatesPath: string; readonly bundlePathPatterns: ReadonlyArray; readonly emitChecker: boolean; readonly emitStarterDoc: boolean; readonly requireReference: boolean; } /** * Resolve a (possibly absent) `IssueTemplatesConfig` into a canonical * `ResolvedIssueTemplates` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs — empty / whitespace-only or absolute * `templatesPath`, empty `bundlePathPatterns`, empty / * whitespace-only path entry — throw a descriptive `Error`. */ declare function resolveIssueTemplates(config?: IssueTemplatesConfig): ResolvedIssueTemplates; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `IssueTemplatesConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * convention fails the build instead of silently shipping broken * guidance. Returns the resolved config unchanged so callers can * write `const it = validateIssueTemplatesConfig(config)` in one line. * * Malformed cases rejected here: * * - `templatesPath` empty, whitespace-only, or absolute. * - `bundlePathPatterns` not an array, empty, or contains an empty / * whitespace-only entry. */ declare function validateIssueTemplatesConfig(config?: IssueTemplatesConfig): ResolvedIssueTemplates; /** * Render the full body for the `issue-templates-convention` rule * shipped by the `base` bundle. The rule documents: * * - Why the convention exists (drift between duplicated * `gh issue create` snippets across bundles). * - The on-disk contract — a single hand-authored page at * `` with one `## Template: ` section * per downstream issue kind. * - The **reference-don't-inline** rule, phrased as a hard * requirement or a strong recommendation per `requireReference`. * - The set of paths the rule applies to. * - The optional lint script (cross-referenced only when emitted). * * When the convention is disabled, the rule renders a short stub. */ declare function renderIssueTemplatesRuleContent(it: ResolvedIssueTemplates, hasDownstreamBundles?: boolean): string; /** * Render the short issue-templates hook section injected into a * phased-agent bundle's workflow rule. The section cites the full * contract documented in the base bundle's * `issue-templates-convention` rule so individual bundles stay DRY. * * When the convention is disabled, the function returns an empty * string so callers can no-op their append path. */ declare function renderIssueTemplatesBundleHook(it: ResolvedIssueTemplates, bundleLabel: string): string; /** * Render the write-once starter issue-templates page — the frontmatter, * the "How to use" preamble, a pointer at the always-regenerated * label-set companion, and a single example template section. Exported * so `AgentConfig` can emit it to disk when the consumer opts in via * `emitStarterDoc: true`. * * The starter stays deliberately sparse on **bodies**: it documents the * expected structure without committing the consumer to a particular * body shape. The correct-by-construction **label sets** live in the * companion page this one links to, which regenerates on every synth — * so a write-once starter can never freeze a consumer on a stale label * taxonomy. */ declare function renderIssueTemplatesStarterPage(it: ResolvedIssueTemplates): string; /******************************************************************************* * * Generated recipe stubs * ******************************************************************************/ /** * One correct-by-construction recipe stub: a phase label plus every * label the recipe must carry, all derived rather than hand-copied. */ interface IssueTemplateRecipeStub { /** The phase label the recipe files (e.g. `people:research`). */ readonly phaseLabel: string; /** The `type:` label the phase-label invariant requires. */ readonly typeLabel: string; /** Bundle that contributes the phase label to `.github/labels.yml`. */ readonly bundleName: string; /** The label's registry description, used as the section blurb. */ readonly description: string; /** Effective `status:*` value for this phase. */ readonly status: IssueDefaultsStatus; /** Effective `priority:*` value for this phase. */ readonly priority: IssueDefaultsPriority; /** GitHub issue type the filed issue must be assigned. */ readonly issueType: GithubIssueType; } /** * Derive one recipe stub per phase label the supplied bundles * contribute to `.github/labels.yml`. * * A contributed label counts as a phase label exactly when * `typeLabelForPhaseLabel` resolves it — i.e. when the canonical * bundle-ownership map claims it. That is the *same* map that drives * the label registry and the orchestrator's phase-label invariant, so * a generated stub can never pair a phase label with the wrong * `type:` label. Consumer-specific labels no bundle owns are * skipped rather than guessed at. * * Results are deduplicated by phase label (co-owned `req:*` labels are * contributed by more than one requirements bundle) and sorted so the * rendered page is stable across synth runs. */ declare function collectIssueTemplateRecipeStubs(bundles: ReadonlyArray, issueDefaults?: ResolvedIssueDefaults): ReadonlyArray; /** * Render the always-regenerated companion page that carries one * label-correct `## Template: ` stub per phase label the * consumer's active bundles emit. * * Only the **label set** and the issue-type assignment are generated — * title and body stay angle-bracket placeholders, so the page is a * correct-by-construction starting point rather than a second source of * truth for recipe bodies. Consumers move a stub into their * hand-authored templates page and flesh out its body there; the * label-consistency lint then holds both copies to the same pairing. */ declare function renderIssueTemplatesGeneratedPage(it: ResolvedIssueTemplates, stubs: ReadonlyArray): string; /** * Render the `.claude/procedures/check-issue-templates.sh` helper * script. Exported so `AgentConfig` can register it as an * `AgentProcedure` when the consumer opts in via `emitChecker: true`. * * The script accepts the list of changed files as either: * * 1. Positional arguments (one file per arg). * 2. Newline-separated entries on stdin (when no args supplied) — * pipe `git diff --name-only` directly into it. * * It fails non-zero when any changed file matches a bundle-path * pattern and contains a multi-line `gh issue create ... --title` * invocation that isn't in the configured allow list (the templates * page itself and the `create-issue-workflow` rule source). */ declare function renderIssueTemplatesCheckerScript(it: ResolvedIssueTemplates): string; /** * Render the `.claude/procedures/check-issue-template-labels.sh` * companion lint. Exported so `AgentConfig` can emit it alongside the * reference-don't-inline lint when the consumer opts in via * `emitChecker: true`. * * Where `check-issue-templates.sh` polices *where* recipes live, this * one polices *what they say*. For every * `## Template: ` section on the templates page, its * router-style child pages, and the generated companion, it asserts: * * 1. The recipe passes `--label ` — the heading and the * command agree. * 2. It carries exactly one `type:*` label, and that label is the * `type:` the phase-label invariant requires. * 3. It carries a GitHub issue-type assignment step (the * `set-issue-type.sh` helper or the `updateIssueIssueType` GraphQL * flow it wraps) — an issue filed without one stays untyped forever. * * Sections whose heading matches no bundle-owned phase label are * skipped, not failed: unrecognised `foo:bar` labels are * consumer-specific and deliberately not policed, exactly as the * orchestrator's invariant sweep treats them. * * The phase-label → type-label resolver is rendered from the same * `PHASE_LABEL_TYPE_MAP` that drives the label registry and the * orchestrator sweep, so the lint can never enforce a stale pairing. */ declare function renderIssueTemplateLabelsCheckerScript(it: ResolvedIssueTemplates): string; /** * Fully-resolved requirement category subdirectory names, relative to * the requirements root. Every property is required. */ interface ResolvedRequirementCategoryDirs { readonly business: string; readonly functional: string; readonly nonFunctional: string; readonly technical: string; readonly architecturalDecisions: string; readonly security: string; readonly data: string; readonly integration: string; readonly operational: string; readonly ux: string; readonly multiTenancy: string; } /** * Fully-resolved agent output-path roots. Every property is required. * * This is the shape that bundle code consumes at module-eval time via * `DEFAULT_AGENT_PATHS`, and the shape that `resolveAgentPaths()` * returns when consumers supply a partial `AgentPathsConfig` override. */ interface ResolvedAgentPaths { readonly docsRoot: string; readonly researchRoot: string; readonly profilesRoot: string; readonly meetingsRoot: string; readonly requirementsRoot: string; readonly researchRequirementsRoot: string; readonly bcmRoot: string; readonly peopleRoot: string; readonly companiesRoot: string; readonly softwareRoot: string; readonly industriesRoot: string; readonly requirementCategoryDirs: ResolvedRequirementCategoryDirs; } /** * Canonical default subdirectory name for each requirement category. * These mirror the hardcoded `functional/`, `non-functional/`, … dirs * that the requirements bundles emitted before category dirs became * configurable, so the generated requirements snapshot is unchanged * unless a consumer overrides an entry. */ declare const DEFAULT_REQUIREMENT_CATEGORY_DIRS: ResolvedRequirementCategoryDirs; /** * Canonical default values for every agent path. These mirror the * hardcoded paths that bundles used before `AgentPathsConfig` existed, * so `DEFAULT_AGENT_PATHS.*` can be substituted into bundle rule * content at module-eval time without changing the generated * `.claude/rules/*.md` snapshot. * * Consumers override the defaults by passing an `AgentPathsConfig` * through `AgentConfigOptions.paths` and resolving it with * `resolveAgentPaths()`. Every path-aware bundle threads the resolved * struct through its rule / skill / sub-agent content, so an override * propagates into the rendered output. */ declare const DEFAULT_AGENT_PATHS: ResolvedAgentPaths; /** * Resolve a partial `AgentPathsConfig` into a fully-populated * `ResolvedAgentPaths`. Unset fields cascade from their parent root: * * - `profilesRoot`, `meetingsRoot`, `requirementsRoot`, and `bcmRoot` * derive from `docsRoot` when not explicitly set. * - `researchRequirementsRoot` derives from `researchRoot` when not * explicitly set. * - `peopleRoot`, `companiesRoot`, `softwareRoot`, and `industriesRoot` * derive from the resolved `profilesRoot` when not explicitly set, * so that overriding `docsRoot` alone (or overriding `profilesRoot` * alone) propagates correctly through every dependent root. */ declare function resolveAgentPaths(paths?: AgentPathsConfig): ResolvedAgentPaths; /** * Default master switch for the progress-file convention. When no * config is supplied, the convention ships **enabled** so every phased * agent writes a progress file on claim and reads it on resume. * * @see ProgressFilesConfig */ declare const DEFAULT_PROGRESS_FILES_ENABLED = true; /** * Default on-disk root for progress files, relative to the repo root. * Every progress file resolves to * `/` where `` is produced from * `filenamePattern` at runtime. * * Lives at the top-level `.state/` directory so the path stays * harness-neutral — any agent runtime (Claude Code, Cursor, a * bespoke worker) can read and write the same progress files * without having to scope under a harness-specific tree. * * @see ProgressFilesConfig */ declare const DEFAULT_PROGRESS_FILES_STATE_DIR = ".state"; /** * Default filename pattern for a progress file. The `` * placeholder is substituted at runtime with the numeric id of the * issue the agent is working on (e.g. `479-progress.json`). * * The placeholder uses the angle-bracketed uppercase-snake form — not * `{{curly-brace}}` form — because `AgentConfig`'s template resolver * claims the curly-brace namespace at rule generation time. * * @see ProgressFilesConfig */ declare const DEFAULT_PROGRESS_FILES_FILENAME_PATTERN = "-progress.json"; /** * Default serialization format for a progress file body. JSON is the * default because it is trivially machine-parseable (e.g. for scripted * resume logic) while still remaining human-readable when opened. * Consumers that prefer the openhi-style markdown body can override. * * @see ProgressFilesConfig */ declare const DEFAULT_PROGRESS_FILES_FORMAT: "json" | "markdown"; /** * Default stale-threshold (hours) for branches carrying a progress * file. When the orchestrator's stale-branch decision tree finds a * progress file older than this many hours **and** no matching open * PR, it treats the branch as abandoned and resets the issue to * `status:ready`. Mirrors the 72-hour in-progress threshold used by * the orchestrator bundle's triage walk. * * @see ProgressFilesConfig */ declare const DEFAULT_PROGRESS_FILES_STALE_AFTER_HOURS = 72; /** * Allowed values for `ProgressFilesConfig.format`. Exported so * consumers can reference the canonical set without hard-coding * literals. */ declare const PROGRESS_FILES_FORMAT_VALUES: readonly ["json", "markdown"]; /** * Fully-resolved progress-file settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. */ interface ResolvedProgressFiles { readonly enabled: boolean; readonly stateDir: string; readonly filenamePattern: string; readonly format: "json" | "markdown"; readonly cleanupOnComplete: boolean; readonly staleAfterHours: number; } /** * Resolve a (possibly absent) `ProgressFilesConfig` into a canonical * `ResolvedProgressFiles` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs — empty / whitespace-only `stateDir`, absolute * `stateDir`, empty / whitespace-only `filenamePattern`, `filenamePattern` * missing the `` placeholder, unknown `format` value, * non-positive `staleAfterHours` — throw a descriptive `Error`. */ declare function resolveProgressFiles(config?: ProgressFilesConfig): ResolvedProgressFiles; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `ProgressFilesConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * convention fails the build instead of silently shipping broken * resume semantics. Returns the resolved config unchanged so callers * can write `const pf = validateProgressFilesConfig(config)` in * one line. * * Malformed cases rejected here: * * - `stateDir` empty, whitespace-only, or absolute. * - `filenamePattern` empty, whitespace-only, or missing the * `` placeholder. * - `format` not one of `"json"` / `"markdown"`. * - `staleAfterHours` non-integer, zero, or negative. */ declare function validateProgressFilesConfig(config?: ProgressFilesConfig): ResolvedProgressFiles; /** * Resolve the runtime filename for a progress file given an issue * number and a resolved config. `` placeholders in the * pattern are substituted; the returned value is **just** the filename * (no directory prefix). * * Exported so consumer-side scripts (or the `partial-resume-protocol` * rule renderer) can compute the on-disk path deterministically. */ declare function renderProgressFileName(pf: ResolvedProgressFiles, issueNumber: number | string): string; /** * Resolve the runtime path (directory + filename) for a progress file * given an issue number and a resolved config. */ declare function renderProgressFilePath(pf: ResolvedProgressFiles, issueNumber: number | string): string; /** * Render the full body for the `progress-file-convention` rule shipped * by the `base` bundle. The rule documents: * * - The progress-file schema and on-disk path contract. * - The partial-resume protocol (read-before-write + acceptance * criteria replay). * - The stale-branch decision tree (clone-level recovery) that every * worker runs at session start. * - The `[BLOCKED]` structured comment format used when an agent * cannot proceed. * * When the convention is disabled, the rule renders a short stub that * tells agents the project does not enforce progress files and they * must pick up work from scratch on every session. */ declare function renderProgressFilesRuleContent(pf: ResolvedProgressFiles): string; /** * Render the short progress-file hook section injected into a * phased-agent bundle's workflow rule (bcm-writer, research-pipeline, * etc.). The section cites the full contract documented in the base * bundle's `progress-file-convention` rule so individual bundles stay * DRY. * * When the convention is disabled, the function returns an empty * string so callers can no-op their append path. */ declare function renderProgressFilesBundleHook(pf: ResolvedProgressFiles, bundleLabel: string): string; /** * Default master switch for the shared-editing convention. When no * config is supplied, the convention ships **enabled** so every agent * that edits an index file follows the single-entry / verify / * re-sort protocol. * * @see SharedEditingConfig */ declare const DEFAULT_SHARED_EDITING_ENABLED = true; /** * Default list of path patterns considered "shared index files". The * patterns are plain glob strings rendered verbatim into the rule body * — agents match against them when deciding whether the shared-editing * contract applies to the file they are about to edit. * * The defaults cover the registry / index files every configulator * consumer ships by convention: * * - A monorepo-wide docs site at `/docs` with one or more `index.md` / * `README.md` registry tables. * - Category landing pages under `docs/src/content/docs/**` that list * every profile, requirement, or capability in their category. * - Feature matrices produced by the `software-profile` bundle. * * Consumers can replace the list outright via `sharedIndexPaths` or * append project-specific registries. * * @see SharedEditingConfig */ declare const DEFAULT_SHARED_INDEX_PATHS: ReadonlyArray; /** * Default conflict-resolution strategy rendered into the rule body. * `rebase` matches the `git pull --rebase` workflow every * configulator-managed repo already uses for feature branches; the * alternative (`merge`) is documented for projects that keep a * merge-commit-only history. * * @see SharedEditingConfig */ declare const DEFAULT_SHARED_EDITING_CONFLICT_STRATEGY: "rebase" | "merge"; /** * Default for whether the convention renders the commit-path * verification protocol (read-back + single-row assertion). The * verification step is cheap, catches staging / path bugs that would * otherwise land on the branch, and is the core safety net the openhi * reference promotes — so it ships **on** by default. * * @see SharedEditingConfig */ declare const DEFAULT_SHARED_EDITING_VERIFY_COMMIT = true; /** * Default for whether the convention emits the * `.claude/procedures/verify-index-row.sh` helper to disk. The helper * is opt-in because many consumers prefer to do the verification * inline via the documented `git show HEAD:` recipe rather than * shell out to a dedicated script. Consumers that want the script * available to sub-agents enable the emission explicitly. * * @see SharedEditingConfig */ declare const DEFAULT_SHARED_EDITING_EMIT_HELPER = false; /** * Allowed values for `SharedEditingConfig.conflictStrategy`. Exported * so consumers can reference the canonical set without hard-coding * literals. */ declare const SHARED_EDITING_CONFLICT_STRATEGY_VALUES: readonly ["rebase", "merge"]; /** * Fully-resolved shared-editing settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. */ interface ResolvedSharedEditing { readonly enabled: boolean; readonly sharedIndexPaths: ReadonlyArray; readonly verifyCommit: boolean; readonly conflictStrategy: "rebase" | "merge"; readonly emitHelper: boolean; } /** * Resolve a (possibly absent) `SharedEditingConfig` into a canonical * `ResolvedSharedEditing` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs — empty / whitespace-only `sharedIndexPaths` * entry, unknown `conflictStrategy` — throw a descriptive `Error`. */ declare function resolveSharedEditing(config?: SharedEditingConfig): ResolvedSharedEditing; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `SharedEditingConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * convention fails the build instead of silently shipping broken * shared-editing guidance. Returns the resolved config unchanged so * callers can write `const se = validateSharedEditingConfig(config)` * in one line. * * Malformed cases rejected here: * * - `sharedIndexPaths` contains an empty / whitespace-only entry, or * the array is supplied but empty. * - `conflictStrategy` is not one of `"rebase"` / `"merge"`. */ declare function validateSharedEditingConfig(config?: SharedEditingConfig): ResolvedSharedEditing; /** * Render the full body for the `shared-editing-safety` rule shipped * by the `base` bundle. The rule documents: * * - The catalog of shared index files the contract applies to. * - The pre-edit read-latest protocol (pull + re-read before editing). * - The single-entry, deterministic-sort row-insert rule. * - The commit-path verification step (read-back + count assertion). * - The merge-conflict resolution recipe (rebase, re-sort, re-verify). * * When the convention is disabled, the rule renders a short stub that * tells agents the project does not enforce the convention and that * concurrent edits to shared index files may require manual conflict * resolution. */ declare function renderSharedEditingRuleContent(se: ResolvedSharedEditing): string; /** * Render the short shared-editing hook section injected into a * phased-agent bundle's workflow rule (company-profile, * people-profile, software-profile, etc.). The section cites the * full contract documented in the base bundle's * `shared-editing-safety` rule so individual bundles stay DRY. * * When the convention is disabled, the function returns an empty * string so callers can no-op their append path. */ declare function renderSharedEditingBundleHook(se: ResolvedSharedEditing, bundleLabel: string): string; /** * Render the `.claude/procedures/verify-index-row.sh` helper script. * Exported so `AgentConfig` can register it as an `AgentProcedure` * when the consumer opts in via `emitHelper: true`. * * The script takes two positional arguments: * * 1. `` — repo-relative path to the shared index file. * 2. `` — substring unique to the new row. * * It exits non-zero on any of the following: * * - Wrong argument count. * - Index file is not present in `HEAD` (i.e. not staged). * - The unique-marker substring appears zero times (row missing) * or more than once (duplicate row from a mis-merged conflict). */ declare function renderSharedEditingHelperScript(_se: ResolvedSharedEditing): string; /** * Default master switch for the skill-eval harness convention. When no * config is supplied, the convention ships **enabled** so every skill * that ships an `evals/evals.json` file has a documented schema, * runner entry-point, and product-context injection contract. * * @see SkillEvalsConfig */ declare const DEFAULT_SKILL_EVALS_ENABLED = true; /** * Default root directory (relative to the repo root) that holds every * skill's SKILL.md. The harness contract says that any skill SKILL.md * under this root may ship an `evals/evals.json` file alongside it — * the runner discovers eval suites by walking * `//evals/evals.json`. * * Defaults to `.claude/skills`, which matches the location every * configulator-managed project ships skills to on disk. * * @see SkillEvalsConfig */ declare const DEFAULT_SKILL_EVALS_SKILLS_ROOT = ".claude/skills"; /** * Default path to the product-context fixture consumed by every eval * suite. Configulator ships with a `docs/src/content/docs/project-context.md` * file that every agent already loads at session start; the eval harness * re-uses that file so eval prompts are parameterised by the consuming * project's domain vocabulary, in-scope capabilities, and stakeholders * without the evals needing per-project forks. * * @see SkillEvalsConfig */ declare const DEFAULT_PRODUCT_CONTEXT_PATH = "docs/src/content/docs/project-context.md"; /** * Default policy for whether the harness should **require** a * product-context file to be present before running the suite. * * `true` (default) — the runner fails fast when the file is missing, * because an eval that silently runs without its product-context * fixture is a false-positive waiting to happen. * * `false` — the runner emits a warning to stderr but still runs the * suite. Useful for bootstrapping a new consuming repo that has not * yet authored its `project-context.md`. * * @see SkillEvalsConfig */ declare const DEFAULT_REQUIRE_PRODUCT_CONTEXT = true; /** * Default for whether the convention emits the * `.claude/procedures/run-skill-evals.sh` helper to disk. The helper * is opt-in because many consumers run evals from CI or ad-hoc from * their own scripts rather than through the bundled harness; consumers * who want a ready-to-invoke runner flip this to `true`. * * @see SkillEvalsConfig */ declare const DEFAULT_SKILL_EVALS_EMIT_RUNNER = false; /** * Fully-resolved skill-evals settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. */ interface ResolvedSkillEvals { readonly enabled: boolean; readonly skillsRoot: string; readonly productContextPath: string; readonly requireProductContext: boolean; readonly emitRunner: boolean; } /** * Resolve a (possibly absent) `SkillEvalsConfig` into a canonical * `ResolvedSkillEvals` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs — empty / whitespace-only or absolute `skillsRoot`, * empty / whitespace-only or absolute `productContextPath` — throw a * descriptive `Error`. */ declare function resolveSkillEvals(config?: SkillEvalsConfig): ResolvedSkillEvals; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `SkillEvalsConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * convention fails the build instead of silently shipping a broken * eval harness. Returns the resolved config unchanged so callers can * write `const se = validateSkillEvalsConfig(config)` in one line. * * Malformed cases rejected here: * * - `skillsRoot` empty, whitespace-only, or absolute. * - `productContextPath` empty, whitespace-only, or absolute. */ declare function validateSkillEvalsConfig(config?: SkillEvalsConfig): ResolvedSkillEvals; /** * Render the full body for the `skill-evals` rule shipped by the * `base` bundle. The rule documents: * * - The on-disk contract (where `evals/evals.json` lives). * - The JSON schema every eval file follows. * - The product-context injection protocol (how evals reference and * interpolate the repo's `project-context.md` without forking). * - The runner entry-point (`run-skill-evals.sh` when opted in, or * the inline `jq`-driven recipe when not). * * When the convention is disabled, the rule renders a short stub that * tells agents the project does not ship skill evals and that skill * changes ride on review alone. */ declare function renderSkillEvalsRuleContent(se: ResolvedSkillEvals): string; /** * Render the short skill-evals hook section injected into a skill's * owning bundle rule (requirements-writer, bcm-writer, etc.). The * section cites the full contract documented in the base bundle's * `skill-evals` rule so individual bundles stay DRY. * * When the convention is disabled, the function returns an empty * string so callers can no-op their append path. */ declare function renderSkillEvalsBundleHook(se: ResolvedSkillEvals, skillLabel: string): string; /** * Render the `.claude/procedures/run-skill-evals.sh` helper script. * Exported so `AgentConfig` can register it as an `AgentProcedure` * when the consumer opts in via `emitRunner: true`. * * The script takes zero or one positional arguments: * * 1. `[]` — optional, restricts the run to one skill. * * It exits non-zero on any of the following: * * - `jq` is not available on `PATH`. * - A discovered `evals.json` is malformed or missing required fields. * - `skill_name` inside the file does not match the parent directory. * - The product-context fixture is missing and `requireProductContext` * is `true` in the resolved config. */ declare function renderSkillEvalsRunnerScript(se: ResolvedSkillEvals): string; /** * Default master switch for the temporal-framing convention. When no * config is supplied the convention ships **enabled** so every * configulator-consuming repo's analyst agents apply the * "as of [date]" qualifier rule. * * @see TemporalFramingConfig */ declare const DEFAULT_TEMPORAL_FRAMING_ENABLED = true; /** * Default path globs the rule applies to — every Markdown file under * the profile / research subtrees of a repo's Starlight docs site. * Consumers may override the list when their content layout differs. * * Out-of-scope locations (meeting notes, requirements, the * project-context page) are excluded by design: their own dating * conventions (file-name date prefix, version frontmatter, living * snapshot under direct human review) already anchor the temporal * meaning of their content. * * @see TemporalFramingConfig */ declare const DEFAULT_TEMPORAL_FRAMING_PATHS: ReadonlyArray; /** * The five canonical time-sensitive claim categories surfaced by the * May 2026 sampled drift audit. The category names are shipped as the * key set for `TemporalFramingConfig.cadences` so consumers can dial * the per-category refresh cadence without inventing their own * category names. */ declare const TEMPORAL_FRAMING_CATEGORY_VALUES: readonly ["ownership", "company-leadership", "regulatory-status", "litigation", "dated-metrics"]; type TemporalFramingCategory = (typeof TEMPORAL_FRAMING_CATEGORY_VALUES)[number]; /** * Default per-category refresh cadences (in days). Fast-decay claims * (regulatory status, litigation) carry a 30-day cadence because a * single press release can invalidate them between scheduled refresh * passes. Slow-decay claims (ownership, dated metrics from press * releases or filings) carry a 180-day cadence — material changes * still happen but rarely outpace a half-yearly refresh. Leadership * tenure sits in the middle at 90 days. * * Consumers may override any subset of categories via * `TemporalFramingConfig.cadences`; unspecified entries fall through * to these defaults. */ declare const DEFAULT_TEMPORAL_FRAMING_CADENCES: { readonly [K in TemporalFramingCategory]: number; }; /** * Default for whether the convention emits the * `.claude/procedures/check-temporal-framing.sh` lint script to disk. * Disabled by default — consumers opt in when they want a hard * pre-commit or CI gate. The rule body itself ships unconditionally * regardless of the lint script. * * @see TemporalFramingConfig */ declare const DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER = false; /** * Fully-resolved temporal-framing settings. Every field is defaulted * so downstream renderers can reason about a single canonical shape. */ interface ResolvedTemporalFraming { readonly enabled: boolean; readonly paths: ReadonlyArray; readonly cadences: { readonly [K in TemporalFramingCategory]: number; }; readonly emitChecker: boolean; } /** * Resolve a (possibly absent) `TemporalFramingConfig` into a canonical * `ResolvedTemporalFraming` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs throw a descriptive `Error`: * * - `paths` containing empty / whitespace-only entries. * - `cadences` containing non-integer or non-positive values. */ declare function resolveTemporalFraming(config?: TemporalFramingConfig): ResolvedTemporalFraming; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `TemporalFramingConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * convention fails the build instead of silently shipping broken * paths or cadences. Returns the resolved config unchanged so callers * can write `const tf = validateTemporalFramingConfig(config)` in * one line. * * Malformed cases rejected here: * * - `paths` containing empty or whitespace-only entries. * - `cadences` containing non-integer or non-positive values. */ declare function validateTemporalFramingConfig(config?: TemporalFramingConfig): ResolvedTemporalFraming; /** * Render the body for the `temporal-framing-convention` rule shipped * by the `base` bundle. The rule documents: * * - The "as of [date]" qualifier requirement on time-sensitive claims. * - The five canonical time-sensitive claim categories. * - Refresh-agent behaviour (grep for `as of `, re-verify against the * category-specific cadence). * - The scope of applicability (profile / research sections only). * * When the convention is disabled, the rule renders a short stub that * tells agents the project does not enforce explicit temporal * qualifiers and that staleness is caught by review alone. */ declare function renderTemporalFramingRuleContent(tf: ResolvedTemporalFraming): string; /** * Render the `.claude/procedures/check-temporal-framing.sh` helper * script. Exported so `AgentConfig` can register it when the consumer * opts in via `emitChecker: true`. * * The script accepts the list of changed files as either: * * 1. Positional arguments (one file per arg). * 2. Newline-separated entries on stdin (when no args supplied) — * pipe `git diff --name-only` directly into it. * * It fails non-zero when any changed file matches a configured path * pattern and contains time-sensitive framing (present-tense forms of * the canonical category triggers) but lacks an `as of ` qualifier * anywhere in the file. The lint is intentionally coarse — file-level * not line-level — so the cost of running it on every PR stays low. */ declare function renderTemporalFramingCheckerScript(tf: ResolvedTemporalFraming): string; /** * Fully-resolved settings for the five config-driven convention rules * the base bundle owns. Every field is already resolved, so * `buildBaseBundle` can seed each rule's final content up front rather * than shipping default content that a later pass has to rewrite. * * Seeding before the rule map exists is what lets `ruleExtensions` * appends and consumer-supplied same-name `rules` entries compose with * a consumer's convention overrides instead of being clobbered by * them. This mirrors the `pr-review-policy` rule, which has always * resolved its policy inside `buildPrReviewBundle`. */ interface ResolvedBaseConventions { readonly progressFiles: ResolvedProgressFiles; readonly sharedEditing: ResolvedSharedEditing; readonly temporalFraming: ResolvedTemporalFraming; readonly skillEvals: ResolvedSkillEvals; readonly issueTemplates: ResolvedIssueTemplates; /** * Whether any bundle contributing a downstream issue kind survived * `excludeBundles`. When false the issue-templates rule renders its * disabled stub — the convention has nothing left to enforce. */ readonly hasDownstreamIssueKindBundles: boolean; } /** * The convention settings the base bundle ships when the consumer * supplies no override. Exported so callers can spread a partial * override over the documented defaults. */ declare const DEFAULT_BASE_CONVENTIONS: ResolvedBaseConventions; /** * Base bundle — always included unless `includeBaseRules: false`. * Contains project-overview, interaction-style, and general-conventions rules. */ declare function buildBaseBundle(paths?: ResolvedAgentPaths, conventions?: ResolvedBaseConventions): AgentRuleBundle; /** * Default-paths instance of the base bundle, preserved for backward * compatibility with consumers that import the const directly. The * factory above is the canonical entry point when a consumer supplies * `AgentConfigOptions.paths`. */ declare const baseBundle: AgentRuleBundle; /** * Fully-resolved build policy for the consuming project. * * The generated agent guidance around `pnpm build:all` used to assert * unconditionally that the command "requires the user to be * authenticated to AWS on the prod account used for Turborepo remote * caching (`readonlyaccess-prod-525259625215-us-east-1` profile)". * Both halves of that sentence were wrong for most consumers: * * 1. The AWS-auth requirement only exists when a Turborepo **remote * cache** is configured. Consumers running a local cache only * (`turbo.json` with just a `cacheDir`) need no credentials at * all, and agents that believed otherwise aborted mid-flow — * three lost-work incidents in `codedrifters/openhi-planning`. * 2. The profile name was this repository's own profile, baked * verbatim into every consumer's generated text. * * This struct carries the two facts the rule renderers need, derived * from the project's actual {@link TurboRepo} configuration, so the * guidance is true for whichever consumer it renders into. * * @see resolveBuildPolicy */ interface ResolvedBuildPolicy { /** * Whether a Turborepo **remote** cache is configured on the project. * * `false` means either there is no {@link TurboRepo} component at * all, or it was constructed without `remoteCacheOptions` — in both * cases `pnpm build:all` needs no AWS credentials and the generated * guidance must not claim otherwise. */ readonly remoteCacheEnabled: boolean; /** * Local AWS profile name used to fetch the remote-cache endpoint and * token, taken from `remoteCacheOptions.profileName`. * * `undefined` whenever {@link remoteCacheEnabled} is `false`. Never * hard-code a profile name in rule content — read it from here so * each consumer's generated text names its own profile. */ readonly awsProfileName?: string; } /** * Build policy for a project with no Turborepo remote cache — the * zero-config default. Rule renderers that receive this omit the * AWS-authentication guidance entirely rather than asserting a * credential requirement that does not exist. */ declare const DEFAULT_BUILD_POLICY: ResolvedBuildPolicy; /** * Derives the {@link ResolvedBuildPolicy} for a project by inspecting * its {@link TurboRepo} component. * * Auto-detection, not opt-in: `remoteCacheOptions` being `undefined` * *is* the "remote cache disabled" signal — `TurboRepo.renderRunArgs` * already branches on exactly the same condition when it decides * whether to emit `--api` / `--token` / `--team` flags. Consumers get * accurate guidance with no extra configuration. * * Call this lazily (at synthesis time), not from a constructor: the * `TurboRepo` component must already be attached to the project for * detection to succeed. */ declare function resolveBuildPolicy(project: Project): ResolvedBuildPolicy; /** * Default dispatch-to-housekeeping ratio — openhi's `DISPATCHER.md` * ships a 4:1 ratio: four consecutive dispatch runs, then one * housekeeping run, then the counter wraps. The ratio value stored * here is the dispatch-run count per housekeeping run; with * `ratio = 4`, runs 1–4 dispatch and run 5 housekeeps. The cycle * length is therefore `ratio + 1`. * * @see RunRatioConfig */ declare const DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO = 4; /** * Default on-disk path for the orchestrator run-counter state file, * relative to the repo root. The file is tiny JSON * (`{ "run_counter": }`) and is gitignored in most consumer repos * because it's local-only — each operator's orchestrator session * maintains its own counter. * * @see RunRatioConfig */ declare const DEFAULT_STATE_FILE_PATH = ".state/orchestrator-runs.json"; /** * Default recommended model label for dispatch runs. Rendered into * the orchestrator-conventions rule so agents and humans can read the * model pairing at a glance; the string is purely informational and * does not cause configulator to set `AGENT_MODEL` on the sub-agent. * * @see RunRatioConfig */ declare const DEFAULT_DISPATCH_MODEL = "opus"; /** * Default recommended model label for housekeeping runs. See * `DEFAULT_DISPATCH_MODEL` for the rendering contract. Housekeeping * runs are mechanical (batch PR review + maintenance scan), so a * cheaper model like Sonnet is the documented recommendation. * * @see RunRatioConfig */ declare const DEFAULT_HOUSEKEEPING_MODEL = "sonnet"; /** * Fully-resolved run-ratio settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. */ interface ResolvedRunRatio { readonly enabled: boolean; readonly ratio: number; readonly stateFilePath: string; readonly dispatchModel: string; readonly housekeepingModel: string; } /** * Resolve a (possibly absent) `RunRatioConfig` into a canonical * `ResolvedRunRatio` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs (non-integer or non-positive ratio, empty or * whitespace-only state file path) throw a descriptive `Error` — * callers should not need to guard against it at runtime. */ declare function resolveRunRatio(config?: RunRatioConfig): ResolvedRunRatio; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `RunRatioConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * ratio fails the build instead of silently shipping broken * housekeeping cadence. Returns the resolved ratio unchanged so * callers can write `const rr = validateRunRatioConfig(config)` in * one line. * * Malformed cases rejected here: * * - `ratio` non-integer, zero, or negative. * - `stateFilePath` empty / whitespace-only, or an absolute path * (the state file must live inside the repo). */ declare function validateRunRatioConfig(config?: RunRatioConfig): ResolvedRunRatio; /** * Compute the run type (`"dispatch"` or `"housekeeping"`) for a * given run counter value and resolved ratio. Used by the TypeScript * side (tests, downstream consumers that want to reason about the * cadence without shelling out). The shell helper produced by * `renderRunRatioShellHelpers` implements the same logic. * * Every `(ratio + 1)`th run is a housekeeping run; all others * dispatch. For `ratio = 4`, runs 1–4 dispatch and run 5 * housekeeps; run 6 is again dispatch; run 10 housekeeps. */ declare function classifyRun(runCounter: number, ratio: ResolvedRunRatio): "dispatch" | "housekeeping"; /** * Render the markdown subsection appended to the * `orchestrator-conventions` rule. Always returns a non-empty string * so the orchestrator rule documents the cadence even when the * consumer relies on the defaults. */ declare function renderRunRatioSection(ratio: ResolvedRunRatio): string; /** * Render a shell-script snippet embedded in `check-blocked.sh`. The * snippet declares a `run_counter_tick()` function that: * * 1. Reads the state file (creating it with `run_counter: 1` if * missing or unparseable). * 2. Increments the counter. * 3. Writes the new counter back atomically (temp file + `mv`). * 4. Echoes the post-increment counter and the classified run type * (`dispatch` or `housekeeping`) in the canonical * `run= type=` format. * * Returns the body of a shell function block (including the * `run_counter_tick()` wrapper) so the surrounding script can splice * it inline at the exact indent level it wants. */ declare function renderRunRatioShellHelpers(ratio: ResolvedRunRatio): string; /** * Recommended model labels surfaced on the scheduled-task SKILL.md * frontmatter. The labels are **informational** — configulator does not * pin a model per task (the Claude Code scheduled-task runtime does not * support per-task model pinning yet). Operators choose the model at * invocation time; splitting workers by type still gives independent * cadence and clean opt-in/opt-out control. * * @see ScheduledTasksConfig */ declare const SCHEDULED_TASK_MODEL_VALUES: readonly ["opus", "sonnet", "haiku"]; type ScheduledTaskModel = (typeof SCHEDULED_TASK_MODEL_VALUES)[number]; /** * Valid `kind` values for a scheduled-task entry. See * `ScheduledTaskEntry.kind` for semantics. */ declare const SCHEDULED_TASK_KIND_VALUES: readonly ["issue-worker", "pipeline"]; type ScheduledTaskKind = (typeof SCHEDULED_TASK_KIND_VALUES)[number]; /** * Default root directory (relative to the repo root) for scheduled-task * files. Mirrors the vortex layout — one directory per task at * `.claude/scheduled-tasks//SKILL.md`. * * @see ScheduledTasksConfig */ declare const DEFAULT_SCHEDULED_TASKS_ROOT = ".claude/scheduled-tasks"; /** * Off-peak cron sample surfaced in the rendered documentation. Every * 20 minutes during off-peak hours (before 08:00 and after 14:00 * local). Informational only — tasks ship disabled by default with * no cron, so the consumer must opt in and set a cron explicitly. * * @see ScheduledTasksConfig */ declare const DEFAULT_OFF_PEAK_CRON_EXAMPLE = "3,23,43 0-7,14-23 * * *"; /** * A single fully-resolved scheduled-task entry. Returned by * `resolveScheduledTasks()`; the rendered documentation section and * the emitted `.claude/scheduled-tasks//SKILL.md` files are * derived from this list. */ interface ResolvedScheduledTask { /** * Unique task directory name. Emitted under * `//SKILL.md`. Derived from the sub-agent name by * default (e.g. `company-profile-analyst` → `worker-company-profile`). */ readonly taskId: string; /** * Target sub-agent name (e.g. `company-profile-analyst`). The task's * rendered prompt points operators at `.claude/agents/.md`. */ readonly agent: string; /** * Human-readable agent label for rendered tables and frontmatter * descriptions (e.g. `"Company Profile"`). */ readonly agentLabel: string; /** * Primary GitHub `type:*` label (without the `type:` prefix). When * `typeLabels` is unset, this is the sole type filter; when * `typeLabels` is set, it is the first element. */ readonly typeLabel: string; /** * Exact `type:*` label values (without the `type:` prefix) the * worker picks up. When set (`length >= 1`), represents a * multi-type filter (e.g. routing-bucket `worker-issue`). When * unset, the `typeLabel` single-value filter applies. */ readonly typeLabels?: ReadonlyArray; /** * Optional phase-label prefix (e.g. `company:`). When present and * `phaseLabels` is unset, the task's SKILL.md instructs the worker * to filter on both `type:` **and** any `*` * label. Ignored when `phaseLabels` is set. */ readonly phasePrefix?: string; /** * Exact phase-label values the worker filters on (e.g. * `["req:write"]`). When set (`length >= 1`), takes precedence over * `phasePrefix`. When unset, the `phasePrefix` prefix-match * applies. */ readonly phaseLabels?: ReadonlyArray; /** * Recommended model tier. Surfaced on the task's SKILL.md * frontmatter and in the rendered documentation table. */ readonly recommendedModel: ScheduledTaskModel; /** Whether the task is emitted to disk. Default: `false`. */ readonly enabled: boolean; /** * Optional cron expression. When `null` the task is manual-only * (runs only when an operator invokes it explicitly). */ readonly cron: string | null; /** * Short one-line description for the rendered table and SKILL.md * frontmatter. */ readonly description: string; /** * Discriminator controlling how the SKILL.md body and the registered- * tasks table cell are rendered. `"issue-worker"` (default) emits the * standard delegate-to-issue-worker prompt; `"pipeline"` emits a * pipeline-manager prompt that points the operator at the target * sub-agent's full workflow and renders `_(none — pipeline manager)_` * for the type-label cell. */ readonly kind: ScheduledTaskKind; } /** * Canonical default registry of scheduled-task entries. One entry per * agent bundle that ships with configulator. Every entry is * **disabled by default** — consumers must explicitly opt in via * `ScheduledTasksConfig.overrides[taskId].enabled = true`. * * The registry mirrors the funnel-tier table in `tiers.ts` so the * dispatch ordering, scheduled-task filter, and orchestrator queue * scan all agree on the type-label taxonomy. */ declare const DEFAULT_SCHEDULED_TASK_ENTRIES: ReadonlyArray; /** * Fully-resolved scheduled-tasks settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. */ interface ResolvedScheduledTasks { readonly enabled: boolean; readonly root: string; readonly tasks: ReadonlyArray; } /** * Resolve a (possibly absent) `ScheduledTasksConfig` into a canonical * `ResolvedScheduledTasks` with every field filled in. Unset fields * cascade from their documented defaults. * * The resolver merges three sources in this order: * * 1. `DEFAULT_SCHEDULED_TASK_ENTRIES` — the built-in per-agent registry. * 2. `config.overrides` — per-task consumer overrides keyed by `taskId`. * 3. `config.tasks` — fully consumer-authored entries. Entries whose * `taskId` matches a default entry **replace** it; entries with a new * `taskId` are appended. * * Malformed configs (unknown model, empty taskId/agent/typeLabel, * duplicate taskIds in a single supplied list) throw a descriptive * `Error` — callers should not need to guard against it at runtime. */ declare function resolveScheduledTasks(config?: ScheduledTasksConfig, excludeBundles?: ReadonlyArray): ResolvedScheduledTasks; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `ScheduledTasksConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * scheduled-tasks table fails the build instead of silently shipping * broken worker prompts. Returns the resolved config unchanged so * callers can write `const st = validateScheduledTasksConfig(config)` in * one line. * * Malformed cases rejected here: * * - `root` empty, whitespace-only, or absolute. * - `overrides[taskId]` referencing an unknown `taskId`. * - Consumer-supplied `tasks` entry with unknown model, empty * `taskId` / `agent` / `typeLabel`, or cron string that is not a * plain string. * - Duplicate `taskId` values within the supplied `tasks` list. */ declare function validateScheduledTasksConfig(config?: ScheduledTasksConfig, excludeBundles?: ReadonlyArray): ResolvedScheduledTasks; /** * Render the markdown subsection appended to the * `orchestrator-conventions` rule. Always returns a non-empty string — * the default registry is non-empty so the section documents the * per-agent scheduled-task layout even when every task is disabled * (the default). */ declare function renderScheduledTasksSection(resolved: ResolvedScheduledTasks): string; /** * Render the body of a single scheduled-task `SKILL.md` file. The * frontmatter carries the task name, description, and recommended * model; the body teaches the worker which label filter to apply and * which agent file to route issues to. * * The output is the full file contents — including the leading * frontmatter delimiter — so callers can hand it straight to a * `TextFile`. */ declare function renderScheduledTaskSkillFile(task: ResolvedScheduledTask): string; /** * Classified scope size for a single issue. Drives the dispatch * decision in the orchestrator — `small` / `medium` are dispatched, * `large` is rejected and decomposed. */ declare const SCOPE_CLASS_VALUES: readonly ["small", "medium", "large"]; type ScopeClass = (typeof SCOPE_CLASS_VALUES)[number]; /** * Default acceptance-criteria thresholds — openhi's published * heuristic. An issue with at most 3 acceptance-criteria checkboxes * is `small`; at most 6 is `medium`; more than 6 is `large`. */ declare const DEFAULT_AC_THRESHOLDS: ScopeGateThresholds; /** * Default sources thresholds — openhi's published heuristic. An * issue with at most 2 listed sources is `small`; at most 5 is * `medium`; more than 5 is `large`. */ declare const DEFAULT_SOURCES_THRESHOLDS: ScopeGateThresholds; /** * Bundle overrides shipped out of the box for content-spec workflows * — issues whose AC list is the per-section content checklist for * one cohesive deliverable (not a phase-completion checklist that * can be decomposed). The global default cap (`mediumMax: 6`) is * calibrated for phased-bundle issues and systematically over-flags * single-document writes. * * The shipped overrides: * * - `req:write` — formal requirement documents (ADR / TR / OPS / * SEC / NFR / UX / MT) routinely carry 12–20 ACs covering * per-section content invariants (definition, alternatives, * risks, traceability, revision history, open items). Cap: * `mediumMax: 20` on the AC axis; sources unchanged. * - `bcm:scaffold` — multi-section BCM documents compress sub- * addendum requirements into coarse ACs but still land above * the global cap. Cap: `mediumMax: 12` on the AC axis; sources * unchanged. * - `research:verify` — the `research-analyst` slice phase emits * a fixed-shape verify template. A 32-issue cohort observed in * `codedrifters/openhi-planning` (2026-05-04) all landed at * identical `ac=9 / src=2` dimensions, so the template's shape * itself is what trips the global cap. Cap: `mediumMax: 12` on * the AC axis (max observed 9 + ~3 headroom); sources unchanged. * - `research:scope` — the `research-analyst` Phase-1 scope phase * decomposes a research question and writes a single scope file * whose ACs are the per-section content checklist for that one * deliverable (question decomposition, per-slice briefs, and the * acceptance criteria for the final deliverable), not a phase- * completion checklist that can be decomposed. Sibling of * `research:verify` in the same pipeline; observed ac=7 * (`codedrifters/openhi-planning`#6034). Cap: `mediumMax: 12` on * the AC axis (matching the `research:verify` sibling); sources * unchanged. * - `software:profile` — software-product profile pages enumerate * per-section content invariants (overview, vendor, integrations, * pricing, security posture, …). Max observed 11 ACs in the same * openhi-planning sample; cap: `mediumMax: 14` on the AC axis * (max observed + 3 headroom); sources unchanged. * - `software:research` — the `software-profile` Phase-1 research * phase gathers public sources and writes a single bounded * research-notes file whose ACs are the per-section content * checklist for that one deliverable, not a decomposable plan. * Sibling of `software:profile` in the same pipeline; observed * ac=8 (`codedrifters/openhi-planning`#6259). Cap: `mediumMax: 14` * on the AC axis (matching the `software:profile` sibling); * sources unchanged. * - `regulatory:research` — single regulation pages carry per- * section ACs covering jurisdiction, scope, obligations, * penalties, and effective dates. Single-document conformance * scans run ac=11; cap: `mediumMax: 12` on the AC axis; * sources unchanged. * - `regulatory:impact` — the `regulatory-research` Phase-3 impact * phase is synthesis-only (no web searches): it appends a single * `## Impact` section to the regulation page — product-impact * assessment, capability-gap enumeration, and obligation hand-offs * — whose ACs are the per-section content checklist for that one * document section. Sibling of `regulatory:research`; observed * ac=9 (`codedrifters/openhi-planning`#3505). Cap: `mediumMax: 10` * on the AC axis (observed max 9 + headroom; the tighter cap * versus the research sibling's 12 reflects the narrower * single-section deliverable); sources unchanged. * - `standards:research` — single standard-version research notes * enumerate one cohesive deliverable across per-section ACs * (candidate encodings, reconciliation, storage/query analysis, * citations, recommendation). Sibling of `regulatory:research` * with the same content-spec note shape. Max observed 7 ACs * (`codedrifters/openhi-planning`#5994); cap: `mediumMax: 10` on * the AC axis (max observed + 3 headroom, matching the * `regulatory:research` sibling); sources unchanged. * - `software:map` — capability-mapping output is a single cohesive * matrix file whose ACs enumerate per-row/per-column requirements * and whose Sources block typically cross-references the entire * BCM tree slice. Max observed `ac=9 / src=13`; cap: * `acceptanceCriteria.mediumMax: 12` (max + 3) and * `sources.mediumMax: 15` (max + 2 — sources headroom is tighter * because BCM cross-refs naturally cap out near the slice size). * - `bcm:connect` — connect-phase outputs cross-link a capability * to upstream value streams, downstream profiles, and adjacent * capabilities. Max observed `ac=9 / src=6`; cap: * `acceptanceCriteria.mediumMax: 12` (max + 3) and * `sources.mediumMax: 8` (max + 2). * - `software:matrix` — physician-RCM-style feature matrices * (per-row/per-column requirements for one cohesive matrix * file). The shape mirrors `software:map`: single-cohesive- * file matrix output whose ACs enumerate per-row/per-column * requirements rather than phase milestones. Mirrors the * `software:map` thresholds verbatim (closest-peer rationale, * surfaced by openhi-planning#3223). * - `people:research` / `company:research` — profile research * phases whose AC list is the per-section content checklist for * one cohesive research-notes file (sources by tier, conflict * resolution, dated claims). Not a phase-completion checklist * that can be decomposed. Cap: `mediumMax: 12` on the AC axis; * sources unchanged. * - `people:draft` / `company:draft` — profile draft phases that * read the research notes and write one cohesive structured * profile document. The per-section AC checklist (overview, * leadership, history, sources, …) is the content spec for that * single document, not a decomposable plan. Cap: `mediumMax: 12` * on the AC axis; sources unchanged. * - `business-models:canvas` / `business-models:complete` — the * business-models pipeline authors one cohesive Business Model * Canvas document per segment across two content-spec phases that * edit the SAME `business-model.md` file. The canvas phase writes * the nine Osterwalder blocks + segment index + downstream handoff; * the complete phase appends BIZBOK value streams, size / geographic * / regulatory variations, and the value-stream-to-capability * linking table. Both AC lists are the per-section content checklist * for that single indivisible document, not a phase-completion * checklist that can be decomposed — a single canvas cannot be split * into sequential sub-issues without every phase editing the same * file. A representative canvas issue observed at ac=7 * (`codedrifters/openhi-planning`#13613). Cap: `mediumMax: 12` on the * AC axis for both (matching the `bcm:*` / `company:research` * document-authoring norm; the complete phase is a multi-section * append, so it takes the same cap as its canvas sibling rather than * a tighter single-section cap); sources unchanged. * - `req:draft-trace` — the `requirements-analyst` draft-trace * phase emits a single proposal-with-traceability document whose * ACs enumerate per-section content invariants. Sibling of * `req:write`; mirrors its `mediumMax: 20` AC cap (observed max * ac=9, headroom intentional). Sources unchanged. * - `meeting:notes` — Phase-2 meeting-notes output is a single * cohesive notes document whose ACs enumerate per-section * content invariants. Cap: `mediumMax: 9` on the AC axis; * sources unchanged. * - `meeting:draft` / `meeting:link` — Phase-3/Phase-4 meeting * outputs carry a "file N action-item issues" criterion plus * per-section content ACs, and cross-reference every session * input. Cap: `acceptanceCriteria.mediumMax: 15` and * `sources.mediumMax: 10` on both axes. * - `type:docs` — single-document doc writes whose AC list is the * per-section content outline for one cohesive page, not a * decomposable plan. This is the first override keyed on a * `type:*` label rather than a phase label; it matches because * the scope gate resolves overrides against every label on the * issue (see `resolveOverrideForLabels`). Cap: `mediumMax: 12` * on the AC axis; sources unchanged (global band). * - `bcm:context` — the `bcm-writer` context phase authors a * single BCM context document whose AC list is a per-section * content checklist (definitions, boundaries, sub-capabilities, * value-stream links), not a decomposable plan. Sibling of * `bcm:scaffold` / `bcm:connect`; mirrors their `mediumMax: 12` * AC cap. AC axis only; sources fall through to the global band. * - `req:scan` — the `requirements-analyst` Phase-1 dedup scan * produces one cohesive scan report, but a dedup pass inherently * reads MANY existing requirements, ADRs, and open issues, so it * trips BOTH the AC ceiling AND the sources ceiling (evidence * issue scored `ac=7 / src=9`). Both axes are raised to * `mediumMax: 12` — this is why `req:scan` carries a `sources` * override the AC-only siblings do not. * - `bcm:outline` — the `bcm-writer` Phase-1 outline phase authors a * single capability outline document whose AC list is the content * checklist for that one indivisible deliverable (identify the L1 * parent, draft the L2 definition, document sub-facets, draw the * capability boundary, write the outline file, update the * capability map), not a decomposable plan — the criteria cannot be * split into independently-mergeable sub-issues because every one * of them edits the same outline file. Sibling of `bcm:scaffold` / * `bcm:context` / `bcm:connect`; mirrors their `mediumMax: 12` AC * cap. Observed ac=7 (`codedrifters/openhi-planning`#11216). AC * axis only; sources fall through to the global band. * * Consumer overrides deep-merge with these defaults with * consumer-wins-per-key (see `resolveScopeGate` for the merge * semantics). */ declare const DEFAULT_BUNDLE_OVERRIDES: { readonly [phaseLabel: string]: ResolvedScopeGateBundleOverride; }; /** * Resolved per-phase-label override. At least one of the two axis * fields is populated — empty resolved overrides are dropped * rather than retained, so callers can assume any entry in the * resolved override map carries actionable thresholds. * * Each axis here is **fully resolved** (`smallMax` + `mediumMax`). * The override resolver fills in the missing axis from the global * resolved thresholds at the time `resolveScopeGate` runs. */ interface ResolvedScopeGateBundleOverride { readonly acceptanceCriteria?: ScopeGateThresholds; readonly sources?: ScopeGateThresholds; } /** * Default decomposition-proposal comment body. The orchestrator * substitutes the following angle-bracketed uppercase-snake * placeholders at comment-composition time: * * - `` — observed acceptance-criteria count. * - `` — observed sources count. * - `` — the `acceptanceCriteria.mediumMax` threshold. * - `` — the `sources.mediumMax` threshold. * * The placeholder syntax deliberately avoids `{{curly-brace}}` form — * `AgentConfig`'s template resolver claims that namespace at rule * generation time, so a `{{acCount}}` placeholder would be rewritten * to `` before the agent ever saw it. * * This template is deliberately generic so every consuming repo * can adopt it without customization — project-specific phrasing * belongs in `ScopeGateConfig.decompositionTemplate`. */ declare const DEFAULT_DECOMPOSITION_TEMPLATE: string; /** * Fully-resolved scope-gate settings. Every field is defaulted so * downstream renderers can reason about a single canonical shape. * * `bundleOverrides` is the deep-merged result of the shipped * `DEFAULT_BUNDLE_OVERRIDES` and any consumer-supplied * `ScopeGateConfig.bundleOverrides`. Consumer entries replace * shipped defaults per-key; entries set to `undefined` opt out of * the shipped default for that key. The resolved map only contains * entries whose merged value carries at least one axis — empty * overrides are dropped. */ interface ResolvedScopeGate { readonly enabled: boolean; readonly acceptanceCriteria: ScopeGateThresholds; readonly sources: ScopeGateThresholds; readonly autoFile: boolean; readonly decompositionTemplate: string; readonly bundleOverrides: { readonly [phaseLabel: string]: ResolvedScopeGateBundleOverride; }; } /** * Resolve a (possibly absent) `ScopeGateConfig` into a canonical * `ResolvedScopeGate` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs (negative or non-integer thresholds, inverted * ranges) throw a descriptive `Error` — callers should not need to * guard against it at runtime. */ declare function resolveScopeGate(config?: ScopeGateConfig): ResolvedScopeGate; /** * Effective thresholds applied to a single issue. Produced by * `resolveOverrideForLabels` so the classifier (and the shell helper) * can use a single shape regardless of whether an override matched. */ interface EffectiveScopeThresholds { readonly acceptanceCriteria: ScopeGateThresholds; readonly sources: ScopeGateThresholds; readonly matchedLabel?: string; } /** * Pick the effective thresholds for an issue carrying `labels`. * Walks the resolved bundle-override map, finds every label whose * exact name appears as a key, and selects the **first match in * alphabetical order on the label name**. The override's * `acceptanceCriteria` and `sources` axes are independent — the * unspecified axis falls through to the global resolved * thresholds. * * When no label matches, returns the global thresholds with no * `matchedLabel`. */ declare function resolveOverrideForLabels(gate: ResolvedScopeGate, labels: ReadonlyArray): EffectiveScopeThresholds; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `ScopeGateConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * gate fails the build instead of silently shipping broken dispatch * thresholds. Returns the resolved gate unchanged so callers can * write `const gate = validateScopeGateConfig(config)` in one line. * * Malformed cases rejected here: * * - Threshold values that are negative or non-integer. * - Threshold ranges where `mediumMax <= smallMax` (the gate would * never classify an issue as `medium`). */ declare function validateScopeGateConfig(config?: ScopeGateConfig): ResolvedScopeGate; /** * Classify a raw issue body against a resolved scope gate. Returns * the scope class and the observed counts so the orchestrator can * embed them in the decomposition-proposal comment. * * The parser is deliberately tolerant: * * - Acceptance-criteria checkboxes — lines matching `- [ ]` or * `- [x]` (case-insensitive) under a `## Acceptance Criteria` * heading (or any heading whose normalized text equals * `acceptance criteria`). Stops at the next heading. * - Sources — bullet list items (`- ` or `* `) under any of * `## Inputs`, `## References`, `## Sources` (case-insensitive). * Counts from every matching section are summed, so an issue * that uses both `## Inputs` and `## References` has its sources * counted across both. * * An issue with **no** `## Acceptance Criteria` section produces an * `acCount` of 0 and is classified `small` on the AC axis. An issue * body that's pure prose with no recognizable sections classifies * `small` — the gate never rejects an issue it can't read. */ declare function classifyIssueScope(body: string, gate: ResolvedScopeGate, labels?: ReadonlyArray): { readonly scope: ScopeClass; readonly acCount: number; readonly sourcesCount: number; readonly matchedLabel?: string; }; /** * Render the markdown subsection appended to the * `orchestrator-conventions` rule. Always returns a non-empty string * so the orchestrator rule documents the scope gate even when the * consumer relies on the defaults. * * When `excludeBundles` is non-empty, per-phase override rows whose * phase label is owned by an excluded bundle are dropped from the * **Per-phase-label thresholds** sub-table. The sub-section as a * whole is hidden when no rows survive. */ declare function renderScopeGateSection(gate: ResolvedScopeGate, excludeBundles?: ReadonlyArray): string; /** * Render a shell-script snippet embedded in `check-blocked.sh`. The * snippet declares a `scope_of()` function that reads an issue body * (passed as `$1`) and echoes one of `small` / `medium` / `large`. * Orchestrator-side bash hooks call this function before dispatch. * * Returns the body of a shell function block (including the * `scope_of()` wrapper) so the surrounding script can splice it * inline at the exact indent level it wants. */ declare function renderScopeGateShellHelpers(gate: ResolvedScopeGate): string; /** * Valid funnel-tier values. Lower numbers dispatch first when priority * is tied. See `DEFAULT_AGENT_TIERS` for the role of each tier. */ declare const AGENT_TIER_VALUES: readonly [0, 1, 2, 3, 4]; type AgentTier = (typeof AGENT_TIER_VALUES)[number]; /** * Human-readable role name for each funnel tier. Rendered into the * orchestrator-conventions rule and surfaced in generated `PICK` lines * so agents and humans can read the sort key at a glance. */ declare const AGENT_TIER_ROLES: Readonly>; /** * A single resolved agent-type → tier mapping. Returned by * `resolveAgentTiers()`; the rendered table in the orchestrator rule * content and the lookup table embedded in `check-blocked.sh` are * derived from this list. */ interface ResolvedAgentTier { /** * GitHub `type:*` label value (e.g. `"feat"`, `"research"`, * `"company-profile"`). The leading `type:` prefix is **not** part * of the stored value — it is added when the resolver stringifies * the lookup table. */ readonly type: string; /** Funnel tier 0–4 (lower dispatches first on a priority tie). */ readonly tier: AgentTier; /** Human-readable tier role (routing / research / profiles / synthesis / support). */ readonly role: string; } /** * Canonical default mapping of GitHub `type:*` label values to funnel * tiers. Mirrors openhi's `DISPATCHER.md` Dispatch Table plus the * taxonomy documented in the Group D epic (#414, issue #473). * * - **Tier 0 — routing.** Unblocks other work. Picked first on a * priority tie. Covers the issue-worker's own type labels * (`feat`, `fix`, `chore`, `refactor`, `docs`, `release`, * `hotfix`). Unknown labels also fall through here via * `UNKNOWN_TYPE_FALLBACK_TIER` so an unclassified issue never * blocks the queue. * - **Tier 1 — research.** Feeds downstream pipelines. * - **Tier 2 — profiles.** Consumes research. * - **Tier 3 — synthesis.** Produces deliverables. * - **Tier 4 — support.** Important but not pipeline-critical. * * Consumers extend or override this list via * `AgentConfigOptions.tiers` (see `resolveAgentTiers`). Every * registered agent type must resolve to exactly one tier; unmapped * types fail synth-time validation. */ declare const DEFAULT_AGENT_TIERS: ReadonlyArray; /** * Fallback tier applied to issues whose `type:*` label does not match * any entry in the resolved tier table. Using tier 0 (routing) means * an unclassified or newly-introduced issue is dispatched **before** * lower-tier work on a priority tie, which matches the openhi * dispatcher's "routing unblocks other work" rule. The synth-time * validator still rejects **registered** custom types that omit a * tier assignment — the fallback exists only for unknown labels * encountered at runtime. */ declare const UNKNOWN_TYPE_FALLBACK_TIER: AgentTier; /** * Resolve a (possibly absent) consumer-supplied `AgentTierConfig` into * a deduplicated list of `ResolvedAgentTier` entries. * * Precedence: * * 1. `config.tiers` fully **replaces** the default list when supplied. * This is the escape hatch for repos that want a bespoke taxonomy. * 2. `config.customTypes` entries **extend** whatever list is in play * (default or replacement). Later entries override earlier ones on * `type` collision, so consumer overrides win over defaults. * * @throws `Error` when any entry references a tier outside 0–4, when a * `type` is empty/whitespace, or when `config.tiers` is an * empty array (use omission to mean "keep the defaults"). */ declare function resolveAgentTiers(config?: AgentTierConfig): ReadonlyArray; /** * Synth-time validation hook. Throws a descriptive `Error` when the * resolved tier list is malformed. Called by `AgentConfig` before * rendering so a misconfigured `AgentTierConfig` fails the build * instead of silently shipping broken sort keys. * * Malformed cases rejected here: * * - Duplicate `type` values **within** the same supplied list (the * resolver dedupes between the default list and `customTypes`, so * those collisions are fine; duplicates inside one list are not). * - Tier values outside 0–4. * - Empty or whitespace-only `type` strings. * * Returns the validated list unchanged so callers can write * `const tiers = validateAgentTierConfig(config)` in one line. */ declare function validateAgentTierConfig(config?: AgentTierConfig): ReadonlyArray; /** * Render the funnel-tier subsection appended to the * `orchestrator-conventions` rule. Always returns a non-empty string * because the default tier list is non-empty — the orchestrator * always documents its sort order. * * When `excludeBundles` is non-empty, rows whose `type:*` label is * owned by an excluded bundle are dropped before rendering. Tiers * that end up with no surviving rows render an empty * `_(none registered)_` cell for consistency with the existing * render — the tier itself stays in the table so the dispatch * ordering is still documented end-to-end. */ declare function renderAgentTierSection(tiers: ReadonlyArray, excludeBundles?: ReadonlyArray): string; /** * Render a shell-script snippet that the `check-blocked.sh` procedure * uses to look up a tier for a given `type:*` label. Emitted as a * `case` statement so the generated script stays POSIX-shell * compatible (no associative arrays). * * The function returns the body of a shell function, not the * surrounding function wrapper, so bundle code can splice it inline * at the exact indent level it wants. */ declare function renderAgentTierCaseStatement(tiers: ReadonlyArray): string; /** * Default for whether the unblock-dependents sweep runs at all. When * the consumer omits `UnblockDependentsConfig`, the bundle ships with * the sweep **enabled** so every `status:done` transition * automatically propagates into the dependency graph. * * @see UnblockDependentsConfig */ declare const DEFAULT_UNBLOCK_DEPENDENTS_ENABLED = true; /** * Default comment body posted on a newly-unblocked issue. Variables * are substituted at runtime by the `unblock-dependents.sh` script: * * - `` — the `#` reference to the issue whose * closure triggered the sweep (e.g. `#123`). * * The placeholder syntax deliberately avoids `{{curly-brace}}` form — * `AgentConfig`'s template resolver claims that namespace at rule * generation time, so a `{{closedIssue}}` placeholder would be * rewritten before the agent ever saw it. */ declare const DEFAULT_UNBLOCK_COMMENT_TEMPLATE = "Dependencies resolved by \u2014 unblocking."; /** * Default comment body posted on a dependent whose other dependencies * are still open (partial unblock). Variables substituted at runtime: * * - `` — the just-closed issue (`#`). * - `` — space-separated list of remaining open * dependencies (e.g. `#45 #47`). * * Applied when `flagPartialUnblockWithAttention` is `true`; otherwise * no comment is posted and the dependent is left untouched. */ declare const DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE = "Dependency resolved, but still waiting on: ."; /** * Fully-resolved unblock-dependents settings. Every field is defaulted * so downstream renderers can reason about a single canonical shape. */ interface ResolvedUnblockDependents { readonly enabled: boolean; readonly commentTemplate: string; readonly flagPartialUnblockWithAttention: boolean; readonly partialUnblockCommentTemplate: string; } /** * Resolve a (possibly absent) `UnblockDependentsConfig` into a canonical * `ResolvedUnblockDependents` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs (empty / whitespace-only `commentTemplate`) throw * a descriptive `Error` — callers should not need to guard against it * at runtime. */ declare function resolveUnblockDependents(config?: UnblockDependentsConfig): ResolvedUnblockDependents; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `UnblockDependentsConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * sweep fails the build instead of silently shipping a broken * procedure script. Returns the resolved config unchanged so callers * can write `const ud = validateUnblockDependentsConfig(config)` in * one line. * * Malformed cases rejected here: * * - `commentTemplate` empty or whitespace-only. * - `partialUnblockCommentTemplate` empty or whitespace-only. */ declare function validateUnblockDependentsConfig(config?: UnblockDependentsConfig): ResolvedUnblockDependents; /** * Render the markdown subsection appended to the * `orchestrator-conventions` rule. Always returns a non-empty string * so the orchestrator rule documents the contract even when the * consumer relies on the defaults. */ declare function renderUnblockDependentsSection(ud: ResolvedUnblockDependents): string; /** * Render the full `unblock-dependents.sh` script body for a given * resolved unblock-dependents config. The script takes a single * positional argument (the just-closed issue number) and emits one * line per processed dependent in the canonical * `UNBLOCKED` / `STILL_BLOCKED` / `NO_DEPENDENTS` format. * * Exported so `AgentConfig.preSynthesize` can emit the procedure * alongside `check-blocked.sh`. */ declare function renderUnblockDependentsScript(ud: ResolvedUnblockDependents): string; /** * Build the check-blocked.sh procedure definition for a given resolved * tier table and scope gate. `AgentConfig.preSynthesize` calls this * with the consumer's resolved configs so the emitted script's * `tier_of()` lookup and `scope_of()` thresholds match whatever the * rendered orchestrator-conventions rule documents. * * Scope-gate settings default to the bundle's built-in defaults * (small: ≤3 AC + ≤2 sources; medium: ≤6 AC + ≤5 sources; * auto-file off) when the caller omits them. * * The `runRatio` parameter is retained for API compatibility but is no * longer rendered into the script — the orchestrator runs a single * linear cycle on every invocation, so the run counter / tick * subcommand were retired. */ declare function buildCheckBlockedProcedure(tiers: ReadonlyArray, scopeGate?: ResolvedScopeGate, runRatio?: ResolvedRunRatio): AgentProcedure; /******************************************************************************* * * unblock-dependents.sh — Targeted post-close dependency sweep. * * Called by every agent that applies `status:done` to an issue. The * script searches open issues for `Depends on: #` and * flips fully-resolved dependents from `status:blocked` to * `status:ready`. * * `buildUnblockDependentsProcedure()` is the factory that parameterises * the rendered script with a resolved `ResolvedUnblockDependents`; * `unblockDependentsProcedure` (declared below) is the default * instance that ships when the consumer supplies no override. * ******************************************************************************/ declare function buildUnblockDependentsProcedure(unblockDependents?: ResolvedUnblockDependents): AgentProcedure; /** * Build the orchestrator-conventions rule content for a given resolved * tier table, scope gate, scheduled-tasks, and unblock-dependents * config. The preamble is constant; each section below it is rendered * from the supplied values so consumer overrides propagate into the * generated rule. * * Every optional parameter defaults to the bundle's built-in default * when the caller omits it. * * The `runRatio` parameter is retained for API compatibility but is * no longer rendered into the conventions content — the orchestrator * runs a single linear cycle on every invocation, so the * dispatch/housekeeping ratio convention was retired. See Phase B * (PR review sweep) in `.claude/agents/orchestrator.md` for the * replacement workflow. */ declare function buildOrchestratorConventionsContent(tiers: ReadonlyArray, scopeGate?: ResolvedScopeGate, _runRatio?: ResolvedRunRatio, scheduledTasks?: ResolvedScheduledTasks, unblockDependents?: ResolvedUnblockDependents, excludeBundles?: ReadonlyArray): string; /** * Resolve the orchestrator-conventions rule content and the * check-blocked.sh procedure content for a given (possibly absent) * consumer-supplied tier config, scope-gate config, run-ratio config, * and scheduled-tasks config. Called by `AgentConfig.preSynthesize`. * * Returns the resolved tier table, scope gate, run ratio, and * scheduled-tasks config alongside both rendered artifacts so callers * can splice them into their rule map and procedure map in a single * pass. * * The `runRatio` parameter is retained for API compatibility but no * longer feeds the rendered conventions content or the * `check-blocked.sh` script — the orchestrator runs a single linear * cycle on every invocation, so the run-counter / `tick` subcommand * were retired. */ declare function resolveOrchestratorAssets(tierConfig?: AgentTierConfig, scopeGateConfig?: ScopeGateConfig, runRatioConfig?: RunRatioConfig, scheduledTasksConfig?: ScheduledTasksConfig, unblockDependentsConfig?: UnblockDependentsConfig, excludeBundles?: ReadonlyArray): { readonly tiers: ReadonlyArray; readonly scopeGate: ResolvedScopeGate; readonly runRatio: ResolvedRunRatio; readonly scheduledTasks: ResolvedScheduledTasks; readonly unblockDependents: ResolvedUnblockDependents; readonly conventionsContent: string; readonly procedure: AgentProcedure; readonly unblockDependentsProcedure: AgentProcedure; }; /** * Fully-resolved settings that feed the `orchestrator-conventions` * rule. Every field is already resolved, so `buildOrchestratorBundle` * can seed the rule's final content up front rather than shipping * default content that a later pass has to rewrite. */ interface ResolvedOrchestratorConventions { readonly tiers: ReadonlyArray; readonly scopeGate: ResolvedScopeGate; readonly runRatio: ResolvedRunRatio; readonly scheduledTasks: ResolvedScheduledTasks; readonly unblockDependents: ResolvedUnblockDependents; /** * Bundle names the consumer excluded. Rows owned by an excluded * bundle are dropped from the rendered tier table, scope-gate * overrides, and scheduled-tasks registry. */ readonly excludeBundles: ReadonlyArray; } /** * The orchestrator-conventions settings the bundle ships when the * consumer supplies no override. */ declare const DEFAULT_ORCHESTRATOR_CONVENTIONS: ResolvedOrchestratorConventions; /** * Build the `orchestrator` bundle with the consumer's resolved tier, * scope-gate, run-ratio, scheduled-tasks, and unblock-dependents * settings already baked into the `orchestrator-conventions` rule * content. * * Resolving here — rather than rewriting the rule after the rule map * has been assembled — is what lets a consumer's * `ruleExtensions["orchestrator-conventions"]` append (or a same-name * `agentConfig.rules` entry) survive alongside a tier / scope-gate / * scheduled-tasks override. The two features compose because the rule * enters the map already carrying the consumer's resolved settings. * * When the argument is omitted the bundle ships with the documented * defaults baked in, identical to the `orchestratorBundle` const below. */ declare function buildOrchestratorBundle(conventions?: ResolvedOrchestratorConventions): AgentRuleBundle; /** * Default-config instance of the orchestrator bundle, preserved for * backward compatibility with consumers that import the const * directly. The factory above is the canonical entry point when a * consumer supplies tier / scope-gate / scheduled-tasks overrides. */ declare const orchestratorBundle: AgentRuleBundle; /** * Default path globs that exempt a PR from the `human-required.size` * rule. The policy walks every changed path in the PR and skips * rule #6 (size threshold) when **every** path matches at least one * glob in this list. Doc-only PRs routinely exceed the 500-insertion * threshold (large migrations, bulk additions, refresh passes) but * carry no production risk that warrants forcing a human reviewer. * * The default exempts the entire `docs/**` tree — every consumer of * configulator places its Starlight docs site there. Consumers can * extend this list (e.g. add `docs/research/**` if doc-style research * notes live outside the Starlight tree) by passing * `prReviewPolicy.autoMerge.pathsExemptFromSize`. * * @see PrReviewPolicyConfig * @see PrReviewAutoMergeConfig.pathsExemptFromSize */ declare const DEFAULT_PATHS_EXEMPT_FROM_SIZE: ReadonlyArray; /** * Fully-resolved PR review policy. Every field is defaulted so * downstream renderers can reason about a single canonical shape. * * Two sub-rules are configurable today: the doc-only carve-out * against the size threshold (`autoMerge.pathsExemptFromSize`) and * the CI-verification fallback's required-workflow list * (`ciVerification.requiredWorkflows`). Additional knobs for other * rules in the policy may be added in future versions of * `PrReviewPolicyConfig`. */ interface ResolvedPrReviewPolicy { readonly autoMerge: ResolvedPrReviewAutoMerge; readonly ciVerification: ResolvedPrReviewCiVerification; } /** * Fully-resolved `auto-merge` half of the policy. * * `pathsExemptFromSize` is always populated — the default * (`["docs/**"]`) ships when the consumer omits the option. */ interface ResolvedPrReviewAutoMerge { readonly pathsExemptFromSize: ReadonlyArray; } /** * Fully-resolved `ci-verification` half of the policy. * * `requiredWorkflows` is always populated — the default (`[]`, i.e. * "treat every observed Actions run as required") ships when the * consumer omits the option. */ interface ResolvedPrReviewCiVerification { readonly requiredWorkflows: ReadonlyArray; } /** * Resolve a (possibly absent) `PrReviewPolicyConfig` into a canonical * `ResolvedPrReviewPolicy` with every field filled in. Unset fields * cascade from their documented defaults. * * Malformed configs (empty / whitespace-only path entries) throw a * descriptive `Error` — callers should not need to guard against it * at runtime. */ declare function resolvePrReviewPolicy(config?: PrReviewPolicyConfig): ResolvedPrReviewPolicy; /** * Synth-time validation hook. Throws a descriptive `Error` when the * supplied `PrReviewPolicyConfig` is malformed. Called by * `AgentConfig.preSynthesize` before any rendering so a misconfigured * policy fails the build instead of silently shipping broken carve-out * globs. Returns the resolved policy unchanged so callers can write * `const policy = validatePrReviewPolicyConfig(config)` in one line. * * Malformed cases rejected here: * * - `pathsExemptFromSize` entries that are empty or whitespace-only — * such an entry would either silently match nothing or match every * path, both of which are almost certainly a typo. * - `requiredWorkflows` entries that are empty or whitespace-only — a * blank workflow name can never match an Actions-run `name`, so the * intended gate would silently never fire. */ declare function validatePrReviewPolicyConfig(config?: PrReviewPolicyConfig): ResolvedPrReviewPolicy; /** * One row in the rendered agent registry table. Each phased-agent * bundle that previously shipped its own `-workflow` rule * contributes exactly one entry here so the registry can answer * "which agent handles X" without rendering 18 prose summaries * into CLAUDE.md. */ interface AgentRegistryEntry { /** Bundle name as it appears in `buildBuiltInBundles`, e.g. `bcm-writer`. */ readonly bundle: string; /** Primary user-invocable skill, with leading slash, e.g. `/write-bcm`. */ readonly skill: string; /** Sub-agent name in `.claude/agents/`, e.g. `bcm-writer`. */ readonly agent: string; /** * Function that resolves the canonical output path for this * bundle from the project's resolved agent-path roots. Returning * an empty string signals "no filesystem output path" (used by * pr-review). Path-aware so consumer overrides on * `AgentConfigOptions.paths` propagate into the rendered table. */ readonly resolveOutputPath: (paths: ResolvedAgentPaths) => string; /** * One-line purpose description. Lifted from the first prose * sentence of the original `-workflow` rule so consumers * keep the same routing signal. */ readonly purpose: string; /** * Name of the original `-workflow` rule. Used by the * registry helper to filter the resolved bundle list and assert * (via the test suite) that no bundle still ships its workflow * rule into the Claude platform output. */ readonly workflowRuleName: string; } /** * Static registry of every phased-agent bundle that contributes a * routing row. Order is alphabetical by bundle name so the * rendered table is stable across runs and consumer-side diffs are * minimal. Adding a new phased-agent bundle requires appending one * row here and suppressing its `-workflow` rule via * `platforms: { claude: { exclude: true } }`. */ declare const AGENT_REGISTRY_ENTRIES: ReadonlyArray; /** * The set of `-workflow` rule names that the registry * subsumes. Used both to suppress those rules from the Claude * platform output and to assert in tests that no bundle still * ships its prose summary into CLAUDE.md. */ declare const SUPPRESSED_WORKFLOW_RULE_NAMES: ReadonlyArray; /** * Returns `true` when the supplied rule name belongs to a * phased-agent `-workflow` rule whose routing summary now * lives in the shared `agent-registry` rule. */ declare function isSuppressedWorkflowRule(name: string): boolean; /** * Reverse map from a `-workflow` rule name to its owning * bundle name. Used by the registry consolidation loop to detect * when a consumer has targeted a bundle with a * `features.customDocSections` entry — those bundles keep * rendering their workflow rule into CLAUDE.md so the consumer- * supplied prose has somewhere to live. Returns `undefined` for * any rule name that is not in the registry's suppression list. */ declare function bundleNameForWorkflowRule(ruleName: string): string | undefined; declare function buildAgentRegistryRule(bundles: ReadonlyArray, paths: ResolvedAgentPaths): AgentRule | undefined; /** * Agenda bundle — enabled by default. * * Consuming projects can disable it with * `excludeBundles: ["agenda"]`. `appliesWhen` always returns `true` * (peer-present assumption, same pattern as the other workflow * bundles). * * Provides a 2-phase pre-meeting agenda pipeline * (draft → finalize), complementing the post-meeting pipeline in * the `meeting-analysis` bundle. Ships a sub-agent, two user- * invocable skills (`/draft-agenda`, `/finalize-agenda`), and * `agenda:*` phase labels via the bundle `labels` mechanism so * consuming projects automatically pick up the label taxonomy * through the sync-labels workflow. * * Reuses the meeting-type taxonomy from * `AgentConfigOptions.meetings.meetingTypes` — the same table the * `meeting-analysis` bundle consumes for post-meeting extraction. */ declare const agendaBundle: AgentRuleBundle; /** * AWS CDK bundle — auto-detected when `aws-cdk-lib` is in dependencies. */ declare const awsCdkBundle: AgentRuleBundle; /** * Hand-maintained registry mapping every bundle name to the cross-bundle * surface it owns: GitHub `type:*` labels, phase-label prefixes, * scheduled-task IDs, whether it emits Starlight docs, and whether it * declares any downstream issue kinds (i.e. files `gh issue create` * recipes via the issue-templates convention). * * The registry is consulted by renderers in other bundles whenever * `AgentConfigOptions.excludeBundles` is non-empty so cross-bundle * references to an excluded bundle's agents, type labels, phase labels, * or scheduled tasks disappear from the generated output. * * The map is **hand-maintained** rather than derived from each bundle's * runtime shape. The defining surfaces (the funnel-tier table in * `tiers.ts`, the per-phase scope-gate overrides in `scope-gate.ts`, and * the scheduled-tasks registry in `scheduled-tasks.ts`) live as flat * data tables that already get walked by their renderers — declaring the * ownership map alongside them keeps the relationship explicit and * readable without forcing every bundle to grow an "ownership" * descriptor. * * Bundles that ship no cross-bundle surface (e.g. `slack`, `typescript`, * `pnpm`, `vitest`, `jest`, `aws-cdk`, `projen`, `turborepo`, * `upstream-configulator-docs`) deliberately do not appear here — * excluding them is already a no-op since they own nothing other * bundles reference. */ interface BundleOwnership { /** * GitHub `type:*` label values (without the `type:` prefix) the * bundle owns. The funnel-tier table in `tiers.ts` and any rendered * tables that group agents by `type:*` label consult this list. */ readonly typeLabels: ReadonlyArray; /** * Phase-label prefixes (with trailing colon, e.g. `"company:"`) the * bundle owns. Used by the scope-gate per-phase override table and * any other renderer that groups by phase label. An entry without a * trailing colon (e.g. `"req:write"`) is treated as an exact * phase-label match instead of a prefix. */ readonly phaseLabelPrefixes: ReadonlyArray; /** * `taskId` values from `DEFAULT_SCHEDULED_TASK_ENTRIES` that target * this bundle's sub-agent. The scheduled-tasks registry filter in * `agent-config.ts` consults this list when pruning default entries * for an excluded bundle. */ readonly scheduledTaskIds: ReadonlyArray; /** * Whether this bundle emits Starlight content roots — i.e. whether * any of its workflows write files under `docs/src/content/docs/` * (or the configured docs root). Drives the auto-suppression of the * `section-index-pages` rule when no docs-emitting bundle is active. */ readonly emitsDocs: boolean; /** * Whether this bundle dispatches downstream issues (i.e. its * workflows file `gh issue create` recipes). Drives the * auto-suppression of the `issue-templates-convention` rule when no * such bundle is active. * * Not an exhaustive enumerator of issue-filing bundles. Only bundles * that own a cross-bundle surface appear in {@link BUNDLE_OWNERSHIP} * at all, so a bundle can file issues and still be absent — the * `upstream-configulator-docs` bundle files into a *foreign* repo * (`codedrifters/packages`) and is deliberately not registered. * Treat a `true` here as "this bundle's phase labels need the * templates convention", not as "these are all the filing sites". */ readonly downstreamIssueKinds: boolean; } /** * Canonical ownership map. Only bundles that own at least one * cross-bundle surface appear here. */ declare const BUNDLE_OWNERSHIP: Readonly>; /** * GitHub `type:*` labels (WITH the `type:` prefix) that come from the * **conventional-commit** vocabulary rather than the bundle/routing * vocabulary. These are derived from an issue's title prefix by the * generic create-issue workflow (`feat:` → `type:feat`, `docs:` → * `type:docs`, …) and are the only `type:*` labels the phase-label * invariant is allowed to remove when it corrects a mislabeled issue. * * A bundle `type:*` label (e.g. `type:research`, `type:bcm-document`) * is deliberately **not** in this set: an issue carrying a phase label * from one bundle plus a `type:*` label owned by a *different* bundle * is genuinely ambiguous and gets flagged for a human rather than * silently rewritten. */ declare const CONVENTIONAL_COMMIT_TYPE_LABELS: ReadonlyArray; /** * Canonical phase-label matcher → `type:` label map, derived * from {@link BUNDLE_OWNERSHIP}. This is the **single source of truth** * for the phase-label → type-label invariant: label registry * generation, the orchestrator's triage sweep, and the consumer-facing * label audit all read this map rather than re-deriving the pairing. * * Keys are matchers in the same notation `BundleOwnership.phaseLabelPrefixes` * uses — an entry ending in a colon (`"company:"`) is a prefix match, * an entry without one (`"req:write"`) is an exact match. Values carry * the `type:` prefix. * * Co-ownership is fine as long as the co-owners agree on the type * label: all three requirements bundles declare `type:requirement`, so * `req:`, `req:write`, `req:review`, and `req:deprecate` all resolve to * the same value. A matcher that resolved to two *different* type * labels would be a registry bug and throws at module load. */ declare const PHASE_LABEL_TYPE_MAP: Readonly>; /** * Outcome of resolving a set of issue labels against * {@link PHASE_LABEL_TYPE_MAP}. * * - `"none"` — the labels carry no **recognised** phase label, so the * invariant does not apply. Unrecognised `foo:bar` labels are * consumer-specific and deliberately not policed. * - `"match"` — the recognised phase labels all imply one and the same * `type:` label, carried in `typeLabel`. * - `"ambiguous"` — the recognised phase labels imply two or more * different `type:` labels. Never auto-corrected; the caller * flags the issue for human triage instead. */ type PhaseLabelTypeOutcome = "none" | "match" | "ambiguous"; /** Result of {@link resolveTypeLabelForLabels}. */ interface PhaseLabelTypeResolution { /** Which of the three outcomes applies. */ readonly outcome: PhaseLabelTypeOutcome; /** * The single implied `type:` label (with the `type:` prefix) * when `outcome` is `"match"`; `undefined` otherwise. */ readonly typeLabel?: string; /** * Every distinct implied `type:` label, sorted. Empty on * `"none"`, one entry on `"match"`, two or more on `"ambiguous"`. */ readonly candidateTypeLabels: ReadonlyArray; /** * The subset of the input labels that matched a phase-label matcher, * in input order. Empty on `"none"`. */ readonly phaseLabels: ReadonlyArray; } /** * Resolve a single phase label to the `type:` label its owning * bundle declares, or `undefined` when no bundle owns it. * * Exact-match entries beat prefix entries: `req:write` is owned by * `requirements-writer` while the `req:` prefix is owned by * `requirements-analyst`. (Both currently declare `type:requirement`, * but the precedence is load-bearing for any future divergence.) */ declare function typeLabelForPhaseLabel(phaseLabel: string): string | undefined; /** * Resolve every label on an issue to the `type:` label the * phase-label invariant requires it to carry. * * The input is the issue's **full** label list — the resolver picks out * the recognised phase labels itself and ignores everything else * (`status:*`, `priority:*`, existing `type:*`, and any consumer label * that matches no bundle). */ declare function resolveTypeLabelForLabels(labels: ReadonlyArray): PhaseLabelTypeResolution; /** * Render the **Phase-label → `type:` invariant** section of the * `orchestrator-conventions` rule. The matcher table is generated from * {@link PHASE_LABEL_TYPE_MAP}, so the documented pairing can never * drift from the pairing the sweep enforces. * * Rows whose owning bundle appears in `excludeBundles` are dropped, * matching every other cross-bundle renderer. */ declare function renderPhaseTypeInvariantSection(excludeBundles?: ReadonlyArray): string; /** * Render the POSIX-shell half of the phase-label → `type:` * invariant, derived from the same {@link PHASE_LABEL_TYPE_MAP} the * TypeScript accessors read. Emitted into `check-blocked.sh` so the * orchestrator's triage sweep and the consumer-runnable label audit * never carry a hand-copied second map. * * Three functions are rendered: * * - `phase_label_type_of