[
  {
    "path": ".context/agents/README.md",
    "content": "# Agent Handbook\n\nThis directory contains ready-to-customize playbooks for AI agents collaborating on the current dotcontext codebase, including the transformation work toward `dotcontext/cli` and `dotcontext/harness`.\n\n## Available Agents\n\n- [Code Reviewer](./code-reviewer.md) -- Review code changes for quality, style, and best practices\n- [Bug Fixer](./bug-fixer.md) -- Analyze bug reports and error messages\n- [Feature Developer](./feature-developer.md) -- Implement new features according to specifications\n- [Refactoring Specialist](./refactoring-specialist.md) -- Identify code smells and improvement opportunities\n- [Test Writer](./test-writer.md) -- Write comprehensive unit and integration tests\n- [Documentation Writer](./documentation-writer.md) -- Create clear, comprehensive documentation\n- [Performance Optimizer](./performance-optimizer.md) -- Identify performance bottlenecks\n\n## Harness Transformation Squad\n\n- [Agent Systems Designer](./agent-systems-designer.md) -- Define the target agent topology, harness primitives, and product boundaries\n- [CLI Experience Architect](./cli-experience-architect.md) -- Shape the `dotcontext/cli` experience and keep the CLI thin\n- [Harness Platform Architect](./harness-platform-architect.md) -- Define the runtime and API surface for `dotcontext/harness`\n- [Workflow Orchestration Engineer](./workflow-orchestration-engineer.md) -- Reframe PREVC, plans, agents, and skills as the harness control plane\n- [Harness Quality Auditor](./harness-quality-auditor.md) -- Map guides, sensors, evaluations, and operational quality gates\n- [Migration Release Manager](./migration-release-manager.md) -- Plan packaging, compatibility, rollout, and migration sequencing\n\n## How To Use These Playbooks\n\n1. Pick the agent that matches your task (e.g., `test-writer` when adding test coverage for a new service).\n2. Open the playbook file and adapt the template with project-specific context:\n   - Reference the relevant service under `src/services/` or generator under `src/generators/`.\n   - Link to the corresponding doc in `.context/docs/` or plan in `.context/plans/`.\n   - Include any active workflow phase or plan constraints.\n3. Paste the final prompt into your AI assistant session.\n4. After the task, capture learnings in the relevant documentation file (`.context/docs/`) so future runs improve.\n\n## Related Resources\n\n- [Documentation Index](../docs/README.md)\n- [Harness Transformation Plan](../plans/dotcontext-harness-engineering-transformation.md)\n- [Agent Knowledge Base](../../AGENTS.md)\n- [Contributor Guidelines](../../CONTRIBUTING.md)\n"
  },
  {
    "path": ".context/agents/bug-fixer.md",
    "content": "---\ntype: agent\nname: bug-fixer\ndescription: Analyze bug reports and error messages\nrole: developer\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Bug Fixer\n\n## Role\n\nDiagnose and fix bugs in the dotcontext CLI tool. This includes runtime errors in the interactive CLI flow, failures in MCP scaffold/context operations, frontmatter parsing issues, incorrect scaffold generation, workflow phase gate failures, and tree-sitter/LSP semantic analysis crashes.\n\n## Key Files to Understand\n\n- `src/index.ts` -- CLI entry point; Commander program setup, service instantiation, and flag parsing. Most user-facing errors surface here.\n- `src/types.ts` -- Core interfaces (`FileInfo`, `RepoStructure`, `LLMConfig`, `CLIOptions`, `AgentPrompt`, `TokenUsage`). Type mismatches often start here.\n- `src/utils/frontMatter.ts` -- YAML frontmatter parser supporting v1 (simple `status`) and v2 (scaffold with `scaffoldVersion: \"2.0.0\"`). A frequent source of parsing edge cases.\n- `src/services/mcp/gateway/context.ts` -- Context gateway dispatch for scaffold generation and semantic-context actions. Bugs here cascade into multiple MCP flows.\n- `src/services/semantic/codebaseAnalyzer.ts` -- Tree-sitter + optional LSP analysis. Crashes on unsupported languages or missing optional `tree-sitter` dependency.\n- `src/services/ai/tools/fillScaffoldingTool.ts` -- Scaffold fill helpers and semantic-context caching. Missing files, stale cache, and oversized payload issues surface here.\n- `src/utils/cliUI.ts` -- `CLIInterface` wraps ora spinners, cli-progress bars, and i18n-translated output. Spinner lifecycle bugs (not stopping on error) are common.\n- `src/workflow/gates/gateChecker.ts` -- PREVC phase gate validation. Gate check failures can block `workflow advance`.\n- `src/services/shared/contextRootResolver.ts` -- Resolves the `.context/` root directory. Path resolution bugs affect init, fill, sync, and export.\n\n## Workflow Steps\n\n1. **Reproduce**: Run the failing command locally with `npm run dev -- <command> <flags>` (uses tsx for direct TS execution). Add `--verbose` for extended logging.\n2. **Locate the error boundary**: Trace from `src/index.ts` command handler -> service `.run()` method -> downstream call. Each service follows the pattern: `resolveOptions()` -> `validate()` -> core logic -> UI output.\n3. **Check frontmatter parsing**: If the bug involves `.context/` files, verify the frontmatter format. v2 scaffold files must have `scaffoldVersion: \"2.0.0\"` and a valid `type` field (`doc`, `agent`, `skill`, `plan`). Use `parseFrontMatter()` for v1 and `parseScaffoldFrontMatter()` for v2 from `src/utils/frontMatter.ts`.\n4. **Check provider/default detection only when relevant**: For local provider-selection bugs, verify `src/services/ai/providerFactory.ts` and `src/utils/prompts/smartDefaults.ts`. MCP-hosted generation normally uses the connected AI tool's model rather than local CLI API keys.\n5. **Run existing tests**: `npm test` executes Jest with ts-jest. Relevant test files:\n   - `src/utils/frontMatter.test.ts`\n   - `src/utils/contentSanitizer.test.ts`\n   - `src/services/semantic/codebaseAnalyzer.test.ts`\n   - `src/workflow/gates/gateChecker.test.ts`\n   - `src/generators/agents/agentGenerator.test.ts`\n   - `src/generators/plans/planGenerator.test.ts`\n6. **Write a regression test**: Place it alongside the source file using the `*.test.ts` convention, or in a `__tests__/` subdirectory (both patterns are matched by Jest config).\n7. **Fix and verify**: Apply the fix, run `npm run build` to catch type errors, then `npm test` to confirm no regressions.\n\n## Best Practices\n\n- Always check whether the bug occurs in v1 (legacy) or v2 (scaffold) frontmatter paths; they share function names but diverge in parsing logic.\n- The `tree-sitter` and `tree-sitter-typescript` packages are optional dependencies. Wrap any tree-sitter usage in try/catch and degrade gracefully. See `CodebaseAnalyzer` constructor pattern.\n- Services use dependency injection via constructor objects (e.g., `InitServiceDependencies`, `SyncServiceDependencies`). When mocking for tests, provide all required fields: `ui` (CLIInterface), `t` (TranslateFn), `version` (string).\n- The i18n system (`src/utils/i18n.ts`) supports `en` and `pt-BR`. When fixing UI-facing strings, update both locale translation maps.\n- Use `sanitizeAIResponse()` from `src/utils/contentSanitizer.ts` on any LLM output before writing to files -- it strips markdown fences and other artifacts.\n\n## Common Pitfalls\n\n- **Spinner not stopped on error**: `CLIInterface` uses ora spinners. If an error is thrown mid-operation without calling `spinner.fail()`, the terminal output breaks. Always stop spinners in catch blocks.\n- **Path resolution assumptions**: `contextRootResolver.ts` walks up from cwd to find `.context/`. If tests or commands run from unexpected directories, paths resolve incorrectly. Always use `path.resolve()` with explicit base paths.\n- **Scaffold file discovery**: `src/services/shared/globPatterns.ts` and the context tools must agree on which scaffold files are discoverable. New file types can be skipped if the discovery logic is not updated consistently.\n- **PREVC phase ordering**: The phase order is strictly `['P', 'R', 'E', 'V', 'C']` (defined in `src/workflow/phases.ts`). Skipping phases requires the phase to be marked `optional: true`. Only `R` (Review) is optional by default.\n- **Zod schema validation**: AI agent outputs are validated with Zod schemas (e.g., `DocumentationOutputSchema` in `src/services/ai/schemas.ts`). Schema mismatches from LLM responses cause silent failures if not caught.\n"
  },
  {
    "path": ".context/agents/code-reviewer.md",
    "content": "---\ntype: agent\nname: code-reviewer\ndescription: Review code changes for quality, style, and best practices\nrole: reviewer\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Code Reviewer\n\n## Role\n\nReview pull requests and code changes in the dotcontext project for correctness, consistency with existing patterns, type safety, proper error handling, and adherence to the project's architectural conventions. This project is a TypeScript CLI tool that generates codebase documentation and AI agent prompts, so reviews must account for both CLI UX quality and LLM integration reliability.\n\n## Key Files to Understand\n\n- `src/index.ts` -- CLI entry point using Commander. All commands are registered here. Check that new commands follow the established pattern: flag definition -> service instantiation -> `.run()` call.\n- `src/types.ts` -- Shared type definitions. Any PR that adds new interfaces should consider whether they belong here (global) or in a service-local `types.ts` file.\n- `tsconfig.json` -- Strict mode enabled, target ES2020, commonjs modules. The `baseUrl` is `./src` with a path alias `@generators/agents/*`.\n- `jest.config.js` -- ts-jest preset, roots in `<rootDir>/src`, test match patterns: `**/__tests__/**/*.ts` and `**/?(*.)+(spec|test).ts`.\n- `src/utils/theme.ts` -- Centralized color palette and symbols using chalk. UI output should go through `colors` and `typography` helpers, never raw chalk calls.\n- `src/utils/i18n.ts` -- Translation system with `en` and `pt-BR` locales. Every user-facing string must use `t('key')` pattern.\n- `src/services/shared/` -- Shared utilities: `pathHelpers.ts`, `llmConfig.ts`, `globPatterns.ts`, `contentTypeRegistry.ts`, `contextRootResolver.ts`, `uiHelpers.ts`, `toolRegistry.ts`. Common code should live here, not be duplicated across services.\n- `src/generators/shared/` -- Shared generator utilities: `generatorUtils.ts`, `contextGenerator.ts`, `scaffoldStructures.ts`, and the structures registry under `structures/`.\n\n## Workflow Steps\n\n1. **Understand scope**: Read the PR description and diff. Identify which services, generators, or utilities are touched. Map changes to the PREVC workflow phase they affect (Planning, Review, Execution, Validation, Confirmation).\n2. **Check type safety**: Run `npm run build` mentally or actually. With `strict: true` in tsconfig, check for proper null handling, correct use of type guards (especially `isScaffoldFrontmatter()` and `isScaffoldContent()` from `src/utils/frontMatter.ts`), and no `any` casts without justification.\n3. **Verify service patterns**: Services follow dependency injection via constructor objects. Check that:\n   - Dependencies are declared as interfaces (e.g., `InitServiceDependencies`, `FillCommandFlags`)\n   - The `run()` method follows: resolve options -> validate -> execute -> UI feedback\n   - `CLIInterface` (`ui`) and `TranslateFn` (`t`) are threaded through, not imported directly\n4. **Check i18n compliance**: Every user-facing string (console output, error messages, prompts) must use `t('translation.key')` or `this.t('key')`. Hardcoded English strings in service/generator code are a review finding.\n5. **Validate frontmatter handling**: If the PR touches `.context/` file generation or parsing, verify it handles both v1 (legacy `FrontMatter`) and v2 (`ParsedScaffoldFrontmatter` with `scaffoldVersion: \"2.0.0\"`) formats correctly.\n6. **Review test coverage**: Check that new logic has corresponding tests. Tests should be co-located using `*.test.ts` or in `__tests__/` directories. Key test files to reference for patterns:\n   - `src/utils/frontMatter.test.ts` -- Unit test pattern with direct function testing\n   - `src/generators/agents/agentGenerator.test.ts` -- Generator test with mocked dependencies\n   - `src/workflow/gates/gateChecker.test.ts` -- Workflow logic testing\n   - `src/services/shared/__tests__/contextRootResolver.test.ts` -- Service test in `__tests__/` directory\n7. **Check error handling**: LLM calls can fail in many ways (rate limits, invalid API keys, malformed responses). Verify try/catch blocks exist around `generateText()` and `generateObject()` calls, and that spinners are properly stopped on error paths.\n\n## Best Practices\n\n- **Barrel exports**: Each service and generator directory has an `index.ts` that re-exports public APIs. New exports should be added there, not imported via deep paths from outside the module.\n- **Optional dependencies**: `tree-sitter` and `tree-sitter-typescript` are in `optionalDependencies`. Code using them must handle the case where they are not installed (dynamic import with try/catch).\n- **Consistent flag naming**: CLI flags use camelCase in code (`useAgents`, `useLsp`, `autoFill`) and kebab-case on the command line (`--use-agents`, `--use-lsp`, `--auto-fill`). Commander handles the conversion.\n- **Scaffold structure system**: The scaffold structure registry in `src/generators/shared/structures/` defines templates for docs, agents, skills, and plans. New content types must register in `registry.ts` and provide proper `ScaffoldStructure` definitions.\n- **AI provider abstraction**: LLM interactions go through `LLMClientFactory` -> provider-specific clients. Never import `@ai-sdk/anthropic` or `@ai-sdk/google` directly in service code; use the factory pattern in `src/services/ai/providerFactory.ts`.\n\n## Common Pitfalls\n\n- **Missing `await` on fs-extra calls**: `fs-extra` methods like `ensureDir`, `writeFile`, `readFile` are async. Missing `await` causes race conditions that only manifest intermittently.\n- **Glob pattern escaping**: The `glob` package (v10) uses different escaping rules than earlier versions. Patterns in `src/services/shared/globPatterns.ts` must use forward slashes even on Windows.\n- **Translation key typos**: The i18n system silently returns the key string if a translation is missing. Review that new translation keys actually exist in the locale maps.\n- **PREVC phase-role mismatches**: When modifying agent-to-phase mappings in `src/generators/agents/agentGenerator.ts` (`AGENT_PHASES`), verify the roles match those defined in `src/workflow/phases.ts` (`PREVC_PHASES`).\n- **Inquirer v12 breaking changes**: The project uses `inquirer@^12.6.3` which has ESM/CJS compatibility nuances. Prompt definitions must match the v12 API.\n"
  },
  {
    "path": ".context/agents/documentation-writer.md",
    "content": "---\ntype: agent\nname: documentation-writer\ndescription: Create clear, comprehensive documentation\nrole: documenter\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Documentation Writer\n\n## Role\n\nCreate and maintain documentation for the dotcontext project, both the user-facing README/guides and the internal `.context/` documentation that the tool itself generates. This includes writing doc templates, improving scaffold structures, updating the guide registry, and ensuring documentation accurately reflects the current CLI commands and service capabilities.\n\n## Key Files to Understand\n\n- `README.md` -- Project README, the primary user-facing documentation. Covers installation, CLI usage, commands, and configuration.\n- `.context/docs/README.md` -- Index of generated documentation within the `.context/` directory structure.\n- `.context/agents/README.md` -- Index of agent playbooks.\n- `src/generators/documentation/documentationGenerator.ts` -- Core generator that scaffolds documentation files in `.context/docs/`. Creates files for project overview, architecture, API reference, testing, onboarding, etc.\n- `src/generators/documentation/guideRegistry.ts` -- Registry of all documentation guide types (`DOCUMENT_GUIDES`). Each guide has a name, description, category, and template. Adding new documentation types starts here.\n- `src/generators/documentation/templates/` -- Template functions that produce the initial content for each documentation type. Contains `common.ts` (shared template helpers), `indexTemplate.ts` (README index), and type definitions.\n- `src/generators/shared/structures/documentation/` -- Scaffold structure definitions for each doc type: `projectOverview.ts`, `architecture.ts`, `apiReference.ts`, `testing.ts`, `onboarding.ts`, `workflow.ts`, `security.ts`, `tooling.ts`, `glossary.ts`, `troubleshooting.ts`, `migration.ts`, `dataFlow.ts`.\n- `src/services/ai/tools/fillScaffoldingTool.ts` -- MCP scaffold-fill helpers. Understanding the context returned here is essential for writing templates that produce good output in connected AI tools.\n- `src/services/autoFill/autoFillService.ts` -- Static auto-fill layer used during scaffold generation. Good structures improve both auto-filled starter content and MCP-guided completion.\n- `src/utils/i18n.ts` -- Translation system. Documentation-related UI strings (section headers, progress messages) must be translatable.\n\n## Workflow Steps\n\n1. **Identify documentation gap**: Determine what needs documenting -- a new CLI command, a new service, a changed workflow, or an improvement to existing scaffold templates.\n2. **Check the guide registry**: Review `src/generators/documentation/guideRegistry.ts` to see existing documentation types and their categories. Categories include: `overview`, `architecture`, `development`, `operations`.\n3. **Update or create scaffold structures**: If adding a new documentation type, create a structure definition in `src/generators/shared/structures/documentation/` following the pattern of existing files (export a `ScaffoldStructure` with `fields`, `sections`, and `metadata`). Register it in the `index.ts` barrel.\n4. **Write the template**: Add a template function in `src/generators/documentation/templates/` that generates the initial scaffold markdown. Use the `common.ts` helpers for consistent formatting. Templates should include YAML frontmatter with `type: doc`, `name`, `description`, `generated`, `status: unfilled`, and `scaffoldVersion: \"2.0.0\"`.\n5. **Update the generator**: If adding new guide types, register them in `documentationGenerator.ts` so MCP `context({ action: \"init\" })` includes them.\n6. **Test generation**: Run the relevant generator tests and verify the MCP context flow (`context init` + `fillSingle`) still provides the right scaffold structure and instructions.\n7. **Verify i18n**: Add translation keys for any new user-facing strings to both `en` and `pt-BR` locales in `src/utils/i18n.ts`.\n\n## Best Practices\n\n- **Scaffold-first approach**: All documentation in `.context/` starts as a scaffold (status: `unfilled`) with structured sections and guidance. MCP clients then use the scaffold structure and semantic context to complete them. Write templates that give the AI clear section headings and context about what to include.\n- **Frontmatter consistency**: Always use v2 scaffold frontmatter format with `scaffoldVersion: \"2.0.0\"`. Include `type`, `name`, `description`, `generated`, and `status` fields at minimum.\n- **Use `serializeStructureForAI()` and `serializeStructureAsMarkdown()`**: These functions in `src/generators/shared/scaffoldStructures.ts` convert scaffold structures to formats consumable by the LLM or written to disk. Use them instead of hand-crafting markdown.\n- **Content sanitization**: LLM-generated documentation passes through `sanitizeAIResponse()` from `src/utils/contentSanitizer.ts`. Be aware that it strips markdown code fences wrapping entire documents and normalizes whitespace.\n- **Cross-reference with agents**: Documentation guides are referenced by agent playbooks via the `docs` field in frontmatter. When creating new documentation, consider which agents would benefit from referencing it.\n- **Keep the codebase map updated**: `src/generators/documentation/codebaseMapGenerator.ts` generates an auto-updated codebase map. Any major structural changes should be reflected here.\n\n## Common Pitfalls\n\n- **Forgetting the index template**: When adding new documentation types, also update `src/generators/documentation/templates/indexTemplate.ts` so the `.context/docs/README.md` index includes a link to the new document.\n- **Template vs. fill content confusion**: Templates generate the scaffold skeleton; MCP clients or auto-fill complete the actual content. Do not put project-specific details in templates -- those are inferred from the codebase during fill.\n- **Stale documentation after refactoring**: After service or generator refactoring, re-run the MCP context flow to regenerate scaffolding and refresh filled content. The `dotcontext report` command can identify stale or unfilled documents.\n- **Ignoring auto-fill**: The `AutoFillService` (`src/services/autoFill/`) can populate scaffolds with semantic data without an LLM. When writing structures, define auto-fill hints in the scaffold structure so auto-fill can provide baseline content.\n- **Missing `getScaffoldStructure()` registration**: New scaffold types must be registered in `src/generators/shared/scaffoldStructures.ts` via `getScaffoldStructure()`. Without this, the fill pipeline cannot locate the structure definition for the document.\n"
  },
  {
    "path": ".context/agents/feature-developer.md",
    "content": "---\ntype: agent\nname: feature-developer\ndescription: Implement new features according to specifications\nrole: developer\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Feature Developer\n\n## Role\n\nImplement new features in the dotcontext CLI tool, including new CLI commands, services, generators, workflow capabilities, MCP tools, and integrations with LLM providers. Features must follow the project's established architecture: Commander-based CLI -> Service layer -> Generator/AI layer, with dependency injection, i18n support, and proper frontmatter handling.\n\n## Key Files to Understand\n\n- `src/index.ts` -- CLI entry point. All commands are registered here using Commander. New commands follow the pattern: `program.command('name').description().option().action(async () => { ... })`.\n- `src/types.ts` -- Core shared types: `FileInfo`, `RepoStructure`, `LLMConfig`, `CLIOptions`, `AgentPrompt`, `TokenUsage`.\n- `src/services/` -- Service layer. Each service lives in its own subdirectory with `index.ts` barrel export, a main service class, a `types.ts` for interfaces, and optionally a `presets.ts` for default configurations.\n- `src/generators/` -- Content generators for `documentation`, `agents`, `plans`, and `skills`. Each generator creates scaffold files in `.context/`.\n- `src/workflow/` -- PREVC workflow system: phases (`P`lanning, `R`eview, `E`xecution, `V`alidation, `C`onfirmation), roles, gates, orchestration, scaling, and status management.\n- `src/services/ai/` -- AI integration layer: `providerFactory.ts` (provider helpers), `tools/` (code analysis and scaffold tools), `schemas/` (Zod schemas for structured output), and MCP-facing utilities.\n- `src/services/mcp/` -- Model Context Protocol server implementation using `@modelcontextprotocol/sdk`.\n- `src/utils/i18n.ts` -- i18n with `en` and `pt-BR` locales. The `TranslateFn` type and `createTranslator()` factory.\n- `src/utils/cliUI.ts` -- `CLIInterface` class for all terminal output (spinners, progress bars, status messages, PREVC diagrams).\n- `src/utils/theme.ts` -- Centralized chalk-based color scheme (`colors`, `symbols`, `typography`).\n- `src/services/shared/` -- Cross-cutting utilities: path helpers, glob patterns, content type registry, context root resolution, and tool registry.\n\n## Workflow Steps\n\n1. **Plan the feature**: Identify which layers need changes. A typical feature touches: (a) CLI command definition in `src/index.ts`, (b) a new or existing service in `src/services/`, (c) possibly a generator in `src/generators/`, and (d) i18n keys in `src/utils/i18n.ts`.\n\n2. **Create the service**: Follow the established directory pattern:\n   - `src/services/<feature>/index.ts` -- Barrel export\n   - `src/services/<feature>/<feature>Service.ts` -- Main service class\n   - `src/services/<feature>/types.ts` -- Interfaces (command flags, dependencies, options)\n   - `src/services/<feature>/presets.ts` -- Default configurations (optional)\n\n   The service constructor takes a dependencies object with at minimum `ui: CLIInterface`, `t: TranslateFn`, and `version: string`. The main method is `async run(flags: CommandFlags): Promise<void>`.\n\n3. **Register the CLI command**: In `src/index.ts`, import the service and add a new `program.command()` block. Follow existing patterns for flag definitions (use `.option()` with short and long forms). Instantiate the service with the shared `ui`, `t`, and `VERSION` dependencies.\n\n4. **Add i18n translations**: Add all user-facing strings as translation keys in `src/utils/i18n.ts` for both `en` and `pt-BR` locales. Use the namespace pattern: `'<command>.<context>.<specific>'` (e.g., `'fill.progress.analyzing'`).\n\n5. **Integrate with generators** (if producing `.context/` files): Use the scaffold structure system in `src/generators/shared/structures/`. Define a `ScaffoldStructure`, register it, and use `createAgentFrontmatter()` or equivalent from `src/types/scaffoldFrontmatter.ts` for proper v2 frontmatter.\n\n6. **Add AI capabilities** (if MCP-facing): Extend `src/services/ai/tools/` and `src/services/mcp/gateway/` rather than adding standalone CLI agent classes. Reuse `providerFactory.ts` helpers only where the MCP server itself needs provider-aware behavior; content generation should flow through the connected MCP client.\n\n7. **Write tests**: Create `<feature>Service.test.ts` or a `__tests__/` directory. Mock `CLIInterface` and `TranslateFn`. Use `jest.mock()` for fs-extra and external dependencies.\n\n8. **Build and test**: Run `npm run build` (TypeScript compilation) then `npm test` (Jest). Test manually with `npm run dev -- <new-command> --verbose`.\n\n## Best Practices\n\n- **Dependency injection everywhere**: Never import `CLIInterface` or `TranslateFn` directly in services. Accept them via constructor dependency objects. This makes testing straightforward.\n- **Use `resolveOptions()` pattern**: Services should have a private `resolveOptions()` method that merges CLI flags with defaults, resolves paths, and validates inputs before the main logic runs.\n- **Respect the `.context/` directory structure**: All generated files go under `.context/` with subdirectories: `docs/`, `agents/`, `plans/`, `skills/`, `workflow/`. Use `contextRootResolver.ts` to find the correct root.\n- **Provider-agnostic AI code**: Support all four providers (`openrouter`, `openai`, `anthropic`, `google`) via `providerFactory.ts`. Never hardcode a specific provider. The default model is defined as `DEFAULT_MODEL` in `src/index.ts`.\n- **Graceful degradation for optional deps**: `tree-sitter` is optional. Any feature using semantic analysis must handle the case where it is not installed.\n\n## Common Pitfalls\n\n- **Not updating barrel exports**: After creating new files, update the `index.ts` in the parent directory to re-export them. Other parts of the codebase import from barrels, not deep paths.\n- **Forgetting the version import**: `VERSION` and `PACKAGE_NAME` come from `src/version.ts`, not `package.json` directly. Use these constants for consistency.\n- **Commander flag name mismatch**: Commander converts `--my-flag` to `myFlag` in the options object. Be careful with the camelCase conversion, especially for flags like `--use-lsp` -> `useLsp`.\n- **Async service initialization**: Some services need async setup (e.g., reading config files). Do this in `run()`, not the constructor. Constructors must be synchronous.\n- **Token limits with LLM agents**: When building agentic features, set reasonable `maxSteps` and `maxOutputTokens` limits. The `stepCountIs()` helper from the AI SDK can cap tool-use loops.\n"
  },
  {
    "path": ".context/agents/performance-optimizer.md",
    "content": "---\ntype: agent\nname: performance-optimizer\ndescription: Identify performance bottlenecks\nrole: developer\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Performance Optimizer\n\n## Role\n\nIdentify and resolve performance bottlenecks in the dotcontext CLI tool. The primary performance concerns are: file system scanning speed (glob operations over large repositories), tree-sitter parsing throughput, semantic-context construction cost, MCP payload size, and CLI startup time. Since this is a developer tool that operates on entire codebases, performance at scale (repositories with thousands of files) is critical.\n\n## Key Files to Understand\n\n- `src/utils/fileMapper.ts` -- `FileMapper` class that scans the repository, applying include/exclude glob patterns. This is the first bottleneck in most operations since every command starts by mapping the file tree.\n- `src/services/shared/globPatterns.ts` -- Default exclude patterns (node_modules, dist, coverage, .git, etc.). Poorly configured patterns cause FileMapper to scan too many files.\n- `src/services/semantic/codebaseAnalyzer.ts` -- `CodebaseAnalyzer` orchestrates tree-sitter parsing. Has `maxFiles: 5000` default limit. The `treeSitter/treeSitterLayer.ts` handles per-file parsing.\n- `src/services/semantic/contextBuilder.ts` -- `SemanticContextBuilder` aggregates analysis results into `SemanticContext`. Memory usage scales with number of extracted symbols.\n- `src/services/ai/tools/fillScaffoldingTool.ts` -- Shared scaffold-fill tool. Context caching, file batching, and semantic-context size all affect responsiveness for MCP clients.\n- `src/services/mcp/gateway/context.ts` -- Context gateway wiring. Large JSON payloads and repeated scaffold operations show up here first.\n- `src/services/ai/tools/` -- Code analysis tools provided to AI agents. Tool execution involves file I/O. Inefficient tool implementations slow down the entire agentic loop.\n- `src/generators/documentation/codebaseMapGenerator.ts` -- Generates a structural map of the codebase. For large repos, this can be slow due to directory traversal.\n- `src/services/quickSync/quickSyncService.ts` -- QuickSync provides a faster alternative to full sync. Understanding its optimizations helps when optimizing other services.\n- `src/index.ts` -- CLI startup imports all services eagerly. Startup time is affected by import chain depth.\n\n## Workflow Steps\n\n1. **Profile the operation**: Identify which CLI command is slow. Run with `--verbose` flag and observe timing. Key operations to profile:\n   - `context({ action: \"init\" })` -- File scanning + scaffold generation\n   - `context({ action: \"fill\" })` / `fillSingle` -- Semantic context assembly per scaffold file\n   - `dotcontext sync` -- File discovery + symlink/reference creation\n   - `dotcontext report` -- File scanning + frontmatter parsing\n\n2. **Measure file I/O**: FileMapper and glob operations are the most common bottleneck. Check:\n   - Are exclude patterns in `globPatterns.ts` covering all irrelevant directories?\n   - Is the `glob` package (v10) being called with `{ ignore }` option rather than post-filtering?\n   - Are files being read multiple times? (frontmatter parsing reads file headers; fill reads full content)\n\n3. **Analyze context size**: For scaffold fill operations, check:\n   - Shared semantic context size returned by `fillScaffoldingTool`\n   - Whether repeated `fillSingle` calls rebuild or bypass cached context\n   - Whether a narrower `target` or lower `limit` would reduce MCP response size\n\n4. **Check tree-sitter parsing**: `CodebaseAnalyzer` defaults to parsing up to 5000 files. For large repos:\n   - Verify language filtering is applied early (only parse files matching requested languages).\n   - Check if caching is enabled (`cacheEnabled: true` in options).\n   - Consider whether the `useLSP` flag is enabled unnecessarily (LSP adds significant overhead).\n\n5. **Implement the optimization**: Common strategies:\n   - **Lazy imports**: Move heavy imports (tree-sitter, AI SDK) to dynamic `import()` inside the functions that need them, reducing startup time.\n   - **Parallel processing**: Use `Promise.all()` for independent file operations where the MCP response model still stays bounded.\n   - **Streaming**: Use `createReadStream` with readline (already used in `frontMatter.ts` for fast status detection) instead of full `fs.readFile()` where only headers are needed.\n   - **Caching**: Cache parsed frontmatter, file maps, and semantic analysis results between commands.\n\n6. **Verify with benchmarks**: Run before/after on a representative large repository. Measure wall-clock time and peak memory usage.\n\n## Best Practices\n\n- **Profile before optimizing**: Use `--verbose` output and `console.time()`/`console.timeEnd()` to identify actual bottlenecks rather than guessing.\n- **Respect the `maxFiles` limit**: `CodebaseAnalyzer` has a 5000-file cap for good reason. Do not remove it; instead, improve filtering so the right 5000 files are analyzed.\n- **Use streaming frontmatter reads**: `src/utils/frontMatter.ts` already has optimized first-line reading for fast status detection. Extend this pattern for any operation that only needs file metadata.\n- **Token-efficient context**: When building context for LLM calls, use `serializeStructureForAI()` which produces a compact representation rather than sending full file contents.\n- **Avoid synchronous fs calls**: The codebase uses `fs-extra` (async) consistently. Never introduce synchronous file operations (`fs.readFileSync`, etc.) as they block the event loop and degrade perceived CLI responsiveness.\n\n## Common Pitfalls\n\n- **Premature parallelization of scaffold work**: Running too many semantic-analysis tasks in parallel can spike memory and produce oversized MCP payloads. Use a concurrency limiter rather than unbounded `Promise.all()`.\n- **Tree-sitter memory leaks**: Tree-sitter parsers hold native memory. If parsing thousands of files, ensure parsers are reused (not re-created per file) and that parse trees are released after symbol extraction.\n- **Glob pattern order matters**: The `glob` package evaluates patterns in order. Put the most selective patterns first to short-circuit directory traversal early.\n- **Over-caching stale data**: If implementing caching, include file modification timestamps or git commit hashes as cache keys. Stale caches cause incorrect scaffold content or missing files.\n- **Ignoring the QuickSync model**: `QuickSyncService` already implements optimized file discovery. Before building custom optimizations for other services, study its approach to reuse patterns.\n"
  },
  {
    "path": ".context/agents/refactoring-specialist.md",
    "content": "---\ntype: agent\nname: refactoring-specialist\ndescription: Identify code smells and improvement opportunities\nrole: developer\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Refactoring Specialist\n\n## Role\n\nIdentify code smells, architectural inconsistencies, and improvement opportunities in the dotcontext codebase. Refactoring targets include: reducing coupling between services, standardizing patterns across generators, consolidating duplicated logic into shared utilities, improving type safety, and simplifying the service dependency injection model.\n\n## Key Files to Understand\n\n- `src/index.ts` -- CLI entry point. Currently houses all command registration and service instantiation inline. A primary refactoring candidate for command registration extraction.\n- `src/services/mcp/gateway/context.ts` -- Consolidates multiple scaffold and semantic-context actions behind one gateway. It is a high-impact refactoring surface because multiple MCP flows converge here.\n- `src/generators/agents/agentGenerator.ts` -- Second most imported (13 dependents). Contains the `AGENT_PHASES` mapping, agent generation logic, and semantic context integration. Consider extracting the phase mapping to the workflow module.\n- `src/services/shared/` -- Shared utilities directory with path helpers, glob patterns, content type registry, context root resolution, UI helpers, and tool registry. Some services duplicate logic that could be centralized here.\n- `src/generators/shared/` -- Shared generator code including `scaffoldStructures.ts`, `generatorUtils.ts`, `contextGenerator.ts`, and the `structures/` registry. The structures system is well-designed but some generators bypass it.\n- `src/workflow/` -- PREVC workflow module with phases, roles, gates, orchestration, scaling, status management, and skills. Internal cohesion is good but the API surface to external consumers could be cleaner.\n- `src/services/ai/providerFactory.ts` -- AI provider abstraction. Four providers (openrouter, openai, anthropic, google) with similar setup patterns that could benefit from a strategy pattern.\n- `src/utils/frontMatter.ts` -- Handles both v1 and v2 frontmatter formats. The dual-format support adds complexity; consider a unified parser with format auto-detection.\n- `src/types.ts` -- Global types file. Some interfaces here are only used by specific services and could be co-located.\n- `src/services/init/initService.ts`, `src/services/sync/syncService.ts`, `src/services/workflow/workflowService.ts` -- Strong reference implementations for the current service pattern.\n\n## Workflow Steps\n\n1. **Audit the dependency graph**: Identify high fan-in (many importers) and high fan-out (many imports) modules. Key metrics:\n   - `handleContext` and `fillScaffoldingTool`: high fan-in around MCP scaffolding flows\n   - `AgentGenerator`: 13 importers -- the `AGENT_PHASES` constant could live in `src/workflow/` instead\n   - `InitService`: 9 importers\n\n2. **Identify pattern violations**: The standard service pattern is:\n   - `Constructor(dependencies: ServiceDependencies)` -> `resolveOptions(flags)` -> `validate()` -> `execute()` -> UI feedback\n   - Find services that deviate: missing dependency injection, direct console.log instead of `ui.*`, hardcoded paths instead of using `contextRootResolver`, or synchronous operations where async is expected.\n\n3. **Find duplicated logic**: Common duplication areas:\n   - Frontmatter reading/writing across services (should all use `src/utils/frontMatter.ts`)\n   - Glob pattern assembly (should all use `src/services/shared/globPatterns.ts`)\n   - Provider/default-model detection (should consistently use `src/services/ai/providerFactory.ts` and `src/utils/prompts/smartDefaults.ts`)\n   - File discovery patterns (scanning `.context/` subdirectories)\n\n4. **Plan the refactoring**: For each identified improvement:\n   - Assess the blast radius (how many files change)\n   - Determine if it can be done incrementally (extract interface first, then migrate callers)\n   - Check test coverage of affected code before refactoring\n\n5. **Execute incrementally**: Make one structural change at a time. After each change:\n   - Run `npm run build` to verify type safety\n   - Run `npm test` to verify behavior\n   - Verify barrel exports in `index.ts` files are updated\n\n6. **Update scaffolds and structures**: If refactoring changes public APIs of services or generators, update the corresponding scaffold structures in `src/generators/shared/structures/` and templates so future MCP `context({ action: \"init\" })` runs produce correct scaffolds.\n\n## Best Practices\n\n- **Extract, don't rewrite**: Move existing code into better locations rather than rewriting from scratch. This preserves tested behavior.\n- **Maintain barrel exports**: Every directory's `index.ts` must be updated when files are added, removed, or renamed. External consumers import from barrels.\n- **Keep the dependency injection contract**: Services accept `{ ui, t, version, ... }` in constructors. When extracting sub-services, give them their own dependency interface rather than passing the parent's full dependency object.\n- **Use TypeScript's type system**: When splitting interfaces, use `Pick<>`, `Omit<>`, and intersection types to derive sub-interfaces from existing ones. This keeps types DRY and ensures compatibility.\n- **Preserve the PREVC model**: The workflow phases (Planning, Review, Execution, Validation, Confirmation) and roles (planner, designer, architect, developer, qa, reviewer, documenter, solo-dev) are core architectural concepts. Refactoring should make these clearer, not obscure them.\n\n## Common Pitfalls\n\n- **Breaking circular dependencies**: The codebase has some circular import risks between `generators/agents` <-> `services/autoFill` <-> `services/stack`. Use dependency inversion (interfaces in shared modules) to break cycles rather than restructuring entire directories.\n- **Over-abstracting the AI layer**: The four LLM providers have genuinely different capabilities (tool calling support, structured output, streaming). Do not force them into an overly uniform interface that hides important differences.\n- **Losing i18n coverage**: When moving user-facing code between files, ensure translation key usage (`t('key')`) follows the code. Orphaned translation keys and missing translations are hard to detect.\n- **Scaffold version compatibility**: The codebase supports both v1 and v2 frontmatter. Refactoring frontmatter handling must preserve backward compatibility -- users have existing `.context/` directories with v1 files.\n- **Large PRs**: Resist the temptation to refactor everything at once. Each refactoring PR should touch one concern (e.g., \"extract command registration from index.ts\" or \"consolidate glob pattern handling\"). This makes review feasible and rollback safe.\n"
  },
  {
    "path": ".context/agents/test-writer.md",
    "content": "---\ntype: agent\nname: test-writer\ndescription: Write comprehensive unit and integration tests\nrole: qa\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Test Writer\n\n## Role\n\nWrite and maintain unit and integration tests for the dotcontext project using Jest with ts-jest. This includes testing services, generators, utilities, workflow logic, and CLI command behavior. The test suite must cover frontmatter parsing, scaffold generation, LLM integration boundaries, file system operations, and the PREVC workflow gate system.\n\n## Key Files to Understand\n\n- `jest.config.js` -- Jest configuration: `ts-jest` preset, roots in `src/`, test patterns `**/__tests__/**/*.ts` and `**/?(*.)+(spec|test).ts`, coverage from `src/**/*.ts` excluding `.d.ts` and `index.ts` files.\n- `tsconfig.json` -- TypeScript strict mode, target ES2020, commonjs modules. Tests are excluded from compilation (`**/*.test.ts` in exclude array) but ts-jest handles them at runtime.\n- `src/utils/frontMatter.test.ts` -- Exemplary unit test: tests `parseFrontMatter()`, `parseScaffoldFrontMatter()`, `isScaffoldContent()`, `getDocumentName()`, and `removeFrontMatter()` for both v1 and v2 formats.\n- `src/utils/contentSanitizer.test.ts` -- Tests the `sanitizeAIResponse()` function that cleans LLM output. Good example of edge-case-driven testing.\n- `src/utils/versionChecker.test.ts` -- Tests the npm version checking utility with mocked HTTP calls.\n- `src/utils/promptLoader.test.ts` -- Tests prompt file loading and resolution.\n- `src/generators/agents/agentGenerator.test.ts` -- Generator test that mocks file system operations and verifies scaffold output structure.\n- `src/generators/plans/planGenerator.test.ts` -- Plan generator test with similar mocking patterns.\n- `src/generators/documentation/documentationGenerator.test.ts` -- Documentation generator test.\n- `src/workflow/gates/gateChecker.test.ts` -- Tests PREVC phase gate validation logic. Good example of testing state machine transitions.\n- `src/services/semantic/codebaseAnalyzer.test.ts` -- Tests tree-sitter integration with actual parsing. Demonstrates handling optional native dependencies in tests.\n- `src/services/shared/__tests__/contextRootResolver.test.ts` -- Service test in `__tests__/` directory structure.\n- `src/cli.test.ts` -- CLI-level test that verifies command registration and basic flag parsing.\n- `src/services/mcp/mcpServer.test.ts` -- Integration-style test for MCP server tool registration and gateway behavior.\n\n## Workflow Steps\n\n1. **Identify untested code**: Run `npm test -- --coverage` to generate a coverage report. Look at `coverage/lcov-report/index.html` for visual coverage gaps. Priority areas with low coverage:\n   - Service `run()` methods (complex orchestration logic)\n   - MCP gateway handlers and scaffold tools\n   - Workflow orchestration and status management\n   - CLI command handlers in `src/index.ts`\n\n2. **Choose test location**: Use one of two patterns:\n   - Co-located: `src/services/mcp/mcpInstallService.test.ts` (next to the source file)\n   - Directory: `src/services/shared/__tests__/contextRootResolver.test.ts` (in `__tests__/` subdirectory)\n\n   Both patterns are matched by Jest config. Prefer co-located for single-file tests and `__tests__/` for multi-file test suites.\n\n3. **Set up mocks**: Common mocking patterns in this codebase:\n\n   **CLIInterface mock**:\n   ```typescript\n   const mockUI = {\n     displayWelcome: jest.fn(),\n     startSpinner: jest.fn(),\n     stopSpinner: jest.fn(),\n     displaySuccess: jest.fn(),\n     displayError: jest.fn(),\n     displayInfo: jest.fn(),\n     displayWarning: jest.fn(),\n   } as unknown as CLIInterface;\n   ```\n\n   **TranslateFn mock**:\n   ```typescript\n   const mockT: TranslateFn = ((key: string) => key) as TranslateFn;\n   ```\n\n   **fs-extra mock**:\n   ```typescript\n   jest.mock('fs-extra', () => ({\n     ensureDir: jest.fn(),\n     writeFile: jest.fn(),\n     readFile: jest.fn(),\n     pathExists: jest.fn().mockResolvedValue(true),\n     readdir: jest.fn().mockResolvedValue([]),\n   }));\n   ```\n\n   **LLM client mock** (for AI service tests):\n   ```typescript\n   jest.mock('ai', () => ({\n     generateText: jest.fn().mockResolvedValue({ text: 'mocked response', steps: [] }),\n     generateObject: jest.fn().mockResolvedValue({ object: {} }),\n   }));\n   ```\n\n4. **Write the test**: Follow the Arrange-Act-Assert pattern. Group related tests with `describe()` blocks matching the class or function name. Use descriptive `it()` strings that state the expected behavior.\n\n5. **Test frontmatter handling**: For any test involving `.context/` files, include both v1 and v2 frontmatter cases:\n   ```typescript\n   // v1 frontmatter\n   const v1Content = '---\\nstatus: unfilled\\ngenerated: 2026-01-01\\n---\\n# Content';\n\n   // v2 scaffold frontmatter\n   const v2Content = '---\\ntype: doc\\nname: overview\\ndescription: Project overview\\ngenerated: 2026-01-01\\nstatus: unfilled\\nscaffoldVersion: \"2.0.0\"\\n---\\n# Content';\n   ```\n\n6. **Run and verify**: Execute `npm test` for the full suite or `npm test -- --testPathPattern=<pattern>` for specific tests. Check that coverage does not regress.\n\n## Best Practices\n\n- **Mock at boundaries, not internals**: Mock `fs-extra`, `glob`, HTTP clients, and the AI SDK. Do not mock internal service methods -- test them through the public API.\n- **Test the service contract**: Services accept flags and produce side effects (file writes, console output). Test that given specific flags, the correct files are written with correct content and the UI methods are called in the right order.\n- **Use `jest.spyOn()` for partial mocking**: When testing a service that calls another service, spy on the dependent service's methods rather than replacing the entire module.\n- **Handle optional dependencies**: `tree-sitter` is optional. Tests for `CodebaseAnalyzer` should have a setup that skips tests if tree-sitter is not installed:\n   ```typescript\n   let treeSitterAvailable = true;\n   try { require('tree-sitter'); } catch { treeSitterAvailable = false; }\n   const describeIfTreeSitter = treeSitterAvailable ? describe : describe.skip;\n   ```\n- **Test i18n key usage**: Verify that service code calls `t()` with valid keys by using the mock `TranslateFn` that returns the key string, then asserting the key was passed to UI methods.\n\n## Common Pitfalls\n\n- **Forgetting `await` in tests**: Many service methods are async. Forgetting `await` on the method call causes the test to pass before assertions are evaluated. Always `await` async operations and use `expect(...).resolves` or `expect(...).rejects` for promise assertions.\n- **File system state leakage**: Tests that write to actual files or temp directories must clean up in `afterEach()` or `afterAll()`. Use `os.tmpdir()` and unique directory names.\n- **Mocking module-level constants**: Some modules export constants at the module level (e.g., `AGENT_TYPES`, `PREVC_PHASE_ORDER`). These cannot be mocked with `jest.mock()` easily. Import them directly and test code that uses them.\n- **Snapshot fragility**: Avoid snapshot tests for generated markdown content -- they break on any formatting change. Instead, assert on structural properties (frontmatter fields present, section headings exist, file count correct).\n- **Testing LLM output**: Never test actual LLM responses in unit tests. Mock the AI SDK and test that the correct prompts and parameters are passed. Integration tests with real LLM calls should be separate and marked with a custom Jest tag or environment variable check.\n"
  },
  {
    "path": ".context/docs/README.md",
    "content": "# Documentation Index\n\nWelcome to the `@dotcontext/cli` repository knowledge base. Start with the project overview, then dive into specific guides as needed.\n\n## Core Guides\n\n| Guide | File | Primary Inputs |\n| --- | --- | --- |\n| Project Overview | [`project-overview.md`](./project-overview.md) | Roadmap, README, stakeholder notes |\n| Development Workflow | [`development-workflow.md`](./development-workflow.md) | Branching rules, CI config, contributing guide |\n| Testing Strategy | [`testing-strategy.md`](./testing-strategy.md) | Test configs, CI gates, known flaky suites |\n| Tooling & Productivity | [`tooling.md`](./tooling.md) | CLI scripts, IDE configs, automation workflows |\n| Harness Split Foundation | [`harness-split-foundation.md`](./harness-split-foundation.md) | Branch changes, package boundary decisions, harness engineering alignment |\n| Harness Roadmap | [`harness-roadmap.md`](./harness-roadmap.md) | Harness engineering capabilities, sequencing, and product direction |\n\n## Q&A\n\nFrequently asked questions are organized by topic in the [`qa/`](./qa/) directory.\nSee [qa/README.md](./qa/README.md) for the full index.\n\n## Codebase Map\n\nMachine-readable project structure and stack metadata: [`codebase-map.json`](./codebase-map.json)\n\n## Repository Snapshot\n\n```\nAGENTS.md\nCHANGELOG.md\nCLAUDE.md\nCONTRIBUTING.md\nLICENSE\nREADME.md\ndocs/             -- Published documentation produced by this tool\nexample-documentation.ts\njest.config.js\npackage.json\npackage-lock.json\nprompts/          -- Prompt templates (update_plan_prompt.md, update_scaffold_prompt.md)\nscripts/          -- Build and test helper scripts\nsrc/              -- TypeScript source (~240 files): CLI entrypoint, services, generators, utilities\ntsconfig.json\n```\n"
  },
  {
    "path": ".context/docs/codebase-map.json",
    "content": "{\n  \"version\": \"1.0.0\",\n  \"generated\": \"2026-03-18T21:32:49.263Z\",\n  \"stack\": {\n    \"primaryLanguage\": \"typescript\",\n    \"languages\": [\n      \"javascript\",\n      \"typescript\"\n    ],\n    \"frameworks\": [],\n    \"buildTools\": [],\n    \"testFrameworks\": [\n      \"jest\"\n    ],\n    \"packageManager\": \"npm\",\n    \"isMonorepo\": false,\n    \"hasDocker\": false,\n    \"hasCI\": true,\n    \"nodeVersion\": \">=20.0.0\",\n    \"runtimeEnvironment\": \"node\"\n  },\n  \"structure\": {\n    \"totalFiles\": 255,\n    \"rootPath\": \".\",\n    \"topDirectories\": [\n      {\n        \"name\": \"docs\",\n        \"fileCount\": 1,\n        \"description\": \"Documentation files\"\n      },\n      {\n        \"name\": \"prompts\",\n        \"fileCount\": 2,\n        \"description\": \"Prompt templates\"\n      },\n      {\n        \"name\": \"scripts\",\n        \"fileCount\": 1,\n        \"description\": \"Build and utility scripts\"\n      },\n      {\n        \"name\": \"src\",\n        \"fileCount\": 240,\n        \"description\": \"Source code root\"\n      }\n    ],\n    \"languageDistribution\": [\n      {\n        \"extension\": \".ts\",\n        \"count\": 240\n      },\n      {\n        \"extension\": \".md\",\n        \"count\": 9\n      },\n      {\n        \"extension\": \".json\",\n        \"count\": 3\n      },\n      {\n        \"extension\": \".js\",\n        \"count\": 2\n      }\n    ]\n  },\n  \"architecture\": {\n    \"layers\": [\n      {\n        \"name\": \"Config\",\n        \"description\": \"Configuration and constants\",\n        \"directories\": [\n          \"src/workflow\",\n          \".\"\n        ],\n        \"symbolCount\": 4,\n        \"dependsOn\": []\n      },\n      {\n        \"name\": \"Utils\",\n        \"description\": \"Shared utilities and helpers\",\n        \"directories\": [\n          \"src/utils\",\n          \"src/utils/prompts\",\n          \"src/generators/shared\",\n          \"src/generators/shared/structures\",\n          \"src/generators/documentation/templates\",\n          \"src/generators/shared/structures/skills\",\n          \"src/generators/shared/structures/plans\",\n          \"src/generators/shared/structures/documentation\",\n          \"src/generators/shared/structures/agents\"\n        ],\n        \"symbolCount\": 84,\n        \"dependsOn\": [\n          \"Services\",\n          \"Generators\"\n        ]\n      },\n      {\n        \"name\": \"Services\",\n        \"description\": \"Business logic and orchestration\",\n        \"directories\": [\n          \"src/utils\",\n          \"src/services\",\n          \"src/services/workflow\",\n          \"src/services/update\",\n          \"src/services/sync\",\n          \"src/services/state\",\n          \"src/services/start\",\n          \"src/services/stack\",\n          \"src/services/shared\",\n          \"src/services/serve\",\n          \"src/services/semantic\",\n          \"src/services/reverseSync\",\n          \"src/services/report\",\n          \"src/services/quickSync\",\n          \"src/services/qa\",\n          \"src/services/plan\",\n          \"src/services/passthrough\",\n          \"src/services/mcp\",\n          \"src/services/init\",\n          \"src/services/import\",\n          \"src/services/fill\",\n          \"src/services/export\",\n          \"src/services/autoFill\",\n          \"src/services/ai\",\n          \"src/services/shared/__tests__\",\n          \"src/services/semantic/treeSitter\",\n          \"src/services/semantic/lsp\",\n          \"src/services/mcp/gateway\",\n          \"src/services/ai/tools\",\n          \"src/services/ai/prompts\",\n          \"src/services/ai/agents\"\n        ],\n        \"symbolCount\": 475,\n        \"dependsOn\": [\n          \"Config\",\n          \"Generators\",\n          \"Utils\"\n        ]\n      },\n      {\n        \"name\": \"Generators\",\n        \"description\": \"Content and object generation\",\n        \"directories\": [\n          \"src/generators/skills\",\n          \"src/generators/plans\",\n          \"src/generators/documentation\",\n          \"src/generators/agents\",\n          \"src/generators/skills/templates\",\n          \"src/generators/plans/templates\",\n          \"src/generators/documentation/templates\",\n          \"src/generators/agents/templates\"\n        ],\n        \"symbolCount\": 51,\n        \"dependsOn\": [\n          \"Config\",\n          \"Utils\",\n          \"Services\"\n        ]\n      }\n    ],\n    \"patterns\": [\n      {\n        \"name\": \"Factory\",\n        \"confidence\": 0.9,\n        \"description\": \"Creates instances of related objects without specifying concrete classes\",\n        \"occurrences\": 1\n      },\n      {\n        \"name\": \"Service Layer\",\n        \"confidence\": 0.85,\n        \"description\": \"Encapsulates business logic in service classes\",\n        \"occurrences\": 22\n      },\n      {\n        \"name\": \"Builder\",\n        \"confidence\": 0.85,\n        \"description\": \"Separates object construction from its representation\",\n        \"occurrences\": 1\n      }\n    ],\n    \"entryPoints\": [\n      \"src/index.ts\",\n      \"src/workflow/index.ts\",\n      \"src/workflow/skills/index.ts\",\n      \"src/workflow/plans/index.ts\",\n      \"src/workflow/orchestration/index.ts\",\n      \"src/workflow/gates/index.ts\",\n      \"src/workflow/agents/index.ts\",\n      \"src/utils/prompts/index.ts\",\n      \"src/services/workflow/index.ts\",\n      \"src/services/update/index.ts\",\n      \"src/services/sync/index.ts\",\n      \"src/services/state/index.ts\",\n      \"src/services/start/index.ts\",\n      \"src/services/stack/index.ts\",\n      \"src/services/shared/index.ts\",\n      \"src/services/serve/index.ts\",\n      \"src/services/semantic/index.ts\",\n      \"src/services/reverseSync/index.ts\",\n      \"src/services/report/index.ts\",\n      \"src/services/quickSync/index.ts\",\n      \"src/services/qa/index.ts\",\n      \"src/services/passthrough/index.ts\",\n      \"src/services/mcp/index.ts\",\n      \"src/services/import/index.ts\",\n      \"src/services/export/index.ts\",\n      \"src/services/autoFill/index.ts\",\n      \"src/services/ai/index.ts\",\n      \"src/generators/skills/index.ts\",\n      \"src/generators/shared/index.ts\",\n      \"src/generators/plans/index.ts\",\n      \"src/generators/documentation/index.ts\",\n      \"src/generators/agents/index.ts\",\n      \"src/services/semantic/treeSitter/index.ts\",\n      \"src/services/semantic/lsp/index.ts\",\n      \"src/services/mcp/gateway/index.ts\",\n      \"src/services/ai/tools/index.ts\",\n      \"src/services/ai/prompts/index.ts\",\n      \"src/services/ai/agents/index.ts\",\n      \"src/generators/shared/structures/index.ts\",\n      \"src/generators/documentation/templates/index.ts\",\n      \"src/generators/agents/templates/index.ts\",\n      \"src/generators/shared/structures/skills/index.ts\",\n      \"src/generators/shared/structures/plans/index.ts\",\n      \"src/generators/shared/structures/documentation/index.ts\",\n      \"src/generators/shared/structures/agents/index.ts\"\n    ],\n    \"mainEntryPoints\": [\n      \"src/index.ts\"\n    ],\n    \"moduleExports\": [\n      \"src/workflow/index.ts\",\n      \"src/workflow/skills/index.ts\",\n      \"src/workflow/plans/index.ts\",\n      \"src/workflow/orchestration/index.ts\",\n      \"src/workflow/gates/index.ts\",\n      \"src/workflow/agents/index.ts\",\n      \"src/utils/prompts/index.ts\",\n      \"src/services/workflow/index.ts\",\n      \"src/services/update/index.ts\",\n      \"src/services/sync/index.ts\",\n      \"src/services/state/index.ts\",\n      \"src/services/start/index.ts\",\n      \"src/services/stack/index.ts\",\n      \"src/services/shared/index.ts\",\n      \"src/services/serve/index.ts\",\n      \"src/services/semantic/index.ts\",\n      \"src/services/reverseSync/index.ts\",\n      \"src/services/report/index.ts\",\n      \"src/services/quickSync/index.ts\",\n      \"src/services/qa/index.ts\",\n      \"src/services/passthrough/index.ts\",\n      \"src/services/mcp/index.ts\",\n      \"src/services/import/index.ts\",\n      \"src/services/export/index.ts\",\n      \"src/services/autoFill/index.ts\",\n      \"src/services/ai/index.ts\",\n      \"src/generators/skills/index.ts\",\n      \"src/generators/shared/index.ts\",\n      \"src/generators/plans/index.ts\",\n      \"src/generators/documentation/index.ts\",\n      \"src/generators/agents/index.ts\",\n      \"src/services/semantic/treeSitter/index.ts\",\n      \"src/services/semantic/lsp/index.ts\",\n      \"src/services/mcp/gateway/index.ts\",\n      \"src/services/ai/tools/index.ts\",\n      \"src/services/ai/prompts/index.ts\",\n      \"src/services/ai/agents/index.ts\",\n      \"src/generators/shared/structures/index.ts\",\n      \"src/generators/documentation/templates/index.ts\",\n      \"src/generators/agents/templates/index.ts\",\n      \"src/generators/shared/structures/skills/index.ts\",\n      \"src/generators/shared/structures/plans/index.ts\",\n      \"src/generators/shared/structures/documentation/index.ts\",\n      \"src/generators/shared/structures/agents/index.ts\"\n    ]\n  },\n  \"symbols\": {\n    \"classes\": [\n      {\n        \"name\": \"AgentGenerator\",\n        \"kind\": \"class\",\n        \"file\": \"src/generators/agents/agentGenerator.ts\",\n        \"line\": 65,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentOrchestrator\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/orchestration/agentOrchestrator.ts\",\n        \"line\": 168,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentRegistry\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/agents/agentRegistry.ts\",\n        \"line\": 67,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentsDetector\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/import/agentsDetector.ts\",\n        \"line\": 7,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AISdkClient\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/ai/aiSdkClient.ts\",\n        \"line\": 8,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AutoAdvanceDetector\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/workflow/autoAdvance.ts\",\n        \"line\": 78,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AutoFillService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/autoFill/autoFillService.ts\",\n        \"line\": 26,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CLIInterface\",\n        \"kind\": \"class\",\n        \"file\": \"src/utils/cliUI.ts\",\n        \"line\": 15,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CodebaseMapGenerator\",\n        \"kind\": \"class\",\n        \"file\": \"src/generators/documentation/codebaseMapGenerator.ts\",\n        \"line\": 192,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CollaborationManager\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/collaboration.ts\",\n        \"line\": 325,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CollaborationSession\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/collaboration.ts\",\n        \"line\": 29,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CommandRouter\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/passthrough/commandRouter.ts\",\n        \"line\": 114,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextExportService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/export/contextExportService.ts\",\n        \"line\": 51,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextGenerator\",\n        \"kind\": \"class\",\n        \"file\": \"src/generators/shared/contextGenerator.ts\",\n        \"line\": 3,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DocumentationAgent\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/ai/agents/documentationAgent.ts\",\n        \"line\": 38,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DocumentationGenerator\",\n        \"kind\": \"class\",\n        \"file\": \"src/generators/documentation/documentationGenerator.ts\",\n        \"line\": 63,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DocumentLinker\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/orchestration/documentLinker.ts\",\n        \"line\": 150,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ExportRulesService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/export/exportRulesService.ts\",\n        \"line\": 93,\n        \"exported\": true\n      },\n      {\n        \"name\": \"FileMapper\",\n        \"kind\": \"class\",\n        \"file\": \"src/utils/fileMapper.ts\",\n        \"line\": 6,\n        \"exported\": true\n      },\n      {\n        \"name\": \"FillService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/fill/fillService.ts\",\n        \"line\": 92,\n        \"exported\": true\n      },\n      {\n        \"name\": \"GeneratorUtils\",\n        \"kind\": \"class\",\n        \"file\": \"src/generators/shared/generatorUtils.ts\",\n        \"line\": 6,\n        \"exported\": true\n      },\n      {\n        \"name\": \"GitService\",\n        \"kind\": \"class\",\n        \"file\": \"src/utils/gitService.ts\",\n        \"line\": 24,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ImportAgentsService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/import/importAgentsService.ts\",\n        \"line\": 16,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ImportRulesService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/import/importRulesService.ts\",\n        \"line\": 17,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ImportSkillsService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/reverseSync/importSkillsService.ts\",\n        \"line\": 41,\n        \"exported\": true\n      },\n      {\n        \"name\": \"InitService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/init/initService.ts\",\n        \"line\": 52,\n        \"exported\": true\n      },\n      {\n        \"name\": \"LLMClientFactory\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/llmClientFactory.ts\",\n        \"line\": 12,\n        \"exported\": true\n      },\n      {\n        \"name\": \"LSPLayer\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/semantic/lsp/lspLayer.ts\",\n        \"line\": 53,\n        \"exported\": true\n      },\n      {\n        \"name\": \"MCPInstallService\",\n        \"kind\": \"class\",\n        \"file\": \"src/services/mcp/mcpInstallService.ts\",\n        \"line\": 416,\n        \"exported\": true\n      },\n      {\n        \"name\": \"NoPlanToApproveError\",\n        \"kind\": \"class\",\n        \"file\": \"src/workflow/errors.ts\",\n        \"line\": 47,\n        \"exported\": true\n      }\n    ],\n    \"interfaces\": [\n      {\n        \"name\": \"AffectedDoc\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/update/updateService.ts\",\n        \"line\": 31,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentCompleteEvent\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"line\": 31,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentDefaultContent\",\n        \"kind\": \"interface\",\n        \"file\": \"src/generators/shared/structures/agents/factory.ts\",\n        \"line\": 10,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentEventCallbacks\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"line\": 37,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentFileInfo\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/sync/types.ts\",\n        \"line\": 47,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentLineupEntry\",\n        \"kind\": \"interface\",\n        \"file\": \"src/workflow/plans/types.ts\",\n        \"line\": 137,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentMetadata\",\n        \"kind\": \"interface\",\n        \"file\": \"src/workflow/agents/agentRegistry.ts\",\n        \"line\": 36,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentOptions\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/mcp/gateway/agent.ts\",\n        \"line\": 23,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentParams\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/mcp/gateway/types.ts\",\n        \"line\": 113,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentPrompt\",\n        \"kind\": \"interface\",\n        \"file\": \"src/types.ts\",\n        \"line\": 48,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentScaffoldFrontmatter\",\n        \"kind\": \"interface\",\n        \"file\": \"src/types/scaffoldFrontmatter.ts\",\n        \"line\": 51,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentSequenceStep\",\n        \"kind\": \"interface\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 320,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentStartEvent\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"line\": 7,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentStatus\",\n        \"kind\": \"interface\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 114,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentStepEvent\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"line\": 12,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentTemplateContext\",\n        \"kind\": \"interface\",\n        \"file\": \"src/generators/agents/templates/types.ts\",\n        \"line\": 16,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentUpdate\",\n        \"kind\": \"interface\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 281,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AIDependencies\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/shared/types.ts\",\n        \"line\": 23,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AnalysisOptions\",\n        \"kind\": \"interface\",\n        \"file\": \"src/utils/prompts/types.ts\",\n        \"line\": 38,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AnalyzerOptions\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/semantic/types.ts\",\n        \"line\": 187,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ArchitectureInfo\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/semantic/types.ts\",\n        \"line\": 147,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ArchitectureLayer\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/semantic/types.ts\",\n        \"line\": 60,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AutoAdvanceResult\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/workflow/autoAdvance.ts\",\n        \"line\": 16,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AutoFillContext\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/autoFill/autoFillService.ts\",\n        \"line\": 14,\n        \"exported\": true\n      },\n      {\n        \"name\": \"BaseDependencies\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/shared/types.ts\",\n        \"line\": 14,\n        \"exported\": true\n      },\n      {\n        \"name\": \"BaseScaffoldFrontmatter\",\n        \"kind\": \"interface\",\n        \"file\": \"src/types/scaffoldFrontmatter.ts\",\n        \"line\": 24,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CapabilitiesData\",\n        \"kind\": \"interface\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 198,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CLIOptions\",\n        \"kind\": \"interface\",\n        \"file\": \"src/types.ts\",\n        \"line\": 34,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CodebaseMap\",\n        \"kind\": \"interface\",\n        \"file\": \"src/generators/documentation/codebaseMapGenerator.ts\",\n        \"line\": 40,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CodebaseMapOptions\",\n        \"kind\": \"interface\",\n        \"file\": \"src/generators/documentation/codebaseMapGenerator.ts\",\n        \"line\": 108,\n        \"exported\": true\n      }\n    ],\n    \"functions\": [\n      {\n        \"name\": \"addError\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/shared/types.ts\",\n        \"line\": 101,\n        \"exported\": true\n      },\n      {\n        \"name\": \"addFrontMatter\",\n        \"kind\": \"function\",\n        \"file\": \"src/utils/frontMatter.ts\",\n        \"line\": 124,\n        \"exported\": true\n      },\n      {\n        \"name\": \"buildDocumentMapTable\",\n        \"kind\": \"function\",\n        \"file\": \"src/generators/documentation/templates/common.ts\",\n        \"line\": 57,\n        \"exported\": true\n      },\n      {\n        \"name\": \"buildExtensionPattern\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/shared/globPatterns.ts\",\n        \"line\": 124,\n        \"exported\": true\n      },\n      {\n        \"name\": \"buildSymbolList\",\n        \"kind\": \"function\",\n        \"file\": \"src/generators/documentation/templates/common.ts\",\n        \"line\": 168,\n        \"exported\": true\n      },\n      {\n        \"name\": \"buildSymbolTable\",\n        \"kind\": \"function\",\n        \"file\": \"src/generators/documentation/templates/common.ts\",\n        \"line\": 125,\n        \"exported\": true\n      },\n      {\n        \"name\": \"checkForUpdates\",\n        \"kind\": \"function\",\n        \"file\": \"src/utils/versionChecker.ts\",\n        \"line\": 31,\n        \"exported\": true\n      },\n      {\n        \"name\": \"checkSymlinkSupport\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/sync/symlinkHandler.ts\",\n        \"line\": 83,\n        \"exported\": true\n      },\n      {\n        \"name\": \"classifyProject\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/stack/projectTypeClassifier.ts\",\n        \"line\": 122,\n        \"exported\": true\n      },\n      {\n        \"name\": \"cleanupSharedContext\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/ai/tools/fillScaffoldingTool.ts\",\n        \"line\": 56,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createAgentFrontmatter\",\n        \"kind\": \"function\",\n        \"file\": \"src/types/scaffoldFrontmatter.ts\",\n        \"line\": 150,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createAgentRegistry\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/agents/agentRegistry.ts\",\n        \"line\": 220,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createAgentStructure\",\n        \"kind\": \"function\",\n        \"file\": \"src/generators/shared/structures/agents/factory.ts\",\n        \"line\": 44,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createBox\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/shared/uiHelpers.ts\",\n        \"line\": 189,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createDocFrontmatter\",\n        \"kind\": \"function\",\n        \"file\": \"src/types/scaffoldFrontmatter.ts\",\n        \"line\": 131,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createEmptyResult\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/shared/types.ts\",\n        \"line\": 74,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createErrorResponse\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 218,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createErrorResponse\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/mcp/gateway/response.ts\",\n        \"line\": 39,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createGateChecker\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/gates/gateChecker.ts\",\n        \"line\": 252,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createInitialStatus\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/status/templates.ts\",\n        \"line\": 97,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createJsonResponse\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/mcp/gateway/response.ts\",\n        \"line\": 27,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createLargeProjectStatus\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/status/templates.ts\",\n        \"line\": 192,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createMarkdownReferences\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/sync/markdownReferenceHandler.ts\",\n        \"line\": 5,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createMediumProjectStatus\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/status/templates.ts\",\n        \"line\": 180,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createPlanFrontmatter\",\n        \"kind\": \"function\",\n        \"file\": \"src/types/scaffoldFrontmatter.ts\",\n        \"line\": 198,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createProgressNotification\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 234,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createProvider\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/ai/providerFactory.ts\",\n        \"line\": 40,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createQuickFlowStatus\",\n        \"kind\": \"function\",\n        \"file\": \"src/workflow/status/templates.ts\",\n        \"line\": 156,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createQuickSyncService\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/quickSync/quickSyncService.ts\",\n        \"line\": 300,\n        \"exported\": true\n      },\n      {\n        \"name\": \"createReverseQuickSyncService\",\n        \"kind\": \"function\",\n        \"file\": \"src/services/reverseSync/reverseQuickSyncService.ts\",\n        \"line\": 288,\n        \"exported\": true\n      }\n    ],\n    \"types\": [\n      {\n        \"name\": \"AgentAction\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/mcp/gateway/types.ts\",\n        \"line\": 20,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentPlaybook\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 439,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentRunRequest\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 87,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentType\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/orchestration/agentOrchestrator.ts\",\n        \"line\": 31,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentType\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"line\": 5,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AgentType\",\n        \"kind\": \"type\",\n        \"file\": \"src/generators/agents/agentTypes.ts\",\n        \"line\": 18,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AIProvider\",\n        \"kind\": \"type\",\n        \"file\": \"src/types.ts\",\n        \"line\": 25,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AnalyzeSymbolsInput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 430,\n        \"exported\": true\n      },\n      {\n        \"name\": \"AnalyzeSymbolsOutput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 431,\n        \"exported\": true\n      },\n      {\n        \"name\": \"BaseRequest\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 19,\n        \"exported\": true\n      },\n      {\n        \"name\": \"BuiltInAgentType\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/agents/agentRegistry.ts\",\n        \"line\": 31,\n        \"exported\": true\n      },\n      {\n        \"name\": \"BuiltInSkillType\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/skills/types.ts\",\n        \"line\": 81,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CapabilitiesRequest\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 98,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CheckScaffoldingInput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 420,\n        \"exported\": true\n      },\n      {\n        \"name\": \"CheckScaffoldingOutput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 421,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextAction\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/mcp/gateway/types.ts\",\n        \"line\": 17,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextBuildRequest\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 65,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextExportServiceDependencies\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/export/contextExportService.ts\",\n        \"line\": 19,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ContextFormat\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/semantic/contextBuilder.ts\",\n        \"line\": 30,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DevelopmentPlan\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 440,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DocType\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/orchestration/documentLinker.ts\",\n        \"line\": 14,\n        \"exported\": true\n      },\n      {\n        \"name\": \"DocumentationOutput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"line\": 438,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ErrorCode\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/passthrough/protocol.ts\",\n        \"line\": 284,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ExecutionAction\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 166,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ExploreAction\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/mcp/gateway/types.ts\",\n        \"line\": 16,\n        \"exported\": true\n      },\n      {\n        \"name\": \"ExportRulesServiceDependencies\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/export/exportRulesService.ts\",\n        \"line\": 24,\n        \"exported\": true\n      },\n      {\n        \"name\": \"FillScaffoldingInput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/tools/fillScaffoldingTool.ts\",\n        \"line\": 265,\n        \"exported\": true\n      },\n      {\n        \"name\": \"FillSingleFileInput\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/ai/tools/fillScaffoldingTool.ts\",\n        \"line\": 177,\n        \"exported\": true\n      },\n      {\n        \"name\": \"FunctionalPatternType\",\n        \"kind\": \"type\",\n        \"file\": \"src/services/semantic/types.ts\",\n        \"line\": 79,\n        \"exported\": true\n      },\n      {\n        \"name\": \"GateType\",\n        \"kind\": \"type\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 145,\n        \"exported\": true\n      }\n    ],\n    \"enums\": [\n      {\n        \"name\": \"ProjectScale\",\n        \"kind\": \"enum\",\n        \"file\": \"src/workflow/types.ts\",\n        \"line\": 33,\n        \"exported\": true\n      }\n    ]\n  },\n  \"publicAPI\": [\n    {\n      \"name\": \"addError\",\n      \"kind\": \"function\",\n      \"file\": \"src/services/shared/types.ts\",\n      \"line\": 101,\n      \"exported\": true\n    },\n    {\n      \"name\": \"addFrontMatter\",\n      \"kind\": \"function\",\n      \"file\": \"src/utils/frontMatter.ts\",\n      \"line\": 124,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AffectedDoc\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/update/updateService.ts\",\n      \"line\": 31,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentAction\",\n      \"kind\": \"type\",\n      \"file\": \"src/services/mcp/gateway/types.ts\",\n      \"line\": 20,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentCompleteEvent\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/ai/agentEvents.ts\",\n      \"line\": 31,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentDefaultContent\",\n      \"kind\": \"interface\",\n      \"file\": \"src/generators/shared/structures/agents/factory.ts\",\n      \"line\": 10,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentEventCallbacks\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/ai/agentEvents.ts\",\n      \"line\": 37,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentFileInfo\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/sync/types.ts\",\n      \"line\": 47,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentGenerator\",\n      \"kind\": \"class\",\n      \"file\": \"src/generators/agents/agentGenerator.ts\",\n      \"line\": 65,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentLineupEntry\",\n      \"kind\": \"interface\",\n      \"file\": \"src/workflow/plans/types.ts\",\n      \"line\": 137,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentMetadata\",\n      \"kind\": \"interface\",\n      \"file\": \"src/workflow/agents/agentRegistry.ts\",\n      \"line\": 36,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentOptions\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/mcp/gateway/agent.ts\",\n      \"line\": 23,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentOrchestrator\",\n      \"kind\": \"class\",\n      \"file\": \"src/workflow/orchestration/agentOrchestrator.ts\",\n      \"line\": 168,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentParams\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/mcp/gateway/types.ts\",\n      \"line\": 113,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentPlaybook\",\n      \"kind\": \"type\",\n      \"file\": \"src/services/ai/schemas.ts\",\n      \"line\": 439,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentPrompt\",\n      \"kind\": \"interface\",\n      \"file\": \"src/types.ts\",\n      \"line\": 48,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentRegistry\",\n      \"kind\": \"class\",\n      \"file\": \"src/workflow/agents/agentRegistry.ts\",\n      \"line\": 67,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentRunRequest\",\n      \"kind\": \"type\",\n      \"file\": \"src/services/passthrough/protocol.ts\",\n      \"line\": 87,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentScaffoldFrontmatter\",\n      \"kind\": \"interface\",\n      \"file\": \"src/types/scaffoldFrontmatter.ts\",\n      \"line\": 51,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentsDetector\",\n      \"kind\": \"class\",\n      \"file\": \"src/services/import/agentsDetector.ts\",\n      \"line\": 7,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentSequenceStep\",\n      \"kind\": \"interface\",\n      \"file\": \"src/workflow/types.ts\",\n      \"line\": 320,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentStartEvent\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/ai/agentEvents.ts\",\n      \"line\": 7,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentStatus\",\n      \"kind\": \"interface\",\n      \"file\": \"src/workflow/types.ts\",\n      \"line\": 114,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentStepEvent\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/ai/agentEvents.ts\",\n      \"line\": 12,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentTemplateContext\",\n      \"kind\": \"interface\",\n      \"file\": \"src/generators/agents/templates/types.ts\",\n      \"line\": 16,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentType\",\n      \"kind\": \"type\",\n      \"file\": \"src/workflow/orchestration/agentOrchestrator.ts\",\n      \"line\": 31,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentType\",\n      \"kind\": \"type\",\n      \"file\": \"src/services/ai/agentEvents.ts\",\n      \"line\": 5,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentType\",\n      \"kind\": \"type\",\n      \"file\": \"src/generators/agents/agentTypes.ts\",\n      \"line\": 18,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AgentUpdate\",\n      \"kind\": \"interface\",\n      \"file\": \"src/workflow/types.ts\",\n      \"line\": 281,\n      \"exported\": true\n    },\n    {\n      \"name\": \"AIDependencies\",\n      \"kind\": \"interface\",\n      \"file\": \"src/services/shared/types.ts\",\n      \"line\": 23,\n      \"exported\": true\n    }\n  ],\n  \"dependencies\": {\n    \"mostImported\": [\n      {\n        \"file\": \"src/types.ts\",\n        \"importedBy\": 26,\n        \"description\": \"Type definitions\"\n      },\n      {\n        \"file\": \"src/utils/i18n.ts\",\n        \"importedBy\": 25,\n        \"description\": \"Shared utilities and helpers\"\n      },\n      {\n        \"file\": \"src/workflow/types.ts\",\n        \"importedBy\": 21,\n        \"description\": \"Type definitions\"\n      },\n      {\n        \"file\": \"src/utils/cliUI.ts\",\n        \"importedBy\": 20,\n        \"description\": \"Shared utilities and helpers\"\n      },\n      {\n        \"file\": \"src/services/mcp/gateway/response.ts\",\n        \"importedBy\": 20\n      },\n      {\n        \"file\": \"src/generators/shared/structures/types.ts\",\n        \"importedBy\": 19,\n        \"description\": \"Type definitions\"\n      },\n      {\n        \"file\": \"src/services/ai/schemas.ts\",\n        \"importedBy\": 16,\n        \"description\": \"AI/LLM integration\"\n      },\n      {\n        \"file\": \"src/utils/theme.ts\",\n        \"importedBy\": 15,\n        \"description\": \"Shared utilities and helpers\"\n      },\n      {\n        \"file\": \"src/workflow/index.ts\",\n        \"importedBy\": 13,\n        \"description\": \"workflow module exports\"\n      },\n      {\n        \"file\": \"src/services/ai/agentEvents.ts\",\n        \"importedBy\": 12,\n        \"description\": \"AI/LLM integration\"\n      },\n      {\n        \"file\": \"src/services/semantic/index.ts\",\n        \"importedBy\": 12,\n        \"description\": \"semantic module exports\"\n      },\n      {\n        \"file\": \"src/services/semantic/types.ts\",\n        \"importedBy\": 11,\n        \"description\": \"Type definitions\"\n      },\n      {\n        \"file\": \"src/generators/agents/agentTypes.ts\",\n        \"importedBy\": 10,\n        \"description\": \"AI agent definitions\"\n      },\n      {\n        \"file\": \"src/services/ai/tools/index.ts\",\n        \"importedBy\": 9,\n        \"description\": \"Module exports\"\n      },\n      {\n        \"file\": \"src/services/shared/index.ts\",\n        \"importedBy\": 9,\n        \"description\": \"shared module exports\"\n      },\n      {\n        \"file\": \"src/services/import/types.ts\",\n        \"importedBy\": 9,\n        \"description\": \"Type definitions\"\n      },\n      {\n        \"file\": \"src/services/ai/providerFactory.ts\",\n        \"importedBy\": 8,\n        \"description\": \"AI/LLM integration\"\n      },\n      {\n        \"file\": \"src/utils/fileMapper.ts\",\n        \"importedBy\": 7,\n        \"description\": \"Shared utilities and helpers\"\n      },\n      {\n        \"file\": \"src/workflow/skills/index.ts\",\n        \"importedBy\": 7,\n        \"description\": \"skills module exports\"\n      },\n      {\n        \"file\": \"src/services/workflow/index.ts\",\n        \"importedBy\": 7,\n        \"description\": \"workflow module exports\"\n      }\n    ]\n  },\n  \"stats\": {\n    \"totalSymbols\": 773,\n    \"exportedSymbols\": 650,\n    \"analysisTimeMs\": 1165\n  },\n  \"keyFiles\": [\n    {\n      \"path\": \"src/index.ts\",\n      \"description\": \"Main library entry point\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/agents/index.ts\",\n      \"description\": \"agents module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/gates/index.ts\",\n      \"description\": \"Module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/index.ts\",\n      \"description\": \"workflow module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/orchestration/index.ts\",\n      \"description\": \"Module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/plans/index.ts\",\n      \"description\": \"plans module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"src/workflow/skills/index.ts\",\n      \"description\": \"skills module exports\",\n      \"category\": \"entrypoint\"\n    },\n    {\n      \"path\": \"jest.config.js\",\n      \"description\": \"Tool configuration\",\n      \"category\": \"config\"\n    },\n    {\n      \"path\": \"package.json\",\n      \"description\": \"Package manifest\",\n      \"category\": \"config\"\n    },\n    {\n      \"path\": \"tsconfig.json\",\n      \"description\": \"TypeScript configuration\",\n      \"category\": \"config\"\n    },\n    {\n      \"path\": \"src/generators/agents/templates/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/generators/documentation/templates/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/generators/plans/templates/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/generators/shared/structures/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/import/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/mcp/gateway/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/reverseSync/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/semantic/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/shared/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/sync/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/utils/prompts/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/workflow/plans/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/workflow/skills/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/workflow/types.ts\",\n      \"description\": \"Type definitions\",\n      \"category\": \"types\"\n    },\n    {\n      \"path\": \"src/services/ai/agents/index.ts\",\n      \"description\": \"agents module exports\",\n      \"category\": \"service\"\n    },\n    {\n      \"path\": \"src/services/ai/index.ts\",\n      \"description\": \"ai module exports\",\n      \"category\": \"service\"\n    },\n    {\n      \"path\": \"src/services/ai/prompts/index.ts\",\n      \"description\": \"prompts module exports\",\n      \"category\": \"service\"\n    },\n    {\n      \"path\": \"src/services/ai/tools/index.ts\",\n      \"description\": \"Module exports\",\n      \"category\": \"service\"\n    },\n    {\n      \"path\": \"src/services/autoFill/autoFillService.ts\",\n      \"description\": \"Service class\",\n      \"category\": \"service\"\n    }\n  ],\n  \"navigation\": {\n    \"tests\": \"**/__tests__/**/*.ts\",\n    \"config\": [\n      \"package.json\",\n      \"tsconfig.json\",\n      \"jest.config.js\"\n    ],\n    \"types\": [\n      \"src/types.ts\",\n      \"src/workflow/types.ts\",\n      \"src/workflow/skills/types.ts\",\n      \"src/workflow/plans/types.ts\",\n      \"src/utils/prompts/types.ts\",\n      \"src/services/sync/types.ts\",\n      \"src/services/shared/types.ts\",\n      \"src/services/semantic/types.ts\",\n      \"src/services/reverseSync/types.ts\",\n      \"src/services/import/types.ts\"\n    ],\n    \"mainLogic\": [\n      \"src/services\"\n    ]\n  }\n}"
  },
  {
    "path": ".context/docs/development-workflow.md",
    "content": "---\ntype: doc\nname: development-workflow\ndescription: Day-to-day engineering processes, branching, and contribution guidelines\ncategory: workflow\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Development Workflow\n\nThis document covers the day-to-day engineering processes for contributing to `@dotcontext/cli`, including branching conventions, local development setup, release procedures, and code review expectations.\n\n## Branching & Releases\n\n### Branch naming\n\nAll work branches fork from `main`. Use descriptive prefixes:\n\n| Prefix        | Purpose                          | Example                              |\n| ------------- | -------------------------------- | ------------------------------------ |\n| `feature/`    | New functionality                | `feature/mcp-tool-support`           |\n| `fix/`        | Bug fixes                        | `fix/auto-fill`                      |\n| `chore/`      | Maintenance, docs, refactoring   | `chore/improve-documentation-for-usage` |\n| `release/`    | Release preparation              | `release/0.7.1`                      |\n\n### Merge strategy\n\n- Feature and fix branches merge into `main` via pull request.\n- Release branches are created from `main`, finalized with a version bump, then merged back.\n- Squash merges are preferred for feature branches to keep history clean.\n\n### Versioning\n\nThe project follows [semantic versioning](https://semver.org/):\n\n- **Patch** (0.7.x): bug fixes, documentation updates, non-breaking tweaks.\n- **Minor** (0.x.0): new features, new CLI commands, non-breaking additions.\n- **Major** (x.0.0): breaking changes to CLI interface, configuration format, or public APIs.\n\n### Release scripts\n\n```bash\nnpm run release          # Bump patch, build, publish to npm\nnpm run release:minor    # Bump minor, build, publish to npm\nnpm run release:major    # Bump major, build, publish to npm\n```\n\nEach release script runs `npm version <level>` (which triggers the `version` script to rebuild) followed by `npm publish --access public`. The `prepublishOnly` hook ensures a fresh build before every publish.\n\n### Changelog\n\nUpdate `CHANGELOG.md` before each release with a summary of changes, referencing relevant pull requests.\n\n## Local Development\n\n### Prerequisites\n\n- Node.js >= 20.0.0\n- npm (ships with Node)\n- Git\n\n### Setup\n\n```bash\ngit clone https://github.com/vinilana/ai-coders-context.git\ncd ai-coders-context\nnpm install\n```\n\n### Common commands\n\n| Command           | Description                                      |\n| ----------------- | ------------------------------------------------ |\n| `npm run dev`     | Run CLI from source using tsx (fast, no build step) |\n| `npm run build`   | Compile TypeScript to `dist/` via tsc             |\n| `npm start`       | Run the compiled CLI from `dist/index.js`         |\n| `npm test`        | Run the full Jest test suite                      |\n\n### Development loop\n\n1. Create a branch from `main` following the naming conventions above.\n2. Make changes in `src/`.\n3. Test interactively with `npm run dev -- <command>` (e.g., `npm run dev -- workflow --help`).\n4. Run `npm test` to verify nothing is broken.\n5. Run `npm run build` to confirm the TypeScript compiles cleanly.\n6. Commit, push, and open a pull request against `main`.\n\n### Environment variables\n\nThe CLI supports locale overrides via `DOTCONTEXT_LANG` (`en` or `pt-BR`).\n\nFor AI-generated filling or refresh, install MCP with `npx -y @dotcontext/cli@latest mcp:install` and use your connected AI tool instead of standalone CLI commands.\n\nUse a `.env` file in the project root (loaded via dotenv). Do not commit `.env` files.\n\n## Code Review Expectations\n\n### What reviewers look for\n\n- **Correctness**: Does the change do what it claims? Are edge cases handled?\n- **Type safety**: TypeScript strict mode is enabled. No `any` casts without justification.\n- **Test coverage**: New features should include tests. Bug fixes should include a regression test when feasible.\n- **CLI UX**: Interactive prompts (inquirer), spinners (ora), and colored output (chalk) should be consistent with existing patterns.\n- **i18n**: User-facing strings should go through the i18n utility (`src/utils/i18n.ts`) for both English and Portuguese (pt-BR).\n- **No secrets**: Ensure API keys, tokens, and credentials are never committed.\n\n### Pull request checklist\n\n- [ ] Branch is up to date with `main`\n- [ ] `npm test` passes locally\n- [ ] `npm run build` succeeds without errors\n- [ ] New/changed user-facing strings are internationalized\n- [ ] CHANGELOG entry drafted (for non-trivial changes)\n- [ ] PR description explains the \"why\" behind the change\n"
  },
  {
    "path": ".context/docs/harness-roadmap.md",
    "content": "---\ntype: doc\nname: harness-roadmap\ndescription: Product roadmap for evolving dotcontext into a harness engineering platform\ncategory: roadmap\ngenerated: 2026-04-11\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Harness Roadmap\n\nThis roadmap describes how dotcontext can evolve from its current architecture split into a full harness engineering platform.\n\nThe intended product shape is:\n\n- `dotcontext/cli` as the operator-facing interface\n- `dotcontext/harness` as the reusable runtime and domain layer\n- `dotcontext/mcp` as the Model Context Protocol adapter over the harness\n\nThe roadmap is organized by product capability, not by code folder, because harness engineering is ultimately about runtime control, feedback, and reliability.\n\n## Roadmap Principles\n\nThe roadmap assumes these design principles:\n\n- transport adapters should stay thinner than the harness runtime\n- runtime state should be explicit, inspectable, and durable\n- computational sensors should be preferred where possible\n- inferential controls should be added where deterministic checks are insufficient\n- every autonomous capability should have matching observability and back-pressure\n- PREVC should be treated as a strong default workflow, but not the only future execution model\n\n## Now\n\nThese are the next capabilities that should be built on top of the current architecture. They have the best ratio of product value to implementation risk.\n\n### 1. Session Runtime\n\nGoal: make long-running harness execution first-class.\n\nFeatures:\n\n- persistent session objects for agent runs\n- resumable execution with checkpoints\n- append-only event log per session\n- explicit run metadata: task, owner, repo, agents involved, timestamps\n- artifact store for outputs, handoffs, and evidence\n\nWhy now:\n\n- the architecture is already split into `cli`, `harness`, and `mcp`\n- session state is the foundation for evals, replay, approvals, and observability\n\n### 2. Sensors and Back-Pressure\n\nGoal: formalize the harness feedback layer.\n\nFeatures:\n\n- sensors as first-class runtime concepts\n- built-in computational sensors:\n  - build\n  - typecheck\n  - lint\n  - test\n  - structural constraints\n- sensor result model with pass/fail, severity, evidence, and timestamps\n- configurable back-pressure so critical sensor failures block completion\n- task completion rules based on sensor status instead of prompt-only conventions\n\nWhy now:\n\n- this is where harness engineering starts to become materially different from prompt engineering\n- it directly improves trust in agent output\n\n### 3. Execution Traces and Observability\n\nGoal: make agent behavior inspectable.\n\nFeatures:\n\n- trace timeline for tool calls, state transitions, retries, and failures\n- normalized event model inside `dotcontext/harness`\n- logs linked to session and task IDs\n- evidence records attached to plans, phases, and approvals\n- reporting views for failed runs and repeated failure classes\n\nWhy now:\n\n- once sessions and sensors exist, traces become the main debugging surface\n- this also creates the basis for future evaluation datasets\n\n### 4. Task Contracts\n\nGoal: replace vague agent assignments with explicit runtime contracts.\n\nFeatures:\n\n- structured task definition:\n  - goal\n  - inputs\n  - expected outputs\n  - acceptance criteria\n  - required sensors\n  - assigned agent type\n- handoff contracts between planner, executor, reviewer, and evaluator\n- artifact schema for task outputs\n\nWhy now:\n\n- this reduces ambiguity in both single-agent and multi-agent operation\n- it is the cleanest way to connect plans, workflow, sensors, and evidence\n\n## Next\n\nThese should follow once sessions, sensors, traces, and task contracts exist.\n\n### 5. Evaluator Layer\n\nGoal: separate execution from judgment.\n\nFeatures:\n\n- evaluator agent role as a first-class harness component\n- scorecards for:\n  - correctness\n  - architecture fit\n  - test adequacy\n  - docs adequacy\n  - policy compliance\n- evaluator loops for long-running tasks\n- configurable stopping conditions based on evaluation quality\n\nWhy next:\n\n- evaluator quality depends on session evidence and sensor results already existing\n\n### 6. Guides as Runtime Assets\n\nGoal: move guides from passive docs into active harness inputs.\n\nFeatures:\n\n- versioned guide assets\n- guide bundles by task type, stack, or repo topology\n- guide selection logic based on task contract and repo context\n- constraints tied to guides, not just narrative instructions\n- progressive disclosure of guides rather than monolithic injection\n\nWhy next:\n\n- the repo already has docs, skills, and playbooks\n- the next step is turning them into selected runtime assets with explicit effect\n\n### 7. Harness Templates\n\nGoal: package battle-tested harness configurations for common project types.\n\nFeatures:\n\n- templates for:\n  - TypeScript monorepo\n  - CRUD backend\n  - frontend app\n  - CLI tool\n  - event-driven worker\n- each template bundles:\n  - default guides\n  - default sensors\n  - recommended agent topology\n  - workflow defaults\n  - expected artifacts\n\nWhy next:\n\n- templates are the most scalable way to make harness engineering reusable\n\n### 8. Multi-Agent Topologies\n\nGoal: support controlled multi-agent execution as a native harness feature.\n\nFeatures:\n\n- planner / executor / reviewer / evaluator topology\n- agent-specific task contracts\n- isolated context windows by role\n- artifact-based handoffs\n- retry and escalation rules per role\n\nWhy next:\n\n- the current codebase already models agents and orchestration\n- after contracts and evidence exist, multi-agent operation becomes much safer to implement\n\n## Later\n\nThese are strategically important, but should come after the runtime core is stable.\n\n### 9. Policy and Governance Engine\n\nGoal: make operational rules enforceable at runtime.\n\nFeatures:\n\n- policy rules for tools, paths, and environments\n- approval requirements based on risk or scope\n- secret handling and restricted connector policies\n- role-based execution permissions\n- audit-grade event trail\n\n### 10. Replay and Evaluation Dataset Generation\n\nGoal: turn harness usage into reusable learning data.\n\nFeatures:\n\n- replay of full sessions\n- diff between expected and observed execution paths\n- failure clustering\n- datasets for prompt, workflow, and policy improvement\n- model comparison using the same harness trace corpus\n\n### 11. Workflow Engine Expansion\n\nGoal: make PREVC one execution strategy among several.\n\nFeatures:\n\n- PREVC as default workflow template\n- support for alternate engines:\n  - fast single-pass execution\n  - gated enterprise workflow\n  - long-running autonomous workflow\n  - review-heavy workflow\n- workflow selection by task type and risk profile\n\n### 12. Package Publication and Platform Distribution\n\nGoal: turn the internal split into a public package and release model.\n\nFeatures:\n\n- independent package publication for:\n  - `@dotcontext/cli`\n  - `@dotcontext/harness`\n  - `@dotcontext/mcp`\n- package-specific smoke tests\n- release channels and compatibility guarantees\n- future adapters beyond MCP, such as HTTP or embedded SDK execution\n\n## Suggested Sequencing\n\nIf the team wants a strict order, this is the most coherent path:\n\n1. sessions\n2. sensors\n3. traces\n4. task contracts\n5. evaluator layer\n6. guide assets\n7. harness templates\n8. multi-agent topologies\n9. policy engine\n10. replay and dataset generation\n11. workflow engine expansion\n12. independent package publication\n\n## Success Criteria By Stage\n\n### Stage 1 Success\n\nThe product can run a task with:\n\n- durable session state\n- explicit task contract\n- sensor-backed completion rules\n- inspectable trace output\n\n### Stage 2 Success\n\nThe product can run the same task with:\n\n- selected guides\n- evaluator feedback\n- template-driven defaults\n- multi-agent handoffs with evidence\n\n### Stage 3 Success\n\nThe product can operate in production with:\n\n- policy enforcement\n- replayability\n- measurable harness quality\n- package-level distribution and upgrade workflows\n\n## Practical Recommendation\n\nIf only one quarter of work can be funded, the best roadmap slice is:\n\n1. sessions\n2. sensors with back-pressure\n3. traces\n4. task contracts\n\nThat is the smallest set that makes dotcontext meaningfully recognizable as a harness engineering platform instead of only an MCP-enabled workflow tool.\n"
  },
  {
    "path": ".context/docs/harness-split-foundation.md",
    "content": "---\ntype: doc\nname: harness-split-foundation\ndescription: Detailed explanation of the first internal split between the CLI boundary and the harness runtime\ncategory: architecture\ngenerated: 2026-04-10\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Harness Split Foundation\n\nThis document explains the first structural changes made on branch `feat/harness-split-foundation`, why they matter, and how they move dotcontext toward a real harness engineering product model.\n\nThe short version is:\n\n- the codebase now has an explicit `cli` boundary and an explicit `harness` boundary,\n- the codebase now also has an explicit `mcp` boundary, so transport is modeled separately from domain,\n- CLI-only concerns were moved away from the MCP runtime surface,\n- core logic for `agent`, `plan`, `context`, and `skill` is now being extracted into harness services,\n- the repository can now generate package bundles for `cli`, `harness`, and `mcp` in `.release/packages`,\n- compatibility shims were kept in place so the refactor does not force a big-bang migration,\n- and the package now exposes subpath entrypoints that prepare the future split between `dotcontext/cli`, `dotcontext/harness`, and a dedicated MCP adapter surface.\n\nThis is a foundation release, not the final split.\n\n## Why This Change Was Needed\n\nBefore this refactor, the repository already contained two distinct product shapes:\n\n1. An operator-facing CLI centered on [src/index.ts](/home/aicoders/workspace/ai-coders-context/src/index.ts)\n2. A reusable runtime centered on [src/services/mcp/mcpServer.ts](/home/aicoders/workspace/ai-coders-context/src/services/mcp/mcpServer.ts), [src/services/workflow/workflowService.ts](/home/aicoders/workspace/ai-coders-context/src/services/workflow/workflowService.ts), and [src/workflow](/home/aicoders/workspace/ai-coders-context/src/workflow)\n\nThose concerns were still mixed in the import graph and in the mental model of the codebase. In practice, that made the MCP runtime look like a feature of the CLI package instead of a reusable harness layer.\n\nThe goal of this iteration was not to rename packages yet. The goal was to make the internal architecture match the intended product direction first.\n\n## What Changed\n\n## 1. New Explicit Entry Boundaries\n\nThree explicit entry modules now exist:\n\n- [src/cli/index.ts](/home/aicoders/workspace/ai-coders-context/src/cli/index.ts)\n- [src/harness/index.ts](/home/aicoders/workspace/ai-coders-context/src/harness/index.ts)\n- [src/mcp/index.ts](/home/aicoders/workspace/ai-coders-context/src/mcp/index.ts)\n\nThese modules create a clear distinction between:\n\n- `cli`: operator-facing commands, local workflows, installation, sync, reporting\n- `harness`: reusable runtime primitives and domain services\n- `mcp`: Model Context Protocol adapter surface over the harness\n\nThis matters because it creates a stable import boundary inside the monolith before turning it into separate packages.\n\n### Current intent of each boundary\n\n| Boundary | Purpose | Examples |\n| --- | --- | --- |\n| `src/cli` | Thin operator surface | `MCPInstallService`, `StateDetector`, sync/import/export/report flows |\n| `src/harness` | Reusable runtime surface | workflow service, harness services, orchestration types |\n| `src/mcp` | Protocol adapter surface | `AIContextMCPServer`, `startMCPServer`, gateway handlers |\n\n## 2. `src/index.ts` Now Depends on the Boundaries\n\nThe main CLI entrypoint in [src/index.ts](/home/aicoders/workspace/ai-coders-context/src/index.ts) was updated so it no longer reaches directly into as many deep service paths.\n\nInstead, it now imports from:\n\n- [src/cli/index.ts](/home/aicoders/workspace/ai-coders-context/src/cli/index.ts)\n- [src/harness/index.ts](/home/aicoders/workspace/ai-coders-context/src/harness/index.ts)\n- [src/mcp/index.ts](/home/aicoders/workspace/ai-coders-context/src/mcp/index.ts)\n\nThat is a small-looking change with large architectural impact:\n\n- it makes the command layer consume a defined surface instead of internal details,\n- it gives future package extraction a natural seam,\n- and it reduces the chance that CLI code will keep accumulating harness-domain logic by accident.\n\n## 3. `MCPInstallService` Was Reclassified as CLI Concern\n\nThe implementation of `MCPInstallService` was moved semantically to:\n\n- [src/services/cli/mcpInstallService.ts](/home/aicoders/workspace/ai-coders-context/src/services/cli/mcpInstallService.ts)\n\nThe old file remains as a compatibility shim:\n\n- [src/services/mcp/mcpInstallService.ts](/home/aicoders/workspace/ai-coders-context/src/services/mcp/mcpInstallService.ts)\n\n### Why this matters\n\n`MCPInstallService` configures editor and tool clients so they can point to the MCP server. That is an operator/install concern, not part of the harness runtime itself.\n\nLeaving it under `services/mcp` suggested the wrong ownership model:\n\n- as if client installation were part of the runtime,\n- as if the harness were defined by transport bootstrapping,\n- and as if MCP transport and local UX were the same subsystem.\n\nBy moving it under `services/cli`, the code now reflects the intended product model:\n\n- `dotcontext/cli` installs and operates\n- `dotcontext/harness` runs the harness\n\n## 4. `StateDetector` Was Reclassified as CLI Concern\n\nThe implementation of `StateDetector` was moved semantically to:\n\n- [src/services/cli/stateDetector.ts](/home/aicoders/workspace/ai-coders-context/src/services/cli/stateDetector.ts)\n\nThe old file remains as a compatibility shim:\n\n- [src/services/state/stateDetector.ts](/home/aicoders/workspace/ai-coders-context/src/services/state/stateDetector.ts)\n\n### Why this matters\n\n`StateDetector` is used to drive local UX and operator workflows:\n\n- detect whether `.context` exists,\n- detect whether scaffolding is unfilled,\n- detect whether docs are outdated for local prompts and menus.\n\nThat is useful, but it is not core harness runtime logic. It is a local operational sensor used by the CLI layer.\n\nMoving it clarifies that not every useful sensor belongs inside the reusable harness core. Some sensors exist only to support local operator experience.\n\n## 5. `services/mcp` Was Tightened Around Adapter Concerns\n\nThe file [src/services/mcp/index.ts](/home/aicoders/workspace/ai-coders-context/src/services/mcp/index.ts) was updated to focus on MCP transport adapter exports only.\n\nIt no longer re-exports `MCPInstallService`.\n\nThe file [src/services/mcp/README.md](/home/aicoders/workspace/ai-coders-context/src/services/mcp/README.md) was also updated to say explicitly that this directory is about the MCP adapter surface, not CLI install/setup concerns and not the center of the domain model.\n\nThis is important because the codebase needs a clean answer to the question:\n\n\"What is the harness runtime?\"  \nand separately:  \n\"What helps a user operate that runtime locally?\"  \nand also:  \n\"What protocol exposes that runtime to external agents?\"\n\n## 6. Package Subpath Exports Were Added\n\n[package.json](/home/aicoders/workspace/ai-coders-context/package.json) now exposes:\n\n- `\"./cli\": \"./dist/cli/index.js\"`\n- `\"./harness\": \"./dist/harness/index.js\"`\n- `\"./mcp\": \"./dist/mcp/index.js\"`\n\nThe package is still `@dotcontext/cli`, but it now has internal subpath exports that model the future product split and the adapter/runtime separation.\n\nThis gives us three benefits:\n\n1. It creates a package-level contract before the package split happens.\n2. It lets internal and future external consumers target the right surface intentionally.\n3. It reduces migration risk because the conceptual split can stabilize before publish-time restructuring.\n\n## 7. Harness Services Were Introduced\n\nThe first transport-agnostic harness services now exist under:\n\n- [src/services/harness/agentsService.ts](/home/aicoders/workspace/ai-coders-context/src/services/harness/agentsService.ts)\n- [src/services/harness/plansService.ts](/home/aicoders/workspace/ai-coders-context/src/services/harness/plansService.ts)\n- [src/services/harness/contextService.ts](/home/aicoders/workspace/ai-coders-context/src/services/harness/contextService.ts)\n- [src/services/harness/skillsService.ts](/home/aicoders/workspace/ai-coders-context/src/services/harness/skillsService.ts)\n\nThese services now hold the application logic that was previously embedded directly in MCP gateway handlers.\n\nThis means the MCP gateway is starting to behave like a real adapter:\n\n- request comes in,\n- gateway translates params,\n- harness service executes domain logic,\n- gateway formats the response.\n\nThat is a major step toward a true harness architecture.\n\n## 8. Shared Execution Helpers Were Neutralized\n\nNon-interactive helpers such as:\n\n- `minimalUI`\n- `mockTranslate`\n- neutral tool execution context\n\nwere moved into [src/services/shared/executionContext.ts](/home/aicoders/workspace/ai-coders-context/src/services/shared/executionContext.ts).\n\nThis matters because harness services should not depend on files living under `mcp/gateway`. The domain layer must not import utilities from the adapter layer.\n\n## 9. Boundary Tests Were Added\n\nTwo small tests were added:\n\n- [src/cli/index.test.ts](/home/aicoders/workspace/ai-coders-context/src/cli/index.test.ts)\n- [src/harness/index.test.ts](/home/aicoders/workspace/ai-coders-context/src/harness/index.test.ts)\n- [src/mcp/index.test.ts](/home/aicoders/workspace/ai-coders-context/src/mcp/index.test.ts)\n\nThese are not deep behavior tests. They are contract tests for the new public surfaces.\n\nThat matters because once a boundary is introduced, it becomes part of the architecture. The tests make sure the intended surfaces stay visible and do not silently collapse back into ad hoc imports.\n\n## 10. Package Bundle Preparation Was Added\n\nThe repository can now generate package-shaped bundle directories with:\n\n```bash\nnpm run build:packages\n```\n\nThis produces:\n\n- `.release/packages/cli`\n- `.release/packages/harness`\n- `.release/packages/mcp`\n\nEach bundle now includes the generated `dist/` tree, package metadata, `LICENSE`, package-specific `README.md`, and the CLI bundle also includes `prompts/`.\n\nThe MCP adapter also has a dedicated executable entrypoint in [src/mcp/bin.ts](/home/aicoders/workspace/ai-coders-context/src/mcp/bin.ts), which prepares the future `@dotcontext/mcp` package to ship as a real runnable adapter instead of only as a library surface.\n\nThis is still a preparation step, not the final multi-package publication workflow, but it closes the gap between architecture and package artifacts.\n\n## 11. Planning and Agent Artifacts Were Added\n\nThe transformation is also documented in `.context`:\n\n- [dotcontext-harness-engineering-transformation.md](/home/aicoders/workspace/ai-coders-context/.context/plans/dotcontext-harness-engineering-transformation.md)\n- [agents README](/home/aicoders/workspace/ai-coders-context/.context/agents/README.md)\n\nAnd the following specialized playbooks were added:\n\n- [agent-systems-designer.md](/home/aicoders/workspace/ai-coders-context/.context/agents/agent-systems-designer.md)\n- [cli-experience-architect.md](/home/aicoders/workspace/ai-coders-context/.context/agents/cli-experience-architect.md)\n- [harness-platform-architect.md](/home/aicoders/workspace/ai-coders-context/.context/agents/harness-platform-architect.md)\n- [workflow-orchestration-engineer.md](/home/aicoders/workspace/ai-coders-context/.context/agents/workflow-orchestration-engineer.md)\n- [harness-quality-auditor.md](/home/aicoders/workspace/ai-coders-context/.context/agents/harness-quality-auditor.md)\n- [migration-release-manager.md](/home/aicoders/workspace/ai-coders-context/.context/agents/migration-release-manager.md)\n\nThese are not runtime changes, but they are still part of harness engineering: they formalize the control-plane thinking and make the transformation process itself more machine-readable and repeatable.\n\n## Backward Compatibility Strategy\n\nThis iteration intentionally avoided a breaking rewrite.\n\nThe old import paths still exist as shims:\n\n- [src/services/mcp/mcpInstallService.ts](/home/aicoders/workspace/ai-coders-context/src/services/mcp/mcpInstallService.ts)\n- [src/services/state/stateDetector.ts](/home/aicoders/workspace/ai-coders-context/src/services/state/stateDetector.ts)\n\nThat means the code can evolve in stages:\n\n1. Move ownership semantically\n2. Preserve old paths temporarily\n3. Migrate callers incrementally\n4. Remove the shims only when the new boundaries are stable\n\nThis is deliberate. A package split done without this intermediate stage would create unnecessary churn and increase regression risk.\n\n## How This Aligns With Harness Engineering\n\nThis refactor is aligned with harness engineering in several ways.\n\n## 1. It Separates the Operator Layer From the Runtime Layer\n\nHarness engineering is not just \"having an MCP server.\" It is the discipline of shaping the environment around agents so that runtime behavior, constraints, tooling, and feedback loops are explicit and governable.\n\nThe new split reflects that:\n\n- `cli` is the operator-facing outer shell\n- `harness` is the reusable runtime substrate\n- `mcp` is the transport adapter over that substrate\n\nThat is much closer to the real harness mental model than a single mixed command package.\n\n## 2. It Makes the Outer Harness More Explicit\n\nThe explicit `cli` boundary represents the local operator harness:\n\n- install flows,\n- diagnostics,\n- local status detection,\n- sync/import/export assistance.\n\nThe explicit `harness` boundary represents the reusable runtime harness:\n\n- context scaffolding,\n- workflow management,\n- orchestration,\n- harness services.\n\nThe explicit `mcp` boundary represents the transport adapter:\n\n- MCP server lifecycle,\n- gateway registration,\n- request/response translation.\n\nHarness engineering improves when those two layers are visible and reasoned about separately.\n\n## 3. It Reduces Coupling Between Transport and Domain Logic\n\nOne of the goals in the broader plan is to make MCP an adapter, not the product itself.\n\nThis change now goes further than the first foundation pass:\n\n- the runtime now has a named harness-facing entrypoint,\n- the transport now has a separate MCP-facing entrypoint,\n- CLI-side install/setup no longer masquerades as MCP runtime logic,\n- and core `agent`, `plan`, `context`, and `skill` logic has started moving into transport-agnostic harness services.\n\nThe split is also no longer only conceptual, because the repository can emit package-shaped artifacts for all three boundaries.\n\nThat is consistent with harness engineering because a good harness must survive changes in transport, toolchain, and model behavior.\n\n## 4. It Treats Compatibility as a Runtime Constraint, Not an Afterthought\n\nHarness engineering values reliable systems over clever rewrites. The shim approach reflects that mindset.\n\nInstead of forcing immediate breakage:\n\n- old paths still resolve,\n- behavior remains stable,\n- the architecture can improve incrementally under test,\n- and package-shaped outputs can be exercised before changing publication strategy.\n\nThis is the same design instinct used in good harnesses generally: constrain change, preserve observability, and avoid destabilizing the system while it is being improved.\n\n## 5. It Improves Agent Readability of the Codebase\n\nFor human developers and coding agents alike, explicit boundaries reduce ambiguity.\n\nBefore:\n\n- an agent reading `services/mcp` could reasonably infer that install/configuration and runtime were one concern.\n\nAfter:\n\n- the code states more clearly what belongs to operator UX,\n- what belongs to harness runtime,\n- and which files are temporary compatibility glue.\n\nThat is directly relevant to harness engineering because the harness itself must be legible to the agents that work within it.\n\n## 6. It Supports “Build to Delete” Rather Than Big-Bang Architecture\n\nThis version does not pretend to know the final package shape in full detail. Instead, it introduces seams:\n\n- subpath exports,\n- explicit boundaries,\n- compatibility shims,\n- boundary tests,\n- and local package bundle generation.\n\nThat is a strong harness-engineering move because it keeps the system adaptable as model behavior, transport assumptions, and product priorities evolve.\n\n## What This Version Does Not Do Yet\n\nThis foundation is intentionally incomplete. It does not yet:\n\n- extract every remaining gateway into harness services,\n- create a fully transport-agnostic harness core beneath every adapter path,\n- rename or publish a separate `dotcontext/harness` package,\n- rename or publish a separate `dotcontext/mcp` package,\n- automate real versioning and publish workflows for the generated package bundles,\n- introduce first-class guides, sensors, evaluations, traces, or replay artifacts as runtime concepts,\n- or redefine PREVC as one harness strategy among several.\n\nThose remain part of the next phases described in the transformation plan.\n\n## Validation\n\nThe code changes associated with this foundation were validated with:\n\n```bash\nnpm run build\nnpm test -- --runInBand\nnpm run build:packages\n```\n\nAt the time of this stage completion, the full suite passed with 21 test suites and 210 tests, and the package bundle pipeline generated all three bundles successfully.\n\n## Recommended Next Steps\n\nThe next technically coherent steps are:\n\n1. Decide which current workflow concepts are permanent harness primitives and which are PREVC-specific policy.\n2. Introduce a clearer internal module map for sessions, guides, sensors, plans, agents, and evaluations.\n3. Design the real publish/version workflow for `@dotcontext/cli`, `@dotcontext/harness`, and `@dotcontext/mcp`.\n4. Keep shrinking the CLI so it orchestrates the harness instead of owning more domain behavior.\n5. Remove compatibility shims only after the new import surfaces are stable and internal callers are migrated.\n\n## Summary\n\nThis version did not change the external story dramatically yet. What it changed was the architecture’s truthfulness.\n\nThe codebase now says, more clearly than before:\n\n- there is a CLI boundary,\n- there is a harness boundary,\n- there is an MCP adapter boundary,\n- installation is not the runtime,\n- local status sensing is not the same thing as reusable harness logic,\n- adapter helpers do not belong inside the domain layer,\n- and the future package split can happen on top of explicit seams instead of ad hoc imports.\n\nThat is exactly the right kind of first move for a harness engineering transformation.\n"
  },
  {
    "path": ".context/docs/project-overview.md",
    "content": "---\ntype: doc\nname: project-overview\ndescription: High-level overview of the project, its purpose, and key components\ncategory: overview\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Project Overview\n\n**@dotcontext/cli** (v0.8.0) is an MCP-first context engineering toolkit for codebase documentation and AI agent prompts. MCP-enabled AI tools create and refresh the `.context` directory structure containing docs, agents, plans, and skills, while the standalone CLI now focuses on workflow management, sync/import utilities, reporting, and MCP configuration. The project also supports semantic code analysis via tree-sitter, a PREVC workflow engine, MCP server mode for AI tool integration, and context export/sync to Claude, Gemini, and Codex.\n\n## Codebase Reference\n\nThe machine-readable codebase map lives at `.context/docs/codebase-map.json`. It contains file listings, dependency graphs, and structural metadata used by MCP tools, workflow orchestration, and codebase analysis features.\n\n## Quick Facts\n\n| Property         | Value                                      |\n| ---------------- | ------------------------------------------ |\n| Package name     | `@dotcontext/cli`                       |\n| Version          | 0.8.0                                      |\n| License          | MIT                                        |\n| Node requirement | >= 20.0.0                                  |\n| Language          | TypeScript (target ES2020, CommonJS)       |\n| Binary           | `dotcontext`                               |\n| Repository       | https://github.com/vinilana/ai-coders-context |\n\n## Entry Points\n\n- **CLI binary**: `dist/index.js` (mapped to the `dotcontext` command via `package.json` `bin` field)\n- **Source entry**: `src/index.ts` -- registers all commander commands and starts the CLI\n- **MCP server**: accessible through the `mcp` CLI command for AI tool integration\n\n## Key Exports\n\nThe package ships only compiled output from `dist/`. Refer to `.context/docs/codebase-map.json` for a full inventory of exported modules and their relationships.\n\nPrimary public surface:\n- CLI commands exposed through commander (sync, import/export, workflow, reporting, MCP setup, etc.)\n- MCP server tools (via `src/services/mcp/`)\n\n## File Structure\n\n```\nsrc/\n  index.ts              # CLI entry point (commander setup)\n  cli.test.ts           # CLI integration test\n  types.ts              # Shared type definitions\n  version.ts            # Version constant\n  generators/           # Template generators\n    agents/             # Agent prompt templates\n    documentation/      # Doc templates\n    plans/              # Plan templates\n    shared/             # Shared generator utilities\n    skills/             # Skill templates\n  services/             # Core business logic\n    ai/                 # MCP-facing AI tool definitions and provider helpers\n    autoFill/           # Static scaffold auto-fill from semantic analysis\n    export/             # Context export to AI tools\n    import/             # Context import\n    init/               # .context scaffolding initializer used by MCP tools\n    mcp/                # MCP server and gateway handlers\n    qa/                 # QA scaffolding service\n    report/             # Context and workflow reporting\n    reverseSync/        # Import external changes back into .context\n    semantic/           # Tree-sitter semantic analysis\n    shared/             # Shared helpers and service types\n    stack/              # Stack detection and scaffold filtering\n    state/              # Interactive state detection\n    sync/               # Context sync to AI tools (Claude, Gemini, Codex)\n    workflow/           # PREVC workflow service layer\n    ... (and more)\n  utils/                # Shared utilities\n    frontMatter.*       # YAML frontmatter parsing\n    i18n.*              # Internationalization (en, pt-BR)\n    prompts/            # CLI prompt definitions (inquirer)\n    ...\n  workflow/             # Workflow config and state\n.context/               # Generated context directory\n  docs/                 # Documentation files\n  agents/               # Agent playbooks\n  plans/                # Plan documents\n  skills/               # Skill definitions\n```\n\n## Technology Stack Summary\n\n| Layer          | Technology                                           |\n| -------------- | ---------------------------------------------------- |\n| Runtime        | Node.js >= 20                                        |\n| Language       | TypeScript 5.x (strict mode, CommonJS output)        |\n| CLI framework  | commander 14.x                                       |\n| Interactive UI | inquirer 12.x, chalk 4.x, ora 5.x, boxen, figures   |\n| AI SDKs        | @ai-sdk/anthropic, @ai-sdk/openai, @ai-sdk/google, ai (Vercel AI SDK) |\n| MCP            | @modelcontextprotocol/sdk 1.x                        |\n| Code analysis  | tree-sitter, tree-sitter-typescript (optional)        |\n| Validation     | zod 4.x                                              |\n| Testing        | Jest 30.x with ts-jest                               |\n| Dev tooling    | tsx (development runner), tsc (build)                 |\n\n## Getting Started Checklist\n\n1. Clone the repository: `git clone https://github.com/vinilana/ai-coders-context.git`\n2. Install dependencies: `npm install`\n3. Run in development mode: `npm run dev` (uses tsx for fast TypeScript execution)\n4. Build for production: `npm run build`\n5. Run tests: `npm test`\n6. Try the CLI: `npx -y @dotcontext/cli@latest mcp:install` to connect the package to your AI tool\n\n## Next Steps\n\n- Read **development-workflow.md** for branching, releases, and contribution guidelines.\n- Read **testing-strategy.md** for test patterns and quality gates.\n- Read **tooling.md** for scripts, IDE setup, and productivity tips.\n- Explore `.context/agents/README.md` for agent playbook documentation.\n- Review `.context/docs/codebase-map.json` for the full structural map of the codebase.\n"
  },
  {
    "path": ".context/docs/qa/README.md",
    "content": "# Q&A Index\n\nProject type: **cli-tool**\n\nGenerated: 2026-03-18T21:32:55.004Z\n\n## Getting-started\n\n- [How do I set up and run this project?](./getting-started.md)\n\n## Architecture\n\n- [How is the codebase organized?](./project-structure.md)\n\n## Operations\n\n- [How are errors handled?](./error-handling.md)\n\n## Testing\n\nSee [testing-strategy.md](../testing-strategy.md) in the parent docs directory for full test framework documentation.\n"
  },
  {
    "path": ".context/docs/qa/error-handling.md",
    "content": "---\nslug: error-handling\ncategory: operations\nstatus: filled\ngeneratedAt: 2026-03-18T21:32:54.231Z\nrelevantFiles:\n  - src/workflow/errors.ts\n  - src/services/shared/types.ts\n  - src/services/mcp/gateway/response.ts\n  - src/services/mcp/mcpServer.ts\n---\n\n# How are errors handled?\n\n## Overview\n\nError handling in this project follows three patterns depending on the context: workflow errors (custom error classes), operation result errors (structured result objects), and MCP tool errors (structured responses returned through the MCP server).\n\n## 1. Workflow errors (`src/workflow/errors.ts`)\n\nThe PREVC workflow engine uses a class hierarchy for typed errors:\n\n- **`WorkflowError`** -- Base error class for all workflow-related failures.\n- **`WorkflowGateError`** -- Thrown when a workflow gate blocks a phase transition. Carries the `transition` (from/to phases), the `gate` type, and a human-readable `hint` for resolution.\n- **`NoPlanToApproveError`** -- Thrown when attempting to approve a plan that has not been linked.\n- **`NoWorkflowError`** -- Thrown when a workflow operation is attempted but no workflow exists.\n\nAll workflow errors extend the native `Error` class and set a descriptive `name` property for easy identification in catch blocks.\n\n## 2. Operation result errors (`src/services/shared/types.ts`)\n\nFile-based operations (import, export, sync) use an `OperationResult` pattern instead of throwing:\n\n```typescript\ninterface OperationResult {\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  errors: OperationError[];\n}\n\ninterface OperationError {\n  file: string;\n  error: string;\n}\n```\n\nThe `addError()` helper increments `filesFailed` and appends an `OperationError` entry. Multiple results can be merged with `mergeResults()`. This pattern allows batch operations to continue processing after individual file failures and report all errors at the end.\n\n## 3. MCP tool errors (`src/services/mcp/gateway/response.ts`)\n\nMCP gateway handlers return structured error payloads through shared response helpers:\n\n```typescript\ninterface MCPToolResponse {\n  content: Array<{\n    type: 'text';\n    text: string;\n  }>;\n  isError?: boolean;\n}\n```\n\nThe error helper serializes responses like:\n\n```json\n{\n  \"success\": false,\n  \"error\": \"Human-readable message\"\n}\n```\n\n`createErrorResponse()` sets `isError: true` and wraps the JSON payload in MCP-compatible text content. At the server boundary, `src/services/mcp/mcpServer.ts` validates tool input with Zod and catches handler failures before returning them to the client.\n\n## 4. CLI-level error handling\n\nAt the CLI level (`src/index.ts`), errors from services are caught and displayed to the user through the `CLIInterface` (`ui`) which formats output with chalk colors. Unhandled promise rejections and uncaught exceptions will cause the process to exit with a non-zero code.\n\n## General conventions\n\n- **Do not throw for expected batch failures** -- Use `OperationResult` to accumulate errors when processing multiple files.\n- **Throw custom error classes for workflow violations** -- Callers can use `instanceof` to handle specific workflow error types.\n- **Return structured error responses in MCP mode** -- Gateway handlers should use the shared response helpers instead of leaking raw exceptions to the client.\n- **Include actionable hints** -- `WorkflowGateError` includes a `hint` field to guide users toward resolution.\n"
  },
  {
    "path": ".context/docs/qa/getting-started.md",
    "content": "---\nslug: getting-started\ncategory: getting-started\nstatus: filled\ngeneratedAt: 2026-03-18T21:32:50.399Z\n---\n\n# How do I set up and run this project?\n\n## Prerequisites\n\n- Node.js >= 20.0.0\n- npm\n\n## Installation (for development)\n\n```bash\n# Clone the repository\ngit clone https://github.com/vinilana/ai-coders-context.git\ncd ai-coders-context\n\n# Install dependencies\nnpm install\n\n# Build the TypeScript source\nnpm run build\n```\n\n## Installation (as a user)\n\n```bash\nnpm install -g @dotcontext/cli\n```\n\nAfter installation the `dotcontext` binary is available globally.\n\n## Running in development\n\n```bash\n# Run directly from TypeScript source (uses tsx)\nnpm run dev\n\n# Run from compiled output\nnpm start\n```\n\n## Key npm scripts\n\n| Script | Purpose |\n| --- | --- |\n| `npm run build` | Compile TypeScript to `dist/` |\n| `npm run dev` | Run from source via `tsx` |\n| `npm start` | Run compiled `dist/index.js` |\n| `npm test` | Run Jest test suite |\n| `npm run release` | Bump patch version and publish to npm |\n| `npm run release:minor` | Bump minor version and publish |\n| `npm run release:major` | Bump major version and publish |\n\n## CLI commands overview\n\nThe CLI is built with Commander. Run `dotcontext --help` to see all commands. Key commands include:\n\n- **`dotcontext sync-agents`** -- Synchronize agent playbooks\n- **`dotcontext reverse-sync`** -- Import rules, agents, and skills from AI tool directories\n- **`dotcontext mcp`** -- Start in MCP (Model Context Protocol) server mode\n- **`dotcontext mcp:install [tool]`** -- Install MCP configuration for AI tools\n- **`dotcontext export-rules`** -- Export rules for AI coding tools\n- **`dotcontext report`** -- Generate context reports\n- **`dotcontext workflow init|status|advance`** -- Manage PREVC workflow phases\n\n## Interactive mode\n\nRunning `dotcontext` with no command arguments enters interactive mode, which guides you through project state, workflow, sync, imports, and MCP setup using Inquirer prompts.\n\n## Environment variables\n\n- `DOTCONTEXT_LANG` -- Override the UI locale (`en` or `pt-BR`)\n- A `.env` file in the project root is loaded automatically via `dotenv`\n\nThe standalone CLI no longer creates or fills context directly.\nFor context creation, plan scaffolding, AI-generated filling, or refresh, use MCP-enabled AI tools after running `dotcontext mcp:install`.\n"
  },
  {
    "path": ".context/docs/qa/project-structure.md",
    "content": "---\nslug: project-structure\ncategory: architecture\nstatus: filled\ngeneratedAt: 2026-03-18T21:32:54.231Z\nrelevantFiles:\n  - src/index.ts\n  - src/generators\n  - src/services\n  - src/utils\n  - src/types.ts\n  - src/workflow\n  - src/version.ts\n---\n\n# How is the codebase organized?\n\n## Top-level layout\n\n```\n.context/          # Generated documentation and agent config (output of the tool itself)\n.github/workflows/ # CI (ci.yml) and release (release.yml) pipelines\ndist/              # Compiled JavaScript output (git-ignored)\nprompts/           # Prompt templates used by generators\nscripts/           # Build/utility scripts\nsrc/               # TypeScript source code (all application logic)\n```\n\n## Source directory (`src/`)\n\n### Entry point\n\n- **`src/index.ts`** -- CLI entry point. Registers all Commander commands, wires up services via dependency injection, handles interactive mode, and locale detection.\n\n### Core directories\n\n| Directory | Purpose |\n| --- | --- |\n| `src/generators/` | Content generation engines (agents, documentation, plans, skills) |\n| `src/services/` | Business logic organized by feature domain |\n| `src/utils/` | Shared utilities (CLI UI, i18n, theme, file mapping, frontmatter parsing) |\n| `src/types/` | Shared TypeScript type definitions |\n| `src/workflow/` | PREVC workflow engine (phases, roles, gates, orchestration, collaboration) |\n| `src/prompts/` | Prompt-related modules |\n\n### Services (`src/services/`)\n\nEach service is a self-contained module with its own directory:\n\n| Service | Purpose |\n| --- | --- |\n| `ai/` | MCP-facing AI tool definitions, schemas, and provider helpers |\n| `autoFill/` | Static content filling from semantic analysis |\n| `export/` | Rule export for AI coding tools (Cursor, Windsurf, etc.) |\n| `import/` | Import rules and agents from external sources |\n| `init/` | Project scaffolding used by MCP context tools |\n| `mcp/` | MCP (Model Context Protocol) server for AI tool integration |\n| `qa/` | Q&A document generation |\n| `quickSync/` | Quick context synchronization |\n| `report/` | Context report generation |\n| `reverseSync/` | Reverse synchronization (merge external changes back) |\n| `semantic/` | Semantic code analysis (optional tree-sitter) |\n| `shared/` | Shared types, context root resolution, common utilities |\n| `stack/` | Technology stack detection |\n| `state/` | Project state detection |\n| `sync/` | Agent synchronization |\n| `workflow/` | Workflow service layer |\n\n### Generators (`src/generators/`)\n\n| Generator | Purpose |\n| --- | --- |\n| `agents/` | Agent playbook generation |\n| `documentation/` | Documentation template generation |\n| `plans/` | Implementation plan generation |\n| `shared/` | Shared generator utilities |\n| `skills/` | Skill document generation |\n\n### Utilities (`src/utils/`)\n\n| Module | Purpose |\n| --- | --- |\n| `cliUI.ts` | CLI output helpers (spinners, banners, tables) using chalk and ora |\n| `i18n.ts` | Internationalization (English and Brazilian Portuguese) |\n| `theme.ts` | Color theme constants |\n| `frontMatter.ts` | YAML frontmatter parsing and serialization |\n| `contentSanitizer.ts` | Content sanitization for generated output |\n| `fileMapper.ts` | File discovery and mapping |\n| `gitService.ts` | Git repository utilities |\n| `promptLoader.ts` | Prompt template loading from `prompts/` directory |\n| `versionChecker.ts` | npm registry version checking for update notifications |\n| `prompts/` | Interactive prompt helpers (config summary, LLM config, analysis options) |\n\n## Key architectural patterns\n\n- **Dependency injection**: Services receive their dependencies (`ui`, `t`, `version`) via constructor options conforming to `BaseDependencies` or `AIDependencies` from `src/services/shared/types.ts`.\n- **Service isolation**: Each feature lives in its own service directory with an index barrel export.\n- **i18n throughout**: All user-facing strings go through the `TranslateFn` (`t`) function.\n- **Zod validation**: MCP tool schemas and gateway parameters use Zod for runtime validation.\n"
  },
  {
    "path": ".context/docs/testing-strategy.md",
    "content": "---\ntype: doc\nname: testing-strategy\ndescription: Test frameworks, patterns, coverage requirements, and quality gates\ncategory: testing\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Testing Strategy\n\nThis document describes the test infrastructure, conventions, and quality expectations for the `@dotcontext/cli` project.\n\n## Test Types\n\n### Unit tests\n\nUnit tests validate individual functions, utilities, and service modules in isolation. They are the primary test type in this project.\n\n**Examples of existing unit tests:**\n\n- `src/utils/contentSanitizer.test.ts` -- content sanitization logic\n- `src/utils/frontMatter.test.ts` -- YAML frontmatter parsing and serialization\n- `src/utils/promptLoader.test.ts` -- prompt template loading\n- `src/utils/versionChecker.test.ts` -- version comparison utilities\n\n### Integration tests\n\nIntegration tests exercise larger slices of functionality, including CLI command execution and service orchestration.\n\n**Examples:**\n\n- `src/cli.test.ts` -- CLI command registration and execution\n- `src/services/mcp/mcpServer.test.ts` -- MCP server registration and gateway behavior\n\n## Framework & Configuration\n\n| Setting              | Value                                        |\n| -------------------- | -------------------------------------------- |\n| Framework            | Jest 30.x                                    |\n| TypeScript transform | ts-jest 29.x (`preset: 'ts-jest'`)           |\n| Test environment     | Node                                         |\n| Config file          | `jest.config.js`                             |\n\n### File discovery\n\nJest is configured to find tests using two patterns:\n\n- `**/__tests__/**/*.ts` -- tests inside `__tests__/` directories\n- `**/?(*.)+(spec|test).ts` -- files ending in `.test.ts` or `.spec.ts`\n\nAll test roots are under `<rootDir>/src`. The `node_modules/` and `dist/` directories are excluded via `testPathIgnorePatterns`.\n\n### Naming conventions\n\n- Place unit tests alongside the source file: `src/utils/frontMatter.ts` -> `src/utils/frontMatter.test.ts`\n- Use `.test.ts` for unit tests and `.integration.test.ts` for integration tests.\n- Use `.spec.ts` when following a behavior-driven style (both are recognized).\n\n## Running Tests\n\n```bash\n# Run the full test suite\nnpm test\n\n# Run tests in watch mode (useful during development)\nnpx jest --watch\n\n# Run a specific test file\nnpx jest src/utils/frontMatter.test.ts\n\n# Run tests matching a pattern\nnpx jest --testPathPattern=\"utils\"\n\n# Run with coverage report\nnpx jest --coverage\n```\n\n### Coverage configuration\n\nCoverage is collected from all `src/**/*.ts` files, excluding:\n\n- Declaration files (`*.d.ts`)\n- Index barrel files (`**/index.ts`)\n\nReports are generated in three formats:\n\n| Format | Output                  | Purpose                    |\n| ------ | ----------------------- | -------------------------- |\n| `text` | Terminal                | Quick local feedback       |\n| `lcov` | `coverage/lcov.info`   | CI integration, tooling    |\n| `html` | `coverage/` directory  | Browsable detailed report  |\n\n## Quality Gates\n\n### Before merging a pull request\n\n1. **All tests pass**: `npm test` must exit with code 0.\n2. **No regressions**: Existing tests must not be removed or weakened without justification.\n3. **New code has tests**: Features and bug fixes should include corresponding tests.\n4. **Build succeeds**: `npm run build` must compile without TypeScript errors (test files are excluded from the build via `tsconfig.json`).\n\n### Recommended coverage targets\n\nWhile there is no enforced coverage threshold at this time, contributors should aim for:\n\n- Utility functions: high coverage (these are pure and easy to test)\n- Service modules: moderate coverage, focusing on core logic paths\n- CLI integration: at least smoke-test-level coverage for new commands\n\n## Troubleshooting\n\n### Common issues\n\n**ts-jest transform errors**\n\nIf you see transform errors, ensure `ts-jest` is installed and the Jest config specifies the correct transform:\n\n```js\ntransform: {\n  '^.+\\\\.ts$': 'ts-jest',\n}\n```\n\n**Module resolution failures**\n\nThe project uses `baseUrl: \"./src\"` and path aliases in `tsconfig.json`. If Jest cannot resolve an import, check that `moduleFileExtensions` includes `ts` and `js`, and that the path alias is correctly mapped if applicable.\n\n**Tree-sitter optional dependency warnings**\n\n`tree-sitter` and `tree-sitter-typescript` are optional dependencies used for semantic analysis. Tests that depend on them may skip gracefully if the native modules are not installed. These warnings are safe to ignore in most development scenarios.\n\n**Slow test runs**\n\n`ts-jest` compiles TypeScript on the fly. For faster iteration, use `--watch` mode which only re-runs affected tests, or target a specific file with `npx jest <path>`.\n\n**Integration test failures**\n\nIntegration tests that exercise MCP or workflow flows may create temporary files or directories. Ensure your working directory is clean and that no stale `.context` artifacts interfere with test expectations.\n"
  },
  {
    "path": ".context/docs/tooling.md",
    "content": "---\ntype: doc\nname: tooling\ndescription: Scripts, IDE settings, automation, and developer productivity tips\ncategory: tooling\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Tooling & Productivity Guide\n\nThis document covers the scripts, tools, IDE configuration, and productivity practices used in the `@dotcontext/cli` project.\n\n## Required Tooling\n\n| Tool            | Version     | Purpose                                          |\n| --------------- | ----------- | ------------------------------------------------ |\n| Node.js         | >= 20.0.0   | Runtime for the CLI and all scripts               |\n| npm             | (bundled)   | Package management and script execution           |\n| TypeScript      | 5.x         | Language (strict mode, ES2020 target, CommonJS)   |\n| tsx             | 4.x         | Fast TypeScript execution for development         |\n| Git             | any recent  | Version control                                   |\n\n### Optional tooling\n\n| Tool                      | Purpose                                    |\n| ------------------------- | ------------------------------------------ |\n| tree-sitter (native)      | Semantic code analysis (optional dependency) |\n| tree-sitter-typescript    | TypeScript grammar for tree-sitter          |\n\n## npm Scripts Reference\n\nAll scripts are defined in `package.json`:\n\n| Script              | Command                                           | Description                              |\n| ------------------- | ------------------------------------------------- | ---------------------------------------- |\n| `dev`               | `tsx src/index.ts`                                | Run CLI from source (no build step)       |\n| `build`             | `tsc`                                             | Compile TypeScript to `dist/`             |\n| `start`             | `node dist/index.js`                              | Run compiled CLI                          |\n| `test`              | `jest`                                            | Run the full test suite                   |\n| `prepublishOnly`    | `npm run build`                                   | Ensure a fresh build before publish       |\n| `version`           | `npm run build`                                   | Rebuild on version bump                   |\n| `release`           | `npm version patch && npm publish --access public`| Patch release to npm                      |\n| `release:minor`     | `npm version minor && npm publish --access public`| Minor release to npm                      |\n| `release:major`     | `npm version major && npm publish --access public`| Major release to npm                      |\n\n## Recommended Automation\n\n### Build and test workflow\n\nA typical development cycle:\n\n```bash\n# 1. Start development (auto-reloads not built-in; re-run as needed)\nnpm run dev -- --help\n\n# 2. Run tests after changes\nnpm test\n\n# 3. Verify production build\nnpm run build && npm start -- --help\n```\n\n### Release workflow\n\n```bash\n# 1. Ensure main is up to date\ngit checkout main && git pull\n\n# 2. Create a release branch\ngit checkout -b release/0.8.0\n\n# 3. Update CHANGELOG.md\n\n# 4. Run the release script (bumps version, builds, publishes)\nnpm run release:minor\n\n# 5. Push tags and branch, then merge to main\ngit push --follow-tags\n```\n\n### Pre-commit checks\n\nWhile the project does not currently enforce pre-commit hooks, the following manual checks are recommended before every commit:\n\n```bash\nnpm test && npm run build\n```\n\nConsider adding [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/lint-staged/lint-staged) if the team wants automated pre-commit enforcement.\n\n## IDE Setup\n\n### VS Code (recommended)\n\n**Suggested extensions:**\n\n- **TypeScript** -- built-in, ensure workspace TypeScript version matches (`5.x`)\n- **ESLint** -- if a linter config is added in the future\n- **Jest Runner** -- run individual tests from the editor\n- **Prettier** -- consistent formatting (configure to match project style)\n\n**Recommended `settings.json` overrides:**\n\n```jsonc\n{\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"jest.jestCommandLine\": \"npx jest\"\n}\n```\n\n### Other editors\n\n- **WebStorm/IntelliJ**: TypeScript support is built-in. Configure the Jest run configuration to use the project's `jest.config.js`.\n- **Vim/Neovim**: Use `coc-tsserver` or `nvim-lspconfig` with `ts_ls` for TypeScript language support. Run tests via terminal.\n\n## TypeScript Configuration Highlights\n\nKey `tsconfig.json` settings that affect the development experience:\n\n| Setting                        | Value        | Impact                                     |\n| ------------------------------ | ------------ | ------------------------------------------ |\n| `strict`                       | `true`       | Full strict type checking enabled           |\n| `target`                       | `ES2020`     | Modern JS features available                |\n| `module`                       | `commonjs`   | Compatible with Node.js require()           |\n| `declaration` / `declarationMap` | `true`     | Type declarations emitted for consumers     |\n| `sourceMap`                    | `true`       | Enables debugger source mapping             |\n| `baseUrl`                      | `./src`      | Short imports from src root                 |\n| `paths`                        | configured   | `@generators/agents/*` alias available      |\n\nTest files (`**/*.test.ts`) are excluded from the build output but included in the IDE's type checking.\n\n## Productivity Tips\n\n1. **Use `npm run dev` for fast iteration.** The `tsx` runner executes TypeScript directly without a build step, making the feedback loop much faster than `build` + `start`.\n\n2. **Target specific tests.** Instead of running the full suite, use `npx jest <file>` or `npx jest --watch` to focus on what you are changing.\n\n3. **Leverage the `--help` flag.** Every CLI command supports `--help` for discoverability: `npm run dev -- workflow --help`.\n\n4. **Use MCP for context creation and AI generation.** Standalone CLI generation is no longer supported. Install MCP with `npx -y @dotcontext/cli@latest mcp:install` and use your AI tool to create, fill, or refresh context.\n\n5. **Read the generated context.** After initializing context through MCP, browse `.context/docs/` and `.context/agents/` to understand what the tool produces -- this helps when working on generators and templates.\n\n6. **Check `codebase-map.json` for navigation.** The file at `.context/docs/codebase-map.json` provides a structural overview of the entire codebase, useful for orienting yourself in unfamiliar areas.\n\n7. **Debug with source maps.** Since `sourceMap: true` is configured, you can attach a Node.js debugger to the compiled output and step through the original TypeScript source.\n"
  },
  {
    "path": ".context/skills/README.md",
    "content": "# Skills\n\nOn-demand expertise for AI agents. Skills are task-specific procedures that get activated when relevant.\n\n> Project: dotcontext\n\n## How Skills Work\n\n1. **Discovery**: AI agents discover available skills via the `skill` MCP tool or by reading this directory\n2. **Matching**: When a task matches a skill's description, it gets activated\n3. **Execution**: The skill's instructions guide the AI's behavior with project-specific procedures\n\n## Available Skills\n\n### Built-in Skills\n\n| Skill | Description | Phases | Status |\n|-------|-------------|--------|--------|\n| [MCP Tool Design](./api-design/SKILL.md) | Design MCP tools and gateway interfaces for the dotcontext server | P, R | filled |\n| [Bug Investigation](./bug-investigation/SKILL.md) | Systematic bug investigation and root cause analysis | E, V | filled |\n| [Code Review](./code-review/SKILL.md) | Review code quality, patterns, and best practices | R, V | filled |\n| [Commit Message](./commit-message/SKILL.md) | Generate commit messages following conventional commits with scope detection | E, C | filled |\n| [Documentation](./documentation/SKILL.md) | Generate and update technical documentation | P, C | filled |\n| [Feature Breakdown](./feature-breakdown/SKILL.md) | Break down features into implementable tasks | P | filled |\n| [PR Review](./pr-review/SKILL.md) | Review pull requests against team standards and best practices | R, V | filled |\n| [Refactoring](./refactoring/SKILL.md) | Safe code refactoring with step-by-step approach | E | filled |\n| [Security Audit](./security-audit/SKILL.md) | Security review checklist for code and infrastructure | R, V | filled |\n| [Test Generation](./test-generation/SKILL.md) | Generate comprehensive test cases for code | E, V | filled |\n\n## Creating Custom Skills\n\nCreate a new skill by adding a directory with a `SKILL.md` file:\n\n```\n.context/skills/\n└── my-skill/\n    ├── SKILL.md          # Required: skill definition\n    └── templates/        # Optional: helper resources\n        └── checklist.md\n```\n\n### SKILL.md Format\n\n```yaml\n---\ntype: skill\nname: my-skill\ndescription: When to use this skill\nskillSlug: my-skill\nphases: [P, E, V]  # Optional: PREVC phases\nmode: false        # Optional: mode command?\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# My Skill\n\n## When to Use\n[Description of when this skill applies]\n\n## Instructions\n1. Step one\n2. Step two\n\n## Examples\n[Usage examples]\n```\n\n## PREVC Phase Mapping\n\n| Phase | Name | Skills |\n|-------|------|--------|\n| P | Planning | feature-breakdown, documentation, api-design (MCP tool design) |\n| R | Review | pr-review, code-review, api-design (MCP tool design), security-audit |\n| E | Execution | commit-message, test-generation, refactoring, bug-investigation |\n| V | Validation | pr-review, code-review, test-generation, bug-investigation, security-audit |\n| C | Confirmation | commit-message, documentation |\n\n## Accessing Skills Programmatically\n\nVia MCP tools:\n- `skill({ action: 'list' })` -- List all skills\n- `skill({ action: 'getContent', skillSlug: 'code-review' })` -- Get skill content\n- `skill({ action: 'getForPhase', phase: 'E' })` -- Get skills for a PREVC phase\n\nVia the SkillRegistry (`src/workflow/skills/skillRegistry.ts`):\n- Discovers built-in and custom skills from `.context/skills/`\n- Parses YAML frontmatter and markdown content\n- Used by MCP gateway handler in `src/services/mcp/gateway/skill.ts`\n"
  },
  {
    "path": ".context/skills/api-design/SKILL.md",
    "content": "---\ntype: skill\nname: MCP Tool Design\ndescription: Design MCP tools and gateway interfaces for the dotcontext server\nskillSlug: api-design\nphases: [P, R]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# MCP Tool Design\n\nGuidance for designing and extending MCP (Model Context Protocol) tools exposed by `AIContextMCPServer` in `src/services/mcp/mcpServer.ts`.\n\n## When to Use\n\n- Adding a new MCP tool or gateway action to the server\n- Refactoring the existing 9-tool surface (5 consolidated gateways + 4 dedicated workflow tools)\n- Changing Zod input schemas for existing tools\n- Adding new resource templates (e.g., `context://`, `file://`, `workflow://`)\n- Designing a new gateway handler in `src/services/mcp/gateway/`\n\n## Architecture Overview\n\nThe MCP server follows a **consolidated gateway** pattern to minimize cognitive load for AI agents:\n\n```\nAIContextMCPServer (mcpServer.ts)\n  registerGatewayTools()\n    -> explore     (read, list, analyze, search, getStructure)\n    -> context     (check, init, fill, fillSingle, listToFill, getMap, buildSemantic, scaffoldPlan)\n    -> sync        (exportRules, exportDocs, exportAgents, exportContext, exportSkills, reverseSync, importDocs, importAgents, importSkills)\n    -> plan        (link, getLinked, getDetails, getForPhase, updatePhase, recordDecision, updateStep, getStatus, syncMarkdown, commitPhase)\n    -> agent       (discover, getInfo, orchestrate, getSequence, getDocs, getPhaseDocs, listTypes)\n    -> skill       (list, getContent, getForPhase, scaffold, export, fill)\n    -> workflow-init / workflow-status / workflow-advance / workflow-manage\n```\n\nEach gateway dispatches to a handler in `src/services/mcp/gateway/<name>.ts`. The handler receives typed params and an options object with `repoPath`.\n\n## Instructions\n\n### 1. Decide: New Gateway vs. New Action\n\n- **New action on existing gateway**: Preferred. Add an action variant to the existing `z.enum([...])` and extend the handler switch.\n- **New dedicated tool**: Only when the domain is distinct enough to warrant its own tool (like `workflow-*` was split out for clarity).\n- **Rule of thumb**: Keep the total tool count under 12 to avoid overwhelming AI agent tool selection.\n\n### 2. Define the Input Schema with Zod\n\nAll MCP tool inputs use Zod v4 schemas (imported from `zod`). Follow the existing pattern:\n\n```typescript\ninputSchema: {\n  action: z.enum(['existingAction', 'newAction'])\n    .describe('Action to perform'),\n  newParam: z.string().optional()\n    .describe('(newAction) What this param does'),\n}\n```\n\nKey conventions:\n- Every parameter gets a `.describe()` with the action prefix in parentheses: `(actionName)`\n- Use `z.enum()` for constrained values, `z.array(z.string())` for lists\n- Mark all action-specific params as `.optional()` since different actions use different params\n- Import shared enums from `../../workflow` (e.g., `PREVC_ROLES`, `AGENT_TYPES`)\n\n### 3. Create the Gateway Handler\n\nAdd a new file in `src/services/mcp/gateway/` or extend an existing one:\n\n```typescript\n// src/services/mcp/gateway/myGateway.ts\nimport { createJsonResponse, createErrorResponse, type MCPToolResponse } from './response';\n\nexport type MyAction = 'doThing' | 'doOther';\n\nexport interface MyParams {\n  action: MyAction;\n  repoPath?: string;\n  // action-specific params\n}\n\nexport interface MyOptions {\n  repoPath: string;\n}\n\nexport async function handleMy(params: MyParams, options: MyOptions): Promise<MCPToolResponse> {\n  switch (params.action) {\n    case 'doThing':\n      return createJsonResponse({ success: true, message: 'Done' });\n    default:\n      return createErrorResponse(`Unknown action: ${params.action}`);\n  }\n}\n```\n\n### 4. Response Format\n\nUse the three helpers from `src/services/mcp/gateway/response.ts`:\n- `createJsonResponse(data)` -- structured JSON for programmatic consumption\n- `createErrorResponse(message)` -- sets `isError: true`\n- `createTextResponse(text)` -- plain text for human-readable output\n\nAll responses conform to `MCPToolResponse` with `content: [{ type: 'text', text }]`.\n\n### 5. Wire It Up in mcpServer.ts\n\nRegister the tool in `registerGatewayTools()` using the `wrap()` helper for automatic action logging:\n\n```typescript\nthis.server.registerTool('my-tool', {\n  description: `Description with action list...`,\n  inputSchema: { /* Zod schemas */ }\n}, wrap('my-tool', async (params): Promise<MCPToolResponse> => {\n  return handleMy(params as MyParams, { repoPath: this.getRepoPath() });\n}));\n```\n\n### 6. Re-export from gatewayTools.ts\n\nAdd your handler, types, and params to the re-export barrel in `src/services/mcp/gatewayTools.ts` and `src/services/mcp/gateway/index.ts`.\n\n### 7. Test\n\nWrite tests in `src/services/mcp/` following the pattern in `mcpServer.test.ts`. Since MCP tools require stdio transport, test the handler functions directly:\n\n```typescript\nimport { handleMy } from './gateway/myGateway';\n\nit('should handle doThing action', async () => {\n  const result = await handleMy(\n    { action: 'doThing' },\n    { repoPath: tempDir }\n  );\n  const payload = JSON.parse(result.content[0].text);\n  expect(payload.success).toBe(true);\n});\n```\n\n## Design Checklist\n\n- [ ] Action names are verbs or verb phrases (e.g., `getStatus`, `buildSemantic`, `exportRules`)\n- [ ] All params have `.describe()` annotations with action prefix\n- [ ] Handler returns `MCPToolResponse` via response helpers\n- [ ] `repoPath` resolution uses `this.getRepoPath(params.repoPath)` for caching\n- [ ] Tool description includes a complete action list for AI discoverability\n- [ ] New types are exported through the gateway barrel file\n- [ ] Action logging works via the `wrap()` helper\n- [ ] Total tool count stays manageable (currently 9)\n\n## Common Pitfalls\n\n- **Do not** use `process.cwd()` directly in handlers; always use the `repoPath` from options\n- **Do not** add required params that are only needed for one action; use `.optional()` and validate inside the handler\n- **Do not** return raw errors; wrap them with `createErrorResponse()` for consistent error shape\n- **Do not** log to stdout in MCP mode; use `process.stderr.write()` to avoid corrupting the MCP protocol\n"
  },
  {
    "path": ".context/skills/bug-investigation/SKILL.md",
    "content": "---\ntype: skill\nname: Bug Investigation\ndescription: Systematic bug investigation and root cause analysis\nskillSlug: bug-investigation\nphases: [E, V]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Bug Investigation\n\nSystematic approach to investigating and fixing bugs in the `@dotcontext/cli` CLI tool.\n\n## When to Use\n\n- A CLI command (`workflow`, `sync`, `mcp`, etc.) produces incorrect output or crashes\n- An MCP tool returns an error or unexpected response\n- Semantic analysis (tree-sitter / LSP) fails on certain codebases\n- Scaffold generation produces malformed frontmatter or missing content\n- Workflow phase transitions behave incorrectly\n- Tests fail unexpectedly after changes\n\n## Project-Specific Investigation Surfaces\n\n| Area | Key Files | Common Issues |\n|------|-----------|---------------|\n| CLI commands | `src/index.ts`, `src/services/*/index.ts` | Commander option parsing, missing args |\n| MCP server | `src/services/mcp/mcpServer.ts`, `src/services/mcp/gateway/*.ts` | repoPath resolution, Zod validation failures |\n| Scaffold generation | `src/generators/*/`, `src/types/scaffoldFrontmatter.ts` | Frontmatter parsing, file path resolution |\n| Scaffold fill via MCP | `src/services/ai/tools/fillScaffoldingTool.ts`, `src/services/mcp/gateway/context.ts` | Missing files, stale context, oversized responses |\n| Semantic analysis | `src/services/semantic/codebaseAnalyzer.ts`, `src/services/semantic/treeSitter/` | tree-sitter optional dep missing, parse failures |\n| Workflow | `src/workflow/`, `src/services/workflow/workflowService.ts` | Phase transition logic, gate checking |\n| i18n | `src/utils/i18n.ts` | Missing translation keys, locale detection |\n| Frontmatter | `src/utils/frontMatter.ts` | v1 vs v2 format confusion, status detection |\n\n## Instructions\n\n### 1. Reproduce the Bug\n\nRun the CLI in dev mode to reproduce:\n\n```bash\n# Direct execution via tsx\nnpx tsx src/index.ts workflow --help\n\n# For MCP-related bugs, capture stderr output\nnpx tsx src/index.ts mcp --verbose 2>mcp-debug.log\n```\n\nFor MCP tool bugs, test the gateway handler directly in a script or test file rather than through the full MCP transport.\n\n### 2. Identify the Service Layer\n\nTrace the call path from the CLI command or MCP tool to the responsible service:\n\n```\nCLI command (src/index.ts)\n  -> Service (src/services/<domain>/)\n    -> Generator (src/generators/) or Util (src/utils/)\n```\n\nFor MCP:\n```\nMCP tool (mcpServer.ts registerGatewayTools)\n  -> Gateway handler (src/services/mcp/gateway/<tool>.ts)\n    -> Service (src/services/<domain>/)\n```\n\n### 3. Check Known Fragile Points\n\n- **Optional dependencies**: `tree-sitter` and `tree-sitter-typescript` are in `optionalDependencies`. Code must handle their absence gracefully. Check `src/services/semantic/treeSitter/treeSitterLayer.ts`.\n- **repoPath resolution**: The MCP server has a 4-level priority: explicit param > cached path > initial path > `process.cwd()`. Bugs often arise from incorrect path resolution. See `getRepoPath()` in `mcpServer.ts`.\n- **Frontmatter parsing**: Two formats exist (v1 simple, v2 scaffold). Check `src/utils/frontMatter.ts` and `isScaffoldFrontmatter()`.\n- **Provider/default detection**: Local model defaults are derived from `src/services/ai/providerFactory.ts` and `src/utils/prompts/smartDefaults.ts`. MCP-hosted generation usually relies on the connected tool's model.\n- **File path handling**: Always use `path.resolve()` or `path.join()`. Watch for relative vs. absolute path confusion, especially in `contextBuilder.ts`.\n\n### 4. Use Existing Tests as Reference\n\nRun the relevant test suite to see what is expected:\n\n```bash\n# Run all tests\nnpm test\n\n# Run specific test file\nnpx jest src/services/mcp/mcpServer.test.ts\n\n# Run tests matching a pattern\nnpx jest --testPathPattern=\"mcp\"\n```\n\nKey test files:\n- `src/services/mcp/mcpServer.test.ts` -- MCP server instantiation\n- `src/services/mcp/mcpServer.test.ts` -- MCP tool registration and dispatch\n- `src/services/semantic/codebaseAnalyzer.test.ts` -- Semantic analysis\n- `src/utils/frontMatter.test.ts` -- Frontmatter parsing\n- `src/utils/contentSanitizer.test.ts` -- Content sanitization\n- `src/workflow/gates/gateChecker.test.ts` -- Workflow gate logic\n\n### 5. Isolate with Logging\n\nThe project uses `ora` for spinners and `chalk` for colored output. For debugging:\n- CLI: Pass `--verbose` flag to enable detailed logging\n- MCP: The `log()` method writes to `process.stderr` when `verbose: true`\n- Services: Add temporary `console.error()` calls (never `console.log()` in MCP mode)\n\n### 6. Fix and Verify\n\nAfter identifying root cause:\n\n1. Write a failing test that demonstrates the bug\n2. Apply the fix in the appropriate service/util\n3. Run the full test suite: `npm test`\n4. Run the CLI manually to verify end-to-end: `npx tsx src/index.ts <command>`\n5. Check TypeScript compilation: `npm run build`\n\n## Common Bug Patterns\n\n### \"Cannot find module\" at Runtime\n- Check if the import uses the `@generators/agents/*` path alias (defined in `tsconfig.json`)\n- Ensure the file is not excluded from compilation by `tsconfig.json`\n\n### MCP Tool Returns Unexpected Error\n- Check Zod schema validation in `mcpServer.ts` -- params may not match the schema\n- Check the gateway handler's switch statement for unhandled actions\n- Verify `repoPath` resolves to a directory with `.context/` scaffolding\n\n### Frontmatter Status Not Detected\n- v2 scaffold files use `scaffoldVersion: \"2.0.0\"` and `status: unfilled|filled`\n- Ensure `isScaffoldFrontmatter()` check happens before v1 fallback\n- Check for BOM or encoding issues in the file header\n\n### Workflow Phase Transition Fails\n- Check gate configuration in `src/workflow/gates/`\n- Verify scale-dependent phase skipping (QUICK skips P and R)\n- Check `src/workflow/prevcConfig.ts` for scale definitions\n\n### MCP Scaffold Fill Produces Empty or Placeholder Guidance\n- Verify `fillSingleFileTool` can read the scaffold and build semantic context\n- Check scaffold structure registration via `getScaffoldStructure()`\n- Confirm `SemanticContextBuilder` is returning project-specific content\n- Check response size and cached-context invalidation behavior\n"
  },
  {
    "path": ".context/skills/code-review/SKILL.md",
    "content": "---\ntype: skill\nname: Code Review\ndescription: Review code quality, patterns, and best practices\nskillSlug: code-review\nphases: [R, V]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Code Review\n\nGuidelines for reviewing code changes in the `@dotcontext/cli` project.\n\n## When to Use\n\n- Reviewing a pull request or diff\n- Validating code during the Review (R) or Validation (V) phases of the PREVC workflow\n- Checking a new service, generator, or utility for adherence to project patterns\n- Evaluating MCP gateway handler implementations\n\n## Project Standards\n\n### TypeScript Configuration\n\n- Target: ES2020, Module: CommonJS\n- Strict mode enabled (`\"strict\": true`)\n- Declarations and source maps generated\n- Path alias: `@generators/agents/*` maps to `src/generators/agents/*`\n\n### Code Organization Patterns\n\n**Service Layer** (22 services in `src/services/`):\n- Each service lives in its own directory under `src/services/`\n- Exports through `index.ts` barrel files\n- Constructor takes an options object or required config\n- Methods are async where I/O is involved\n- Example: `FillService`, `WorkflowService`, `AIContextMCPServer`\n\n**Generators** (`src/generators/`):\n- Separate generators for docs, agents, plans, skills\n- Accept options objects, return typed result objects (e.g., `SkillGeneratorResult`)\n- Use frontmatter serialization from `src/types/scaffoldFrontmatter.ts`\n\n**Utilities** (`src/utils/`):\n- Pure functions or thin wrappers: `frontMatter.ts`, `contentSanitizer.ts`, `gitService.ts`\n- CLI UI abstraction: `cliUI.ts` with `CLIInterface` type\n- i18n: `i18n.ts` with `TranslateFn` type and `en`/`pt-BR` locale support\n\n**MCP Gateway Handlers** (`src/services/mcp/gateway/`):\n- One file per gateway: `explore.ts`, `context.ts`, `sync.ts`, etc.\n- Exports: handler function, params type, options type, action type\n- Uses `createJsonResponse`/`createErrorResponse`/`createTextResponse` from `response.ts`\n- Barrel re-export through `index.ts` and `gatewayTools.ts`\n\n### Review Checklist\n\n#### Architecture\n\n- [ ] New code follows the service/generator/util separation\n- [ ] No direct file I/O in generators -- delegate to services or `fs-extra`\n- [ ] MCP handlers do not use `console.log` (use `process.stderr.write` for debug output)\n- [ ] `repoPath` is never hardcoded; always resolved through options or `getRepoPath()`\n\n#### TypeScript Quality\n\n- [ ] No `any` types (use `unknown` + type guards where needed)\n- [ ] Exported interfaces/types for all public APIs\n- [ ] Async functions return typed Promises, not implicit `any`\n- [ ] Optional dependencies (tree-sitter) have graceful fallbacks\n\n#### Error Handling\n\n- [ ] MCP gateway handlers return `createErrorResponse()` rather than throwing\n- [ ] CLI commands catch errors and display via `chalk`/`ora` with user-friendly messages\n- [ ] File operations use `fs-extra` methods which handle missing directories\n\n#### Naming Conventions\n\n- [ ] Files: camelCase (`fillService.ts`, `codebaseAnalyzer.ts`)\n- [ ] Classes: PascalCase (`SkillGenerator`, `SemanticContextBuilder`)\n- [ ] Interfaces: PascalCase, often with `I`-free naming (`SkillMetadata`, not `ISkillMetadata`)\n- [ ] Constants: UPPER_SNAKE_CASE (`BUILT_IN_SKILLS`, `PREVC_PHASE_ORDER`)\n- [ ] MCP tools: kebab-case (`workflow-init`, `workflow-status`)\n\n#### Testing\n\n- [ ] New services have corresponding `.test.ts` files\n- [ ] Tests use `jest.mock()` for external dependencies (AI providers, file system)\n- [ ] Temp directories created with `fs.mkdtemp()` and cleaned up in `afterEach`\n- [ ] Mock `CLIInterface` and `TranslateFn` for service tests (see `fillService.test.ts`)\n\n#### Frontmatter\n\n- [ ] New scaffold files use v2 format with `scaffoldVersion: \"2.0.0\"`\n- [ ] `status` field is set correctly (`unfilled` for new, `filled` after content generation)\n- [ ] Type-specific fields present (`skillSlug` for skills, `agentType` for agents, etc.)\n\n#### i18n\n\n- [ ] New user-facing strings added to both `en` and `pt-BR` in `src/utils/i18n.ts`\n- [ ] Translation keys follow dot-notation: `'commands.fill.description'`\n- [ ] CLI output uses `t()` function, not hardcoded strings\n\n## Instructions\n\n### Reviewing a Service Change\n\n1. Check the service's public interface -- does it follow the options-object constructor pattern?\n2. Verify barrel exports in `index.ts` are updated\n3. Check for proper error handling (try/catch with typed errors)\n4. Ensure no side effects in constructors (async init should be a separate method)\n5. Look for dependency injection points (e.g., `AIContextMCPServer` accepts `contextBuilder` for testing)\n\n### Reviewing an MCP Tool Change\n\n1. Verify Zod schema has `.describe()` on every param with action prefix\n2. Check that the handler switch covers all actions in the enum\n3. Confirm responses use `createJsonResponse`/`createErrorResponse` consistently\n4. Verify new types are re-exported through `gatewayTools.ts`\n5. Check that `repoPath` uses the caching mechanism via `getRepoPath()`\n\n### Reviewing a Generator Change\n\n1. Check output paths are constructed with `path.join(repoPath, outputDir, ...)`\n2. Verify `force` flag is respected (skip existing files when `false`)\n3. Ensure frontmatter is generated via `createSkillFrontmatter`/`serializeFrontmatter` from `scaffoldFrontmatter.ts`\n4. Check that the result type includes all relevant counts (generated, skipped, etc.)\n\n### Reviewing a Workflow Change\n\n1. Verify phase transitions follow PREVC order (P -> R -> E -> V -> C)\n2. Check scale-dependent phase inclusion (QUICK: E+V only, SMALL: P+E+V, MEDIUM: P+R+E+V, LARGE: all)\n3. Ensure gate checks are enforced unless `autonomous` or `force` is true\n4. Verify workflow status file is written to `.context/workflow/`\n"
  },
  {
    "path": ".context/skills/commit-message/SKILL.md",
    "content": "---\ntype: skill\nname: Commit Message\ndescription: Generate commit messages following conventional commits with scope detection\nskillSlug: commit-message\nphases: [E, C]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Commit Message\n\nGenerate consistent, informative commit messages for the `@dotcontext/cli` project.\n\n## When to Use\n\n- Committing code during the Execution (E) or Confirmation (C) phases\n- After completing a feature, bug fix, or refactoring task\n- When using the `plan({ action: 'commitPhase' })` MCP tool to auto-commit phase outputs\n\n## Commit Format\n\nThis project follows **Conventional Commits** with scope detection:\n\n```\n<type>(<scope>): <description>\n\n[optional body]\n\n[optional footer]\n```\n\n### Types\n\n| Type | Use When |\n|------|----------|\n| `feat` | Adding new functionality (new MCP tool, new generator, new CLI command) |\n| `fix` | Fixing a bug (broken scaffold generation, incorrect path resolution) |\n| `refactor` | Restructuring code without changing behavior (service extraction, gateway consolidation) |\n| `docs` | Documentation only (README, CHANGELOG, `.context/` content) |\n| `test` | Adding or updating tests |\n| `chore` | Build, tooling, dependency updates |\n| `ci` | CI/CD configuration |\n\n### Scopes\n\nDerive scope from the primary area of change:\n\n| Scope | Directory/Area |\n|-------|---------------|\n| `mcp` | `src/services/mcp/` -- MCP server, gateway tools |\n| `cli` | `src/index.ts` -- CLI commands, commander setup |\n| `context` | `src/services/ai/tools/`, `src/services/init/` -- MCP scaffold and semantic-context flows |\n| `init` | `src/services/init/` -- Scaffold initialization |\n| `sync` | `src/services/sync/`, `src/services/export/`, `src/services/import/` |\n| `semantic` | `src/services/semantic/` -- Tree-sitter, LSP, codebase analysis |\n| `workflow` | `src/workflow/`, `src/services/workflow/` -- PREVC phases, gates |\n| `generators` | `src/generators/` -- Doc, agent, plan, skill generators |\n| `skills` | `src/workflow/skills/`, `.context/skills/` |\n| `agents` | `src/generators/agents/`, `.context/agents/` |\n| `plans` | `src/generators/plans/`, `src/services/mcp/gateway/plan.ts`, `.context/plans/` |\n| `i18n` | `src/utils/i18n.ts` -- Translations |\n| `utils` | `src/utils/` -- Frontmatter, git, CLI UI |\n| `ai` | `src/services/ai/` -- AI SDK, providers, tools |\n| `types` | `src/types/`, `src/types.ts` -- Type definitions |\n| `deps` | `package.json` -- Dependency changes |\n\nWhen changes span multiple scopes, use the most significant one or omit the scope.\n\n## Instructions\n\n### 1. Analyze the Diff\n\nLook at what changed:\n- Which services/files are modified?\n- Is this a new feature, a fix, or a refactor?\n- Does it touch user-facing behavior (CLI output, MCP responses)?\n\n### 2. Choose Type and Scope\n\n```\n# New MCP gateway action\nfeat(mcp): add buildSemantic action to context gateway\n\n# Bug fix in scaffold generation\nfix(generators): handle missing frontmatter in skill scaffold files\n\n# Refactoring workflow internals\nrefactor(workflow): extract gate checking into separate GateChecker class\n\n# Updating translations\nfeat(i18n): add pt-BR translations for workflow status messages\n```\n\n### 3. Write the Description\n\n- Use imperative mood (\"add\", \"fix\", \"update\", not \"added\", \"fixes\")\n- Keep under 72 characters\n- Focus on **what** changed and **why**, not implementation details\n- Reference the affected MCP tool or CLI command when relevant\n\n### 4. Add Body for Complex Changes\n\nFor multi-file changes, include context:\n\n```\nfeat(mcp): add skill management gateway with 6 actions\n\nAdd the `skill` gateway tool to the MCP server with actions:\nlist, getContent, getForPhase, scaffold, export, fill.\n\nIncludes Zod schema validation, action logging via wrap(),\nand barrel re-exports through gatewayTools.ts.\n```\n\n### 5. Add Footer When Applicable\n\n```\n# Breaking change\nfeat(mcp)!: consolidate project tools into context gateway\n\nBREAKING CHANGE: Removed standalone project-setup and project-report\ntools. Use context({ action: \"init\" }) and workflow-init instead.\n\n# Issue reference\nfix(semantic): handle missing tree-sitter gracefully\n\nCloses #31\n\n# Co-authored commits (used by plan commitPhase)\nfeat(workflow): implement PREVC phase auto-advance\n\nCo-Authored-By: feature-developer <agent@ai-coders>\n```\n\n## Examples\n\n```bash\n# Small fix\ngit commit -m \"fix(frontmatter): detect v2 scaffold format before v1 fallback\"\n\n# New feature\ngit commit -m \"feat(mcp): add plan commitPhase action for git integration\"\n\n# Dependency update\ngit commit -m \"chore(deps): upgrade @modelcontextprotocol/sdk to 1.25.2\"\n\n# Documentation\ngit commit -m \"docs(skills): fill bug-investigation skill with project-specific content\"\n\n# Test addition\ngit commit -m \"test(context): cover fillSingle guidance for generated scaffolds\"\n\n# Multi-scope refactor\ngit commit -m \"refactor: extract gateway handlers into individual modules\"\n```\n\n## PREVC Phase Commits\n\nWhen using the plan tool's `commitPhase` action, commits are auto-generated with:\n- Stage patterns defaulting to `[\".context/**\"]`\n- Co-Author footer from the agent name\n- Message format: `<type>(<scope>): complete <phase> phase for <plan>`\n\nFor manual phase commits, follow the same pattern:\n\n```\nfeat(workflow): complete Planning phase for add-caching-layer\n\n- Created PRD and tech spec in .context/plans/\n- Defined acceptance criteria and test plan\n- Linked plan to active workflow\n\nCo-Authored-By: planner <agent@ai-coders>\n```\n"
  },
  {
    "path": ".context/skills/documentation/SKILL.md",
    "content": "---\ntype: skill\nname: Documentation\ndescription: Generate and update technical documentation\nskillSlug: documentation\nphases: [P, C]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Documentation\n\nGuidelines for creating and maintaining documentation in the `@dotcontext/cli` project.\n\n## When to Use\n\n- Planning phase (P): Writing PRDs, tech specs, or architecture docs in `.context/plans/`\n- Confirmation phase (C): Updating project docs after feature completion\n- Filling scaffold documentation files in `.context/docs/`\n- Updating the README, CHANGELOG, or CONTRIBUTING guides\n- Writing or updating `.context/` content (docs, agents, skills, plans)\n\n## Documentation Locations\n\n| Location | Purpose | Format |\n|----------|---------|--------|\n| `.context/docs/` | Project knowledge base (generated scaffolds) | Markdown with YAML frontmatter |\n| `.context/agents/` | AI agent playbooks | Markdown with YAML frontmatter |\n| `.context/skills/` | On-demand expertise guides | Markdown with YAML frontmatter |\n| `.context/plans/` | Feature plans and tracking | Markdown with YAML frontmatter |\n| `README.md` | Public-facing project overview | Standard Markdown |\n| `CHANGELOG.md` | Version history (Keep a Changelog format) | Standard Markdown |\n| `CONTRIBUTING.md` | Contributor guidelines | Standard Markdown |\n| `AGENTS.md` | Agent knowledge base reference | Standard Markdown |\n| `CLAUDE.md` | Claude-specific project instructions | Standard Markdown |\n\n## Instructions\n\n### Updating Scaffold Documentation (.context/docs/)\n\nScaffold docs use v2 frontmatter:\n\n```yaml\n---\ntype: documentation\nname: Project Overview\ndescription: High-level project summary\ncategory: core\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n```\n\nCore docs in this project:\n- `project-overview.md` -- Technology stack, entry points, getting started\n- `development-workflow.md` -- Branching, CI, contributing process\n- `testing-strategy.md` -- Jest config, test types, quality gates\n- `tooling.md` -- CLI scripts, dev commands, IDE setup\n- `codebase-map.json` -- Auto-generated semantic analysis output\n\nWhen updating, preserve the frontmatter and set `status: filled`.\n\n### Writing Agent Playbooks (.context/agents/)\n\nEach playbook follows this structure:\n1. **Mission** -- When to use this agent\n2. **Responsibilities** -- Concrete task list\n3. **Best Practices** -- Project-specific guidelines\n4. **Collaboration Checklist** -- Step-by-step workflow\n\nAvailable agents: `code-reviewer`, `bug-fixer`, `feature-developer`, `refactoring-specialist`, `test-writer`, `documentation-writer`, `performance-optimizer`.\n\n### Writing Skill Guides (.context/skills/)\n\nSkills use this structure:\n1. **When to Use** -- Activation triggers\n2. **Instructions** -- Step-by-step procedure\n3. **Examples** -- Concrete, project-specific examples\n4. **Checklist or Guidelines** -- Quick reference\n\n### Updating CHANGELOG.md\n\nFollow Keep a Changelog format. Group changes under version headers:\n\n```markdown\n## [0.7.2]\n\n### Included Pull Requests\n- [#XX](url) - type: description\n\n### Added\n- **Feature Name**: Details\n\n### Changed\n- **Area**: What changed\n\n### Fixed\n- **Bug**: What was fixed\n```\n\n### Writing Plan Documents (.context/plans/)\n\nPlans are created via `context({ action: 'scaffoldPlan', planName: '...' })` or manually. Structure:\n\n```markdown\n---\ntype: plan\nname: feature-name\nplanSlug: feature-name\nstatus: active\n---\n\n# Feature Name\n\n## Goal\nWhat this achieves.\n\n## Phases\n### Phase 1: Planning\n- [ ] Step 1\n- [ ] Step 2\n\n### Phase 2: Execution\n- [ ] Step 1\n```\n\n## Content Guidelines\n\n### For This Project Specifically\n\n- Reference actual file paths: `src/services/mcp/mcpServer.ts`, not \"the MCP server file\"\n- Use the project's terminology: \"scaffolding\" (not \"templates\"), \"fill\" (not \"populate\"), \"gateway\" (not \"endpoint\")\n- Include CLI commands with the dev runner: `npx tsx src/index.ts <command>`\n- Reference PREVC phases by letter and name: \"Execution (E) phase\"\n- Mention the 9-tool MCP surface when discussing architecture\n- Note Node >= 20 requirement and TypeScript strict mode\n\n### Formatting Standards\n\n- Use tables for structured comparisons\n- Use code blocks with language tags for all code samples\n- Keep paragraphs short (3-4 sentences max)\n- Use headings to create scannable structure\n- Frontmatter must be valid YAML between `---` delimiters\n\n### i18n Awareness\n\n- User-facing CLI strings should reference translation keys from `src/utils/i18n.ts`\n- Documentation itself is written in English\n- The project supports `en` and `pt-BR` locales\n\n## Generating Documentation via MCP\n\nStandalone CLI AI generation is not supported. Documentation completion flows through MCP:\n\n- `context({ action: 'fillSingle', filePath: '/path/to/doc.md' })` -- returns semantic context and scaffold structure for one file\n- `context({ action: 'fill', target: 'docs' })` -- returns batched guidance for multiple scaffold files\n- `src/services/autoFill/autoFillService.ts` -- provides static starter content during scaffold generation\n\nThe MCP fill process:\n1. Generates scaffolding through `context({ action: 'init' })`\n2. Builds semantic context via `SemanticContextBuilder`\n3. Returns scaffold structure and instructions to the connected AI tool\n4. Lets the MCP client generate and write the final content\n5. Updates the frontmatter to `status: filled`\n"
  },
  {
    "path": ".context/skills/feature-breakdown/SKILL.md",
    "content": "---\ntype: skill\nname: Feature Breakdown\ndescription: Break down features into implementable tasks\nskillSlug: feature-breakdown\nphases: [P]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Feature Breakdown\n\nBreak down features into concrete, implementable tasks within the `@dotcontext/cli` architecture.\n\n## When to Use\n\n- Starting the Planning (P) phase of a PREVC workflow\n- Scoping a new CLI command, MCP tool, or service\n- Creating a plan document in `.context/plans/`\n- Estimating complexity to choose the right workflow scale (QUICK/SMALL/MEDIUM/LARGE)\n\n## Instructions\n\n### 1. Classify the Feature Scope\n\nDetermine which layers of the codebase are involved:\n\n| Layer | Directory | Examples |\n|-------|-----------|----------|\n| CLI | `src/index.ts` | New command, new flag, output format change |\n| Service | `src/services/<domain>/` | New business logic, data processing |\n| Generator | `src/generators/` | New scaffold type, template changes |\n| MCP | `src/services/mcp/` | New tool, new gateway action, schema change |\n| Workflow | `src/workflow/` | Phase logic, gate rules, skill/agent integration |\n| Utils | `src/utils/` | New utility function, i18n keys, CLI UI changes |\n| Types | `src/types/`, `src/types.ts` | New interfaces, frontmatter types |\n\n### 2. Determine the Workflow Scale\n\nUse these criteria to set the `scale` when calling `workflow-init`:\n\n| Scale | Criteria | Typical Feature |\n|-------|----------|-----------------|\n| QUICK | Single file, no design decisions | Fix typo, update translation key |\n| SMALL | 2-5 files, straightforward logic | Add a flag to existing CLI command |\n| MEDIUM | Multiple services, design decisions needed | New MCP gateway action with service |\n| LARGE | New subsystem, cross-cutting concerns | New generator type, new AI provider |\n\n### 3. Identify the Task Breakdown Pattern\n\nMost features in this project follow one of these patterns:\n\n#### Pattern A: New MCP Gateway Action\n1. Define the action in the Zod schema (`mcpServer.ts`)\n2. Add handler logic in the gateway file (`src/services/mcp/gateway/<tool>.ts`)\n3. Create or extend the backing service (`src/services/<domain>/`)\n4. Update type exports in `gatewayTools.ts` and `gateway/index.ts`\n5. Add tests for the handler\n6. Update MCP tool description string\n\n#### Pattern B: New CLI Command\n1. Define the command with Commander in `src/index.ts`\n2. Create the service in `src/services/<name>/`\n3. Add i18n keys to `src/utils/i18n.ts` (both `en` and `pt-BR`)\n4. Wire CLI options to service calls\n5. Add CLI output via `CLIInterface` from `src/utils/cliUI.ts`\n6. Write tests\n\n#### Pattern C: New Generator/Scaffold Type\n1. Create generator class in `src/generators/<type>/`\n2. Define templates in `src/generators/<type>/templates/`\n3. Add frontmatter type to `src/types/scaffoldFrontmatter.ts`\n4. Integrate with `InitService` for scaffold creation\n5. Integrate with `FillService` for AI content generation\n6. Add to MCP context gateway if applicable\n7. Write generator tests\n\n#### Pattern D: New Workflow Feature\n1. Update types in `src/workflow/types.ts`\n2. Modify phase/gate logic in `src/workflow/`\n3. Update `WorkflowService` in `src/services/workflow/`\n4. Update MCP workflow tools if user-facing\n5. Write tests for phase transitions\n\n### 4. Create the Plan Document\n\nUse the MCP tool or create manually:\n\n```bash\n# Via MCP\ncontext({ action: 'scaffoldPlan', planName: 'my-feature', title: 'My Feature', summary: 'What it does' })\n\n# Manual creation in .context/plans/my-feature.md\n```\n\nStructure each phase with concrete steps:\n\n```markdown\n## Phases\n\n### P - Planning\n- [ ] Define requirements and acceptance criteria\n- [ ] Identify affected services and files\n- [ ] Choose workflow scale\n\n### R - Review (for MEDIUM/LARGE)\n- [ ] Review architecture impact\n- [ ] Document design decisions as ADRs\n- [ ] Get plan approval\n\n### E - Execution\n- [ ] Implement service logic in src/services/<domain>/\n- [ ] Add MCP gateway handler (if applicable)\n- [ ] Update CLI integration (if applicable)\n- [ ] Add i18n keys for new user-facing strings\n- [ ] Update type exports\n\n### V - Validation\n- [ ] Write unit tests with jest.mock() for externals\n- [ ] Run full test suite: npm test\n- [ ] Manual CLI verification: npx tsx src/index.ts <command>\n- [ ] TypeScript compilation check: npm run build\n\n### C - Confirmation (for LARGE)\n- [ ] Update CHANGELOG.md\n- [ ] Update relevant .context/ documentation\n- [ ] Update README if public API changed\n```\n\n### 5. Identify Dependencies and Risks\n\nCommon dependencies to flag:\n- **AI provider changes**: Require testing with actual API keys (not just mocks)\n- **tree-sitter changes**: Optional dependency -- test with and without it installed\n- **MCP protocol changes**: Must maintain backward compatibility with existing clients\n- **Frontmatter format changes**: Must handle both v1 and v2 formats\n- **i18n additions**: Both locales must be updated simultaneously\n\n### 6. Link the Plan to the Workflow\n\nAfter creating the plan, link it to the active workflow:\n\n```\nplan({ action: 'link', planSlug: 'my-feature' })\n```\n\nThis enables phase tracking and the `commitPhase` action for git integration.\n\n## Example: Breaking Down \"Add Caching to Semantic Analysis\"\n\n**Scale**: MEDIUM (multiple files, design decision on cache strategy)\n\n**Tasks**:\n1. P: Define cache invalidation strategy (file mtime vs. content hash)\n2. P: Decide cache storage location (`.context/.cache/` vs. temp dir)\n3. R: Review impact on `CodebaseAnalyzer` and `SemanticContextBuilder`\n4. E: Add cache layer to `src/services/semantic/codebaseAnalyzer.ts`\n5. E: Add cache config to `AnalyzerOptions` type\n6. E: Wire cache option through MCP `context({ action: 'buildSemantic' })` params\n7. V: Test cache hit/miss with `codebaseAnalyzer.test.ts`\n8. V: Test that `cacheEnabled: false` bypasses cache\n9. V: Manual test with large repo to verify performance improvement\n\n**Files affected**: `src/services/semantic/codebaseAnalyzer.ts`, `src/services/semantic/types.ts`, `src/services/mcp/gateway/context.ts`, `src/services/mcp/mcpServer.ts`\n"
  },
  {
    "path": ".context/skills/pr-review/SKILL.md",
    "content": "---\ntype: skill\nname: PR Review\ndescription: Review pull requests against team standards and best practices\nskillSlug: pr-review\nphases: [R, V]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# PR Review\n\nReview pull requests for the `@dotcontext/cli` project against established patterns and quality standards.\n\n## When to Use\n\n- Reviewing a PR before merge\n- Validating changes during the Review (R) or Validation (V) phases\n- Checking that a PR meets the project's architectural and testing standards\n\n## PR Review Process\n\n### 1. Understand the Change Scope\n\nCheck which areas are affected:\n\n```bash\n# View changed files grouped by area\ngit diff main...HEAD --stat\n```\n\nMap changes to project layers:\n- `src/services/mcp/` -- MCP server and gateway tools\n- `src/services/<domain>/` -- Service layer business logic\n- `src/generators/` -- Scaffold generation\n- `src/workflow/` -- PREVC workflow system\n- `src/utils/` -- Shared utilities\n- `src/index.ts` -- CLI command definitions\n- `.context/` -- Documentation and scaffolding content\n\n### 2. Verify the Commit History\n\nThis project uses Conventional Commits:\n\n```\n<type>(<scope>): <description>\n```\n\nCheck that:\n- [ ] Each commit has a valid type (`feat`, `fix`, `refactor`, `docs`, `test`, `chore`)\n- [ ] Scope matches the primary area of change (see commit-message skill for scope list)\n- [ ] Messages are in imperative mood (\"add\", not \"added\")\n- [ ] Breaking changes use `!` suffix and `BREAKING CHANGE:` footer\n\n### 3. Architecture Review\n\n#### Service Layer Compliance\n- [ ] New services follow the directory-per-service pattern under `src/services/`\n- [ ] Services export through `index.ts` barrel files\n- [ ] Constructor accepts options object; async init is a separate method\n- [ ] Dependencies are injectable (like `contextBuilder` in `AIContextMCPServer`)\n\n#### MCP Tool Changes\n- [ ] Zod schemas have `.describe()` on every parameter\n- [ ] Gateway handler covers all actions in the enum (no unreachable branches)\n- [ ] Responses use `createJsonResponse`/`createErrorResponse` from `response.ts`\n- [ ] New types exported through `gatewayTools.ts` barrel\n- [ ] Total MCP tool count remains manageable (currently 9)\n- [ ] Tool description strings include all action names for AI discovery\n\n#### Generator Changes\n- [ ] Generators use v2 frontmatter (`scaffoldVersion: \"2.0.0\"`)\n- [ ] `force` flag is respected (skip existing when false)\n- [ ] Result type includes generated/skipped counts\n- [ ] Templates are in `templates/` subdirectory\n\n#### Workflow Changes\n- [ ] Phase transitions follow PREVC order\n- [ ] Scale-dependent phases are correctly skipped\n- [ ] Gate logic is enforced unless explicitly bypassed\n\n### 4. Code Quality Checks\n\n- [ ] No `any` types; use `unknown` + type guards\n- [ ] No `console.log` in MCP server code (use `process.stderr.write`)\n- [ ] File paths use `path.join()`/`path.resolve()`, never string concatenation\n- [ ] Optional dependencies (tree-sitter) handled with graceful fallback\n- [ ] Error messages are user-friendly (CLI) or structured JSON (MCP)\n\n### 5. Testing Verification\n\n```bash\n# Run all tests\nnpm test\n\n# Build check\nnpm run build\n```\n\n- [ ] New services have `.test.ts` files\n- [ ] Tests mock external dependencies (`jest.mock()` for AI providers, fs)\n- [ ] Temp directories used (`fs.mkdtemp`) and cleaned up (`afterEach`)\n- [ ] Mock `CLIInterface` and `TranslateFn` provided where needed\n- [ ] MCP handler tests call handler functions directly (not through transport)\n\n### 6. i18n Completeness\n\n- [ ] New user-facing strings added to both `en` and `pt-BR` in `src/utils/i18n.ts`\n- [ ] Translation keys use dot notation (`commands.new.description`)\n- [ ] CLI output uses `t()` function, not hardcoded strings\n\n### 7. Documentation Impact\n\n- [ ] `CHANGELOG.md` updated if the change is user-facing\n- [ ] `README.md` updated if public CLI commands or MCP tools changed\n- [ ] `.context/docs/` updated if architecture changed significantly\n- [ ] MCP tool description strings updated if actions added/removed\n\n## Common PR Issues\n\n### Missing Barrel Exports\nNew types or handlers added in `src/services/mcp/gateway/*.ts` but not re-exported through `gateway/index.ts` and `gatewayTools.ts`.\n\n### Inconsistent Error Handling\nMCP handlers that throw instead of returning `createErrorResponse()`. CLI commands that swallow errors silently.\n\n### Untested Edge Cases\n- What happens when `.context/` directory does not exist?\n- What happens when tree-sitter is not installed?\n- What happens with empty or malformed frontmatter?\n- What happens with relative vs. absolute paths?\n\n### Breaking MCP Compatibility\nRenaming or removing an MCP tool action without a migration path. Always add new actions alongside old ones, then deprecate.\n\n## PR Description Template\n\nWhen creating PRs for this project:\n\n```markdown\n## Summary\n- What this PR does (1-3 bullet points)\n\n## Changes\n- List of key changes by file/area\n\n## Test plan\n- [ ] Unit tests pass: `npm test`\n- [ ] Build succeeds: `npm run build`\n- [ ] Manual verification: `npx tsx src/index.ts <command>`\n- [ ] MCP tool tested (if applicable)\n```\n"
  },
  {
    "path": ".context/skills/refactoring/SKILL.md",
    "content": "---\ntype: skill\nname: Refactoring\ndescription: Safe code refactoring with step-by-step approach\nskillSlug: refactoring\nphases: [E]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Refactoring\n\nSafe, systematic refactoring procedures for the `@dotcontext/cli` codebase.\n\n## When to Use\n\n- Extracting logic into a new service or utility\n- Consolidating duplicate code across services or generators\n- Restructuring MCP gateway handlers\n- Improving type safety or removing `any` usage\n- Simplifying complex functions (high cyclomatic complexity)\n- Migrating between patterns (e.g., callback to async/await)\n\n## Pre-Refactoring Checklist\n\nBefore starting any refactoring:\n\n- [ ] All tests pass: `npm test`\n- [ ] Build succeeds: `npm run build`\n- [ ] Create a branch or ensure changes can be reverted\n- [ ] Identify all callers of the code being refactored (use grep or IDE references)\n\n## Common Refactoring Scenarios\n\n### Extracting a New Service\n\nThe project follows a directory-per-service pattern. To extract logic into a new service:\n\n1. Create `src/services/<name>/` directory\n2. Create the service class:\n\n```typescript\n// src/services/cache/cacheService.ts\nexport interface CacheServiceOptions {\n  repoPath: string;\n  cacheDir?: string;\n}\n\nexport class CacheService {\n  private readonly repoPath: string;\n  private readonly cacheDir: string;\n\n  constructor(options: CacheServiceOptions) {\n    this.repoPath = options.repoPath;\n    this.cacheDir = options.cacheDir || '.context/.cache';\n  }\n\n  async get(key: string): Promise<string | null> { /* ... */ }\n  async set(key: string, value: string): Promise<void> { /* ... */ }\n}\n```\n\n3. Create `src/services/<name>/index.ts` barrel:\n\n```typescript\nexport { CacheService, CacheServiceOptions } from './cacheService';\n```\n\n4. Update callers to import from the new service\n5. Write tests in `src/services/<name>/cacheService.test.ts`\n\n### Splitting a Large Gateway Handler\n\nGateway handlers in `src/services/mcp/gateway/` can grow large. To split:\n\n1. Identify action groups that share logic\n2. Extract helper functions within the same file first\n3. If helpers grow, extract into a separate utility or service\n4. Keep the main handler function as a thin dispatcher:\n\n```typescript\nexport async function handleContext(params: ContextParams, options: ContextOptions): Promise<MCPToolResponse> {\n  switch (params.action) {\n    case 'check': return handleCheck(options);\n    case 'init': return handleInit(params, options);\n    // Each case delegates to a focused function\n  }\n}\n```\n\n### Improving Type Safety\n\nCommon type improvements:\n\n1. **Replace `any` with `unknown`**: Then add type guards:\n```typescript\n// Before\nfunction process(data: any) { return data.value; }\n\n// After\nfunction process(data: unknown): string {\n  if (typeof data === 'object' && data !== null && 'value' in data) {\n    return String((data as { value: unknown }).value);\n  }\n  throw new Error('Invalid data shape');\n}\n```\n\n2. **Add discriminated unions** for action-based params:\n```typescript\ntype ContextParams =\n  | { action: 'check'; repoPath?: string }\n  | { action: 'init'; repoPath?: string; type?: string }\n  | { action: 'fill'; repoPath?: string; target?: string };\n```\n\n3. **Extract interface from implementation**: When a class has grown, extract its public interface for dependency injection (pattern used by `AIContextMCPServer` with `contextBuilder`).\n\n### Consolidating Duplicate Logic\n\nCommon duplication spots in this project:\n\n- **Path resolution**: Multiple services resolve `repoPath` + `outputDir`. Extract to `src/services/shared/`.\n- **Frontmatter parsing**: Used in generators, scaffold tools, and MCP flows. Centralized in `src/utils/frontMatter.ts`.\n- **File listing with glob**: Used in semantic analysis, generators, and export. Check `src/services/shared/` for existing utilities.\n- **Error response creation**: MCP handlers should all use `createErrorResponse()` from `response.ts`.\n\n### Updating Barrel Exports\n\nWhen moving or renaming files, update the export chain:\n\n```\nsrc/services/mcp/gateway/<file>.ts  -- implementation\n  -> src/services/mcp/gateway/index.ts  -- gateway barrel\n    -> src/services/mcp/gatewayTools.ts  -- compatibility barrel\n      -> src/services/mcp/mcpServer.ts  -- consumer\n```\n\n## Step-by-Step Refactoring Process\n\n1. **Identify**: Find the code smell (large function, duplication, poor typing)\n2. **Plan**: Determine the target structure and list affected files\n3. **Test**: Ensure existing tests pass and cover the code being changed\n4. **Extract**: Move code to the new location, keeping the old code as a thin wrapper\n5. **Redirect**: Update all callers to use the new location\n6. **Remove**: Delete the old wrapper once all callers are redirected\n7. **Verify**: Run `npm test` and `npm run build`\n8. **Commit**: Use `refactor(<scope>):` prefix\n\n## Refactoring Safety Rails\n\n### Tests as Safety Net\n- Never refactor code that lacks tests. Write tests first.\n- Run `npx jest --testPathPattern=\"<area>\"` after each step.\n\n### Preserve Public APIs\n- MCP tool names and schemas are public API. Do not rename tools or remove actions without a deprecation strategy.\n- CLI command names and flags are public API.\n- Barrel export paths are consumed by other modules. Keep backward-compatible re-exports.\n\n### TypeScript Compiler as Guard\n- `npm run build` will catch broken imports, type mismatches, and missing exports.\n- Enable `noUnusedLocals` temporarily to find dead code after extraction.\n\n### Small Commits\n- Commit after each logical step (extract, redirect, remove).\n- This makes it easy to bisect if something breaks.\n\n## Anti-Patterns to Watch For\n\n- **Big bang refactors**: Changing everything at once. Prefer incremental changes.\n- **Refactoring while adding features**: Do one or the other per commit.\n- **Changing test behavior during refactoring**: Refactoring should not change test expectations.\n- **Forgetting barrel exports**: New file locations need updated export chains.\n- **Breaking MCP backward compatibility**: Clients depend on stable tool names and schemas.\n"
  },
  {
    "path": ".context/skills/security-audit/SKILL.md",
    "content": "---\ntype: skill\nname: Security Audit\ndescription: Security review checklist for code and infrastructure\nskillSlug: security-audit\nphases: [R, V]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Security Audit\n\nSecurity review checklist specific to the `@dotcontext/cli` CLI tool and MCP server.\n\n## When to Use\n\n- Reviewing code that handles API keys or credentials\n- Adding a new AI provider integration\n- Changing the MCP server's file access or path resolution\n- Modifying the export/sync service that writes files\n- Evaluating dependencies for vulnerabilities\n- Before a release (especially major or minor versions)\n\n## Threat Model\n\nThe `@dotcontext/cli` tool has a specific threat surface:\n\n| Asset | Risk | Location |\n|-------|------|----------|\n| AI provider API keys | Exposure via logs, config files, or provider helpers | `src/services/ai/providerFactory.ts`, env vars |\n| File system access | Path traversal, reading outside repo boundaries | `src/services/mcp/gateway/explore.ts`, `readFileTool.ts` |\n| MCP server stdio | Command injection via tool parameters | `src/services/mcp/mcpServer.ts` |\n| Generated scaffold content | Inclusion of secrets in `.context/` output | `src/services/ai/tools/`, `src/services/autoFill/`, `src/generators/` |\n| User repository content | Unintended exposure via exported context | `src/services/export/`, `src/services/sync/` |\n| Dependencies | Supply chain vulnerabilities | `package.json` |\n\n## Security Checklist\n\n### API Key Handling\n\n- [ ] API keys are read from environment variables or CLI flags, never hardcoded\n- [ ] Provider env access goes through `src/services/ai/providerFactory.ts` helpers\n- [ ] Keys are never logged, even in verbose mode\n- [ ] Keys are never written to scaffold files or `.context/` output\n- [ ] Content sanitization (`src/utils/contentSanitizer.ts`) strips potential secrets from generated content\n- [ ] `.env` files are in `.gitignore`\n\n### File System Security\n\n- [ ] File paths are resolved with `path.resolve()` before access\n- [ ] Path traversal is prevented: no `../` sequences that escape the repo root\n- [ ] `readFileTool.ts` validates paths are within the project boundary\n- [ ] MCP explore tool's `read` action bounds access to the repo path\n- [ ] Glob patterns in `list` and `search` actions respect exclude patterns\n- [ ] Generated files are written only to the configured output directory (`.context/` by default)\n\n### MCP Server Security\n\n- [ ] All tool inputs are validated through Zod schemas before processing\n- [ ] `repoPath` parameter is resolved and optionally cached, not used raw\n- [ ] Error messages do not expose internal file paths or stack traces to the client\n- [ ] Logging goes to `process.stderr`, not `process.stdout` (which is the MCP transport)\n- [ ] No shell command execution from MCP tool parameters\n- [ ] `wrapWithActionLogging` does not log sensitive parameter values\n\n### Content Sanitization\n\nThe `contentSanitizer.ts` utility filters generated content:\n- [ ] AI-generated content is sanitized before writing to scaffold files\n- [ ] Patterns matching API keys, tokens, and passwords are detected\n- [ ] File paths in generated docs do not expose absolute system paths\n- [ ] MCP tool responses with file content strip sensitive markers\n\n### Dependency Security\n\n- [ ] `npm audit` shows no high/critical vulnerabilities\n- [ ] `tree-sitter` and `tree-sitter-typescript` are optional dependencies (native binaries)\n- [ ] AI SDK packages (`@ai-sdk/anthropic`, `@ai-sdk/openai`, `@ai-sdk/google`) are from official sources\n- [ ] `@modelcontextprotocol/sdk` is from the official MCP organization\n- [ ] No unnecessary runtime dependencies\n- [ ] `axios` usage (if any) validates URLs and does not follow arbitrary redirects\n\n### Export/Sync Security\n\n- [ ] `exportRules` and `exportContext` actions do not include API keys in output\n- [ ] `reverseSync` validates imported file content before writing to `.context/`\n- [ ] Symlink mode in agent export does not create symlinks outside the repo\n- [ ] Dry run mode (`dryRun: true`) never writes files\n\n### Git Integration\n\n- [ ] `gitService.ts` does not expose credentials\n- [ ] Plan `commitPhase` action stages only files matching the provided patterns\n- [ ] Default stage pattern is `[\".context/**\"]` -- not `[\"**\"]`\n- [ ] Commit messages do not contain sensitive information\n\n## Audit Procedure\n\n### Quick Audit (Before PR Merge)\n\n1. Search for hardcoded strings that look like keys:\n   - Patterns: `/[A-Za-z0-9]{32,}/`, `/sk-[a-zA-Z0-9]+/`, `/AKIA[A-Z0-9]{16}/`\n2. Check that new file I/O uses `path.resolve()` or `path.join()`\n3. Verify no `console.log()` in MCP server code paths\n4. Check that error responses do not leak internal paths\n\n### Full Audit (Before Release)\n\n1. Run `npm audit` and address any high/critical findings\n2. Review all files in `src/services/mcp/gateway/` for input validation\n3. Check `src/utils/contentSanitizer.ts` for completeness of sanitization patterns\n4. Review `src/services/ai/providerFactory.ts` and any prompt/default helpers for key handling\n5. Verify `.gitignore` includes `.env`, `*.pem`, `credentials*`\n6. Check that scaffold output in `.context/` does not contain repo-external paths\n7. Review `src/services/ai/tools/` for file access boundaries\n8. Test with a repo containing sensitive-looking strings to verify sanitization\n\n## Vulnerability Response\n\nIf a security issue is found:\n\n1. Do not commit details to public branches\n2. Fix in a private branch or direct commit\n3. Update `contentSanitizer.ts` if the issue involves generated content\n4. Update Zod schemas if the issue involves input validation\n5. Add a test case that verifies the fix\n6. Document in CHANGELOG under `### Security`\n"
  },
  {
    "path": ".context/skills/test-generation/SKILL.md",
    "content": "---\ntype: skill\nname: Test Generation\ndescription: Generate comprehensive test cases for code\nskillSlug: test-generation\nphases: [E, V]\ngenerated: 2026-03-18\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n\n# Test Generation\n\nGenerate and maintain tests for the `@dotcontext/cli` project using Jest with ts-jest.\n\n## When to Use\n\n- Writing tests for new services, generators, or utilities\n- Adding test coverage for MCP gateway handlers\n- Creating integration tests for CLI commands\n- Validating bug fixes with regression tests\n- During Execution (E) and Validation (V) phases\n\n## Test Configuration\n\n- **Framework**: Jest with ts-jest preset\n- **Config**: `jest.config.js` at project root\n- **Test location**: Co-located with source files (`*.test.ts`) or in `__tests__/` directories\n- **Test match patterns**: `**/__tests__/**/*.ts`, `**/?(*.)+(spec|test).ts`\n- **Root**: `src/`\n\n```bash\n# Run all tests\nnpm test\n\n# Run specific test file\nnpx jest src/services/mcp/mcpServer.test.ts\n\n# Run tests matching a pattern\nnpx jest --testPathPattern=\"mcp\"\n\n# Run with coverage\nnpx jest --coverage\n```\n\n## Test Patterns by Layer\n\n### Service Tests\n\nServices are the most common test target. Follow the pattern in current service and MCP tests:\n\n```typescript\nimport * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { MyService } from './myService';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\n\n// Create temp directory for isolation\nlet tempDir: string;\n\nbeforeEach(async () => {\n  tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-'));\n});\n\nafterEach(async () => {\n  await fs.remove(tempDir);\n});\n\n// Mock CLI interface\nfunction createMockUI(): CLIInterface {\n  return {\n    displayWelcome: jest.fn(),\n    displayProjectInfo: jest.fn(),\n    displayStep: jest.fn(),\n    displaySuccess: jest.fn(),\n    displayWarning: jest.fn(),\n    displayError: jest.fn(),\n    // ... other CLIInterface methods\n  };\n}\n\n// Mock translate function\nconst mockT: TranslateFn = ((key: string) => key) as TranslateFn;\n```\n\nKey patterns:\n- **Temp directories**: Always use `fs.mkdtemp()` and clean up in `afterEach`\n- **Mock CLIInterface**: Provide a mock with `jest.fn()` for each method\n- **Mock TranslateFn**: Simple passthrough `(key) => key`\n- **Mock tool or gateway boundaries**: Use `jest.mock()` at module level for filesystem, prompt loaders, or MCP helpers\n\n### MCP Gateway Handler Tests\n\nTest handlers directly, not through MCP transport:\n\n```typescript\nimport { handleExplore, ExploreParams } from './gateway/explore';\n\ndescribe('handleExplore', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'explore-'));\n    await fs.writeFile(path.join(tempDir, 'test.ts'), 'export const x = 1;');\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('should read a file', async () => {\n    const result = await handleExplore(\n      { action: 'read', filePath: path.join(tempDir, 'test.ts') },\n      { repoPath: tempDir }\n    );\n\n    const payload = JSON.parse(result.content[0].text);\n    expect(payload.success).toBe(true);\n    expect(payload.content).toContain('export const x');\n  });\n\n  it('should return error for missing file', async () => {\n    const result = await handleExplore(\n      { action: 'read', filePath: path.join(tempDir, 'missing.ts') },\n      { repoPath: tempDir }\n    );\n\n    expect(result.isError).toBe(true);\n  });\n});\n```\n\n### Generator Tests\n\nTest that generators produce correct file structures:\n\n```typescript\nimport { SkillGenerator } from './skillGenerator';\n\ndescribe('SkillGenerator', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gen-'));\n  });\n\n  it('should generate skill directories', async () => {\n    const generator = new SkillGenerator({ repoPath: tempDir });\n    const result = await generator.generate({ skills: ['commit-message'] });\n\n    expect(result.generatedSkills).toContain('commit-message');\n    expect(fs.existsSync(path.join(tempDir, '.context/skills/commit-message/SKILL.md'))).toBe(true);\n  });\n\n  it('should skip existing skills when force is false', async () => {\n    const generator = new SkillGenerator({ repoPath: tempDir });\n    await generator.generate({ skills: ['commit-message'] });\n    const result = await generator.generate({ skills: ['commit-message'], force: false });\n\n    expect(result.skippedSkills).toContain('commit-message');\n  });\n});\n```\n\n### Utility Tests\n\nTest pure functions directly:\n\n```typescript\nimport { parseFrontMatter, isScaffoldFrontmatter } from './frontMatter';\n\ndescribe('frontMatter', () => {\n  it('should parse v2 scaffold frontmatter', () => {\n    const content = `---\ntype: skill\nname: Test\nstatus: filled\nscaffoldVersion: \"2.0.0\"\n---\n# Content`;\n\n    const result = parseFrontMatter(content);\n    expect(isScaffoldFrontmatter(result)).toBe(true);\n    expect(result.status).toBe('filled');\n  });\n});\n```\n\n### Workflow Tests\n\nTest phase transitions and gate logic:\n\n```typescript\nimport { GateChecker } from './gateChecker';\n\ndescribe('GateChecker', () => {\n  it('should block P->R transition when plan required but missing', () => {\n    const checker = new GateChecker({ require_plan: true });\n    const result = checker.canAdvance('P', 'R', { hasPlan: false });\n\n    expect(result.allowed).toBe(false);\n    expect(result.reason).toContain('plan');\n  });\n});\n```\n\n## What to Mock\n\n| Dependency | Mock Strategy | Example |\n|-----------|--------------|---------|\n| MCP tools / gateway helpers | `jest.mock()` at module level | MCP service tests |\n| File system (for isolation) | Use `fs.mkdtemp()` temp dirs | All service tests |\n| Prompt loader | `jest.mock('../../utils/promptLoader')` | Return test prompt |\n| CLI interface | Manual mock object | `createMockUI()` helper |\n| Translate function | Passthrough `(key) => key` | All CLI-facing tests |\n| tree-sitter (optional dep) | Test with and without | Semantic analysis tests |\n\n## Test Checklist\n\n- [ ] Tests are co-located with source or in `__tests__/`\n- [ ] File name matches pattern: `<name>.test.ts`\n- [ ] External dependencies are mocked (AI, file system, network)\n- [ ] Temp directories created in `beforeEach`, removed in `afterEach`\n- [ ] Both success and error paths are tested\n- [ ] Edge cases covered: empty input, missing files, malformed frontmatter\n- [ ] Tests run independently (no shared mutable state between tests)\n- [ ] TypeScript types verified at compile time (`npm run build`)\n\n## Integration Tests\n\nFor end-to-end testing of current interfaces, see `src/cli.test.ts` and `src/services/mcp/mcpServer.test.ts`:\n\n```typescript\n// Integration tests verify current CLI help surfaces and MCP tool registration\n```\n\nIntegration tests are slower and may require:\n- Real `.context/` directory structure\n- Actual file writes (in temp dirs)\n- Mocked AI responses (but real service orchestration)\n\nRun integration tests separately if needed:\n```bash\nnpx jest --testPathPattern=\"integration\"\n```\n"
  },
  {
    "path": ".env.example",
    "content": "# OpenRouter API Configuration\nOPENROUTER_API_KEY=your_openrouter_api_key_here\n\n# Optional: Override default model\n# OPENROUTER_MODEL=x-ai/grok-4-fast\n\n# Optional: Custom base URL (rarely needed)\n# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nGOOGLE_API_KEY=..."
  },
  {
    "path": ".github/PULL_REQUEST_BODY.md",
    "content": "## Summary\n\n<!-- Brief description of what this PR does and why -->\n\n## Changes\n\n<!-- List the key changes -->\n\n-\n\n## Breaking Changes\n\n<!-- Remove this section if not applicable -->\n\n- None\n\n## How to Test\n\n<!-- Steps to verify the changes work correctly -->\n\n1.\n\n## Checklist\n\n- [ ] Code follows the project's style guidelines\n- [ ] Tests added/updated for new functionality\n- [ ] All tests pass (`npm test`)\n- [ ] TypeScript compilation clean (`tsc --noEmit`)\n- [ ] No breaking changes to public API (or documented above)\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ main, develop ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    \n    strategy:\n      matrix:\n        node-version: [20.x, 22.x, 23.x, 24.x]\n    \n    steps:\n    - uses: actions/checkout@v4\n    \n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v4\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n    \n    - name: Install dependencies\n      run: npm ci\n    \n    - name: Build\n      run: npm run build\n    \n    - name: Run Tests\n      run: npm test\n    \n    - name: Test CLI\n      run: |\n        node dist/index.js --help\n        node dist/index.js analyze --help\n        node dist/index.js init --help\n        node dist/index.js update --help\n        node dist/index.js preview --help\n\n  packages:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Use Node.js 20.x\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20.x'\n        cache: 'npm'\n\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Build package bundles\n      run: npm run build:packages\n\n    - name: Smoke test package bundles\n      run: npm run smoke:packages\n\n  build-and-test:\n    runs-on: ${{ matrix.os }}\n    \n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        node-version: [20.x, 22.x, 23.x, 24.x]\n    \n    steps:\n    - uses: actions/checkout@v4\n    \n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v4\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n    \n    - name: Install dependencies\n      run: npm ci\n    \n    - name: Build\n      run: npm run build\n    \n    - name: Upload build artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: dist-${{ matrix.os }}-${{ matrix.node-version }}\n        path: dist/\n        overwrite: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n  workflow_dispatch:\n\njobs:\n  verify-mcp-package:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Use Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20.x'\n        registry-url: 'https://registry.npmjs.org'\n        cache: 'npm'\n\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Build package bundles\n      run: npm run build:packages\n\n    - name: Smoke test package bundles\n      run: npm run smoke:packages\n\n    - name: Dry run publish @dotcontext/mcp\n      working-directory: .release/packages/mcp\n      run: npm publish --dry-run --access public\n      env:\n        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n    - name: Upload @dotcontext/mcp bundle\n      uses: actions/upload-artifact@v4\n      with:\n        name: dotcontext-mcp-package\n        path: .release/packages/mcp/\n        overwrite: true\n\n  publish-mcp-package:\n    if: github.event_name == 'push'\n    needs: verify-mcp-package\n    runs-on: ubuntu-latest\n    steps:\n    - name: Use Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20.x'\n        registry-url: 'https://registry.npmjs.org'\n\n    - name: Download @dotcontext/mcp bundle\n      uses: actions/download-artifact@v4\n      with:\n        name: dotcontext-mcp-package\n        path: .release/packages/mcp\n\n    - name: Publish @dotcontext/mcp\n      working-directory: .release/packages/mcp\n      run: npm publish --access public\n      env:\n        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Dependencies\nnode_modules/\n\n# Build output\ndist/\n*.tsbuildinfo\n\n# Environment files\n.env\n.env.*\n!.env.example\n\n# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# OS files\n.DS_Store\nThumbs.db\n\n# IDE\n.vscode/\n.idea/\n*.swp\n*.swo\n\n# Test coverage\ncoverage/\n.nyc_output/\n\n# Output directories\noutput/\n.context-test/\n.release/\n\n# .context runtime state\n.context/plans/\n.context/workflow/**\n.context/harness/sessions/\n.context/harness/traces/\n.context/harness/artifacts/\n.context/harness/contracts/\n.context/harness/workflows/\n.context/harness/replays/\n.context/harness/datasets/\n.context/**/archive/\n\n# AI Context state files\ncontext-log.json\n\n# Temporary files\n*.tmp\n*.temp\n.cache/\n"
  },
  {
    "path": ".npmignore",
    "content": "# Source files\nsrc/\n*.ts\n\n# Configuration files\ntsconfig.json\n.env\n.env.*\n!.env.example\n\n# Development files\nnode_modules/\n*.log\n.DS_Store\n\n# CI/CD\n.github/\n.gitignore\n\n# Test files\n**/*.test.*\n**/*.spec.*\ncoverage/\njest.config.*\n\n# Build files\n*.tsbuildinfo\n\n# IDE\n.vscode/\n.idea/\n*.swp\n*.swo\n\n# Documentation source\ndocs/\n*.md\n!README.md\n\n# Examples\nexamples/\nai-context-output/\ndotcontext-output/"
  },
  {
    "path": "AGENTS.md",
    "content": "# Dotcontext Agent Instructions\n\nThis repository exposes one runtime through three surfaces:\n\n```text\ncli -> harness <- mcp\n```\n\nAgents working in this repo should preserve that separation.\n\n## Architectural Intent\n\n- `cli` is the operator interface\n- `harness` is the reusable execution runtime\n- `mcp` is the transport adapter for AI tools\n\nIf a change affects reusable execution logic, prefer `src/services/harness`.\nIf a change affects protocol shape or request handling, prefer `src/services/mcp/gateway`.\nIf a change affects user-facing commands or installation flows, prefer `src/cli` and `src/services/cli`.\n\n## Repository References\n\n- `README.md` explains what dotcontext is and why it exists\n- `docs/GUIDE.md` explains how to use it\n- `ARCHITECTURE.md` explains how the harness works\n- `CONTRIBUTING.md` explains contributor workflow\n- `CHANGELOG.md` tracks release-facing changes\n\n## Expected Validation\n\nFor code changes:\n\n```bash\nnpm run build\nnpm test -- --runInBand\n```\n\nFor packaging or release-surface changes:\n\n```bash\nnpm run build:packages\nnpm run smoke:packages\n```\n\n## MCP Install Changes\n\nIf you touch `mcp:install`, keep these aligned:\n\n- `src/services/cli/mcpInstallService.ts`\n- `src/services/cli/mcpInstallService.test.ts`\n- `README.md`\n- `docs/GUIDE.md`\n- `CHANGELOG.md`\n\nDocumentation must reflect the actual config written by the installer.\n"
  },
  {
    "path": "ARCHITECTURE.md",
    "content": "# Dotcontext Harness Architecture\n\nThis document explains how dotcontext works as a harness engineering runtime, how the current architecture is consolidated, and how the repository is organized after the `cli -> harness <- mcp` split.\n\n## Core Model\n\nDotcontext now treats harness engineering as a first-class runtime concern.\n\n- `cli` is the operator-facing interface\n- `harness` is the reusable runtime and domain layer\n- `mcp` is the transport adapter that exposes the harness to AI tools\n\n```mermaid\nflowchart LR\n    User[\"Human / Operator\"] --> CLI[\"dotcontext/cli\"]\n    AITool[\"AI Tool / MCP Client\"] --> MCP[\"dotcontext/mcp\"]\n\n    CLI --> H[\"dotcontext/harness\"]\n    MCP --> H\n\n    H --> WF[\"Workflow Runtime\"]\n    H --> RS[\"Runtime State\"]\n    H --> QC[\"Sensors + Backpressure\"]\n    H --> TC[\"Task Contracts + Handoffs\"]\n    H --> PO[\"Policy Engine\"]\n    H --> RP[\"Replay Service\"]\n    H --> DS[\"Failure Dataset Builder\"]\n\n    RS --> Store[\".context/harness/*\"]\n    WF --> Store\n    QC --> Store\n    TC --> Store\n    RP --> Store\n    DS --> Store\n    PO --> Policy[\".context/harness/policy.json\"]\n```\n\n## Consolidated Boundaries\n\nThe current architecture is intentionally asymmetric:\n\n```text\ncli -> harness <- mcp\n```\n\nThat means:\n\n- `harness` does not depend on `cli`\n- `harness` does not depend on `mcp`\n- `cli` and `mcp` are adapters over the same runtime\n- transport concerns stay outside the core domain\n\nThis keeps the harness reusable for future adapters such as HTTP, workers, or SDKs.\n\n## Runtime Responsibilities\n\n### 1. Runtime State\n\nThe harness persists durable execution state under `.context/harness`.\n\n- sessions\n- traces\n- artifacts\n- checkpoints\n- contracts\n- replays\n- datasets\n- policy documents\n\n### 2. Guides\n\nThe current guide layer is implemented primarily through:\n\n- workflow structure\n- task contracts\n- handoff contracts\n- policy rules\n\nThese mechanisms constrain what the agent can do and what evidence it must produce.\n\n### 3. Sensors\n\nSensors are the feedback layer.\n\n- they run checks\n- persist evidence\n- produce blocking or non-blocking findings\n- feed backpressure into task and workflow completion\n\n### 4. Replay and Dataset\n\nReplay and dataset generation turn runtime history into inspectable artifacts.\n\n- session replay reconstructs the execution timeline\n- failure datasets cluster repeated breakdowns\n- this is the basis for future evaluation and learning loops\n\n## Execution Lifecycle\n\nThe harness lifecycle is now explicit and durable.\n\n```mermaid\nsequenceDiagram\n    participant U as User / AI Tool\n    participant A as CLI or MCP Adapter\n    participant H as Harness Runtime\n    participant S as State Store\n\n    U->>A: Start task / workflow action\n    A->>H: Create or load session\n    H->>S: Persist session state\n\n    A->>H: Define task contract\n    H->>S: Persist contract\n\n    A->>H: Run sensors / add artifacts / append traces\n    H->>S: Persist evidence and execution events\n\n    A->>H: Evaluate backpressure + task completion\n    H->>H: Apply policies and completion rules\n\n    alt completion allowed\n        H->>S: Checkpoint or complete session\n        A-->>U: Success\n    else blocked\n        H-->>A: Blocking reasons\n        A-->>U: Action required\n    end\n\n    A->>H: Replay session / build failure dataset\n    H->>S: Read runtime history\n    H-->>A: Replay or dataset output\n```\n\n## Current Repository Shape\n\n```mermaid\nflowchart TD\n    Root[\"Repository Root\"]\n\n    Root --> CLI[\"src/cli\"]\n    Root --> Harness[\"src/harness\"]\n    Root --> MCP[\"src/mcp\"]\n    Root --> Services[\"src/services\"]\n    Root --> Scripts[\"scripts\"]\n    Root --> Context[\".context\"]\n\n    CLI --> CLIBoundary[\"CLI boundary exports\"]\n    Harness --> HarnessBoundary[\"Harness boundary exports\"]\n    MCP --> MCPBoundary[\"MCP boundary exports\"]\n\n    Services --> HarnessServices[\"services/harness\"]\n    Services --> WorkflowServices[\"services/workflow\"]\n    Services --> MCPGateway[\"services/mcp/gateway\"]\n    Services --> Shared[\"services/shared\"]\n\n    HarnessServices --> Runtime[\"runtimeStateService\"]\n    HarnessServices --> Sensors[\"sensorsService\"]\n    HarnessServices --> Contracts[\"taskContractsService\"]\n    HarnessServices --> Execution[\"executionService\"]\n    HarnessServices --> Policy[\"policyService\"]\n    HarnessServices --> Replay[\"replayService\"]\n    HarnessServices --> Dataset[\"datasetService\"]\n\n    Scripts --> Packaging[\"build-package-bundles / smoke / release\"]\n    Context --> Docs[\"docs / plans / agents / skills\"]\n```\n\n## Packaging Model\n\nThe codebase is still developed in one repository, but the runtime is now organized to package cleanly into three surfaces.\n\n```mermaid\nflowchart LR\n    Repo[\"Monorepo Source\"] --> Build[\"npm run build:packages\"]\n    Build --> B1[\".release/packages/cli\"]\n    Build --> B2[\".release/packages/harness\"]\n    Build --> B3[\".release/packages/mcp\"]\n\n    B1 --> Smoke[\"npm run smoke:packages\"]\n    B2 --> Smoke\n    B3 --> Smoke\n\n    Smoke --> Release[\"npm run release:packages:*\"]\n    Release --> Local[\".release/releases/<version>\"]\n```\n\n## Why This Matches Harness Engineering\n\nThis architecture aligns with harness engineering in four practical ways:\n\n1. The model is no longer the center of the system; the runtime is.\n2. The core controls are explicit: guides, sensors, policies, contracts, and backpressure.\n3. Execution is durable and inspectable through traces, replays, and datasets.\n4. Transport is separated from control logic, so the harness can evolve without rewriting the CLI or MCP surface.\n\n## Current Status\n\nThe current consolidated architecture already supports:\n\n- durable harness sessions\n- workflow-bound execution\n- policy-controlled mutations\n- evidence-driven completion checks\n- replayable execution history\n- clustered failure datasets\n- local packaging and smoke validation for `cli`, `harness`, and `mcp`\n\nThe next layer of evolution is not more boundary work. It is product depth on top of this runtime:\n\n- stronger default policies\n- richer evaluator flows\n- replay-driven benchmarking\n- multi-agent templates\n- publishable package distribution\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [v0.9.2]\n\n### Fixed\n\n- **Local release bundles are now executable through `npx` from the bundle root**\n  - `build:packages` now generates package-local `node_modules/.bin` shims for bin-bearing bundles\n  - `smoke:packages` now verifies `npm exec` against the generated `cli` and `mcp` bundle roots instead of checking only static manifest fields\n\n## [v0.9.1]\n\n### Fixed\n\n- **`@dotcontext/mcp install` now honors the guided install flow**\n  - Running `npx @dotcontext/mcp install` in an interactive terminal now opens the same tool-selection prompt used by the main CLI compatibility command\n  - Non-interactive runs without an explicit tool continue to fall back to `all` detected tools\n  - The standalone MCP package now prints the post-install restart hint after successful installs, matching the CLI flow\n\n## [0.9.0] - 2026-04-11\n\n### Why this release matters\n\n`0.9.0` is the release where `dotcontext` stops being described primarily as a CLI plus MCP installer and becomes a harness engineering runtime for agent-driven software delivery.\n\nThe reason for this shift is practical:\n\n- The project already had three distinct responsibilities in the same codebase: operator commands, MCP transport, and runtime orchestration/state.\n- Keeping those concerns blurred made the product harder to evolve, harder to reason about, and harder to make reliable for long-running agent workflows.\n- The new harness layer makes the execution model explicit: sessions, traces, artifacts, sensors, task contracts, handoffs, policy, replay, and workflow state are now first-class runtime concerns instead of incidental side effects spread across the CLI and MCP adapters.\n\nIn product terms, this release establishes the architecture the project was already converging toward:\n\n- `dotcontext` CLI for operator-facing sync, reverse-sync, install, and local admin flows\n- `dotcontext/harness` as the reusable runtime and control plane\n- `dotcontext/mcp` as the transport adapter agents talk to\n\nThis release does not just add new commands. It changes what the product is for:\n\n- from scaffolding and sync around `.context`\n- to a runtime that can govern how agents operate, validate work, exchange artifacts, persist execution state, and expose that behavior consistently through MCP and workflow layers\n\nThat is why many of the changes below are structural. The value of `0.9.0` is not only new functionality, but that the system now has a coherent runtime model instead of multiple partially overlapping ones.\n\n### Added\n\n- **Harness runtime foundation**: added a transport-agnostic harness layer with durable sessions, traces, artifacts, and checkpoints under `.context/harness`\n  - New runtime state service for persistent execution state shared by CLI, workflow, and MCP adapters\n  - Session quality snapshots with backpressure evaluation and task completion checks\n\n- **Current tool-surface support in sync/export flows**\n  - Added primary-surface support for `GEMINI.md` exports/imports for Gemini CLI\n  - Added GitHub Copilot skill export/import support via `.github/skills`\n  - Added Windsurf skill export/import support via `.windsurf/skills`\n  - Added support for GitHub Copilot agent filename compatibility using `.agent.md`\n  - Added support for per-tool agent filename suffixes and per-tool rules file extensions in the unified tool registry\n\n- **Bootstrap policy scaffolding**: `context init` now also materializes a project-local harness policy document\n  - Repositories now start with explicit `.context/harness/policy.json` instead of relying on implicit runtime defaults\n  - Bootstrap readiness now distinguishes configuration readiness from runtime readiness\n\n- **Harness quality controls**: added first-class sensors, backpressure policies, task contracts, and handoff contracts\n  - Sensors can persist execution evidence and block completion when critical checks fail\n  - Task contracts now define required sensors, artifacts, outputs, and acceptance criteria\n  - Handoff contracts provide explicit evidence and artifact tracking between agent roles\n\n- **Replay and dataset MVP**: added replayable harness sessions and failure dataset generation\n  - Durable session replay with ordered event logs across traces, artifacts, checkpoints, sensors, tasks, and handoffs\n  - Failure dataset builder with repeated-signature clustering for sensor, task, session, and trace failures\n\n- **Harness policy engine**: added persistent policy documents and rule-based runtime authorization\n  - Policy rules can target `tool`, `action`, `path`, and `risk`\n  - Support for `allow`, `deny`, and `require_approval` effects\n  - MCP harness gateway now supports `getPolicy`, `setPolicy`, `resetPolicy`, `registerPolicy`, `listPolicies`, and `evaluatePolicy`\n\n- **Package productization workflow**: added local packaging, smoke validation, and release preparation for split package distribution\n  - `build:packages` prepares bundle outputs for `cli`, `harness`, and `mcp`\n  - `smoke:packages` validates generated bundle manifests and exports\n  - `release:packages:patch|minor|major` prepares local release directories in `.release/releases/<version>`\n\n### Changed\n\n- **Repositioned generated Q&A as an optional helper layer instead of a default context path**\n  - `context init` no longer enables `generateQA` by default\n  - MCP and README descriptions now emphasize semantic context and snapshots as the primary codebase-understanding surface\n  - `searchQA` is documented as keyword ranking over generated Q&A helper docs, not embedding-based semantic search\n\n- **Architectural split is now explicit**: the codebase now follows `cli -> harness <- mcp`\n  - `src/cli` is the operator-facing boundary\n  - `src/harness` is the reusable runtime/domain boundary\n  - `src/mcp` is the transport adapter boundary\n\n- Updated CLI-facing documentation to reflect current public and hidden command surfaces, including `admin workflow`, `admin skill`, `sync`, `reverse-sync`, and the newer AI-tool context file conventions.\n\n- **Sync/export surfaces updated toward current conventions**\n  - Cursor rules directory exports now use `.mdc` files\n  - Codex rules now export to `AGENTS.md`, while still importing legacy `.codex/instructions.md`\n  - Google Antigravity now exports to `.agents/rules` and `.agents/workflows` while continuing to import legacy `.agent/*` layouts\n  - Quick sync defaults and prompts now reflect the newer rules/skills target set for GitHub Copilot, Gemini, Windsurf, Codex, and Antigravity\n\n- **Skill scaffolding now produces stronger source content and cleaner exported skills**\n  - Scaffolded `.context/skills/*/SKILL.md` files now start with actionable starter sections (`Workflow`, `Examples`, `Quality Bar`, `Resource Strategy`) instead of near-empty placeholders\n  - Built-in skill templates now bias toward concise procedural guidance, progressive disclosure, and trigger language in frontmatter descriptions instead of `When to Use` sections in the body\n  - Exported AI-tool skills now use portable frontmatter with only `name` and `description`, improving compatibility with external skill runtimes\n\n- **PREVC workflow state is now harness-native**\n  - Canonical workflow state and workflow-to-session binding now live together under `.context/harness/workflows/prevc.json`\n  - Legacy workflow-side binding files are treated only as migration inputs and are no longer part of the active runtime model\n\n- **Workflow integration now uses harness runtime controls**\n  - PREVC workflow initialization creates and binds harness sessions\n  - Workflow advance can be blocked by harness completion checks\n  - Workflow management records artifacts, checkpoints, tasks, handoffs, and sensor runs through the harness runtime\n\n- **Failure dataset generation is now canonical and side-effect free**\n  - Failure datasets are generated exclusively through `datasetService`\n  - Replay generation for datasets no longer persists replay artifacts as a side effect\n\n- **Package root is now library-safe**\n  - The package root export now points at the CLI boundary instead of the process-bootstrapping entrypoint\n  - MCP shutdown handling was centralized to avoid duplicate signal handlers and race conditions during server stop\n\n- **MCP package surface is now explicit**\n  - Preferred MCP installation now uses `npx @dotcontext/mcp install`\n  - Generated AI-tool configs now start the server from `@dotcontext/mcp` instead of routing through the CLI package\n  - The dedicated MCP package now supports both `install` and default server startup flows\n\n- **MCP gateway surface expanded**\n  - Added explicit harness operations for replay, dataset building, and policy document management\n  - Harness-related gateway handlers are thinner and delegate to transport-agnostic services\n\n- **Removed dormant standalone AI SDK internals**\n  - Removed the unused `src/services/ai` runtime, provider auto-detection, and the old standalone provider dependency path\n  - Moved the active scaffolding and explore helpers into `src/services/harness/contextTools.ts` to keep reusable execution logic inside the harness boundary\n  - Kept semantic analysis centered on the active `tree-sitter` and optional LSP pipeline instead of bundling unused provider SDK dependencies\n\n### Fixed\n\n- **Policy enforcement consistency**\n  - Aligned workflow and MCP policy handling around a single harness policy model\n  - Fixed policy document operations through the MCP harness gateway\n  - Sensor execution now goes through harness policy authorization\n  - MCP `evaluatePolicy` now maps target/action/path/approval inputs correctly\n\n- **Sync/export metrics and auditability**\n  - `SyncService.run()` now returns aggregate results instead of only printing UI output\n  - `ContextExportService` now reports the real number of exported agent files instead of a placeholder count\n  - `ReverseQuickSyncService` now uses real import results for rules and agents instead of detection counts\n\n- **Reverse-sync signal quality**\n  - Skill detection now imports only canonical `SKILL.md` files instead of treating any markdown file under skill directories as a skill\n  - Agent import normalizes `.agent.md` filenames back into canonical `.context/agents/*.md`\n  - Rules imported from non-markdown sources now normalize to `.md` targets in `.context/docs`\n  - Skill merge mode now avoids re-appending identical imported content\n\n- **Skill fill/export consistency**\n  - Skill refill detection now respects scaffold `status: unfilled` metadata instead of weak placeholder/size heuristics\n  - Exporting built-in skills now falls back to the stronger canonical template body when a scaffolded source file still has no meaningful body content\n\n- **Packaging validation coverage**\n  - Added smoke checks for generated `cli`, `harness`, and `mcp` package bundles before local release preparation\n\n- **Workflow-plan approval drift**\n  - `workflow-manage approvePlan` now persists approval metadata into workflow plan tracking instead of mutating transient in-memory refs\n  - Re-linking an already approved plan now preserves approval metadata\n\n- **Headless MCP installation**\n  - `mcp:install` no longer no-ops in non-interactive contexts when no installed tools are detected\n  - Headless installs now fall back deterministically to supported tool targets\n\n- **MCP execution logging**\n  - MCP activity now resolves workflow session binding through canonical harness state before appending traces\n  - Logging falls back cleanly when stale workflow bindings point at missing sessions\n\n### Technical Details\n\n#### New Files\n- `src/services/harness/policyService.ts` — persistent harness policy engine and authorization rules\n- `src/services/harness/replayService.ts` — durable replay generation for harness sessions\n- `src/services/harness/datasetService.ts` — failure dataset and cluster generation\n- `src/services/harness/policyService.test.ts` — policy engine tests\n- `src/services/harness/replayService.test.ts` — replay service tests\n- `src/services/harness/datasetService.test.ts` — dataset service tests\n- `scripts/smoke-package-bundles.js` — package smoke validation for generated bundles\n- `scripts/release-packages.js` — local release preparation for split package outputs\n\n#### Modified Files\n- `src/index.ts` — consumes explicit CLI/MCP boundaries instead of deep service imports\n- `src/cli/index.ts` — operator-facing boundary exports\n- `src/harness/index.ts` — reusable harness boundary exports\n- `src/mcp/index.ts` — MCP transport boundary exports\n- `src/services/workflow/workflowService.ts` — workflow-to-harness session binding and completion enforcement\n- `src/services/mcp/gateway/harness.ts` — harness runtime, replay, dataset, and policy operations\n- `src/services/mcp/mcpServer.ts` — expanded harness tool schema\n- `package.json` — added split exports and packaging/release scripts\n\n## [0.8.0] - 2026-03-21\n\n### Changed\n- **BREAKING: Renamed package from `@ai-coders/context` to `@dotcontext/cli`**\n  - CLI command changed from `ai-context` to `dotcontext`\n  - MCP server name changed from `ai-context` to `dotcontext`\n  - Why: The previous name caused frequent confusion with Context7 during\n    prompt-based installation and search. \"context\" is too generic in the\n    AI/LLM space. The new name \"dotcontext\" is unique, searchable, and\n    directly references the `.context/` directory convention that is the\n    core of this tool.\n  - Migration: Replace `ai-context` with `dotcontext` in your shell aliases\n    and MCP configurations. Re-run `npx dotcontext mcp:install` to\n    update all tool integrations.\n\n- **BREAKING: Standalone CLI no longer generates context or plans**\n  - Context creation, filling, and refresh are now MCP-only — your AI tool provides the LLM\n  - Plan initialization and management moved to MCP tools (`context` and `plan` gateways)\n  - The standalone CLI is now focused on workflow management, sync, reverse sync, imports, and MCP setup\n  - Migration: Run `npx dotcontext mcp:install` and use your MCP-connected AI tool for context and plan operations\n\n### Added\n\n- **Themed Inquirer Prompts**: Applied custom theme to all interactive prompts via new `themedPrompt.ts` wrappers (`themedSelect`, `themedConfirm`, `themedInput`, `themedPassword`, `themedCheckbox`), replacing raw inquirer calls with consistently styled interactions using the project's two-tone color scheme.\n\n- **\"View Pending Files\" Option**: When the CLI detects unfilled scaffold files, users can now see which specific files need content before deciding to fill them.\n\n- **Smart Defaults Transparency**: The interactive flow now displays detected project information on startup (e.g., \"Detected: TypeScript project, openrouter provider configured\") instead of silently using auto-detected values.\n\n- **API Key Format Validation**: Lightweight format checks warn users when an API key doesn't match the expected prefix for a provider (e.g., `sk-` for OpenAI, `sk-ant-` for Anthropic). Non-blocking warnings only.\n\n- **\"Back\" Navigation in Prompt Flows**: Added escape options in `promptAnalysisOptions()` and `promptLLMConfig()` so users can return to the previous menu instead of being forced through multi-step flows.\n\n- **Comprehensive .context Content**: Rewrote all scaffolding files with project-specific content:\n  - 4 documentation guides (project-overview, development-workflow, testing-strategy, tooling)\n  - 7 agent playbooks with codebase-specific workflows and file references\n  - 10 skill files (repurposed api-design to MCP Tool Design)\n  - 3 QA guides (getting-started, project-structure, error-handling)\n  - 1 development plan (simplify-interactive-cli)\n\n- **Skills System**: Full skill scaffolding exported to `.claude/skills/`, `.gemini/skills/`, `.codex/skills/`\n\n- **Multi-tool Context Export**: Context now syncs to Claude Code, Cursor, GitHub Copilot, Codex, Windsurf, and Gemini\n\n- **Codex MCP Install Support**: `mcp:install` now supports Codex CLI directly\n  - Writes MCP configuration to `.codex/config.toml`\n  - Uses the documented `[mcp_servers.dotcontext]` TOML configuration block\n  - Brings Codex in line with other first-class MCP install targets\n\n- **GitIgnore Integration in FileMapper**: Automatic `.gitignore` respect prevents stack overflow in large repositories\n  - New `GitIgnoreManager` class with spec-compliant `.gitignore` parsing via `ignore` npm package\n  - O(1) cached lookups with hierarchical `.gitignore` loading from repo root\n  - Graceful fallback to existing hardcoded excludes when no `.gitignore` found\n  - Integrated into `FileMapper.getRepoStructure()` before glob scanning\n\n- **Path Traversal Protection for MCP Server**: Security hardening for all file operations\n  - New `PathValidator` class with null byte, URL-encoding, and traversal detection\n  - `SecurityError` class with forensics metadata (attempted path, attack type)\n  - Validates `filePath`, `rootPath`, and `cwd` params in `wrapWithActionLogging()` before tool execution\n\n- **Semantic Context Cache**: In-memory caching for `SemanticContextBuilder` output\n  - TTL-based expiration (default 5 minutes) with directory mtime invalidation\n  - Per-repo and global invalidation methods\n  - Integrated into MCP `registerResources()` context handler\n\n- **CLI Modular Architecture**: Extracted command groups from monolithic `index.ts`\n  - New `CLIDependencies` interface for dependency injection\n  - Skill commands (5 subcommands) extracted to `src/cli/commands/skillCommands.ts`\n  - Workflow commands (6 subcommands) extracted to `src/cli/commands/workflowCommands.ts`\n  - `index.ts` reduced from 2818 to 2478 lines\n\n### Fixed\n\n- **`needsFill()` false positives**: Fixed bug where `needsFill()` matched `status: unfilled` in document body content (e.g., code examples in agent playbooks) instead of only checking the YAML frontmatter block. The function now parses only the frontmatter between `---` delimiters.\n\n- **`configSummary` i18n**: `displayConfigSummary()` now uses the `_t()` translation function instead of hardcoded English labels (\"Config:\", \"Options:\", \"Yes\", \"No\").\n\n- **Missing i18n key**: Added `agent.type.skill` translation key (en + pt-BR) that was referenced but undefined.\n\n- **Guide Consistency**: Updated the user guide to match the real CLI surface\n  - Clarifies that quick sync still exists in the interactive CLI\n  - Removes the mismatch where Codex was described as an MCP install target before it was actually supported\n\n- **Frontmatter-Safe Fill Pipeline**: 100% preservation of YAML frontmatter during fill operations\n  - `needsFill()` now reads 15 lines (was 3) to detect `status:` in v2 scaffold format\n  - `processTarget()` and `processTargetWithAgent()` now preserve frontmatter with `status: filled` update\n  - `collectTargets()` filters by `needsFill()` with `--force` override to prevent re-filling\n  - Added `force` option to `FillCommandFlags` and `ResolvedFillOptions`\n\n### Security\n\n- Path traversal attacks via `../`, URL encoding (`%2e%2e`), and null bytes now blocked in MCP tool handlers\n- Audit logging for security events with forensics metadata\n\n### Performance\n\n- Context generation 80-95% faster for unchanged files via semantic caching\n- Eliminated stack overflow crashes in repositories with large unignored directories\n\n### Technical Details\n\n#### New Files\n- `src/utils/gitignoreManager.ts` — GitIgnoreManager with hierarchical `.gitignore` loading\n- `src/utils/gitignoreManager.test.ts` — 18 tests\n- `src/utils/pathSecurity.ts` — PathValidator with comprehensive sanitization\n- `src/utils/pathSecurity.test.ts` — 18 tests\n- `src/services/semantic/contextCache.ts` — In-memory TTL cache with mtime invalidation\n- `src/services/semantic/contextCache.test.ts` — 13 tests\n- `src/cli/types.ts` — CLIDependencies interface\n- `src/cli/commands/index.ts` — Barrel export\n- `src/cli/commands/skillCommands.ts` — Extracted skill subcommands\n- `src/cli/commands/workflowCommands.ts` — Extracted workflow subcommands\n- `src/tests/integrity/postRefactoringIntegrity.test.ts` — 26 integration tests\n\n#### Modified Files\n- `src/utils/fileMapper.ts` — GitIgnoreManager integration\n- `src/utils/frontMatter.ts` — `needsFill()` increased to 15 lines\n- `src/services/fill/fillService.ts` — Frontmatter preservation, `force` option, `needsFill` filtering\n- `src/services/mcp/mcpServer.ts` — PathValidator + ContextCache integration\n- `src/index.ts` — Replaced inline skill/workflow commands with modular imports\n- `package.json` — Added `ignore` dependency\n\n### Removed\n\n- **Irrelevant QA docs**: Removed `api-endpoints.md` (no REST API), `deployment.md` (npm package, not deployed service), and `testing.md` (redundant with `testing-strategy.md`).\n\n### Acknowledgements\n\nSpecial thanks to [@LorranHippolyte](https://github.com/LorranHippolyte) and [@jeansassi](https://github.com/jeansassi) for their massive contributions through pull requests.\n\n## [0.7.1]\n\n### Included Pull Requests\n\n- [#31](https://github.com/vinilana/dotcontext/pull/31) - fix: exclude venv from semantic analysis\n  - Excludes `venv/` and `.venv/` from semantic analysis by default to avoid noisy Python environment paths.\n  - Persists user-defined `exclude` patterns during `init`, so `fillSingle` uses project-specific exclusions.\n  - Aligns semantic analysis to shared default exclude patterns for consistent behavior across tools.\n- [#23](https://github.com/vinilana/dotcontext/pull/23) - [Fix] Auto-fill files without LLMs\n  - Adds project-type-aware filtering so generated scaffolding better matches CLI, web, backend, and other stacks.\n  - Introduces static `defaultContent` across docs, agents, and skills, enabling usable output without LLM enhancement.\n  - Replaces placeholder scaffold content with practical starter templates.\n\n### Added\n\n- **Project Type Filtering in InitService**: Scaffolds are now automatically filtered based on detected project type\n  - Uses `StackDetector` and `classifyProject` to determine project type (cli, web-frontend, web-backend, full-stack, mobile, library, monorepo, desktop)\n  - Passes `filteredDocs` and `filteredAgents` to generators based on project classification\n  - CLI projects get core scaffolds only; web projects get additional architecture, security, and specialist agents\n  - Graceful fallback to all scaffolds if classification fails\n\n- **Static Default Content for Scaffolds**: All scaffolds now include useful template content out-of-the-box\n  - New `defaultContent` field in `ScaffoldSection` type provides static content when not autoFilled\n  - Serialization uses `defaultContent` directly instead of placeholder text (\"_Content to be added._\")\n  - Works immediately without requiring LLM enhancement or semantic analysis\n\n- **Agent Playbook Default Content**: All 14 agent playbooks now include comprehensive static content\n  - **Mission** — Clear description of agent purpose and when to engage\n  - **Responsibilities** — Concrete list of tasks the agent handles\n  - **Best Practices** — Guidelines for effective agent operation\n  - **Collaboration Checklist** — Step-by-step workflow with checkboxes\n  - Agents: code-reviewer, bug-fixer, feature-developer, refactoring-specialist, test-writer, documentation-writer, performance-optimizer, security-auditor, backend-specialist, frontend-specialist, architect-specialist, devops-specialist, database-specialist, mobile-specialist\n\n- **Documentation Default Content**: 8 core documentation templates now include useful starter content\n  - **project-overview.md** — Quick facts, entry points, technology stack, getting started checklist\n  - **development-workflow.md** — Branching model, local development commands, code review expectations\n  - **testing-strategy.md** — Test types, running tests, quality gates, troubleshooting\n  - **architecture.md** — System overview, layers, patterns table, diagrams placeholder\n  - **tooling.md** — Required tools, automation commands, IDE setup recommendations\n  - **security.md** — Authentication, secrets management, compliance checklist\n  - **glossary.md** — Type definitions, enums, core terms, acronyms table\n  - **data-flow.md** — Module dependencies, service layer, high-level flow diagram\n\n- **Skill Default Content**: All 10 built-in skills now include comprehensive static content\n  - **When to Use** — Clear activation triggers for each skill\n  - **Instructions** — Step-by-step execution guide\n  - **Examples** — Concrete, copy-pasteable examples\n  - **Guidelines** — Best practices for effective use\n  - Skills: commit-message, pr-review, code-review, test-generation, documentation, refactoring, bug-investigation, feature-breakdown, api-design, security-audit\n\n### Changed\n\n- **Scaffold Generation**: Scaffolds now generate with useful content instead of empty placeholders\n  - Previously: `_Content to be added._` with guidance comments\n  - Now: Practical template content that works for any project type\n  - AutoFill still enhances with project-specific content when semantic analysis is available\n\n### Fixed\n\n- **Exclude Python virtual environments from semantic analysis by default**\n  - Added `venv/` and `.venv/` to default exclude patterns\n  - Unified `SemanticContextBuilder` to use shared default exclude patterns\n  - Persisted user-provided `exclude` patterns from `init` into `.context/config.json` so `fillSingle` respects them\n\n### Technical Details\n\n#### Modified Files\n- `src/services/init/initService.ts` — Added project type detection and scaffold filtering\n- `src/generators/shared/structures/types.ts` — Added `defaultContent` field to `ScaffoldSection`\n- `src/generators/shared/structures/serialization.ts` — Updated to use `defaultContent` when available\n- `src/generators/shared/structures/agents/factory.ts` — Added `AgentDefaultContent` interface and section mapping\n- `src/generators/shared/structures/agents/definitions.ts` — Added default content for all 14 agents\n- `src/generators/shared/structures/skills/factory.ts` — Added `SkillDefaultContent` interface and section mapping\n- `src/generators/shared/structures/skills/definitions.ts` — Added default content for all 10 skills\n- `src/generators/shared/structures/documentation/projectOverview.ts` — Added default content\n- `src/generators/shared/structures/documentation/workflow.ts` — Added default content\n- `src/generators/shared/structures/documentation/testing.ts` — Added default content\n- `src/generators/shared/structures/documentation/architecture.ts` — Added default content\n- `src/generators/shared/structures/documentation/tooling.ts` — Added default content\n- `src/generators/shared/structures/documentation/security.ts` — Added default content\n- `src/generators/shared/structures/documentation/glossary.ts` — Added default content\n- `src/generators/shared/structures/documentation/dataFlow.ts` — Added default content\n- `src/services/semantic/types.ts` — Added `venv/` and `.venv/` to default exclude patterns\n- `src/services/semantic/contextBuilder.ts` — Uses shared default exclude patterns in semantic analysis\n- `src/services/ai/tools/initializeContextTool.ts` — Persists user exclude patterns in `.context/config.json`\n- `src/services/ai/tools/fillScaffoldingTool.ts` — Applies persisted exclude patterns during `fillSingle`\n\n## [0.7.0]\n\n### Added\n\n- **Interactive Mode Environment Prompt**: Ask user before loading `.env` file in interactive mode\n  - Prompts \"Load environment variables from .env file?\" at startup\n  - Default is No for explicit/secure approach\n  - Command-line mode still loads `.env` automatically (no change)\n  - MCP mode continues to skip `.env` loading (existing behavior)\n\n- **MCP Install Command**: New `mcp:install` CLI command for easy MCP server configuration\n  - Automatically configures ai-context MCP server in AI tools (Claude Code, Cursor, Windsurf, Cline, Continue.dev)\n  - Interactive mode with tool detection and selection\n  - Supports global (home directory) and local (project directory) installation\n  - Merges with existing MCP configurations without overwriting\n  - Dry-run mode for previewing changes\n  - Bilingual support (English and Portuguese)\n\n- **Gateway Tools Consolidation**: Unified MCP tools into 9 focused tools (5 gateways + 4 dedicated workflow tools)\n  - `explore` - File and code exploration (read, list, analyze, search, getStructure)\n  - `context` - Context scaffolding and semantic context (check, init, fill, fillSingle, listToFill, getMap, buildSemantic, scaffoldPlan)\n  - `plan` - Plan management and execution tracking (link, getLinked, getDetails, getForPhase, updatePhase, recordDecision, updateStep, getStatus, syncMarkdown, commitPhase)\n  - `agent` - Agent orchestration and discovery (discover, getInfo, orchestrate, getSequence, getDocs, getPhaseDocs, listTypes)\n  - `skill` - Skill management (list, getContent, getForPhase, scaffold, export, fill)\n  - `sync` - Import/export synchronization (exportRules, exportDocs, exportAgents, exportContext, exportSkills, reverseSync, importDocs, importAgents, importSkills)\n  - `workflow-init` - Initialize PREVC workflow (creates .context/workflow/)\n  - `workflow-status` - Get current workflow status\n  - `workflow-advance` - Advance to next phase\n  - `workflow-manage` - Manage handoffs, collaboration, documents, gates\n  - Standardized response utilities and shared context across all handlers\n  - Improved organization, discoverability, and reduced cognitive load\n\n- **MCP Export Tools**: New granular export tools for docs, agents, and skills\n  - `exportDocs` - Export documentation from `.context/docs/` with README indexing mode\n  - `exportAgents` - Export agents from `.context/agents/` (symlink by default)\n  - `exportContext` - Unified export of docs, agents, and skills in one operation\n\n- **MCP Import Tools**: Individual import tools for each content type\n  - `importDocs` - Import documentation from AI tool directories into `.context/docs/`\n  - `importAgents` - Import agents from AI tool directories into `.context/agents/`\n  - `importSkills` - Import skills from AI tool directories into `.context/skills/`\n\n- **README Index Mode**: New `indexMode` option for docs export\n  - `readme` (default) - Export only README.md files as indices\n  - `all` - Export all matching files (previous behavior)\n  - Cleaner exports that reference documentation indices\n\n- **Content Type Registry**: Extensible registry for future content types\n  - `ContentTypeRegistry` in `src/services/shared/contentTypeRegistry.ts`\n  - Supports docs, agents, skills, plans\n  - Easy addition of new content types (prompts, workflows, etc.)\n\n- **Unified Context Export Service**: Orchestrates export of all content types\n  - `ContextExportService` combines docs, agents, and skills export\n  - Configurable skip options for each content type\n  - Consistent error handling and reporting\n\n- **MCP Response Optimization**: New `skipContentGeneration` option for `initializeContext`\n  - Reduces response size from ~10k tokens to ~500 tokens\n  - Enables two-phase workflow: scaffold first, fill on-demand\n  - Default `true` for MCP to reduce context usage\n  - Use `fillSingleFile` or `fillScaffolding` tools to generate content when needed\n\n- **Workflow Gates System**: Comprehensive gate checking for phase transitions\n  - `require_plan` gate - Enforces plan creation before P → R transition\n  - `require_approval` gate - Requires plan approval before R → E transition\n  - Automatic gate settings based on project scale (QUICK, SMALL, MEDIUM, LARGE, ENTERPRISE)\n  - Custom error types for gate violations (`WorkflowGateError`)\n  - New `getGates` action to check current gate status\n  - Unit tests for gate checking logic\n\n- **Workflow Autonomous Mode**: Toggle autonomous execution for AI agents\n  - `setAutonomous` action to enable/disable autonomous mode\n  - Autonomous mode bypasses certain gates for faster iteration\n  - Tracks reason for mode changes in workflow status\n\n- **Execution History Tracking**: Detailed action logging throughout workflow lifecycle\n  - `ExecutionHistory` structure tracks all workflow actions\n  - Records phase starts, completions, plan linking, step execution\n  - `archive_previous` option for workflow initialization (archive vs delete existing)\n  - Methods to archive or clear plans and workflows\n\n- **Plan Execution Management**: Step-level tracking and synchronization\n  - `updateStep` action for updating individual step status\n  - `getStatus` action for retrieving plan execution status\n  - `syncMarkdown` action to sync tracking data back to plan markdown files\n  - Detailed interfaces for step execution (`StepExecution`) and phase tracking (`PhaseExecution`)\n\n- **Git Integration for Plans**: Commit completed phases directly from MCP\n  - `commitPhase` action creates git commits for completed workflow phases\n  - Optional co-authoring support with `coAuthor` parameter\n  - Configurable staging patterns with `stagePatterns` (default: `.context/**`)\n  - Dry-run mode for previewing commits\n  - Commit tracking records hash and timestamp for each phase\n\n- **Breadcrumb Logging**: Step-level execution trails for debugging\n  - Enhanced `PlanLinker` with breadcrumb trail generation\n  - `generateResumeContext` provides step-level context for session resumption\n  - Actions tracked: step_started, step_completed, step_skipped\n  - Improves AI agent ability to resume interrupted workflows\n\n- **V2 Scaffold System**: New scaffold generation architecture\n  - Frontmatter-only files that define structure without content\n  - `scaffoldStructure` context passed to AI agents for content generation\n  - Centralized scaffold structure definitions in `scaffoldStructures.ts`\n  - Supports documentation, agents, and skills scaffolding\n  - Improved validation and serialization of scaffold structures\n  - Deprecated legacy templates in favor of new system\n\n- **Fill Tool Enhancements**: Better context for content generation\n  - `fillSingleFile` and `fillScaffolding` exports from scaffolding tools now include scaffold structure context\n  - Semantic context integrated into fill instructions\n  - Removed deprecated content generation functions\n  - Enhanced error handling and user guidance\n\n- **Q&A Service**: Question and answer generation from codebase (via MCP context gateway)\n  - `QAService` for generating and searching Q&A entries\n  - `generateQA` action in context gateway creates Q&A files from codebase analysis\n  - `searchQA` action for semantic search over generated Q&A\n  - Utilizes pre-computed codebase maps when available\n\n- **Topic & Pattern Detection**: Automatic detection of functional patterns (via MCP context gateway)\n  - `TopicDetector` identifies capabilities in codebase\n  - Detects: authentication, database access, API endpoints, caching, messaging, etc.\n  - `detectPatterns` and `getFlow` actions provide pattern analysis\n  - Used to generate contextually relevant Q&A and architectural insights\n\n- **Context Metrics Service**: Usage tracking for context tools (via MCP metrics gateway)\n  - Tracks context tool usage and file reads\n  - Provides insights into pre-computed context effectiveness\n  - Guides optimization of codebase map generation\n  - Accessible via `metrics` gateway with tracking and reporting actions\n\n- **Enhanced Tool Status**: Improved response structures\n  - New `incomplete` status for tracking pending actions\n  - `instruction` field with clear next-step guidance\n  - `pendingWrites` replaces `requiredActions` for clarity\n  - `checklist` field for actionable task lists\n\n- **Scaffold Enhancement Prompt**: Consistent MCP enhancement instructions across all scaffolding operations\n  - New `MCP_SCAFFOLD_ENHANCEMENT_PROMPT` constant for standardized AI guidance\n  - New `createScaffoldResponse()` helper ensures all scaffold responses include enhancement instructions\n  - `_actionRequired`, `_status: \"incomplete\"`, and `_warning` signals for AI agent awareness\n  - `enhancementPrompt` field with clear workflow steps\n  - `nextSteps` array with actionable instructions\n  - `pendingEnhancement` list of files requiring content\n  - Applied to: `context init` and `scaffoldPlan` MCP actions\n  - Ensures AI agents always receive instructions to enhance scaffolding via MCP tools\n\n### Changed\n\n- **Context Initialization Simplified**: `.context` folder creation now uses simple path logic instead of complex detection\n  - `.context` is created in the specified path or current working directory\n  - Cleaner, more predictable behavior without hidden traversal logic\n  - Internal complexity reduced from 496 lines to ~20 lines\n  - Public APIs remain unchanged - static factory methods (`WorkflowService.create()`, `PlanLinker.create()`) are preserved\n  - Backwards compatibility maintained for existing code\n\n- **MCP Action Logging**: Logs every MCP tool invocation to `.context/workflow/actions.jsonl` with sanitized metadata for auditability.\n\n- **Phase Orchestration Skills**: Workflow responses now include recommended skills alongside agent orchestration for each PREVC phase.\n\n- **Workflow Status Serialization**: Omits empty or default sections to keep `status.yaml` minimal and readable.\n\n- **Agents Export Default**: Changed default sync mode from `markdown` to `symlink`\n  - Symlinks keep AI tool directories automatically synchronized\n  - Changes in `.context/agents/` reflect immediately in target directories\n\n- **MCP Tool Simplification**: Removed project-setup and project-report tools\n  - Simplified from 11 tools to 9 tools (5 gateways + 4 dedicated workflow tools)\n  - New explicit workflow: `context init` → `fillSingle` → `workflow-init`\n  - Project setup functionality now achieved through composable steps\n  - Removed `ProjectAction`, `ProjectParams`, `WorkflowAction`, `WorkflowParams` types\n  - Enhancement prompts no longer use function call syntax\n  - Tool descriptions clarify that workflow-init creates `.context/workflow/` folder\n  - Added MCP README.md documentation for simplified tool structure\n\n- **MCP Server Architecture**: Gateway pattern replaces individual tools\n  - Single entry point per domain (explore, context, workflow, etc.)\n  - Action-based dispatching within each gateway\n  - Consistent parameter validation and error handling\n\n- **Scaffold Generation**: Templates now generate structure, not content\n  - AI agents receive scaffold structure and fill based on context\n  - Better separation of structure definition and content generation\n\n- **Workflow Initialization**: New settings and options\n  - `autonomous`, `require_plan`, `require_approval` settings\n  - Scale-based default settings for different project sizes\n  - `archive_previous` controls handling of existing workflows\n\n### Breaking Changes\n\n- **ENTERPRISE Scale Removed**\n  - `ProjectScale.ENTERPRISE` enum value removed (breaking change for TypeScript code)\n  - Consolidated into `ProjectScale.LARGE` for simpler mental model\n  - Security/compliance keywords now map to LARGE scale instead of ENTERPRISE\n  - **Backward compatibility**: Existing `status.yaml` files with `scale: ENTERPRISE` automatically migrate to LARGE\n  - **API compatibility**: `getScaleFromName('enterprise')` maps to LARGE for smooth transitions\n  - **MCP interface**: `workflow-init` scale parameter no longer accepts 'ENTERPRISE'\n\n- **Context Initialization**\n  - `.context` is now created only in the specified path or current working directory\n\n### Fixed\n\n- **Workflow Init Paths**: Correctly resolves `.context` repo paths to ensure `status.yaml` is created in the expected location.\n- **Plan Index Initialization**: Ensures `.context/workflow/plans.json` is created when starting a workflow.\n- **Export Validation**: Export commands now properly check if source directories exist\n  - `exportContext`, `exportDocs`, `exportAgents`, `exportSkills` only export content that actually exists in `.context/`\n  - Added `fs.pathExists` checks before processing docs, agents, and skills directories\n  - Skills export without `includeBuiltIn` no longer fails silently when `.context/skills/` doesn't exist\n  - Prevents misleading success messages when exporting non-existent content\n\n### Technical Details\n\n#### New Files\n- `src/services/mcp/gateway/` - Gateway handler modules\n  - `explore.ts` - File/code exploration handler\n  - `context.ts` - Context management handler\n  - `plan.ts` - Plan management handler\n  - `agent.ts` - Agent orchestration handler\n  - `skill.ts` - Skill management handler\n  - `sync.ts` - Sync operations handler\n  - `workflowInit.ts` - Workflow initialization handler\n  - `workflowStatus.ts` - Workflow status handler\n  - `workflowAdvance.ts` - Workflow advance handler\n  - `workflowManage.ts` - Workflow management handler\n  - `shared.ts` - Shared utilities and response helpers\n- `src/services/mcp/README.md` - MCP tools documentation and usage guide\n- `src/workflow/gates/gateChecker.ts` - Gate checking logic\n- `src/workflow/gates/gateChecker.test.ts` - Gate checker unit tests\n- `src/workflow/errors.ts` - Custom workflow error types\n- `src/generators/shared/scaffoldStructures.ts` - Centralized scaffold definitions\n- `src/services/qa/qaService.ts` - Q&A generation service\n- `src/services/qa/topicDetector.ts` - Functional pattern detection\n- `src/utils/gitService.ts` - Git operations utility\n- `src/types/scaffoldFrontmatter.ts` - Scaffold frontmatter types\n- `src/services/mcp/mcpInstallService.ts` - MCP installation service for AI tools\n- `src/services/mcp/mcpInstallService.test.ts` - Unit tests for MCP install service\n- `src/services/export/contextExportService.ts` - Unified export orchestrator\n- `src/services/shared/contentTypeRegistry.ts` - Extensible content type definitions\n- `src/services/mcp/gateway/response.ts` - Gateway response helpers and scaffold response builder\n- `src/services/mcp/gateway/types.ts` - Gateway action types and parameters\n- `src/services/mcp/gateway/index.ts` - Gateway module exports\n- `src/services/mcp/gateway/shared.ts` - Shared utilities for gateway handlers\n- `src/services/mcp/gateway/metrics.ts` - Context metrics tracking gateway\n\n#### Modified Files\n- `src/index.ts` - Interactive mode environment prompt, conditional dotenv loading\n- `src/utils/prompts/index.ts` - Added `promptLoadEnv()` function\n- `src/utils/i18n.ts` - Added `prompts.env.loadEnv` translations (EN/PT)\n- `src/services/mcp/mcpServer.ts` - Gateway integration and new actions\n- `src/services/mcp/gateway/context.ts` - Gateway implementation with `init`, `scaffoldPlan` actions, Q&A and pattern detection\n- `src/services/ai/tools/scaffoldPlanTool.ts` - Added consistent `status: \"incomplete\"` pattern and `nextStep` guidance\n- `src/prompts/defaults.ts` - Added `MCP_SCAFFOLD_ENHANCEMENT_PROMPT` constant\n- `src/workflow/orchestrator.ts` - Gate checking and execution history\n- `src/workflow/status/statusManager.ts` - Execution tracking and settings\n- `src/workflow/plans/planLinker.ts` - Step tracking and commit support\n- `src/services/ai/tools/fillScaffoldingTool.ts` - Scaffold structure integration, exports both `fillScaffoldingTool` and `fillSingleFileTool`\n- `src/services/ai/schemas.ts` - New action schemas and parameters\n- `src/generators/documentation/documentationGenerator.ts` - V2 scaffold support\n- `src/generators/agents/agentGenerator.ts` - V2 scaffold support\n- `src/generators/skills/skillGenerator.ts` - V2 scaffold support\n- `src/index.ts` - Added `mcp:install` CLI command with interactive mode\n- `src/services/mcp/index.ts` - Exported MCPInstallService\n- `src/utils/i18n.ts` - Added translations for mcp:install command (EN + PT-BR)\n- `src/services/export/exportRulesService.ts` - Added `indexMode` option and source path validation\n- `src/services/export/skillExportService.ts` - Added skills directory existence check\n- `src/services/export/index.ts` - Export ContextExportService\n- `src/services/shared/index.ts` - Export ContentTypeRegistry\n\n## [0.6.2] - 2026-01-16\n\n### Added\n\n- **Google Antigravity Support**: Full bidirectional sync support for Google Antigravity\n  - Rules export to `.agent/rules/` directory\n  - Agents sync to `.agent/agents/` directory\n  - Workflows (skills) export to `.agent/workflows/` directory\n  - New `antigravity` preset for export and sync commands\n  - MCP tools updated to include `antigravity` preset option\n\n- **Trae AI Support**: Full bidirectional sync support for Trae AI\n  - Rules export to `.trae/rules/` directory\n  - Agents sync to `.trae/agents/` directory\n  - New `trae` preset for export and sync commands\n  - MCP tools updated to include `trae` preset option\n\n- **Windsurf Directory Format**: Changed Windsurf rules export from single file to directory format\n  - Now exports to `.windsurf/rules/` as multiple markdown files\n  - Aligns with Windsurf documentation (12,000 char limit per file)\n\n- **Reverse Quick Sync**: Import rules, agents, and skills from AI tool directories into `.context/`\n  - Inverse of Quick Sync - consolidates scattered AI tool configurations into centralized `.context/`\n  - New `reverse-sync` CLI command with comprehensive options\n  - Interactive mode with component selection and merge strategy prompts\n  - Supports 12 AI tools: Claude, Cursor, GitHub Copilot, Windsurf, Cline, Continue, Gemini, Codex, Aider, Zed, Antigravity, Trae\n\n- **Tool Detection**: High-level detection of which AI tools are present in a repository\n  - `ToolDetector` class aggregates results from rules, agents, and skills detectors\n  - Returns summary grouped by tool with file counts\n  - MCP tool: `detectAITools`\n\n- **Skills Detection**: New detector for skills from AI tool directories\n  - `SkillsDetector` class scans `.claude/skills/`, `.gemini/skills/`, `.codex/skills/`\n  - Parses SKILL.md frontmatter for metadata (name, description, phases, tags)\n  - Follows existing AgentsDetector pattern\n\n- **Import Skills Service**: Import skills with merge strategy support\n  - `ImportSkillsService` class with four merge strategies: skip, overwrite, merge, rename\n  - Adds frontmatter metadata to imported files (source_tool, source_path, imported_at)\n  - Preserves skill directory structure during import\n\n- **Merge Strategies**: Flexible conflict handling for imports\n  - `skip` - Skip existing files (default)\n  - `overwrite` - Replace existing files\n  - `merge` - Append content with separator\n  - `rename` - Create `{name}-{tool}.md` for conflicts\n\n- **MCP Tools for Reverse Sync**:\n  - `detectAITools` - Detect AI tool configurations with summary\n  - `reverseQuickSync` - Import from AI tool directories to `.context/`\n\n- **CLI Command**: `npx @ai-coders/context reverse-sync [options]`\n  - `--dry-run` - Preview changes without importing\n  - `--force` - Overwrite existing files\n  - `--skip-agents`, `--skip-skills`, `--skip-rules` - Skip specific components\n  - `--merge-strategy <strategy>` - How to handle conflicts\n  - `--no-metadata` - Skip frontmatter metadata addition\n  - `-v, --verbose` - Verbose output\n\n- **Interactive Menu**: Added \"Reverse Sync (import from AI tools)\" option\n  - Component selection (rules, agents, skills)\n  - Merge strategy selection\n  - Detection summary display\n\n- **i18n Support**: Full English and Portuguese translations for reverse sync\n\n### Technical Details\n\n#### New Files\n- `src/services/reverseSync/types.ts` - Types and interfaces\n- `src/services/reverseSync/presets.ts` - SKILL_SOURCES and tool mappings\n- `src/services/reverseSync/skillsDetector.ts` - Skills detection\n- `src/services/reverseSync/toolDetector.ts` - High-level tool detection\n- `src/services/reverseSync/importSkillsService.ts` - Skills import service\n- `src/services/reverseSync/reverseQuickSyncService.ts` - Main orchestrator\n- `src/services/reverseSync/index.ts` - Module exports\n\n#### Modified Files\n- `src/index.ts` - Added `reverse-sync` CLI command and interactive menu option\n- `src/services/mcp/mcpServer.ts` - Added `detectAITools` and `reverseQuickSync` MCP tools\n- `src/utils/i18n.ts` - Added reverse sync translations (EN/PT)\n\n## [0.6.1] - 2026-01-15\n\n### Added\n\n- **Auto-fill instructions for scaffolding**: MCP scaffolding tools now return detailed fill instructions\n  - `initializeContext` returns semantic context and per-file fill instructions when `autoFill=true` (default)\n  - `scaffoldPlan` returns fill instructions for the plan template\n  - AI agents receive comprehensive prompts to fill each generated file\n  - New `fillPrompt` field with complete instructions for all files\n  - New `fillInstructions` per file with specific guidance (architecture, data-flow, conventions, etc.)\n\n- **Workflow file path visibility**: `workflowInit` and `workflowStatus` now return file paths\n  - `statusFilePath`: Absolute path to `.context/workflow/status.yaml`\n  - `contextPath`: Path to the `.context` directory\n  - Helps AI agents and users locate workflow state files\n\n- **New `autoFill` parameter**: Added to `initializeContext` and `scaffoldPlan` MCP tools\n  - Defaults to `true` - scaffolding tools return semantic context and fill instructions\n  - Set to `false` to skip context building and get template-only response\n\n### Changed\n\n- **Scaffolding tool responses**: Now include structured fill instructions instead of generic nextSteps\n  - Each generated file includes type-specific fill instructions\n  - Semantic context included for codebase-aware content generation\n  - Clear action directives for AI agents to fill files\n\n### Fixed\n\n- **MCP `fillSkills` no longer requires API key**: Fixed critical bug where `fillSkills` MCP tool\n  incorrectly required an AI API key when called via Claude Code\n  - MCP tools now return fill instructions instead of calling LLM services directly\n  - API keys are only required for CLI usage, never for MCP\n  - Design principle: AI agent (Claude Code) IS the LLM, so MCP tools return context/instructions\n\n### Technical Details\n\n#### Modified Files\n- `src/services/ai/schemas.ts` - Added `autoFill` parameter to input schemas\n- `src/services/ai/tools/initializeContextTool.ts` - Returns fill instructions and semantic context\n- `src/services/ai/tools/scaffoldPlanTool.ts` - Returns fill instructions for plans\n- `src/services/ai/tools/fillScaffoldingTool.ts` - Exported helper functions for reuse\n- `src/services/ai/tools/index.ts` - Updated exports\n- `src/services/mcp/mcpServer.ts` - Fixed `fillSkills` to return fill instructions instead of calling SkillFillService, added `autoFill` to tool schemas, file paths to workflow responses\n\n## [0.6.0] - 2026-01-14\n\n### Added\n\n- **Quick Sync Service**: Unified synchronization of agents, skills, and documentation\n  - New `quick-sync` command for one-click export to all AI tools\n  - Component selection: choose agents, skills, docs, or all\n  - Target selection: export to specific tools or all at once\n  - **Selective doc targets**: Choose which rules files to export (cursorrules, CLAUDE.md, AGENTS.md, windsurfrules, clinerules, CONVENTIONS.md)\n  - **AGENTS.md universal export**: New preset for tools that support universal agent files\n  - CLI: `npx @ai-coders/context quick-sync [--components agents,skills,docs] [--targets claude,cursor]`\n  - Interactive mode with per-component target selection (agents, skills, docs separately)\n\n- **getCodebaseMap MCP Tool**: Retrieve structured codebase data\n  - Access pre-analyzed codebase information from `.context/docs/codebase-map.json`\n  - Sections: `stack`, `structure`, `architecture`, `symbols`, `publicAPI`, `dependencies`, `stats`\n  - Token-efficient retrieval with specific section queries\n  - Reduces need for repeated codebase analysis\n\n- **Project Type Classification**: Smart filtering for agents and documentation\n  - Automatic project type detection (backend, frontend, fullstack, api, library, cli, mobile)\n  - Filter agent playbooks based on project type\n  - Filter documentation templates based on relevance\n  - `scaffoldFilter` service for intelligent scaffolding selection\n\n- **Interactive Mode Enhancements**:\n  - Welcome screen with PREVC visual explanation\n  - User prompt input on startup\n  - Multi-select component options for scaffolding (docs, agents, skills)\n  - Target selection with presets for AI tools\n\n- **Front Matter Wrapping**: Enhanced options for generated files\n  - Additional front matter options: `wrap`, `template`, `source`\n  - Better metadata management for scaffolded files\n\n- **Agent Front Matter Enhancement**: Agent playbooks now include name and description\n  - `name` field auto-populated from agent title\n  - `description` field auto-populated from first responsibility\n  - Improves agent discovery and metadata for AI tools\n\n- **Skills System**: On-demand expertise for AI agents (Claude Code, Gemini CLI, Codex)\n  - 10 built-in skills: commit-message, pr-review, code-review, test-generation, documentation, refactoring, bug-investigation, feature-breakdown, api-design, security-audit\n  - `SkillRegistry` class for skill discovery and management\n  - `SkillGenerator` for scaffolding SKILL.md files\n  - `SkillExportService` for exporting to `.claude/skills/`, `.gemini/skills/`, `.codex/skills/`\n  - CLI commands: `skill init`, `skill list`, `skill export`, `skill create`, `skill fill`\n  - MCP tools: `listSkills`, `getSkillContent`, `getSkillsForPhase`, `scaffoldSkills`, `exportSkills`, `fillSkills`\n  - Skills are mapped to PREVC phases for workflow integration\n\n- **Skill Fill Feature**: AI-powered skill personalization\n  - `skill fill` CLI command personalizes skills with project-specific content\n  - `fillSkills` MCP tool for programmatic skill filling\n  - `SkillAgent` - New AI agent for skill personalization (follows PlaybookAgent pattern)\n  - `buildSkillContext()` method in SemanticContextBuilder for skill-specific context\n  - Uses docs and agents context for richer personalization\n  - Semantic analysis mode for token-efficient generation\n  - i18n support for English and Portuguese\n\n- **Plan-Workflow Integration**: Link plans to PREVC workflow phases\n  - `PlanLinker` class for managing plan-workflow relationships\n  - Plans now include PREVC phase mapping in frontmatter\n  - Track plan status, decisions, and risks per workflow phase\n  - MCP tools: `linkPlan`, `getLinkedPlans`, `getPlanDetails`, `getPlansForPhase`, `updatePlanPhase`, `recordDecision`\n\n- **Agent Lineup in Plans**: Plans now include recommended agents in frontmatter\n  - AI agents can discover which agents to use for each plan step\n  - `AgentLineupEntry` type with phase mapping\n  - Frontmatter parsing extracts agent lineup automatically\n\n- **Custom Agent Discovery**: Support for custom agent playbooks\n  - Discover agents from `.context/agents/` directory\n  - Support for both built-in and custom agents (e.g., `marketing-agent.md`)\n  - MCP tools: `discoverAgents`, `getAgentInfo`\n\n- **Centralized Agent Registry**: Single source of truth for agent management\n  - `AgentRegistry` class with caching and metadata retrieval\n  - `BUILT_IN_AGENTS` constant with type-safe agent types\n  - `isBuiltInAgent()` helper for validation\n  - Exported from `workflow/agents` module\n\n- **New MCP Tools for Plan Management**:\n  - `linkPlan` - Link a plan file to the current workflow\n  - `getLinkedPlans` - Get all linked plans for current workflow\n  - `getPlanDetails` - Get detailed information about a linked plan\n  - `getPlansForPhase` - Get plans relevant to a PREVC phase\n  - `updatePlanPhase` - Update plan phase status\n  - `recordDecision` - Record a decision for a plan\n  - `discoverAgents` - Discover all available agents (built-in + custom)\n  - `getAgentInfo` - Get metadata for a specific agent\n\n### Changed\n\n- **UI/UX Minimalist**: Removed emoticons from all UI components\n  - Report service uses text indicators: `[x]`, `[>]`, `[ ]`, `[-]`\n  - Menu choices use simple text without emoji prefixes\n  - Cleaner, more professional interface\n\n- **PlanLinker Refactored**: Now delegates agent operations to AgentRegistry (SRP)\n\n### Fixed\n\n- **Orphaned Spinners**: Fixed CLI spinners not stopping properly in certain conditions\n  - Prevents visual artifacts when operations complete or fail\n\n- **Skills Path Construction**: Fixed Quick Sync creating incorrect folder paths for skills\n  - Now uses absolute paths consistent with agents sync behavior\n  - Skills correctly exported to `.claude/skills/`, `.gemini/skills/`, etc.\n\n### Technical Details\n\n#### New Files\n- `src/services/quickSync/quickSyncService.ts` - Quick Sync service for unified synchronization\n- `src/services/quickSync/index.ts` - Quick Sync module exports\n- `src/services/ai/tools/getCodebaseMapTool.ts` - MCP tool for codebase map retrieval\n- `src/services/stack/projectTypeClassifier.ts` - Project type classification service\n- `src/services/stack/scaffoldFilter.ts` - Intelligent scaffold filtering\n- `src/generators/documentation/codebaseMapGenerator.ts` - Codebase map generation\n- `src/services/ai/agents/skillAgent.ts` - AI agent for skill personalization\n- `src/services/fill/skillFillService.ts` - Service orchestrating skill fill operations\n- `src/workflow/plans/types.ts` - Plan-workflow integration types\n- `src/workflow/plans/planLinker.ts` - Plan-workflow linking service\n- `src/workflow/plans/index.ts` - Plans module exports\n- `src/workflow/agents/agentRegistry.ts` - Centralized agent registry\n- `src/workflow/agents/index.ts` - Agents module exports\n\n#### Modified Files\n- `src/services/semantic/contextBuilder.ts` - Added `buildSkillContext()` method\n- `src/services/ai/prompts/sharedPrompts.ts` - Added `getSkillAgentPrompt()`\n- `src/services/ai/agentEvents.ts` - Added 'skill' to AgentType\n- `src/services/mcp/mcpServer.ts` - Added `fillSkills` MCP tool\n- `src/utils/i18n.ts` - Added skill fill translations (EN/PT)\n\n#### New Exports from `workflow` module\n```typescript\n// Plan Integration\nexport { PlanLinker, createPlanLinker, PlanReference, LinkedPlan, ... } from './plans';\n\n// Agent Registry\nexport { BUILT_IN_AGENTS, AgentRegistry, createAgentRegistry, ... } from './agents';\n```\n\n## [0.5.2] - 2026-01-09\n\n### Fixed\n\n- **Dotenv configuration handling**: Updated dotenv configuration to respect command-line arguments\n  - MCP server now skips loading `.env` file when `--skip-dotenv` flag is passed\n  - Prevents environment variable conflicts when running as MCP server\n  - Fix MCP to work in Antigravity and Codex\n\n## [0.5.1] - 2026-01-09\n\n### Added\n\n- **Update command**: New `update` command for selective documentation updates\n  - Target specific files or sections without regenerating everything\n  - Supports `--files` flag to update specific documentation files\n  - Preserves manual edits in other files\n\n- **StateDetector service**: Wizard-based project state detection\n  - Automatically detects scaffolding completeness (docs, agents, plans)\n  - Parses YAML front matter for instant status detection\n  - Provides actionable recommendations based on project state\n\n- **YAML front matter utilities**: Instant status detection for generated files\n  - `parseFrontMatter()` - Extract metadata from markdown files\n  - `updateFrontMatter()` - Update metadata while preserving content\n  - `hasFrontMatter()` - Quick check for front matter presence\n  - Status tracking: `generated`, `filled`, `customized`\n\n- **Documentation guides**: Extracted detailed guides from README\n  - `docs/GUIDE.md` - Comprehensive usage guide\n  - `docs/MCP.md` - MCP server setup and configuration\n  - `docs/PROVIDERS.md` - Multi-provider configuration guide\n\n- **New MCP tools for incremental scaffolding**: Avoid output size limits\n  - `listFilesToFill` - Returns only file paths (~1KB response) for efficient listing\n  - `fillSingleFile` - Process one scaffold file at a time (~10KB per file)\n  - MCP server now exposes 12 tools (up from 10)\n\n- **Tests for new utilities**:\n  - `frontMatter.test.ts` - 12 tests for YAML front matter parsing/updating\n  - `stateDetector.test.ts` - 8 tests for project state detection\n\n### Changed\n\n- **Simplified README**: Streamlined to essentials with links to detailed guides\n- **Interactive mode improvements**:\n  - Menu reordered to prioritize plan creation over docs update\n  - Plan creation now asks for goal/summary instead of just name\n- **Quick setup fix**: Uses correct `both` value instead of `all` for scaffold type\n- **fillScaffolding pagination**: Added `offset` and `limit` parameters (default: 3 files)\n  - Prevents output size errors for large projects\n  - Returns `pagination.hasMore` to indicate remaining files\n- **Centralized tool descriptions**: Single source of truth for MCP and AI SDK\n  - New `toolRegistry.ts` with all tool descriptions\n  - MCP server uses `getToolDescription()` instead of inline strings\n- **Shared agent prompts**: Eliminated redundancy across agents\n  - New `prompts/sharedPrompts.ts` with common prompt components\n  - `getDocumentationAgentPrompt()`, `getPlaybookAgentPrompt()`, `getPlanAgentPrompt()`\n  - Agents now import prompts instead of defining inline\n\n### Removed\n\n- **Direct OpenRouter client**: Removed `OpenRouterClient` class and `OpenRouterConfig` type\n  - OpenRouter is now used exclusively through AI SDK via OpenAI-compatible provider\n  - Simplifies provider architecture with single unified approach\n\n### Technical Details\n\n#### New Files\n- `src/services/state/stateDetector.ts` - StateDetector service\n- `src/services/state/stateDetector.test.ts` - StateDetector tests\n- `src/services/update/updateService.ts` - Update command service\n- `src/utils/frontMatter.ts` - YAML front matter utilities\n- `src/utils/frontMatter.test.ts` - Front matter tests\n- `src/services/ai/toolRegistry.ts` - Centralized tool descriptions\n- `src/services/ai/prompts/sharedPrompts.ts` - Shared agent system prompts\n- `src/services/ai/prompts/index.ts` - Prompts barrel export\n- `docs/GUIDE.md` - Usage guide\n- `docs/MCP.md` - MCP documentation\n- `docs/PROVIDERS.md` - Provider configuration\n\n#### Removed Files\n- `src/services/openRouterClient.ts` - Legacy direct OpenRouter client\n\n## [0.5.0] - 2026-01-08\n\n### Added\n\n- **MCP Server**: New `mcp` command for Claude Code integration via Model Context Protocol\n  - Exposes 10 code analysis tools: `readFile`, `listFiles`, `analyzeSymbols`, `getFileStructure`, `searchCode`, `buildSemanticContext`, `checkScaffolding`, `initializeContext`, `fillScaffolding`, `scaffoldPlan`\n  - Exposes 2 resource templates: `context://codebase/{contextType}` for semantic context, `file://{path}` for file contents\n  - Uses stdio transport for seamless Claude Code integration\n  - Configure in `~/.claude/settings.json` with `npx @ai-coders/context mcp`\n\n- **MCP Scaffolding Tools**: New tools for AI agents to manage `.context` scaffolding\n  - `checkScaffolding` - Check if scaffolding exists with granular status (docs, agents, plans separately)\n  - `initializeContext` - Initialize `.context` scaffolding with template files\n  - `fillScaffolding` - Analyze codebase and generate content for each template (AI agent writes the suggestedContent to each file)\n  - `scaffoldPlan` - Create plan templates in `.context/plans/` with optional semantic analysis\n\n- **Passthrough Server**: New `serve` command for external AI agent integration\n  - JSON-RPC style communication via stdin/stdout\n  - Supports methods: `capabilities`, `tool.list`, `tool.call`, `context.build`, `agent.run`\n  - Real-time notifications for progress, tool calls, and results\n  - Protocol types with Zod validation for type safety\n  - Enables any AI agent to use code analysis tools without MCP support\n\n- **Agent sync command**: New `sync-agents` command to sync agent playbooks to AI tool directories\n  - Syncs from `.context/agents/` (source of truth) to tool-specific directories\n  - Built-in presets: `claude` (.claude/agents), `github` (.github/agents), `cursor` (.cursor/agents)\n  - Two sync modes: `symlink` (default, uses relative symlinks) and `markdown` (generates reference files)\n  - Custom target support via `--target` flag for any AI tool directory\n  - `--dry-run` to preview changes, `--force` to overwrite existing files\n  - Cross-platform: Windows fallback (file copy) when symlinks require elevated permissions\n  - Full i18n support (English and Portuguese)\n\n- **Interactive sync flow**: Added \"Sync agents to AI tools\" option to interactive mode\n  - Prompts for source directory, sync mode, target selection, and options\n  - Supports preset selection or custom path input\n\n- **Multi-provider AI support**: Added support for OpenAI, Anthropic, Google, and OpenRouter providers\n  - New `--provider` flag for `fill` and `plan` commands\n  - Auto-detection of available API keys from environment variables\n  - Provider-specific model defaults (e.g., `gpt-5.2` for OpenAI, `claude-sonnet-4.5` for Anthropic)\n\n- **Semantic context mode**: Token-efficient LLM calls using pre-computed Tree-sitter analysis\n  - Enabled by default for faster, more consistent documentation generation\n  - New `--no-semantic` flag to disable and use tool-based exploration instead\n  - New `--languages` flag to specify programming languages for analysis\n  - Supports: TypeScript, JavaScript, Python, Go, Rust, Java, C++, C#, Ruby, PHP\n\n- **Real-time agent progress display**: Visual feedback during LLM operations\n  - Shows which agent is currently working (DocumentationAgent, PlaybookAgent, PlanAgent)\n  - Displays tool calls and their results in real-time\n  - Progress indicators for multi-step operations\n\n- **SemanticContextBuilder**: New service for generating optimized context strings\n  - `buildDocumentationContext()` - Context for documentation generation\n  - `buildPlaybookContext()` - Context for agent playbook generation\n  - `buildPlanContext()` - Context for development plan generation\n  - Caches analysis results for efficiency\n\n- **LSP integration for semantic analysis**: Optional deep semantic analysis via Language Server Protocol\n  - New `--lsp` flag for `fill` command to enable LSP-enhanced analysis\n  - Enabled by default for `plan fill` (use `--no-lsp` to disable)\n  - Adds type information, implementations, and references to symbol analysis\n  - Supports TypeScript, JavaScript, Python, Go, and Rust language servers\n  - Graceful fallback when LSP servers are unavailable\n\n- **CodebaseAnalyzer**: New orchestrator for hybrid Tree-sitter + LSP analysis\n  - Combines fast syntactic analysis with deep semantic understanding\n  - Architecture layer detection (Services, Controllers, Models, Utils, etc.)\n  - Design pattern detection (Factory, Repository, Service Layer, Observer, etc.)\n  - Entry point and public API identification\n  - Dependency graph construction\n\n- **Unit tests for services**: Comprehensive test coverage for core services\n  - `PlanService` tests (13 tests) - scaffolding, plan fill, error handling, LSP options\n  - `FillService` tests (17 tests) - directory validation, agent processing, options handling\n  - `CodebaseAnalyzer` tests (24 tests) - LSP integration, architecture detection, pattern detection\n\n- **Agent event callback system**: Infrastructure for tracking agent progress\n  - `onAgentStart`, `onAgentStep`, `onToolCall`, `onToolResult`, `onAgentComplete` callbacks\n  - Integrated with CLI UI for real-time display\n\n- **Interactive mode enhancements**:\n  - Language selection for semantic analysis (checkbox interface)\n  - Semantic mode toggle (defaults to enabled)\n  - Provider and model selection\n\n### Changed\n\n- **AI SDK integration**: Replaced axios-based OpenRouter client with Vercel AI SDK\n  - Enables tool-based agent workflows with `generateText` and `maxSteps`\n  - Structured outputs with Zod schemas\n  - Better error handling and streaming support\n\n- **FillService refactored**: Now uses specialized agents instead of basic LLM client\n  - `DocumentationAgent` for docs/*.md files\n  - `PlaybookAgent` for agents/*.md files\n  - Agents support both semantic and tool-based modes\n\n- **PlanService refactored**: Uses `PlanAgent` with tool support\n  - Better context gathering for plan generation\n  - Support for referenced docs and agents\n\n- **Default behavior**: Semantic context mode is now the default\n  - More token-efficient out of the box\n  - Use `--no-semantic` for thorough tool-based exploration\n\n### Removed\n\n- **axios dependency**: Replaced with Vercel AI SDK for HTTP requests\n- **OpenRouterClient**: Replaced with `AISdkClient` supporting multiple providers\n\n### Technical Details\n\n#### New Dependencies\n- `ai` - Vercel AI SDK core\n- `@ai-sdk/openai` - OpenAI provider\n- `@ai-sdk/anthropic` - Anthropic provider\n- `@ai-sdk/google` - Google provider\n- `@ai-sdk/openrouter` - OpenRouter provider\n- `zod` - Schema validation for structured outputs\n- `@modelcontextprotocol/sdk` - MCP server SDK for Claude Code integration\n\n#### New Files\n- `src/services/sync/` - Agent sync service module\n  - `types.ts` - Type definitions (SyncMode, PresetName, SyncOptions, etc.)\n  - `presets.ts` - Built-in target presets (claude, github, cursor)\n  - `symlinkHandler.ts` - Cross-platform symlink creation\n  - `markdownReferenceHandler.ts` - Markdown reference file generation\n  - `syncService.ts` - Main sync orchestrator\n  - `index.ts` - Barrel export\n- `src/services/ai/aiSdkClient.ts` - Main AI SDK client\n- `src/services/ai/providerFactory.ts` - Provider creation factory\n- `src/services/ai/schemas.ts` - Zod schemas for tools and outputs\n- `src/services/ai/tools/*.ts` - Code analysis tools (readFile, listFiles, analyzeSymbols, checkScaffolding, initializeContext, scaffoldPlan, etc.)\n- `src/services/ai/agents/*.ts` - Specialized agents (DocumentationAgent, PlaybookAgent, PlanAgent)\n- `src/services/ai/agentEvents.ts` - Agent event callback types\n- `src/services/semantic/contextBuilder.ts` - SemanticContextBuilder for pre-computed context\n- `src/services/semantic/codebaseAnalyzer.ts` - Main orchestrator for hybrid analysis\n- `src/services/semantic/lsp/lspLayer.ts` - LSP client for semantic queries\n- `src/services/semantic/treeSitter/treeSitterLayer.ts` - Tree-sitter based parsing\n- `src/services/semantic/types.ts` - Shared types for semantic analysis\n- `src/services/plan/planService.test.ts` - PlanService unit tests\n- `src/services/fill/fillService.test.ts` - FillService unit tests\n- `src/services/semantic/codebaseAnalyzer.test.ts` - CodebaseAnalyzer unit tests\n- `src/services/mcp/` - MCP server module\n  - `mcpServer.ts` - Main MCP server implementation\n  - `mcpServer.test.ts` - MCP server tests\n  - `index.ts` - Barrel export\n- `src/services/passthrough/` - Passthrough server module\n  - `protocol.ts` - JSON-RPC protocol types with Zod schemas\n  - `protocol.test.ts` - Protocol tests\n  - `stdinReader.ts` - stdin JSON reader with event emitter\n  - `commandRouter.ts` - Command routing and tool execution\n  - `commandRouter.test.ts` - Router tests\n  - `index.ts` - Barrel export\n- `src/services/serve/` - Serve command service\n  - `serveService.ts` - Main serve service implementation\n  - `index.ts` - Barrel export\n\n#### Environment Variables\n```bash\n# Provider API keys\nOPENROUTER_API_KEY=...\nOPENAI_API_KEY=...\nANTHROPIC_API_KEY=...\nGOOGLE_API_KEY=...\n\n# Model overrides\nOPENROUTER_MODEL=x-ai/grok-4.1-fast\nOPENAI_MODEL=gpt-5.2\nANTHROPIC_MODEL=claude-sonnet-4.5\nGOOGLE_MODEL=gemini-3-flash-preview\n```\n\n## [0.4.0] - Previous Release\n\nInitial release with scaffolding capabilities, `init`, `fill`, and `plan` commands.\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# Dotcontext Repository Guide\n\nThis repository is the source for the dotcontext runtime and its operator/adaptor surfaces.\n\nCurrent product shape:\n\n```text\ncli -> harness <- mcp\n```\n\n## Source of Truth\n\nStart here:\n\n- `README.md` for product description and install guidance\n- `docs/GUIDE.md` for usage flows\n- `ARCHITECTURE.md` for runtime and boundary diagrams\n- `CHANGELOG.md` for release notes\n\n## Working Boundaries\n\n- `src/cli` contains operator-facing exports and CLI-oriented services\n- `src/harness` contains reusable runtime exports\n- `src/mcp` contains the MCP transport boundary\n- `src/services/harness` contains transport-agnostic harness logic\n- `src/services/mcp/gateway` contains MCP handlers and schemas\n- `src/services/workflow` contains PREVC workflow integration\n\nDo not move domain behavior into `cli` or `mcp` if it belongs in `harness`.\n\n## Current Priorities\n\nThe repository now supports:\n\n- durable harness sessions\n- sensors and backpressure\n- task contracts and handoffs\n- policy documents and policy evaluation\n- replay and failure datasets\n- local packaging for `cli`, `harness`, and `mcp`\n\n## Validation Commands\n\n```bash\nnpm run build\nnpm test -- --runInBand\nnpm run build:packages\nnpm run smoke:packages\n```\n\n## Documentation Hygiene\n\nWhen changing any of the following, keep docs in sync:\n\n- product positioning\n- MCP install behavior\n- package boundaries\n- workflow commands\n- release/versioning guidance\n\nAt minimum, review:\n\n- `README.md`\n- `docs/GUIDE.md`\n- `ARCHITECTURE.md`\n- `CONTRIBUTING.md`\n- `CHANGELOG.md`\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to dotcontext\n\nDotcontext is now organized around an explicit runtime split:\n\n```text\ncli -> harness <- mcp\n```\n\nIf you change behavior, keep that boundary intact:\n\n- `cli` is the operator-facing surface\n- `harness` is the reusable runtime/domain layer\n- `mcp` is the transport adapter over the harness\n\n## Development Setup\n\n```bash\ngit clone https://github.com/YOUR_USERNAME/dotcontext.git\ncd dotcontext\nnpm install\nnpm run build\nnpm test -- --runInBand\n```\n\nUseful commands:\n\n```bash\nnpm run dev\nnpm run build\nnpm test -- --runInBand\nnpm run build:packages\nnpm run smoke:packages\n```\n\n## Contribution Expectations\n\n1. Create your branch from `main`.\n2. Keep the change scoped. Do not mix refactors, product changes, and release edits in one PR unless they are tightly coupled.\n3. Add or update tests when behavior changes.\n4. Update docs when commands, package surfaces, workflows, or MCP install behavior change.\n5. Run `npm run build` and `npm test -- --runInBand` before opening a PR.\n6. If the change affects packaging, also run `npm run build:packages` and `npm run smoke:packages`.\n\n## Documentation Expectations\n\nThe public docs that matter most are:\n\n- `README.md` for product positioning and install guidance\n- `docs/GUIDE.md` for usage guidance\n- `ARCHITECTURE.md` for runtime and boundary explanations\n- `CHANGELOG.md` for release notes\n\nContributor and agent-facing instructions live in:\n\n- `CONTRIBUTING.md`\n- `CLAUDE.md`\n- `AGENTS.md`\n\nIf you update one of these areas, check the adjacent docs for drift.\n\n## MCP Install Changes\n\nIf you change `mcp:install`, update all of the following together:\n\n- `README.md`\n- `docs/GUIDE.md`\n- `CHANGELOG.md`\n- `src/services/cli/mcpInstallService.ts`\n- `src/services/cli/mcpInstallService.test.ts`\n\nThe installer is the source of truth for supported clients and config formats. Documentation should describe what the installer actually writes, not what we hope clients support.\n\n## Release Expectations\n\n- Do not bump `package.json` version in feature PRs unless the change is explicitly part of release preparation.\n- Keep `CHANGELOG.md` aligned with the intended release line.\n- For local packaging validation, use:\n\n```bash\nnpm run release:packages:patch\n```\n\nThis prepares local release bundles under `.release/packages/` and `.release/releases/<version>`.\n\n### Publishing split packages\n\n`release:packages:*` does not publish anything to npm. Publishing the split packages is a separate step.\n\n1. Build and validate the package bundles:\n\n```bash\nnpm run build:packages\nnpm run smoke:packages\n```\n\n2. Publish from the generated bundle directory for each package you intend to release:\n\n```bash\ncd .release/packages/cli\nnpm publish --access public\n\ncd ../harness\nnpm publish --access public\n\ncd ../mcp\nnpm publish --access public\n```\n\n3. If npm 2FA is enabled for publishes, pass the current OTP:\n\n```bash\nnpm publish --access public --otp=<code>\n```\n\nUseful verification commands:\n\n```bash\nnpm view @dotcontext/cli version\nnpm view @dotcontext/harness version\nnpm view @dotcontext/mcp version\n```\n\nNotes:\n\n- Publish from `.release/packages/<slug>`, not from the repo root.\n- `@dotcontext/mcp` is a separately published package. If it is missing from npm, `npx @dotcontext/mcp install` will fail with a registry `404`.\n- Run `npm publish --dry-run --access public` first if you want to inspect the tarball before the real publish.\n\n## Pull Requests\n\nA good PR should include:\n\n- a clear problem statement\n- the chosen approach\n- risks or compatibility notes\n- validation performed\n\nWhen relevant, include file references or screenshots of updated docs.\n\n## Bugs and Issues\n\nUse GitHub issues for bugs and feature requests. A useful bug report includes:\n\n- what you tried\n- expected behavior\n- actual behavior\n- reproduction steps\n- environment details\n\n## License\n\nBy contributing, you agree that your contributions are licensed under the [MIT License](LICENSE).\n"
  },
  {
    "path": "CONVENTIONS.md",
    "content": "# Project Rules and Guidelines\n\n> Auto-generated from .context/docs on 2026-03-18T21:47:40.751Z\n\n## README\n\n# Documentation Index\n\nWelcome to the `@dotcontext/cli` repository knowledge base. Start with the project overview, then dive into specific guides as needed.\n\n## Core Guides\n\n| Guide | File | Primary Inputs |\n| --- | --- | --- |\n| Project Overview | [`project-overview.md`](./project-overview.md) | Roadmap, README, stakeholder notes |\n| Development Workflow | [`development-workflow.md`](./development-workflow.md) | Branching rules, CI config, contributing guide |\n| Testing Strategy | [`testing-strategy.md`](./testing-strategy.md) | Test configs, CI gates, known flaky suites |\n| Tooling & Productivity | [`tooling.md`](./tooling.md) | CLI scripts, IDE configs, automation workflows |\n\n## Q&A\n\nFrequently asked questions are organized by topic in the [`qa/`](./qa/) directory.\nSee [qa/README.md](./qa/README.md) for the full index.\n\n## Codebase Map\n\nMachine-readable project structure and stack metadata: [`codebase-map.json`](./codebase-map.json)\n\n## Repository Snapshot\n\n```\nAGENTS.md\nCHANGELOG.md\nCLAUDE.md\nCONTRIBUTING.md\nLICENSE\nREADME.md\ndocs/             -- Published documentation produced by this tool\nexample-documentation.ts\njest.config.js\npackage.json\npackage-lock.json\nprompts/          -- Prompt templates (update_plan_prompt.md, update_scaffold_prompt.md)\nscripts/          -- Build and test helper scripts\nsrc/              -- TypeScript source (~240 files): CLI entrypoint, services, generators, utilities\ntsconfig.json\n```\n\n\n## qa/README\n\n# Q&A Index\n\nProject type: **cli-tool**\n\nGenerated: 2026-03-18T21:32:55.004Z\n\n## Getting-started\n\n- [How do I set up and run this project?](./getting-started.md)\n\n## Architecture\n\n- [How is the codebase organized?](./project-structure.md)\n\n## Operations\n\n- [How are errors handled?](./error-handling.md)\n\n## Testing\n\nSee [testing-strategy.md](../testing-strategy.md) in the parent docs directory for full test framework documentation.\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 AI Coders Academy\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# @dotcontext/cli\n\n[![npm version](https://badge.fury.io/js/@dotcontext%2Fcli.svg)](https://www.npmjs.com/package/@dotcontext/cli)\n[![CI](https://github.com/vinilana/dotcontext/actions/workflows/ci.yml/badge.svg)](https://github.com/vinilana/dotcontext/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n> **Formerly `@ai-coders/context`.** Renamed to avoid confusion with Context7 and other \"context\" tools in the AI space. The `.context/` directory standard is unchanged. See [Migration Guide](#migration-from-ai-coderscontext).\n\n**Dotcontext is a harness engineering runtime for AI-assisted software delivery.**\n\nIt gives coding agents a real operating environment instead of a loose prompt and a pile of conventions. Dotcontext combines shared project context, workflow structure, policies, sensors, task contracts, replayable execution history, and MCP access into one system.\n\nThe point is not just to \"give the model more context\". The point is to make agent execution legible, constrained, reusable, and auditable.\n\n## What Dotcontext Is\n\nDotcontext is three things at once:\n\n- a `.context/` convention for durable project knowledge\n- a harness runtime that governs how agents execute work\n- a CLI and MCP surface that expose that runtime to humans and AI tools\n\nPREVC remains the default execution model for structured work: **Planning, Review, Execution, Validation, and Confirmation**.\n\n## Why Dotcontext Exists\n\nMost agent workflows break down for the same reasons:\n\n- project knowledge is scattered across tool-specific formats\n- execution rules live in prompts instead of in runtime controls\n- agents can change code without producing evidence\n- there is no durable record of why an agent did what it did\n- teams cannot reuse the same operating model across Claude, Cursor, Codex, Copilot, and others\n\nDotcontext exists to solve that layer, not just the prompt layer.\n\n## Architecture\n\nDotcontext is now organized around an explicit harness runtime:\n\n```text\ncli -> harness <- mcp\n```\n\n- `@dotcontext/cli` is the operator-facing surface\n- `dotcontext/harness` is the reusable runtime and domain layer\n- `dotcontext/mcp` is the MCP transport adapter\n\nThe main architecture reference, with Mermaid diagrams for runtime flow, boundaries, and packaging, lives in [ARCHITECTURE.md](./ARCHITECTURE.md).\n\n## Problems It Solves\n\n### 1. Context Fragmentation\n\nEvery AI coding tool now has a primary surface plus older compatibility paths that still show up in the wild. Dotcontext keeps track of both so teams can write against the current surface without losing legacy imports.\n\n| Tool | Primary surface | Legacy / compatibility surface |\n| --- | --- | --- |\n| Cursor | `.cursor/rules/*.mdc`, `AGENTS.md`-scoped instructions | `.cursorrules`, `.cursor/rules/*.md` |\n| Claude Code | `CLAUDE.md`, `.claude/agents`, `.claude/skills` | older memory-style files under `.claude/` |\n| GitHub Copilot | `.github/copilot-instructions.md`, `.github/instructions/*.instructions.md`, `.github/agents/*.agent.md`, `.github/skills` | `.github/copilot/*` and `.github/.copilot/*` |\n| Windsurf | `AGENTS.md`, `.windsurf/rules`, `.windsurf/skills` | `.windsurfrules`, older `.windsurf/` rule files |\n| Gemini | `GEMINI.md`, `.gemini/commands`, `.gemini/settings.json`, `.gemini/skills` | older `.gemini/` config layouts |\n| Codex | `AGENTS.md`, `.codex/skills`, `.codex/config.toml` | `.codex/instructions.md` |\n| Google Antigravity | `.agents/rules`, `.agents/workflows` | older `.agent/` layouts |\n| Trae AI | `.trae/rules`, `.trae/agents` | older `.trae/` rule files |\n\nUsing multiple tools means duplicating rules, playbooks, and documentation across incompatible formats.\n\n### 2. Weak Runtime Control\n\nMost agent setups still rely on:\n\n- a long agent file\n- a few MCP tools\n- best-effort conventions\n\nThat is not enough for production-grade behavior. You need runtime controls such as policies, sensors, contracts, and backpressure.\n\n### 3. No Durable Execution Model\n\nWithout sessions, traces, artifacts, and replay:\n\n- agents cannot hand off work cleanly\n- failures are hard to cluster and learn from\n- workflow gates are hard to enforce\n- evaluation becomes anecdotal instead of operational\n\n## What Dotcontext Does\n\nDotcontext consolidates those concerns into one operating model.\n\n### Shared Context\n\nOne `.context/` directory. Works everywhere.\n\n```\n.context/\n├── docs/           # Your documentation (architecture, patterns, decisions)\n├── agents/         # Agent playbooks (code-reviewer, feature-developer, etc.)\n├── plans/          # Work plans linked to PREVC workflow\n└── skills/         # On-demand expertise (commit-message, pr-review, etc.)\n```\n\nExport to any tool.\n**Write once. Use anywhere. No boilerplate.**\n\n### Harness Runtime\n\nThe runtime adds execution controls on top of the shared context:\n\n- durable sessions, traces, artifacts, and checkpoints\n- sensors and backpressure\n- task contracts and handoffs\n- policy enforcement\n- replay and failure dataset generation\n\n### Multi-Surface Access\n\nThe same runtime is exposed through:\n\n- `@dotcontext/cli` for operator workflows\n- `dotcontext/mcp` for AI tools\n- `dotcontext/harness` as the reusable domain/runtime boundary\n\n## How The Harness Works\n\nAt runtime, both the CLI and the MCP server delegate to the same harness services. The harness is responsible for:\n\n- durable sessions, traces, artifacts, and checkpoints\n- sensors and backpressure\n- task contracts and handoffs\n- policy enforcement\n- replay generation\n- failure dataset clustering\n\n```mermaid\nflowchart LR\n    CLI[\"CLI\"] --> H[\"Harness Runtime\"]\n    MCP[\"MCP Server\"] --> H\n\n    H --> S[\"Sessions + State\"]\n    H --> Q[\"Sensors + Backpressure\"]\n    H --> T[\"Task Contracts + Handoffs\"]\n    H --> P[\"Policy Engine\"]\n    H --> R[\"Replay + Datasets\"]\n```\n\nFor the full system view, see [ARCHITECTURE.md](./ARCHITECTURE.md).\n\n> **Using GitHub Copilot, Cursor, Claude, or another AI tool?**\n> Just run `npx @dotcontext/mcp install` — no API key needed!\n>\n> **Usando GitHub Copilot, Cursor, Claude ou outra ferramenta de IA?**\n> Execute `npx @dotcontext/mcp install` — sem necessidade de API key!\n\n> **Note / Nota**\n> Standalone CLI generation is no longer supported. Use MCP-enabled AI tools to create, fill, or refresh context.\n> A geração na CLI standalone não é mais suportada. Use ferramentas com MCP para criar, preencher ou atualizar o contexto.\n\n## Getting Started / Como Começar\n\n### Path 1: MCP (Recommended / Recomendado) — no API key\n\n#### English\n\n1. Run `npx @dotcontext/mcp install`\n2. Prompt your AI agent: `init the context`\n3. Then: `plan [YOUR TASK] using dotcontext`\n4. After planned: `start the workflow`\n\n**No API key needed.** Your AI tool provides the LLM.\n\n#### Português\n\n1. Execute `npx @dotcontext/mcp install`\n2. Diga ao seu agente de IA: `init the context`\n3. Depois: `plan [SUA TAREFA] using dotcontext`\n4. Após o planejamento: `start the workflow`\n\n**Sem necessidade de API key.** Sua ferramenta de IA fornece o LLM.\n\n### Path 2: Standalone CLI — sync, imports, and admin tools\n\n#### English\n\n1. Run `npx -y @dotcontext/cli@latest`\n2. Use the interactive CLI for sync, reverse sync, hidden admin tools, and MCP setup\n3. When you need context creation or AI-generated content, use your MCP-connected AI tool\n\n#### Português\n\n1. Execute `npx -y @dotcontext/cli@latest`\n2. Use a CLI interativa para sincronização, reverse sync, ferramentas administrativas ocultas e configuração MCP\n3. Quando precisar criar contexto ou gerar conteúdo com IA, use sua ferramenta conectada via MCP\n\n## MCP Server Setup\n\nThis package includes an MCP (Model Context Protocol) server that provides AI coding assistants with powerful tools to analyze and document your codebase.\n\n### Recommended Installation\n\nUse the installer. It is the source of truth for supported tools and config formats:\n\n```bash\nnpx @dotcontext/mcp install\n```\n\nIf you already have the MCP package installed globally, `dotcontext-mcp install` works too. The legacy `dotcontext mcp:install` CLI flow still works as a compatibility path.\n\nThe installer:\n- Detects installed AI tools on your system\n- Opens the interactive tool picker when you run `install` in a terminal without a tool id\n- Configures the `dotcontext` MCP server in each tool\n- Supports global (home directory) and local (project directory) installation\n- Falls back to `all` detected tools in non-interactive runs without a tool id\n- Merges with existing MCP configurations without overwriting unrelated servers\n- Includes `--dry-run` and `--verbose` modes\n- Writes the config shape required by each supported client\n\nExamples:\n\n```bash\n# Interactive install for detected tools\nnpx @dotcontext/mcp install\n\n# Install for a specific tool\nnpx @dotcontext/mcp install codex\n\n# Install in the current project instead of your home directory\nnpx @dotcontext/mcp install cursor --local\n\n# Preview without writing files\nnpx @dotcontext/mcp install claude --dry-run --verbose\n```\n\n### Supported MCP Install Targets\n\n`install` currently supports these tool ids:\n\n| Tool ID | Tool | Config Shape |\n| --- | --- | --- |\n| `claude` | Claude Code | `mcpServers` JSON |\n| `cursor` | Cursor AI | `mcpServers` JSON with `type: \"stdio\"` |\n| `windsurf` | Windsurf | `mcpServers` JSON |\n| `continue` | Continue.dev | standalone `.continue/mcpServers/dotcontext.json` |\n| `claude-desktop` | Claude Desktop | `mcpServers` JSON |\n| `vscode` | VS Code (GitHub Copilot) | `servers` JSON |\n| `roo` | Roo Code | `mcpServers` JSON |\n| `amazonq` | Amazon Q Developer CLI | `mcpServers` JSON |\n| `gemini-cli` | Gemini CLI | `mcpServers` JSON |\n| `codex` | Codex CLI | TOML `[mcp_servers.dotcontext]` |\n| `kiro` | Kiro | `mcpServers` JSON |\n| `zed` | Zed Editor | `context_servers` JSON |\n| `jetbrains` | JetBrains IDEs | `servers` array |\n| `trae` | Trae AI | `mcpServers` JSON |\n| `kilo` | Kilo Code | `mcp` JSON |\n| `copilot-cli` | GitHub Copilot CLI | `mcpServers` JSON |\n\n### Manual Configuration\n\nUse manual configuration only when you cannot use `@dotcontext/mcp install`. The exact file format depends on the client.\n\nDotcontext writes this command into client configs:\n\n```text\ncommand: npx\nargs: [\"-y\", \"@dotcontext/mcp@latest\"]\n```\n\n#### Standard `mcpServers` JSON\n\nUsed by tools such as Claude Code, Windsurf, Claude Desktop, Roo Code, Amazon Q Developer CLI, Gemini CLI, Trae AI, and GitHub Copilot CLI.\n\n```json\n{\n  \"mcpServers\": {\n    \"dotcontext\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dotcontext/mcp@latest\"]\n    }\n  }\n}\n```\n\n#### Cursor\n\nCursor expects `type: \"stdio\"`:\n\n```json\n{\n  \"mcpServers\": {\n    \"dotcontext\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dotcontext/mcp@latest\"]\n    }\n  }\n}\n```\n\n#### Continue.dev\n\nContinue uses a standalone per-server file:\n\n```json\n{\n  \"command\": \"npx\",\n  \"args\": [\"-y\", \"@dotcontext/mcp@latest\"],\n  \"env\": {}\n}\n```\n\n#### VS Code (GitHub Copilot)\n\nVS Code uses `servers` instead of `mcpServers`:\n\n```json\n{\n  \"servers\": {\n    \"dotcontext\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dotcontext/mcp@latest\"]\n    }\n  }\n}\n```\n\n#### Zed\n\nZed uses `context_servers`:\n\n```json\n{\n  \"context_servers\": {\n    \"dotcontext\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dotcontext/mcp@latest\"],\n      \"env\": {}\n    }\n  }\n}\n```\n\n#### JetBrains IDEs\n\nJetBrains uses a `servers` array:\n\n```json\n{\n  \"servers\": [\n    {\n      \"name\": \"dotcontext\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dotcontext/mcp@latest\"],\n      \"env\": {}\n    }\n  ]\n}\n```\n\n#### Kilo Code\n\nKilo uses `mcp.dotcontext` with a command array:\n\n```json\n{\n  \"mcp\": {\n    \"dotcontext\": {\n      \"type\": \"local\",\n      \"command\": [\"npx\", \"-y\", \"@dotcontext/mcp@latest\"],\n      \"enabled\": true\n    }\n  }\n}\n```\n\n#### Codex CLI\n\nCodex uses TOML:\n\n```toml\n[mcp_servers.dotcontext]\ncommand = \"npx\"\nargs = [\"-y\", \"@dotcontext/mcp@latest\"]\n```\n\n### Local Development\n\nFor local development, point directly to the dedicated MCP binary after `npm run build`:\n\n```json\n{\n  \"mcpServers\": {\n    \"dotcontext-dev\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/this-repo/dist/mcp/bin.js\"]\n    }\n  }\n}\n```\n\n## Youtube video\n[![Watch the video](https://img.youtube.com/vi/p9uV3CeLaKY/0.jpg)](https://www.youtube.com/watch?v=p9uV3CeLaKY)\n\n## Connect with Us\n\nBuilt by [AI Coders Academy](http://aicoders.academy/) — Learn AI-assisted development and become a more productive developer.\n\n- [AI Coders Academy](http://aicoders.academy/) — Courses and resources for AI-powered coding\n- [YouTube Channel](https://www.youtube.com/@aicodersacademy) — Tutorials, demos, and best practices\n- [Connect with Vini](https://www.linkedin.com/in/viniciuslanadepaula/) — Creator of @dotcontext/cli\n\n\n## Why PREVC?\n\n### English\n\nLLMs produce better results when they follow a structured process instead of generating code blindly. PREVC ensures:\n\n- **Specifications before code** — AI understands what to build before building it\n- **Context awareness** — Each phase has the right documentation and agent\n- **Human checkpoints** — Review and validate at each step, not just at the end\n- **Reproducible quality** — Same process, consistent results across projects\n\n### Português\n\nLLMs produzem melhores resultados quando seguem um processo estruturado em vez de gerar código cegamente. PREVC garante:\n\n- **Especificações antes do código** — IA entende o que construir antes de construir\n- **Consciência de contexto** — Cada fase tem a documentação e o agente corretos\n- **Checkpoints humanos** — Revise e valide em cada etapa, não apenas no final\n- **Qualidade reproduzível** — Mesmo processo, resultados consistentes entre projetos\n\n## What it does / O que faz\n\n### English\n\n1. **Creates documentation** — Structured docs from your codebase (architecture, data flow, decisions)\n2. **Generates agent playbooks** — 14 specialized AI agents (code-reviewer, bug-fixer, architect, etc.)\n3. **Smart scaffold filtering** — Automatically detects project type and generates only relevant content\n4. **Useful out-of-the-box** — Scaffolds include practical template content, not empty placeholders\n5. **Manages workflows** — PREVC process with scale detection, gates, and execution history\n6. **Provides skills** — On-demand expertise (commit messages, PR reviews, security audits)\n7. **Syncs everywhere** — Export to Cursor, Claude, Copilot, Windsurf, Cline, Codex, Antigravity, Trae, and more\n8. **Tracks execution** — Step-level tracking with git integration for workflow phases\n9. **Keeps it updated** — Detects code changes and suggests documentation updates\n\n### Português\n\n1. **Cria documentação** — Docs estruturados do seu codebase (arquitetura, fluxo de dados, decisões)\n2. **Gera playbooks de agentes** — 14 agentes de IA especializados (code-reviewer, bug-fixer, architect, etc.)\n3. **Filtragem inteligente de scaffold** — Detecta automaticamente o tipo de projeto e gera apenas conteúdo relevante\n4. **Útil de imediato** — Scaffolds incluem conteúdo prático, não placeholders vazios\n5. **Gerencia workflows** — Processo PREVC com detecção de escala, gates e histórico de execução\n6. **Fornece skills** — Expertise sob demanda (mensagens de commit, revisões de PR, auditorias de segurança)\n7. **Sincroniza em todos os lugares** — Exporte para Cursor, Claude, Copilot, Windsurf, Cline, Codex, Antigravity, Trae e mais\n8. **Rastreia execução** — Rastreamento por etapa com integração git para fases de workflow\n9. **Mantém atualizado** — Detecta mudanças no código e sugere atualizações de documentação\n\nPT-BR Tutorial\nhttps://www.youtube.com/watch?v=5BPrfZAModk\n\n## PREVC Workflow System\n\nA universal 5-phase process designed to improve LLM output quality through structured, spec-driven development:\n\n| Phase | Name | Purpose |\n|-------|------|---------|\n| **P** | Planning | Define what to build. Gather requirements, write specs, identify scope. No code yet. |\n| **R** | Review | Validate the approach. Architecture decisions, technical design, risk assessment. |\n| **E** | Execution | Build it. Implementation follows the approved specs and design. |\n| **V** | Validation | Verify it works. Tests, QA, code review against original specs. |\n| **C** | Confirmation | Ship it. Documentation, deployment, stakeholder handoff. |\n\n### The Problem with Autopilot AI\n\nMost AI coding workflows look like this:\n```\nUser: \"Add authentication\"\nAI: *generates 500 lines of code*\nUser: \"That's not what I wanted...\"\n```\n\nPREVC fixes this:\n```\nP: What type of auth? OAuth, JWT, session? What providers?\nR: Here's the architecture. Dependencies: X, Y. Risks: Z. Approve?\nE: Implementing approved design...\nV: All 15 tests pass. Security audit complete.\nC: Deployed. Docs updated. Ready for review.\n```\n\n## Documentation\n\n- [User Guide](./docs/GUIDE.md) — Complete usage guide\n\n\n### Smart Project Detection\n\nThe system automatically detects your project type and generates only relevant scaffolds:\n\n| Project Type | Detected By | Docs | Agents |\n|--------------|-------------|------|--------|\n| **CLI** | `bin` field, commander/yargs | Core docs | Core agents |\n| **Web Frontend** | React, Vue, Angular, Svelte | + architecture, security | + frontend, devops |\n| **Web Backend** | Express, NestJS, FastAPI | + architecture, data-flow, security | + backend, database, devops |\n| **Full Stack** | Both frontend + backend | All docs | All agents |\n| **Mobile** | React Native, Flutter | + architecture, security | + mobile, devops |\n| **Library** | `main`/`exports` without `bin` | Core docs | Core agents |\n| **Monorepo** | Lerna, Nx, Turborepo | All docs | All agents |\n\n**Core scaffolds** (always included):\n- Docs: project-overview, development-workflow, testing-strategy, tooling\n- Agents: code-reviewer, bug-fixer, feature-developer, refactoring-specialist, test-writer, documentation-writer, performance-optimizer\n\n### Scale-Adaptive Routing\n\nThe system automatically detects project scale and adjusts the workflow:\n\n| Scale | Phases | Use Case |\n|-------|--------|----------|\n| QUICK | E → V | Bug fixes, small tweaks |\n| SMALL | P → E → V | Simple features |\n| MEDIUM | P → R → E → V | Regular features |\n| LARGE | P → R → E → V → C | Complex systems, compliance |\n\n## CLI Reference\n\n### Requirements\n\n- Node.js 20+\n\n**Context creation, AI generation, and refresh are MCP-only.** Use `npx @dotcontext/mcp install` and let your AI tool use its own LLM.\n\n### Available MCP Tools\n\nOnce configured, your AI assistant will have access to 9 gateway tools with action-based dispatching:\n\n#### Gateway Tools (Primary Interface)\n\n| Gateway | Description | Actions |\n|---------|-------------|---------|\n| **explore** | File and code exploration | `read`, `list`, `analyze`, `search`, `getStructure` |\n| **context** | Context scaffolding, semantic context, and optional Q&A/flow helpers | `check`, `bootstrapStatus`, `init`, `fill`, `fillSingle`, `listToFill`, `getMap`, `buildSemantic`, `scaffoldPlan`, `searchQA`, `generateQA`, `getFlow`, `detectPatterns` |\n| **plan** | Plan management and execution tracking | `link`, `getLinked`, `getDetails`, `getForPhase`, `updatePhase`, `recordDecision`, `updateStep`, `getStatus`, `syncMarkdown`, `commitPhase` |\n| **agent** | Agent orchestration and discovery | `discover`, `getInfo`, `orchestrate`, `getSequence`, `getDocs`, `getPhaseDocs`, `listTypes` |\n| **skill** | Skill management for on-demand expertise | `list`, `getContent`, `getForPhase`, `scaffold`, `export`, `fill` |\n| **sync** | Import/export synchronization with AI tools | `exportRules`, `exportDocs`, `exportAgents`, `exportContext`, `exportSkills`, `reverseSync`, `importDocs`, `importAgents`, `importSkills` |\n\n`context init` also bootstraps `.context/harness/sensors.json`. While that catalog is still in bootstrap form, `context listToFill`/`fill` can return it so the AI can customize project-specific quality sensors.\n\n`searchQA` ranks generated `.context/docs/qa/*.md` helper docs by keyword match. It is a lightweight shortcut, not embedding-based semantic retrieval, and `generateQA` is opt-in.\n\n#### Dedicated Workflow Tools\n\n| Tool | Description |\n|------|-------------|\n| **workflow-init** | Initialize a PREVC workflow with scale detection, gates, and autonomous mode |\n| **workflow-status** | Get current workflow status, phases, and execution history |\n| **workflow-advance** | Advance to the next PREVC phase with gate checking |\n| **workflow-manage** | Manage handoffs, collaboration, documents, gates, and approvals |\n\n#### Key Features in v0.7.0\n\n- **Gateway Pattern**: Simplified, action-based tools reduce cognitive load\n- **Plan Execution Tracking**: Step-level tracking with `updateStep`, `getStatus`, `syncMarkdown` actions\n- **Git Integration**: `commitPhase` action for creating commits on phase completion\n- **Q&A & Pattern Detection**: Automatic Q&A generation and functional pattern analysis\n- **Execution History**: Comprehensive logging of all workflow actions to `.context/workflow/actions.jsonl`\n- **Workflow Gates**: Phase transition gates based on project scale with approval requirements\n- **Export/Import Tools**: Granular control over docs, agents, and skills sync with merge strategies\n\n### Skills (On-Demand Expertise)\n\nSkills are task-specific procedures that AI agents activate when needed:\n\n| Skill | Description | Phases |\n|-------|-------------|--------|\n| `commit-message` | Generate conventional commits | E, C |\n| `pr-review` | Review PRs against standards | R, V |\n| `code-review` | Code quality review | R, V |\n| `test-generation` | Generate test cases | E, V |\n| `documentation` | Generate/update docs | P, C |\n| `refactoring` | Safe refactoring steps | E |\n| `bug-investigation` | Bug investigation flow | E, V |\n| `feature-breakdown` | Break features into tasks | P |\n| `api-design` | Design RESTful APIs | P, R |\n| `security-audit` | Security review checklist | R, V |\n\n```bash\nnpx -y @dotcontext/cli@latest admin skill list   # List available skills\nnpx -y @dotcontext/cli@latest admin skill export # Export to AI tools\n```\n\nUse MCP tools from your AI assistant to scaffold, fill, or refresh skills and other context files.\n\n### Agent Types\n\nThe orchestration system maps tasks to specialized agents:\n\n| Agent | Focus |\n|-------|-------|\n| `architect-specialist` | System architecture and patterns |\n| `feature-developer` | New feature implementation |\n| `bug-fixer` | Bug identification and fixes |\n| `test-writer` | Test suites and coverage |\n| `code-reviewer` | Code quality and best practices |\n| `security-auditor` | Security vulnerabilities |\n| `performance-optimizer` | Performance bottlenecks |\n| `documentation-writer` | Technical documentation |\n| `backend-specialist` | Server-side logic and APIs |\n| `frontend-specialist` | User interfaces |\n| `database-specialist` | Database solutions |\n| `devops-specialist` | CI/CD and deployment |\n| `mobile-specialist` | Mobile applications |\n| `refactoring-specialist` | Code structure improvements |\n\n\n## Migration from @ai-coders/context\n\n### Why the rename?\n\nThe previous name `@ai-coders/context` caused frequent confusion with **Context7** and other tools that use \"context\" in their name. In the AI/LLM tooling space, \"context\" is too generic. The new name **dotcontext** is unique, searchable, and directly references the `.context/` directory convention at the core of this tool.\n\n### What changed\n\n| Before | After |\n|--------|-------|\n| `npm install @ai-coders/context` | `npm install @dotcontext/cli` |\n| `npx @ai-coders/context` | `npx -y @dotcontext/cli@latest` |\n| CLI command: `ai-context` | CLI command: `dotcontext` |\n| MCP server name: `\"ai-context\"` | MCP server name: `\"dotcontext\"` |\n| Env var: `AI_CONTEXT_LANG` | Env var: `DOTCONTEXT_LANG` |\n\n### What did NOT change\n\n- The `.context/` directory structure and all its contents\n- The PREVC workflow system\n- All MCP tool names and actions\n- All scaffold formats and frontmatter conventions\n- The MIT license\n\n### Step-by-step migration\n\n1. **Update your global install** (if applicable):\n   ```bash\n   npm uninstall -g @ai-coders/context\n   npm install -g @dotcontext/cli\n   ```\n\n2. **Update MCP configurations** -- re-run the installer:\n   ```bash\n   npx @dotcontext/mcp install\n   ```\n   Or manually replace `\"ai-context\"` with `\"dotcontext\"` and `\"@ai-coders/context\"` with `\"@dotcontext/mcp\"` in your MCP JSON configs.\n\n3. **Update shell aliases** -- replace `ai-context` with `dotcontext` in your `.bashrc`, `.zshrc`, or equivalent.\n\n4. **Update environment variables** -- rename `AI_CONTEXT_LANG` to `DOTCONTEXT_LANG` if you set it.\n\n5. **No changes to `.context/` needed** -- the directory, files, and frontmatter are all unchanged.\n\n## License\n\nMIT © Vinícius Lana\n"
  },
  {
    "path": "docs/CONTEXT-WORKFLOW-HARNESS-CLI-PTBR.md",
    "content": "# Guia Educativo: Contexto, Workflow, Harness e CLI no Dotcontext\n\nEste material explica como o dotcontext organiza contexto, workflow e execucao assistida por IA. A ideia central do projeto nao e \"dar mais texto para o modelo\", e sim criar um sistema operacional de trabalho para agentes.\n\nUse este guia como complemento de:\n\n- `README.md` para visao geral do produto\n- `ARCHITECTURE.md` para arquitetura consolidada\n- `docs/GUIDE.md` para uso pratico da ferramenta\n\n## 1. Mapa Mental\n\nEm uma frase para cada conceito:\n\n\n| Conceito    | Papel no sistema                                      |\n| ----------- | ----------------------------------------------------- |\n| `.context/` | Memoria operacional e conhecimento duravel do projeto |\n| `workflow`  | Processo estruturado de trabalho, baseado em PREVC    |\n| `harness`   | Runtime que controla e persiste a execucao do agente  |\n| `cli`       | Superficie para operadores humanos                    |\n| `mcp`       | Superficie para ferramentas de IA                     |\n\n\nO modelo arquitetural e este:\n\n```mermaid\nflowchart LR\n    Human[\"Operador humano\"] --> CLI[\"CLI\"]\n    AI[\"Ferramenta de IA\"] --> MCP[\"MCP adapter\"]\n\n    CLI --> H[\"Harness runtime\"]\n    MCP --> H\n\n    H --> C[\"Contexto compartilhado\"]\n    H --> W[\"Workflow PREVC\"]\n    H --> R[\"Estado de execucao\"]\n    H --> G[\"Policies, sensors e contracts\"]\n```\n\n\n\nLeitura correta do diagrama:\n\n- `cli` e `mcp` sao portas de entrada\n- `harness` e o nucleo reutilizavel\n- o workflow nao substitui o harness; ele roda em cima do harness\n- o contexto nao e apenas documentacao; ele tambem alimenta workflow, skills, agentes e operacao\n\n## 2. O Que E o Sistema de Contexto\n\nNo dotcontext, \"contexto\" e um conjunto de artefatos persistentes e operacionais. Nao e apenas um prompt grande.\n\nO diretorio `.context/` concentra esse conhecimento:\n\n```mermaid\nflowchart TD\n    Root[\".context/\"]\n\n    Root --> Docs[\"docs/\"]\n    Root --> Agents[\"agents/\"]\n    Root --> Skills[\"skills/\"]\n    Root --> Plans[\"plans/\"]\n    Root --> Workflow[\"workflow/\"]\n    Root --> Harness[\"harness/\"]\n\n    Harness --> Sensors[\"sensors.json\"]\n    Harness --> Policy[\"policy.json\"]\n    Harness --> Sessions[\"sessions/\"]\n    Harness --> Traces[\"traces/\"]\n    Harness --> Artifacts[\"artifacts/\"]\n    Harness --> Contracts[\"contracts/\"]\n    Harness --> HWorkflows[\"workflows/\"]\n    Harness --> Replays[\"replays/\"]\n    Harness --> Datasets[\"datasets/\"]\n```\n\n\n\n### 2.1 Tipos de contexto\n\nO projeto separa contexto em tres classes importantes:\n\n\n| Classe      | Exemplos                                                                                             | Vai para git?   | Funcao                                         |\n| ----------- | ---------------------------------------------------------------------------------------------------- | --------------- | ---------------------------------------------- |\n| `versioned` | `.context/docs`, `.context/agents`, `.context/skills`, `harness/sensors.json`, `harness/policy.json` | Sim             | Conhecimento duravel e configuracao do projeto |\n| `local`     | `.context/plans`                                                                                     | Nao, por padrao | Artefatos locais de trabalho e planejamento    |\n| `runtime`   | `.context/workflow`, `.context/harness/sessions`, `traces`, `artifacts`, `replays`, `datasets`       | Nao             | Estado vivo de execucao                        |\n\n\nEssa distincao e importante porque evita misturar:\n\n- conhecimento duravel com lixo transiente de execucao\n- especificacao com historico de runtime\n- configuracao de equipe com estado local do agente\n\n### 2.2 O que entra no contexto\n\nEm termos praticos, o sistema de contexto agrega:\n\n- documentacao do projeto\n- playbooks de agentes\n- skills reutilizaveis\n- planos de implementacao\n- configuracao de sensores e policy\n- estado de workflow e runtime\n- contexto semantico gerado a partir do codebase\n\n## 3. Engenharia de Contexto\n\nEngenharia de contexto nao e \"colocar mais informacao no prompt\". E desenhar um sistema em que a informacao certa aparece na fase certa, no formato certo, com persistencia e reutilizacao.\n\nNo dotcontext, isso aparece em quatro camadas:\n\n1. `scaffolding`: cria a estrutura base de `.context/`\n2. `semantic analysis`: gera contexto a partir do codigo real\n3. `workflow`: escolhe quando cada contexto deve ser usado\n4. `runtime`: registra o que foi feito, com evidencias e restricoes\n\n```mermaid\nflowchart LR\n    Codebase[\"Repositorio\"] --> Scan[\"Analise semantica\"]\n    Scan --> Scaffold[\"Scaffolding .context/\"]\n    Scaffold --> Docs[\"Docs, agents, skills, plans\"]\n    Docs --> Workflow[\"Workflow PREVC\"]\n    Workflow --> Runtime[\"Harness runtime\"]\n    Runtime --> Evidence[\"Traces, artifacts, sensores, replay\"]\n```\n\n\n\n### 3.1 O que isso resolve\n\nSem engenharia de contexto, o fluxo costuma ser:\n\n- pedido do usuario\n- prompt improvisado\n- geracao direta de codigo\n- pouco controle sobre por que aquilo foi feito\n\nCom engenharia de contexto, o fluxo vira:\n\n- pedido do usuario\n- descoberta do repositorio\n- materializacao de contexto duravel\n- definicao de plano e criterios\n- execucao com rastreabilidade\n\n## 4. Spec-Driven Development\n\nSpec-driven development significa que o codigo e consequencia da especificacao, nao o contrario.\n\nAqui, \"spec\" nao significa apenas um documento formal. Pode incluir:\n\n- escopo\n- requisitos\n- criterios de aceitacao\n- decisoes arquiteturais\n- riscos e dependencias\n- plano por fase\n- evidencias de validacao\n\nO README resume isso bem: primeiro entender o que construir, depois validar a abordagem, depois implementar.\n\n```mermaid\nflowchart LR\n    Ask[\"Pedido\"] --> Spec[\"Especificacao\"]\n    Spec --> Review[\"Revisao tecnica\"]\n    Review --> Build[\"Implementacao\"]\n    Build --> Validate[\"Validacao\"]\n    Validate --> Confirm[\"Confirmacao e handoff\"]\n```\n\n\n\n### 4.1 Como PREVC implementa isso\n\nO workflow PREVC e a espinha dorsal do spec-driven development no dotcontext:\n\n\n| Fase | Significado  | Pergunta principal                              |\n| ---- | ------------ | ----------------------------------------------- |\n| `P`  | Planning     | O que precisa ser feito?                        |\n| `R`  | Review       | Esta abordagem faz sentido?                     |\n| `E`  | Execution    | Como implementar o que foi aprovado?            |\n| `V`  | Validation   | Isso atende a spec e passou nos checks?         |\n| `C`  | Confirmation | O que fica documentado, entregue e transferido? |\n\n\n```mermaid\nflowchart LR\n    P[\"P<br/>Planning\"] --> R[\"R<br/>Review\"]\n    R --> E[\"E<br/>Execution\"]\n    E --> V[\"V<br/>Validation\"]\n    V --> C[\"C<br/>Confirmation\"]\n\n    P --> POut[\"Escopo, requisitos, criterios\"]\n    R --> ROut[\"Arquitetura, riscos, aprovacoes\"]\n    E --> EOut[\"Codigo, testes basicos, artefatos\"]\n    V --> VOut[\"QA, review, evidencias\"]\n    C --> COut[\"Docs finais, changelog, handoff\"]\n```\n\n\n\n### 4.2 Por que isso melhora agentes\n\nSem spec-driven development, o modelo tenta adivinhar intencoes. Com spec-driven development:\n\n- o agente tem uma fronteira clara de escopo\n- a revisao acontece antes do custo alto da implementacao\n- validacao compara a entrega contra a spec original\n- o trabalho fica reproduzivel entre pessoas, ferramentas e sessoes\n\n## 5. Workflow Nao E Harness\n\nEssa distincao merece destaque:\n\n\n| Elemento   | Funcao                                                             |\n| ---------- | ------------------------------------------------------------------ |\n| `workflow` | Define as fases, gates e transicoes de trabalho                    |\n| `harness`  | Garante estado, evidencia, policy, sensores e controle de execucao |\n\n\nEm outras palavras:\n\n- workflow responde \"em que etapa estamos?\"\n- harness responde \"o que aconteceu, com que evidencias, sob quais regras?\"\n\nNo projeto atual, o estado canonico do workflow e persistido em `.context/harness/workflows/prevc.json`, enquanto `.context/workflow/` continua sendo a area operacional de workflow.\n\n## 6. Harness Engineering\n\nHarness engineering e o passo seguinte depois de prompt engineering e context engineering.\n\n### 6.1 Diferenca entre as tres ideias\n\n\n| Abordagem           | Foco                                          | Limite                                      |\n| ------------------- | --------------------------------------------- | ------------------------------------------- |\n| Prompt engineering  | Melhorar uma interacao                        | Nao controla o ciclo de vida inteiro        |\n| Context engineering | Organizar conhecimento e recuperacao          | Ainda nao garante comportamento operacional |\n| Harness engineering | Controlar a execucao com runtime e evidencias | Exige arquitetura e persistencia            |\n\n\n### 6.2 O que o harness faz aqui\n\nO harness do dotcontext centraliza servicos reutilizaveis como:\n\n- sessoes\n- traces\n- artifacts\n- checkpoints\n- task contracts\n- handoff contracts\n- sensors\n- backpressure\n- policies\n- replay\n- failure datasets\n\n```mermaid\nflowchart TD\n    H[\"Harness runtime\"]\n\n    H --> Session[\"Sessions\"]\n    H --> Trace[\"Traces\"]\n    H --> Artifact[\"Artifacts\"]\n    H --> Contract[\"Task contracts\"]\n    H --> Sensor[\"Sensors\"]\n    H --> Policy[\"Policy engine\"]\n    H --> Replay[\"Replay service\"]\n    H --> Dataset[\"Failure datasets\"]\n    H --> WorkflowState[\"Workflow binding/state\"]\n```\n\n\n\n### 6.3 Ciclo de vida de execucao\n\n```mermaid\nsequenceDiagram\n    participant U as Usuario ou AI tool\n    participant A as CLI ou MCP\n    participant H as Harness\n    participant S as .context/harness\n\n    U->>A: Iniciar tarefa ou workflow\n    A->>H: Criar ou carregar sessao\n    H->>S: Persistir sessao\n\n    A->>H: Definir task contract\n    H->>S: Persistir contrato\n\n    A->>H: Anexar traces, artifacts e sensor runs\n    H->>S: Persistir evidencias\n\n    H->>H: Avaliar policy e backpressure\n\n    alt Pode concluir\n        H->>S: Checkpoint ou complete\n        H-->>A: Sucesso\n    else Bloqueado\n        H-->>A: Motivos e acoes para desbloqueio\n    end\n```\n\n\n\n### 6.4 Por que isso importa\n\nSem harness engineering, um agente pode:\n\n- editar codigo sem contexto suficiente\n- concluir tarefa sem evidencia\n- pular validacao\n- perder historico entre sessoes\n\nCom harness engineering, a execucao fica:\n\n- rastreavel\n- auditavel\n- sujeita a regras\n- reutilizavel entre CLI, MCP e futuros adapters\n\n## 7. Papel da CLI\n\nA CLI e a interface do operador humano. Ela nao deveria concentrar regra de dominio reutilizavel.\n\nNo repositorio, isso aparece em:\n\n- `src/index.ts`: definicao dos comandos\n- `src/cli/index.ts`: boundary da superficie CLI\n- `src/services/cli`: servicos especificos de CLI\n\nResponsabilidades principais da CLI hoje:\n\n- iniciar a configuracao MCP e os fluxos operacionais\n- expor comandos operacionais e administrativos\n- executar sync/import/export\n- acompanhar workflow fora da ferramenta de IA\n\nExemplos:\n\n```bash\nnpx -y @dotcontext/cli@latest\nnpx @dotcontext/mcp install\nnpx -y @dotcontext/cli@latest admin workflow status\nnpx -y @dotcontext/cli@latest sync --preset cursor\n```\n\nPonto importante: a CLI nao e o centro do produto. Ela e uma superficie de operacao.\n\n## 8. Papel do MCP\n\nO MCP e o adaptador para ferramentas de IA. Ele expoe o runtime atraves de tools.\n\nNo codigo, isso aparece em:\n\n- `src/mcp/index.ts`: boundary MCP\n- `src/services/mcp/mcpServer.ts`: servidor MCP\n- `src/services/mcp/gateway`: handlers das tools\n\nEm vez de despejar toda a logica na camada MCP, o projeto tenta manter o MCP como adaptador fino:\n\n- recebe request\n- valida parametros\n- chama servicos do harness\n- devolve resposta para a ferramenta\n\nExemplos de tools MCP:\n\n- `context`\n- `workflow-init`\n- `workflow-status`\n- `workflow-advance`\n- `workflow-manage`\n- `plan`\n- `agent`\n- `skill`\n\n## 9. Onde Cada Responsabilidade Mora no Codigo\n\nSe voce for evoluir o sistema, este mapa evita acoplamento errado:\n\n\n| Se voce quer mudar...                       | Preferir                                      |\n| ------------------------------------------- | --------------------------------------------- |\n| logica reutilizavel de execucao             | `src/services/harness`                        |\n| superficie de runtime exportada como pacote | `src/harness/index.ts`                        |\n| shape do protocolo MCP e handlers           | `src/services/mcp/gateway`                    |\n| boundary MCP                                | `src/mcp/index.ts`                            |\n| comandos e UX de terminal                   | `src/index.ts`, `src/cli`, `src/services/cli` |\n| modelo PREVC, gates e orquestracao          | `src/workflow` e `src/services/workflow`      |\n\n\nEsse alinhamento preserva a intencao arquitetural do repositorio:\n\n```text\ncli -> harness <- mcp\n```\n\n## 10. Fluxo Completo de Exemplo\n\nImagine a entrega de uma feature de autenticacao.\n\n```mermaid\nsequenceDiagram\n    participant Human as Operador\n    participant Tool as Ferramenta de IA\n    participant MCP as MCP\n    participant Harness as Harness\n    participant Ctx as .context/\n\n    Human->>Tool: \"planeje autenticacao com dotcontext\"\n    Tool->>MCP: context({ action: \"init\" })\n    MCP->>Harness: inicializar contexto\n    Harness->>Ctx: criar docs, agents, skills, sensores\n\n    Tool->>MCP: context({ action: \"fillSingle\" })\n    MCP->>Harness: preencher scaffold com base no repo\n\n    Tool->>MCP: workflow-init(\"auth-rollout\")\n    MCP->>Harness: criar workflow + sessao\n    Harness->>Ctx: persistir workflow e runtime state\n\n    Tool->>MCP: plan({ action: \"link\" })\n    MCP->>Harness: vincular plano ao workflow\n\n    Tool->>MCP: workflow-advance()\n    MCP->>Harness: checar gates e avancar fases\n\n    Tool->>MCP: workflow-status()\n    MCP-->>Tool: status + evidencias + bloqueios\n```\n\n\n\n### 10.1 O que acontece de verdade nesse fluxo\n\n1. O contexto e materializado em disco, nao apenas na conversa.\n2. O workflow ganha fases e gates explicitos.\n3. O harness cria sessao, traces e binding entre workflow e runtime.\n4. A execucao pode ser bloqueada se policy, sensores ou evidencias estiverem incompletos.\n5. O historico fica reutilizavel para replay, dataset e auditoria.\n\n## 11. Resumo Executivo\n\nSe voce precisar explicar o dotcontext rapidamente para outra pessoa, uma boa versao e esta:\n\n- `.context/` e a memoria operacional do projeto\n- PREVC e o processo de desenvolvimento guiado por especificacao\n- o harness e o runtime que controla a execucao do agente\n- a CLI serve o operador humano\n- o MCP serve a ferramenta de IA\n- a arquitetura separa superficie, runtime e protocolo para manter o sistema evolutivo\n\nEm termos de maturidade:\n\n- prompt engineering melhora respostas isoladas\n- context engineering melhora continuidade e relevancia\n- harness engineering melhora controle, rastreabilidade e confiabilidade operacional\n"
  },
  {
    "path": "docs/GUIDE.md",
    "content": "# Dotcontext Guide\n\n`dotcontext` ships separate CLI and MCP package surfaces for shared AI context, PREVC workflow tracking, and context import/export across coding tools.\n\nThis guide reflects the current product shape in `0.9.x`:\n\n- Context creation and AI-generated fills happen through MCP-connected AI tools.\n- The standalone CLI is still useful for sync/import operations, MCP setup, and hidden `admin` utilities.\n- The public product name is `dotcontext`.\n- The operator CLI package is `@dotcontext/cli`.\n- The MCP setup and server package is `@dotcontext/mcp`.\n\n## Start Here\n\nThe fastest path for most users is:\n\n```bash\n# 1. Install the MCP server into your AI tool\nnpx @dotcontext/mcp install\n\n# 2. In your AI tool, ask it to initialize context\n# Example prompts:\n#   init the context\n#   plan authentication rollout using dotcontext\n#   start the workflow\n\n# 3. For non-trivial work, track execution from the CLI\nnpx -y @dotcontext/cli@latest admin workflow init \"feature-name\"\n```\n\nIf you are using the MCP path, you usually do not need an API key. Your AI tool provides the model.\n\n## What The Package Does Today\n\n| Area | Current Role |\n| --- | --- |\n| MCP setup | Installs and configures the `dotcontext` MCP server across supported AI clients with client-specific config formats |\n| Context scaffolding | Creates and fills `.context/` content through MCP-connected AI tools |\n| Workflow tracking | Manages PREVC phase progression from the hidden `admin workflow` surface or MCP |\n| Sync and imports | Exports rules and agents to AI tools, and imports existing tool-specific content back into `.context/` |\n| Skills | Lists and exports built-in or project skills through the hidden `admin skill` surface |\n\n## What Changed\n\nOlder docs and examples may still refer to flows that are no longer the primary path.\n\n- Standalone CLI generation is no longer the recommended path for creating or filling context.\n- Use MCP-connected AI tools for context init, plan scaffolding, and AI-generated content.\n- There is no top-level `quick-sync` command, but interactive quick sync is still available from `npx -y @dotcontext/cli@latest`.\n\nIf you are looking for `init`, `fill`, `plan`, `update`, or `analyze` as direct CLI commands, that is expected. Those responsibilities moved into MCP workflows.\n\n## Recommended Workflow\n\n### 1. Install MCP\n\n```bash\nnpx @dotcontext/mcp install\n```\n\nThis configures the `dotcontext` MCP server for supported AI tools. Use `--dry-run` to preview changes, `--local` to install in the current project instead of your home directory, or pass a specific tool id such as `codex`, `cursor`, or `claude`.\n\nWhen you run `npx @dotcontext/mcp install` in an interactive terminal without a tool id, the installer opens a guided tool picker. In non-interactive contexts, it defaults to installing for `all` detected tools.\n\nExamples:\n\n```bash\nnpx @dotcontext/mcp install codex\nnpx @dotcontext/mcp install cursor --local\nnpx @dotcontext/mcp install claude --dry-run --verbose\n```\n\nCurrently supported install targets include Claude Code, Cursor, Windsurf, Continue.dev, Claude Desktop, VS Code (GitHub Copilot), Roo Code, Amazon Q Developer CLI, Gemini CLI, Codex CLI, Kiro, Zed, JetBrains IDEs, Trae AI, Kilo Code, and GitHub Copilot CLI.\n\n### 2. Initialize Context From Your AI Tool\n\nAfter MCP is installed, ask your AI assistant to initialize the repository context. A typical flow is:\n\n1. Initialize `.context/`\n2. Fill pending docs, agents, or plans\n3. Start a PREVC workflow when the task is not trivial\n\nTypical prompts:\n\n- `init the context`\n- `plan this task using dotcontext`\n- `fill the pending context files`\n\n### 3. Track Execution With PREVC\n\nFor work that needs structure, use the workflow commands:\n\n```bash\nnpx -y @dotcontext/cli@latest admin workflow init \"feature-name\"\nnpx -y @dotcontext/cli@latest admin workflow status\nnpx -y @dotcontext/cli@latest admin workflow advance\n```\n\nThe workflow uses PREVC:\n\n| Phase | Meaning | Typical Output |\n| --- | --- | --- |\n| `P` | Planning | Requirements, PRD, scope |\n| `R` | Review | Architecture, design decisions, approval |\n| `E` | Execution | Code changes and implementation notes |\n| `V` | Validation | Tests, QA, review feedback |\n| `C` | Confirmation | Final docs, changelog, handoff |\n\nFor larger tasks, the workflow can also record handoffs and collaboration:\n\n```bash\nnpx -y @dotcontext/cli@latest admin workflow handoff feature-developer code-reviewer\nnpx -y @dotcontext/cli@latest admin workflow collaborate \"API contract review\"\n```\n\n### 4. Sync or Import Context As Needed\n\nUse the CLI when you need to move rules or agents between `.context/` and tool-specific directories.\n\nExport rules:\n\n```bash\nnpx -y @dotcontext/cli@latest sync --preset cursor\n```\n\nExport agents:\n\n```bash\nnpx -y @dotcontext/cli@latest sync --preset claude\n```\n\nImport rules or agents from existing tool configs:\n\n```bash\nnpx -y @dotcontext/cli@latest import-rules\nnpx -y @dotcontext/cli@latest import-agents\n```\n\nReverse-sync everything back into `.context/`:\n\n```bash\nnpx -y @dotcontext/cli@latest reverse-sync --dry-run\n```\n\n### 4a. Interactive Quick Sync\n\nIf you want a guided export flow instead of calling individual commands, use the interactive CLI:\n\n```bash\nnpx -y @dotcontext/cli@latest\n```\n\nThe quick sync flow can export docs, agents, and skills together. It is part of the interactive experience, not a separate `quick-sync` command.\n\n### 5. Review Progress\n\nGenerate a workflow report when you want a quick status snapshot:\n\n```bash\nnpx -y @dotcontext/cli@latest admin report\n```\n\nYou can also export the report as Markdown or JSON.\n\n## `.context/` Layout\n\nThe exact files vary by project type, but the working structure is centered on `.context/`:\n\n```text\n.context/\n├── docs/        # Project documentation\n├── agents/      # Agent playbooks\n├── skills/      # On-demand expertise guides\n├── plans/       # Plan templates and execution tracking\n└── workflow/    # PREVC workflow state\n```\n\nThe generated docs are project-aware. You should expect the content to differ between CLI tools, libraries, backends, frontends, and monorepos.\n\n## CLI Reference\n\nThese are the main commands currently exposed by the CLI:\n\n| Command | Purpose |\n| --- | --- |\n| `npx -y @dotcontext/cli@latest` | Launch the interactive CLI, including quick sync |\n| `npx @dotcontext/mcp install` | Install MCP configuration for supported AI tools |\n| `npx -y @dotcontext/mcp@latest` | Start the MCP server manually |\n| `npx -y @dotcontext/cli@latest admin workflow ...` | Manage PREVC workflow state |\n| `npx -y @dotcontext/cli@latest admin skill list` | List available skills |\n| `npx -y @dotcontext/cli@latest admin skill export` | Export skills to AI tool directories |\n| `npx -y @dotcontext/cli@latest sync` | Export agent playbooks to AI tools |\n| `npx -y @dotcontext/cli@latest export-rules` | Export `.context/docs` rules to AI tools |\n| `npx -y @dotcontext/cli@latest import-rules` | Import rules into `.context/docs` |\n| `npx -y @dotcontext/cli@latest import-agents` | Import agents into `.context/agents` |\n| `npx -y @dotcontext/cli@latest reverse-sync` | Import rules, agents, and skills into `.context/` |\n| `npx -y @dotcontext/cli@latest admin report` | Generate workflow progress reports |\n\n## MCP Reference\n\nThe MCP server exposes focused tools instead of a large set of one-off commands.\n\n| MCP Tool | Purpose |\n| --- | --- |\n| `explore` | Read files, list paths, search code, analyze symbols, inspect structure |\n| `context` | Check/init/fill context, build semantic context, scaffold plans, and use optional Q&A/flow helpers |\n| `workflow-init` | Start PREVC workflow tracking |\n| `workflow-status` | Read current workflow status |\n| `workflow-advance` | Advance to the next PREVC phase |\n| `workflow-manage` | Handoffs, collaboration, docs, approvals, gate changes |\n| `sync` | Export or import docs, rules, agents, and skills |\n| `plan` | Link plans, update execution state, record decisions, commit phase artifacts |\n| `agent` | Discover and orchestrate agents |\n| `skill` | List, scaffold, export, and fill skills |\n\nFor AI-agent use, provide `repoPath` on the first context-heavy MCP call so dotcontext can cache the working repository.\n\n## Skills\n\nThe current standalone skill commands are intentionally narrow:\n\n```bash\nnpx -y @dotcontext/cli@latest admin skill list\nnpx -y @dotcontext/cli@latest admin skill export\n```\n\nUse the MCP `skill` tool when you want skill scaffolding or AI-assisted fill behavior. The CLI remains focused on discovery and export.\n\n## Troubleshooting\n\n### \"CLI init/plan/fill command not found\"\n\nThat is expected in the current product. Use an MCP-connected AI tool for context creation, plan scaffolding, and AI-generated fill operations.\n\n### \".context/ does not exist yet\"\n\nInstall MCP and ask your AI tool to initialize context first. If you already have tool-specific files elsewhere, use `reverse-sync` to import them.\n\n### \"Workflow not initialized\"\n\nInitialize the workflow after `.context/` exists:\n\n```bash\nnpx -y @dotcontext/cli@latest admin workflow init \"feature-name\"\n```\n\n### \"I only need exports/imports\"\n\nYou do not need the full PREVC workflow for that. Use the sync and import commands directly.\n\n## Environment Notes\n\n- Node.js `>=20` is required.\n- The CLI supports English and `pt-BR`.\n- The CLI package is `@dotcontext/cli`, the MCP package is `@dotcontext/mcp`, the CLI command is `dotcontext`, and the MCP server name is `dotcontext`.\n\n## Quick Reference\n\n```bash\nnpx -y @dotcontext/cli@latest\nnpx @dotcontext/mcp install\nnpx -y @dotcontext/cli@latest admin workflow init \"feature-name\"\nnpx -y @dotcontext/cli@latest admin workflow status\nnpx -y @dotcontext/cli@latest admin workflow advance\nnpx -y @dotcontext/cli@latest admin skill list\nnpx -y @dotcontext/cli@latest admin skill export\nnpx -y @dotcontext/cli@latest sync --preset claude\nnpx -y @dotcontext/cli@latest export-rules --preset cursor\nnpx -y @dotcontext/cli@latest reverse-sync --dry-run\nnpx -y @dotcontext/cli@latest admin report\n```\n"
  },
  {
    "path": "example-documentation.ts",
    "content": "/**\n * Example script demonstrating how to scaffold documentation and agent playbooks\n * programmatically without going through the CLI binary.\n */\n\nimport path from 'node:path';\nimport { DocumentationGenerator } from './src/generators/documentation';\nimport { AgentGenerator } from './src/generators/agents';\nimport { FileMapper } from './src/utils/fileMapper';\n\nasync function scaffoldRepo(repoRoot: string, outputDir: string = path.join(repoRoot, '.context')) {\n  const fileMapper = new FileMapper();\n  const documentationGenerator = new DocumentationGenerator();\n  const agentGenerator = new AgentGenerator();\n\n  const repoStructure = await fileMapper.mapRepository(repoRoot);\n\n  await documentationGenerator.generateDocumentation(repoStructure, outputDir, {}, true);\n  await agentGenerator.generateAgentPrompts(repoStructure, outputDir, true);\n\n  console.log(`Scaffold written to ${outputDir}`);\n}\n\nif (require.main === module) {\n  const repoRoot = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();\n  scaffoldRepo(repoRoot).catch(error => {\n    console.error('Failed to scaffold repository:', error);\n    process.exit(1);\n  });\n}\n\nexport { scaffoldRepo };\n"
  },
  {
    "path": "jest.config.js",
    "content": "/** @type {import('jest').Config} */\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  roots: ['<rootDir>/src'],\n  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],\n  transform: {\n    '^.+\\\\.ts$': 'ts-jest',\n  },\n  collectCoverageFrom: [\n    'src/**/*.ts',\n    '!src/**/*.d.ts',\n    '!src/**/index.ts',\n  ],\n  coverageDirectory: 'coverage',\n  coverageReporters: ['text', 'lcov', 'html'],\n  moduleFileExtensions: ['ts', 'js', 'json'],\n  testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n  verbose: true,\n};"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@dotcontext/cli\",\n  \"version\": \"0.9.2\",\n  \"description\": \"Harness engineering runtime for AI-assisted software delivery, with CLI and MCP surfaces\",\n  \"main\": \"dist/cli/index.js\",\n  \"exports\": {\n    \".\": \"./dist/cli/index.js\",\n    \"./package.json\": \"./package.json\",\n    \"./cli\": \"./dist/cli/index.js\",\n    \"./harness\": \"./dist/harness/index.js\",\n    \"./mcp\": \"./dist/mcp/index.js\"\n  },\n  \"bin\": {\n    \"dotcontext\": \"dist/index.js\"\n  },\n  \"files\": [\n    \"dist/**/*\",\n    \"README.md\",\n    \"LICENSE\",\n    \"prompts/**/*\"\n  ],\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"node -e \\\"require('fs').rmSync('dist', { recursive: true, force: true })\\\" && tsc\",\n    \"build:packages\": \"npm run build && node scripts/build-package-bundles.js\",\n    \"smoke:packages\": \"node scripts/smoke-package-bundles.js\",\n    \"release:packages\": \"node scripts/release-packages.js\",\n    \"release:packages:patch\": \"node scripts/release-packages.js patch\",\n    \"release:packages:minor\": \"node scripts/release-packages.js minor\",\n    \"release:packages:major\": \"node scripts/release-packages.js major\",\n    \"dev\": \"tsx src/index.ts\",\n    \"start\": \"node dist/index.js\",\n    \"test\": \"jest\",\n    \"prepublishOnly\": \"npm run build\",\n    \"version\": \"npm run build\",\n    \"release\": \"npm version patch && npm publish --access public\",\n    \"release:minor\": \"npm version minor && npm publish --access public\",\n    \"release:major\": \"npm version major && npm publish --access public\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"registry\": \"https://registry.npmjs.org/\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vinilana/dotcontext.git\"\n  },\n  \"homepage\": \"https://github.com/vinilana/dotcontext#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/vinilana/dotcontext/issues\"\n  },\n  \"keywords\": [\n    \"cli\",\n    \"documentation\",\n    \"agents\",\n    \"scaffold\"\n  ],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@inquirer/prompts\": \"^7.10.1\",\n    \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n    \"boxen\": \"^5.1.2\",\n    \"chalk\": \"^4.1.2\",\n    \"cli-progress\": \"^3.12.0\",\n    \"commander\": \"^14.0.1\",\n    \"fs-extra\": \"^11.3.2\",\n    \"glob\": \"^13.0.6\",\n    \"ignore\": \"^7.0.5\",\n    \"inquirer\": \"^12.6.3\",\n    \"ora\": \"^5.4.1\",\n    \"semver\": \"^7.6.3\",\n    \"zod\": \"^4.3.5\"\n  },\n  \"devDependencies\": {\n    \"@types/cli-progress\": \"^3.11.0\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/inquirer\": \"^9.0.8\",\n    \"@types/jest\": \"^30.0.0\",\n    \"@types/node\": \"^24.5.2\",\n    \"@types/semver\": \"^7.5.8\",\n    \"jest\": \"^30.1.3\",\n    \"ts-jest\": \"^29.4.4\",\n    \"tsx\": \"^4.20.6\",\n    \"typescript\": \"^5.9.2\"\n  },\n  \"optionalDependencies\": {\n    \"tree-sitter\": \"^0.21.0\",\n    \"tree-sitter-typescript\": \"^0.23.2\"\n  }\n}\n"
  },
  {
    "path": "prompts/update_plan_prompt.md",
    "content": "# Prompt: Update Collaboration Plans\n\n## Purpose\nYou are an AI assistant responsible for refining collaboration plans that live in the `.context/plans/` directory. Each plan orchestrates work across documentation guides (`docs/`) and agent playbooks (`agents/`). Your goal is to replace placeholders with actionable guidance that keeps the plan aligned with the referenced docs, agents, and repository context.\n\n## Preparation Checklist\n1. Review the plan’s YAML front matter to understand the stated `ai_update_goal`, `required_inputs`, and `success_criteria`.\n2. Inspect the provided documentation excerpts (from `docs/`) and agent playbooks to ensure the plan reflects their current guidance.\n3. Confirm that the “Agent Lineup” and “Documentation Touchpoints” tables link to real files and reference the correct `agent-update` markers.\n4. Note any TODOs, `agent-fill` placeholders, or missing evidence sections that must be resolved.\n\n## Update Procedure\n1. **Task Snapshot**\n   - Summarize the primary goal and success signal in concrete terms.\n   - List authoritative references (docs, issues, specs) that contributors should consult.\n\n2. **Agent Alignment**\n   - For each agent in the lineup, describe why they are involved and call out the first responsibility they should focus on.\n   - Ensure playbook links and responsibility summaries match the referenced agent files.\n\n3. **Documentation Touchpoints**\n   - Map each plan stage to the docs excerpts provided, highlighting which sections need to be updated during execution.\n   - Keep the table sorted and ensure the listed `agent-update` markers exist.\n\n4. **Working Stages**\n   - Break the work into clear stages with owners, deliverables, and evidence checkpoints.\n   - Reference documentation and agent resources that the team should consult while executing each stage.\n\n5. **Evidence & Follow-up**\n   - Specify the artefacts that must be captured (PR links, test runs, change logs) before the plan is considered complete.\n   - Record any follow-up actions or decisions that require human confirmation.\n\n## Acceptance Criteria\n- Every TODO or placeholder inside the plan’s `agent-update` block is resolved or accompanied by a clear escalation note.\n- Tables reference existing files and stay in sync with the docs/agent indices.\n- Stages provide actionable guidance, owners, and success signals.\n- The plan remains fully self-contained and ready for contributors to execute.\n\n## Deliverables\n- Updated plan Markdown returned verbatim.\n- No additional commentary outside the Markdown output.\n"
  },
  {
    "path": "prompts/update_scaffold_prompt.md",
    "content": "# Prompt: Update Repository Documentation and Agent Playbooks\n\n## Purpose\nYou are an AI assistant responsible for refreshing the documentation (`docs/`) and agent playbooks (`agents/`). Your goal is to bring every guide up to date with the latest repository state and maintain cross-references between docs and agent instructions.\n\n## Context Gathering\n1. Review the repository structure and recent changes.\n2. Inspect `package.json`, CI configuration, and any release or roadmap notes.\n3. Check `docs/README.md` for the current document map.\n\n## Update Procedure\n1. **Update Documentation**\n   - Replace TODO placeholders with accurate, current information.\n   - Verify that links between docs remain valid.\n   - If you add new guides or sections, update `docs/README.md`.\n\n2. **Agent Playbook Alignment**\n   - For each change in `docs/`, adjust the related `agents/*.md` playbooks.\n   - Update responsibilities, best practices, and documentation touchpoints.\n\n## Acceptance Criteria\n- No unresolved TODO placeholders remain unless they require explicit human input.\n- Agent playbooks list accurate responsibilities and best practices.\n- Changes are self-contained, well-formatted Markdown.\n\n## Deliverables\n- Updated Markdown files.\n"
  },
  {
    "path": "scripts/build-package-bundles.js",
    "content": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst repoRoot = path.resolve(__dirname, '..');\nconst distDir = path.join(repoRoot, 'dist');\nconst outputRoot = path.join(repoRoot, '.release', 'packages');\n\nfunction ensureDir(dir) {\n  fs.mkdirSync(dir, { recursive: true });\n}\n\nfunction readJson(filePath) {\n  return JSON.parse(fs.readFileSync(filePath, 'utf8'));\n}\n\nfunction writeJson(filePath, value) {\n  fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\\n', 'utf8');\n}\n\nfunction copyFile(src, dest) {\n  ensureDir(path.dirname(dest));\n  fs.copyFileSync(src, dest);\n}\n\nfunction copyDir(src, dest) {\n  ensureDir(dest);\n  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n    const srcPath = path.join(src, entry.name);\n    const destPath = path.join(dest, entry.name);\n\n    if (entry.isDirectory()) {\n      copyDir(srcPath, destPath);\n    } else if (entry.isFile()) {\n      copyFile(srcPath, destPath);\n    }\n  }\n}\n\nfunction resetDir(dir) {\n  fs.rmSync(dir, { recursive: true, force: true });\n  ensureDir(dir);\n}\n\nfunction writeExecutable(filePath, content) {\n  ensureDir(path.dirname(filePath));\n  fs.writeFileSync(filePath, content, 'utf8');\n  fs.chmodSync(filePath, 0o755);\n}\n\nfunction createLocalBinShims(pkgRoot, manifest) {\n  if (!manifest.bin) {\n    return;\n  }\n\n  const binDir = path.join(pkgRoot, 'node_modules', '.bin');\n  for (const [binName, target] of Object.entries(manifest.bin)) {\n    const normalizedTarget = target.split('/').join(path.sep);\n    const posixTarget = target.split(path.sep).join('/');\n    const shellShim = `#!/usr/bin/env sh\nset -e\nSCRIPT_DIR=$(CDPATH= cd -- \"$(dirname \"$0\")\" && pwd)\nexec node \"$SCRIPT_DIR/../../${posixTarget}\" \"$@\"\n`;\n    const cmdShim = `@ECHO OFF\\r\\nnode \"%~dp0\\\\..\\\\..\\\\${normalizedTarget}\" %*\\r\\n`;\n\n    writeExecutable(path.join(binDir, binName), shellShim);\n    writeExecutable(path.join(binDir, `${binName}.cmd`), cmdShim);\n  }\n}\n\nfunction loadTemplate(name) {\n  return fs.readFileSync(path.join(repoRoot, 'templates', 'packages', name), 'utf8');\n}\n\nfunction createManifest(rootPkg, packageName, description, main, types, options = {}) {\n  const manifest = {\n    name: packageName,\n    version: rootPkg.version,\n    description,\n    license: rootPkg.license,\n    repository: rootPkg.repository,\n    homepage: rootPkg.homepage,\n    bugs: rootPkg.bugs,\n    engines: rootPkg.engines,\n    main,\n    types,\n    files: ['dist/**/*', 'README.md', 'LICENSE'],\n  };\n\n  if (options.exports) manifest.exports = options.exports;\n  if (options.bin) manifest.bin = options.bin;\n  if (options.dependencies) manifest.dependencies = options.dependencies;\n  if (options.files) manifest.files = options.files;\n\n  return manifest;\n}\n\nfunction packageDependencies(rootPkg, names) {\n  const picked = {};\n  for (const name of names) {\n    if (rootPkg.dependencies && rootPkg.dependencies[name]) {\n      picked[name] = rootPkg.dependencies[name];\n    }\n  }\n  return picked;\n}\n\nfunction buildBundles() {\n  if (!fs.existsSync(distDir)) {\n    throw new Error('dist/ does not exist. Run \"npm run build\" first.');\n  }\n\n  const rootPkg = readJson(path.join(repoRoot, 'package.json'));\n  resetDir(outputRoot);\n\n  const commonFiles = ['LICENSE'];\n  const packages = [\n    {\n      slug: 'cli',\n      manifest: createManifest(\n        rootPkg,\n        '@dotcontext/cli',\n        'Operator-facing package for dotcontext',\n        'dist/cli/index.js',\n        'dist/cli/index.d.ts',\n        {\n          exports: { '.': './dist/cli/index.js' },\n          bin: { dotcontext: 'dist/index.js' },\n          dependencies: rootPkg.dependencies,\n          files: ['dist/**/*', 'prompts/**/*', 'README.md', 'LICENSE'],\n        }\n      ),\n      readme: loadTemplate('cli.README.md'),\n      copyPrompts: true,\n    },\n    {\n      slug: 'harness',\n      manifest: createManifest(\n        rootPkg,\n        '@dotcontext/harness',\n        'Reusable harness runtime for dotcontext',\n        'dist/harness/index.js',\n        'dist/harness/index.d.ts',\n        {\n          exports: { '.': './dist/harness/index.js' },\n          dependencies: packageDependencies(rootPkg, [\n            '@ai-sdk/anthropic',\n            '@ai-sdk/google',\n            '@ai-sdk/openai',\n            '@modelcontextprotocol/sdk',\n            'ai',\n            'fs-extra',\n            'glob',\n            'ignore',\n            'semver',\n            'zod',\n          ]),\n        }\n      ),\n      readme: loadTemplate('harness.README.md'),\n    },\n    {\n      slug: 'mcp',\n      manifest: createManifest(\n        rootPkg,\n        '@dotcontext/mcp',\n        'Model Context Protocol adapter for dotcontext',\n        'dist/mcp/index.js',\n        'dist/mcp/index.d.ts',\n        {\n          exports: { '.': './dist/mcp/index.js' },\n          bin: { 'dotcontext-mcp': 'dist/mcp/bin.js' },\n          dependencies: rootPkg.dependencies,\n        }\n      ),\n      readme: loadTemplate('mcp.README.md'),\n    },\n  ];\n\n  for (const pkg of packages) {\n    const pkgRoot = path.join(outputRoot, pkg.slug);\n    resetDir(pkgRoot);\n    copyDir(distDir, path.join(pkgRoot, 'dist'));\n    if (pkg.copyPrompts && fs.existsSync(path.join(repoRoot, 'prompts'))) {\n      copyDir(path.join(repoRoot, 'prompts'), path.join(pkgRoot, 'prompts'));\n    }\n    for (const file of commonFiles) {\n      copyFile(path.join(repoRoot, file), path.join(pkgRoot, file));\n    }\n    fs.writeFileSync(path.join(pkgRoot, 'README.md'), pkg.readme, 'utf8');\n    writeJson(path.join(pkgRoot, 'package.json'), pkg.manifest);\n    createLocalBinShims(pkgRoot, pkg.manifest);\n  }\n\n  console.log(`Prepared package bundles in ${outputRoot}`);\n}\n\nbuildBundles();\n"
  },
  {
    "path": "scripts/release-packages.js",
    "content": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\nconst semver = require('semver');\nconst { execFileSync } = require('child_process');\n\nconst repoRoot = path.resolve(__dirname, '..');\nconst releaseRoot = path.join(repoRoot, '.release');\nconst bundlesRoot = path.join(releaseRoot, 'packages');\nconst releasesRoot = path.join(releaseRoot, 'releases');\nconst rootPackagePath = path.join(repoRoot, 'package.json');\nconst rootPackage = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8'));\n\nfunction run(command, args) {\n  execFileSync(command, args, {\n    cwd: repoRoot,\n    stdio: 'inherit',\n  });\n}\n\nfunction assert(condition, message) {\n  if (!condition) {\n    throw new Error(message);\n  }\n}\n\nfunction resolveVersion(input) {\n  const current = rootPackage.version;\n  const next = input || 'patch';\n\n  if (['patch', 'minor', 'major'].includes(next)) {\n    const inc = semver.inc(current, next);\n    assert(inc, `Unable to calculate next version from ${current} using ${next}`);\n    return inc;\n  }\n\n  assert(semver.valid(next), `Invalid version: ${next}`);\n  return next;\n}\n\nfunction copyDir(src, dest) {\n  fs.mkdirSync(dest, { recursive: true });\n  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n    const srcPath = path.join(src, entry.name);\n    const destPath = path.join(dest, entry.name);\n    if (entry.isDirectory()) {\n      copyDir(srcPath, destPath);\n    } else if (entry.isFile()) {\n      fs.copyFileSync(srcPath, destPath);\n    }\n  }\n}\n\nfunction rewriteBundleVersion(bundleDir, version) {\n  const packagePath = path.join(bundleDir, 'package.json');\n  const manifest = JSON.parse(fs.readFileSync(packagePath, 'utf8'));\n  manifest.version = version;\n  fs.writeFileSync(packagePath, `${JSON.stringify(manifest, null, 2)}\\n`, 'utf8');\n}\n\nfunction buildRelease(version) {\n  assert(fs.existsSync(bundlesRoot), 'Bundle directory does not exist. Run build:packages first.');\n\n  const releaseDir = path.join(releasesRoot, version);\n  fs.rmSync(releaseDir, { recursive: true, force: true });\n  fs.mkdirSync(releaseDir, { recursive: true });\n\n  for (const slug of ['cli', 'harness', 'mcp']) {\n    const sourceDir = path.join(bundlesRoot, slug);\n    const targetDir = path.join(releaseDir, slug);\n    assert(fs.existsSync(sourceDir), `Missing bundle: ${slug}`);\n    copyDir(sourceDir, targetDir);\n    rewriteBundleVersion(targetDir, version);\n  }\n\n  const manifest = {\n    version,\n    rootVersion: rootPackage.version,\n    createdAt: new Date().toISOString(),\n    packages: ['cli', 'harness', 'mcp'].map((slug) => ({\n      slug,\n      path: `./${slug}`,\n    })),\n  };\n\n  fs.writeFileSync(\n    path.join(releaseDir, 'release-manifest.json'),\n    `${JSON.stringify(manifest, null, 2)}\\n`,\n    'utf8'\n  );\n\n  return releaseDir;\n}\n\nfunction main() {\n  const arg = process.argv[2];\n  const version = resolveVersion(arg);\n\n  run('npm', ['run', 'build:packages']);\n  run('npm', ['run', 'smoke:packages']);\n\n  const releaseDir = buildRelease(version);\n  console.log(`Prepared local release ${version} in ${releaseDir}`);\n}\n\nmain();\n"
  },
  {
    "path": "scripts/smoke-package-bundles.js",
    "content": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\nconst { execFileSync } = require('child_process');\n\nconst repoRoot = path.resolve(__dirname, '..');\nconst bundlesRoot = path.join(repoRoot, '.release', 'packages');\nconst rootPackage = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));\n\nconst bundles = [\n  {\n    slug: 'cli',\n    packageName: '@dotcontext/cli',\n    main: 'dist/cli/index.js',\n    types: 'dist/cli/index.d.ts',\n    expectedExports: [\n      'MCPInstallService',\n      'StateDetector',\n      'SyncService',\n      'ReportService',\n    ],\n    bin: 'dotcontext',\n    requiresPrompts: true,\n  },\n  {\n    slug: 'harness',\n    packageName: '@dotcontext/harness',\n    main: 'dist/harness/index.js',\n    types: 'dist/harness/index.d.ts',\n    expectedExports: [\n      'HarnessExecutionService',\n      'HarnessRuntimeStateService',\n      'HarnessSensorsService',\n      'HarnessTaskContractsService',\n      'WorkflowService',\n    ],\n  },\n  {\n    slug: 'mcp',\n    packageName: '@dotcontext/mcp',\n    main: 'dist/mcp/index.js',\n    types: 'dist/mcp/index.d.ts',\n    expectedExports: [\n      'AIContextMCPServer',\n      'startMCPServer',\n      'handleHarness',\n      'handleWorkflowManage',\n    ],\n    bin: 'dotcontext-mcp',\n  },\n];\n\nfunction assert(condition, message) {\n  if (!condition) {\n    throw new Error(message);\n  }\n}\n\nfunction requireFresh(filePath) {\n  const resolved = require.resolve(filePath);\n  delete require.cache[resolved];\n  return require(resolved);\n}\n\nfunction runBundleCommand(bundleRoot, args) {\n  return execFileSync('npm', ['exec', '--', ...args], {\n    cwd: bundleRoot,\n    encoding: 'utf8',\n    stdio: ['ignore', 'pipe', 'pipe'],\n  });\n}\n\nfunction smokeBundle(bundle) {\n  const bundleRoot = path.join(bundlesRoot, bundle.slug);\n  const manifestPath = path.join(bundleRoot, 'package.json');\n  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n  const mainPath = path.join(bundleRoot, bundle.main);\n  const typesPath = path.join(bundleRoot, bundle.types);\n\n  assert(manifest.name === bundle.packageName, `${bundle.slug}: package name mismatch`);\n  assert(manifest.version === rootPackage.version, `${bundle.slug}: version mismatch`);\n  assert(fs.existsSync(mainPath), `${bundle.slug}: main entry missing`);\n  assert(fs.existsSync(typesPath), `${bundle.slug}: types entry missing`);\n  assert(fs.existsSync(path.join(bundleRoot, 'README.md')), `${bundle.slug}: README missing`);\n  assert(fs.existsSync(path.join(bundleRoot, 'LICENSE')), `${bundle.slug}: LICENSE missing`);\n\n  if (bundle.bin) {\n    assert(manifest.bin && manifest.bin[bundle.bin], `${bundle.slug}: bin entry missing`);\n    assert(\n      fs.existsSync(path.join(bundleRoot, manifest.bin[bundle.bin])),\n      `${bundle.slug}: bin target missing`\n    );\n    assert(\n      fs.existsSync(path.join(bundleRoot, 'node_modules', '.bin', bundle.bin)),\n      `${bundle.slug}: local bin shim missing`\n    );\n  }\n\n  if (bundle.requiresPrompts) {\n    assert(fs.existsSync(path.join(bundleRoot, 'prompts')), `${bundle.slug}: prompts missing`);\n  }\n\n  const mod = requireFresh(mainPath);\n  for (const exportName of bundle.expectedExports) {\n    assert(mod[exportName], `${bundle.slug}: missing export ${exportName}`);\n  }\n\n  if (bundle.slug === 'cli') {\n    const helpOutput = runBundleCommand(bundleRoot, ['@dotcontext/cli', '--help']);\n    assert(helpOutput.includes('dotcontext'), 'cli: local npm exec help failed');\n  }\n\n  if (bundle.slug === 'mcp') {\n    const installOutput = runBundleCommand(bundleRoot, [\n      '@dotcontext/mcp',\n      'install',\n      'codex',\n      '--local',\n      '--dry-run',\n    ]);\n    assert(installOutput.includes('Would install MCP for Codex CLI'), 'mcp: local npm exec install failed');\n  }\n\n  return {\n    bundle: bundle.slug,\n    exports: bundle.expectedExports.length,\n  };\n}\n\nfunction main() {\n  if (!fs.existsSync(bundlesRoot)) {\n    throw new Error('Bundle directory does not exist. Run \"npm run build:packages\" first.');\n  }\n\n  const summary = bundles.map(smokeBundle);\n  console.log(`Smoke tests passed for ${summary.length} bundles in ${bundlesRoot}`);\n}\n\nmain();\n"
  },
  {
    "path": "scripts/test-mcp.js",
    "content": "#!/usr/bin/env node\n/**\n * Simple MCP test client for local development\n *\n * Usage: node scripts/test-mcp.js\n */\n\nconst { spawn } = require('child_process');\nconst readline = require('readline');\n\nconst server = spawn('node', ['dist/index.js', 'mcp', '-v'], {\n  stdio: ['pipe', 'pipe', 'inherit']\n});\n\nconst rl = readline.createInterface({\n  input: server.stdout,\n  crlfDelay: Infinity\n});\n\nlet messageId = 1;\n\nfunction send(method, params = {}) {\n  const message = {\n    jsonrpc: '2.0',\n    id: messageId++,\n    method,\n    params\n  };\n  console.log('\\n→ Sending:', JSON.stringify(message, null, 2));\n  server.stdin.write(JSON.stringify(message) + '\\n');\n}\n\nrl.on('line', (line) => {\n  try {\n    const response = JSON.parse(line);\n    console.log('\\n← Received:', JSON.stringify(response, null, 2));\n  } catch {\n    console.log('← Raw:', line);\n  }\n});\n\n// Test sequence\nasync function runTests() {\n  console.log('=== MCP Server Test ===\\n');\n\n  // 1. Initialize\n  send('initialize', {\n    protocolVersion: '2024-11-05',\n    capabilities: {},\n    clientInfo: { name: 'test-client', version: '1.0.0' }\n  });\n\n  await sleep(500);\n\n  // 2. Send initialized notification\n  server.stdin.write(JSON.stringify({\n    jsonrpc: '2.0',\n    method: 'notifications/initialized'\n  }) + '\\n');\n\n  await sleep(500);\n\n  // 3. List tools\n  send('tools/list', {});\n\n  await sleep(500);\n\n  // 4. Call a tool\n  send('tools/call', {\n    name: 'listFiles',\n    arguments: {\n      pattern: '*.ts',\n      cwd: process.cwd() + '/src'\n    }\n  });\n\n  await sleep(1000);\n\n  // 5. List resources\n  send('resources/list', {});\n\n  await sleep(500);\n\n  console.log('\\n=== Tests complete ===');\n  console.log('Press Ctrl+C to exit\\n');\n}\n\nfunction sleep(ms) {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nserver.on('close', (code) => {\n  console.log(`Server exited with code ${code}`);\n  process.exit(code);\n});\n\nprocess.on('SIGINT', () => {\n  server.kill();\n  process.exit(0);\n});\n\nrunTests().catch(console.error);\n"
  },
  {
    "path": "src/cli/index.test.ts",
    "content": "import {\n  MCPInstallService,\n  SyncService,\n  ImportRulesService,\n  ImportAgentsService,\n  ExportRulesService,\n  ReportService,\n  QuickSyncService,\n  ReverseQuickSyncService,\n  StateDetector,\n} from './index';\n\ndescribe('CLI boundary exports', () => {\n  it('exposes operator-facing services', () => {\n    expect(MCPInstallService).toBeDefined();\n    expect(SyncService).toBeDefined();\n    expect(ImportRulesService).toBeDefined();\n    expect(ImportAgentsService).toBeDefined();\n    expect(ExportRulesService).toBeDefined();\n    expect(ReportService).toBeDefined();\n    expect(QuickSyncService).toBeDefined();\n    expect(ReverseQuickSyncService).toBeDefined();\n    expect(StateDetector).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "src/cli/index.ts",
    "content": "/**\n * CLI boundary exports.\n *\n * This module defines the operator-facing surface that is expected to\n * become the future `dotcontext/cli` package. Keep domain/runtime logic\n * out of this boundary whenever possible.\n */\n\nexport {\n  MCPInstallService,\n  buildMcpInstallToolChoices,\n  resolveMcpInstallToolSelection,\n  type MCPInstallServiceDependencies,\n  type MCPInstallOptions,\n  type MCPInstallResult,\n  type MCPInstallation,\n  type MCPInstallToolChoice,\n  type MCPInstallToolPrompt,\n  type ResolveMcpInstallToolSelectionOptions,\n  StateDetector,\n  type ProjectState,\n  type StateDetectionResult,\n  type StateDetectorOptions,\n} from '../services/cli';\n\nexport { SyncService } from '../services/sync/syncService';\nexport { ImportRulesService, ImportAgentsService } from '../services/import';\nexport { ExportRulesService } from '../services/export';\nexport { ReportService } from '../services/report';\nexport { QuickSyncService, type QuickSyncOptions } from '../services/quickSync';\nexport { ReverseQuickSyncService, type MergeStrategy } from '../services/reverseSync';\n"
  },
  {
    "path": "src/cli.test.ts",
    "content": "import { execSync } from 'child_process';\nimport * as path from 'path';\n\ndescribe('CLI Commands', () => {\n  const cliPath = path.join(__dirname, '../dist/index.js');\n  \n  beforeAll(() => {\n    // Build the project before running tests\n    execSync('npm run build', { stdio: 'pipe' });\n  });\n\n  describe('Main CLI', () => {\n    it('should display help when --help flag is used', () => {\n      const output = execSync(`node ${cliPath} --help`, { encoding: 'utf8' });\n      expect(output).toContain('Sync .context assets, reverse-sync tool state, and install MCP integrations');\n      expect(output).toContain('Commands:');\n      expect(output).toContain('sync');\n      expect(output).toContain('reverse-sync');\n      expect(output).toContain('mcp:install');\n      expect(output).toContain('admin');\n      expect(output).not.toContain('report');\n      expect(output).not.toContain('sync-agents');\n      expect(output).not.toContain('preview-splash');\n      expect(output).not.toMatch(/\\n\\s+workflow\\b/);\n      expect(output).not.toMatch(/\\n\\s+skill\\b/);\n      expect(output).not.toMatch(/\\n\\s+init\\b/);\n      expect(output).not.toMatch(/\\n\\s+plan\\b/);\n      expect(output).not.toMatch(/\\n\\s+start\\b/);\n      expect(output).not.toMatch(/\\n\\s+fill\\b/);\n      expect(output).not.toMatch(/\\n\\s+serve\\b/);\n    });\n\n    it('should display version when --version flag is used', () => {\n      const output = execSync(`node ${cliPath} --version`, { encoding: 'utf8' });\n      expect(output).toMatch(/\\d+\\.\\d+\\.\\d+/);\n    });\n\n    it('should render the splash preview command', () => {\n      const output = execSync(\n        `FORCE_COLOR=0 node ${cliPath} admin preview-splash --title \"AI Coders CLI\" --directory ${process.cwd()}`,\n        { encoding: 'utf8' }\n      );\n\n      expect(output).toContain('AI Coders CLI');\n      expect(output).toContain('directory:');\n    });\n  });\n\n  describe('admin commands', () => {\n    it('should expose advanced commands under admin', () => {\n      const output = execSync(`node ${cliPath} admin --help`, { encoding: 'utf8' });\n      expect(output).toContain('workflow');\n      expect(output).toContain('skill');\n      expect(output).toContain('report');\n      expect(output).toContain('preview-splash');\n    });\n\n    it('should only expose supported utility subcommands', () => {\n      const output = execSync(`node ${cliPath} admin skill --help`, { encoding: 'utf8' });\n      expect(output).toContain('list');\n      expect(output).toContain('export');\n      expect(output).not.toMatch(/\\n\\s+init\\b/);\n      expect(output).not.toMatch(/\\n\\s+create\\b/);\n      expect(output).not.toMatch(/\\n\\s+fill\\b/);\n    });\n  });\n});\n"
  },
  {
    "path": "src/generators/agents/agentConfig.ts",
    "content": "import { AgentType } from './agentTypes';\n\nexport const AGENT_RESPONSIBILITIES: Record<AgentType, string[]> = {\n  'code-reviewer': [\n    'Review code changes for quality, style, and best practices',\n    'Identify potential bugs and security issues',\n    'Ensure code follows project conventions',\n    'Provide constructive feedback and suggestions'\n  ],\n  'bug-fixer': [\n    'Analyze bug reports and error messages',\n    'Identify root causes of issues',\n    'Implement targeted fixes with minimal side effects',\n    'Test fixes thoroughly before deployment'\n  ],\n  'feature-developer': [\n    'Implement new features according to specifications',\n    'Design clean, maintainable code architecture',\n    'Integrate features with existing codebase',\n    'Write comprehensive tests for new functionality'\n  ],\n  'refactoring-specialist': [\n    'Identify code smells and improvement opportunities',\n    'Refactor code while maintaining functionality',\n    'Improve code organization and structure',\n    'Optimize performance where applicable'\n  ],\n  'test-writer': [\n    'Write comprehensive unit and integration tests',\n    'Ensure good test coverage across the codebase',\n    'Create test utilities and fixtures',\n    'Maintain and update existing tests'\n  ],\n  'documentation-writer': [\n    'Create clear, comprehensive documentation',\n    'Update existing documentation as code changes',\n    'Write helpful code comments and examples',\n    'Maintain README and API documentation'\n  ],\n  'performance-optimizer': [\n    'Identify performance bottlenecks',\n    'Optimize code for speed and efficiency',\n    'Implement caching strategies',\n    'Monitor and improve resource usage'\n  ],\n  'security-auditor': [\n    'Identify security vulnerabilities',\n    'Implement security best practices',\n    'Review dependencies for security issues',\n    'Ensure data protection and privacy compliance'\n  ],\n  'backend-specialist': [\n    'Design and implement server-side architecture',\n    'Create and maintain APIs and microservices',\n    'Optimize database queries and data models',\n    'Implement authentication and authorization',\n    'Handle server deployment and scaling'\n  ],\n  'frontend-specialist': [\n    'Design and implement user interfaces',\n    'Create responsive and accessible web applications',\n    'Optimize client-side performance and bundle sizes',\n    'Implement state management and routing',\n    'Ensure cross-browser compatibility'\n  ],\n  'architect-specialist': [\n    'Design overall system architecture and patterns',\n    'Define technical standards and best practices',\n    'Evaluate and recommend technology choices',\n    'Plan system scalability and maintainability',\n    'Create architectural documentation and diagrams'\n  ],\n  'devops-specialist': [\n    'Design and maintain CI/CD pipelines',\n    'Implement infrastructure as code',\n    'Configure monitoring and alerting systems',\n    'Manage container orchestration and deployments',\n    'Optimize cloud resources and cost efficiency'\n  ],\n  'database-specialist': [\n    'Design and optimize database schemas',\n    'Create and manage database migrations',\n    'Optimize query performance and indexing',\n    'Ensure data integrity and consistency',\n    'Implement backup and recovery strategies'\n  ],\n  'mobile-specialist': [\n    'Develop native and cross-platform mobile applications',\n    'Optimize mobile app performance and battery usage',\n    'Implement mobile-specific UI/UX patterns',\n    'Handle app store deployment and updates',\n    'Integrate push notifications and offline capabilities'\n  ]\n};\n\nexport const AGENT_BEST_PRACTICES: Record<AgentType, string[]> = {\n  'code-reviewer': [\n    'Focus on maintainability and readability',\n    'Consider the broader impact of changes',\n    'Be constructive and specific in feedback'\n  ],\n  'bug-fixer': [\n    'Reproduce the bug before fixing',\n    'Write tests to prevent regression',\n    'Document the fix for future reference'\n  ],\n  'feature-developer': [\n    'Follow existing patterns and conventions',\n    'Consider edge cases and error handling',\n    'Write tests alongside implementation'\n  ],\n  'refactoring-specialist': [\n    'Make small, incremental changes',\n    'Ensure tests pass after each refactor',\n    'Preserve existing functionality exactly'\n  ],\n  'test-writer': [\n    'Write tests that are clear and maintainable',\n    'Test both happy path and edge cases',\n    'Use descriptive test names'\n  ],\n  'documentation-writer': [\n    'Keep documentation up-to-date with code',\n    'Write from the user\\'s perspective',\n    'Include practical examples'\n  ],\n  'performance-optimizer': [\n    'Measure before optimizing',\n    'Focus on actual bottlenecks',\n    'Don\\'t sacrifice readability unnecessarily'\n  ],\n  'security-auditor': [\n    'Follow security best practices',\n    'Stay updated on common vulnerabilities',\n    'Consider the principle of least privilege'\n  ],\n  'backend-specialist': [\n    'Design APIs according the specification of the project',\n    'Implement proper error handling and logging',\n    'Use appropriate design patterns and clean architecture',\n    'Consider scalability and performance from the start',\n    'Implement comprehensive testing for business logic'\n  ],\n  'frontend-specialist': [\n    'Follow modern frontend development patterns',\n    'Optimize for accessibility and user experience',\n    'Implement responsive design principles',\n    'Use component-based architecture effectively',\n    'Optimize performance and loading times'\n  ],\n  'architect-specialist': [\n    'Consider long-term maintainability and scalability',\n    'Balance technical debt with business requirements',\n    'Document architectural decisions and rationale',\n    'Promote code reusability and modularity',\n    'Stay updated on industry trends and technologies'\n  ],\n  'devops-specialist': [\n    'Automate everything that can be automated',\n    'Implement infrastructure as code for reproducibility',\n    'Monitor system health proactively',\n    'Design for failure and implement proper fallbacks',\n    'Keep security and compliance in every deployment'\n  ],\n  'database-specialist': [\n    'Always benchmark queries before and after optimization',\n    'Plan migrations with rollback strategies',\n    'Use appropriate indexing strategies for workloads',\n    'Maintain data consistency across transactions',\n    'Document schema changes and their business impact'\n  ],\n  'mobile-specialist': [\n    'Test on real devices, not just simulators',\n    'Optimize for battery life and data usage',\n    'Follow platform-specific design guidelines',\n    'Implement proper offline-first strategies',\n    'Plan for app store review requirements early'\n  ]\n};"
  },
  {
    "path": "src/generators/agents/agentGenerator.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { AgentGenerator } from './agentGenerator';\nimport { AGENT_TYPES } from './agentTypes';\nimport type { RepoStructure } from '../../types';\n\nfunction createRepoStructure(rootPath: string): RepoStructure {\n  return {\n    rootPath,\n    files: [\n      {\n        path: path.join(rootPath, 'src/index.ts'),\n        relativePath: 'src/index.ts',\n        extension: '.ts',\n        size: 128,\n        type: 'file'\n      }\n    ],\n    directories: [\n      {\n        path: path.join(rootPath, 'src'),\n        relativePath: 'src',\n        extension: '',\n        size: 0,\n        type: 'directory'\n      },\n      {\n        path: path.join(rootPath, 'docs'),\n        relativePath: 'docs',\n        extension: '',\n        size: 0,\n        type: 'directory'\n      },\n      {\n        path: path.join(rootPath, 'agents'),\n        relativePath: 'agents',\n        extension: '',\n        size: 0,\n        type: 'directory'\n      }\n    ],\n    totalFiles: 1,\n    totalSize: 128,\n    topLevelDirectoryStats: [\n      {\n        name: 'src',\n        fileCount: 1,\n        totalSize: 128\n      },\n      {\n        name: 'docs',\n        fileCount: 0,\n        totalSize: 0\n      },\n      {\n        name: 'agents',\n        fileCount: 0,\n        totalSize: 0\n      }\n    ]\n  };\n}\n\ndescribe('AgentGenerator', () => {\n  let tempDir: string;\n  let outputDir: string;\n  const generator = new AgentGenerator();\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-agents-'));\n    outputDir = path.join(tempDir, '.context');\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  it('generates selected agent playbooks and index', async () => {\n    const repoStructure = createRepoStructure(path.join(tempDir, 'repo'));\n    const selectedAgents = ['code-reviewer', 'test-writer'];\n\n    const created = await generator.generateAgentPrompts(\n      repoStructure,\n      outputDir,\n      selectedAgents\n    );\n\n    expect(created).toBe(selectedAgents.length + 1);\n\n    const agentsDir = path.join(outputDir, 'agents');\n    const files = (await fs.readdir(agentsDir)).sort();\n    expect(files).toEqual(['README.md', 'code-reviewer.md', 'test-writer.md']);\n\n    // v2.0 scaffold system: files contain only frontmatter, content is AI-generated during fill\n    const playbookContent = await fs.readFile(path.join(agentsDir, 'code-reviewer.md'), 'utf8');\n    expect(playbookContent).toContain('type: agent');\n    expect(playbookContent).toContain('agentType: code-reviewer');\n    expect(playbookContent).toContain('status: unfilled');\n    expect(playbookContent).toContain('scaffoldVersion: \"2.0.0\"');\n\n    const indexContent = await fs.readFile(path.join(agentsDir, 'README.md'), 'utf8');\n    expect(indexContent).toContain('[Code Reviewer](./code-reviewer.md)');\n    expect(indexContent).toContain('[Test Writer](./test-writer.md)');\n  });\n\n  it('falls back to all agent types when selection is invalid', async () => {\n    const repoStructure = createRepoStructure(path.join(tempDir, 'repo'));\n\n    const created = await generator.generateAgentPrompts(\n      repoStructure,\n      outputDir,\n      ['not-a-real-agent']\n    );\n\n    expect(created).toBe(AGENT_TYPES.length + 1);\n\n    const agentsDir = path.join(outputDir, 'agents');\n    const files = await fs.readdir(agentsDir);\n    AGENT_TYPES.forEach(agent => {\n      expect(files).toContain(`${agent}.md`);\n    });\n  });\n});\n"
  },
  {
    "path": "src/generators/agents/agentGenerator.ts",
    "content": "import * as path from 'path';\nimport { RepoStructure } from '../../types';\nimport { GeneratorUtils } from '../shared';\nimport { AGENT_TYPES, AgentType } from './agentTypes';\nimport { renderAgentIndex } from './templates';\nimport { DOCUMENT_GUIDES } from '../documentation/guideRegistry';\nimport { CodebaseAnalyzer, SemanticContext, ExtractedSymbol } from '../../services/semantic';\nimport { KeySymbolInfo } from './templates/types';\nimport { AGENT_RESPONSIBILITIES } from './agentConfig';\nimport {\n  createAgentFrontmatter,\n  serializeFrontmatter,\n} from '../../types/scaffoldFrontmatter';\nimport { PrevcPhase } from '../../workflow/types';\nimport { getScaffoldStructure, serializeStructureAsMarkdown } from '../shared/scaffoldStructures';\nimport { AutoFillService, AutoFillContext } from '../../services/autoFill';\nimport { StackDetector, StackInfo } from '../../services/stack';\n\ninterface AgentContext {\n  topLevelDirectories: string[];\n  semantics?: SemanticContext;\n}\n\n/** Skill info that agents can reference */\ninterface AvailableSkill {\n  slug: string;\n  name: string;\n  description: string;\n  phases: string[];\n}\n\ninterface AgentGenerationConfig {\n  selectedAgents?: string[];\n  semantic?: boolean;\n  /** Filtered list of agents based on project type classification */\n  filteredAgents?: AgentType[];\n  /** Include section headings and guidance in scaffolds (CLI mode) */\n  includeContentStubs?: boolean;\n  /** Fill scaffolds with semantic data (no LLM required) */\n  autoFill?: boolean;\n  /** Available skills that agents can reference */\n  availableSkills?: AvailableSkill[];\n}\n\n/**\n * Mapping of agent types to relevant skill slugs\n */\nconst AGENT_SKILL_MAP: Partial<Record<AgentType, string[]>> = {\n  'code-reviewer': ['code-review', 'security-audit'],\n  'bug-fixer': ['bug-investigation'],\n  'feature-developer': ['feature-breakdown', 'commit-message'],\n  'refactoring-specialist': ['refactoring'],\n  'test-writer': ['test-generation'],\n  'documentation-writer': ['documentation', 'commit-message'],\n  'security-auditor': ['security-audit'],\n};\n\n/**\n * Agent to PREVC phase mapping\n */\nconst AGENT_PHASES: Record<AgentType, PrevcPhase[]> = {\n  'code-reviewer': ['R', 'V'],\n  'bug-fixer': ['E', 'V'],\n  'feature-developer': ['P', 'E'],\n  'refactoring-specialist': ['E'],\n  'test-writer': ['E', 'V'],\n  'documentation-writer': ['P', 'C'],\n  'performance-optimizer': ['E', 'V'],\n  'security-auditor': ['R', 'V'],\n  'backend-specialist': ['P', 'E'],\n  'frontend-specialist': ['P', 'E'],\n  'architect-specialist': ['P', 'R'],\n  'devops-specialist': ['E', 'C'],\n  'database-specialist': ['P', 'E'],\n  'mobile-specialist': ['P', 'E'],\n};\n\n/**\n * Format agent type as display title\n */\nfunction formatAgentTitle(agentType: AgentType): string {\n  return agentType\n    .split('-')\n    .map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))\n    .join(' ');\n}\n\nexport class AgentGenerator {\n  private readonly docTouchpoints = [\n    {\n      title: 'Documentation Index',\n      path: '../docs/README.md'\n    },\n    ...DOCUMENT_GUIDES.map(guide => ({\n      title: guide.title,\n      path: `../docs/${guide.file}`\n    }))\n  ];\n\n  private analyzer?: CodebaseAnalyzer;\n\n  constructor(..._legacyArgs: unknown[]) {}\n\n  async generateAgentPrompts(\n    repoStructure: RepoStructure,\n    outputDir: string,\n    config: AgentGenerationConfig | string[] = {},\n    verbose: boolean = false\n  ): Promise<number> {\n    // Support legacy API: if config is an array, treat it as selectedAgents\n    const normalizedConfig: AgentGenerationConfig = Array.isArray(config)\n      ? { selectedAgents: config }\n      : config;\n\n    const agentsDir = path.join(outputDir, 'agents');\n    await GeneratorUtils.ensureDirectoryAndLog(agentsDir, verbose, 'Generating agent scaffold in');\n\n    // Perform semantic analysis if enabled\n    let semantics: SemanticContext | undefined;\n    if (normalizedConfig.semantic) {\n      GeneratorUtils.logProgress('Running semantic analysis for agents...', verbose);\n      this.analyzer = new CodebaseAnalyzer();\n      try {\n        semantics = await this.analyzer.analyze(repoStructure.rootPath);\n        GeneratorUtils.logProgress(\n          `Analyzed ${semantics.stats.totalFiles} files, found ${semantics.stats.totalSymbols} symbols`,\n          verbose\n        );\n      } catch (error) {\n        GeneratorUtils.logError('Semantic analysis failed, continuing without it', error, verbose);\n      }\n    }\n\n    // Detect stack info for autoFill\n    let stackInfo: StackInfo | undefined;\n    if (normalizedConfig.autoFill) {\n      try {\n        const stackDetector = new StackDetector();\n        stackInfo = await stackDetector.detect(repoStructure.rootPath);\n      } catch (error) {\n        GeneratorUtils.logError('Stack detection failed, continuing without it', error, verbose);\n      }\n    }\n\n    const context = this.buildContext(repoStructure, semantics);\n    const agentTypes = this.resolveAgentSelection(\n      normalizedConfig.selectedAgents,\n      normalizedConfig.filteredAgents\n    );\n\n    let created = 0;\n\n    // Generate frontmatter-only files for each agent (scaffold v2)\n    for (const agentType of agentTypes) {\n      const title = formatAgentTitle(agentType);\n      const responsibilities = AGENT_RESPONSIBILITIES[agentType] || [];\n      const description = responsibilities[0] || `${title} agent playbook`;\n      const phases = AGENT_PHASES[agentType];\n\n      const frontmatter = createAgentFrontmatter(\n        title,\n        description,\n        agentType,\n        phases\n      );\n      let content = serializeFrontmatter(frontmatter) + '\\n';\n\n      // Add content based on mode\n      const structure = getScaffoldStructure(agentType);\n      if (structure) {\n        if (normalizedConfig.autoFill && semantics) {\n          // AutoFill: generate content from semantic analysis (no LLM needed)\n          const autoFillService = new AutoFillService();\n          const autoFillContext: AutoFillContext = {\n            semantics,\n            stackInfo,\n            repoPath: repoStructure.rootPath,\n            topLevelDirectories: context.topLevelDirectories\n          };\n          content += autoFillService.fillAgent(agentType, structure, autoFillContext);\n        } else if (normalizedConfig.includeContentStubs) {\n          // Content stubs: section headings with guidance comments\n          content += serializeStructureAsMarkdown(structure);\n        }\n      }\n\n      // Append available skills section if skills are provided\n      if (normalizedConfig.availableSkills && normalizedConfig.availableSkills.length > 0) {\n        const skillsSection = this.renderSkillsSection(agentType, normalizedConfig.availableSkills);\n        if (skillsSection) {\n          content += skillsSection;\n        }\n      }\n\n      const filePath = path.join(agentsDir, `${agentType}.md`);\n      await GeneratorUtils.writeFileWithLogging(filePath, content, verbose, `Created ${agentType}.md`);\n      created += 1;\n    }\n\n    // Generate README.md index\n    const indexPath = path.join(agentsDir, 'README.md');\n    const indexContent = renderAgentIndex(agentTypes);\n    await GeneratorUtils.writeFileWithLogging(indexPath, indexContent, verbose, 'Created README.md');\n    created += 1;\n\n    return created;\n  }\n\n  /**\n   * Render an \"Available Skills\" markdown section for an agent, filtered to relevant skills.\n   */\n  private renderSkillsSection(agentType: AgentType, availableSkills: AvailableSkill[]): string | null {\n    const relevantSlugs = AGENT_SKILL_MAP[agentType];\n    if (!relevantSlugs || relevantSlugs.length === 0) {\n      return null;\n    }\n\n    const matchedSkills = availableSkills.filter(s => relevantSlugs.includes(s.slug));\n    if (matchedSkills.length === 0) {\n      return null;\n    }\n\n    const rows = matchedSkills.map(\n      s => `| [${s.slug}](./../skills/${s.slug}/SKILL.md) | ${s.description} |`\n    );\n\n    return `\\n## Available Skills\\n\\nThe following skills provide detailed procedures for specific tasks. Activate them when needed:\\n\\n| Skill | Description |\\n|-------|-------------|\\n${rows.join('\\n')}\\n`;\n  }\n\n  private getRelevantSymbolsForAgent(agentType: AgentType, semantics?: SemanticContext): KeySymbolInfo[] {\n    if (!semantics) return [];\n\n    const { symbols } = semantics;\n    let relevantSymbols: ExtractedSymbol[] = [];\n\n    // Filter symbols based on agent type\n    switch (agentType) {\n      case 'test-writer':\n        // Test writer needs test-related symbols\n        relevantSymbols = [\n          ...symbols.functions.filter(s => /test|spec|mock|stub/i.test(s.name)),\n          ...symbols.classes.filter(s => /test|spec/i.test(s.name)),\n        ];\n        break;\n\n      case 'code-reviewer':\n      case 'refactoring-specialist':\n        // These need main classes and interfaces\n        relevantSymbols = [\n          ...symbols.classes.filter(s => s.exported),\n          ...symbols.interfaces.filter(s => s.exported),\n        ];\n        break;\n\n      case 'documentation-writer':\n        // Documentation writer needs exported symbols\n        relevantSymbols = [\n          ...symbols.classes.filter(s => s.exported),\n          ...symbols.interfaces.filter(s => s.exported),\n          ...symbols.functions.filter(s => s.exported),\n          ...symbols.types.filter(s => s.exported),\n        ];\n        break;\n\n      case 'security-auditor':\n        // Security auditor needs auth-related symbols\n        relevantSymbols = [\n          ...symbols.functions.filter(s => /auth|security|crypt|token|password|secret/i.test(s.name)),\n          ...symbols.classes.filter(s => /auth|security|guard|policy/i.test(s.name)),\n        ];\n        break;\n\n      case 'performance-optimizer':\n        // Performance needs cache, async, and data processing symbols\n        relevantSymbols = [\n          ...symbols.functions.filter(s => /cache|async|batch|queue|pool/i.test(s.name)),\n          ...symbols.classes.filter(s => /cache|pool|buffer|queue/i.test(s.name)),\n        ];\n        break;\n\n      case 'database-specialist':\n        // Database specialist needs repository and model symbols\n        relevantSymbols = [\n          ...symbols.classes.filter(s => /repository|model|entity|schema|migration/i.test(s.name)),\n          ...symbols.interfaces.filter(s => /repository|model|entity/i.test(s.name)),\n        ];\n        break;\n\n      case 'backend-specialist':\n        // Backend needs services, controllers, handlers\n        relevantSymbols = [\n          ...symbols.classes.filter(s => /service|controller|handler|middleware/i.test(s.name)),\n        ];\n        break;\n\n      case 'frontend-specialist':\n        // Frontend needs components and hooks\n        relevantSymbols = [\n          ...symbols.functions.filter(s => /^use[A-Z]/i.test(s.name)), // hooks\n          ...symbols.classes.filter(s => /component|view|page|screen/i.test(s.name)),\n        ];\n        break;\n\n      default:\n        // Default: top exported symbols\n        relevantSymbols = [\n          ...symbols.classes.filter(s => s.exported).slice(0, 5),\n          ...symbols.interfaces.filter(s => s.exported).slice(0, 5),\n        ];\n    }\n\n    // Convert to KeySymbolInfo and limit\n    return relevantSymbols.slice(0, 15).map(s => ({\n      name: s.name,\n      kind: s.kind,\n      file: s.location.file,\n      line: s.location.line,\n    }));\n  }\n\n  private resolveAgentSelection(\n    selected?: string[],\n    filteredByProjectType?: AgentType[]\n  ): readonly AgentType[] {\n    // If explicitly selected agents are provided, use those\n    if (selected && selected.length > 0) {\n      const allowed = new Set<AgentType>(AGENT_TYPES);\n      const filtered = selected.filter((agent): agent is AgentType => allowed.has(agent as AgentType));\n      return (filtered.length > 0 ? filtered : AGENT_TYPES) as readonly AgentType[];\n    }\n\n    // If filtered by project type, use those\n    if (filteredByProjectType && filteredByProjectType.length > 0) {\n      return filteredByProjectType;\n    }\n\n    // Default: all agents\n    return AGENT_TYPES;\n  }\n\n  private buildContext(repoStructure: RepoStructure, semantics?: SemanticContext): AgentContext {\n    const directorySet = new Set<string>();\n\n    repoStructure.directories.forEach(dir => {\n      const [firstSegment] = dir.relativePath.split(/[\\\\/]/).filter(Boolean);\n      if (firstSegment) {\n        directorySet.add(firstSegment);\n      }\n    });\n\n    return {\n      topLevelDirectories: Array.from(directorySet).sort(),\n      semantics\n    };\n  }\n\n}\n"
  },
  {
    "path": "src/generators/agents/agentTypes.ts",
    "content": "export const AGENT_TYPES = [\n  'code-reviewer',\n  'bug-fixer',\n  'feature-developer',\n  'refactoring-specialist',\n  'test-writer',\n  'documentation-writer',\n  'performance-optimizer',\n  'security-auditor',\n  'backend-specialist',\n  'frontend-specialist',\n  'architect-specialist',\n  'devops-specialist',\n  'database-specialist',\n  'mobile-specialist'\n] as const;\n\nexport type AgentType = typeof AGENT_TYPES[number];\n\nexport const IMPORTANT_FILES = [\n  'package.json', 'tsconfig.json', 'webpack.config.js', \n  'next.config.js', 'tailwind.config.js', 'README.md',\n  '.gitignore', 'Dockerfile', 'docker-compose.yml'\n];"
  },
  {
    "path": "src/generators/agents/index.ts",
    "content": "export { AgentGenerator } from './agentGenerator';\nexport { AGENT_TYPES, AgentType, IMPORTANT_FILES } from './agentTypes';\nexport { AGENT_RESPONSIBILITIES, AGENT_BEST_PRACTICES } from './agentConfig';\n"
  },
  {
    "path": "src/generators/agents/templates/index.ts",
    "content": "export { renderAgentIndex } from './indexTemplate';\nexport type { AgentTemplateContext, DocTouchpoint } from './types';\n"
  },
  {
    "path": "src/generators/agents/templates/indexTemplate.ts",
    "content": "import { AgentType } from '../agentTypes';\nimport { AGENT_RESPONSIBILITIES } from '../agentConfig';\n\nexport function renderAgentIndex(agentTypes: readonly AgentType[]): string {\n  const agentEntries = agentTypes.map(type => {\n    const title = formatTitle(type);\n    const primaryResponsibility = AGENT_RESPONSIBILITIES[type]?.[0] || 'Document responsibilities here.';\n    return `- [${title}](./${type}.md) — ${primaryResponsibility}`;\n  }).join('\\n');\n\n  return `# Agent Handbook\n\nThis directory contains ready-to-customize playbooks for AI agents collaborating on the repository.\n\n## Available Agents\n${agentEntries}\n\n## How To Use These Playbooks\n1. Pick the agent that matches your task.\n2. Enrich the template with project-specific context or links.\n3. Share the final prompt with your AI assistant.\n4. Capture learnings in the relevant documentation file so future runs improve.\n\n## Related Resources\n- [Documentation Index](../docs/README.md)\n- [Agent Knowledge Base](../../AGENTS.md)\n- [Contributor Guidelines](../../CONTRIBUTING.md)\n`;\n}\n\nfunction formatTitle(agentType: string): string {\n  return agentType\n    .split('-')\n    .map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))\n    .join(' ');\n}\n"
  },
  {
    "path": "src/generators/agents/templates/types.ts",
    "content": "import { AgentType } from '../agentTypes';\nimport { SemanticContext } from '../../../services/semantic';\n\nexport interface DocTouchpoint {\n  title: string;\n  path: string;\n}\n\nexport interface KeySymbolInfo {\n  name: string;\n  kind: string;\n  file: string;\n  line: number;\n}\n\nexport interface AgentTemplateContext {\n  agentType: AgentType;\n  topLevelDirectories: string[];\n  docTouchpoints: DocTouchpoint[];\n  responsibilities: string[];\n  bestPractices: string[];\n  semantics?: SemanticContext;\n  relevantSymbols?: KeySymbolInfo[];\n}\n"
  },
  {
    "path": "src/generators/documentation/codebaseMapGenerator.ts",
    "content": "/**\n * Codebase Map Generator\n *\n * Generates a summary JSON file for the codebase structure, stack,\n * architecture, dependencies, and functional capabilities.\n *\n * Symbol payloads are intentionally excluded. Detailed symbol data belongs\n * to live semantic analysis, not persisted summary artifacts.\n */\n\nimport * as path from 'path';\nimport { RepoStructure } from '../../types';\nimport type { SemanticContext, DetectedFunctionalPatterns } from '../../services/semantic/types';\nimport type { StackInfo } from '../../services/stack/stackDetector';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KeyFile {\n  path: string;\n  description: string;\n  category: 'entrypoint' | 'config' | 'types' | 'service' | 'generator' | 'util';\n}\n\nexport interface NavigationHints {\n  tests: string;\n  config: string[];\n  types: string[];\n  mainLogic: string[];\n}\n\nexport interface SemanticSnapshotMetadata {\n  schemaVersion: string;\n  generatedAt: string;\n  repoFingerprint: string;\n  analyzer: {\n    useLSP: boolean;\n    includesSymbolPayload: false;\n  };\n}\n\nexport interface CodebaseMap {\n  version: string;\n  generated: string;\n  meta?: SemanticSnapshotMetadata;\n\n  stack: {\n    primaryLanguage: string | null;\n    languages: string[];\n    frameworks: string[];\n    buildTools: string[];\n    testFrameworks: string[];\n    packageManager: string | null;\n    isMonorepo: boolean;\n    hasDocker: boolean;\n    hasCI: boolean;\n    nodeVersion?: string;\n    runtimeEnvironment?: 'node' | 'browser' | 'both';\n    hasBinField?: boolean;\n    hasMainExport?: boolean;\n    hasTypesField?: boolean;\n    cliLibraries?: string[];\n  };\n\n  structure: {\n    totalFiles: number;\n    rootPath?: string;\n    topDirectories: Array<{ name: string; fileCount: number; description?: string }>;\n    languageDistribution: Array<{ extension: string; count: number }>;\n  };\n\n  architecture: {\n    layers: Array<{\n      name: string;\n      description: string;\n      directories: string[];\n      symbolCount: number;\n      dependsOn: string[];\n    }>;\n    patterns: Array<{\n      name: string;\n      confidence: number;\n      description: string;\n      occurrences: number;\n    }>;\n    entryPoints: string[];\n    mainEntryPoints?: string[];\n    moduleExports?: string[];\n  };\n\n  functionalPatterns: DetectedFunctionalPatterns;\n\n  dependencies: {\n    mostImported: Array<{ file: string; importedBy: number; description?: string }>;\n  };\n\n  stats: {\n    totalFiles: number;\n    totalSymbols: number;\n    exportedSymbols: number;\n    analysisTimeMs: number;\n  };\n\n  keyFiles?: KeyFile[];\n  navigation?: NavigationHints;\n}\n\nexport interface CodebaseMapOptions {\n  maxDependencies?: number;\n  maxKeyFiles?: number;\n}\n\nexport function createEmptyFunctionalPatterns(): DetectedFunctionalPatterns {\n  return {\n    hasAuthPattern: false,\n    hasDatabasePattern: false,\n    hasApiPattern: false,\n    hasCachePattern: false,\n    hasQueuePattern: false,\n    hasWebSocketPattern: false,\n    hasLoggingPattern: false,\n    hasValidationPattern: false,\n    hasErrorHandlingPattern: false,\n    hasTestingPattern: false,\n    patterns: [],\n  };\n}\n\n// ============================================================================\n// Generator\n// ============================================================================\n\nconst DIRECTORY_DESCRIPTIONS: Record<string, string> = {\n  src: 'Source code root',\n  services: 'Business logic and orchestration services',\n  generators: 'Content and scaffold generation',\n  utils: 'Shared utilities and helpers',\n  types: 'TypeScript type definitions',\n  tests: 'Test files and fixtures',\n  __tests__: 'Unit and integration tests',\n  components: 'UI components',\n  hooks: 'React hooks',\n  api: 'API endpoints and handlers',\n  config: 'Configuration files',\n  models: 'Data models and entities',\n  controllers: 'Request handlers',\n  middleware: 'Request/response middleware',\n  routes: 'Route definitions',\n  views: 'View templates',\n  assets: 'Static assets (images, fonts)',\n  styles: 'CSS/SCSS stylesheets',\n  lib: 'Shared library code',\n  core: 'Core application logic',\n  shared: 'Shared code across modules',\n  workflow: 'Workflow and orchestration',\n  mcp: 'MCP server implementation',\n  ai: 'AI/LLM integration',\n  semantic: 'Semantic code analysis',\n  plans: 'Development plans',\n  agents: 'AI agent definitions',\n  skills: 'Skill definitions',\n  docs: 'Documentation files',\n  scripts: 'Build and utility scripts',\n  prompts: 'Prompt templates',\n  public: 'Public static assets',\n  dist: 'Build output directory',\n  build: 'Build output directory',\n  bin: 'CLI binaries and executables',\n  test: 'Test files',\n  spec: 'Test specifications',\n  fixtures: 'Test fixtures and mock data',\n  mocks: 'Mock implementations',\n  pages: 'Page components (Next.js/Nuxt)',\n  app: 'Application entry (Next.js app router)',\n  store: 'State management',\n  reducers: 'Redux reducers',\n  actions: 'Redux actions',\n  selectors: 'Redux selectors',\n  contexts: 'React contexts',\n  providers: 'Context providers',\n  layouts: 'Layout components',\n  templates: 'Template files',\n  i18n: 'Internationalization',\n  locales: 'Locale files',\n  translations: 'Translation files',\n};\n\nconst KEY_FILE_PATTERNS: Array<{ pattern: RegExp; description: string; category: KeyFile['category'] }> = [\n  { pattern: /^src\\/index\\.(ts|js)$/, description: 'Main library entry point', category: 'entrypoint' },\n  { pattern: /^src\\/main\\.(ts|js)$/, description: 'Application entry point', category: 'entrypoint' },\n  { pattern: /^src\\/cli\\.(ts|js)$/, description: 'CLI entry point', category: 'entrypoint' },\n  { pattern: /^src\\/server\\.(ts|js)$/, description: 'Server entry point', category: 'entrypoint' },\n  { pattern: /^src\\/app\\.(ts|js)$/, description: 'Application entry point', category: 'entrypoint' },\n  { pattern: /^bin\\//, description: 'CLI executable', category: 'entrypoint' },\n  { pattern: /^index\\.(ts|js)$/, description: 'Package entry point', category: 'entrypoint' },\n  { pattern: /types?\\.(ts|d\\.ts)$/, description: 'Type definitions', category: 'types' },\n  { pattern: /tsconfig.*\\.json$/, description: 'TypeScript configuration', category: 'config' },\n  { pattern: /^package\\.json$/, description: 'Package manifest', category: 'config' },\n  { pattern: /\\.config\\.(ts|js|mjs|cjs)$/, description: 'Tool configuration', category: 'config' },\n  { pattern: /^\\.env/, description: 'Environment variables', category: 'config' },\n  { pattern: /Service\\.(ts|js)$/, description: 'Service class', category: 'service' },\n  { pattern: /Generator\\.(ts|js)$/, description: 'Generator class', category: 'generator' },\n  { pattern: /utils?\\.(ts|js)$/, description: 'Utility functions', category: 'util' },\n  { pattern: /helpers?\\.(ts|js)$/, description: 'Helper functions', category: 'util' },\n];\n\nexport class CodebaseMapGenerator {\n  private readonly maxDependencies: number;\n  private readonly maxKeyFiles: number;\n\n  constructor(options: CodebaseMapOptions = {}) {\n    this.maxDependencies = options.maxDependencies ?? 20;\n    this.maxKeyFiles = options.maxKeyFiles ?? 30;\n  }\n\n  generate(\n    repoStructure: RepoStructure,\n    semantics?: SemanticContext,\n    stackInfo?: StackInfo,\n    functionalPatterns?: DetectedFunctionalPatterns,\n    metadata?: SemanticSnapshotMetadata\n  ): CodebaseMap {\n    const architecture = this.buildArchitectureSection(repoStructure.rootPath, semantics);\n\n    return {\n      version: metadata?.schemaVersion ?? '2.0.0',\n      generated: metadata?.generatedAt ?? new Date().toISOString(),\n      ...(metadata ? { meta: metadata } : {}),\n      stack: this.buildStackSection(stackInfo, repoStructure.rootPath),\n      structure: this.buildStructureSection(repoStructure),\n      architecture,\n      functionalPatterns: functionalPatterns ?? createEmptyFunctionalPatterns(),\n      dependencies: this.buildDependenciesSection(repoStructure.rootPath, semantics),\n      stats: this.buildStatsSection(repoStructure, semantics),\n      keyFiles: this.buildKeyFilesSection(repoStructure, semantics),\n      navigation: this.buildNavigationSection(repoStructure, stackInfo),\n    };\n  }\n\n  private buildStackSection(stackInfo?: StackInfo, repoRoot?: string): CodebaseMap['stack'] {\n    if (!stackInfo) {\n      return {\n        primaryLanguage: null,\n        languages: [],\n        frameworks: [],\n        buildTools: [],\n        testFrameworks: [],\n        packageManager: null,\n        isMonorepo: false,\n        hasDocker: false,\n        hasCI: false,\n      };\n    }\n\n    let nodeVersion: string | undefined;\n    let runtimeEnvironment: 'node' | 'browser' | 'both' | undefined;\n\n    if (repoRoot) {\n      try {\n        const packageJsonPath = path.join(repoRoot, 'package.json');\n        const fs = require('fs-extra');\n        if (fs.existsSync(packageJsonPath)) {\n          const packageJson = fs.readJsonSync(packageJsonPath);\n          if (packageJson.engines?.node) {\n            nodeVersion = packageJson.engines.node;\n          }\n\n          const hasBrowserField = !!packageJson.browser;\n          const hasMainField = !!packageJson.main || !!packageJson.exports;\n          const hasBinField = !!packageJson.bin;\n\n          const isBrowserFramework = stackInfo.frameworks.some((framework) =>\n            ['nextjs', 'nuxt', 'vue', 'angular', 'svelte', 'react', 'gatsby', 'astro'].includes(framework)\n          );\n          const isNodeFramework = stackInfo.frameworks.some((framework) =>\n            ['nestjs', 'express', 'fastify', 'koa', 'hapi'].includes(framework)\n          ) || hasBinField || stackInfo.frameworks.includes('cli');\n\n          if (isBrowserFramework && isNodeFramework) {\n            runtimeEnvironment = 'both';\n          } else if (isBrowserFramework || hasBrowserField) {\n            runtimeEnvironment = 'browser';\n          } else if (isNodeFramework || hasMainField) {\n            runtimeEnvironment = 'node';\n          }\n        }\n      } catch {\n        // Ignore errors reading package.json\n      }\n    }\n\n    return {\n      primaryLanguage: stackInfo.primaryLanguage,\n      languages: stackInfo.languages,\n      frameworks: stackInfo.frameworks,\n      buildTools: stackInfo.buildTools,\n      testFrameworks: stackInfo.testFrameworks,\n      packageManager: stackInfo.packageManager,\n      isMonorepo: stackInfo.isMonorepo,\n      hasDocker: stackInfo.hasDocker,\n      hasCI: stackInfo.hasCI,\n      ...(nodeVersion && { nodeVersion }),\n      ...(runtimeEnvironment && { runtimeEnvironment }),\n      ...(typeof stackInfo.hasBinField === 'boolean' ? { hasBinField: stackInfo.hasBinField } : {}),\n      ...(typeof stackInfo.hasMainExport === 'boolean' ? { hasMainExport: stackInfo.hasMainExport } : {}),\n      ...(typeof stackInfo.hasTypesField === 'boolean' ? { hasTypesField: stackInfo.hasTypesField } : {}),\n      ...(stackInfo.cliLibraries?.length ? { cliLibraries: stackInfo.cliLibraries } : {}),\n    };\n  }\n\n  private buildStructureSection(repoStructure: RepoStructure): CodebaseMap['structure'] {\n    const topDirectories = (repoStructure.topLevelDirectoryStats ?? [])\n      .filter((stat) => {\n        const hasFilesUnder = repoStructure.files.some((file) =>\n          file.relativePath.startsWith(stat.name + '/') || file.relativePath.startsWith(stat.name + '\\\\')\n        );\n        const isDirectory = repoStructure.directories.some((directory) =>\n          directory.relativePath === stat.name || path.basename(directory.relativePath) === stat.name\n        );\n        return hasFilesUnder || isDirectory || stat.fileCount > 1;\n      })\n      .map((stat) => ({\n        name: stat.name,\n        fileCount: stat.fileCount,\n        description: this.getDirectoryDescription(stat.name),\n      }));\n\n    const extensionCounts = new Map<string, number>();\n    for (const file of repoStructure.files) {\n      const ext = path.extname(file.relativePath).toLowerCase();\n      if (ext) {\n        extensionCounts.set(ext, (extensionCounts.get(ext) ?? 0) + 1);\n      }\n    }\n\n    const languageDistribution = Array.from(extensionCounts.entries())\n      .map(([extension, count]) => ({ extension, count }))\n      .sort((a, b) => b.count - a.count)\n      .slice(0, 10);\n\n    return {\n      totalFiles: repoStructure.totalFiles,\n      rootPath: '.',\n      topDirectories,\n      languageDistribution,\n    };\n  }\n\n  private getDirectoryDescription(dirName: string): string {\n    const lowerName = dirName.toLowerCase();\n    if (DIRECTORY_DESCRIPTIONS[lowerName]) {\n      return DIRECTORY_DESCRIPTIONS[lowerName];\n    }\n\n    for (const [key, desc] of Object.entries(DIRECTORY_DESCRIPTIONS)) {\n      if (lowerName.includes(key) || key.includes(lowerName)) {\n        return desc;\n      }\n    }\n\n    if (lowerName.includes('test')) return 'Test files';\n    if (lowerName.includes('spec')) return 'Test specifications';\n    if (lowerName.endsWith('s') && !['utils', 'types', 'tests'].includes(lowerName)) {\n      const singular = lowerName.slice(0, -1);\n      return `${singular.charAt(0).toUpperCase() + singular.slice(1)} definitions`;\n    }\n\n    return 'Module directory';\n  }\n\n  private buildArchitectureSection(\n    repoRoot: string,\n    semantics?: SemanticContext\n  ): CodebaseMap['architecture'] {\n    if (!semantics) {\n      return {\n        layers: [],\n        patterns: [],\n        entryPoints: [],\n        mainEntryPoints: [],\n        moduleExports: [],\n      };\n    }\n\n    const layers = semantics.architecture.layers.map((layer) => ({\n      name: layer.name,\n      description: layer.description,\n      directories: layer.directories.map((directory) => this.relativePath(repoRoot, directory)),\n      symbolCount: layer.symbols.length,\n      dependsOn: layer.dependsOn,\n    }));\n\n    const patterns = semantics.architecture.patterns.map((pattern) => ({\n      name: pattern.name,\n      confidence: pattern.confidence,\n      description: pattern.description,\n      occurrences: pattern.locations.length,\n    }));\n\n    const allEntryPoints = semantics.architecture.entryPoints.map((entryPoint) =>\n      this.relativePath(repoRoot, entryPoint)\n    );\n    const { mainEntryPoints, moduleExports } = this.categorizeEntryPoints(allEntryPoints);\n\n    return {\n      layers,\n      patterns,\n      entryPoints: allEntryPoints,\n      mainEntryPoints,\n      moduleExports,\n    };\n  }\n\n  private categorizeEntryPoints(entryPoints: string[]): {\n    mainEntryPoints: string[];\n    moduleExports: string[];\n  } {\n    const mainEntryPoints: string[] = [];\n    const moduleExports: string[] = [];\n\n    const mainPatterns = [\n      /^src\\/index\\.(ts|js)$/,\n      /^index\\.(ts|js)$/,\n      /^src\\/main\\.(ts|js)$/,\n      /^src\\/cli\\.(ts|js)$/,\n      /^src\\/server\\.(ts|js)$/,\n      /^src\\/app\\.(ts|js)$/,\n      /^bin\\//,\n      /^cli\\.(ts|js)$/,\n      /^server\\.(ts|js)$/,\n      /^app\\.(ts|js)$/,\n      /^main\\.(ts|js)$/,\n    ];\n\n    const barrelPatterns = [\n      /\\/index\\.(ts|js)$/,\n    ];\n\n    for (const entryPoint of entryPoints) {\n      const isMain = mainPatterns.some((pattern) => pattern.test(entryPoint));\n      const isBarrel = barrelPatterns.some((pattern) => pattern.test(entryPoint)) && !isMain;\n\n      if (isMain) {\n        mainEntryPoints.push(entryPoint);\n      } else if (isBarrel) {\n        moduleExports.push(entryPoint);\n      } else if (entryPoint.split('/').length <= 2) {\n        mainEntryPoints.push(entryPoint);\n      } else {\n        moduleExports.push(entryPoint);\n      }\n    }\n\n    return { mainEntryPoints, moduleExports };\n  }\n\n  private buildDependenciesSection(repoRoot: string, semantics?: SemanticContext): CodebaseMap['dependencies'] {\n    if (!semantics) {\n      return { mostImported: [] };\n    }\n\n    const importCounts: Array<{ file: string; importedBy: number; description?: string }> = [];\n\n    for (const [file, importers] of semantics.dependencies.reverseGraph.entries()) {\n      const relativePath = this.relativePath(repoRoot, file);\n      importCounts.push({\n        file: relativePath,\n        importedBy: importers.length,\n        description: this.inferFileDescription(relativePath),\n      });\n    }\n\n    const mostImported = importCounts\n      .sort((a, b) => b.importedBy - a.importedBy)\n      .slice(0, this.maxDependencies);\n\n    return { mostImported };\n  }\n\n  private inferFileDescription(filePath: string): string | undefined {\n    const basename = path.basename(filePath, path.extname(filePath));\n    const dirname = path.dirname(filePath);\n\n    if (basename === 'index') {\n      const parentDir = path.basename(dirname);\n      if (parentDir && DIRECTORY_DESCRIPTIONS[parentDir.toLowerCase()]) {\n        return `${parentDir} module exports`;\n      }\n      return 'Module exports';\n    }\n\n    if (basename === 'types' || basename === 'type') {\n      return 'Type definitions';\n    }\n\n    if (basename.endsWith('Service')) {\n      return `${basename.replace(/Service$/, '')} service`;\n    }\n\n    if (basename.endsWith('Generator')) {\n      return `${basename.replace(/Generator$/, '')} generator`;\n    }\n\n    if (basename.endsWith('Utils') || basename.endsWith('Util')) {\n      return 'Utility functions';\n    }\n\n    if (basename === 'constants' || basename === 'config') {\n      return 'Configuration and constants';\n    }\n\n    const dirName = path.basename(dirname).toLowerCase();\n    if (DIRECTORY_DESCRIPTIONS[dirName]) {\n      return DIRECTORY_DESCRIPTIONS[dirName];\n    }\n\n    return undefined;\n  }\n\n  private buildStatsSection(repoStructure: RepoStructure, semantics?: SemanticContext): CodebaseMap['stats'] {\n    if (!semantics) {\n      return {\n        totalFiles: repoStructure.totalFiles,\n        totalSymbols: 0,\n        exportedSymbols: 0,\n        analysisTimeMs: 0,\n      };\n    }\n\n    const allSymbols = [\n      ...semantics.symbols.classes,\n      ...semantics.symbols.interfaces,\n      ...semantics.symbols.functions,\n      ...semantics.symbols.types,\n      ...semantics.symbols.enums,\n    ];\n\n    const exportedCount = allSymbols.filter((symbol) => symbol.exported).length;\n\n    return {\n      totalFiles: semantics.stats.totalFiles || repoStructure.totalFiles,\n      totalSymbols: semantics.stats.totalSymbols,\n      exportedSymbols: exportedCount,\n      analysisTimeMs: semantics.stats.analysisTimeMs,\n    };\n  }\n\n  private relativePath(repoRoot: string, filePath: string): string {\n    if (path.isAbsolute(filePath)) {\n      return path.relative(repoRoot, filePath);\n    }\n    return filePath;\n  }\n\n  private buildKeyFilesSection(repoStructure: RepoStructure, semantics?: SemanticContext): KeyFile[] {\n    const keyFiles: KeyFile[] = [];\n    const seenPaths = new Set<string>();\n\n    for (const file of repoStructure.files) {\n      const relativePath = file.relativePath;\n      for (const { pattern, description, category } of KEY_FILE_PATTERNS) {\n        if (pattern.test(relativePath) && !seenPaths.has(relativePath)) {\n          seenPaths.add(relativePath);\n          keyFiles.push({\n            path: relativePath,\n            description: this.enhanceFileDescription(relativePath, description, semantics),\n            category,\n          });\n          break;\n        }\n      }\n    }\n\n    if (semantics) {\n      for (const entryPoint of semantics.architecture.entryPoints) {\n        const relativePath = this.relativePath(repoStructure.rootPath, entryPoint);\n        if (!seenPaths.has(relativePath)) {\n          seenPaths.add(relativePath);\n          keyFiles.push({\n            path: relativePath,\n            description: this.inferFileDescription(relativePath) || 'Entry point',\n            category: this.inferCategory(relativePath),\n          });\n        }\n      }\n    }\n\n    const categoryPriority: Record<string, number> = {\n      entrypoint: 0,\n      config: 1,\n      types: 2,\n      service: 3,\n      generator: 4,\n      util: 5,\n    };\n\n    return keyFiles\n      .sort((a, b) => {\n        const aPriority = categoryPriority[a.category] ?? 99;\n        const bPriority = categoryPriority[b.category] ?? 99;\n        if (aPriority !== bPriority) return aPriority - bPriority;\n        return a.path.localeCompare(b.path);\n      })\n      .slice(0, this.maxKeyFiles);\n  }\n\n  private enhanceFileDescription(\n    filePath: string,\n    defaultDescription: string,\n    semantics?: SemanticContext\n  ): string {\n    if (!semantics) return defaultDescription;\n\n    const allSymbols = [\n      ...semantics.symbols.classes,\n      ...semantics.symbols.interfaces,\n      ...semantics.symbols.functions,\n    ];\n\n    const fileSymbols = allSymbols.filter((symbol) =>\n      symbol.location.file.endsWith(filePath) || filePath.endsWith(path.basename(symbol.location.file))\n    );\n\n    if (fileSymbols.length > 0) {\n      const mainSymbol = fileSymbols.find((symbol) => symbol.exported) || fileSymbols[0];\n      if (mainSymbol.documentation) {\n        const firstLine = mainSymbol.documentation.split('\\n')[0].trim();\n        if (firstLine.length > 0 && firstLine.length <= 100) {\n          return firstLine;\n        }\n      }\n    }\n\n    return defaultDescription;\n  }\n\n  private inferCategory(filePath: string): KeyFile['category'] {\n    const lowerPath = filePath.toLowerCase();\n\n    if (lowerPath.includes('service')) return 'service';\n    if (lowerPath.includes('generator')) return 'generator';\n    if (lowerPath.includes('util') || lowerPath.includes('helper')) return 'util';\n    if (lowerPath.includes('type') || lowerPath.endsWith('.d.ts')) return 'types';\n    if (lowerPath.includes('config') || lowerPath.endsWith('.json')) return 'config';\n\n    const basename = path.basename(filePath).toLowerCase();\n    if (['index', 'main', 'cli', 'server', 'app'].some((name) => basename.startsWith(name))) {\n      return 'entrypoint';\n    }\n\n    return 'util';\n  }\n\n  private buildNavigationSection(repoStructure: RepoStructure, stackInfo?: StackInfo): NavigationHints {\n    const files = repoStructure.files.map((file) => file.relativePath);\n\n    let testPattern = 'src/**/*.test.ts';\n    if (files.some((file) => file.includes('__tests__'))) {\n      testPattern = '**/__tests__/**/*.ts';\n    } else if (files.some((file) => file.includes('.spec.'))) {\n      testPattern = '**/*.spec.ts';\n    } else if (files.some((file) => file.endsWith('.test.ts') || file.endsWith('.test.js'))) {\n      testPattern = '**/*.test.{ts,js}';\n    }\n\n    const configPatterns = [\n      'package.json',\n      'tsconfig.json',\n      'tsconfig.*.json',\n      'jest.config.js',\n      'jest.config.ts',\n      'vitest.config.ts',\n      '.eslintrc.js',\n      '.eslintrc.json',\n      'eslint.config.js',\n      '.prettierrc',\n      '.prettierrc.json',\n      'vite.config.ts',\n      'webpack.config.js',\n      'next.config.js',\n      'next.config.ts',\n    ];\n\n    const configFiles = configPatterns.filter((pattern) => {\n      if (pattern.includes('*')) {\n        return files.some((file) => {\n          const regex = new RegExp('^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*') + '$');\n          return regex.test(file);\n        });\n      }\n      return files.some((file) => file === pattern || file.endsWith('/' + pattern));\n    });\n\n    const typeFiles: string[] = [];\n    for (const file of files) {\n      if (\n        file.endsWith('.d.ts') ||\n        file.endsWith('/types.ts') ||\n        file.endsWith('/types/index.ts') ||\n        file === 'src/types.ts'\n      ) {\n        typeFiles.push(file);\n      }\n    }\n\n    const mainLogicPatterns = ['src/services', 'src/core', 'src/lib', 'lib', 'src/modules'];\n    const mainLogic = mainLogicPatterns.filter((pattern) =>\n      files.some((file) => file.startsWith(pattern + '/'))\n    );\n\n    if (stackInfo?.frameworks.includes('nestjs') && !mainLogic.includes('src/modules')) {\n      if (files.some((file) => file.startsWith('src/modules/'))) {\n        mainLogic.push('src/modules');\n      }\n    }\n\n    return {\n      tests: testPattern,\n      config: configFiles.slice(0, 10),\n      types: typeFiles.slice(0, 10),\n      mainLogic: mainLogic.length > 0 ? mainLogic : ['src'],\n    };\n  }\n}\n"
  },
  {
    "path": "src/generators/documentation/documentationGenerator.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { DocumentationGenerator } from './documentationGenerator';\nimport { DOCUMENT_GUIDES } from './guideRegistry';\nimport type { RepoStructure } from '../../types';\n\nfunction createRepoStructure(rootPath: string): RepoStructure {\n  return {\n    rootPath,\n    files: [\n      {\n        path: path.join(rootPath, 'src/index.ts'),\n        relativePath: 'src/index.ts',\n        extension: '.ts',\n        size: 128,\n        type: 'file'\n      },\n      {\n        path: path.join(rootPath, 'package.json'),\n        relativePath: 'package.json',\n        extension: '.json',\n        size: 256,\n        type: 'file'\n      }\n    ],\n    directories: [\n      {\n        path: path.join(rootPath, 'src'),\n        relativePath: 'src',\n        extension: '',\n        size: 0,\n        type: 'directory'\n      },\n      {\n        path: path.join(rootPath, 'tests'),\n        relativePath: 'tests',\n        extension: '',\n        size: 0,\n        type: 'directory'\n      }\n    ],\n    totalFiles: 2,\n    totalSize: 384,\n    topLevelDirectoryStats: [\n      {\n        name: 'src',\n        fileCount: 1,\n        totalSize: 128\n      },\n      {\n        name: 'tests',\n        fileCount: 0,\n        totalSize: 0\n      }\n    ]\n  };\n}\n\ndescribe('DocumentationGenerator', () => {\n  let tempDir: string;\n  let outputDir: string;\n  const generator = new DocumentationGenerator();\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-docs-'));\n    outputDir = path.join(tempDir, '.context');\n    await fs.ensureDir(path.join(tempDir, 'repo'));\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  it('generates all guides by default', async () => {\n    const repoStructure = createRepoStructure(path.join(tempDir, 'repo'));\n\n    const created = await generator.generateDocumentation(repoStructure, outputDir);\n\n    expect(created).toBe(DOCUMENT_GUIDES.length + 1);\n\n    const docsDir = path.join(outputDir, 'docs');\n    const files = (await fs.readdir(docsDir)).sort();\n    const expectedFiles = ['README.md', ...DOCUMENT_GUIDES.map(guide => guide.file)].sort();\n    expect(files).toEqual(expectedFiles);\n\n    const indexContent = await fs.readFile(path.join(docsDir, 'README.md'), 'utf8');\n    expect(indexContent).toContain('# Documentation Index');\n\n    // v2.0 scaffold system: files contain only frontmatter, content is AI-generated during fill\n    const overviewContent = await fs.readFile(path.join(docsDir, 'project-overview.md'), 'utf8');\n    expect(overviewContent).toContain('type: doc');\n    expect(overviewContent).toContain('name: project-overview');\n    expect(overviewContent).toContain('status: unfilled');\n    expect(overviewContent).toContain('scaffoldVersion: \"2.0.0\"');\n  });\n\n  it('respects explicit guide selection', async () => {\n    const repoStructure = createRepoStructure(path.join(tempDir, 'repo'));\n    const selected = ['project-overview', 'glossary'];\n\n    const created = await generator.generateDocumentation(\n      repoStructure,\n      outputDir,\n      { selectedDocs: selected }\n    );\n\n    expect(created).toBe(selected.length + 1);\n\n    const docsDir = path.join(outputDir, 'docs');\n    const files = (await fs.readdir(docsDir)).sort();\n    expect(files).toEqual(['README.md', 'glossary.md', 'project-overview.md']);\n  });\n\n  it('creates AGENTS.md when missing using the default template', async () => {\n    const repoStructure = createRepoStructure(path.join(tempDir, 'repo'));\n\n    await generator.generateDocumentation(repoStructure, outputDir);\n\n    const agentsPath = path.join(repoStructure.rootPath, 'AGENTS.md');\n    const content = await fs.readFile(agentsPath, 'utf8');\n\n    expect(content).toContain('# AGENTS.md');\n    expect(content).toContain('## Dev environment tips');\n    expect(content).toContain('`.context/agents/README.md`');\n  });\n\n  it('adds AI context references to AGENTS.md when present', async () => {\n    const repoPath = path.join(tempDir, 'repo');\n    const agentsPath = path.join(repoPath, 'AGENTS.md');\n    await fs.outputFile(agentsPath, '# Agent Guide\\n\\nExisting content.\\n');\n    const repoStructure = createRepoStructure(repoPath);\n\n    await generator.generateDocumentation(repoStructure, outputDir);\n\n    const updatedAgents = await fs.readFile(agentsPath, 'utf8');\n    expect(updatedAgents).toContain('## AI Context References');\n    expect(updatedAgents).toContain('`.context/docs/README.md`');\n    expect(updatedAgents).toContain('`.context/agents/README.md`');\n  });\n});\n"
  },
  {
    "path": "src/generators/documentation/documentationGenerator.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { RepoStructure } from '../../types';\nimport { GeneratorUtils } from '../shared';\nimport {\n  DocumentationTemplateContext,\n  GuideMeta,\n  renderIndex,\n} from './templates';\nimport { getGuidesByKeys, DOCUMENT_GUIDES } from './guideRegistry';\nimport { CodebaseAnalyzer, SemanticContext, SemanticSnapshotService } from '../../services/semantic';\nimport { StackDetector } from '../../services/stack';\nimport {\n  createDocFrontmatter,\n  serializeFrontmatter,\n  DocScaffoldFrontmatter,\n} from '../../types/scaffoldFrontmatter';\nimport { getScaffoldStructure, ScaffoldStructure, serializeStructureAsMarkdown } from '../shared/scaffoldStructures';\nimport { AutoFillService, AutoFillContext } from '../../services/autoFill';\n\n/**\n * Category mapping from document name to frontmatter category.\n * Using SCAFFOLD_STRUCTURES as single source of truth for descriptions/titles.\n */\nconst DOC_CATEGORY_MAP: Record<string, DocScaffoldFrontmatter['category']> = {\n  'project-overview': 'overview',\n  'architecture': 'architecture',\n  'development-workflow': 'workflow',\n  'testing-strategy': 'testing',\n  'glossary': 'glossary',\n  'data-flow': 'data-flow',\n  'security': 'security',\n  'tooling': 'tooling',\n};\n\n/**\n * Get document info from scaffold structure (single source of truth)\n */\nfunction getDocInfo(key: string): { title: string; description: string; category: DocScaffoldFrontmatter['category'] } | undefined {\n  const structure = getScaffoldStructure(key);\n  if (!structure || structure.fileType !== 'doc') {\n    return undefined;\n  }\n  return {\n    title: structure.title,\n    description: structure.description,\n    category: DOC_CATEGORY_MAP[key],\n  };\n}\n\ninterface DocumentationGenerationConfig {\n  selectedDocs?: string[];\n  semantic?: boolean;\n  /** Filtered list of docs based on project type classification */\n  filteredDocs?: string[];\n  /** Include section headings and guidance in scaffolds (CLI mode) */\n  includeContentStubs?: boolean;\n  /** Fill scaffolds with semantic data (no LLM required) */\n  autoFill?: boolean;\n}\n\nexport class DocumentationGenerator {\n  private analyzer?: CodebaseAnalyzer;\n\n  constructor(..._legacyArgs: unknown[]) {}\n\n  async generateDocumentation(\n    repoStructure: RepoStructure,\n    outputDir: string,\n    config: DocumentationGenerationConfig = {},\n    verbose: boolean = false\n  ): Promise<number> {\n    const docsDir = path.join(outputDir, 'docs');\n    await GeneratorUtils.ensureDirectoryAndLog(docsDir, verbose, 'Generating documentation scaffold in');\n    const snapshotService = config.semantic ? new SemanticSnapshotService() : null;\n\n    // Perform semantic analysis if enabled\n    let semantics: SemanticContext | undefined;\n    let snapshotFingerprint: string | undefined;\n    if (config.semantic) {\n      GeneratorUtils.logProgress('Running semantic analysis...', verbose);\n      this.analyzer = new CodebaseAnalyzer();\n      try {\n        snapshotFingerprint = await snapshotService!.captureRepoFingerprint(repoStructure.rootPath);\n        semantics = await this.analyzer.analyze(repoStructure.rootPath);\n        GeneratorUtils.logProgress(\n          `Analyzed ${semantics.stats.totalFiles} files, found ${semantics.stats.totalSymbols} symbols in ${semantics.stats.analysisTimeMs}ms`,\n          verbose\n        );\n      } catch (error) {\n        GeneratorUtils.logError('Semantic analysis failed, continuing without it', error, verbose);\n      }\n    }\n\n    // Detect stack info for codebase map and autoFill\n    let stackInfo;\n    if (semantics || config.autoFill) {\n      try {\n        const stackDetector = new StackDetector();\n        stackInfo = await stackDetector.detect(repoStructure.rootPath);\n      } catch (error) {\n        GeneratorUtils.logError('Stack detection failed, continuing without it', error, verbose);\n      }\n    }\n\n    // Persist semantic snapshot into the semantic cache\n    if (semantics) {\n      try {\n        GeneratorUtils.logProgress('Persisting semantic snapshot...', verbose);\n        const snapshot = await snapshotService!.writeSnapshot(repoStructure, {\n          outputDir,\n          semantics,\n          stackInfo,\n          repoFingerprint: snapshotFingerprint,\n        });\n\n        GeneratorUtils.logProgress(\n          `Created semantic snapshot summary at ${path.relative(outputDir, snapshot.publishedSummaryPath)}`,\n          verbose\n        );\n      } catch (error) {\n        GeneratorUtils.logError('Semantic snapshot generation failed, continuing without it', error, verbose);\n      }\n    }\n\n    // Prioritize explicitly selected docs, then filtered by project type\n    const docKeys = config.selectedDocs ?? config.filteredDocs;\n    const guidesToGenerate = getGuidesByKeys(docKeys);\n    const context = this.buildContext(repoStructure, guidesToGenerate, semantics);\n\n    let created = 0;\n\n    // Generate README.md index (still uses template rendering for summary)\n    const readmePath = path.join(docsDir, 'README.md');\n    const readmeContent = renderIndex(context);\n    await GeneratorUtils.writeFileWithLogging(readmePath, readmeContent, verbose, 'Created README.md');\n    created += 1;\n\n    // Generate frontmatter-only files for each guide (scaffold v2)\n    for (const guide of guidesToGenerate) {\n      const docInfo = getDocInfo(guide.key);\n      if (!docInfo) {\n        continue;\n      }\n\n      const filename = `${guide.key}.md`;\n      const targetPath = path.join(docsDir, filename);\n      const frontmatter = createDocFrontmatter(\n        guide.key,\n        docInfo.description,\n        docInfo.category\n      );\n      let content = serializeFrontmatter(frontmatter) + '\\n';\n\n      // Add content based on mode\n      const structure = getScaffoldStructure(guide.key);\n      if (structure) {\n        if (config.autoFill && semantics) {\n          // AutoFill: generate content from semantic analysis (no LLM needed)\n          const autoFillService = new AutoFillService();\n          const autoFillContext: AutoFillContext = {\n            semantics,\n            stackInfo,\n            repoPath: repoStructure.rootPath,\n            topLevelDirectories: context.topLevelDirectories\n          };\n          content += autoFillService.fillDocumentation(guide.key, structure, autoFillContext);\n        } else if (config.includeContentStubs) {\n          // Content stubs: section headings with guidance comments\n          content += serializeStructureAsMarkdown(structure);\n        }\n      }\n\n      await GeneratorUtils.writeFileWithLogging(targetPath, content, verbose, `Created ${filename}`);\n      created += 1;\n    }\n\n    await this.updateAgentGuideReferences(repoStructure, verbose);\n\n    return created;\n  }\n\n  private buildContext(\n    repoStructure: RepoStructure,\n    guides: GuideMeta[],\n    semantics?: SemanticContext\n  ): DocumentationTemplateContext {\n    const topLevelStats = repoStructure.topLevelDirectoryStats ?? [];\n    const topLevelDirectories = topLevelStats.length\n      ? topLevelStats.map(stat => stat.name)\n      : this.deriveTopLevelDirectories(repoStructure);\n\n    const directoryStats = topLevelStats.length\n      ? topLevelStats.map(stat => ({ name: stat.name, fileCount: stat.fileCount }))\n      : topLevelDirectories.map(name => ({\n          name,\n          fileCount: repoStructure.files.filter(file => file.relativePath.startsWith(`${name}/`)).length\n        }));\n    const primaryLanguages = GeneratorUtils.getTopFileExtensions(repoStructure, 5)\n      .filter(([ext]) => !!ext)\n      .map(([extension, count]) => ({ extension, count }));\n\n    return {\n      repoStructure,\n      topLevelDirectories,\n      primaryLanguages,\n      directoryStats,\n      guides,\n      semantics\n    };\n  }\n\n  private deriveTopLevelDirectories(repoStructure: RepoStructure): string[] {\n    const directorySet = new Set<string>();\n    repoStructure.directories.forEach(dir => {\n      const [firstSegment] = dir.relativePath.split(/[\\\\/]/).filter(Boolean);\n      if (firstSegment) {\n        directorySet.add(firstSegment);\n      }\n    });\n    return Array.from(directorySet).sort();\n  }\n\n  private async updateAgentGuideReferences(repoStructure: RepoStructure, verbose: boolean): Promise<void> {\n    const repoRoot = repoStructure.rootPath;\n    const agentGuidePath = path.join(repoRoot, 'AGENTS.md');\n\n    try {\n      const exists = await fs.pathExists(agentGuidePath);\n      if (!exists) {\n        const template = this.createDefaultAgentGuide(repoStructure);\n        await fs.writeFile(agentGuidePath, template, 'utf-8');\n        GeneratorUtils.logProgress('Created AGENTS.md using the agents.md example starter.', verbose);\n        return;\n      }\n\n      const content = await fs.readFile(agentGuidePath, 'utf-8');\n      const docsReference = '.context/docs/README.md';\n      const agentsReference = '.context/agents/README.md';\n\n      if (content.includes(docsReference) && content.includes(agentsReference)) {\n        return;\n      }\n\n      const referencesBlock = `\\n## AI Context References\\n- Documentation index: \\`${docsReference}\\`\\n- Agent playbooks: \\`${agentsReference}\\`\\n`;\n      const updatedContent = `${content.trimEnd()}${referencesBlock}\\n`;\n\n      await fs.writeFile(agentGuidePath, updatedContent, 'utf-8');\n\n      GeneratorUtils.logProgress('Linked AGENTS.md to generated docs and agent indexes.', verbose);\n    } catch (error) {\n      GeneratorUtils.logError('Failed to update AGENTS.md with documentation references', error, verbose);\n    }\n  }\n\n  private createDefaultAgentGuide(repoStructure: RepoStructure): string {\n    const directories = (repoStructure.topLevelDirectoryStats?.length\n      ? repoStructure.topLevelDirectoryStats.map(stat => stat.name)\n      : this.deriveTopLevelDirectories(repoStructure)\n    ).filter(Boolean);\n\n    const directorySection = directories.length\n      ? directories\n          .slice(0, 8)\n          .map(dir => `- \\`${dir}/\\` — explain what lives here and when agents should edit it.`)\n          .join('\\n')\n      : '- Document the major directories so agents know where to work.';\n\n    return `# AGENTS.md\n\n## Dev environment tips\n- Install dependencies with \\`npm install\\` before running scaffolds.\n- Use \\`npm run dev\\` for the interactive TypeScript session that powers local experimentation.\n- Run \\`npm run build\\` to refresh the CommonJS bundle in \\`dist/\\` before shipping changes.\n- Store generated artefacts in \\`.context/\\` so reruns stay deterministic.\n\n## Testing instructions\n- Execute \\`npm run test\\` to run the Jest suite.\n- Append \\`-- --watch\\` while iterating on a failing spec.\n- Trigger \\`npm run build && npm run test\\` before opening a PR to mimic CI.\n- Add or update tests alongside any generator or CLI changes.\n\n## PR instructions\n- Follow Conventional Commits (for example, \\`feat(scaffolding): add doc links\\`).\n- Cross-link new scaffolds in \\`docs/README.md\\` and \\`agents/README.md\\` so future agents can find them.\n- Attach sample CLI output or generated markdown when behaviour shifts.\n- Confirm the built artefacts in \\`dist/\\` match the new source changes.\n\n## Repository map\n${directorySection}\n\n## AI Context References\n- Documentation index: \\`.context/docs/README.md\\`\n- Agent playbooks: \\`.context/agents/README.md\\`\n- Contributor guide: \\`CONTRIBUTING.md\\`\n`;\n  }\n}\n"
  },
  {
    "path": "src/generators/documentation/guideRegistry.ts",
    "content": "import { GuideMeta } from './templates/types';\n\nexport const DOCUMENT_GUIDES: GuideMeta[] = [\n  {\n    key: 'project-overview',\n    title: 'Project Overview',\n    file: 'project-overview.md',\n    primaryInputs: 'Roadmap, README, stakeholder notes'\n  },\n  {\n    key: 'architecture',\n    title: 'Architecture Notes',\n    file: 'architecture.md',\n    primaryInputs: 'ADRs, service boundaries, dependency graphs'\n  },\n  {\n    key: 'development-workflow',\n    title: 'Development Workflow',\n    file: 'development-workflow.md',\n    primaryInputs: 'Branching rules, CI config, contributing guide'\n  },\n  {\n    key: 'testing-strategy',\n    title: 'Testing Strategy',\n    file: 'testing-strategy.md',\n    primaryInputs: 'Test configs, CI gates, known flaky suites'\n  },\n  {\n    key: 'glossary',\n    title: 'Glossary & Domain Concepts',\n    file: 'glossary.md',\n    primaryInputs: 'Business terminology, user personas, domain rules'\n  },\n  {\n    key: 'data-flow',\n    title: 'Data Flow & Integrations',\n    file: 'data-flow.md',\n    primaryInputs: 'System diagrams, integration specs, queue topics'\n  },\n  {\n    key: 'security',\n    title: 'Security & Compliance Notes',\n    file: 'security.md',\n    primaryInputs: 'Auth model, secrets management, compliance requirements'\n  },\n  {\n    key: 'tooling',\n    title: 'Tooling & Productivity Guide',\n    file: 'tooling.md',\n    primaryInputs: 'CLI scripts, IDE configs, automation workflows'\n  }\n];\n\nexport const DOCUMENT_GUIDE_KEYS = DOCUMENT_GUIDES.map(guide => guide.key);\n\nexport function getGuidesByKeys(keys?: string[]): GuideMeta[] {\n  if (!keys || keys.length === 0) {\n    return DOCUMENT_GUIDES;\n  }\n\n  const set = new Set(keys);\n  const filtered = DOCUMENT_GUIDES.filter(guide => set.has(guide.key));\n  return filtered.length > 0 ? filtered : DOCUMENT_GUIDES;\n}\n\nexport function getDocFilesByKeys(keys?: string[]): Set<string> | undefined {\n  if (!keys || keys.length === 0) {\n    return undefined;\n  }\n  const files = DOCUMENT_GUIDES\n    .filter(guide => keys.includes(guide.key))\n    .map(guide => guide.file);\n  return files.length ? new Set(files) : undefined;\n}\n"
  },
  {
    "path": "src/generators/documentation/index.ts",
    "content": "export { DocumentationGenerator } from './documentationGenerator';\nexport { CodebaseMapGenerator } from './codebaseMapGenerator';\nexport type { CodebaseMap, CodebaseMapOptions, KeyFile, NavigationHints, SemanticSnapshotMetadata } from './codebaseMapGenerator';\n"
  },
  {
    "path": "src/generators/documentation/templates/common.ts",
    "content": "import * as path from 'path';\nimport { DirectoryStat, DocumentationTemplateContext } from './types';\nimport type { ExtractedSymbol } from '../../../services/semantic/types';\n\n/**\n * Wrap template content with YAML front matter for status detection.\n * This allows instant detection of unfilled files by reading only the first line.\n */\nexport function wrapWithFrontMatter(\n  content: string,\n  options?: { name?: string; description?: string }\n): string {\n  const date = new Date().toISOString().split('T')[0];\n  const nameLine = options?.name ? `name: ${options.name}\\n` : '';\n  const descLine = options?.description ? `description: ${options.description}\\n` : '';\n  return `---\n${nameLine}${descLine}status: unfilled\ngenerated: ${date}\n---\n\n${content}`;\n}\n\nconst KNOWN_DESCRIPTIONS: Record<string, string> = {\n  src: 'TypeScript source files and CLI entrypoints.',\n  dist: 'Compiled JavaScript output generated by the build step.',\n  docs: 'Living documentation produced by this tool.',\n  agents: 'AI agent playbooks and prompts.',\n  tests: 'Automated tests and fixtures.',\n  packages: 'Workspace packages or modules.'\n};\n\nexport function formatDirectoryList(\n  context: DocumentationTemplateContext,\n  includePlaceholders: boolean\n): string {\n  if (context.topLevelDirectories.length === 0) {\n    return '';\n  }\n\n  return context.topLevelDirectories\n    .map(dir => {\n      const description = KNOWN_DESCRIPTIONS[dir];\n      if (description) {\n        return `- \\`${dir}/\\` — ${description}`;\n      }\n\n      if (!includePlaceholders) {\n        return `- \\`${dir}/\\``;\n      }\n\n      return `- \\`${dir}/\\` — TODO: Describe the purpose of this directory.`;\n    })\n    .join('\\n');\n}\n\nexport function buildDocumentMapTable(guides: DocumentationTemplateContext['guides']): string {\n  const rows = guides.map(meta => `| ${meta.title} | \\`${meta.file}\\` | ${meta.primaryInputs} |`);\n  return ['| Guide | File | Primary Inputs |', '| --- | --- | --- |', ...rows].join('\\n');\n}\n\nexport function formatDirectoryStats(stats: DirectoryStat[]): string {\n  if (!stats.length) {\n    return '*No directories detected.*';\n  }\n\n  return stats\n    .map(stat => `- \\`${stat.name}/\\` — approximately ${stat.fileCount} files`)\n    .join('\\n');\n}\n\nexport function formatInlineDirectoryList(directories: string[]): string {\n  if (!directories.length) {\n    return '`n/a`';\n  }\n\n  return directories.map(dir => `\\`${dir}\\``).join(', ');\n}\n\n// Code Reference Helpers\n\n/**\n * Format a symbol as a markdown link with line number\n * Output: [`SymbolName`](src/path/file.ts#L42)\n */\nexport function formatSymbolRef(\n  symbol: ExtractedSymbol,\n  repoRoot: string\n): string {\n  const relPath = path.relative(repoRoot, symbol.location.file);\n  return `[\\`${symbol.name}\\`](${relPath}#L${symbol.location.line})`;\n}\n\n/**\n * Format a file:line reference (non-link format)\n * Output: src/path/file.ts:42\n */\nexport function formatCodeLocation(\n  filePath: string,\n  line: number,\n  repoRoot: string\n): string {\n  const relPath = path.relative(repoRoot, filePath);\n  return `${relPath}:${line}`;\n}\n\n/**\n * Format a file path as a markdown link with line number\n * Output: [`file.ts`](src/path/file.ts#L42)\n */\nexport function formatFileRef(\n  filePath: string,\n  line: number,\n  repoRoot: string,\n  displayName?: string\n): string {\n  const relPath = path.relative(repoRoot, filePath);\n  const name = displayName || path.basename(filePath);\n  return `[\\`${name}\\`](${relPath}#L${line})`;\n}\n\n/**\n * Build a markdown table of symbols with links\n */\nexport function buildSymbolTable(\n  symbols: ExtractedSymbol[],\n  repoRoot: string,\n  columns: ('name' | 'kind' | 'location' | 'description')[] = ['name', 'kind', 'location']\n): string {\n  if (symbols.length === 0) {\n    return '*No symbols found.*';\n  }\n\n  const headers: Record<string, string> = {\n    name: 'Symbol',\n    kind: 'Type',\n    location: 'Location',\n    description: 'Description'\n  };\n\n  const headerRow = '| ' + columns.map(c => headers[c]).join(' | ') + ' |';\n  const separatorRow = '| ' + columns.map(() => '---').join(' | ') + ' |';\n\n  const dataRows = symbols.map(sym => {\n    const cells = columns.map(col => {\n      switch (col) {\n        case 'name':\n          return formatSymbolRef(sym, repoRoot);\n        case 'kind':\n          return sym.kind;\n        case 'location':\n          return formatCodeLocation(sym.location.file, sym.location.line, repoRoot);\n        case 'description':\n          return sym.documentation || '-';\n        default:\n          return '-';\n      }\n    });\n    return '| ' + cells.join(' | ') + ' |';\n  });\n\n  return [headerRow, separatorRow, ...dataRows].join('\\n');\n}\n\n/**\n * Build a bullet list of symbols with links\n */\nexport function buildSymbolList(\n  symbols: ExtractedSymbol[],\n  repoRoot: string,\n  includeKind: boolean = true\n): string {\n  if (symbols.length === 0) {\n    return '*No symbols found.*';\n  }\n\n  return symbols.map(sym => {\n    const ref = formatSymbolRef(sym, repoRoot);\n    const kindSuffix = includeKind ? ` (${sym.kind})` : '';\n    const docSuffix = sym.documentation ? ` — ${sym.documentation}` : '';\n    return `- ${ref}${kindSuffix}${docSuffix}`;\n  }).join('\\n');\n}\n\n/**\n * Group symbols by their containing directory\n */\nexport function groupSymbolsByDirectory(\n  symbols: ExtractedSymbol[],\n  repoRoot: string\n): Map<string, ExtractedSymbol[]> {\n  const groups = new Map<string, ExtractedSymbol[]>();\n\n  for (const sym of symbols) {\n    const relPath = path.relative(repoRoot, sym.location.file);\n    const dir = path.dirname(relPath);\n    const existing = groups.get(dir) || [];\n    existing.push(sym);\n    groups.set(dir, existing);\n  }\n\n  return groups;\n}\n"
  },
  {
    "path": "src/generators/documentation/templates/index.ts",
    "content": "export { renderIndex } from './indexTemplate';\nexport type { DocumentationTemplateContext, GuideMeta, DirectoryStat } from './types';\n"
  },
  {
    "path": "src/generators/documentation/templates/indexTemplate.ts",
    "content": "import { buildDocumentMapTable, formatDirectoryList } from './common';\nimport { DocumentationTemplateContext } from './types';\n\nexport function renderIndex(context: DocumentationTemplateContext): string {\n\n  const directoryList = formatDirectoryList(context, false);\n  const documentMap = buildDocumentMapTable(context.guides);\n  const navigationList = context.guides\n    .map(guide => `- [${guide.title}](./${guide.file})`)\n    .join('\\n') || '- *No guides selected.*';\n\n  return `# Documentation Index\n\nWelcome to the repository knowledge base. Start with the project overview, then dive into specific guides as needed.\n\n## Core Guides\n${navigationList}\n\n## Repository Snapshot\n${directoryList || '*Top-level directories will appear here once the repository contains subfolders.*'}\n\n## Document Map\n${documentMap}\n`;\n}\n"
  },
  {
    "path": "src/generators/documentation/templates/types.ts",
    "content": "import { RepoStructure } from '../../../types';\nimport { SemanticContext } from '../../../services/semantic';\n\nexport interface GuideMeta {\n  key: string;\n  title: string;\n  file: string;\n  primaryInputs: string;\n}\n\nexport interface DirectoryStat {\n  name: string;\n  fileCount: number;\n}\n\nexport interface DocumentationTemplateContext {\n  repoStructure: RepoStructure;\n  topLevelDirectories: string[];\n  primaryLanguages: Array<{ extension: string; count: number }>;\n  directoryStats: DirectoryStat[];\n  guides: GuideMeta[];\n  semantics?: SemanticContext;\n}\n"
  },
  {
    "path": "src/generators/plans/index.ts",
    "content": "export { PlanGenerator } from './planGenerator';\n"
  },
  {
    "path": "src/generators/plans/planGenerator.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { PlanGenerator } from './planGenerator';\n\nfunction createTempOutput(prefix: string): Promise<string> {\n  return fs.mkdtemp(path.join(os.tmpdir(), prefix));\n}\n\ndescribe('PlanGenerator', () => {\n  let tempDir: string;\n  let outputDir: string;\n  let generator: PlanGenerator;\n\n  beforeEach(async () => {\n    tempDir = await createTempOutput('dotcontext-plans-');\n    outputDir = path.join(tempDir, '.context');\n    generator = new PlanGenerator();\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  it('creates a plan file and updates the index', async () => {\n    const result = await generator.generatePlan({\n      planName: 'New Initiative',\n      outputDir\n    });\n\n    expect(result.slug).toBe('new-initiative');\n\n    const planPath = path.join(outputDir, 'plans', 'new-initiative.md');\n    expect(await fs.pathExists(planPath)).toBe(true);\n\n    const content = await fs.readFile(planPath, 'utf8');\n    expect(content).toContain('# New Initiative Plan');\n    expect(content).toContain('## Task Snapshot');\n    expect(content).toContain('## Working Phases');\n    expect(content).toContain('**Commit Checkpoint**');\n\n    const indexContent = await fs.readFile(path.join(outputDir, 'plans', 'README.md'), 'utf8');\n    expect(indexContent).toContain('1. [New Initiative](./new-initiative.md)');\n  });\n\n  it('respects selected agents and docs and supports force overwrite', async () => {\n    const options = {\n      planName: 'Release Readiness',\n      outputDir,\n      selectedAgentTypes: ['test-writer'],\n      selectedDocKeys: ['testing-strategy']\n    };\n\n    const firstResult = await generator.generatePlan(options);\n    const planPath = path.join(outputDir, 'plans', 'release-readiness.md');\n    const content = await fs.readFile(planPath, 'utf8');\n\n    expect(content).toContain('[Test Writer](../agents/test-writer.md)');\n    expect(content).toContain('[testing-strategy.md](../docs/testing-strategy.md)');\n\n    await expect(generator.generatePlan(options)).rejects.toThrow('Plan already exists');\n\n    const forcedResult = await generator.generatePlan({ ...options, force: true, summary: 'Dry run' });\n    expect(forcedResult.slug).toBe(firstResult.slug);\n\n    const updatedContent = await fs.readFile(planPath, 'utf8');\n    expect(updatedContent).toContain('Dry run');\n  });\n});\n"
  },
  {
    "path": "src/generators/plans/planGenerator.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { GeneratorUtils } from '../shared';\nimport { AgentType, AGENT_TYPES } from '../agents/agentTypes';\nimport { AGENT_RESPONSIBILITIES } from '../agents/agentConfig';\nimport { getGuidesByKeys } from '../documentation/guideRegistry';\nimport { renderPlanTemplate } from './templates/planTemplate';\nimport { renderPlanIndex } from './templates/indexTemplate';\nimport { PlanAgentSummary, PlanIndexEntry, CodebaseSnapshot } from './templates/types';\nimport { CodebaseAnalyzer, SemanticContext } from '../../services/semantic';\n\ninterface PlanGeneratorOptions {\n  planName: string;\n  outputDir: string;\n  title?: string;\n  summary?: string;\n  selectedAgentTypes?: string[] | null;\n  selectedDocKeys?: string[] | null;\n  force?: boolean;\n  verbose?: boolean;\n  semantic?: boolean;\n  projectPath?: string;\n}\n\ninterface PlanGenerationResult {\n  planPath: string;\n  relativePath: string;\n  slug: string;\n}\n\nexport class PlanGenerator {\n  private analyzer?: CodebaseAnalyzer;\n\n  async generatePlan(options: PlanGeneratorOptions): Promise<PlanGenerationResult> {\n    const {\n      planName,\n      outputDir,\n      title,\n      summary,\n      selectedAgentTypes,\n      selectedDocKeys,\n      force = false,\n      verbose = false,\n      semantic = false,\n      projectPath\n    } = options;\n\n    const slug = GeneratorUtils.slugify(planName);\n    if (!slug) {\n      throw new Error('Plan name must contain at least one alphanumeric character.');\n    }\n\n    const planTitle = title?.trim() || GeneratorUtils.formatTitle(slug);\n    const resolvedOutput = path.resolve(outputDir);\n    const plansDir = path.join(resolvedOutput, 'plans');\n\n    await fs.ensureDir(resolvedOutput);\n    await GeneratorUtils.ensureDirectoryAndLog(plansDir, verbose, 'Ensuring plans directory');\n\n    const planFileName = `${slug}.md`;\n    const planPath = path.join(plansDir, planFileName);\n\n    if (!force && await fs.pathExists(planPath)) {\n      throw new Error(`Plan already exists at ${planPath}. Use --force to overwrite.`);\n    }\n\n    // Perform semantic analysis if enabled\n    let semantics: SemanticContext | undefined;\n    let codebaseSnapshot: CodebaseSnapshot | undefined;\n\n    if (semantic && projectPath) {\n      GeneratorUtils.logProgress('Running semantic analysis for plan...', verbose);\n      this.analyzer = new CodebaseAnalyzer();\n      try {\n        semantics = await this.analyzer.analyze(projectPath);\n        codebaseSnapshot = this.buildCodebaseSnapshot(semantics);\n        GeneratorUtils.logProgress(\n          `Analyzed ${semantics.stats.totalFiles} files, found ${semantics.stats.totalSymbols} symbols`,\n          verbose\n        );\n      } catch (error) {\n        GeneratorUtils.logError('Semantic analysis failed, continuing without it', error, verbose);\n      }\n    }\n\n    const agentSummaries = this.resolveAgents(selectedAgentTypes);\n    const docGuides = selectedDocKeys === null\n      ? []\n      : getGuidesByKeys(selectedDocKeys || undefined);\n\n    const content = renderPlanTemplate({\n      title: planTitle,\n      slug,\n      summary,\n      agents: agentSummaries,\n      docs: docGuides,\n      semantics,\n      codebaseSnapshot\n    });\n\n    await GeneratorUtils.writeFileWithLogging(\n      planPath,\n      content,\n      verbose,\n      `Created ${planFileName}`\n    );\n\n    await this.updatePlanIndex(plansDir, verbose);\n\n    return {\n      planPath,\n      relativePath: path.relative(resolvedOutput, planPath),\n      slug\n    };\n  }\n\n  private resolveAgents(selected?: string[] | null): PlanAgentSummary[] {\n    const allowed = new Set<AgentType>(Array.from(AGENT_TYPES));\n\n    if (selected === null) {\n      return [];\n    }\n\n    const chosen: AgentType[] = selected && selected.length > 0\n      ? Array.from(new Set(selected.map(value => value.toLowerCase().trim())))\n          .filter(value => allowed.has(value as AgentType)) as AgentType[]\n      : Array.from(allowed);\n\n    return chosen.map(type => ({\n      type,\n      title: GeneratorUtils.formatTitle(type),\n      responsibility: AGENT_RESPONSIBILITIES[type]?.[0] || 'Document this agent\\'s primary responsibility.'\n    }));\n  }\n\n  private buildCodebaseSnapshot(semantics: SemanticContext): CodebaseSnapshot {\n    const { architecture, stats } = semantics;\n\n    return {\n      totalFiles: stats.totalFiles,\n      totalSymbols: stats.totalSymbols,\n      layers: architecture.layers.map(layer => layer.name),\n      patterns: architecture.patterns.map(pattern => pattern.name),\n      entryPoints: architecture.entryPoints\n    };\n  }\n\n  private async updatePlanIndex(plansDir: string, verbose: boolean): Promise<void> {\n    const files = await fs.readdir(plansDir);\n    const entries: PlanIndexEntry[] = files\n      .filter(file => file.toLowerCase().endsWith('.md') && file.toLowerCase() !== 'readme.md')\n      .map(file => file.replace(/\\.md$/i, ''))\n      .map(slug => ({ slug, title: GeneratorUtils.formatTitle(slug) }))\n      .sort((a, b) => a.title.localeCompare(b.title));\n\n    const indexContent = renderPlanIndex(entries);\n    const indexPath = path.join(plansDir, 'README.md');\n\n    await GeneratorUtils.writeFileWithLogging(\n      indexPath,\n      indexContent,\n      verbose,\n      'Updated plans index'\n    );\n  }\n}\n"
  },
  {
    "path": "src/generators/plans/templates/indexTemplate.ts",
    "content": "import { PlanIndexEntry } from './types';\n\nexport function renderPlanIndex(entries: PlanIndexEntry[]): string {\n  const planList = entries.length\n    ? entries\n        .map((entry, index) => `${index + 1}. [${entry.title}](./${entry.slug}.md)`)\n        .join('\\n')\n    : '_No plans created yet. Use \"dotcontext plan <name>\" to create the first one._';\n\n  return `# Collaboration Plans\n\nThis directory contains plans for coordinating work across documentation and playbooks.\n\n## Plan Queue\n${planList}\n\n## How To Create Or Update Plans\n- Run \"dotcontext plan <name>\" to scaffold a new plan template.\n- Run \"dotcontext plan <name> --fill\" to have an LLM refresh the plan using the latest repository context.\n\n## Related Resources\n- [Agent Handbook](../agents/README.md)\n- [Documentation Index](../docs/README.md)\n- [Agent Knowledge Base](../../AGENTS.md)\n- [Contributor Guidelines](../../CONTRIBUTING.md)\n`;\n}\n"
  },
  {
    "path": "src/generators/plans/templates/planTemplate.ts",
    "content": "/**\n * REFERENCE ONLY - This file is not used by generators anymore.\n *\n * Scaffold structures are now defined in:\n * src/generators/shared/scaffoldStructures.ts\n *\n * This file serves as historical reference for the structure/content\n * that should be generated for this plan type.\n *\n * @deprecated Since v2.0.0 scaffold system\n */\nimport { PlanTemplateContext, CodebaseSnapshot } from './types';\nimport { SemanticContext } from '../../../services/semantic';\n\n/**\n * Wrap plan content with enhanced YAML front matter including agent lineup.\n * This allows AI agents to quickly read which agents are needed for the plan.\n */\nfunction wrapWithPlanFrontMatter(\n  content: string,\n  options: {\n    agents: Array<{ type: string; role?: string }>;\n    docs: string[];\n    phases: Array<{ id: string; name: string; prevcPhase: string; agent?: string }>;\n  }\n): string {\n  const date = new Date().toISOString().split('T')[0];\n\n  // Format agents as YAML array\n  const agentsYaml = options.agents.length > 0\n    ? options.agents.map(a => `  - type: \"${a.type}\"${a.role ? `\\n    role: \"${a.role}\"` : ''}`).join('\\n')\n    : '  - type: \"documentation-writer\"';\n\n  // Format docs as YAML array\n  const docsYaml = options.docs.length > 0\n    ? options.docs.map(d => `  - \"${d}\"`).join('\\n')\n    : '  - \"README.md\"';\n\n  // Format phases as YAML array (includes agent placeholder)\n  const phasesYaml = options.phases.map(p =>\n    `  - id: \"${p.id}\"\\n    name: \"${p.name}\"\\n    prevc: \"${p.prevcPhase}\"\\n    agent: \"${p.agent || 'TODO: assign-agent'}\"`\n  ).join('\\n');\n\n  return `---\nstatus: unfilled\ngenerated: ${date}\nagents:\n${agentsYaml}\ndocs:\n${docsYaml}\nphases:\n${phasesYaml}\n---\n\n${content}`;\n}\n\nfunction renderCodebaseSnapshot(snapshot?: CodebaseSnapshot): string {\n  if (!snapshot) {\n    return `- **Codebase analysis:** *No codebase insights available.*`;\n  }\n\n  const lines = [\n    `- **Total files analyzed:** ${snapshot.totalFiles}`,\n    `- **Total symbols discovered:** ${snapshot.totalSymbols}`\n  ];\n\n  if (snapshot.layers.length > 0) {\n    lines.push(`- **Architecture layers:** ${snapshot.layers.join(', ')}`);\n  }\n\n  if (snapshot.patterns.length > 0) {\n    lines.push(`- **Detected patterns:** ${snapshot.patterns.join(', ')}`);\n  }\n\n  if (snapshot.entryPoints.length > 0) {\n    lines.push(`- **Entry points:** ${snapshot.entryPoints.slice(0, 3).join(', ')}${snapshot.entryPoints.length > 3 ? ` (+${snapshot.entryPoints.length - 3} more)` : ''}`);\n  }\n\n  return lines.join('\\n');\n}\n\nfunction renderKeyComponents(semantics?: SemanticContext): string {\n  if (!semantics) {\n    return '';\n  }\n\n  const { symbols } = semantics;\n  const keyClasses = symbols.classes.filter(s => s.exported).slice(0, 5);\n  const keyInterfaces = symbols.interfaces.filter(s => s.exported).slice(0, 5);\n\n  if (keyClasses.length === 0 && keyInterfaces.length === 0) {\n    return '';\n  }\n\n  const lines = ['### Key Components'];\n\n  if (keyClasses.length > 0) {\n    lines.push('**Core Classes:**');\n    keyClasses.forEach(cls => {\n      lines.push(`- \\`${cls.name}\\` — ${cls.location.file}:${cls.location.line}`);\n    });\n  }\n\n  if (keyInterfaces.length > 0) {\n    lines.push('', '**Key Interfaces:**');\n    keyInterfaces.forEach(iface => {\n      lines.push(`- \\`${iface.name}\\` — ${iface.location.file}:${iface.location.line}`);\n    });\n  }\n\n  return lines.join('\\n') + '\\n';\n}\n\nexport function renderPlanTemplate(context: PlanTemplateContext): string {\n  const { title, slug, summary, agents, docs, semantics, codebaseSnapshot } = context;\n\n  const relatedAgents = agents.length\n    ? agents.map(agent => `  - \"${agent.type}\"`).join('\\n')\n    : '  - \"documentation-writer\"';\n\n  const agentTableRows = agents.length\n    ? agents\n        .map(agent => `| ${agent.title} | TODO: Describe why this agent is involved. | [${agent.title}](../agents/${agent.type}.md) | ${agent.responsibility} |`)\n        .join('\\n')\n    : '| Documentation Writer | TODO: Describe why this agent is involved. | [Documentation Writer](../agents/documentation-writer.md) | Create clear, comprehensive documentation |';\n\n  const docsTableRows = docs.length\n    ? docs\n        .map(doc => `| ${doc.title} | [${doc.file}](../docs/${doc.file}) | ${doc.primaryInputs} |`)\n        .join('\\n')\n    : '| Documentation Index | [README.md](../docs/README.md) | Current docs directory listing |';\n\n  const content = `# ${title} Plan\n\n> ${summary?.trim() || 'TODO: Summarize the desired outcome and the problem this plan addresses.'}\n\n## Task Snapshot\n- **Primary goal:** TODO: Describe the outcome to achieve.\n- **Success signal:** TODO: Define how the team will know the plan worked.\n- **Key references:**\n  - [Documentation Index](../docs/README.md)\n  - [Agent Handbook](../agents/README.md)\n  - [Plans Index](./README.md)\n\n## Codebase Context\n${renderCodebaseSnapshot(codebaseSnapshot)}\n\n${renderKeyComponents(semantics)}## Agent Lineup\n| Agent | Role in this plan | Playbook | First responsibility focus |\n| --- | --- | --- | --- |\n${agentTableRows}\n\n## Documentation Touchpoints\n| Guide | File | Primary Inputs |\n| --- | --- | --- |\n${docsTableRows}\n\n## Risk Assessment\nIdentify potential blockers, dependencies, and mitigation strategies before beginning work.\n\n### Identified Risks\n| Risk | Probability | Impact | Mitigation Strategy | Owner (Agent) |\n| --- | --- | --- | --- | --- |\n| TODO: Dependency on external team | Medium | High | Early coordination meeting, clear requirements | \\`TODO: agent\\` |\n| TODO: Insufficient test coverage | Low | Medium | Allocate time for test writing in Phase 2 | \\`test-writer\\` |\n\n### Dependencies\n- **Internal:** TODO: List dependencies on other teams, services, or infrastructure\n- **External:** TODO: List dependencies on third-party services, vendors, or partners\n- **Technical:** TODO: List technical prerequisites or required upgrades\n\n### Assumptions\n- TODO: Document key assumptions being made (e.g., \"Assume current API schema remains stable\")\n- TODO: Note what happens if assumptions prove false\n\n## Resource Estimation\n\n### Time Allocation\n| Phase | Estimated Effort | Calendar Time | Team Size |\n| --- | --- | --- | --- |\n| Phase 1 - Discovery | TODO: e.g., 2 person-days | 3-5 days | 1-2 people |\n| Phase 2 - Implementation | TODO: e.g., 5 person-days | 1-2 weeks | 2-3 people |\n| Phase 3 - Validation | TODO: e.g., 2 person-days | 3-5 days | 1-2 people |\n| **Total** | **TODO: total** | **TODO: total** | **-** |\n\n### Required Skills\n- TODO: List required expertise (e.g., \"React experience\", \"Database optimization\", \"Infrastructure knowledge\")\n- TODO: Identify skill gaps and training needs\n\n### Resource Availability\n- **Available:** TODO: List team members and their availability\n- **Blocked:** TODO: Note any team members with conflicting priorities\n- **Escalation:** TODO: Name of person to contact if resources are insufficient\n\n## Working Phases\n\n### Phase 1 — Discovery & Alignment\n> **Primary Agent:** \\`TODO: assign-agent\\` - [Playbook](../agents/TODO-agent.md)\n\n**Objective:** TODO: Define the goal for this phase.\n\n**Tasks**\n\n| # | Task | Agent | Status | Deliverable |\n|---|------|-------|--------|-------------|\n| 1.1 | TODO: Outline discovery task | \\`TODO: agent\\` | pending | TODO: Expected output |\n| 1.2 | TODO: Capture open questions | \\`TODO: agent\\` | pending | TODO: Expected output |\n\n**Commit Checkpoint**\n- After completing this phase, capture the agreed context and create a commit (for example, \\`git commit -m \"chore(plan): complete phase 1 discovery\"\\`).\n\n---\n\n### Phase 2 — Implementation & Iteration\n> **Primary Agent:** \\`TODO: assign-agent\\` - [Playbook](../agents/TODO-agent.md)\n\n**Objective:** TODO: Define the goal for this phase.\n\n**Tasks**\n\n| # | Task | Agent | Status | Deliverable |\n|---|------|-------|--------|-------------|\n| 2.1 | TODO: Build task description | \\`TODO: agent\\` | pending | TODO: Expected output |\n| 2.2 | TODO: Reference docs or playbooks | \\`TODO: agent\\` | pending | TODO: Expected output |\n\n**Commit Checkpoint**\n- Summarize progress, update cross-links, and create a commit documenting the outcomes of this phase (for example, \\`git commit -m \"chore(plan): complete phase 2 implementation\"\\`).\n\n---\n\n### Phase 3 — Validation & Handoff\n> **Primary Agent:** \\`TODO: assign-agent\\` - [Playbook](../agents/TODO-agent.md)\n\n**Objective:** TODO: Define the goal for this phase.\n\n**Tasks**\n\n| # | Task | Agent | Status | Deliverable |\n|---|------|-------|--------|-------------|\n| 3.1 | TODO: Testing and verification | \\`TODO: agent\\` | pending | TODO: Expected output |\n| 3.2 | TODO: Documentation updates | \\`TODO: agent\\` | pending | TODO: Expected output |\n| 3.3 | TODO: Capture evidence for maintainers | \\`TODO: agent\\` | pending | TODO: Expected output |\n\n**Commit Checkpoint**\n- Record the validation evidence and create a commit signalling the handoff completion (for example, \\`git commit -m \"chore(plan): complete phase 3 validation\"\\`).\n\n## Rollback Plan\nDocument how to revert changes if issues arise during or after implementation.\n\n### Rollback Triggers\nWhen to initiate rollback:\n- Critical bugs affecting core functionality\n- Performance degradation beyond acceptable thresholds\n- Data integrity issues detected\n- Security vulnerabilities introduced\n- User-facing errors exceeding alert thresholds\n\n### Rollback Procedures\n#### Phase 1 Rollback\n- Action: Discard discovery branch, restore previous documentation state\n- Data Impact: None (no production changes)\n- Estimated Time: < 1 hour\n\n#### Phase 2 Rollback\n- Action: TODO: Revert commits, restore database to pre-migration snapshot\n- Data Impact: TODO: Describe any data loss or consistency concerns\n- Estimated Time: TODO: e.g., 2-4 hours\n\n#### Phase 3 Rollback\n- Action: TODO: Full deployment rollback, restore previous version\n- Data Impact: TODO: Document data synchronization requirements\n- Estimated Time: TODO: e.g., 1-2 hours\n\n### Post-Rollback Actions\n1. Document reason for rollback in incident report\n2. Notify stakeholders of rollback and impact\n3. Schedule post-mortem to analyze failure\n4. Update plan with lessons learned before retry\n\n## Evidence & Follow-up\n\n### Artifacts to Collect\n- TODO: List artifacts (logs, PR links, test runs, design notes)\n\n### Success Metrics\n- TODO: Define measurable success criteria\n\n### Follow-up Actions\n| Action | Owner (Agent) | Due |\n|--------|---------------|-----|\n| TODO: Action description | \\`TODO: agent\\` | TODO: Date/milestone |\n`;\n\n  // Build frontmatter data\n  const frontMatterAgents = agents.length > 0\n    ? agents.map(a => ({ type: a.type, role: a.responsibility }))\n    : [{ type: 'documentation-writer' }];\n\n  const frontMatterDocs = docs.length > 0\n    ? docs.map(d => d.file)\n    : ['README.md'];\n\n  const frontMatterPhases = [\n    { id: 'phase-1', name: 'Discovery & Alignment', prevcPhase: 'P', agent: 'TODO: assign-agent' },\n    { id: 'phase-2', name: 'Implementation & Iteration', prevcPhase: 'E', agent: 'TODO: assign-agent' },\n    { id: 'phase-3', name: 'Validation & Handoff', prevcPhase: 'V', agent: 'TODO: assign-agent' },\n  ];\n\n  return wrapWithPlanFrontMatter(content, {\n    agents: frontMatterAgents,\n    docs: frontMatterDocs,\n    phases: frontMatterPhases,\n  });\n}\n"
  },
  {
    "path": "src/generators/plans/templates/types.ts",
    "content": "import { AgentType } from '../../agents/agentTypes';\nimport { GuideMeta } from '../../documentation/templates/types';\nimport { SemanticContext } from '../../../services/semantic';\n\nexport interface PlanAgentSummary {\n  type: AgentType;\n  title: string;\n  responsibility: string;\n}\n\nexport interface CodebaseSnapshot {\n  totalFiles: number;\n  totalSymbols: number;\n  layers: string[];\n  patterns: string[];\n  entryPoints: string[];\n}\n\nexport interface PlanTemplateContext {\n  title: string;\n  slug: string;\n  summary?: string;\n  agents: PlanAgentSummary[];\n  docs: GuideMeta[];\n  semantics?: SemanticContext;\n  codebaseSnapshot?: CodebaseSnapshot;\n}\n\nexport interface PlanIndexEntry {\n  slug: string;\n  title: string;\n}\n"
  },
  {
    "path": "src/generators/shared/contextGenerator.ts",
    "content": "import { FileMapper } from '../../utils/fileMapper';\n\nexport class ContextGenerator {\n  constructor(protected readonly fileMapper: FileMapper) {}\n\n  protected async loadFileContent(path: string): Promise<string> {\n    return this.fileMapper.readFileContent(path);\n  }\n}\n"
  },
  {
    "path": "src/generators/shared/directoryTemplateHelpers.ts",
    "content": "export function formatDirectoryList(directories: string[], placeholderMessage?: string): string {\n  if (!directories.length) {\n    return placeholderMessage ?? '';\n  }\n\n  return directories\n    .map(dir => `- \\`${dir}/\\` — TODO: Describe the purpose of this directory.`)\n    .join('\\n');\n}\n"
  },
  {
    "path": "src/generators/shared/generatorUtils.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { RepoStructure } from '../../types';\nimport { colors, symbols, typography } from '../../utils/theme';\n\nexport class GeneratorUtils {\n  static formatBytes(bytes: number): string {\n    if (bytes === 0) return '0 Bytes';\n    const k = 1024;\n    const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n  }\n\n  static slugify(text: string): string {\n    return text\n      .toLowerCase()\n      .replace(/\\s+/g, '-')\n      .replace(/[^a-z0-9-]/g, '');\n  }\n\n  static formatModuleName(name: string): string {\n    return name\n      .split(/[-_]/)\n      .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n      .join(' ');\n  }\n\n  static formatTitle(text: string): string {\n    return text.split('-').map(word =>\n      word.charAt(0).toUpperCase() + word.slice(1)\n    ).join(' ');\n  }\n\n  static async ensureDirectoryAndLog(dir: string, verbose: boolean, description: string): Promise<void> {\n    await fs.ensureDir(dir);\n    if (verbose) {\n      console.log(`  ${colors.secondary(symbols.pointer)} ${colors.secondary(description)}: ${colors.primary(dir)}`);\n    }\n  }\n\n  static async writeFileWithLogging(\n    filePath: string,\n    content: string,\n    verbose: boolean,\n    successMessage?: string\n  ): Promise<void> {\n    const fileName = path.basename(filePath);\n\n    if (verbose) {\n      console.log(`  ${colors.secondary(symbols.pointer)} ${colors.secondary(`Creating ${fileName}...`)}`);\n    }\n\n    await fs.writeFile(filePath, content);\n\n    if (verbose) {\n      console.log(typography.success(successMessage || `Created ${fileName}`));\n    }\n  }\n\n  static logError(message: string, error: any, verbose: boolean): void {\n    if (verbose) {\n      console.log(typography.error(`${message}: ${error}`));\n    }\n  }\n\n  static logProgress(message: string, verbose: boolean): void {\n    if (verbose) {\n      console.log(typography.warning(message));\n    }\n  }\n\n  static getFileTypeDistribution(repoStructure: RepoStructure): Map<string, number> {\n    const extensions = new Map<string, number>();\n    repoStructure.files.forEach(file => {\n      const ext = file.extension || 'no-extension';\n      extensions.set(ext, (extensions.get(ext) || 0) + 1);\n    });\n    return extensions;\n  }\n\n  static getTopFileExtensions(repoStructure: RepoStructure, limit: number = 5): Array<[string, number]> {\n    const extensions = this.getFileTypeDistribution(repoStructure);\n    return Array.from(extensions.entries())\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, limit);\n  }\n\n  static createTimestamp(): string {\n    return new Date().toISOString();\n  }\n\n  static createGeneratedByFooter(additionalInfo?: string): string {\n    return `---\n*Generated by AI Coders Context*\n${additionalInfo ? `*${additionalInfo}*` : ''}\n*Generated on: ${this.createTimestamp()}*\n`;\n  }\n}\n"
  },
  {
    "path": "src/generators/shared/index.ts",
    "content": "export { GeneratorUtils } from './generatorUtils';\nexport { ContextGenerator } from './contextGenerator';\nexport { formatDirectoryList } from './directoryTemplateHelpers';\n\n// Scaffold structures (backward compatibility & new modular structure)\nexport * from './structures';\n"
  },
  {
    "path": "src/generators/shared/scaffoldStructures.ts",
    "content": "/**\n * Scaffold Structure Definitions (DEPRECATED)\n *\n * This file is maintained for backward compatibility.\n * All functionality has been moved to the ./structures/ module.\n *\n * New code should import from './structures' instead.\n *\n * Old import: import { getScaffoldStructure } from './scaffoldStructures';\n * New import: import { getScaffoldStructure } from './structures';\n */\n\n// Re-export everything from the new modular structure\nexport * from './structures';\n"
  },
  {
    "path": "src/generators/shared/structures/agents/definitions.ts",
    "content": "/**\n * Agent structure definitions with static default content\n */\n\nimport { createAgentStructure, AgentDefaultContent } from './factory';\n\n// ============================================================================\n// Default Content Definitions\n// ============================================================================\n\nconst codeReviewerContent: AgentDefaultContent = {\n  mission: `This agent reviews code changes for quality, consistency, and adherence to project standards.\n\n**When to engage:**\n- Pull request reviews\n- Pre-commit code quality checks\n- Architecture decision validation\n- Code pattern compliance verification\n\n**Review focus areas:**\n- Code correctness and logic\n- Performance implications\n- Security considerations\n- Test coverage\n- Documentation completeness`,\n\n  responsibilities: `- Review pull requests for code quality and correctness\n- Check adherence to project coding standards and conventions\n- Identify potential bugs, edge cases, and error handling gaps\n- Evaluate test coverage for changed code\n- Assess performance implications of changes\n- Flag security vulnerabilities or concerns\n- Suggest improvements for readability and maintainability\n- Verify documentation is updated for public API changes`,\n\n  bestPractices: `- Start with understanding the context and purpose of changes\n- Focus on the most impactful issues first\n- Provide actionable, specific feedback with examples\n- Distinguish between required changes and suggestions\n- Be respectful and constructive in feedback\n- Check for consistency with existing codebase patterns\n- Consider the reviewer's perspective and time constraints\n- Link to relevant documentation or examples when suggesting changes`,\n\n  collaborationChecklist: `- [ ] Read the PR description and linked issues to understand context\n- [ ] Review the overall design approach before diving into details\n- [ ] Check that tests cover the main functionality and edge cases\n- [ ] Verify documentation is updated for any API changes\n- [ ] Confirm the PR follows project coding standards\n- [ ] Leave clear, actionable feedback with suggested solutions\n- [ ] Approve or request changes based on review findings`,\n};\n\nconst bugFixerContent: AgentDefaultContent = {\n  mission: `This agent analyzes bug reports and implements targeted fixes with minimal side effects.\n\n**When to engage:**\n- Bug reports and issue investigation\n- Production incident response\n- Regression identification\n- Error log analysis\n\n**Fix approach:**\n- Root cause analysis before coding\n- Minimal, focused changes\n- Regression test creation\n- Impact assessment`,\n\n  responsibilities: `- Analyze bug reports and reproduce issues locally\n- Investigate root causes through debugging and log analysis\n- Implement focused fixes with minimal code changes\n- Write regression tests to prevent recurrence\n- Document the bug cause and fix for future reference\n- Verify fix doesn't introduce new issues\n- Update error handling if gaps are discovered\n- Coordinate with test writer for comprehensive test coverage`,\n\n  bestPractices: `- Always reproduce the bug before attempting to fix\n- Understand the root cause, not just the symptoms\n- Make the smallest change that fixes the issue\n- Add a test that would have caught this bug\n- Consider if the bug exists elsewhere in similar code\n- Check for related issues that might have the same cause\n- Document the investigation steps for future reference\n- Verify the fix in an environment similar to where the bug occurred`,\n\n  collaborationChecklist: `- [ ] Reproduce the bug consistently\n- [ ] Identify the root cause through debugging\n- [ ] Implement a minimal, targeted fix\n- [ ] Write a regression test for the bug\n- [ ] Verify the fix doesn't break existing functionality\n- [ ] Document the cause and solution\n- [ ] Update related documentation if needed`,\n};\n\nconst featureDeveloperContent: AgentDefaultContent = {\n  mission: `This agent implements new features according to specifications with clean architecture.\n\n**When to engage:**\n- New feature implementation\n- Feature enhancement requests\n- User story development\n- API endpoint additions\n\n**Implementation approach:**\n- Understand requirements thoroughly\n- Design before coding\n- Integrate with existing patterns\n- Write tests alongside code`,\n\n  responsibilities: `- Implement new features based on specifications and requirements\n- Design solutions that integrate well with existing architecture\n- Write clean, maintainable, and well-documented code\n- Create comprehensive tests for new functionality\n- Handle edge cases and error scenarios gracefully\n- Coordinate with other agents for reviews and testing\n- Update documentation for new features\n- Ensure backward compatibility when modifying existing APIs`,\n\n  bestPractices: `- Start with understanding the full requirements and acceptance criteria\n- Design the solution before writing code\n- Follow existing code patterns and conventions in the project\n- Write tests as you develop, not as an afterthought\n- Keep commits focused and well-documented\n- Communicate blockers or unclear requirements early\n- Consider performance, security, and accessibility from the start\n- Leave the codebase cleaner than you found it`,\n\n  collaborationChecklist: `- [ ] Understand requirements and acceptance criteria fully\n- [ ] Design the solution and get feedback on approach\n- [ ] Implement feature following project patterns\n- [ ] Write unit and integration tests\n- [ ] Update relevant documentation\n- [ ] Create PR with clear description and testing notes\n- [ ] Address code review feedback`,\n};\n\nconst refactoringSpecialistContent: AgentDefaultContent = {\n  mission: `This agent identifies code smells and improves code structure while preserving functionality.\n\n**When to engage:**\n- Code smell identification\n- Technical debt reduction\n- Architecture improvements\n- Pattern standardization\n\n**Refactoring approach:**\n- Incremental, safe changes\n- Test coverage first\n- Preserve behavior exactly\n- Improve readability and maintainability`,\n\n  responsibilities: `- Identify code smells and areas needing improvement\n- Plan and execute refactoring in safe, incremental steps\n- Ensure comprehensive test coverage before refactoring\n- Preserve existing functionality exactly\n- Improve code readability and maintainability\n- Reduce duplication and complexity\n- Standardize patterns across the codebase\n- Document architectural decisions and improvements`,\n\n  bestPractices: `- Never refactor without adequate test coverage\n- Make one type of change at a time (rename, extract, move)\n- Commit frequently with clear descriptions\n- Preserve behavior exactly - refactoring is not feature change\n- Use automated refactoring tools when available\n- Review changes carefully before committing\n- If tests break, the refactoring changed behavior - investigate\n- Keep refactoring PRs focused and reviewable`,\n\n  collaborationChecklist: `- [ ] Ensure adequate test coverage exists for the code\n- [ ] Identify specific improvements to make\n- [ ] Plan incremental steps for the refactoring\n- [ ] Execute changes one step at a time\n- [ ] Run tests after each step to verify behavior\n- [ ] Update documentation for any structural changes\n- [ ] Request review focusing on behavior preservation`,\n};\n\nconst testWriterContent: AgentDefaultContent = {\n  mission: `This agent writes comprehensive tests and maintains test coverage standards.\n\n**When to engage:**\n- New feature testing\n- Bug regression tests\n- Test coverage improvements\n- Test suite maintenance\n\n**Testing approach:**\n- Test pyramid (unit, integration, e2e)\n- Edge case coverage\n- Clear, maintainable tests\n- Fast, reliable execution`,\n\n  responsibilities: `- Write unit tests for individual functions and components\n- Create integration tests for feature workflows\n- Add end-to-end tests for critical user paths\n- Identify and cover edge cases and error scenarios\n- Maintain test suite performance and reliability\n- Update tests when code changes\n- Improve test coverage for undertested areas\n- Document testing patterns and best practices`,\n\n  bestPractices: `- Follow the test pyramid: many unit tests, fewer integration, minimal e2e\n- Write tests that are fast, isolated, and deterministic\n- Use descriptive test names that explain what and why\n- Test behavior, not implementation details\n- Cover happy paths, edge cases, and error scenarios\n- Keep tests maintainable and avoid test code duplication\n- Use appropriate mocking strategies\n- Ensure tests can run independently and in any order`,\n\n  collaborationChecklist: `- [ ] Understand the feature or bug being tested\n- [ ] Identify key test scenarios (happy path, edge cases, errors)\n- [ ] Write unit tests for individual components\n- [ ] Add integration tests for feature workflows\n- [ ] Verify test coverage meets project standards\n- [ ] Ensure tests are fast and reliable\n- [ ] Document any complex test setups or patterns`,\n};\n\nconst documentationWriterContent: AgentDefaultContent = {\n  mission: `This agent creates and maintains documentation to keep it in sync with code.\n\n**When to engage:**\n- New feature documentation\n- API reference updates\n- README improvements\n- Code comment reviews\n\n**Documentation approach:**\n- Clear and concise writing\n- Practical code examples\n- Up-to-date with code changes\n- Accessible to target audience`,\n\n  responsibilities: `- Write and maintain README files and getting started guides\n- Create API documentation with clear examples\n- Document architecture decisions and system design\n- Keep inline code comments accurate and helpful\n- Update documentation when code changes\n- Create tutorials and how-to guides\n- Maintain changelog and release notes\n- Review documentation for clarity and accuracy`,\n\n  bestPractices: `- Write for your target audience (developers, users, etc.)\n- Include working code examples that can be copied\n- Keep documentation close to the code it describes\n- Update docs in the same PR as code changes\n- Use consistent formatting and terminology\n- Include common use cases and troubleshooting tips\n- Make documentation searchable and well-organized\n- Review docs from a newcomer's perspective`,\n\n  collaborationChecklist: `- [ ] Identify what needs to be documented\n- [ ] Determine the target audience and their needs\n- [ ] Write clear, concise documentation\n- [ ] Include working code examples\n- [ ] Verify examples work with current code\n- [ ] Review for clarity and completeness\n- [ ] Get feedback from someone unfamiliar with the feature`,\n};\n\nconst performanceOptimizerContent: AgentDefaultContent = {\n  mission: `This agent identifies bottlenecks and optimizes performance based on measurements.\n\n**When to engage:**\n- Performance investigations\n- Optimization requests\n- Scalability planning\n- Resource usage concerns\n\n**Optimization approach:**\n- Measure before optimizing\n- Target actual bottlenecks\n- Verify improvements with benchmarks\n- Document trade-offs`,\n\n  responsibilities: `- Profile and measure performance to identify bottlenecks\n- Optimize algorithms and data structures\n- Implement caching strategies where appropriate\n- Reduce memory usage and prevent leaks\n- Optimize database queries and access patterns\n- Improve network request efficiency\n- Create performance benchmarks and tests\n- Document performance requirements and baselines`,\n\n  bestPractices: `- Always measure before and after optimization\n- Focus on actual bottlenecks, not assumed ones\n- Profile in production-like conditions\n- Consider the 80/20 rule - optimize what matters most\n- Document performance baselines and targets\n- Be aware of optimization trade-offs (memory vs speed, etc.)\n- Don't sacrifice readability for micro-optimizations\n- Add performance regression tests for critical paths`,\n\n  collaborationChecklist: `- [ ] Define performance requirements and targets\n- [ ] Profile to identify actual bottlenecks\n- [ ] Propose optimization approach\n- [ ] Implement optimization with minimal side effects\n- [ ] Measure improvement against baseline\n- [ ] Add performance tests to prevent regression\n- [ ] Document the optimization and trade-offs`,\n};\n\nconst securityAuditorContent: AgentDefaultContent = {\n  mission: `This agent identifies security vulnerabilities and implements security best practices.\n\n**When to engage:**\n- Security reviews\n- Vulnerability assessments\n- Authentication/authorization changes\n- Sensitive data handling\n\n**Security approach:**\n- OWASP top 10 awareness\n- Defense in depth\n- Principle of least privilege\n- Security testing`,\n\n  responsibilities: `- Review code for security vulnerabilities\n- Assess authentication and authorization implementations\n- Check for injection vulnerabilities (SQL, XSS, command, etc.)\n- Verify proper handling of sensitive data\n- Review dependency security (known vulnerabilities)\n- Implement security headers and configurations\n- Design secure API endpoints\n- Document security requirements and controls`,\n\n  bestPractices: `- Never trust user input - always validate and sanitize\n- Apply principle of least privilege\n- Use established security libraries, don't roll your own\n- Keep dependencies updated to patch vulnerabilities\n- Implement defense in depth (multiple security layers)\n- Log security events for monitoring and alerting\n- Encrypt sensitive data at rest and in transit\n- Review authentication and session management carefully`,\n\n  collaborationChecklist: `- [ ] Review for OWASP top 10 vulnerabilities\n- [ ] Check input validation and sanitization\n- [ ] Verify authentication and authorization\n- [ ] Assess sensitive data handling\n- [ ] Review dependencies for known vulnerabilities\n- [ ] Check security headers and configurations\n- [ ] Document security findings and recommendations`,\n};\n\nconst backendSpecialistContent: AgentDefaultContent = {\n  mission: `This agent designs and implements server-side architecture and APIs.\n\n**When to engage:**\n- API design and implementation\n- Service architecture decisions\n- Database integration\n- Backend performance optimization\n\n**Implementation approach:**\n- RESTful or GraphQL API design\n- Service layer patterns\n- Database optimization\n- Authentication and authorization`,\n\n  responsibilities: `- Design and implement RESTful or GraphQL APIs\n- Create service layer architecture\n- Implement data access patterns and repositories\n- Design and optimize database schemas\n- Set up authentication and authorization\n- Implement background jobs and queues\n- Create API documentation\n- Handle error handling and logging`,\n\n  bestPractices: `- Follow REST conventions or GraphQL best practices\n- Use proper HTTP status codes and error responses\n- Implement pagination, filtering, and sorting for collections\n- Design idempotent operations where appropriate\n- Use transactions for data consistency\n- Implement proper request validation\n- Cache responses when appropriate\n- Log requests and errors for debugging`,\n\n  collaborationChecklist: `- [ ] Design API contract and document endpoints\n- [ ] Implement service layer with business logic\n- [ ] Create data access layer and repositories\n- [ ] Add input validation and error handling\n- [ ] Implement authentication if required\n- [ ] Write tests for API endpoints\n- [ ] Update API documentation`,\n};\n\nconst frontendSpecialistContent: AgentDefaultContent = {\n  mission: `This agent designs and implements user interfaces with focus on UX and accessibility.\n\n**When to engage:**\n- UI component development\n- State management decisions\n- Accessibility improvements\n- Frontend performance optimization\n\n**Implementation approach:**\n- Component-based architecture\n- Responsive design\n- Accessibility first\n- Performance optimization`,\n\n  responsibilities: `- Implement UI components and layouts\n- Manage application state effectively\n- Ensure responsive design across devices\n- Implement accessibility standards (WCAG)\n- Optimize frontend performance (bundle size, rendering)\n- Handle form validation and user input\n- Implement client-side routing\n- Create reusable component libraries`,\n\n  bestPractices: `- Build components that are reusable and composable\n- Follow accessibility guidelines from the start\n- Test on multiple devices and browsers\n- Optimize bundle size and loading performance\n- Use semantic HTML elements\n- Implement proper keyboard navigation\n- Handle loading, error, and empty states\n- Write component tests and visual regression tests`,\n\n  collaborationChecklist: `- [ ] Review design specifications and requirements\n- [ ] Plan component structure and state management\n- [ ] Implement responsive, accessible components\n- [ ] Handle all UI states (loading, error, empty)\n- [ ] Test across browsers and devices\n- [ ] Optimize performance and bundle size\n- [ ] Write component tests`,\n};\n\nconst architectSpecialistContent: AgentDefaultContent = {\n  mission: `This agent designs overall system architecture and establishes technical standards.\n\n**When to engage:**\n- System design decisions\n- Technology selection\n- Architecture reviews\n- Scalability planning\n\n**Design approach:**\n- Scalable and maintainable architecture\n- Clear separation of concerns\n- Technology evaluation\n- Documentation of decisions`,\n\n  responsibilities: `- Design system architecture and component interactions\n- Evaluate and select technologies and frameworks\n- Establish coding standards and patterns\n- Create architecture decision records (ADRs)\n- Plan for scalability and reliability\n- Review designs for technical soundness\n- Guide team on architectural best practices\n- Balance technical debt with delivery needs`,\n\n  bestPractices: `- Document architectural decisions and their rationale\n- Design for change - anticipate future requirements\n- Keep architecture as simple as needed\n- Consider operational concerns (monitoring, deployment)\n- Evaluate trade-offs explicitly\n- Use proven patterns and avoid over-engineering\n- Ensure architecture supports testing and debugging\n- Review architecture regularly as requirements evolve`,\n\n  collaborationChecklist: `- [ ] Understand requirements and constraints\n- [ ] Evaluate architectural options and trade-offs\n- [ ] Design component structure and interactions\n- [ ] Document decisions in ADRs\n- [ ] Review design with team for feedback\n- [ ] Plan implementation approach\n- [ ] Create guidelines for developers`,\n};\n\nconst devopsSpecialistContent: AgentDefaultContent = {\n  mission: `This agent designs CI/CD pipelines, infrastructure, and deployment automation.\n\n**When to engage:**\n- CI/CD pipeline setup\n- Infrastructure provisioning\n- Deployment automation\n- Monitoring and alerting\n\n**DevOps approach:**\n- Infrastructure as code\n- Automated testing in pipelines\n- Continuous deployment\n- Observability and monitoring`,\n\n  responsibilities: `- Design and maintain CI/CD pipelines\n- Provision and manage infrastructure as code\n- Automate deployment processes\n- Set up monitoring, logging, and alerting\n- Manage containerization and orchestration\n- Configure environments (dev, staging, production)\n- Implement security in the deployment pipeline\n- Optimize build and deployment times`,\n\n  bestPractices: `- Use infrastructure as code for reproducibility\n- Automate everything that can be automated\n- Implement proper secrets management\n- Use immutable deployments when possible\n- Monitor all critical systems and set up alerts\n- Test infrastructure changes before applying\n- Document runbooks for common operations\n- Implement proper backup and recovery procedures`,\n\n  collaborationChecklist: `- [ ] Define deployment requirements and environments\n- [ ] Design CI/CD pipeline stages\n- [ ] Implement infrastructure as code\n- [ ] Set up automated testing in pipeline\n- [ ] Configure monitoring and alerting\n- [ ] Document deployment procedures\n- [ ] Test rollback and recovery processes`,\n};\n\nconst databaseSpecialistContent: AgentDefaultContent = {\n  mission: `This agent designs and optimizes database schemas and queries.\n\n**When to engage:**\n- Schema design decisions\n- Query performance issues\n- Migration planning\n- Data integrity concerns\n\n**Database approach:**\n- Normalized schema design\n- Index optimization\n- Query performance tuning\n- Data migration planning`,\n\n  responsibilities: `- Design database schemas and relationships\n- Write and optimize complex queries\n- Create and manage database migrations\n- Implement proper indexing strategies\n- Ensure data integrity and consistency\n- Plan for database scaling\n- Optimize query performance\n- Set up database backups and recovery`,\n\n  bestPractices: `- Design schemas with normalization in mind\n- Use appropriate data types and constraints\n- Index based on actual query patterns\n- Write migrations that are reversible\n- Test migrations on production-like data\n- Monitor query performance and slow queries\n- Use transactions for data consistency\n- Plan for data growth and scaling needs`,\n\n  collaborationChecklist: `- [ ] Understand data requirements and relationships\n- [ ] Design normalized schema structure\n- [ ] Plan indexing strategy based on queries\n- [ ] Write migration scripts\n- [ ] Test migrations with production-like data\n- [ ] Optimize queries for performance\n- [ ] Document schema and relationships`,\n};\n\nconst mobileSpecialistContent: AgentDefaultContent = {\n  mission: `This agent develops mobile applications for iOS and Android platforms.\n\n**When to engage:**\n- Mobile app development\n- Cross-platform decisions\n- Mobile performance issues\n- App store submissions\n\n**Mobile approach:**\n- Platform best practices\n- Performance optimization\n- Offline support\n- Native integrations`,\n\n  responsibilities: `- Develop mobile applications (native or cross-platform)\n- Implement responsive mobile UI/UX\n- Handle mobile-specific concerns (offline, battery, etc.)\n- Integrate with device features (camera, GPS, etc.)\n- Optimize app performance and startup time\n- Manage app store submissions and updates\n- Implement push notifications\n- Handle mobile security and data storage`,\n\n  bestPractices: `- Follow platform UI/UX guidelines\n- Optimize for battery and network usage\n- Implement proper offline support\n- Use native components when appropriate\n- Test on real devices, not just simulators\n- Handle different screen sizes and orientations\n- Implement proper error handling for network issues\n- Follow app store guidelines for submissions`,\n\n  collaborationChecklist: `- [ ] Review mobile design specifications\n- [ ] Plan architecture for cross-platform needs\n- [ ] Implement core functionality\n- [ ] Handle offline and network scenarios\n- [ ] Test on multiple devices and OS versions\n- [ ] Optimize performance and battery usage\n- [ ] Prepare for app store submission`,\n};\n\n// ============================================================================\n// Agent Structure Exports\n// ============================================================================\n\nexport const codeReviewerStructure = createAgentStructure(\n  'code-reviewer',\n  'Code Reviewer',\n  'Reviews code changes for quality, style, and best practices',\n  'Focus on code quality, maintainability, security issues, and adherence to project conventions.',\n  codeReviewerContent\n);\n\nexport const bugFixerStructure = createAgentStructure(\n  'bug-fixer',\n  'Bug Fixer',\n  'Analyzes bug reports and implements targeted fixes',\n  'Focus on root cause analysis, minimal side effects, and regression prevention.',\n  bugFixerContent\n);\n\nexport const featureDeveloperStructure = createAgentStructure(\n  'feature-developer',\n  'Feature Developer',\n  'Implements new features according to specifications',\n  'Focus on clean architecture, integration with existing code, and comprehensive testing.',\n  featureDeveloperContent\n);\n\nexport const refactoringSpecialistStructure = createAgentStructure(\n  'refactoring-specialist',\n  'Refactoring Specialist',\n  'Identifies code smells and improves code structure',\n  'Focus on incremental changes, test coverage, and preserving functionality.',\n  refactoringSpecialistContent\n);\n\nexport const testWriterStructure = createAgentStructure(\n  'test-writer',\n  'Test Writer',\n  'Writes comprehensive tests and maintains test coverage',\n  'Focus on unit tests, integration tests, edge cases, and test maintainability.',\n  testWriterContent\n);\n\nexport const documentationWriterStructure = createAgentStructure(\n  'documentation-writer',\n  'Documentation Writer',\n  'Creates and maintains documentation',\n  'Focus on clarity, practical examples, and keeping docs in sync with code.',\n  documentationWriterContent\n);\n\nexport const performanceOptimizerStructure = createAgentStructure(\n  'performance-optimizer',\n  'Performance Optimizer',\n  'Identifies bottlenecks and optimizes performance',\n  'Focus on measurement, actual bottlenecks, and caching strategies.',\n  performanceOptimizerContent\n);\n\nexport const securityAuditorStructure = createAgentStructure(\n  'security-auditor',\n  'Security Auditor',\n  'Identifies security vulnerabilities and implements best practices',\n  'Focus on OWASP top 10, dependency scanning, and principle of least privilege.',\n  securityAuditorContent\n);\n\nexport const backendSpecialistStructure = createAgentStructure(\n  'backend-specialist',\n  'Backend Specialist',\n  'Designs and implements server-side architecture',\n  'Focus on APIs, microservices, database optimization, and authentication.',\n  backendSpecialistContent\n);\n\nexport const frontendSpecialistStructure = createAgentStructure(\n  'frontend-specialist',\n  'Frontend Specialist',\n  'Designs and implements user interfaces',\n  'Focus on responsive design, accessibility, state management, and performance.',\n  frontendSpecialistContent\n);\n\nexport const architectSpecialistStructure = createAgentStructure(\n  'architect-specialist',\n  'Architect Specialist',\n  'Designs overall system architecture and patterns',\n  'Focus on scalability, maintainability, and technical standards.',\n  architectSpecialistContent\n);\n\nexport const devopsSpecialistStructure = createAgentStructure(\n  'devops-specialist',\n  'DevOps Specialist',\n  'Designs CI/CD pipelines and infrastructure',\n  'Focus on automation, infrastructure as code, and monitoring.',\n  devopsSpecialistContent\n);\n\nexport const databaseSpecialistStructure = createAgentStructure(\n  'database-specialist',\n  'Database Specialist',\n  'Designs and optimizes database schemas',\n  'Focus on schema design, query optimization, and data integrity.',\n  databaseSpecialistContent\n);\n\nexport const mobileSpecialistStructure = createAgentStructure(\n  'mobile-specialist',\n  'Mobile Specialist',\n  'Develops mobile applications',\n  'Focus on native/cross-platform development, performance, and app store requirements.',\n  mobileSpecialistContent\n);\n"
  },
  {
    "path": "src/generators/shared/structures/agents/factory.ts",
    "content": "/**\n * Factory function for creating agent structures\n */\n\nimport { ScaffoldStructure, ScaffoldSection } from '../types';\n\n/**\n * Default content overrides for specific sections\n */\nexport interface AgentDefaultContent {\n  mission?: string;\n  responsibilities?: string;\n  bestPractices?: string;\n  keyProjectResources?: string;\n  repositoryStartingPoints?: string;\n  keyFiles?: string;\n  architectureContext?: string;\n  keySymbols?: string;\n  documentationTouchpoints?: string;\n  collaborationChecklist?: string;\n  handoffNotes?: string;\n}\n\n/**\n * Map of section headings to content keys\n */\nconst SECTION_KEY_MAP: Record<string, keyof AgentDefaultContent> = {\n  'Mission': 'mission',\n  'Responsibilities': 'responsibilities',\n  'Best Practices': 'bestPractices',\n  'Key Project Resources': 'keyProjectResources',\n  'Repository Starting Points': 'repositoryStartingPoints',\n  'Key Files': 'keyFiles',\n  'Architecture Context': 'architectureContext',\n  'Key Symbols for This Agent': 'keySymbols',\n  'Documentation Touchpoints': 'documentationTouchpoints',\n  'Collaboration Checklist': 'collaborationChecklist',\n  'Hand-off Notes': 'handoffNotes',\n};\n\n/**\n * Create an agent structure with standard sections\n */\nexport function createAgentStructure(\n  agentType: string,\n  title: string,\n  description: string,\n  additionalContext?: string,\n  defaultContent?: AgentDefaultContent\n): ScaffoldStructure {\n  const sections: ScaffoldSection[] = [\n    {\n      heading: 'Mission',\n      order: 1,\n      contentType: 'prose',\n      guidance: `Describe how the ${title.toLowerCase()} agent supports the team and when to engage it.`,\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Responsibilities',\n      order: 2,\n      contentType: 'list',\n      guidance: 'List specific responsibilities this agent handles. Be concrete about what tasks it performs.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Best Practices',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List best practices and guidelines for this agent to follow.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Key Project Resources',\n      order: 4,\n      contentType: 'list',\n      guidance: 'Link to documentation index, agent handbook, AGENTS.md, and contributor guide.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Repository Starting Points',\n      order: 5,\n      contentType: 'list',\n      guidance: 'List top-level directories relevant to this agent with brief descriptions.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Key Files',\n      order: 6,\n      contentType: 'list',\n      guidance: 'List entry points, pattern implementations, and service files relevant to this agent.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Architecture Context',\n      order: 7,\n      contentType: 'list',\n      guidance: 'For each architectural layer, describe directories, symbol counts, and key exports.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Key Symbols for This Agent',\n      order: 8,\n      contentType: 'list',\n      guidance: 'List symbols (classes, functions, types) most relevant to this agent with links.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Documentation Touchpoints',\n      order: 9,\n      contentType: 'list',\n      guidance: 'Link to relevant documentation files this agent should reference.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Collaboration Checklist',\n      order: 10,\n      contentType: 'checklist',\n      guidance: 'Numbered checklist for agent workflow: confirm assumptions, review PRs, update docs, capture learnings.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Hand-off Notes',\n      order: 11,\n      contentType: 'prose',\n      guidance: 'Summarize outcomes, remaining risks, and suggested follow-up actions after the agent completes work.',\n      required: false,\n      headingLevel: 2,\n    },\n  ];\n\n  // Apply default content to sections\n  if (defaultContent) {\n    for (const section of sections) {\n      const key = SECTION_KEY_MAP[section.heading];\n      if (key && defaultContent[key]) {\n        section.defaultContent = defaultContent[key];\n      }\n    }\n  }\n\n  return {\n    fileType: 'agent',\n    documentName: agentType,\n    title: `${title} Agent Playbook`,\n    description,\n    tone: 'instructional',\n    audience: 'ai-agents',\n    sections,\n    linkTo: ['../docs/README.md', 'README.md', '../../AGENTS.md'],\n    additionalContext,\n  };\n}\n"
  },
  {
    "path": "src/generators/shared/structures/agents/index.ts",
    "content": "/**\n * Agent structure exports\n */\n\nexport { createAgentStructure } from './factory';\nexport {\n  codeReviewerStructure,\n  bugFixerStructure,\n  featureDeveloperStructure,\n  refactoringSpecialistStructure,\n  testWriterStructure,\n  documentationWriterStructure,\n  performanceOptimizerStructure,\n  securityAuditorStructure,\n  backendSpecialistStructure,\n  frontendSpecialistStructure,\n  architectSpecialistStructure,\n  devopsSpecialistStructure,\n  databaseSpecialistStructure,\n  mobileSpecialistStructure,\n} from './definitions';\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/apiReference.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const apiReferenceStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'api-reference',\n  title: 'API Reference',\n  description: 'Complete API documentation for programmatic and AI agent access',\n  tone: 'technical',\n  audience: 'mixed',\n  sections: [\n    {\n      heading: 'API Reference',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Describe the API purpose and provide agent-specific instructions for programmatic interaction.',\n      exampleContent: '**Agent Usage Instructions:**\\n- Use documented curl commands as templates\\n- Verify expected response codes\\n- Handle authentication token expiration automatically\\n- Parse error responses to determine retry strategy\\n- Log all API interactions for debugging',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Base URLs',\n      order: 2,\n      contentType: 'code-block',\n      guidance: 'List API base URLs for different environments (dev, staging, production) with example environment variable configuration.',\n      exampleContent: '```bash\\nexport API_BASE_DEV=\"http://localhost:3000/api\"\\nexport API_BASE_STAGING=\"https://staging-api.example.com\"\\nexport API_BASE_PROD=\"https://api.example.com\"\\n```',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Authentication',\n      order: 3,\n      contentType: 'prose',\n      guidance: 'Document authentication flow including token generation, storage, expiration handling, and refresh mechanisms with curl examples for agents.',\n      exampleContent: 'Include bash examples for: 1) Obtain token via POST /auth/login, 2) Use token in requests with Bearer header, 3) Check token expiration and refresh if needed',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'API Versioning',\n      order: 4,\n      contentType: 'prose',\n      guidance: 'Document API versioning strategy (URL path, header, or query parameter), current version, and deprecation policy.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Rate Limiting',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Document rate limits with code examples showing agents how to check headers and handle backoff.',\n      exampleContent: 'Include monitoring of X-RateLimit-* headers and handling 429 responses with exponential backoff',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Common Headers',\n      order: 6,\n      contentType: 'list',\n      guidance: 'List request and response headers including Content-Type, Authorization, X-Request-ID, and any custom headers.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Endpoints',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Document API endpoints organized by resource with request/response examples for GET, POST, PUT, PATCH, DELETE methods.',\n      exampleContent: 'Include: method, path, parameters, request body examples, response codes, error handling for each endpoint',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Webhooks',\n      order: 8,\n      contentType: 'prose',\n      guidance: 'Document webhook registration, available events, payload structure, and security mechanisms.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Error Responses',\n      order: 9,\n      contentType: 'prose',\n      guidance: 'Document standard error response format, HTTP status codes, and common error codes with their meanings.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'SDKs & Client Libraries',\n      order: 10,\n      contentType: 'list',\n      guidance: 'Link to available SDKs (JavaScript, Python, etc.) and OpenAPI/Swagger specifications.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['architecture.md', 'security.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/architecture.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nconst SEMANTIC_SNAPSHOT_GUIDANCE =\n  'Use `context({ action: \"getMap\", section: \"all\" })` to inspect the generated semantic snapshot for stack, architecture, key files, and dependency hotspots.';\n\nexport const architectureStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'architecture',\n  title: 'Architecture Notes',\n  description: 'System architecture, layers, patterns, and design decisions',\n  tone: 'technical',\n  audience: 'architects',\n  sections: [\n    {\n      heading: 'Architecture Notes',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Describe how the system is assembled and why the current design exists.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document describes the system architecture, design patterns, and key technical decisions.\n\n${SEMANTIC_SNAPSHOT_GUIDANCE}`,\n    },\n    {\n      heading: 'System Architecture Overview',\n      order: 2,\n      contentType: 'prose',\n      guidance: 'Summarize the top-level topology (monolith, modular service, microservices) and deployment model. Highlight how requests traverse the system and where control pivots between layers.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Architecture Style**: [Monolith / Modular Monolith / Microservices]\n\n**Key Components**:\n- **Entry Layer**: Handles incoming requests (CLI, HTTP, etc.)\n- **Service Layer**: Core business logic and orchestration\n- **Data Layer**: Persistence and external service integration\n\n**Request Flow**:\n1. Request enters through entry point\n2. Routed to appropriate service handler\n3. Service processes and returns response`,\n    },\n    {\n      heading: 'Architectural Layers',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List architecture layers with their purpose and key directories. Reference the semantic snapshot for generated architecture and dependency summaries.',\n      exampleContent: '- **Services**: Core business logic (`src/services/`)\\n- **Generators**: Content generation (`src/generators/`)\\n\\n> Use `context({ action: \"getMap\", section: \"all\" })` for generated architecture and dependency summaries.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- **Entry Points**: Application entry and initialization (\\`src/\\`)\n- **Services**: Core business logic (\\`src/services/\\`)\n- **Models/Types**: Data structures and type definitions (\\`src/types/\\`)\n- **Utilities**: Shared helper functions (\\`src/utils/\\`)\n\n> Use \\`context({ action: \"getMap\", section: \"all\" })\\` for generated architecture and dependency summaries.`,\n    },\n    {\n      heading: 'Detected Design Patterns',\n      order: 4,\n      contentType: 'table',\n      guidance: 'Table with Pattern, Confidence, Locations, and Description columns. Link to actual implementations.',\n      exampleContent: '| Pattern | Confidence | Locations | Description |\\n|---------|------------|-----------|-------------|\\n| Factory | 85% | `LLMClientFactory` | Creates LLM client instances |',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `| Pattern | Locations | Description |\n|---------|-----------|-------------|\n| [Pattern Name] | \\`src/path/\\` | [Brief description] |\n\n*Update this table as patterns are identified in the codebase.*`,\n    },\n    {\n      heading: 'Entry Points',\n      order: 5,\n      contentType: 'list',\n      guidance: 'List entry points with markdown links to the actual files.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- [\\`src/index.ts\\`](../src/index.ts) — Main module entry\n- [\\`src/cli.ts\\`](../src/cli.ts) — CLI entry point (if applicable)`,\n    },\n    {\n      heading: 'Public API',\n      order: 6,\n      contentType: 'table',\n      guidance: 'Table of exported symbols with Symbol, Type, and Location columns.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `| Symbol | Type | Location |\n|--------|------|----------|\n| [ExportName] | class/function/type | \\`src/path.ts\\` |\n`,\n    },\n    {\n      heading: 'Internal System Boundaries',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Document seams between domains, bounded contexts, or service ownership. Note data ownership, synchronization strategies, and shared contract enforcement.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'External Service Dependencies',\n      order: 8,\n      contentType: 'list',\n      guidance: 'List SaaS platforms, third-party APIs, or infrastructure services. Describe authentication methods, rate limits, and failure considerations.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Key Decisions & Trade-offs',\n      order: 9,\n      contentType: 'prose',\n      guidance: 'Summarize architectural decisions, experiments, or ADR outcomes. Explain why selected approaches won over alternatives.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `Document key architectural decisions here. Consider creating Architecture Decision Records (ADRs) for significant choices.\n\n**Template**:\n- **Decision**: [What was decided]\n- **Context**: [Why this decision was needed]\n- **Alternatives**: [What else was considered]\n- **Consequences**: [Impact of this decision]`,\n    },\n    {\n      heading: 'Diagrams',\n      order: 10,\n      contentType: 'diagram',\n      guidance: 'Link architectural diagrams or add mermaid definitions showing system components and their relationships.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `\\`\\`\\`mermaid\ngraph TD\n    A[Entry Point] --> B[Service Layer]\n    B --> C[Data Layer]\n    B --> D[External Services]\n\\`\\`\\`\n\n*Replace with actual system architecture diagram.*`,\n    },\n    {\n      heading: 'Risks & Constraints',\n      order: 11,\n      contentType: 'prose',\n      guidance: 'Document performance constraints, scaling considerations, or external system assumptions.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Top Directories Snapshot',\n      order: 12,\n      contentType: 'list',\n      guidance: 'List top directories with approximate file counts.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- \\`src/\\` — Source code\n- \\`tests/\\` — Test files\n- \\`docs/\\` — Documentation\n\n*Use \\`context({ action: \"getMap\", section: \"stats\" })\\` for detailed file counts.*`,\n    },\n    {\n      heading: 'Related Resources',\n      order: 13,\n      contentType: 'list',\n      guidance: 'Link to Project Overview and other relevant documentation.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- [Project Overview](./project-overview.md)\n- [Data Flow](./data-flow.md) (if applicable)\n- Semantic snapshot via \\`context({ action: \"getMap\", section: \"all\" })\\``,\n    },\n  ],\n  linkTo: ['project-overview.md', 'data-flow.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/dataFlow.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const dataFlowStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'data-flow',\n  title: 'Data Flow & Integrations',\n  description: 'How data moves through the system and external integrations',\n  tone: 'technical',\n  audience: 'architects',\n  sections: [\n    {\n      heading: 'Data Flow & Integrations',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Explain how data enters, moves through, and exits the system, including interactions with external services.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document describes how data flows through the system, including internal processing and external integrations.\n\nUnderstanding data flow helps with debugging, performance optimization, and maintaining system reliability.`,\n    },\n    {\n      heading: 'Module Dependencies',\n      order: 2,\n      contentType: 'list',\n      guidance: 'List cross-module dependencies showing which modules depend on which.',\n      exampleContent: '- **src/** → `utils`, `config`\\n- **services/** → `utils`',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `Module dependency overview:\n\n- **Entry Layer** → Services, Utils\n- **Services** → Data Access, External APIs\n- **Data Access** → Database, Cache\n\n*Use \\`context({ action: \"getMap\", section: \"dependencies\" })\\` for generated dependency hotspots and architecture summaries.*`,\n    },\n    {\n      heading: 'Service Layer',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List service classes with links to their implementations.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `Key services in the system:\n\n- **[ServiceName]** — [Purpose] (\\`src/services/path.ts\\`)\n\nCapture the main service modules here with links to their implementations.`,\n    },\n    {\n      heading: 'High-level Flow',\n      order: 4,\n      contentType: 'prose',\n      guidance: 'Summarize the primary pipeline from input to output. Reference diagrams or embed Mermaid definitions.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `\\`\\`\\`mermaid\nflowchart LR\n    A[Input] --> B[Processing]\n    B --> C[Storage]\n    B --> D[Output]\n\\`\\`\\`\n\n**Data Flow Steps**:\n1. Data enters through entry points (API, CLI, etc.)\n2. Services process and transform data\n3. Results are stored and/or returned to caller\n\n*Replace with actual system data flow.*`,\n    },\n    {\n      heading: 'Internal Movement',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Describe how modules collaborate (queues, events, RPC calls, shared databases).',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'External Integrations',\n      order: 6,\n      contentType: 'list',\n      guidance: 'Document each integration with purpose, authentication, payload shapes, and retry strategy.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**External Services**:\n\n| Service | Purpose | Auth Method |\n|---------|---------|-------------|\n| [Service] | [Purpose] | [API Key/OAuth/etc.] |\n\n*Document error handling and retry strategies for each integration.*`,\n    },\n    {\n      heading: 'Observability & Failure Modes',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Describe metrics, traces, or logs that monitor the flow. Note backoff, dead-letter, or compensating actions.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['architecture.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/glossary.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const glossaryStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'glossary',\n  title: 'Glossary & Domain Concepts',\n  description: 'Project terminology, type definitions, domain entities, and business rules',\n  tone: 'technical',\n  audience: 'mixed',\n  sections: [\n    {\n      heading: 'Glossary & Domain Concepts',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'List project-specific terminology, acronyms, domain entities, and user personas.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document defines project-specific terminology, concepts, and domain knowledge.\n\nUse this as a reference when encountering unfamiliar terms in the codebase or documentation.`,\n    },\n    {\n      heading: 'Type Definitions',\n      order: 2,\n      contentType: 'list',\n      guidance: 'List exported type definitions and interfaces with links to their locations.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `Key type definitions in this project:\n\n- **[TypeName]** — [Description] (\\`src/types/path.ts\\`)\n\nDocument the main shared types here and link to their source files.`,\n    },\n    {\n      heading: 'Enumerations',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List exported enums with links to their locations.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `Enums defined in this project:\n\n- **[EnumName]** — [Description] (\\`src/types/path.ts\\`)\n\nDocument the main enums here and link to their source files.`,\n    },\n    {\n      heading: 'Core Terms',\n      order: 4,\n      contentType: 'list',\n      guidance: 'Define key terms, their relevance, and where they surface in the codebase.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**[Term]**: [Definition and context]\n\nAdd project-specific terminology here as the codebase evolves.`,\n    },\n    {\n      heading: 'Acronyms & Abbreviations',\n      order: 5,\n      contentType: 'list',\n      guidance: 'Expand abbreviations and note associated services or APIs.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `| Acronym | Expansion | Context |\n|---------|-----------|---------|\n| API | Application Programming Interface | External/internal interfaces |\n| CLI | Command Line Interface | User interaction |`,\n    },\n    {\n      heading: 'Personas / Actors',\n      order: 6,\n      contentType: 'prose',\n      guidance: 'Describe user goals, key workflows, and pain points addressed by the system.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Domain Rules & Invariants',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Capture business rules, validation constraints, or compliance requirements. Note region/localization nuances.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['project-overview.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/index.ts",
    "content": "/**\n * Documentation structure definitions\n */\n\nexport { projectOverviewStructure } from './projectOverview';\nexport { architectureStructure } from './architecture';\nexport { developmentWorkflowStructure } from './workflow';\nexport { testingStrategyStructure } from './testing';\nexport { toolingStructure } from './tooling';\nexport { securityStructure } from './security';\nexport { glossaryStructure } from './glossary';\nexport { dataFlowStructure } from './dataFlow';\nexport { onboardingStructure } from './onboarding';\nexport { apiReferenceStructure } from './apiReference';\nexport { migrationStructure } from './migration';\nexport { troubleshootingStructure } from './troubleshooting';\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/migration.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const migrationStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'migration',\n  title: 'Migration Guide',\n  description: 'Complete guide for upgrading between versions, breaking changes, and data migration procedures',\n  tone: 'technical',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Migration Guide',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Introduction explaining version support policy and the migration guide purpose.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Current Version & Support Policy',\n      order: 2,\n      contentType: 'prose',\n      guidance: 'Document latest version, release date, and version support lifecycle (how long each version receives support).',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Before You Migrate',\n      order: 3,\n      contentType: 'checklist',\n      guidance: 'Pre-migration checklist including backup, changelog review, staging test, dependency check, stakeholder notification, and rollback planning.',\n      exampleContent: '- [ ] Backup all data (database, file storage)\\n- [ ] Review changelog for target version\\n- [ ] Test migration in staging environment\\n- [ ] Check dependency compatibility\\n- [ ] Notify stakeholders\\n- [ ] Plan rollback procedure\\n- [ ] Schedule maintenance window',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'System Requirements',\n      order: 4,\n      contentType: 'list',\n      guidance: 'List minimum requirements for each version including Node.js, database, OS, memory, and disk space.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Migration Paths',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Document supported upgrade paths with breaking changes, step-by-step procedures with bash commands, and rollback instructions.',\n      exampleContent: 'For each major version upgrade: list breaking changes, provide detailed migration steps with code blocks, include rollback procedures',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Database Migrations',\n      order: 6,\n      contentType: 'prose',\n      guidance: 'Document database migration procedures including running migrations, creating custom migrations, and handling large datasets.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Data Transformation',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Document data transformation strategies including batch migration scripts and zero-downtime migration patterns.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Deprecation Timeline',\n      order: 8,\n      contentType: 'list',\n      guidance: 'List deprecated features and the timeline for removal with migration paths.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Backward Compatibility',\n      order: 9,\n      contentType: 'prose',\n      guidance: 'Explain compatibility mode and feature flags for managing transition between versions.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Common Issues & Solutions',\n      order: 10,\n      contentType: 'prose',\n      guidance: 'Document common migration issues and solutions including migration failures, data inconsistencies, and performance problems.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Verification & Testing',\n      order: 11,\n      contentType: 'list',\n      guidance: 'List scripts and tools for verifying migration success and testing application functionality.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Getting Help',\n      order: 12,\n      contentType: 'prose',\n      guidance: 'Provide support contact information and guidance for reporting migration issues.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['development-workflow.md', 'architecture.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/onboarding.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const onboardingStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'onboarding',\n  title: 'Onboarding Guide',\n  description: 'Complete guide for getting new team members productive, including access setup and first contributions',\n  tone: 'instructional',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Onboarding Guide',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Welcome message and high-level overview of the onboarding process.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Day 1: Access & Accounts',\n      order: 2,\n      contentType: 'checklist',\n      guidance: 'Checklist for Day 1 access setup including email, chat, code repository, issue tracking, and documentation access.',\n      exampleContent: '- [ ] Email account - Verify access to team email\\n- [ ] Chat/Communication - Join Slack/Teams and relevant channels\\n- [ ] Code repository - GitHub access and SSH key configured\\n- [ ] Issue tracking - Jira/GitHub Issues access\\n- [ ] Documentation - Wiki/Confluence access',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Day 1-2: Development Environment',\n      order: 3,\n      contentType: 'list',\n      guidance: 'Step-by-step development environment setup with code blocks for installation and configuration commands.',\n      exampleContent: '- Install required software (Node.js, npm, Git, IDE)\\n- Clone repository with example bash commands\\n- Configure environment variables (.env file)\\n- Run database migrations if applicable\\n- Verify setup with test commands',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Day 2-3: Project Understanding',\n      order: 4,\n      contentType: 'list',\n      guidance: 'Reading list of core documentation and activities for understanding the codebase, team structure, and responsibilities.',\n      exampleContent: '- [ ] Read core documentation (Project Overview, Architecture, Development Workflow, Testing Strategy)\\n- [ ] Explore project structure and main entry points\\n- [ ] Meet team members and understand roles\\n- [ ] Review recent pull requests for code standards',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Week 1: First Contributions',\n      order: 5,\n      contentType: 'list',\n      guidance: 'Guidance for making first code contributions, learning workflow, and setting up productivity tools.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Required Access & Permissions',\n      order: 6,\n      contentType: 'list',\n      guidance: 'List of accounts, access levels, and permissions needed for different roles (repository, deployments, databases, cloud resources).',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Team Communication',\n      order: 7,\n      contentType: 'list',\n      guidance: 'Document chat channels, recurring meetings, and communication guidelines.',\n      exampleContent: '- **Channels:** #engineering, #team-name, #deployments, #incidents\\n- **Meetings:** Daily standup, Sprint planning, Retrospective, Team sync\\n- **Guidelines:** Use threads, mention directly when needed, default to public channels',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Learning Resources',\n      order: 8,\n      contentType: 'list',\n      guidance: 'Links to project-specific documentation, technical skill resources, company handbooks, and external resources.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Getting Help',\n      order: 9,\n      contentType: 'prose',\n      guidance: 'Escalation steps when stuck: self-debug, search resources, ask onboarding buddy, ask team channel, ask broader engineering.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Team Culture & Expectations',\n      order: 10,\n      contentType: 'prose',\n      guidance: 'Work hours, code review expectations, development principles, and growth opportunities.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['development-workflow.md', 'project-overview.md', 'architecture.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/projectOverview.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nconst SEMANTIC_SNAPSHOT_GUIDANCE =\n  'Use `context({ action: \"getMap\", section: \"all\" })` to inspect the generated semantic snapshot for stack, architecture layers, key files, and dependency hotspots.';\n\nexport const projectOverviewStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'project-overview',\n  title: 'Project Overview',\n  description: 'High-level overview of the project, its purpose, and key components',\n  tone: 'conversational',\n  audience: 'mixed',\n  sections: [\n    {\n      heading: 'Project Overview',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Summarize in 2-3 sentences what problem this project solves and who benefits from it. Focus on the value proposition.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This project provides [describe main functionality]. It helps [target users] to [key benefit].\n\nThe codebase is organized to support [main use case] with a focus on [key qualities like maintainability, performance, etc.].`,\n    },\n    {\n      heading: 'Codebase Reference',\n      order: 2,\n      contentType: 'prose',\n      guidance: 'Add a callout pointing to the semantic snapshot for generated stack, architecture layers, key files, and dependency hotspots.',\n      exampleContent: '> **Semantic Snapshot**: Use `context({ action: \"getMap\", section: \"all\" })` for generated stack, architecture layers, key files, and dependency hotspots.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `> **Semantic Snapshot**: ${SEMANTIC_SNAPSHOT_GUIDANCE}`,\n    },\n    {\n      heading: 'Quick Facts',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List root directory path, primary languages, and key entry points. Reference the semantic snapshot for the generated summary.',\n      exampleContent: '- Root: `/path/to/repo`\\n- Languages: TypeScript, Python\\n- Entry: `src/index.ts`\\n- Semantic snapshot: `context({ action: \"getMap\", section: \"all\" })`',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- **Root**: \\`./\\`\n- **Primary Language**: [Language] ([X] files)\n- **Entry Point**: \\`src/index.ts\\` or \\`src/main.ts\\`\n- **Semantic Snapshot**: \\`context({ action: \"getMap\", section: \"all\" })\\``,\n    },\n    {\n      heading: 'Entry Points',\n      order: 4,\n      contentType: 'list',\n      guidance: 'List main entry points with links (CLI, server, library exports). Use markdown links with line numbers.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- **Main Entry**: \\`src/index.ts\\` - Primary module exports\n- **CLI**: \\`src/cli.ts\\` - Command-line interface (if applicable)\n- **Server**: \\`src/server.ts\\` - HTTP server entry (if applicable)`,\n    },\n    {\n      heading: 'Key Exports',\n      order: 5,\n      contentType: 'list',\n      guidance: 'Summarize the main public entry points and exported surfaces. Do not rely only on the semantic snapshot for a full symbol inventory.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `Key public APIs:\n- [List main exported classes/functions]`,\n    },\n    {\n      heading: 'File Structure & Code Organization',\n      order: 6,\n      contentType: 'list',\n      guidance: 'List top-level directories with brief descriptions of their purpose.',\n      exampleContent: '- `src/` — TypeScript source files and CLI entrypoints.\\n- `tests/` — Automated tests and fixtures.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `- \\`src/\\` — Source code and main application logic\n- \\`tests/\\` or \\`__tests__/\\` — Test files and fixtures\n- \\`dist/\\` or \\`build/\\` — Compiled output (gitignored)\n- \\`docs/\\` — Documentation files\n- \\`scripts/\\` — Build and utility scripts`,\n    },\n    {\n      heading: 'Technology Stack Summary',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Outline primary runtimes, languages, and platforms in use. Note build tooling, linting, and formatting infrastructure.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Runtime**: Node.js\n\n**Language**: TypeScript/JavaScript\n\n**Build Tools**:\n- TypeScript compiler (tsc) or bundler (esbuild, webpack, etc.)\n- Package manager: npm/yarn/pnpm\n\n**Code Quality**:\n- Linting: ESLint\n- Formatting: Prettier\n- Type checking: TypeScript strict mode`,\n    },\n    {\n      heading: 'Core Framework Stack',\n      order: 8,\n      contentType: 'prose',\n      guidance: 'Document core frameworks per layer (backend, frontend, data, messaging). Mention architectural patterns enforced by these frameworks.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'UI & Interaction Libraries',\n      order: 9,\n      contentType: 'prose',\n      guidance: 'List UI kits, CLI interaction helpers, or design system dependencies. Note theming, accessibility, or localization considerations.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Development Tools Overview',\n      order: 10,\n      contentType: 'prose',\n      guidance: 'Highlight essential CLIs, scripts, or developer environments. Link to Tooling guide for deeper setup.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `See [Tooling](./tooling.md) for detailed development environment setup.\n\n**Essential Commands**:\n- \\`npm install\\` — Install dependencies\n- \\`npm run build\\` — Build the project\n- \\`npm run test\\` — Run tests\n- \\`npm run dev\\` — Start development mode`,\n    },\n    {\n      heading: 'Getting Started Checklist',\n      order: 11,\n      contentType: 'checklist',\n      guidance: 'Provide numbered steps to get a new developer productive. Include install, run, and verify steps.',\n      exampleContent: '1. Install dependencies with `npm install`.\\n2. Explore the CLI by running `npm run dev`.\\n3. Review Development Workflow for day-to-day tasks.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `1. Clone the repository\n2. Install dependencies: \\`npm install\\`\n3. Copy environment template: \\`cp .env.example .env\\` (if applicable)\n4. Run tests to verify setup: \\`npm run test\\`\n5. Start development: \\`npm run dev\\`\n6. Review [Development Workflow](./development-workflow.md) for day-to-day tasks`,\n    },\n    {\n      heading: 'Next Steps',\n      order: 12,\n      contentType: 'prose',\n      guidance: 'Capture product positioning, key stakeholders, and links to external documentation or product specs.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `- Review [Architecture](./architecture.md) for system design details\n- See [Development Workflow](./development-workflow.md) for contribution guidelines\n- Check [Testing Strategy](./testing-strategy.md) for quality requirements`,\n    },\n  ],\n  linkTo: ['architecture.md', 'development-workflow.md', 'tooling.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/security.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const securityStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'security',\n  title: 'Security & Compliance Notes',\n  description: 'Security policies, authentication, secrets management, and compliance requirements',\n  tone: 'formal',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Security & Compliance Notes',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Capture the policies and guardrails that keep this project secure and compliant.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document outlines security practices, policies, and guidelines for this project.\n\n**Security Principles**:\n- Defense in depth — Multiple security layers\n- Principle of least privilege — Minimal necessary access\n- Secure by default — Safe configurations out of the box`,\n    },\n    {\n      heading: 'Authentication & Authorization',\n      order: 2,\n      contentType: 'prose',\n      guidance: 'Describe identity providers, token formats, session strategies, and role/permission models.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Authentication**:\n- [Describe authentication mechanism: JWT, sessions, OAuth, etc.]\n- Token/session expiration: [Duration]\n- Refresh strategy: [How tokens are refreshed]\n\n**Authorization**:\n- Permission model: [RBAC, ABAC, etc.]\n- Role definitions: [Admin, User, etc.]\n- Access control enforcement: [Where/how permissions are checked]`,\n    },\n    {\n      heading: 'Secrets & Sensitive Data',\n      order: 3,\n      contentType: 'prose',\n      guidance: 'Document storage locations (vaults, parameter stores), rotation cadence, encryption practices, and data classifications.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Secrets Management**:\n- Storage: Environment variables / secrets manager\n- Never commit secrets to version control\n- Use \\`.env.example\\` as a template (without real values)\n\n**Sensitive Data Handling**:\n- Encryption at rest: [Yes/No, method]\n- Encryption in transit: TLS 1.2+\n- Data classification: [Public, Internal, Confidential, Restricted]\n\n**Best Practices**:\n- Rotate secrets regularly\n- Use strong, unique passwords\n- Audit access to sensitive data`,\n    },\n    {\n      heading: 'Compliance & Policies',\n      order: 4,\n      contentType: 'list',\n      guidance: 'List applicable standards (GDPR, SOC2, HIPAA, internal policies) and evidence requirements.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**Applicable Standards**:\n- [List relevant compliance frameworks]\n\n**Security Policies**:\n- Code review required for all changes\n- Dependency scanning for vulnerabilities\n- Regular security assessments`,\n    },\n    {\n      heading: 'Incident Response',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Note on-call contacts, escalation steps, and tooling for detection, triage, and post-incident analysis.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**Reporting Security Issues**:\n- Report security vulnerabilities to [security contact]\n- Do not disclose publicly before fix is available\n\n**Incident Response**:\n1. Identify and contain the issue\n2. Assess impact and scope\n3. Remediate and recover\n4. Document and learn from the incident`,\n    },\n  ],\n  linkTo: ['architecture.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/testing.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const testingStrategyStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'testing-strategy',\n  title: 'Testing Strategy',\n  description: 'Test frameworks, patterns, coverage requirements, and quality gates',\n  tone: 'instructional',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Testing Strategy',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Document how quality is maintained across the codebase.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document outlines the testing strategy for maintaining code quality.\n\n**Testing Philosophy**:\n- Tests should be fast, isolated, and deterministic\n- Follow the test pyramid: many unit tests, fewer integration tests, minimal E2E tests\n- Test behavior, not implementation details\n- Every bug fix should include a regression test`,\n    },\n    {\n      heading: 'Test Types',\n      order: 2,\n      contentType: 'list',\n      guidance: 'For Unit, Integration, and E2E tests: list frameworks, file naming conventions, and required tooling.',\n      exampleContent: '- **Unit**: Jest, files named `*.test.ts`\\n- **Integration**: Describe scenarios\\n- **E2E**: Note harnesses or environments',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Unit Tests**:\n- Framework: Jest / Vitest\n- Location: \\`__tests__/\\` or co-located \\`*.test.ts\\` files\n- Purpose: Test individual functions and components in isolation\n- Mocking: Use jest mocks for external dependencies\n\n**Integration Tests**:\n- Framework: Jest / Vitest\n- Location: \\`tests/integration/\\` or \\`*.integration.test.ts\\`\n- Purpose: Test feature workflows and component interactions\n- Setup: May require test database or external services\n\n**E2E Tests** (if applicable):\n- Framework: Playwright / Cypress\n- Location: \\`e2e/\\` or \\`tests/e2e/\\`\n- Purpose: Test critical user paths end-to-end\n- Environment: Requires full application stack`,\n    },\n    {\n      heading: 'Running Tests',\n      order: 3,\n      contentType: 'list',\n      guidance: 'Commands for running all tests, watch mode, and coverage. Use code blocks.',\n      exampleContent: '- All tests: `npm run test`\\n- Watch mode: `npm run test -- --watch`\\n- Coverage: `npm run test -- --coverage`',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Commands**:\n\\`\\`\\`bash\n# Run all tests\nnpm run test\n\n# Run tests in watch mode (for development)\nnpm run test -- --watch\n\n# Run tests with coverage report\nnpm run test -- --coverage\n\n# Run specific test file\nnpm run test -- path/to/file.test.ts\n\n# Run tests matching pattern\nnpm run test -- --testNamePattern=\"pattern\"\n\\`\\`\\``,\n    },\n    {\n      heading: 'Quality Gates',\n      order: 4,\n      contentType: 'list',\n      guidance: 'Define minimum coverage expectations. Capture linting or formatting requirements before merging.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Coverage Requirements**:\n- Minimum overall coverage: 80%\n- New code should have higher coverage\n- Critical paths require 100% coverage\n\n**Pre-merge Checks**:\n- [ ] All tests pass\n- [ ] Coverage thresholds met\n- [ ] Linting passes (\\`npm run lint\\`)\n- [ ] Type checking passes (\\`npm run typecheck\\`)\n- [ ] Build succeeds (\\`npm run build\\`)\n\n**CI Pipeline**:\n- Tests run automatically on every PR\n- Coverage reports generated and compared to baseline\n- Failed checks block merge`,\n    },\n    {\n      heading: 'Troubleshooting',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Document flaky suites, long-running tests, or environment quirks.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**Common Issues**:\n\n*Tests timing out*:\n- Increase timeout for slow operations\n- Check for unresolved promises\n- Verify mocks are properly configured\n\n*Flaky tests*:\n- Avoid time-dependent assertions\n- Use proper async/await patterns\n- Isolate tests from external state\n\n*Environment issues*:\n- Ensure Node version matches project requirements\n- Clear node_modules and reinstall if dependencies are corrupted\n- Check for conflicting global installations`,\n    },\n  ],\n  linkTo: ['development-workflow.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/tooling.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const toolingStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'tooling',\n  title: 'Tooling & Productivity Guide',\n  description: 'Scripts, IDE settings, automation, and developer productivity tips',\n  tone: 'instructional',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Tooling & Productivity Guide',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Collect the scripts, automation, and editor settings that keep contributors efficient.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This guide covers the tools, scripts, and configurations that make development efficient.\n\nFollowing these setup recommendations ensures a consistent development experience across the team.`,\n    },\n    {\n      heading: 'Required Tooling',\n      order: 2,\n      contentType: 'list',\n      guidance: 'List tools with installation instructions, version requirements, and what they power.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Runtime**:\n- Node.js (v18+ recommended)\n- npm / yarn / pnpm\n\n**Version Management** (recommended):\n- [nvm](https://github.com/nvm-sh/nvm) for Node.js version management\n- \\`.nvmrc\\` file specifies project Node version\n\n**Installation**:\n\\`\\`\\`bash\n# Using nvm (recommended)\nnvm install\nnvm use\n\n# Install dependencies\nnpm install\n\\`\\`\\``,\n    },\n    {\n      heading: 'Recommended Automation',\n      order: 3,\n      contentType: 'prose',\n      guidance: 'Document pre-commit hooks, linting/formatting commands, code generators, or scaffolding scripts. Include shortcuts or watch modes.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Pre-commit Hooks**:\nThe project uses [husky](https://typicode.github.io/husky/) for git hooks:\n- Pre-commit: Runs linting and type checking\n- Commit message: Validates commit message format\n\n**Code Quality Commands**:\n\\`\\`\\`bash\nnpm run lint          # Check code style\nnpm run lint:fix      # Auto-fix style issues\nnpm run format        # Format code with Prettier\nnpm run typecheck     # TypeScript type checking\n\\`\\`\\`\n\n**Watch Mode**:\n\\`\\`\\`bash\nnpm run dev           # Development with hot reload\nnpm run test:watch    # Tests in watch mode\n\\`\\`\\``,\n    },\n    {\n      heading: 'IDE / Editor Setup',\n      order: 4,\n      contentType: 'list',\n      guidance: 'List extensions or plugins that catch issues early. Share snippets, templates, or workspace settings.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**VS Code Recommended Extensions**:\n- ESLint — Inline linting\n- Prettier — Code formatting\n- TypeScript + JavaScript Language Features — IntelliSense\n- Error Lens — Inline error highlighting\n\n**Workspace Settings**:\nThe \\`.vscode/\\` folder contains shared settings:\n- \\`settings.json\\` — Editor configuration\n- \\`extensions.json\\` — Recommended extensions\n- \\`launch.json\\` — Debug configurations`,\n    },\n    {\n      heading: 'Productivity Tips',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Document terminal aliases, container workflows, or local emulators. Link to shared scripts or dotfiles.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**Useful Aliases**:\n\\`\\`\\`bash\nalias nr='npm run'\nalias nrd='npm run dev'\nalias nrt='npm run test'\n\\`\\`\\`\n\n**Quick Commands**:\n- \\`npm run build && npm run test\\` — Full verification before PR\n- \\`npm run clean\\` — Clear build artifacts and caches`,\n    },\n  ],\n  linkTo: ['development-workflow.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/troubleshooting.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const troubleshootingStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'troubleshooting',\n  title: 'Troubleshooting Guide',\n  description: 'Diagnostic procedures and solutions for common issues, designed for both developers and AI agents',\n  tone: 'technical',\n  audience: 'mixed',\n  sections: [\n    {\n      heading: 'Troubleshooting Guide',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Describe the purpose and agent protocol: run diagnostics, match symptoms to patterns, resolve if confident, escalate if uncertain.',\n      exampleContent: '**Agent Protocol:**\\n1. Run diagnostic script to gather facts\\n2. Match symptoms to known patterns\\n3. Execute resolution if confidence high (>80%)\\n4. Escalate to human if uncertain\\n5. Log all diagnostic steps',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Agent Diagnostic Script',\n      order: 2,\n      contentType: 'code-block',\n      guidance: 'Provide bash script for agents to diagnose system state including application status, errors, resources, services, and processes.',\n      exampleContent: 'Script that checks: health endpoint, recent errors, CPU/memory/disk usage, database/cache connectivity, recent changes, and processes',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Agent Decision Tree',\n      order: 3,\n      contentType: 'diagram',\n      guidance: 'ASCII or Mermaid diagram showing how agents classify issues based on diagnostic output (not responding, database unreachable, errors, performance).',\n      exampleContent: 'App responding? → DB reachable? → Error log entries? → Resource usage normal?',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Common Issues & Solutions',\n      order: 4,\n      contentType: 'prose',\n      guidance: 'Document common issues organized by category (startup, database, performance) with: error detection patterns, agent diagnostic procedures, auto-resolution scripts, and escalation criteria.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Application Won\\'t Start',\n      order: 5,\n      contentType: 'list',\n      guidance: 'List common startup issues (port in use, missing modules, missing environment variables) with bash commands for diagnosis and resolution.',\n      exampleContent: '- Port in use: use lsof to find process, kill it, restart app\\n- Missing module: clear node_modules and reinstall\\n- Missing env var: check .env.example and set required variables',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Performance Issues',\n      order: 6,\n      contentType: 'prose',\n      guidance: 'Document how to diagnose performance problems (high CPU, memory, slow queries) with monitoring commands and optimization steps.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Database Issues',\n      order: 7,\n      contentType: 'prose',\n      guidance: 'Document database connection diagnostics with connection checking procedures and startup attempts for containerized or local databases.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Escalation Criteria',\n      order: 8,\n      contentType: 'prose',\n      guidance: 'Define when agents must escalate: unknown patterns, data corruption risk, security issues, multiple failed attempts, or human approval needed.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Escalation Format',\n      order: 9,\n      contentType: 'code-block',\n      guidance: 'Provide template for structured escalation including issue description, symptoms, diagnostics run, attempted resolutions, and recommendations.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n  linkTo: ['development-workflow.md', 'security.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/documentation/workflow.ts",
    "content": "import { ScaffoldStructure } from '../types';\n\nexport const developmentWorkflowStructure: ScaffoldStructure = {\n  fileType: 'doc',\n  documentName: 'development-workflow',\n  title: 'Development Workflow',\n  description: 'Day-to-day engineering processes, branching, and contribution guidelines',\n  tone: 'instructional',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Development Workflow',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Outline the day-to-day engineering process for this repository.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `This document outlines the day-to-day engineering process for contributing to this repository.\n\nFollowing these guidelines ensures consistent code quality and smooth collaboration across the team.`,\n    },\n    {\n      heading: 'Branching & Releases',\n      order: 2,\n      contentType: 'list',\n      guidance: 'Describe the branching model (trunk-based, Git Flow, etc.). Note release cadence and tagging conventions.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Branching Model**: Feature branches off \\`main\\`\n\n- \\`main\\` — Production-ready code, always deployable\n- \\`feature/*\\` — New features and enhancements\n- \\`fix/*\\` — Bug fixes\n- \\`chore/*\\` — Maintenance and tooling updates\n\n**Release Process**:\n1. Features are developed in branches\n2. PRs require review and passing CI\n3. Merged PRs are deployed automatically (or tagged for release)\n\n**Versioning**: Semantic versioning (semver) - MAJOR.MINOR.PATCH`,\n    },\n    {\n      heading: 'Local Development',\n      order: 3,\n      contentType: 'list',\n      guidance: 'Commands to install dependencies, run locally, and build for distribution. Use code blocks for commands.',\n      exampleContent: '- Install: `npm install`\\n- Run: `npm run dev`\\n- Build: `npm run build`',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**Setup**:\n\\`\\`\\`bash\n# Clone and install\ngit clone <repository-url>\ncd <project-name>\nnpm install\n\\`\\`\\`\n\n**Daily Commands**:\n- \\`npm run dev\\` — Start development server/watch mode\n- \\`npm run build\\` — Build for production\n- \\`npm run test\\` — Run test suite\n- \\`npm run lint\\` — Check code style\n\n**Before Committing**:\n\\`\\`\\`bash\nnpm run lint && npm run test && npm run build\n\\`\\`\\``,\n    },\n    {\n      heading: 'Code Review Expectations',\n      order: 4,\n      contentType: 'prose',\n      guidance: 'Summarize review checklists and required approvals. Reference AGENTS.md for agent collaboration tips.',\n      required: true,\n      headingLevel: 2,\n      defaultContent: `**PR Requirements**:\n- Clear description of changes and motivation\n- Tests for new functionality\n- Documentation updates for API changes\n- Passing CI checks\n\n**Review Checklist**:\n- [ ] Code follows project conventions\n- [ ] Tests cover the changes adequately\n- [ ] No security vulnerabilities introduced\n- [ ] Documentation is updated\n- [ ] Commit messages follow conventions\n\n**Approval**: At least one approving review required before merge.\n\nSee [AGENTS.md](../../AGENTS.md) for AI assistant collaboration guidelines.`,\n    },\n    {\n      heading: 'Onboarding Tasks',\n      order: 5,\n      contentType: 'prose',\n      guidance: 'Point newcomers to first issues or starter tickets. Link to internal runbooks or dashboards.',\n      required: false,\n      headingLevel: 2,\n      defaultContent: `**First Steps for New Contributors**:\n1. Read the [Project Overview](./project-overview.md)\n2. Set up local development environment\n3. Run the test suite to verify setup\n4. Look for issues labeled \\`good-first-issue\\` or \\`help-wanted\\`\n\n**Helpful Resources**:\n- [Architecture Notes](./architecture.md) — System design overview\n- [Testing Strategy](./testing-strategy.md) — How to write tests\n- [CONTRIBUTING.md](../../CONTRIBUTING.md) — Contribution guidelines`,\n    },\n  ],\n  linkTo: ['testing-strategy.md', 'tooling.md'],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/index.ts",
    "content": "/**\n * Scaffold structures index\n *\n * This file serves as the main barrel export for all scaffold structures,\n * providing backward compatibility with the original scaffoldStructures.ts import path.\n */\n\n// Types\nexport * from './types';\n\n// Documentation structures\nexport * from './documentation';\n\n// Agent structures\nexport * from './agents';\n\n// Skill structures\nexport * from './skills';\n\n// Plan structures\nexport * from './plans';\n\n// Registry & utilities\nexport * from './registry';\nexport * from './validation';\nexport * from './serialization';\n"
  },
  {
    "path": "src/generators/shared/structures/plans/index.ts",
    "content": "/**\n * Plan structure exports\n */\n\nexport { planStructure } from './planStructure';\n"
  },
  {
    "path": "src/generators/shared/structures/plans/planStructure.ts",
    "content": "/**\n * Plan structure definition\n */\n\nimport { ScaffoldStructure } from '../types';\n\nexport const planStructure: ScaffoldStructure = {\n  fileType: 'plan',\n  documentName: 'implementation-plan',\n  title: 'Implementation Plan',\n  description: 'Detailed implementation plan for a feature or task',\n  tone: 'instructional',\n  audience: 'developers',\n  sections: [\n    {\n      heading: 'Overview',\n      order: 1,\n      contentType: 'prose',\n      guidance: 'Summarize the goal and scope of this implementation plan.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Codebase Context',\n      order: 2,\n      contentType: 'prose',\n      guidance: 'Describe the relevant parts of the codebase, architecture layers, and key components.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Implementation Phases',\n      order: 3,\n      contentType: 'list',\n      guidance: 'Break down implementation into phases mapped to PREVC (Planning, Review, Execution, Validation, Confirmation).',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Agent Assignments',\n      order: 4,\n      contentType: 'list',\n      guidance: 'List which agents are responsible for which phases.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Documentation Updates',\n      order: 5,\n      contentType: 'list',\n      guidance: 'List documentation files that need to be updated.',\n      required: false,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Risks & Mitigations',\n      order: 6,\n      contentType: 'list',\n      guidance: 'Identify potential risks and mitigation strategies.',\n      required: false,\n      headingLevel: 2,\n    },\n  ],\n};\n"
  },
  {
    "path": "src/generators/shared/structures/registry.ts",
    "content": "/**\n * Scaffold structures registry and lookup functions\n */\n\nimport { ScaffoldFileType } from '../../../types/scaffoldFrontmatter';\nimport { ScaffoldStructure } from './types';\nimport * as docStructures from './documentation';\nimport * as agentStructures from './agents';\nimport * as skillStructures from './skills';\nimport { planStructure } from './plans';\n\n/**\n * All scaffold structures indexed by document name\n */\nexport const SCAFFOLD_STRUCTURES: Record<string, ScaffoldStructure> = {\n  // Documentation\n  'project-overview': docStructures.projectOverviewStructure,\n  'architecture': docStructures.architectureStructure,\n  'development-workflow': docStructures.developmentWorkflowStructure,\n  'testing-strategy': docStructures.testingStrategyStructure,\n  'tooling': docStructures.toolingStructure,\n  'security': docStructures.securityStructure,\n  'glossary': docStructures.glossaryStructure,\n  'data-flow': docStructures.dataFlowStructure,\n  'onboarding': docStructures.onboardingStructure,\n  'api-reference': docStructures.apiReferenceStructure,\n  'migration': docStructures.migrationStructure,\n  'troubleshooting': docStructures.troubleshootingStructure,\n\n  // Agents\n  'code-reviewer': agentStructures.codeReviewerStructure,\n  'bug-fixer': agentStructures.bugFixerStructure,\n  'feature-developer': agentStructures.featureDeveloperStructure,\n  'refactoring-specialist': agentStructures.refactoringSpecialistStructure,\n  'test-writer': agentStructures.testWriterStructure,\n  'documentation-writer': agentStructures.documentationWriterStructure,\n  'performance-optimizer': agentStructures.performanceOptimizerStructure,\n  'security-auditor': agentStructures.securityAuditorStructure,\n  'backend-specialist': agentStructures.backendSpecialistStructure,\n  'frontend-specialist': agentStructures.frontendSpecialistStructure,\n  'architect-specialist': agentStructures.architectSpecialistStructure,\n  'devops-specialist': agentStructures.devopsSpecialistStructure,\n  'database-specialist': agentStructures.databaseSpecialistStructure,\n  'mobile-specialist': agentStructures.mobileSpecialistStructure,\n\n  // Skills\n  'commit-message': skillStructures.commitMessageSkillStructure,\n  'pr-review': skillStructures.prReviewSkillStructure,\n  'code-review': skillStructures.codeReviewSkillStructure,\n  'test-generation': skillStructures.testGenerationSkillStructure,\n  'documentation': skillStructures.documentationSkillStructure,\n  'refactoring': skillStructures.refactoringSkillStructure,\n  'bug-investigation': skillStructures.bugInvestigationSkillStructure,\n  'feature-breakdown': skillStructures.featureBreakdownSkillStructure,\n  'api-design': skillStructures.apiDesignSkillStructure,\n  'security-audit': skillStructures.securityAuditSkillStructure,\n\n  // Plans\n  'implementation-plan': planStructure,\n};\n\n/**\n * Get scaffold structure by name\n */\nexport function getScaffoldStructure(name: string): ScaffoldStructure | undefined {\n  return SCAFFOLD_STRUCTURES[name];\n}\n\n/**\n * Get all structures of a specific file type\n */\nexport function getStructuresByType(fileType: ScaffoldFileType): ScaffoldStructure[] {\n  return Object.values(SCAFFOLD_STRUCTURES).filter(s => s.fileType === fileType);\n}\n"
  },
  {
    "path": "src/generators/shared/structures/serialization.ts",
    "content": "/**\n * Serialization functions for scaffold structures\n */\n\nimport { ScaffoldStructure, ScaffoldSection } from './types';\n\n/**\n * Serialize a scaffold structure to markdown content with section headings,\n * guidance comments, and example content.\n * Used for CLI mode to provide useful templates for manual filling.\n */\nexport function serializeStructureAsMarkdown(structure: ScaffoldStructure): string {\n  const lines: string[] = [];\n\n  const sortedSections = [...structure.sections].sort((a, b) => a.order - b.order);\n\n  for (const section of sortedSections) {\n    lines.push(formatSectionAsMarkdown(section));\n    lines.push('');\n  }\n\n  // Add cross-references section if present\n  if (structure.linkTo && structure.linkTo.length > 0) {\n    lines.push('## Related Resources');\n    lines.push('');\n    lines.push('<!-- Link to related documents for cross-navigation. -->');\n    lines.push('');\n    for (const link of structure.linkTo) {\n      lines.push(`- [${link}](./${link})`);\n    }\n    lines.push('');\n  }\n\n  return lines.join('\\n');\n}\n\n/**\n * Format a single section as markdown with heading, guidance, and example/placeholder content.\n */\nfunction formatSectionAsMarkdown(section: ScaffoldSection): string {\n  const lines: string[] = [];\n\n  // Create heading with appropriate level\n  const headingLevel = section.headingLevel || 2;\n  const headingPrefix = '#'.repeat(headingLevel);\n  lines.push(`${headingPrefix} ${section.heading}`);\n  lines.push('');\n\n  // Prefer defaultContent (static useful content) over guidance + placeholder\n  if (section.defaultContent) {\n    lines.push(section.defaultContent);\n    return lines.join('\\n');\n  }\n\n  // Add guidance as HTML comment\n  lines.push(`<!-- ${section.guidance} -->`);\n  lines.push('');\n\n  // Add example content or placeholder based on content type\n  if (section.exampleContent) {\n    lines.push(section.exampleContent);\n  } else {\n    lines.push(getPlaceholderForContentType(section.contentType, section.required));\n  }\n\n  return lines.join('\\n');\n}\n\n/**\n * Get appropriate placeholder text based on content type.\n */\nfunction getPlaceholderForContentType(contentType: string, required: boolean): string {\n  const requiredNote = required ? '' : ' (optional)';\n\n  switch (contentType) {\n    case 'prose':\n      return `_Add descriptive content here${requiredNote}._`;\n    case 'list':\n      return `- _Item 1${requiredNote}_\\n- _Item 2_\\n- _Item 3_`;\n    case 'code-block':\n      return '```\\n// Add code example here\\n```';\n    case 'table':\n      return '| Column 1 | Column 2 | Column 3 |\\n|----------|----------|----------|\\n| _value_ | _value_ | _value_ |';\n    case 'checklist':\n      return `- [ ] _Task 1${requiredNote}_\\n- [ ] _Task 2_\\n- [ ] _Task 3_`;\n    case 'diagram':\n      return '```mermaid\\ngraph TD\\n    A[Start] --> B[End]\\n```';\n    default:\n      return `_Add content here${requiredNote}._`;\n  }\n}\n\n/**\n * Serialize a scaffold structure to a readable format for AI context\n */\nexport function serializeStructureForAI(structure: ScaffoldStructure): string {\n  const lines: string[] = [];\n\n  lines.push(`# Document Structure: ${structure.title}`);\n  lines.push('');\n  lines.push(`**Type:** ${structure.fileType}`);\n  lines.push(`**Tone:** ${structure.tone}`);\n  lines.push(`**Audience:** ${structure.audience}`);\n  lines.push(`**Description:** ${structure.description}`);\n\n  if (structure.additionalContext) {\n    lines.push(`**Additional Context:** ${structure.additionalContext}`);\n  }\n\n  lines.push('');\n  lines.push('## Required Sections');\n  lines.push('');\n\n  const sortedSections = [...structure.sections].sort((a, b) => a.order - b.order);\n\n  for (const section of sortedSections) {\n    const requiredLabel = section.required ? '(REQUIRED)' : '(optional)';\n    const level = section.headingLevel || 2;\n    const headingPrefix = '#'.repeat(level);\n\n    lines.push(`### ${section.order}. ${headingPrefix} ${section.heading} ${requiredLabel}`);\n    lines.push(`- **Content Type:** ${section.contentType}`);\n    lines.push(`- **Guidance:** ${section.guidance}`);\n\n    if (section.exampleContent) {\n      lines.push('- **Example:**');\n      lines.push('```');\n      lines.push(section.exampleContent);\n      lines.push('```');\n    }\n\n    lines.push('');\n  }\n\n  if (structure.linkTo && structure.linkTo.length > 0) {\n    lines.push('## Cross-References');\n    lines.push('Link to these related documents where appropriate:');\n    for (const link of structure.linkTo) {\n      lines.push(`- ${link}`);\n    }\n  }\n\n  return lines.join('\\n');\n}\n"
  },
  {
    "path": "src/generators/shared/structures/skills/definitions.ts",
    "content": "/**\n * Skill structure definitions with static default content\n */\n\nimport { createSkillStructure, SkillDefaultContent } from './factory';\n\n// ============================================================================\n// Default Content Definitions\n// ============================================================================\n\nexport const commitMessageContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Creating git commits after code changes\n- Writing commit messages for staged changes\n- Following conventional commit format for the project`,\n\n  instructions: `1. Review the staged changes using \\`git diff --staged\\`\n2. Identify the type of change (feat, fix, docs, style, refactor, test, chore)\n3. Determine the scope (component, module, or area affected)\n4. Write a concise subject line (50 chars max, imperative mood)\n5. Add body if needed to explain \"why\" not \"what\"\n6. Reference issue numbers if applicable`,\n\n  examples: `**Feature commit:**\n\\`\\`\\`\nfeat(auth): add password reset functionality\n\nImplement forgot password flow with email verification.\nUsers can now reset their password via email link.\n\nCloses #123\n\\`\\`\\`\n\n**Bug fix commit:**\n\\`\\`\\`\nfix(api): handle null response in user lookup\n\nPreviously threw TypeError when user not found.\nNow returns 404 with appropriate error message.\n\nFixes #456\n\\`\\`\\``,\n\n  guidelines: `- Use imperative mood: \"add\" not \"added\" or \"adds\"\n- Keep subject line under 50 characters\n- Separate subject from body with blank line\n- Use body to explain why, not what (code shows what)\n- Reference issues with \"Closes #X\" or \"Fixes #X\"\n- One logical change per commit\n- Don't end subject line with period`,\n};\n\nexport const prReviewContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Reviewing a pull request before merge\n- Providing feedback on proposed changes\n- Validating PR meets project standards`,\n\n  instructions: `1. Read the PR description to understand the goal\n2. Review the linked issue(s) for context\n3. Check that tests are included and passing\n4. Review code changes file by file\n5. Verify documentation is updated if needed\n6. Leave constructive feedback with specific suggestions\n7. Approve, request changes, or comment based on findings`,\n\n  examples: `**Approval comment:**\n\\`\\`\\`\nLooks good! Clean implementation with comprehensive tests.\n\nMinor suggestion: Consider extracting the validation logic\nin \\`UserService.ts:45\\` into a separate function for reusability.\n\nApproved ✅\n\\`\\`\\`\n\n**Request changes:**\n\\`\\`\\`\nGood progress, but a few items need attention:\n\n1. Missing test for error handling in \\`fetchUser()\\`\n2. The new endpoint needs documentation in the API docs\n3. Consider adding input validation for the email field\n\nPlease address these and I'll re-review.\n\\`\\`\\``,\n\n  guidelines: `- Start with understanding the PR's goal\n- Be constructive and specific in feedback\n- Distinguish between required changes and suggestions\n- Test the changes locally if complex\n- Check for security implications\n- Verify backward compatibility\n- Approve only when confident in the changes`,\n};\n\nexport const codeReviewContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Reviewing code changes for quality\n- Checking adherence to coding standards\n- Identifying potential bugs or issues`,\n\n  instructions: `1. Understand the context and purpose of the code\n2. Check for correctness and logic errors\n3. Evaluate code structure and organization\n4. Look for potential performance issues\n5. Check for security vulnerabilities\n6. Verify error handling is appropriate\n7. Assess readability and maintainability`,\n\n  examples: `**Code quality feedback:**\n\\`\\`\\`\n// Before: Nested callbacks\nfetchUser(id, (user) => {\n  fetchPosts(user.id, (posts) => {\n    render(posts);\n  });\n});\n\n// Suggestion: Use async/await\nconst user = await fetchUser(id);\nconst posts = await fetchPosts(user.id);\nrender(posts);\n\\`\\`\\`\n\n**Security feedback:**\n\\`\\`\\`\n// Issue: SQL injection vulnerability\nconst query = \\`SELECT * FROM users WHERE id = \\${userId}\\`;\n\n// Fix: Use parameterized query\nconst query = 'SELECT * FROM users WHERE id = ?';\ndb.query(query, [userId]);\n\\`\\`\\``,\n\n  guidelines: `- Focus on the most impactful issues first\n- Explain why something is a problem\n- Provide concrete suggestions for improvement\n- Consider the developer's experience level\n- Balance thoroughness with pragmatism\n- Praise good patterns when you see them`,\n};\n\nexport const testGenerationContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Writing tests for new functionality\n- Adding tests for bug fixes (regression tests)\n- Improving test coverage for existing code`,\n\n  instructions: `1. Identify the function/component to test\n2. List the behaviors that need testing\n3. Write tests for happy path scenarios\n4. Add tests for edge cases and boundaries\n5. Include error handling tests\n6. Mock external dependencies appropriately\n7. Verify tests are deterministic and isolated`,\n\n  examples: `**Unit test example:**\n\\`\\`\\`typescript\ndescribe('calculateTotal', () => {\n  it('should sum item prices correctly', () => {\n    const items = [{ price: 10 }, { price: 20 }];\n    expect(calculateTotal(items)).toBe(30);\n  });\n\n  it('should return 0 for empty array', () => {\n    expect(calculateTotal([])).toBe(0);\n  });\n\n  it('should handle negative prices', () => {\n    const items = [{ price: 10 }, { price: -5 }];\n    expect(calculateTotal(items)).toBe(5);\n  });\n});\n\\`\\`\\``,\n\n  guidelines: `- Test behavior, not implementation\n- Use descriptive test names that explain what and why\n- Follow Arrange-Act-Assert pattern\n- Keep tests independent and isolated\n- Don't test external libraries\n- Mock at the boundary, not everywhere\n- Aim for fast, reliable tests`,\n};\n\nexport const documentationContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Documenting new features or APIs\n- Updating docs for code changes\n- Creating README or getting started guides`,\n\n  instructions: `1. Identify the target audience\n2. Determine what needs to be documented\n3. Write clear, concise explanations\n4. Include working code examples\n5. Add any necessary diagrams or visuals\n6. Review for clarity and completeness\n7. Verify examples work with current code`,\n\n  examples: `**Function documentation:**\n\\`\\`\\`typescript\n/**\n * Calculates the total price of items in a cart.\n *\n * @param items - Array of cart items with price property\n * @returns The sum of all item prices\n * @throws {Error} If items is not an array\n *\n * @example\n * const total = calculateTotal([{ price: 10 }, { price: 20 }]);\n * // Returns: 30\n */\nfunction calculateTotal(items: CartItem[]): number\n\\`\\`\\``,\n\n  guidelines: `- Write for your audience's knowledge level\n- Lead with the most important information\n- Include working, copy-pasteable examples\n- Keep documentation close to the code\n- Update docs in the same PR as code changes\n- Use consistent formatting and terminology`,\n};\n\nexport const refactoringContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Improving code structure without changing behavior\n- Reducing code duplication\n- Simplifying complex logic`,\n\n  instructions: `1. Ensure adequate test coverage exists\n2. Identify the specific improvement to make\n3. Make one type of change at a time\n4. Run tests after each change\n5. Commit frequently with clear messages\n6. Verify no behavior changes occurred`,\n\n  examples: `**Extract function:**\n\\`\\`\\`typescript\n// Before: Inline validation logic\nif (email && email.includes('@') && email.length > 5) {\n  // process email\n}\n\n// After: Extracted to function\nfunction isValidEmail(email: string): boolean {\n  return email && email.includes('@') && email.length > 5;\n}\n\nif (isValidEmail(email)) {\n  // process email\n}\n\\`\\`\\``,\n\n  guidelines: `- Never refactor without tests\n- Small steps, frequent commits\n- One refactoring type per commit\n- If tests break, you changed behavior\n- Use IDE refactoring tools when available\n- Keep the PR focused and reviewable`,\n};\n\nexport const bugInvestigationContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Investigating reported bugs\n- Diagnosing unexpected behavior\n- Finding the root cause of issues`,\n\n  instructions: `1. Reproduce the bug consistently\n2. Gather information (logs, stack traces, steps)\n3. Identify when the bug was introduced (git bisect)\n4. Form a hypothesis about the cause\n5. Verify hypothesis with debugging\n6. Document the root cause\n7. Plan the fix approach`,\n\n  examples: `**Bug investigation notes:**\n\\`\\`\\`\n## Bug: User profile fails to load\n\n### Reproduction:\n1. Log in as any user\n2. Navigate to /profile\n3. Error: \"Cannot read property 'name' of undefined\"\n\n### Investigation:\n- Stack trace points to ProfilePage.tsx:25\n- API returns 200 but empty body when session expired\n- Bug introduced in commit abc123 (session refactor)\n\n### Root cause:\nSession middleware not checking token expiration correctly.\nReturns empty response instead of 401.\n\n### Fix approach:\nUpdate session middleware to return 401 when token expired.\n\\`\\`\\``,\n\n  guidelines: `- Always reproduce before investigating\n- Check recent changes that might relate\n- Use debugger and logging strategically\n- Document your findings\n- Consider if bug exists elsewhere\n- Write a regression test with the fix`,\n};\n\nexport const featureBreakdownContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Planning new feature implementation\n- Breaking large tasks into smaller pieces\n- Creating implementation roadmap`,\n\n  instructions: `1. Understand the full feature requirements\n2. Identify the main components needed\n3. Break into independent, testable tasks\n4. Identify dependencies between tasks\n5. Order tasks by dependency and priority\n6. Add acceptance criteria to each task\n7. Flag any unknowns or risks`,\n\n  examples: `**Feature breakdown example:**\n\\`\\`\\`\n## Feature: User Authentication\n\n### Task 1: Database schema\n- Add users table with email, password_hash, created_at\n- Add sessions table with user_id, token, expires_at\n- Acceptance: Migrations run successfully\n\n### Task 2: Registration endpoint\n- POST /api/auth/register\n- Validate email format and password strength\n- Hash password before storing\n- Acceptance: Can create user, returns 201\n\n### Task 3: Login endpoint\n- POST /api/auth/login\n- Verify credentials, create session\n- Return JWT token\n- Acceptance: Can login, receive valid token\n\n### Dependencies:\nTask 2 requires Task 1\nTask 3 requires Task 1\n\\`\\`\\``,\n\n  guidelines: `- Each task should be independently testable\n- Tasks should be small enough to complete in a day\n- Clearly state acceptance criteria\n- Identify and document dependencies\n- Flag technical risks or unknowns early\n- Consider parallel work opportunities`,\n};\n\nexport const apiDesignContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Designing new API endpoints\n- Restructuring existing APIs\n- Planning API versioning strategy`,\n\n  instructions: `1. Define the resources and their relationships\n2. Choose appropriate HTTP methods\n3. Design URL structure following REST conventions\n4. Define request/response schemas\n5. Plan error handling and status codes\n6. Consider pagination, filtering, sorting\n7. Document the API specification`,\n\n  examples: `**RESTful API design:**\n\\`\\`\\`\n# Users API\n\nGET    /api/v1/users          # List users (paginated)\nPOST   /api/v1/users          # Create user\nGET    /api/v1/users/:id      # Get user by ID\nPUT    /api/v1/users/:id      # Update user\nDELETE /api/v1/users/:id      # Delete user\n\n# Nested resources\nGET    /api/v1/users/:id/posts    # Get user's posts\n\n# Response format\n{\n  \"data\": { ... },\n  \"meta\": { \"page\": 1, \"total\": 100 }\n}\n\n# Error format\n{\n  \"error\": {\n    \"code\": \"VALIDATION_ERROR\",\n    \"message\": \"Email is required\",\n    \"details\": [...]\n  }\n}\n\\`\\`\\``,\n\n  guidelines: `- Use nouns for resources, not verbs\n- Use proper HTTP methods and status codes\n- Version your API from the start\n- Be consistent in naming and structure\n- Provide clear error messages\n- Document all endpoints\n- Consider rate limiting and caching`,\n};\n\nexport const securityAuditContent: SkillDefaultContent = {\n  whenToUse: `Use this skill when:\n- Reviewing code for security vulnerabilities\n- Assessing authentication/authorization\n- Checking for OWASP top 10 issues`,\n\n  instructions: `1. Review authentication implementation\n2. Check authorization on all endpoints\n3. Look for injection vulnerabilities\n4. Verify input validation and sanitization\n5. Check for sensitive data exposure\n6. Review dependency security\n7. Document findings with severity`,\n\n  examples: `**Security audit findings:**\n\\`\\`\\`\n## Security Audit Report\n\n### Critical\n1. SQL Injection in UserController.ts:45\n   - Query constructed with string concatenation\n   - Fix: Use parameterized queries\n\n### High\n2. Missing authentication on /api/admin/*\n   - Admin routes accessible without auth\n   - Fix: Add auth middleware\n\n### Medium\n3. Sensitive data in logs\n   - Passwords logged in debug mode\n   - Fix: Sanitize logs, remove sensitive fields\n\n### Recommendations\n- Enable security headers (HSTS, CSP)\n- Implement rate limiting\n- Add input validation middleware\n\\`\\`\\``,\n\n  guidelines: `- Check OWASP top 10 vulnerabilities\n- Never trust user input\n- Review authentication carefully\n- Verify authorization on all routes\n- Check for sensitive data exposure\n- Scan dependencies for known vulnerabilities\n- Document findings with clear severity levels`,\n};\n\n// ============================================================================\n// Skill Structure Exports\n// ============================================================================\n\nexport const commitMessageSkillStructure = createSkillStructure(\n  'commit-message',\n  'Commit Message',\n  'Generates conventional commit messages following project conventions',\n  'Focus on conventional commits format, clear descriptions, and linking to issues.',\n  commitMessageContent\n);\n\nexport const prReviewSkillStructure = createSkillStructure(\n  'pr-review',\n  'PR Review',\n  'Reviews pull requests for quality, completeness, and adherence to standards',\n  'Focus on code quality, test coverage, documentation, and potential issues.',\n  prReviewContent\n);\n\nexport const codeReviewSkillStructure = createSkillStructure(\n  'code-review',\n  'Code Review',\n  'Reviews code changes for quality and best practices',\n  'Focus on maintainability, performance, security, and style consistency.',\n  codeReviewContent\n);\n\nexport const testGenerationSkillStructure = createSkillStructure(\n  'test-generation',\n  'Test Generation',\n  'Generates comprehensive tests for code',\n  'Focus on unit tests, edge cases, mocking strategies, and test organization.',\n  testGenerationContent\n);\n\nexport const documentationSkillStructure = createSkillStructure(\n  'documentation',\n  'Documentation',\n  'Creates and updates documentation',\n  'Focus on clarity, examples, API documentation, and keeping docs current.',\n  documentationContent\n);\n\nexport const refactoringSkillStructure = createSkillStructure(\n  'refactoring',\n  'Refactoring',\n  'Refactors code to improve structure and maintainability',\n  'Focus on small incremental changes, test coverage, and preserving behavior.',\n  refactoringContent\n);\n\nexport const bugInvestigationSkillStructure = createSkillStructure(\n  'bug-investigation',\n  'Bug Investigation',\n  'Investigates and diagnoses bugs',\n  'Focus on reproduction, root cause analysis, and fix verification.',\n  bugInvestigationContent\n);\n\nexport const featureBreakdownSkillStructure = createSkillStructure(\n  'feature-breakdown',\n  'Feature Breakdown',\n  'Breaks down features into implementable tasks',\n  'Focus on clear requirements, dependencies, and estimation.',\n  featureBreakdownContent\n);\n\nexport const apiDesignSkillStructure = createSkillStructure(\n  'api-design',\n  'API Design',\n  'Designs APIs following best practices',\n  'Focus on RESTful design, versioning, error handling, and documentation.',\n  apiDesignContent\n);\n\nexport const securityAuditSkillStructure = createSkillStructure(\n  'security-audit',\n  'Security Audit',\n  'Audits code for security vulnerabilities',\n  'Focus on OWASP top 10, input validation, authentication, and authorization.',\n  securityAuditContent\n);\n"
  },
  {
    "path": "src/generators/shared/structures/skills/factory.ts",
    "content": "/**\n * Factory function for creating skill structures\n */\n\nimport { ScaffoldStructure, ScaffoldSection } from '../types';\n\n/**\n * Default content overrides for specific sections\n */\nexport interface SkillDefaultContent {\n  whenToUse?: string;\n  instructions?: string;\n  examples?: string;\n  guidelines?: string;\n}\n\n/**\n * Map of section headings to content keys\n */\nconst SECTION_KEY_MAP: Record<string, keyof SkillDefaultContent> = {\n  Workflow: 'instructions',\n  'Examples': 'examples',\n  'Quality Bar': 'guidelines',\n};\n\nconst RESOURCE_STRATEGY_DEFAULT = `- Add \\`scripts/\\` only when the task is fragile, repetitive, or benefits from deterministic execution.\n- Add \\`references/\\` only when details are too large or too variant-specific to keep in \\`SKILL.md\\`.\n- Add \\`assets/\\` only for files that will be consumed in the final output.\n- Keep extra docs out of the skill folder; prefer \\`SKILL.md\\` plus only the resources that materially help.`;\n\n/**\n * Create a skill structure with standard sections\n */\nexport function createSkillStructure(\n  skillSlug: string,\n  title: string,\n  description: string,\n  additionalContext?: string,\n  defaultContent?: SkillDefaultContent\n): ScaffoldStructure {\n  const sections: ScaffoldSection[] = [\n    {\n      heading: 'Workflow',\n      order: 1,\n      contentType: 'list',\n      guidance: `Write the minimum reliable procedure for ${skillSlug}. Use imperative steps, stay concise, and optimize for repeated execution.`,\n      exampleContent: '1. Inspect the task inputs, constraints, and desired output.\\n2. Execute the smallest reliable sequence.\\n3. Validate the result against real files, commands, or behavior.\\n4. Capture reusable helpers only if they reduce future repetition.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Examples',\n      order: 2,\n      contentType: 'code-block',\n      guidance: 'Provide short, concrete examples. Prefer one or two examples that show the expected input shape and outcome.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Quality Bar',\n      order: 3,\n      contentType: 'list',\n      guidance: 'List the checks, constraints, and non-obvious heuristics that keep this skill reliable.',\n      required: true,\n      headingLevel: 2,\n    },\n    {\n      heading: 'Resource Strategy',\n      order: 4,\n      contentType: 'list',\n      guidance: `Explain when ${skillSlug} should add helper resources. Prefer progressive disclosure and avoid unnecessary files.`,\n      required: true,\n      headingLevel: 2,\n      defaultContent: RESOURCE_STRATEGY_DEFAULT,\n    },\n  ];\n\n  // Apply default content to sections\n  if (defaultContent) {\n    for (const section of sections) {\n      const key = SECTION_KEY_MAP[section.heading];\n      if (key && defaultContent[key]) {\n        section.defaultContent = defaultContent[key];\n      }\n    }\n  }\n\n  return {\n    fileType: 'skill',\n    documentName: skillSlug,\n    title,\n    description,\n    tone: 'instructional',\n    audience: 'ai-agents',\n    sections,\n    additionalContext,\n  };\n}\n"
  },
  {
    "path": "src/generators/shared/structures/skills/index.ts",
    "content": "/**\n * Skill structure exports\n */\n\nexport { createSkillStructure } from './factory';\nexport {\n  commitMessageSkillStructure,\n  prReviewSkillStructure,\n  codeReviewSkillStructure,\n  testGenerationSkillStructure,\n  documentationSkillStructure,\n  refactoringSkillStructure,\n  bugInvestigationSkillStructure,\n  featureBreakdownSkillStructure,\n  apiDesignSkillStructure,\n  securityAuditSkillStructure,\n  commitMessageContent,\n  prReviewContent,\n  codeReviewContent,\n  testGenerationContent,\n  documentationContent,\n  refactoringContent,\n  bugInvestigationContent,\n  featureBreakdownContent,\n  apiDesignContent,\n  securityAuditContent,\n} from './definitions';\n"
  },
  {
    "path": "src/generators/shared/structures/types.ts",
    "content": "/**\n * Type definitions for scaffold structures\n */\n\nimport { ScaffoldFileType } from '../../../types/scaffoldFrontmatter';\n\n/**\n * Content type for a section\n */\nexport type ScaffoldContentType =\n  | 'prose'        // Paragraph text\n  | 'list'         // Bullet/numbered list\n  | 'code-block'   // Code snippet\n  | 'table'        // Markdown table\n  | 'checklist'    // Task list with checkboxes\n  | 'diagram';     // Mermaid or ASCII diagram\n\n/**\n * Tone for document generation\n */\nexport type ScaffoldTone =\n  | 'technical'        // Precise, detailed technical language\n  | 'conversational'   // Friendly, accessible\n  | 'formal'           // Professional, structured\n  | 'instructional';   // Step-by-step, directive\n\n/**\n * Target audience\n */\nexport type ScaffoldAudience =\n  | 'developers'   // Software engineers working on the codebase\n  | 'ai-agents'    // AI assistants using the playbooks\n  | 'architects'   // Technical leads and architects\n  | 'mixed';       // Multiple audiences\n\n/**\n * A section within a scaffold structure\n */\nexport interface ScaffoldSection {\n  /** Section heading (H2 or H3) */\n  heading: string;\n  /** Display order (1-based) */\n  order: number;\n  /** What type of content this section should contain */\n  contentType: ScaffoldContentType;\n  /** Instructions for AI on what to include */\n  guidance: string;\n  /** Optional example of expected content */\n  exampleContent?: string;\n  /** Whether this section is required */\n  required: boolean;\n  /** Heading level (2 = H2, 3 = H3) */\n  headingLevel?: 2 | 3;\n  /** Static default content when not autoFilled. Provides useful template content that works for any project. */\n  defaultContent?: string;\n}\n\n/**\n * Complete scaffold structure definition\n */\nexport interface ScaffoldStructure {\n  /** File type */\n  fileType: ScaffoldFileType;\n  /** Document identifier (e.g., 'project-overview', 'code-reviewer') */\n  documentName: string;\n  /** Display title for the document */\n  title: string;\n  /** Brief description of document purpose */\n  description: string;\n  /** Writing tone */\n  tone: ScaffoldTone;\n  /** Target audience */\n  audience: ScaffoldAudience;\n  /** Ordered sections */\n  sections: ScaffoldSection[];\n  /** Related documents to cross-link */\n  linkTo?: string[];\n  /** Additional context for AI generation */\n  additionalContext?: string;\n}\n"
  },
  {
    "path": "src/generators/shared/structures/validation.ts",
    "content": "/**\n * Validation functions for scaffold structures\n */\n\nimport { ScaffoldStructure } from './types';\nimport { SCAFFOLD_STRUCTURES } from './registry';\n\n/**\n * Validate that a structure is well-formed\n */\nexport function validateStructure(structure: ScaffoldStructure): string[] {\n  const errors: string[] = [];\n\n  if (!structure.documentName) {\n    errors.push('Missing documentName');\n  }\n\n  if (!structure.title) {\n    errors.push('Missing title');\n  }\n\n  if (!structure.sections || structure.sections.length === 0) {\n    errors.push('No sections defined');\n  }\n\n  const orders = new Set<number>();\n  for (const section of structure.sections) {\n    if (!section.heading) {\n      errors.push(`Section ${section.order} missing heading`);\n    }\n    if (!section.guidance) {\n      errors.push(`Section \"${section.heading}\" missing guidance`);\n    }\n    if (orders.has(section.order)) {\n      errors.push(`Duplicate order ${section.order}`);\n    }\n    orders.add(section.order);\n  }\n\n  return errors;\n}\n\n/**\n * Validate all structures in the registry\n */\nexport function validateAllStructures(): Map<string, string[]> {\n  const results = new Map<string, string[]>();\n\n  for (const [name, structure] of Object.entries(SCAFFOLD_STRUCTURES)) {\n    const errors = validateStructure(structure);\n    if (errors.length > 0) {\n      results.set(name, errors);\n    }\n  }\n\n  return results;\n}\n"
  },
  {
    "path": "src/generators/skills/index.ts",
    "content": "/**\n * Skills Generator Module\n */\n\nexport {\n  SkillGenerator,\n  SkillGeneratorOptions,\n  SkillGeneratorResult,\n  createSkillGenerator,\n} from './skillGenerator';\n\nexport { generateSkillContent, getDefaultPhases } from './templates/skillTemplate';\n\nexport { generateSkillsIndex } from './templates/indexTemplate';\n"
  },
  {
    "path": "src/generators/skills/skillGenerator.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { SkillGenerator } from './skillGenerator';\n\ndescribe('SkillGenerator', () => {\n  let tempDir: string;\n  let repoPath: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-skills-'));\n    repoPath = path.join(tempDir, 'repo');\n    await fs.ensureDir(repoPath);\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  it('generates scaffolded built-in skills with useful starter content', async () => {\n    const generator = new SkillGenerator({ repoPath });\n\n    const result = await generator.generate({ skills: ['commit-message'] });\n\n    expect(result.generatedSkills).toEqual(['commit-message']);\n\n    const skillPath = path.join(repoPath, '.context', 'skills', 'commit-message', 'SKILL.md');\n    const content = await fs.readFile(skillPath, 'utf-8');\n\n    expect(content).toContain('type: skill');\n    expect(content).toContain('skillSlug: commit-message');\n    expect(content).toContain('status: unfilled');\n    expect(content).toContain('## Workflow');\n    expect(content).toContain('## Examples');\n    expect(content).toContain('## Quality Bar');\n    expect(content).toContain('## Resource Strategy');\n    expect(content).not.toContain('## When to Use');\n  });\n\n  it('generates custom skills with the generic starter structure', async () => {\n    const generator = new SkillGenerator({ repoPath });\n\n    const skillPath = await generator.generateCustomSkill({\n      name: 'release-notes',\n      description: 'Create release notes for the repository',\n    });\n\n    const content = await fs.readFile(skillPath, 'utf-8');\n\n    expect(content).toContain('type: skill');\n    expect(content).toContain('skillSlug: release-notes');\n    expect(content).toContain('## Workflow');\n    expect(content).toContain('## Resource Strategy');\n    expect(content).toContain('Avoid extra docs such as `README.md` or `CHANGELOG.md` inside the skill folder.');\n  });\n});\n"
  },
  {
    "path": "src/generators/skills/skillGenerator.ts",
    "content": "/**\n * Skill Generator\n *\n * Scaffolds skill directories and SKILL.md files.\n * Generates scaffold v2 files with fillable frontmatter plus useful starter content.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n  BUILT_IN_SKILLS,\n  BuiltInSkillType,\n  isBuiltInSkill,\n  createSkillRegistry,\n  getBuiltInSkillTemplates,\n  SKILL_TO_PHASES,\n} from '../../workflow/skills';\nimport { getDefaultPhases } from './templates/skillTemplate';\nimport { generateSkillsIndex } from './templates/indexTemplate';\nimport { PrevcPhase } from '../../workflow/types';\nimport {\n  createSkillFrontmatter,\n  serializeFrontmatter,\n} from '../../types/scaffoldFrontmatter';\nimport {\n  getScaffoldStructure,\n  serializeStructureAsMarkdown,\n} from '../shared/scaffoldStructures';\n\nexport interface SkillGeneratorOptions {\n  /** Repository path */\n  repoPath: string;\n  /** Output directory (default: .context) */\n  outputDir?: string;\n  /** Skills to generate (default: all built-in) */\n  skills?: string[];\n  /** Force overwrite existing files */\n  force?: boolean;\n}\n\nexport interface SkillGeneratorResult {\n  skillsDir: string;\n  generatedSkills: string[];\n  skippedSkills: string[];\n  indexPath: string;\n}\n\nexport class SkillGenerator {\n  private readonly repoPath: string;\n  private readonly outputDir: string;\n  private readonly skillsDir: string;\n\n  constructor(options: SkillGeneratorOptions) {\n    this.repoPath = options.repoPath;\n    this.outputDir = options.outputDir || '.context';\n    this.skillsDir = path.join(this.repoPath, this.outputDir, 'skills');\n  }\n\n  /**\n   * Generate skills directory with selected skills\n   */\n  async generate(options: {\n    skills?: string[];\n    force?: boolean;\n  } = {}): Promise<SkillGeneratorResult> {\n    const { skills = [...BUILT_IN_SKILLS], force = false } = options;\n\n    // Create skills directory\n    fs.mkdirSync(this.skillsDir, { recursive: true });\n\n    const generatedSkills: string[] = [];\n    const skippedSkills: string[] = [];\n\n    const templates = getBuiltInSkillTemplates();\n\n    for (const skillName of skills) {\n      const skillDir = path.join(this.skillsDir, skillName);\n      const skillPath = path.join(skillDir, 'SKILL.md');\n\n      // Check if skill already exists\n      if (fs.existsSync(skillPath) && !force) {\n        skippedSkills.push(skillName);\n        continue;\n      }\n\n      // Create skill directory\n      fs.mkdirSync(skillDir, { recursive: true });\n\n      // Generate scaffold content with internal frontmatter plus starter body\n      let description: string;\n      let phases: PrevcPhase[];\n\n      if (isBuiltInSkill(skillName)) {\n        // Use built-in template description\n        const template = templates[skillName as BuiltInSkillType];\n        description = template.description;\n        phases = SKILL_TO_PHASES[skillName as BuiltInSkillType];\n      } else {\n        // Default description for custom skill\n        description = `On-demand expertise for ${skillName}`;\n        phases = getDefaultPhases(skillName);\n      }\n\n      // Create scaffold content\n      const frontmatter = createSkillFrontmatter(\n        this.formatSkillTitle(skillName),\n        description,\n        skillName,\n        { phases }\n      );\n      const content = serializeFrontmatter(frontmatter) + '\\n' + this.buildSkillBody(skillName);\n\n      // Write SKILL.md\n      fs.writeFileSync(skillPath, content, 'utf-8');\n      generatedSkills.push(skillName);\n    }\n\n    // Generate index README\n    const indexPath = await this.generateIndex();\n\n    return {\n      skillsDir: this.skillsDir,\n      generatedSkills,\n      skippedSkills,\n      indexPath,\n    };\n  }\n\n  /**\n   * Format skill name as display title\n   */\n  private formatSkillTitle(skillName: string): string {\n    return skillName\n      .split('-')\n      .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n      .join(' ');\n  }\n\n  private buildSkillBody(skillName: string): string {\n    const structure = getScaffoldStructure(skillName);\n    if (structure) {\n      return serializeStructureAsMarkdown(structure);\n    }\n\n    return [\n      '## Workflow',\n      '',\n      `<!-- Write the minimum reliable procedure for ${skillName}. Use imperative steps, stay concise, and focus on what another Codex instance would not infer on its own. -->`,\n      '',\n      '1. Inspect the task inputs, constraints, and desired outcome.',\n      '2. Execute the smallest reliable sequence for the task.',\n      '3. Validate the result against real files, commands, or behavior.',\n      '4. Add reusable helpers only if they reduce future repetition.',\n      '',\n      '## Examples',\n      '',\n      `<!-- Add one or two short examples that show when ${skillName} should be used and what good output looks like. -->`,\n      '',\n      '```text',\n      `User: Use the ${skillName} skill to handle this task.`,\n      'Assistant: Follow the workflow, apply repository-specific rules, and produce the requested output.',\n      '```',\n      '',\n      '## Quality Bar',\n      '',\n      '- Keep the skill concise; include only non-obvious guidance.',\n      '- Put activation triggers in the description frontmatter, not in the body.',\n      '- Use imperative instructions and short examples.',\n      '- Prefer progressive disclosure over large inline explanations.',\n      '',\n      '## Resource Strategy',\n      '',\n      '- Add `scripts/` only for deterministic or repetitive work.',\n      '- Add `references/` only for large details that should be loaded on demand.',\n      '- Add `assets/` only for files consumed by the final output.',\n      '- Avoid extra docs such as `README.md` or `CHANGELOG.md` inside the skill folder.',\n      '',\n    ].join('\\n');\n  }\n\n  /**\n   * Generate a single custom skill\n   */\n  async generateCustomSkill(options: {\n    name: string;\n    description: string;\n    phases?: PrevcPhase[];\n    force?: boolean;\n  }): Promise<string> {\n    const { name, description, phases = ['E'], force = false } = options;\n\n    const skillDir = path.join(this.skillsDir, name);\n    const skillPath = path.join(skillDir, 'SKILL.md');\n\n    if (fs.existsSync(skillPath) && !force) {\n      throw new Error(`Skill ${name} already exists. Use --force to overwrite.`);\n    }\n\n    // Create skill directory\n    fs.mkdirSync(skillDir, { recursive: true });\n\n    // Generate scaffold content with internal frontmatter plus starter body\n    const frontmatter = createSkillFrontmatter(\n      this.formatSkillTitle(name),\n      description,\n      name,\n      { phases }\n    );\n    const content = serializeFrontmatter(frontmatter) + '\\n' + this.buildSkillBody(name);\n\n    // Write SKILL.md\n    fs.writeFileSync(skillPath, content, 'utf-8');\n\n    // Update index\n    await this.generateIndex();\n\n    return skillPath;\n  }\n\n  /**\n   * Generate README.md index\n   */\n  async generateIndex(): Promise<string> {\n    const registry = createSkillRegistry(this.repoPath);\n    const discovered = await registry.discoverAll();\n\n    const content = generateSkillsIndex({\n      skills: discovered.all,\n      projectName: path.basename(this.repoPath),\n    });\n\n    const indexPath = path.join(this.skillsDir, 'README.md');\n    fs.writeFileSync(indexPath, content, 'utf-8');\n\n    return indexPath;\n  }\n\n}\n\n/**\n * Factory function\n */\nexport function createSkillGenerator(options: SkillGeneratorOptions): SkillGenerator {\n  return new SkillGenerator(options);\n}\n"
  },
  {
    "path": "src/generators/skills/templates/indexTemplate.ts",
    "content": "/**\n * Skills Index (README.md) Template\n */\n\nimport { Skill } from '../../../workflow/skills';\n\nexport interface SkillsIndexOptions {\n  skills: Skill[];\n  projectName?: string;\n}\n\n/**\n * Generate README.md for skills directory\n */\nexport function generateSkillsIndex(options: SkillsIndexOptions): string {\n  const { skills, projectName } = options;\n\n  const builtIn = skills.filter((s) => s.isBuiltIn);\n  const custom = skills.filter((s) => !s.isBuiltIn);\n\n  let content = `# Skills\n\nOn-demand expertise for AI agents. Skills are task-specific procedures that get activated when relevant.\n\n`;\n\n  if (projectName) {\n    content += `> Project: ${projectName}\\n\\n`;\n  }\n\n  content += `## How Skills Work\n\n1. **Discovery**: AI agents discover available skills\n2. **Matching**: When a task matches a skill's description, it's activated\n3. **Execution**: The skill's instructions guide the AI's behavior\n\n## Available Skills\n\n`;\n\n  if (builtIn.length > 0) {\n    content += `### Built-in Skills\n\n| Skill | Description | Phases |\n|-------|-------------|--------|\n`;\n\n    for (const skill of builtIn) {\n      const phases = skill.metadata.phases?.join(', ') || '-';\n      content += `| [${skill.metadata.name}](./${skill.slug}/SKILL.md) | ${skill.metadata.description} | ${phases} |\\n`;\n    }\n\n    content += '\\n';\n  }\n\n  if (custom.length > 0) {\n    content += `### Custom Skills\n\n| Skill | Description | Phases |\n|-------|-------------|--------|\n`;\n\n    for (const skill of custom) {\n      const phases = skill.metadata.phases?.join(', ') || '-';\n      content += `| [${skill.metadata.name}](./${skill.slug}/SKILL.md) | ${skill.metadata.description} | ${phases} |\\n`;\n    }\n\n    content += '\\n';\n  }\n\n  content += `## Creating Custom Skills\n\nCreate a new skill by adding a directory with a \\`SKILL.md\\` file:\n\n\\`\\`\\`\n.context/skills/\n└── my-skill/\n    ├── SKILL.md          # Required: source skill definition\n    ├── scripts/          # Optional: deterministic helpers\n    ├── references/       # Optional: load-on-demand details\n    └── assets/           # Optional: output resources\n\\`\\`\\`\n\n### Skill Anatomy\n\n\\`\\`\\`md\nThe source file under \\`.context/skills/\\` keeps internal scaffold metadata so dotcontext can track fill status.\nWhen exported to AI-tool skill directories, the portable frontmatter should keep only:\n\n---\nname: my-skill\ndescription: Describe what the skill does and the concrete triggers for when to use it\n---\n\n## Workflow\n1. Step one\n2. Step two\n\n## Examples\n\\`\\`\\`\n[Short example]\n\\`\\`\\`\n\n## Quality Bar\n- List the checks and constraints that keep the skill reliable\n\n## Resource Strategy\n- Explain when to add \\`scripts/\\`, \\`references/\\`, or \\`assets/\\`\n\\`\\`\\`\n\nKeep activation language in the description frontmatter, keep the body concise, and avoid extra docs such as \\`README.md\\` or \\`CHANGELOG.md\\` inside the skill folder.\n\n## PREVC Phase Mapping\n\n| Phase | Name | Skills |\n|-------|------|--------|\n| P | Planning | feature-breakdown, documentation, api-design |\n| R | Review | pr-review, code-review, api-design, security-audit |\n| E | Execution | commit-message, test-generation, refactoring, bug-investigation |\n| V | Validation | pr-review, code-review, test-generation, security-audit |\n| C | Confirmation | commit-message, documentation |\n`;\n\n  return content;\n}\n"
  },
  {
    "path": "src/generators/skills/templates/skillTemplate.ts",
    "content": "/**\n * SKILL.md Template\n *\n * REFERENCE ONLY - This file is not used by generators anymore.\n *\n * Scaffold structures are now defined in:\n * src/generators/shared/scaffoldStructures.ts\n *\n * This file serves as historical reference for the structure/content\n * that should be generated for this skill type.\n *\n * @deprecated Since v2.0.0 scaffold system\n */\n\nimport { SkillMetadata, SKILL_TO_PHASES, BuiltInSkillType, isBuiltInSkill } from '../../../workflow/skills';\nimport { PrevcPhase } from '../../../workflow/types';\n\nexport interface SkillTemplateOptions {\n  name: string;\n  description: string;\n  phases?: PrevcPhase[];\n  mode?: boolean;\n  disableModelInvocation?: boolean;\n}\n\n/**\n * Generate SKILL.md content\n */\nexport function generateSkillContent(options: SkillTemplateOptions): string {\n  const { name, description, phases, mode, disableModelInvocation } = options;\n\n  const frontmatter = buildFrontmatter({\n    name,\n    description,\n    phases,\n    mode,\n    disableModelInvocation,\n  });\n\n  const body = buildBody(name, description);\n\n  return `${frontmatter}\\n\\n${body}`;\n}\n\nfunction buildFrontmatter(options: SkillTemplateOptions): string {\n  const lines: string[] = ['---'];\n\n  lines.push(`name: ${options.name}`);\n  lines.push(`description: ${options.description}`);\n\n  if (options.phases && options.phases.length > 0) {\n    lines.push(`phases: [${options.phases.join(', ')}]`);\n  }\n\n  if (options.mode !== undefined) {\n    lines.push(`mode: ${options.mode}`);\n  }\n\n  if (options.disableModelInvocation !== undefined) {\n    lines.push(`disable-model-invocation: ${options.disableModelInvocation}`);\n  }\n\n  lines.push('---');\n\n  return lines.join('\\n');\n}\n\nfunction buildBody(name: string, description: string): string {\n  const title = name\n    .split('-')\n    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n    .join(' ');\n\n  return `# ${title}\n\n## When to Use\n\n${description}\n\n## Instructions\n\n<!-- TODO: Add step-by-step instructions for this skill -->\n\n1. First step\n2. Second step\n3. Third step\n\n## Examples\n\n<!-- TODO: Add examples of how to use this skill -->\n\n\\`\\`\\`\nExample usage here\n\\`\\`\\`\n\n## Guidelines\n\n<!-- TODO: Add guidelines and best practices -->\n\n- Guideline 1\n- Guideline 2\n- Guideline 3\n`;\n}\n\n/**\n * Get default phases for a skill name\n */\nexport function getDefaultPhases(skillName: string): PrevcPhase[] {\n  if (isBuiltInSkill(skillName)) {\n    return SKILL_TO_PHASES[skillName as BuiltInSkillType];\n  }\n  return ['E']; // Default to Execution phase\n}\n"
  },
  {
    "path": "src/harness/index.test.ts",
    "content": "import {\n  WorkflowService,\n  HarnessAgentsService,\n  HarnessPlansService,\n  HarnessContextService,\n  HarnessSkillsService,\n  HarnessRuntimeStateService,\n  HarnessSensorsService,\n  HarnessTaskContractsService,\n  HarnessExecutionService,\n  HarnessReplayService,\n  HarnessDatasetService,\n  HarnessPolicyService,\n  getScaleName,\n  PHASE_NAMES_PT,\n  ROLE_DISPLAY_NAMES,\n} from './index';\n\ndescribe('Harness boundary exports', () => {\n  it('exposes runtime and orchestration services', () => {\n    expect(WorkflowService).toBeDefined();\n    expect(HarnessAgentsService).toBeDefined();\n    expect(HarnessPlansService).toBeDefined();\n    expect(HarnessContextService).toBeDefined();\n    expect(HarnessSkillsService).toBeDefined();\n    expect(HarnessRuntimeStateService).toBeDefined();\n    expect(HarnessSensorsService).toBeDefined();\n    expect(HarnessTaskContractsService).toBeDefined();\n    expect(HarnessExecutionService).toBeDefined();\n    expect(HarnessReplayService).toBeDefined();\n    expect(HarnessDatasetService).toBeDefined();\n    expect(HarnessPolicyService).toBeDefined();\n    expect(getScaleName).toBeDefined();\n    expect(PHASE_NAMES_PT).toBeDefined();\n    expect(ROLE_DISPLAY_NAMES).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "src/harness/index.ts",
    "content": "/**\n * Harness boundary exports.\n *\n * This module defines the reusable runtime surface that is expected to\n * become the future `dotcontext/harness` package. It intentionally excludes\n * CLI-only concerns and MCP transport adapters.\n */\n\nexport {\n  WorkflowService,\n  type WorkflowServiceDependencies,\n  type WorkflowInitOptions,\n  type WorkflowHarnessStatus,\n  HarnessWorkflowBlockedError,\n} from '../services/workflow';\nexport {\n  HarnessAgentsService,\n  type HarnessAgentsServiceOptions,\n  HarnessPlansService,\n  type HarnessPlansServiceOptions,\n  HarnessContextService,\n  type HarnessContextServiceOptions,\n  HarnessSkillsService,\n  type HarnessSkillsServiceOptions,\n  type HarnessBootstrapStatusResult,\n  HarnessRuntimeStateService,\n  type HarnessRuntimeStateServiceOptions,\n  type HarnessSessionStatus,\n  type HarnessTraceLevel,\n  type HarnessArtifactKind,\n  type HarnessSessionRecord,\n  type HarnessSessionCheckpoint,\n  type HarnessTraceRecord,\n  type HarnessArtifactRecord,\n  type HarnessRuntimeStatePort,\n  type CreateSessionInput,\n  type AppendTraceInput,\n  type AddArtifactInput,\n  type CheckpointInput,\n  HarnessSensorsService,\n  HarnessSensorCatalogService,\n  type HarnessSensorsServiceOptions,\n  type HarnessSensorCatalogServiceOptions,\n  type HarnessSensorSeverity,\n  type HarnessSensorStatus,\n  type HarnessSensorExecutionInput,\n  type HarnessSensorExecutionResult,\n  type HarnessSensorDefinition,\n  type HarnessSensorRun,\n  type HarnessBackpressurePolicy,\n  type HarnessBackpressureResult,\n  type HarnessShellSensorConfig,\n  type HarnessSensorCatalogDocument,\n  type HarnessSensorCatalogSeverity,\n  HarnessTaskContractsService,\n  type HarnessTaskContractsServiceOptions,\n  type HarnessTaskContractStatus,\n  type HarnessTaskContract,\n  type HarnessHandoffContract,\n  type HarnessTaskCompletionResult,\n  HarnessExecutionService,\n  type HarnessExecutionServiceOptions,\n  type HarnessSessionQualitySnapshot,\n  HarnessReplayService,\n  type HarnessReplayServiceOptions,\n  type HarnessReplayDependencies,\n  type HarnessReplayEvent,\n  type HarnessReplayEventSource,\n  type HarnessReplayRecord,\n  type ReplaySessionOptions,\n  HarnessDatasetService,\n  type HarnessDatasetServiceOptions,\n  type HarnessDatasetDependencies,\n  type BuildHarnessDatasetOptions,\n  type HarnessFailureKind,\n  type HarnessFailureRecord,\n  type HarnessFailureCluster,\n  type HarnessFailureDataset,\n  HarnessPolicyService,\n  HarnessPolicyBlockedError,\n  type HarnessPolicyServiceOptions,\n  type HarnessPolicyTarget,\n  type HarnessPolicyScope,\n  type HarnessPolicyEffect,\n  type HarnessPolicyRisk,\n  type HarnessPolicyRule,\n  type CreateHarnessPolicyRuleInput,\n  type HarnessPolicyEvaluationInput,\n  type HarnessPolicyEvaluationResult,\n  HarnessWorkflowStateService,\n  type HarnessWorkflowStateServiceOptions,\n  type HarnessWorkflowRecord,\n  type WorkflowHarnessBinding,\n} from '../services/harness';\n\nexport {\n  getScaleName,\n  PHASE_NAMES_PT,\n  ROLE_DISPLAY_NAMES,\n  type PrevcRole,\n} from '../workflow';\n"
  },
  {
    "path": "src/index.ts",
    "content": "#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport * as path from 'path';\nimport inquirer from 'inquirer';\n\nimport { colors, typography } from './utils/theme';\nimport {\n  formatSplashDirectory,\n  packageNameToDisplayName,\n  renderSplashScreen\n} from './utils/splashScreen';\nimport { themedSelect, Separator } from './utils/themedPrompt';\nimport { CLIInterface } from './utils/cliUI';\nimport { checkForUpdates } from './utils/versionChecker';\nimport { registerProcessShutdown } from './utils/processShutdown';\nimport { createTranslator, detectLocale, SUPPORTED_LOCALES, normalizeLocale } from './utils/i18n';\nimport type { TranslateFn, Locale, TranslationKey } from './utils/i18n';\nimport {\n  MCPInstallService,\n  resolveMcpInstallToolSelection,\n  SyncService,\n  ImportRulesService,\n  ImportAgentsService,\n  ExportRulesService,\n  ReportService,\n  QuickSyncService,\n  type QuickSyncOptions,\n  ReverseQuickSyncService,\n  type MergeStrategy,\n  StateDetector\n} from './cli';\nimport {\n  startMCPServer,\n} from './mcp';\nimport {\n  WorkflowService,\n  type WorkflowServiceDependencies,\n  getScaleName,\n  PHASE_NAMES_PT,\n  ROLE_DISPLAY_NAMES,\n  type PrevcRole\n} from './harness';\nimport {\n  detectSmartDefaults,\n  displayConfigSummary,\n  type ConfigSummary\n} from './utils/prompts';\nimport { VERSION, PACKAGE_NAME } from './version';\n\nconst rawArgs = process.argv.slice(2);\nconst isMcpCommand = rawArgs.includes('mcp');\n\n// Determine if we're in interactive mode (no command args, only flags like --lang)\nconst isInteractiveMode = rawArgs.every(arg =>\n  arg.startsWith('-') ||\n  rawArgs[rawArgs.indexOf(arg) - 1]?.startsWith('--lang') ||\n  rawArgs[rawArgs.indexOf(arg) - 1]?.startsWith('--language') ||\n  rawArgs[rawArgs.indexOf(arg) - 1]?.startsWith('-l')\n);\n\nconst initialLocale = detectLocale(rawArgs, process.env.DOTCONTEXT_LANG, [\n  process.env.LC_ALL,\n  process.env.LC_MESSAGES,\n  process.env.LANG\n]);\nlet currentLocale: Locale = initialLocale;\nlet translateFn = createTranslator(initialLocale);\nconst t: TranslateFn = (key, params) => translateFn(key, params);\n\nconst localeLabelKeys: Record<Locale, TranslationKey> = {\n  en: 'prompts.language.option.en',\n  'pt-BR': 'prompts.language.option.pt-BR'\n};\n\nconst program = new Command();\nconst ui = new CLIInterface(t);\n\nconst syncService = new SyncService({\n  ui,\n  t,\n  version: VERSION\n});\n\nconst importRulesService = new ImportRulesService({\n  ui,\n  t,\n  version: VERSION\n});\n\nconst importAgentsService = new ImportAgentsService({\n  ui,\n  t,\n  version: VERSION\n});\n\nprogram\n  .name('dotcontext')\n  .description(t('cli.description'))\n  .version(VERSION);\n\nprogram.option('-l, --lang <locale>', t('global.options.lang'), initialLocale);\n\nlet versionCheckPromise: Promise<void> | null = null;\n\nfunction scheduleVersionCheck(force: boolean = false): Promise<void> {\n  if (!versionCheckPromise || force) {\n    versionCheckPromise = checkForUpdates({\n      packageName: PACKAGE_NAME,\n      currentVersion: VERSION,\n      ui,\n      t,\n      force\n    }).catch(() => { });\n  }\n\n  return versionCheckPromise;\n}\n\nprogram.hook('preAction', () => {\n  void scheduleVersionCheck();\n});\n\nfunction registerSyncCommand(parent: Command, name: string, hidden = false): Command {\n  const command = parent\n    .command(name, hidden ? { hidden: true } : {})\n    .description(t('commands.sync.description'))\n    .option('-s, --source <dir>', t('commands.sync.options.source'), './.context/agents')\n    .option('-t, --target <paths...>', t('commands.sync.options.target'))\n    .option('-m, --mode <type>', t('commands.sync.options.mode'), 'symlink')\n    .option('-p, --preset <name>', t('commands.sync.options.preset'))\n    .option('--force', t('commands.sync.options.force'))\n    .option('--dry-run', t('commands.sync.options.dryRun'))\n    .option('-v, --verbose', t('commands.sync.options.verbose'))\n    .action(async (options: any) => {\n      try {\n        await syncService.run(options);\n      } catch (error) {\n        ui.displayError(t('errors.sync.failed'), error as Error);\n        process.exit(1);\n      }\n    });\n  return command;\n}\n\nregisterSyncCommand(program, 'sync');\nregisterSyncCommand(program, 'sync-agents', true);\n\nprogram\n  .command('import-rules')\n  .description(t('commands.importRules.description'))\n  .argument('[repo-path]', 'Repository path to scan', process.cwd())\n  .option('-s, --source <paths...>', t('commands.importRules.options.source'))\n  .option('-t, --target <dir>', t('commands.importRules.options.target'))\n  .option('-f, --format <format>', t('commands.importRules.options.format'), 'markdown')\n  .option('--force', t('commands.importRules.options.force'))\n  .option('--dry-run', t('commands.importRules.options.dryRun'))\n  .option('-v, --verbose', t('commands.importRules.options.verbose'))\n  .option('--no-auto-detect', 'Disable auto-detection')\n  .action(async (repoPath: string, options: any) => {\n    try {\n      await importRulesService.run({\n        source: options.source,\n        target: options.target,\n        format: options.format,\n        force: options.force,\n        dryRun: options.dryRun,\n        verbose: options.verbose,\n        autoDetect: options.autoDetect !== false\n      }, repoPath);\n    } catch (error) {\n      ui.displayError(t('errors.import.failed'), error as Error);\n      process.exit(1);\n    }\n  });\n\nprogram\n  .command('import-agents')\n  .description(t('commands.importAgents.description'))\n  .argument('[repo-path]', 'Repository path to scan', process.cwd())\n  .option('-s, --source <paths...>', t('commands.importAgents.options.source'))\n  .option('-t, --target <dir>', t('commands.importAgents.options.target'))\n  .option('--force', t('commands.importAgents.options.force'))\n  .option('--dry-run', t('commands.importAgents.options.dryRun'))\n  .option('-v, --verbose', t('commands.importAgents.options.verbose'))\n  .option('--no-auto-detect', 'Disable auto-detection')\n  .action(async (repoPath: string, options: any) => {\n    try {\n      await importAgentsService.run({\n        source: options.source,\n        target: options.target,\n        force: options.force,\n        dryRun: options.dryRun,\n        verbose: options.verbose,\n        autoDetect: options.autoDetect !== false\n      }, repoPath);\n    } catch (error) {\n      ui.displayError(t('errors.import.failed'), error as Error);\n      process.exit(1);\n    }\n  });\n\nprogram\n  .command('reverse-sync')\n  .description('Import rules, agents, and skills from AI tool directories into .context/')\n  .argument('[repo-path]', 'Repository path to scan', process.cwd())\n  .option('--dry-run', 'Preview changes without importing')\n  .option('-f, --force', 'Overwrite existing files')\n  .option('--skip-agents', 'Skip importing agents')\n  .option('--skip-skills', 'Skip importing skills')\n  .option('--skip-rules', 'Skip importing rules')\n  .option('--merge-strategy <strategy>', 'How to handle conflicts: skip|overwrite|merge|rename', 'skip')\n  .option('--format <format>', 'Output format for rules: markdown|raw|formatted', 'formatted')\n  .option('--no-metadata', 'Do not add import metadata to files')\n  .option('-v, --verbose', 'Verbose output')\n  .action(async (repoPath: string, options: any) => {\n    try {\n      const service = new ReverseQuickSyncService({ ui, t, version: VERSION });\n      await service.run(repoPath, {\n        dryRun: options.dryRun,\n        force: options.force,\n        skipAgents: options.skipAgents,\n        skipSkills: options.skipSkills,\n        skipRules: options.skipRules,\n        mergeStrategy: options.mergeStrategy as MergeStrategy,\n        format: options.format,\n        metadata: options.metadata !== false,\n        verbose: options.verbose,\n      });\n    } catch (error) {\n      ui.displayError(t('errors.reverseSync.failed'), error as Error);\n      process.exit(1);\n    }\n  });\n\nprogram\n  .command('mcp')\n  .description('Start MCP (Model Context Protocol) server for Claude Code integration')\n  .option('-r, --repo-path <path>', 'Default repository path for tools')\n  .option('-v, --verbose', 'Enable verbose logging to stderr')\n  .action(async (options: any) => {\n    try {\n      const server = await startMCPServer({\n        repoPath: options.repoPath,\n        verbose: options.verbose\n      });\n\n      registerProcessShutdown(server, {\n        onError: (error) => {\n          if (options.verbose) {\n            process.stderr.write(`[mcp] Shutdown error: ${error}\\n`);\n          }\n        },\n        exit: (code) => process.exit(code),\n      });\n    } catch (error) {\n      if (options.verbose) {\n        process.stderr.write(`[mcp] Error: ${error}\\n`);\n      }\n      process.exit(1);\n    }\n  });\n\n// MCP Install Command\nprogram\n  .command('mcp:install [tool]')\n  .description(t('commands.mcpInstall.description'))\n  .option('-g, --global', t('commands.mcpInstall.options.global'), true)\n  .option('-l, --local', t('commands.mcpInstall.options.local'))\n  .option('--dry-run', t('commands.mcpInstall.options.dryRun'))\n  .option('-v, --verbose', t('commands.mcpInstall.options.verbose'))\n  .action(async (tool: string | undefined, options: any) => {\n    try {\n      const mcpInstallService = new MCPInstallService({ ui, t, version: VERSION });\n      const selectedTool = await resolveMcpInstallToolSelection({\n        selectedTool: tool,\n        isInteractive: Boolean(process.stdin.isTTY),\n        service: mcpInstallService,\n        t,\n        promptTool: ({ message, choices }) => themedSelect({ message, choices }),\n      });\n\n      const result = await mcpInstallService.run({\n        tool: selectedTool,\n        global: options.local ? false : options.global,\n        dryRun: options.dryRun,\n        verbose: options.verbose,\n        repoPath: process.cwd(),\n      });\n\n      if (result.installations.length > 0) {\n        ui.displayInfo('MCP', t('info.mcp.restartTools'));\n      }\n    } catch (error) {\n      ui.displayError(t('errors.mcp.installFailed', { tool: tool || 'unknown' }), error as Error);\n      process.exit(1);\n    }\n  });\n\nfunction registerPreviewSplashCommand(parent: Command, hidden = false): Command {\n  const command = parent\n    .command('preview-splash', hidden ? { hidden: true } : {})\n    .description(t('commands.previewSplash.description'))\n    .option('--title <title>', t('commands.previewSplash.options.title'))\n    .option('--directory <path>', t('commands.previewSplash.options.directory'), process.cwd())\n    .action(async (options: any) => {\n      try {\n        await renderStartupSplash(options.directory, options.title);\n      } catch (error) {\n        ui.displayError(t('errors.cli.executionFailed'), error as Error);\n        process.exit(1);\n      }\n    });\n  return command;\n}\n\nfunction registerExportRulesCommand(parent: Command, hidden = false): Command {\n  const command = parent\n    .command('export-rules', hidden ? { hidden: true } : {})\n    .description(t('commands.export.description'))\n    .argument('[repo-path]', 'Repository path', process.cwd())\n    .option('-s, --source <dir>', t('commands.export.options.source'), '.context/docs')\n    .option('-t, --targets <paths...>', t('commands.export.options.targets'))\n    .option('--preset <name>', t('commands.export.options.preset'))\n    .option('--force', t('commands.export.options.force'))\n    .option('--dry-run', t('commands.export.options.dryRun'))\n    .option('-v, --verbose', t('commands.fill.options.verbose'))\n    .action(async (repoPath: string, options: any) => {\n      try {\n        const exportService = new ExportRulesService({\n          ui,\n          t,\n          version: VERSION,\n        });\n\n        await exportService.run(repoPath, {\n          source: options.source,\n          targets: options.targets,\n          preset: options.preset,\n          force: options.force,\n          dryRun: options.dryRun,\n          verbose: options.verbose,\n        });\n      } catch (error) {\n        ui.displayError(t('errors.cli.executionFailed'), error as Error);\n        process.exit(1);\n      }\n    });\n  return command;\n}\n\nfunction registerReportCommand(parent: Command, hidden = false): Command {\n  const command = parent\n    .command('report', hidden ? { hidden: true } : {})\n    .description(t('commands.report.description'))\n    .argument('[repo-path]', 'Repository path', process.cwd())\n    .option('-f, --format <format>', t('commands.report.options.format'), 'console')\n    .option('-o, --output <path>', t('commands.report.options.output'))\n    .option('--include-stack', t('commands.report.options.includeStack'))\n    .option('-v, --verbose', t('commands.fill.options.verbose'))\n    .action(async (repoPath: string, options: any) => {\n      try {\n        const reportService = new ReportService({\n          ui,\n          t,\n          version: VERSION,\n        });\n\n        const report = await reportService.generate(repoPath, {\n          format: options.format,\n          output: options.output,\n          includeStack: options.includeStack,\n          verbose: options.verbose,\n        });\n\n        await reportService.output(report, options);\n      } catch (error) {\n        ui.displayError(t('errors.cli.executionFailed'), error as Error);\n        process.exit(1);\n      }\n    });\n  return command;\n}\n\nfunction registerSkillCommands(parent: Command, hidden = false): Command {\n  const skillCommand = parent\n    .command('skill', hidden ? { hidden: true } : {})\n    .description(t('commands.skill.description'));\n\n  skillCommand\n    .command('list')\n    .description(t('commands.skill.list.description'))\n    .argument('[repo-path]', 'Repository path', process.cwd())\n    .option('--json', 'Output as JSON')\n    .action(async (repoPath: string, options: any) => {\n      try {\n        const { createSkillRegistry } = await import('./workflow/skills');\n        const registry = createSkillRegistry(repoPath);\n        const discovered = await registry.discoverAll();\n\n        if (options.json) {\n          console.log(JSON.stringify({\n            builtIn: discovered.builtIn.map(s => s.slug),\n            custom: discovered.custom.map(s => s.slug),\n            total: discovered.all.length,\n          }, null, 2));\n          return;\n        }\n\n        console.log('\\nBuilt-in Skills:');\n        for (const skill of discovered.builtIn) {\n          const projectSkill = discovered.all.find(s => s.slug === skill.slug && s.path.includes('.context'));\n          const status = projectSkill ? '[project]' : '[available]';\n          console.log(`  ${skill.slug} ${status}`);\n          console.log(`    ${skill.metadata.description}`);\n        }\n\n        if (discovered.custom.length > 0) {\n          console.log('\\nCustom Skills:');\n          for (const skill of discovered.custom) {\n            console.log(`  ${skill.slug}`);\n            console.log(`    ${skill.metadata.description}`);\n          }\n        }\n\n        console.log(`\\nTotal: ${discovered.all.length} skills (${discovered.builtIn.length} built-in, ${discovered.custom.length} custom)`);\n      } catch (error) {\n        ui.displayError('Failed to list skills', error as Error);\n        process.exit(1);\n      }\n    });\n\n  skillCommand\n    .command('export')\n    .description(t('commands.skill.export.description'))\n    .argument('[repo-path]', 'Repository path', process.cwd())\n    .option('-p, --preset <preset>', 'Export preset, for example: claude, github, windsurf, codex, antigravity, all', 'all')\n    .option('-f, --force', 'Overwrite existing files')\n    .option('--include-builtin', 'Include built-in skills even if not scaffolded')\n    .option('--dry-run', 'Preview changes without writing')\n    .action(async (repoPath: string, options: any) => {\n      try {\n        const { SkillExportService } = await import('./services/export/skillExportService');\n        const exportService = new SkillExportService({\n          ui,\n          t,\n          version: VERSION,\n        });\n\n        const result = await exportService.run(repoPath, {\n          preset: options.preset,\n          force: options.force,\n          includeBuiltIn: options.includeBuiltin,\n          dryRun: options.dryRun,\n        });\n\n        if (options.dryRun) {\n          ui.displayInfo('Dry run', 'No files were written');\n        } else {\n          ui.displaySuccess(`Exported ${result.skillsExported.length} skills to ${result.targets.length} targets`);\n        }\n      } catch (error) {\n        ui.displayError('Failed to export skills', error as Error);\n        process.exit(1);\n      }\n    });\n  return skillCommand;\n}\n\n// Helper to create workflow service dependencies\nconst getWorkflowDeps = (): WorkflowServiceDependencies => ({\n  ui: {\n    displaySuccess: (msg: string) => ui.displaySuccess(msg),\n    displayError: (msg: string, err?: Error) => ui.displayError(msg, err),\n    displayInfo: (title: string, detail?: string) => ui.displayInfo(title, detail || '')\n  }\n});\n\nfunction registerWorkflowCommands(parent: Command, hidden = false): Command {\n  const workflowCommand = parent\n    .command('workflow', hidden ? { hidden: true } : {})\n    .description('PREVC workflow management (Planning, Review, Execution, Validation, Confirmation)');\n\n  workflowCommand\n    .command('init <name>')\n    .description('Initialize a new PREVC workflow')\n    .option('-d, --description <text>', 'Project description for scale detection')\n    .option('-s, --scale <scale>', 'Project scale: QUICK, SMALL, MEDIUM, LARGE')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .action(async (name: string, options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n        const status = await workflowService.init({\n          name,\n          description: options.description,\n          scale: options.scale\n        });\n\n        ui.displaySuccess(`Workflow PREVC initialized: ${name}`);\n        ui.displayInfo('Scale', getScaleName(status.project.scale as any));\n        ui.displayInfo('Current Phase', `${status.project.current_phase} - ${PHASE_NAMES_PT[status.project.current_phase]}`);\n      } catch (error) {\n        ui.displayError('Failed to initialize workflow', error as Error);\n        process.exit(1);\n      }\n    });\n\n  workflowCommand\n    .command('status')\n    .description('Show current workflow status')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .action(async (options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n\n        if (!await workflowService.hasWorkflow()) {\n          ui.displayError('No workflow found. Run \"workflow init <name>\" first.');\n          process.exit(1);\n        }\n\n        const formattedStatus = await workflowService.getFormattedStatus();\n        console.log(formattedStatus);\n\n        const actions = await workflowService.getRecommendedActions();\n        if (actions.length > 0) {\n          console.log('\\nRecommended actions:');\n          actions.forEach((action, i) => console.log(`  ${i + 1}. ${action}`));\n        }\n      } catch (error) {\n        ui.displayError('Failed to get workflow status', error as Error);\n        process.exit(1);\n      }\n    });\n\n  workflowCommand\n    .command('advance')\n    .description('Complete current phase and advance to next')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .option('-o, --outputs <files...>', 'Output files generated in current phase')\n    .action(async (options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n\n        if (!await workflowService.hasWorkflow()) {\n          ui.displayError('No workflow found. Run \"workflow init <name>\" first.');\n          process.exit(1);\n        }\n\n        const nextPhase = await workflowService.advance(options.outputs);\n\n        if (nextPhase) {\n          ui.displaySuccess(`Advanced to phase: ${nextPhase} - ${PHASE_NAMES_PT[nextPhase]}`);\n        } else {\n          ui.displaySuccess('Workflow completed!');\n        }\n      } catch (error) {\n        ui.displayError('Failed to advance workflow', error as Error);\n        process.exit(1);\n      }\n    });\n\n  workflowCommand\n    .command('handoff <from> <to>')\n    .description('Perform handoff between roles')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .option('-a, --artifacts <files...>', 'Artifacts to hand off')\n    .action(async (from: string, to: string, options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n\n        if (!await workflowService.hasWorkflow()) {\n          ui.displayError('No workflow found. Run \"workflow init <name>\" first.');\n          process.exit(1);\n        }\n\n        await workflowService.handoff(from as PrevcRole, to as PrevcRole, options.artifacts || []);\n        ui.displaySuccess(`Handoff: ${ROLE_DISPLAY_NAMES[from as PrevcRole]} → ${ROLE_DISPLAY_NAMES[to as PrevcRole]}`);\n      } catch (error) {\n        ui.displayError('Failed to perform handoff', error as Error);\n        process.exit(1);\n      }\n    });\n\n  workflowCommand\n    .command('collaborate <topic>')\n    .description('Start a collaboration session between roles')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .option('-p, --participants <roles...>', 'Participating roles')\n    .action(async (topic: string, options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n\n        const session = await workflowService.startCollaboration(\n          topic,\n          options.participants as PrevcRole[]\n        );\n\n        ui.displaySuccess(`Collaboration started: ${topic}`);\n        ui.displayInfo('Session ID', session.getId());\n        ui.displayInfo('Participants', session.getParticipantNames().join(', '));\n        console.log('\\nUse MCP tools to contribute and synthesize the collaboration.');\n      } catch (error) {\n        ui.displayError('Failed to start collaboration', error as Error);\n        process.exit(1);\n      }\n    });\n\n  workflowCommand\n    .command('role <action> <role>')\n    .description('Manage role status (start/complete)')\n    .option('-r, --repo-path <path>', 'Repository path', process.cwd())\n    .option('-o, --outputs <files...>', 'Output files (for complete action)')\n    .action(async (action: string, role: string, options: any) => {\n      try {\n        const workflowService = new WorkflowService(options.repoPath, getWorkflowDeps());\n\n        if (!await workflowService.hasWorkflow()) {\n          ui.displayError('No workflow found. Run \"workflow init <name>\" first.');\n          process.exit(1);\n        }\n\n        if (action === 'start') {\n          await workflowService.startRole(role as PrevcRole);\n          ui.displaySuccess(`Started role: ${ROLE_DISPLAY_NAMES[role as PrevcRole]}`);\n        } else if (action === 'complete') {\n          await workflowService.completeRole(role as PrevcRole, options.outputs || []);\n          ui.displaySuccess(`Completed role: ${ROLE_DISPLAY_NAMES[role as PrevcRole]}`);\n        } else {\n          ui.displayError(`Unknown action: ${action}. Use 'start' or 'complete'.`);\n          process.exit(1);\n        }\n      } catch (error) {\n        ui.displayError('Failed to manage role', error as Error);\n        process.exit(1);\n      }\n    });\n  return workflowCommand;\n}\n\nconst adminCommand = program\n  .command('admin')\n  .description('Advanced and low-level commands that are not part of the primary sync-focused CLI surface');\n\nregisterExportRulesCommand(program);\nregisterReportCommand(adminCommand);\nregisterReportCommand(program, true);\nregisterSkillCommands(adminCommand);\nregisterSkillCommands(program, true);\nregisterWorkflowCommands(adminCommand);\nregisterWorkflowCommands(program, true);\nregisterPreviewSplashCommand(adminCommand);\nregisterPreviewSplashCommand(program, true);\n\nasync function selectLocale(): Promise<void> {\n  const locale = await themedSelect<Locale>({\n    message: t('prompts.language.select'),\n    default: currentLocale,\n    choices: SUPPORTED_LOCALES.map(option => ({\n      value: option,\n      name: t(localeLabelKeys[option])\n    }))\n  });\n\n  const normalizedLocale = normalizeLocale(locale);\n  currentLocale = normalizedLocale;\n  translateFn = createTranslator(normalizedLocale);\n}\n\ntype InteractiveAction = 'syncAgents' | 'update' | 'changeLanguage' | 'exit' | 'quickSync' | 'reverseSync' | 'settings' | 'mcpInstall' | 'viewPending';\ntype StateAction = 'exit' | 'mcpInstall' | 'reverseSync' | 'settings';\n\nasync function runInteractive(): Promise<void> {\n  const projectPath = process.cwd();\n  const detector = new StateDetector({ projectPath });\n  const result = await detector.detect();\n\n  // Detect smart defaults for display\n  const defaults = await detectSmartDefaults(projectPath);\n\n  console.log('');\n  console.log(renderSplashScreen({\n    title: packageNameToDisplayName(PACKAGE_NAME),\n    version: VERSION,\n    lines: [\n      {\n        label: t('ui.splash.directoryLabel'),\n        value: formatSplashDirectory(projectPath)\n      }\n    ]\n  }));\n\n  // Show what was detected from environment/project\n  const detectedParts: string[] = [];\n  if (defaults.detectedLanguages.length > 0) {\n    const langs = defaults.detectedLanguages.map(l => l.charAt(0).toUpperCase() + l.slice(1)).join(', ');\n    detectedParts.push(t('status.detected.project', { languages: langs }));\n  }\n  if (detectedParts.length > 0) {\n    console.log(colors.secondaryDim(detectedParts.join(', ')));\n  }\n\n  // Show compact status line based on state\n  if (result.state === 'new') {\n    console.log(colors.secondaryDim(t('status.new')));\n  } else if (result.state === 'unfilled') {\n    console.log(colors.secondaryDim(t('status.unfilled', { count: result.details.unfilledFiles })));\n  } else {\n    // Get quick stats only when we have context\n    const quickSyncService = new QuickSyncService({\n      ui,\n      t,\n      version: VERSION,\n    });\n    const stats = await quickSyncService.getStats(projectPath);\n\n    if (result.state === 'outdated') {\n      console.log(colors.warning(\n        t('status.outdated', { days: result.details.daysBehind || 0 })\n      ));\n    } else {\n      console.log(colors.success(\n        t('status.compact', {\n          docs: stats.docs,\n          agents: stats.agents,\n          skills: stats.skills\n        })\n      ));\n    }\n  }\n  console.log('');\n\n  // Handle state-based flow: auto-detect what to show\n  if (result.state === 'new') {\n    const action = await themedSelect<StateAction>({\n      message: t('prompts.main.action'),\n      choices: [\n        { name: t('prompts.main.choice.mcpInstall'), value: 'mcpInstall' },\n        { name: t('prompts.main.choice.reverseSync'), value: 'reverseSync' },\n        { name: t('prompts.main.choice.settings'), value: 'settings' },\n        { name: t('prompts.main.choice.exit'), value: 'exit' }\n      ]\n    });\n\n    if (action === 'mcpInstall') {\n      await runMcpInstall();\n    } else if (action === 'reverseSync') {\n      await runReverseSync();\n    } else if (action === 'settings') {\n      await runSettings();\n    }\n\n    const postOnboardingState = await detector.detect();\n    if (postOnboardingState.state !== 'new') {\n      await runFullMenu();\n    }\n    return;\n  }\n\n  // For any project that has completed onboarding, always show the full menu.\n  await runFullMenu();\n}\n\nasync function runFullMenu(): Promise<void> {\n  let exitRequested = false;\n  while (!exitRequested) {\n    const detector = new StateDetector({ projectPath: process.cwd() });\n    const state = await detector.detect();\n    const isUnfilled = state.state === 'unfilled';\n\n    const choices = isUnfilled\n      ? [\n        { name: t('prompts.main.choice.viewPending'), value: 'viewPending' as InteractiveAction },\n        { name: t('prompts.main.choice.mcpInstall'), value: 'mcpInstall' as InteractiveAction },\n        new Separator(),\n        { name: t('prompts.main.choice.quickSync'), value: 'quickSync' as InteractiveAction },\n        { name: t('prompts.main.choice.reverseSync'), value: 'reverseSync' as InteractiveAction },\n        { name: t('prompts.main.choice.settings'), value: 'settings' as InteractiveAction },\n        { name: t('prompts.main.choice.exit'), value: 'exit' as InteractiveAction }\n      ]\n      : [\n        { name: t('prompts.main.choice.quickSync'), value: 'quickSync' as InteractiveAction },\n        { name: t('prompts.main.choice.reverseSync'), value: 'reverseSync' as InteractiveAction },\n        { name: t('prompts.main.choice.mcpInstall'), value: 'mcpInstall' as InteractiveAction },\n        { name: t('prompts.main.choice.settings'), value: 'settings' as InteractiveAction },\n        { name: t('prompts.main.choice.exit'), value: 'exit' as InteractiveAction }\n      ];\n\n    const action = await themedSelect<InteractiveAction>({\n      message: isUnfilled\n        ? t('prompts.main.unfilledPrompt', { count: state.details.unfilledFiles })\n        : t('prompts.main.action'),\n      choices\n    });\n\n    if (action === 'exit') {\n      exitRequested = true;\n      break;\n    }\n\n    if (action === 'viewPending') {\n      await displayPendingFiles(state.contextDir);\n      continue;\n    }\n\n    if (action === 'quickSync') {\n      await runQuickSync();\n    } else if (action === 'reverseSync') {\n      await runReverseSync();\n    } else if (action === 'mcpInstall') {\n      await runMcpInstall();\n    } else if (action === 'settings') {\n      await runSettings();\n    }\n  }\n\n  ui.displaySuccess(t('success.interactive.goodbye'));\n}\n\nasync function displayPendingFiles(contextDir: string): Promise<void> {\n  const { getUnfilledFiles } = await import('./utils/frontMatter');\n  const unfilled = await getUnfilledFiles(contextDir);\n\n  console.log();\n  console.log(typography.subheader(t('prompts.main.pendingFilesHeader')));\n  for (const file of unfilled) {\n    const relative = path.relative(contextDir, file);\n    console.log(`  ${colors.secondary('•')} ${colors.primary(relative)}`);\n  }\n  console.log();\n}\n\nasync function runMcpInstall(): Promise<void> {\n  const mcpInstallService = new MCPInstallService({ ui, t, version: VERSION });\n  const selectedTool = await resolveMcpInstallToolSelection({\n    isInteractive: true,\n    service: mcpInstallService,\n    t,\n    promptTool: ({ message, choices }) => themedSelect({ message, choices }),\n  });\n\n  const mcpResult = await mcpInstallService.run({\n    tool: selectedTool,\n    global: true,\n    dryRun: false,\n    verbose: false,\n    repoPath: process.cwd(),\n  });\n\n  if (mcpResult.installations.length > 0) {\n    ui.displayInfo('MCP', t('info.mcp.restartTools'));\n  }\n}\n\nasync function runInteractiveSync(): Promise<void> {\n  const defaults = await detectSmartDefaults();\n  const defaultSource = path.resolve(defaults.repoPath, '.context/agents');\n\n  // Simplified: single prompt for target selection with common presets\n  const { quickTarget } = await inquirer.prompt<{ quickTarget: string }>([\n    {\n      type: 'list',\n      name: 'quickTarget',\n      message: t('prompts.sync.quickTarget'),\n      choices: [\n        { name: t('prompts.sync.quickTarget.common'), value: 'common' },\n        { name: t('prompts.sync.quickTarget.claude'), value: 'claude' },\n        { name: t('prompts.sync.quickTarget.all'), value: 'all' },\n        { name: t('prompts.sync.quickTarget.custom'), value: 'custom' }\n      ],\n      default: 'common'\n    }\n  ]);\n\n  let preset: string | undefined;\n  let target: string[] | undefined;\n  let sourcePath = defaultSource;\n\n  if (quickTarget === 'custom') {\n    // Custom path: ask for source and target\n    const answers = await inquirer.prompt<{ sourcePath: string; customPath: string }>([\n      {\n        type: 'input',\n        name: 'sourcePath',\n        message: t('prompts.sync.source'),\n        default: defaultSource\n      },\n      {\n        type: 'input',\n        name: 'customPath',\n        message: t('prompts.sync.customPath')\n      }\n    ]);\n    sourcePath = answers.sourcePath;\n    target = [answers.customPath];\n  } else if (quickTarget === 'common') {\n    // Common: Claude + GitHub - use explicit target paths instead of preset\n    target = [\n      path.resolve(defaults.repoPath, '.claude/agents'),\n      path.resolve(defaults.repoPath, '.github/agents')\n    ];\n  } else {\n    preset = quickTarget;\n  }\n\n  // Show summary\n  const summary: ConfigSummary = {\n    operation: 'sync',\n    repoPath: sourcePath,\n    options: {\n      Target: quickTarget === 'custom' ? (target?.[0] || 'custom') : quickTarget,\n      Mode: 'symlink'\n    }\n  };\n\n  displayConfigSummary(summary, t);\n\n  try {\n    await syncService.run({\n      source: sourcePath,\n      mode: 'symlink',\n      preset: preset as any,\n      target,\n      force: false,\n      dryRun: false\n    });\n  } catch (error) {\n    ui.displayError(t('errors.sync.failed'), error as Error);\n  }\n}\n\n\n// ============================================================================\n// Quick Sync - Unified sync for agents, skills, and docs\n// ============================================================================\n\nasync function runQuickSync(): Promise<void> {\n  const projectPath = process.cwd();\n\n  // Single prompt: sync all or customize?\n  const { syncMode } = await inquirer.prompt<{ syncMode: string }>([\n    {\n      type: 'list',\n      name: 'syncMode',\n      message: t('prompts.quickSync.mode'),\n      choices: [\n        { name: t('prompts.quickSync.mode.syncAll'), value: 'all' },\n        { name: t('prompts.quickSync.mode.customize'), value: 'customize' },\n        { name: t('prompts.quickSync.mode.cancel'), value: 'cancel' },\n      ],\n    },\n  ]);\n\n  if (syncMode === 'cancel') return;\n\n  let options: QuickSyncOptions;\n\n  if (syncMode === 'all') {\n    // Sync all with default targets\n    options = {\n      skipAgents: false,\n      skipSkills: false,\n      skipDocs: false,\n      agentTargets: ['claude', 'github'],\n      skillTargets: ['claude', 'github', 'windsurf', 'codex', 'antigravity'],\n      docTargets: ['cursor', 'claude', 'github', 'gemini', 'agents'],\n      force: false,\n      dryRun: false,\n      verbose: false,\n    };\n  } else {\n    // Customize: show the existing checkbox prompts\n    const { components } = await inquirer.prompt<{ components: string[] }>([\n      {\n        type: 'checkbox',\n        name: 'components',\n        message: t('prompts.quickSync.selectComponents'),\n        choices: [\n          { name: t('prompts.quickSync.components.agents'), value: 'agents', checked: true },\n          { name: t('prompts.quickSync.components.skills'), value: 'skills', checked: true },\n          { name: t('prompts.quickSync.components.docs'), value: 'docs', checked: true },\n        ],\n      },\n    ]);\n\n    if (components.length === 0) {\n      ui.displayWarning(t('prompts.quickSync.noComponentsSelected'));\n      return;\n    }\n\n    let agentTargets: string[] | undefined;\n    let skillTargets: string[] | undefined;\n    let docTargets: string[] | undefined;\n\n    if (components.includes('agents')) {\n      const { targets } = await inquirer.prompt<{ targets: string[] }>([\n        {\n          type: 'checkbox',\n          name: 'targets',\n          message: t('prompts.quickSync.selectAgentTargets'),\n          choices: [\n            { name: '.claude/agents (Claude Code)', value: 'claude', checked: true },\n            { name: '.github/agents (GitHub Copilot)', value: 'github', checked: true },\n            { name: '.cursor/agents (Cursor AI)', value: 'cursor', checked: false },\n            { name: '.windsurf/agents (Windsurf/Codeium)', value: 'windsurf', checked: false },\n            { name: '.cline/agents (Cline)', value: 'cline', checked: false },\n            { name: '.continue/agents (Continue.dev)', value: 'continue', checked: false },\n          ],\n        },\n      ]);\n      agentTargets = targets.length > 0 ? targets : undefined;\n    }\n\n    if (components.includes('skills')) {\n      const { targets } = await inquirer.prompt<{ targets: string[] }>([\n        {\n          type: 'checkbox',\n          name: 'targets',\n          message: t('prompts.quickSync.selectSkillTargets'),\n          choices: [\n            { name: '.claude/skills (Claude Code)', value: 'claude', checked: true },\n            { name: '.github/skills (GitHub Copilot)', value: 'github', checked: true },\n            { name: '.windsurf/skills (Windsurf)', value: 'windsurf', checked: true },\n            { name: '.codex/skills (Codex compatibility)', value: 'codex', checked: true },\n            { name: '.agents/workflows (Google Antigravity)', value: 'antigravity', checked: true },\n            { name: '.gemini/skills (Gemini compatibility)', value: 'gemini', checked: false },\n          ],\n        },\n      ]);\n      skillTargets = targets.length > 0 ? targets : undefined;\n    }\n\n    if (components.includes('docs')) {\n      const { targets } = await inquirer.prompt<{ targets: string[] }>([\n        {\n          type: 'checkbox',\n          name: 'targets',\n          message: t('prompts.quickSync.selectDocTargets'),\n          choices: [\n            { name: '.cursor/rules (Cursor AI)', value: 'cursor', checked: true },\n            { name: 'CLAUDE.md (Claude Code)', value: 'claude', checked: true },\n            { name: '.github/copilot-instructions.md (GitHub Copilot)', value: 'github', checked: true },\n            { name: 'GEMINI.md (Gemini CLI)', value: 'gemini', checked: true },\n            { name: 'AGENTS.md (Universal)', value: 'agents', checked: true },\n            { name: '.windsurf/rules (Windsurf)', value: 'windsurf', checked: false },\n            { name: '.clinerules (Cline)', value: 'cline', checked: false },\n            { name: 'CONVENTIONS.md (Aider)', value: 'aider', checked: false },\n          ],\n        },\n      ]);\n      docTargets = targets.length > 0 ? targets : undefined;\n    }\n\n    options = {\n      skipAgents: !components.includes('agents'),\n      skipSkills: !components.includes('skills'),\n      skipDocs: !components.includes('docs'),\n      agentTargets,\n      skillTargets,\n      docTargets,\n      force: false,\n      dryRun: false,\n      verbose: false,\n    };\n  }\n\n  const quickSyncService = new QuickSyncService({\n    ui,\n    t,\n    version: VERSION,\n  });\n\n  await quickSyncService.run(projectPath, options);\n\n  ui.displaySuccess(t('success.quickSync.complete'));\n}\n\n// ============================================================================\n// Reverse Quick Sync - Import from AI tool directories\n// ============================================================================\n\nasync function runReverseSync(): Promise<void> {\n  const projectPath = process.cwd();\n\n  // Create service\n  const reverseSyncService = new ReverseQuickSyncService({\n    ui,\n    t,\n    version: VERSION,\n  });\n\n  // Step 1: Detect available tools\n  ui.startSpinner(t('prompts.reverseSync.detecting'));\n  const detection = await reverseSyncService.detect(projectPath);\n  ui.stopSpinner();\n\n  if (detection.summary.totalFiles === 0) {\n    ui.displayWarning(t('prompts.reverseSync.noFilesFound'));\n    return;\n  }\n\n  // Display detection summary\n  console.log('');\n  console.log(t('prompts.reverseSync.detected'));\n  console.log('');\n  for (const tool of detection.tools) {\n    if (tool.detected) {\n      const parts: string[] = [];\n      if (tool.counts.rules > 0) parts.push(`${tool.counts.rules} rules`);\n      if (tool.counts.agents > 0) parts.push(`${tool.counts.agents} agents`);\n      if (tool.counts.skills > 0) parts.push(`${tool.counts.skills} skills`);\n      console.log(`  ${colors.success('✓')} ${colors.primary(tool.displayName)} (${parts.join(', ')})`);\n    }\n  }\n  console.log('');\n\n  // Step 2: Select components to import\n  const { components } = await inquirer.prompt<{ components: string[] }>([\n    {\n      type: 'checkbox',\n      name: 'components',\n      message: t('prompts.reverseSync.selectComponents'),\n      choices: [\n        {\n          name: `Rules (${detection.summary.totalRules} files)`,\n          value: 'rules',\n          checked: detection.summary.totalRules > 0,\n          disabled: detection.summary.totalRules === 0,\n        },\n        {\n          name: `Agents (${detection.summary.totalAgents} files)`,\n          value: 'agents',\n          checked: detection.summary.totalAgents > 0,\n          disabled: detection.summary.totalAgents === 0,\n        },\n        {\n          name: `Skills (${detection.summary.totalSkills} files)`,\n          value: 'skills',\n          checked: detection.summary.totalSkills > 0,\n          disabled: detection.summary.totalSkills === 0,\n        },\n      ],\n    },\n  ]);\n\n  if (components.length === 0) {\n    ui.displayWarning(t('prompts.reverseSync.noComponentsSelected'));\n    return;\n  }\n\n  // Step 3: Select merge strategy\n  const { mergeStrategy } = await inquirer.prompt<{ mergeStrategy: MergeStrategy }>([\n    {\n      type: 'list',\n      name: 'mergeStrategy',\n      message: t('prompts.reverseSync.mergeStrategy'),\n      choices: [\n        { name: t('prompts.reverseSync.strategy.skip'), value: 'skip' },\n        { name: t('prompts.reverseSync.strategy.overwrite'), value: 'overwrite' },\n        { name: t('prompts.reverseSync.strategy.merge'), value: 'merge' },\n        { name: t('prompts.reverseSync.strategy.rename'), value: 'rename' },\n      ],\n    },\n  ]);\n\n  // Step 4: Run import\n  const result = await reverseSyncService.run(projectPath, {\n    skipRules: !components.includes('rules'),\n    skipAgents: !components.includes('agents'),\n    skipSkills: !components.includes('skills'),\n    mergeStrategy,\n    verbose: false,\n  });\n\n  // Display result\n  const totalImported = result.rulesImported + result.agentsImported + result.skillsImported;\n  if (totalImported > 0) {\n    ui.displaySuccess(t('success.reverseSync.complete', { count: totalImported }));\n  }\n}\n\n// ============================================================================\n// Settings - Submenu for configuration\n// ============================================================================\n\nasync function runSettings(): Promise<void> {\n  // Directly show language selection (the only setting currently)\n  await selectLocale();\n}\n\nasync function renderStartupSplash(\n  directory: string,\n  titleOverride?: string\n): Promise<void> {\n  console.log('');\n  console.log(renderSplashScreen({\n    title: titleOverride || packageNameToDisplayName(PACKAGE_NAME),\n    version: VERSION,\n    lines: [\n      {\n        label: t('ui.splash.directoryLabel'),\n        value: formatSplashDirectory(directory)\n      }\n    ]\n  }));\n  console.log('');\n}\n\nfunction filterOutLocaleArgs(args: string[]): string[] {\n  const filtered: string[] = [];\n  for (let index = 0; index < args.length; index += 1) {\n    const current = args[index];\n    if (current === '--lang' || current === '--language' || current === '-l') {\n      index += 1;\n      continue;\n    }\n    if (current.startsWith('--lang=') || current.startsWith('--language=')) {\n      continue;\n    }\n    filtered.push(current);\n  }\n  return filtered;\n}\n\nasync function main(): Promise<void> {\n  const userArgs = process.argv.slice(2);\n  void scheduleVersionCheck();\n  const meaningfulArgs = filterOutLocaleArgs(userArgs);\n  if (meaningfulArgs.length === 0) {\n    await runInteractive();\n    return;\n  }\n\n  await program.parseAsync(process.argv);\n}\n\n/**\n * Check if an error is from user interrupt (Ctrl+C)\n */\nfunction isUserInterrupt(error: unknown): boolean {\n  if (error instanceof Error) {\n    // Inquirer's ExitPromptError when user presses Ctrl+C\n    if (error.name === 'ExitPromptError') return true;\n    // Check message patterns\n    if (error.message.includes('force closed')) return true;\n    if (error.message.includes('User force closed')) return true;\n  }\n  return false;\n}\n\n/**\n * Handle graceful exit\n */\nfunction handleGracefulExit(): void {\n  console.log('');\n  ui.displaySuccess(t('success.interactive.goodbye'));\n  process.exit(0);\n}\n\nif (require.main === module) {\n  main().catch(error => {\n    if (isUserInterrupt(error)) {\n      handleGracefulExit();\n    } else {\n      ui.displayError(t('errors.cli.executionFailed'), error as Error);\n      process.exit(1);\n    }\n  });\n}\n"
  },
  {
    "path": "src/mcp/bin.test.ts",
    "content": "const mockRun = jest.fn();\nconst mockResolveMcpInstallToolSelection = jest.fn();\nconst mockStartMCPServer = jest.fn();\n\njest.mock('../cli', () => ({\n  MCPInstallService: jest.fn().mockImplementation(() => ({\n    run: mockRun,\n  })),\n  resolveMcpInstallToolSelection: (...args: unknown[]) =>\n    mockResolveMcpInstallToolSelection(...args),\n}));\n\njest.mock('./index', () => ({\n  startMCPServer: (...args: unknown[]) => mockStartMCPServer(...args),\n}));\n\njest.mock('../utils/processShutdown', () => ({\n  registerProcessShutdown: jest.fn(),\n}));\n\nimport { parseMcpPackageArgs, runMcpPackage } from './bin';\n\ndescribe('mcp package bin', () => {\n  const originalIsTTY = process.stdin.isTTY;\n  let stdoutWriteSpy: jest.SpyInstance;\n  let stderrWriteSpy: jest.SpyInstance;\n\n  beforeEach(() => {\n    mockRun.mockReset();\n    mockResolveMcpInstallToolSelection.mockReset();\n    mockStartMCPServer.mockReset();\n    mockRun.mockResolvedValue({\n      filesCreated: 1,\n      filesFailed: 0,\n      filesSkipped: 0,\n      directoriesCreated: 0,\n      errors: [],\n      warnings: [],\n      installations: [\n        {\n          tool: 'codex',\n          toolDisplayName: 'Codex CLI',\n          configPath: '/tmp/.codex/config.toml',\n          action: 'created',\n          dryRun: false,\n        },\n      ],\n    });\n    mockResolveMcpInstallToolSelection.mockResolvedValue('codex');\n    stdoutWriteSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);\n    stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);\n  });\n\n  afterEach(() => {\n    stdoutWriteSpy.mockRestore();\n    stderrWriteSpy.mockRestore();\n    Object.defineProperty(process.stdin, 'isTTY', {\n      configurable: true,\n      value: originalIsTTY,\n    });\n  });\n\n  it('parses install command arguments', () => {\n    expect(\n      parseMcpPackageArgs(['install', 'cursor', '--local', '--dry-run', '--verbose'])\n    ).toEqual({\n      command: 'install',\n      tool: 'cursor',\n      global: false,\n      dryRun: true,\n      verbose: true,\n    });\n  });\n\n  it('defaults to serve mode when no subcommand is provided', () => {\n    expect(parseMcpPackageArgs([])).toEqual({\n      command: 'serve',\n    });\n  });\n\n  it('parses serve options without requiring the serve keyword', () => {\n    expect(parseMcpPackageArgs(['--repo-path', '/tmp/repo', '--verbose'])).toEqual({\n      command: 'serve',\n      repoPath: '/tmp/repo',\n      verbose: true,\n    });\n  });\n\n  it('uses interactive tool selection for install when no tool is provided in a TTY', async () => {\n    Object.defineProperty(process.stdin, 'isTTY', {\n      configurable: true,\n      value: true,\n    });\n\n    await runMcpPackage(['install']);\n\n    expect(mockResolveMcpInstallToolSelection).toHaveBeenCalledWith(\n      expect.objectContaining({\n        selectedTool: undefined,\n        isInteractive: true,\n      })\n    );\n    expect(mockRun).toHaveBeenCalledWith(\n      expect.objectContaining({\n        tool: 'codex',\n        global: true,\n      })\n    );\n  });\n\n  it('falls back to non-interactive install selection outside a TTY', async () => {\n    Object.defineProperty(process.stdin, 'isTTY', {\n      configurable: true,\n      value: false,\n    });\n    mockResolveMcpInstallToolSelection.mockResolvedValue('all');\n\n    await runMcpPackage(['install', '--local']);\n\n    expect(mockResolveMcpInstallToolSelection).toHaveBeenCalledWith(\n      expect.objectContaining({\n        selectedTool: undefined,\n        isInteractive: false,\n      })\n    );\n    expect(mockRun).toHaveBeenCalledWith(\n      expect.objectContaining({\n        tool: 'all',\n        global: false,\n      })\n    );\n  });\n});\n"
  },
  {
    "path": "src/mcp/bin.ts",
    "content": "#!/usr/bin/env node\n\n/**\n * Dedicated MCP package binary for `@dotcontext/mcp`.\n *\n * Default behavior starts the MCP server.\n * `install` configures supported AI tools to use this package as the MCP server.\n */\n\nimport { startMCPServer } from './index';\nimport { registerProcessShutdown } from '../utils/processShutdown';\nimport { createTranslator, detectLocale } from '../utils/i18n';\nimport { themedSelect } from '../utils/themedPrompt';\nimport { MCPInstallService, resolveMcpInstallToolSelection } from '../cli';\nimport { VERSION } from '../version';\n\ntype ParsedArgs = {\n  command: 'serve' | 'install' | 'help';\n  tool?: string;\n  repoPath?: string;\n  verbose?: boolean;\n  dryRun?: boolean;\n  global?: boolean;\n};\n\nconst consoleUI = {\n  displayWelcome: () => {},\n  displayError: (message: string, error?: Error) => {\n    const details = error ? `\\n${error.message}` : '';\n    process.stderr.write(`${message}${details}\\n`);\n  },\n  displaySuccess: (message: string) => {\n    process.stdout.write(`${message}\\n`);\n  },\n  displayInfo: (title: string, message: string) => {\n    process.stdout.write(`${title}: ${message}\\n`);\n  },\n  displayWarning: (message: string) => {\n    process.stderr.write(`${message}\\n`);\n  },\n  displayList: () => {},\n  displayTable: () => {},\n  displayJson: () => {},\n  displayProjectConfiguration: () => {},\n  displayFileTypeDistribution: () => {},\n  displayGenerationSummary: () => {},\n  startSpinner: () => {},\n  updateSpinner: () => {},\n  stopSpinner: () => {},\n  displayAnalysisComplete: () => {},\n  displayBox: () => {},\n  displaySection: () => {},\n  displayStep: () => {},\n  displayDiff: () => {},\n  displaySkillHeader: () => {},\n  displaySkillDefinition: () => {},\n  displaySkillExamples: () => {},\n  displaySkillContent: () => {},\n} as any;\n\nexport function parseMcpPackageArgs(argv: string[]): ParsedArgs {\n  const args = [...argv];\n  const first = args[0];\n\n  if (!first || first === 'serve') {\n    return parseServeArgs(first === 'serve' ? args.slice(1) : args);\n  }\n\n  if (first === 'install') {\n    return parseInstallArgs(args.slice(1));\n  }\n\n  if (first === '--help' || first === '-h' || first === 'help') {\n    return { command: 'help' };\n  }\n\n  if (first.startsWith('-')) {\n    return parseServeArgs(args);\n  }\n\n  return { command: 'help' };\n}\n\nfunction parseServeArgs(args: string[]): ParsedArgs {\n  const parsed: ParsedArgs = { command: 'serve' };\n\n  for (let i = 0; i < args.length; i++) {\n    const arg = args[i];\n    if ((arg === '--repo-path' || arg === '-r') && args[i + 1]) {\n      parsed.repoPath = args[++i];\n      continue;\n    }\n    if (arg === '--verbose' || arg === '-v') {\n      parsed.verbose = true;\n      continue;\n    }\n    if (arg === '--help' || arg === '-h') {\n      return { command: 'help' };\n    }\n  }\n\n  return parsed;\n}\n\nfunction parseInstallArgs(args: string[]): ParsedArgs {\n  const parsed: ParsedArgs = {\n    command: 'install',\n    global: true,\n  };\n\n  for (let i = 0; i < args.length; i++) {\n    const arg = args[i];\n\n    if (!arg.startsWith('-') && !parsed.tool) {\n      parsed.tool = arg;\n      continue;\n    }\n\n    if (arg === '--local' || arg === '-l') {\n      parsed.global = false;\n      continue;\n    }\n\n    if (arg === '--global' || arg === '-g') {\n      parsed.global = true;\n      continue;\n    }\n\n    if (arg === '--dry-run') {\n      parsed.dryRun = true;\n      continue;\n    }\n\n    if (arg === '--verbose' || arg === '-v') {\n      parsed.verbose = true;\n      continue;\n    }\n\n    if ((arg === '--repo-path' || arg === '-r') && args[i + 1]) {\n      parsed.repoPath = args[++i];\n      continue;\n    }\n\n    if (arg === '--help' || arg === '-h') {\n      return { command: 'help' };\n    }\n  }\n\n  return parsed;\n}\n\nfunction printHelp(): void {\n  process.stdout.write(`@dotcontext/mcp\n\nUsage:\n  npx @dotcontext/mcp install [tool] [--local|--global] [--dry-run] [--verbose]\n  npx -y @dotcontext/mcp@latest [serve] [--repo-path <path>] [--verbose]\n\nExamples:\n  npx @dotcontext/mcp install\n  npx @dotcontext/mcp install codex\n  npx @dotcontext/mcp install cursor --local\n  npx -y @dotcontext/mcp@latest\n`);\n}\n\nasync function runInstallCommand(args: ParsedArgs, argv: string[]): Promise<void> {\n  const locale = detectLocale(argv, process.env.DOTCONTEXT_LANG, [\n    process.env.LC_ALL,\n    process.env.LC_MESSAGES,\n    process.env.LANG,\n  ]);\n  const t = createTranslator(locale);\n  const service = new MCPInstallService({\n    ui: consoleUI,\n    t,\n    version: VERSION,\n  });\n\n  const selectedTool = await resolveMcpInstallToolSelection({\n    selectedTool: args.tool,\n    isInteractive: Boolean(process.stdin.isTTY),\n    service,\n    t,\n    promptTool: ({ message, choices }) => themedSelect({ message, choices }),\n  });\n\n  const result = await service.run({\n    tool: selectedTool,\n    global: args.global,\n    dryRun: args.dryRun,\n    verbose: args.verbose,\n    repoPath: args.repoPath || process.cwd(),\n  });\n\n  if (result.installations.length > 0) {\n    consoleUI.displayInfo('MCP', t('info.mcp.restartTools'));\n  }\n\n  if (args.tool && result.installations.length === 0) {\n    process.exit(1);\n  }\n\n  if (result.installations.length === 0 && result.filesFailed > 0) {\n    process.exit(1);\n  }\n}\n\nasync function runServeCommand(args: ParsedArgs): Promise<void> {\n  const server = await startMCPServer({\n    repoPath: args.repoPath,\n    verbose: args.verbose,\n  });\n\n  registerProcessShutdown(server, {\n    onError: (error) => {\n      if (args.verbose) {\n        process.stderr.write(`[mcp] Shutdown error: ${error}\\n`);\n      }\n    },\n    exit: (code) => process.exit(code),\n  });\n}\n\nexport async function runMcpPackage(argv: string[]): Promise<void> {\n  const parsed = parseMcpPackageArgs(argv);\n\n  if (parsed.command === 'help') {\n    printHelp();\n    return;\n  }\n\n  if (parsed.command === 'install') {\n    await runInstallCommand(parsed, argv);\n    return;\n  }\n\n  await runServeCommand(parsed);\n}\n\nif (require.main === module) {\n  void runMcpPackage(process.argv.slice(2)).catch((error) => {\n    console.error(error instanceof Error ? error.message : String(error));\n    process.exit(1);\n  });\n}\n"
  },
  {
    "path": "src/mcp/index.test.ts",
    "content": "import {\n  AIContextMCPServer,\n  startMCPServer,\n  handleExplore,\n  handleContext,\n  handlePlan,\n  handleAgent,\n  handleSkill,\n  handleHarness,\n  handleWorkflowInit,\n  handleWorkflowStatus,\n  handleWorkflowAdvance,\n  handleWorkflowManage,\n} from './index';\n\ndescribe('MCP boundary exports', () => {\n  it('exposes MCP transport and gateway handlers', () => {\n    expect(AIContextMCPServer).toBeDefined();\n    expect(startMCPServer).toBeDefined();\n    expect(handleExplore).toBeDefined();\n    expect(handleContext).toBeDefined();\n    expect(handlePlan).toBeDefined();\n    expect(handleAgent).toBeDefined();\n    expect(handleSkill).toBeDefined();\n    expect(handleHarness).toBeDefined();\n    expect(handleWorkflowInit).toBeDefined();\n    expect(handleWorkflowStatus).toBeDefined();\n    expect(handleWorkflowAdvance).toBeDefined();\n    expect(handleWorkflowManage).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "src/mcp/index.ts",
    "content": "/**\n * MCP boundary exports.\n *\n * This module defines the protocol adapter surface that is expected to\n * become the future `dotcontext/mcp` package or adapter module.\n *\n * Dependency intent:\n * cli -> harness <- mcp\n */\n\nexport {\n  AIContextMCPServer,\n  startMCPServer,\n  type MCPServerOptions,\n} from '../services/mcp/mcpServer';\n\nexport {\n  handleExplore,\n  handleContext,\n  handleSync,\n  handlePlan,\n  handleAgent,\n  handleSkill,\n  handleHarness,\n  handleWorkflowInit,\n  handleWorkflowStatus,\n  handleWorkflowAdvance,\n  handleWorkflowManage,\n  type ExploreAction,\n  type ContextAction,\n  type SyncAction,\n  type PlanAction,\n  type AgentAction,\n  type SkillAction,\n  type HarnessAction,\n  type ExploreParams,\n  type ContextParams,\n  type SyncParams,\n  type PlanParams,\n  type AgentParams,\n  type SkillParams,\n  type HarnessParams,\n  type WorkflowInitParams,\n  type WorkflowStatusParams,\n  type WorkflowAdvanceParams,\n  type WorkflowManageParams,\n  type MCPToolResponse,\n} from '../services/mcp/gatewayTools';\n"
  },
  {
    "path": "src/prompts/defaults.ts",
    "content": "export const UPDATE_SCAFFOLD_PROMPT_FALLBACK = `# Prompt: Update Repository Documentation and Agent Playbooks\n\n## Purpose\nYou are an AI assistant responsible for refreshing the documentation (\\`docs/\\`) and agent playbooks (\\`agents/\\`). Your goal is to bring every guide up to date with the latest repository state and maintain cross-references between docs and agent instructions.\n\n## Context Gathering\n1. Review the repository structure and recent changes.\n2. Inspect \\`package.json\\`, CI configuration, and any release or roadmap notes.\n3. Check \\`docs/README.md\\` for the current document map.\n\n## Update Procedure\n1. **Update Documentation**\n   - Replace TODO placeholders with accurate, current information.\n   - Verify that links between docs remain valid.\n   - If you add new guides or sections, update \\`docs/README.md\\`.\n\n2. **Agent Playbook Alignment**\n   - For each change in \\`docs/\\`, adjust the related \\`agents/*.md\\` playbooks.\n   - Update responsibilities, best practices, and documentation touchpoints.\n\n## Acceptance Criteria\n- No unresolved TODO placeholders remain unless they require explicit human input.\n- Agent playbooks list accurate responsibilities and best practices.\n- Changes are self-contained, well-formatted Markdown.\n\n## Deliverables\n- Updated Markdown files.\n`;\n\n/**\n * Prompt for AI agents to enhance scaffolding via MCP tools.\n * This prompt MUST be included in ALL scaffolding generation responses.\n */\nexport const MCP_SCAFFOLD_ENHANCEMENT_PROMPT = `# AI Enhancement Required\n\nYour scaffolding has been generated but contains placeholder content that MUST be enhanced.\n\n## Required Actions\n\n1. **For each generated file**, call \\`fillSingleFile\\` to get:\n   - Semantic context from the codebase\n   - Scaffold structure with section guidance\n   - Current file content to enhance\n\n2. **Generate rich content** based on the context provided:\n   - Replace ALL placeholders with accurate, codebase-specific information\n   - Follow the tone and audience guidelines in the scaffold structure\n   - Include cross-references to related docs/agents\n   - Use concrete examples from the actual codebase\n\n3. **Write the enhanced content** using the Write tool\n\n## Quality Checklist\n\n- [ ] No TODO placeholders remain\n- [ ] All sections have concrete, actionable content\n- [ ] Cross-references point to existing files\n- [ ] Examples are drawn from actual codebase code\n- [ ] Terminology matches project conventions\n\n## Example Workflow\n\n\\`\\`\\`\n1. context({ action: \"listToFill\" })           → Get list of files needing content\n2. context({ action: \"fillSingle\", filePath }) → Get context for each file\n3. Generate content based on semantic context\n4. Write enhanced content to file\n5. Repeat for all files\n\\`\\`\\`\n\nDO NOT consider scaffolding complete until ALL files have been enhanced with codebase-specific content.\n`;\n\nexport const UPDATE_PLAN_PROMPT_FALLBACK = `# Prompt: Update Collaboration Plans\n\n## Purpose\nYou are an AI assistant responsible for refining collaboration plans. Each plan orchestrates work across documentation guides (\\`docs/\\`) and agent playbooks (\\`agents/\\`). Your goal is to replace TODOs with actionable guidance.\n\n## Update Procedure\n1. **Task Snapshot**\n   - Summarize the primary goal and success signal in concrete terms.\n   - List authoritative references (docs, issues, specs).\n\n2. **Agent Alignment**\n   - For each agent in the lineup, describe why they are involved.\n   - Ensure playbook links match the referenced agent files.\n\n3. **Documentation Touchpoints**\n   - Map each plan stage to the docs excerpts provided.\n\n4. **Working Phases**\n   - Break the work into sequential phases with numbered steps and deliverables.\n   - Reference documentation and agent resources for each phase.\n\n5. **Evidence & Follow-up**\n   - Specify artefacts to capture (PR links, test runs, change logs).\n   - Record any follow-up actions.\n\n## Acceptance Criteria\n- TODOs are resolved with concrete information.\n- Tables reference existing files.\n- Phases provide actionable guidance.\n\n## Deliverables\n- Updated plan Markdown returned verbatim.\n`;\n"
  },
  {
    "path": "src/services/autoFill/autoFillService.ts",
    "content": "/**\n * AutoFillService - Generates documentation content from semantic analysis\n *\n * This service fills scaffold templates with actual codebase data without\n * requiring an LLM API key. It maps semantic analysis results to scaffold\n * sections, providing useful starter content based on code structure.\n */\n\nimport * as path from 'path';\nimport { SemanticContext, ExtractedSymbol, ArchitectureLayer, DetectedPattern } from '../semantic/types';\nimport { StackInfo } from '../stack/stackDetector';\nimport { ScaffoldStructure, ScaffoldSection } from '../../generators/shared/structures/types';\n\nexport interface AutoFillContext {\n  semantics?: SemanticContext;\n  stackInfo?: StackInfo;\n  repoPath: string;\n  topLevelDirectories?: string[];\n}\n\ninterface DirectoryStat {\n  name: string;\n  fileCount: number;\n}\n\nconst SEMANTIC_SNAPSHOT_REFERENCE =\n  'Use `context({ action: \"getMap\", section: \"all\" })` to inspect the generated semantic snapshot for stack, architecture, key files, and dependency hotspots.';\n\nexport class AutoFillService {\n  /**\n   * Fill a documentation scaffold with semantic data\n   */\n  fillDocumentation(\n    docName: string,\n    structure: ScaffoldStructure,\n    ctx: AutoFillContext\n  ): string {\n    const sections: string[] = [];\n    const sortedSections = [...structure.sections].sort((a, b) => a.order - b.order);\n\n    for (const section of sortedSections) {\n      sections.push(this.fillSection(section, docName, ctx));\n    }\n\n    // Add cross-references section if present\n    if (structure.linkTo && structure.linkTo.length > 0) {\n      sections.push('## Related Resources\\n');\n      for (const link of structure.linkTo) {\n        sections.push(`- [${link}](./${link})`);\n      }\n      sections.push('');\n    }\n\n    return sections.join('\\n');\n  }\n\n  /**\n   * Fill an agent playbook scaffold with semantic data\n   */\n  fillAgent(\n    agentType: string,\n    structure: ScaffoldStructure,\n    ctx: AutoFillContext\n  ): string {\n    const sections: string[] = [];\n    const sortedSections = [...structure.sections].sort((a, b) => a.order - b.order);\n\n    for (const section of sortedSections) {\n      sections.push(this.fillAgentSection(section, agentType, ctx));\n    }\n\n    return sections.join('\\n');\n  }\n\n  private fillSection(\n    section: ScaffoldSection,\n    docName: string,\n    ctx: AutoFillContext\n  ): string {\n    const heading = '#'.repeat(section.headingLevel || 2) + ' ' + section.heading;\n    const content = this.generateDocContent(section, docName, ctx);\n    return `${heading}\\n\\n${content}\\n`;\n  }\n\n  private fillAgentSection(\n    section: ScaffoldSection,\n    agentType: string,\n    ctx: AutoFillContext\n  ): string {\n    const heading = '#'.repeat(section.headingLevel || 2) + ' ' + section.heading;\n    const content = this.generateAgentContent(section, agentType, ctx);\n    return `${heading}\\n\\n${content}\\n`;\n  }\n\n  private generateDocContent(\n    section: ScaffoldSection,\n    docName: string,\n    ctx: AutoFillContext\n  ): string {\n    // Map document sections to semantic data\n    if (docName === 'project-overview') {\n      return this.fillProjectOverviewSection(section, ctx);\n    }\n\n    if (docName === 'architecture') {\n      return this.fillArchitectureSection(section, ctx);\n    }\n\n    if (docName === 'tooling') {\n      return this.fillToolingSection(section, ctx);\n    }\n\n    // Default: use guidance as placeholder\n    return `<!-- ${section.guidance} -->\\n\\n_Content to be added._`;\n  }\n\n  private generateAgentContent(\n    section: ScaffoldSection,\n    agentType: string,\n    ctx: AutoFillContext\n  ): string {\n    const { semantics, stackInfo } = ctx;\n\n    // Fill \"Key Files\" section\n    if (section.heading.toLowerCase().includes('key files') || section.heading.toLowerCase().includes('relevant files')) {\n      return this.generateKeyFilesForAgent(agentType, ctx);\n    }\n\n    // Fill \"Relevant Symbols\" section\n    if (section.heading.toLowerCase().includes('symbol') || section.heading.toLowerCase().includes('classes') || section.heading.toLowerCase().includes('functions')) {\n      return this.generateRelevantSymbolsForAgent(agentType, ctx);\n    }\n\n    // Fill \"Technology Context\" section\n    if (section.heading.toLowerCase().includes('technology') || section.heading.toLowerCase().includes('stack')) {\n      if (stackInfo) {\n        return this.formatTechStack(stackInfo);\n      }\n    }\n\n    // Default: use guidance as placeholder\n    return `<!-- ${section.guidance} -->\\n\\n_Content to be added._`;\n  }\n\n  // ===== Project Overview Sections =====\n\n  private fillProjectOverviewSection(section: ScaffoldSection, ctx: AutoFillContext): string {\n    const { semantics, stackInfo, repoPath, topLevelDirectories } = ctx;\n\n    switch (section.heading) {\n      case 'Project Overview':\n        return this.generateProjectOverview(ctx);\n\n      case 'Codebase Reference':\n        return `> **Semantic Snapshot**: ${SEMANTIC_SNAPSHOT_REFERENCE}`;\n\n      case 'Quick Facts':\n        return this.generateQuickFacts(ctx);\n\n      case 'Entry Points':\n        return this.formatEntryPoints(semantics?.architecture?.entryPoints || []);\n\n      case 'Key Exports':\n        return this.formatPublicAPI(semantics?.architecture?.publicAPI?.slice(0, 10) || []);\n\n      case 'File Structure & Code Organization':\n        return this.formatDirectoryStructure(topLevelDirectories || []);\n\n      case 'Technology Stack Summary':\n        return this.formatTechStack(stackInfo);\n\n      case 'Core Framework Stack':\n        return this.formatFrameworks(stackInfo);\n\n      case 'Getting Started Checklist':\n        return this.generateGettingStarted(stackInfo);\n\n      default:\n        return `<!-- ${section.guidance} -->\\n\\n_Content to be added._`;\n    }\n  }\n\n  // ===== Architecture Sections =====\n\n  private fillArchitectureSection(section: ScaffoldSection, ctx: AutoFillContext): string {\n    const { semantics, topLevelDirectories } = ctx;\n\n    switch (section.heading) {\n      case 'Architectural Layers':\n        return this.formatLayers(semantics?.architecture?.layers || []);\n\n      case 'Detected Design Patterns':\n        return this.formatPatterns(semantics?.architecture?.patterns || []);\n\n      case 'Entry Points':\n        return this.formatEntryPoints(semantics?.architecture?.entryPoints || []);\n\n      case 'Public API':\n        return this.formatPublicAPITable(semantics?.architecture?.publicAPI?.slice(0, 15) || []);\n\n      case 'Top Directories Snapshot':\n        return this.formatDirectoryStructure(topLevelDirectories || []);\n\n      default:\n        return `<!-- ${section.guidance} -->\\n\\n_Content to be added._`;\n    }\n  }\n\n  // ===== Tooling Sections =====\n\n  private fillToolingSection(section: ScaffoldSection, ctx: AutoFillContext): string {\n    const { stackInfo } = ctx;\n\n    if (section.heading.toLowerCase().includes('build') || section.heading.toLowerCase().includes('tool')) {\n      return this.formatBuildTools(stackInfo);\n    }\n\n    if (section.heading.toLowerCase().includes('test')) {\n      return this.formatTestFrameworks(stackInfo);\n    }\n\n    if (section.heading.toLowerCase().includes('package')) {\n      return this.formatPackageManager(stackInfo);\n    }\n\n    return `<!-- ${section.guidance} -->\\n\\n_Content to be added._`;\n  }\n\n  // ===== Content Generators =====\n\n  private generateProjectOverview(ctx: AutoFillContext): string {\n    const { stackInfo, semantics } = ctx;\n    const lines: string[] = [];\n\n    if (stackInfo) {\n      const lang = stackInfo.primaryLanguage || 'multi-language';\n      const frameworks = stackInfo.frameworks.slice(0, 3).join(', ') || 'custom';\n\n      lines.push(`This is a **${lang}** project${frameworks !== 'custom' ? ` using ${frameworks}` : ''}.`);\n    }\n\n    if (semantics?.stats) {\n      lines.push(`The codebase contains **${semantics.stats.totalFiles} files** with **${semantics.stats.totalSymbols} symbols**.`);\n    }\n\n    return lines.join(' ') || '_Project description to be added._';\n  }\n\n  private generateQuickFacts(ctx: AutoFillContext): string {\n    const { semantics, stackInfo, repoPath } = ctx;\n    const lines: string[] = [];\n\n    lines.push(`- **Root**: \\`${repoPath}\\``);\n\n    if (stackInfo?.languages.length) {\n      const langs = stackInfo.languages.slice(0, 3).map(l => `${l}`).join(', ');\n      lines.push(`- **Languages**: ${langs}`);\n    }\n\n    if (semantics?.stats) {\n      lines.push(`- **Total Files**: ${semantics.stats.totalFiles}`);\n      lines.push(`- **Total Symbols**: ${semantics.stats.totalSymbols}`);\n    }\n\n    if (semantics?.architecture?.entryPoints?.length) {\n      const ep = semantics.architecture.entryPoints[0];\n      lines.push(`- **Entry Point**: \\`${ep}\\``);\n    }\n\n    lines.push(`- **Semantic Snapshot**: ${SEMANTIC_SNAPSHOT_REFERENCE}`);\n\n    return lines.join('\\n');\n  }\n\n  private formatEntryPoints(entryPoints: string[]): string {\n    if (!entryPoints.length) {\n      return '_No entry points detected. Add main entry files here._';\n    }\n\n    return entryPoints.map(ep => `- [\\`${ep}\\`](../${ep})`).join('\\n');\n  }\n\n  private formatPublicAPI(symbols: ExtractedSymbol[]): string {\n    if (!symbols.length) {\n      return '_Document the main exported surfaces here._';\n    }\n\n    const lines = symbols.slice(0, 10).map(s => {\n      const relPath = s.location.file;\n      return `- \\`${s.name}\\` (${s.kind}) - ${path.basename(relPath)}:${s.location.line}`;\n    });\n\n    lines.push(`\\n> ${SEMANTIC_SNAPSHOT_REFERENCE}`);\n    return lines.join('\\n');\n  }\n\n  private formatPublicAPITable(symbols: ExtractedSymbol[]): string {\n    if (!symbols.length) {\n      return '_Document the main exported surfaces here._';\n    }\n\n    const lines = [\n      '| Symbol | Type | Location |',\n      '|--------|------|----------|',\n    ];\n\n    for (const s of symbols) {\n      const fileName = path.basename(s.location.file);\n      lines.push(`| \\`${s.name}\\` | ${s.kind} | ${fileName}:${s.location.line} |`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatDirectoryStructure(directories: string[]): string {\n    if (!directories.length) {\n      return '_Add top-level directory descriptions here._';\n    }\n\n    return directories\n      .slice(0, 10)\n      .map(dir => `- \\`${dir}/\\` — _describe purpose_`)\n      .join('\\n');\n  }\n\n  private formatTechStack(stackInfo?: StackInfo): string {\n    if (!stackInfo) {\n      return '_Technology stack to be documented._';\n    }\n\n    const lines: string[] = [];\n\n    if (stackInfo.primaryLanguage) {\n      lines.push(`**Primary Language**: ${stackInfo.primaryLanguage}`);\n    }\n\n    if (stackInfo.languages.length > 1) {\n      lines.push(`**Other Languages**: ${stackInfo.languages.filter(l => l !== stackInfo.primaryLanguage).join(', ')}`);\n    }\n\n    if (stackInfo.frameworks.length) {\n      lines.push(`**Frameworks**: ${stackInfo.frameworks.join(', ')}`);\n    }\n\n    if (stackInfo.buildTools.length) {\n      lines.push(`**Build Tools**: ${stackInfo.buildTools.join(', ')}`);\n    }\n\n    if (stackInfo.packageManager) {\n      lines.push(`**Package Manager**: ${stackInfo.packageManager}`);\n    }\n\n    return lines.join('\\n\\n') || '_Technology stack to be documented._';\n  }\n\n  private formatFrameworks(stackInfo?: StackInfo): string {\n    if (!stackInfo?.frameworks.length) {\n      return '_No frameworks detected._';\n    }\n\n    return stackInfo.frameworks.map(f => `- **${f}**`).join('\\n');\n  }\n\n  private formatBuildTools(stackInfo?: StackInfo): string {\n    if (!stackInfo?.buildTools.length) {\n      return '_No build tools detected._';\n    }\n\n    return stackInfo.buildTools.map(t => `- **${t}**`).join('\\n');\n  }\n\n  private formatTestFrameworks(stackInfo?: StackInfo): string {\n    if (!stackInfo?.testFrameworks.length) {\n      return '_No test frameworks detected._';\n    }\n\n    return stackInfo.testFrameworks.map(t => `- **${t}**`).join('\\n');\n  }\n\n  private formatPackageManager(stackInfo?: StackInfo): string {\n    if (!stackInfo?.packageManager) {\n      return '_Package manager not detected._';\n    }\n\n    return `Using **${stackInfo.packageManager}** for dependency management.`;\n  }\n\n  private formatLayers(layers: ArchitectureLayer[]): string {\n    if (!layers.length) {\n      return '_No architecture layers detected. Document your layers here._';\n    }\n\n    const lines = layers.map(layer => {\n      const dirs = layer.directories.slice(0, 3).map(d => `\\`${d}\\``).join(', ');\n      return `- **${layer.name}**: ${layer.description} (${dirs})`;\n    });\n\n    lines.push(`\\n> ${SEMANTIC_SNAPSHOT_REFERENCE}`);\n\n    return lines.join('\\n');\n  }\n\n  private formatPatterns(patterns: DetectedPattern[]): string {\n    if (!patterns.length) {\n      return '_No design patterns detected._';\n    }\n\n    const lines = [\n      '| Pattern | Confidence | Locations | Description |',\n      '|---------|------------|-----------|-------------|',\n    ];\n\n    for (const p of patterns) {\n      const confidence = `${Math.round(p.confidence * 100)}%`;\n      const locations = p.locations.slice(0, 2).map(l => `\\`${l.symbol}\\``).join(', ');\n      lines.push(`| ${p.name} | ${confidence} | ${locations} | ${p.description} |`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateGettingStarted(stackInfo?: StackInfo): string {\n    const lines: string[] = [];\n    let step = 1;\n\n    // Install dependencies\n    if (stackInfo?.packageManager) {\n      const installCmd = {\n        npm: 'npm install',\n        yarn: 'yarn',\n        pnpm: 'pnpm install',\n        bun: 'bun install',\n      }[stackInfo.packageManager] || 'npm install';\n\n      lines.push(`${step}. Install dependencies with \\`${installCmd}\\`.`);\n      step++;\n    }\n\n    // Run dev command\n    if (stackInfo?.primaryLanguage) {\n      if (['typescript', 'javascript'].includes(stackInfo.primaryLanguage)) {\n        lines.push(`${step}. Start development with \\`npm run dev\\` or \\`npm start\\`.`);\n      } else if (stackInfo.primaryLanguage === 'python') {\n        lines.push(`${step}. Activate virtual environment and run the main script.`);\n      } else if (stackInfo.primaryLanguage === 'go') {\n        lines.push(`${step}. Run \\`go run .\\` to start the application.`);\n      }\n      step++;\n    }\n\n    // Run tests\n    if (stackInfo?.testFrameworks.length) {\n      const testCmd = stackInfo.testFrameworks.includes('jest') ? 'npm test' :\n                      stackInfo.testFrameworks.includes('pytest') ? 'pytest' :\n                      stackInfo.testFrameworks.includes('vitest') ? 'npm test' : 'npm test';\n      lines.push(`${step}. Run tests with \\`${testCmd}\\`.`);\n      step++;\n    }\n\n    lines.push(`${step}. Review the development workflow documentation.`);\n\n    return lines.join('\\n');\n  }\n\n  // ===== Agent-specific content generators =====\n\n  private generateKeyFilesForAgent(agentType: string, ctx: AutoFillContext): string {\n    const { semantics, repoPath } = ctx;\n\n    if (!semantics) {\n      return '_Key files to be identified._';\n    }\n\n    const allFiles = new Set<string>();\n\n    // Collect files based on agent type patterns\n    const patterns = this.getFilePatternForAgent(agentType);\n\n    for (const [file] of semantics.dependencies.graph) {\n      const relPath = path.relative(repoPath, file);\n      if (patterns.some(p => p.test(relPath))) {\n        allFiles.add(relPath);\n      }\n    }\n\n    if (allFiles.size === 0) {\n      return '_No specific files detected for this agent type._';\n    }\n\n    return Array.from(allFiles)\n      .slice(0, 10)\n      .map(f => `- [\\`${f}\\`](../${f})`)\n      .join('\\n');\n  }\n\n  private generateRelevantSymbolsForAgent(agentType: string, ctx: AutoFillContext): string {\n    const { semantics } = ctx;\n\n    if (!semantics) {\n      return '_Relevant symbols to be identified._';\n    }\n\n    const symbols = this.getRelevantSymbolsForAgent(agentType, semantics);\n\n    if (symbols.length === 0) {\n      return '_No specific symbols detected for this agent type._';\n    }\n\n    return symbols\n      .slice(0, 15)\n      .map(s => `- \\`${s.name}\\` (${s.kind}) - ${path.basename(s.location.file)}:${s.location.line}`)\n      .join('\\n');\n  }\n\n  private getFilePatternForAgent(agentType: string): RegExp[] {\n    switch (agentType) {\n      case 'code-reviewer':\n      case 'refactoring-specialist':\n        return [/service/i, /controller/i, /handler/i, /repository/i];\n\n      case 'test-writer':\n        return [/test|spec/i, /__tests__/i, /mock/i];\n\n      case 'documentation-writer':\n        return [/readme/i, /docs?/i, /\\.md$/i];\n\n      case 'security-auditor':\n        return [/auth/i, /security/i, /middleware/i, /guard/i];\n\n      case 'performance-optimizer':\n        return [/cache/i, /queue/i, /pool/i, /buffer/i];\n\n      case 'database-specialist':\n        return [/model/i, /entity/i, /repository/i, /migration/i, /schema/i];\n\n      case 'backend-specialist':\n        return [/service/i, /controller/i, /handler/i, /api/i];\n\n      case 'frontend-specialist':\n        return [/component/i, /hook/i, /view/i, /page/i, /screen/i];\n\n      default:\n        return [/service/i, /controller/i, /index/i];\n    }\n  }\n\n  private getRelevantSymbolsForAgent(agentType: string, semantics: SemanticContext): ExtractedSymbol[] {\n    const { symbols } = semantics;\n\n    switch (agentType) {\n      case 'test-writer':\n        return [\n          ...symbols.functions.filter(s => /test|spec|mock|stub/i.test(s.name)),\n          ...symbols.classes.filter(s => /test|spec/i.test(s.name)),\n        ];\n\n      case 'code-reviewer':\n      case 'refactoring-specialist':\n        return [\n          ...symbols.classes.filter(s => s.exported),\n          ...symbols.interfaces.filter(s => s.exported),\n        ];\n\n      case 'documentation-writer':\n        return [\n          ...symbols.classes.filter(s => s.exported),\n          ...symbols.interfaces.filter(s => s.exported),\n          ...symbols.functions.filter(s => s.exported),\n        ];\n\n      case 'security-auditor':\n        return [\n          ...symbols.functions.filter(s => /auth|security|crypt|token|password|secret/i.test(s.name)),\n          ...symbols.classes.filter(s => /auth|security|guard|policy/i.test(s.name)),\n        ];\n\n      case 'performance-optimizer':\n        return [\n          ...symbols.functions.filter(s => /cache|async|batch|queue|pool/i.test(s.name)),\n          ...symbols.classes.filter(s => /cache|pool|buffer|queue/i.test(s.name)),\n        ];\n\n      case 'database-specialist':\n        return [\n          ...symbols.classes.filter(s => /repository|model|entity|schema|migration/i.test(s.name)),\n          ...symbols.interfaces.filter(s => /repository|model|entity/i.test(s.name)),\n        ];\n\n      case 'backend-specialist':\n        return [\n          ...symbols.classes.filter(s => /service|controller|handler|middleware/i.test(s.name)),\n        ];\n\n      case 'frontend-specialist':\n        return [\n          ...symbols.functions.filter(s => /^use[A-Z]/i.test(s.name)),\n          ...symbols.classes.filter(s => /component|view|page|screen/i.test(s.name)),\n        ];\n\n      default:\n        return [\n          ...symbols.classes.filter(s => s.exported).slice(0, 5),\n          ...symbols.interfaces.filter(s => s.exported).slice(0, 5),\n        ];\n    }\n  }\n}\n"
  },
  {
    "path": "src/services/autoFill/index.ts",
    "content": "export { AutoFillService, AutoFillContext } from './autoFillService';\n"
  },
  {
    "path": "src/services/cli/index.ts",
    "content": "/**\n * CLI service exports.\n *\n * These services belong to the operator-facing CLI boundary.\n */\n\nexport {\n  MCPInstallService,\n  buildMcpInstallToolChoices,\n  resolveMcpInstallToolSelection,\n  type MCPInstallServiceDependencies,\n  type MCPInstallOptions,\n  type MCPInstallResult,\n  type MCPInstallation,\n  type MCPInstallToolChoice,\n  type MCPInstallToolPrompt,\n  type ResolveMcpInstallToolSelectionOptions,\n} from './mcpInstallService';\n\nexport {\n  StateDetector,\n  default as DefaultStateDetector,\n  type ProjectState,\n  type StateDetectionResult,\n  type StateDetectorOptions,\n} from './stateDetector';\n"
  },
  {
    "path": "src/services/cli/mcpInstallService.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  MCPInstallService,\n  buildMcpInstallToolChoices,\n  resolveMcpInstallToolSelection,\n} from './mcpInstallService';\nimport type { CLIInterface } from '../../utils/cliUI';\n\n// Mock CLIInterface\nconst createMockUI = (): CLIInterface => ({\n  displayWelcome: jest.fn(),\n  displayError: jest.fn(),\n  displaySuccess: jest.fn(),\n  displayInfo: jest.fn(),\n  displayWarning: jest.fn(),\n  displayList: jest.fn(),\n  displayTable: jest.fn(),\n  displayJson: jest.fn(),\n  displayProjectConfiguration: jest.fn(),\n  displayFileTypeDistribution: jest.fn(),\n  displayGenerationSummary: jest.fn(),\n  startSpinner: jest.fn(),\n  updateSpinner: jest.fn(),\n  stopSpinner: jest.fn(),\n  displayAnalysisComplete: jest.fn(),\n  displayBox: jest.fn(),\n  displaySection: jest.fn(),\n  displayStep: jest.fn(),\n  displayDiff: jest.fn(),\n  displaySkillHeader: jest.fn(),\n  displaySkillDefinition: jest.fn(),\n  displaySkillExamples: jest.fn(),\n  displaySkillContent: jest.fn(),\n} as unknown as CLIInterface);\n\n// Mock translate function\nconst mockT = (key: string, params?: Record<string, unknown>) => {\n  if (params) {\n    let result = key;\n    for (const [k, v] of Object.entries(params)) {\n      result = result.replace(`{${k}}`, String(v));\n    }\n    return result;\n  }\n  return key;\n};\n\ndescribe('MCPInstallService', () => {\n  let tempDir: string;\n  let service: MCPInstallService;\n  let mockUI: CLIInterface;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-install-test-'));\n    mockUI = createMockUI();\n    service = new MCPInstallService({\n      ui: mockUI,\n      t: mockT,\n      version: '1.0.0',\n    });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  describe('getSupportedTools', () => {\n    it('should return list of supported tools', () => {\n      const tools = service.getSupportedTools();\n      expect(tools.length).toBeGreaterThan(0);\n      expect(tools.some(t => t.id === 'claude')).toBe(true);\n      expect(tools.some(t => t.id === 'cursor')).toBe(true);\n    });\n  });\n\n  describe('buildMcpInstallToolChoices', () => {\n    it('orders detected tools first and labels them', () => {\n      const choices = buildMcpInstallToolChoices(\n        [\n          { id: 'claude', displayName: 'Claude Code' },\n          { id: 'codex', displayName: 'Codex CLI' },\n          { id: 'cursor', displayName: 'Cursor AI' },\n        ],\n        ['codex'],\n        mockT as any\n      );\n\n      expect(choices).toEqual([\n        { name: 'commands.mcpInstall.allDetected', value: 'all' },\n        { name: 'Codex CLI (labels.detected)', value: 'codex' },\n        { name: 'Claude Code', value: 'claude' },\n        { name: 'Cursor AI', value: 'cursor' },\n      ]);\n    });\n  });\n\n  describe('resolveMcpInstallToolSelection', () => {\n    it('returns the provided tool without prompting', async () => {\n      const promptTool = jest.fn();\n\n      const selectedTool = await resolveMcpInstallToolSelection({\n        selectedTool: 'cursor',\n        isInteractive: true,\n        service,\n        t: mockT as any,\n        promptTool,\n      });\n\n      expect(selectedTool).toBe('cursor');\n      expect(promptTool).not.toHaveBeenCalled();\n    });\n\n    it('defaults to all in non-interactive mode when no tool is provided', async () => {\n      const selectedTool = await resolveMcpInstallToolSelection({\n        isInteractive: false,\n        service,\n        t: mockT as any,\n      });\n\n      expect(selectedTool).toBe('all');\n    });\n\n    it('prompts with detected tools first in interactive mode', async () => {\n      const promptTool = jest.fn().mockResolvedValue('codex');\n      const detectSpy = jest.spyOn(service, 'detectInstalledTools').mockResolvedValue(['codex']);\n\n      const selectedTool = await resolveMcpInstallToolSelection({\n        isInteractive: true,\n        service,\n        t: mockT as any,\n        promptTool,\n      });\n\n      expect(selectedTool).toBe('codex');\n      expect(promptTool).toHaveBeenCalledWith({\n        message: 'commands.mcpInstall.selectTool',\n        choices: expect.arrayContaining([\n          { name: 'commands.mcpInstall.allDetected', value: 'all' },\n          { name: 'Codex CLI (labels.detected)', value: 'codex' },\n        ]),\n      });\n\n      detectSpy.mockRestore();\n    });\n  });\n\n  describe('getSupportedToolIds', () => {\n    it('should return list of supported tool IDs', () => {\n      const ids = service.getSupportedToolIds();\n      expect(ids).toContain('claude');\n      expect(ids).toContain('cursor');\n      expect(ids).toContain('codex');\n      expect(ids).toContain('windsurf');\n      expect(ids).toContain('continue');\n      expect(ids).toContain('trae');\n      expect(ids).toContain('copilot-cli');\n    });\n\n    it('should not include removed tool IDs', () => {\n      const ids = service.getSupportedToolIds();\n      expect(ids).not.toContain('warp');\n      expect(ids).not.toContain('cline');\n    });\n  });\n\n  describe('detectInstalledTools', () => {\n    it('should return an array of tool IDs', async () => {\n      const detected = await service.detectInstalledTools();\n      expect(Array.isArray(detected)).toBe(true);\n      const validIds = service.getSupportedToolIds();\n      for (const id of detected) {\n        expect(validIds).toContain(id);\n      }\n    });\n  });\n\n  describe('run', () => {\n    it('should install MCP configuration for Claude', async () => {\n      const result = await service.run({\n        tool: 'claude',\n        global: false,\n        repoPath: tempDir,\n        dryRun: false,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      expect(result.installations.length).toBe(1);\n      expect(result.installations[0].tool).toBe('claude');\n      expect(result.installations[0].action).toBe('created');\n\n      // Verify file was created at new path\n      const configPath = path.join(tempDir, '.mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      // Verify config content\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers).toBeDefined();\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n      expect(config.mcpServers['dotcontext'].command).toBe('npx');\n    });\n\n    it('should support dry-run mode', async () => {\n      const result = await service.run({\n        tool: 'claude',\n        global: false,\n        repoPath: tempDir,\n        dryRun: true,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      expect(result.installations[0].dryRun).toBe(true);\n\n      // Verify file was NOT created\n      const configPath = path.join(tempDir, '.mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(false);\n    });\n\n    it('should skip if already configured', async () => {\n      // First install\n      await service.run({\n        tool: 'claude',\n        global: false,\n        repoPath: tempDir,\n        dryRun: false,\n      });\n\n      // Second install should skip\n      const result = await service.run({\n        tool: 'claude',\n        global: false,\n        repoPath: tempDir,\n        dryRun: false,\n      });\n\n      expect(result.filesSkipped).toBe(1);\n      expect(result.installations[0].action).toBe('skipped');\n    });\n\n    it('should merge with existing config', async () => {\n      // Create existing config with other servers\n      const configPath = path.join(tempDir, '.mcp.json');\n      await fs.ensureDir(path.dirname(configPath));\n      await fs.writeJson(configPath, {\n        mcpServers: {\n          'other-server': { command: 'other', args: [] },\n        },\n      });\n\n      const result = await service.run({\n        tool: 'claude',\n        global: false,\n        repoPath: tempDir,\n        dryRun: false,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      expect(result.installations[0].action).toBe('updated');\n\n      // Verify both servers exist\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['other-server']).toBeDefined();\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n\n    it('should install for multiple tools when specifying tool as \"all\"', async () => {\n      const result = await service.run({\n        tool: 'all',\n        global: false,\n        repoPath: tempDir,\n        dryRun: false,\n      });\n\n      expect(result.installations.length).toBeGreaterThan(0);\n      const validIds = service.getSupportedToolIds();\n      for (const install of result.installations) {\n        expect(validIds).toContain(install.tool);\n      }\n    });\n\n    it('should fall back to all supported tools when \"all\" is requested and nothing is detected', async () => {\n      const detectSpy = jest.spyOn(service, 'detectInstalledTools').mockResolvedValue([]);\n\n      const result = await service.run({\n        tool: 'all',\n        global: false,\n        repoPath: tempDir,\n        dryRun: true,\n      });\n\n      expect(detectSpy).toHaveBeenCalled();\n      expect(result.installations.length).toBe(service.getSupportedToolIds().length);\n\n      detectSpy.mockRestore();\n    });\n\n    it('should show warning when unsupported tool is specified', async () => {\n      const result = await service.run({\n        tool: 'nonexistent-tool',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(mockUI.displayError).toHaveBeenCalled();\n      expect(result.filesCreated).toBe(0);\n    });\n\n    it('should fall back to all supported tools when no tool is specified and nothing is detected', async () => {\n      const detectSpy = jest.spyOn(service, 'detectInstalledTools').mockResolvedValue([]);\n\n      const result = await service.run({\n        global: false,\n        repoPath: tempDir,\n        dryRun: true,\n      });\n\n      expect(detectSpy).toHaveBeenCalled();\n      expect(result.installations.length).toBe(service.getSupportedToolIds().length);\n\n      detectSpy.mockRestore();\n    });\n  });\n\n  describe('tool-specific configurations', () => {\n    it('should generate correct config for Cursor with type field', async () => {\n      const result = await service.run({\n        tool: 'cursor',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n\n      const configPath = path.join(tempDir, '.cursor', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n      expect(config.mcpServers['dotcontext'].type).toBe('stdio');\n      expect(config.mcpServers['dotcontext'].command).toBe('npx');\n    });\n\n    it('should generate correct config for Continue.dev as standalone file', async () => {\n      const result = await service.run({\n        tool: 'continue',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n\n      const configPath = path.join(tempDir, '.continue', 'mcpServers', 'dotcontext.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.command).toBe('npx');\n      expect(config.args).toBeDefined();\n      expect(config.env).toBeDefined();\n      // Should NOT have experimental or modelContextProtocolServers wrapper\n      expect(config.experimental).toBeUndefined();\n    });\n  });\n\n  describe('Phase 1: Standard JSON tools', () => {\n    it('should install MCP configuration for Claude Desktop', async () => {\n      const result = await service.run({\n        tool: 'claude-desktop',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.claude-desktop', 'mcp_servers.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n      expect(config.mcpServers['dotcontext'].command).toBe('npx');\n    });\n\n    it('should install MCP configuration for VS Code with servers key and type field', async () => {\n      const result = await service.run({\n        tool: 'vscode',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.vscode', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      // Should use 'servers' key, not 'mcpServers'\n      expect(config.servers).toBeDefined();\n      expect(config.servers['dotcontext']).toBeDefined();\n      expect(config.servers['dotcontext'].type).toBe('stdio');\n      expect(config.servers['dotcontext'].command).toBe('npx');\n      expect(config.mcpServers).toBeUndefined();\n    });\n\n    it('should install MCP configuration for Roo Code', async () => {\n      const result = await service.run({\n        tool: 'roo',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.roo', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n\n    it('should install MCP configuration for Amazon Q Developer CLI', async () => {\n      const result = await service.run({\n        tool: 'amazonq',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.amazonq', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n\n    it('should install MCP configuration for Gemini CLI', async () => {\n      const result = await service.run({\n        tool: 'gemini-cli',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.gemini', 'settings.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n\n    it('should install MCP configuration for Codex CLI using TOML', async () => {\n      const result = await service.run({\n        tool: 'codex',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.codex', 'config.toml');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readFile(configPath, 'utf-8');\n      expect(config).toContain('[mcp_servers.dotcontext]');\n      expect(config).toContain('command = \"npx\"');\n      expect(config).toContain('args = [\"-y\", \"@dotcontext/mcp@latest\"]');\n    });\n\n    it('should install MCP configuration for Kiro at settings path', async () => {\n      const result = await service.run({\n        tool: 'kiro',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.kiro', 'settings', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n\n    it('should install MCP configuration for Windsurf at codeium path', async () => {\n      const result = await service.run({\n        tool: 'windsurf',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.codeium', 'windsurf', 'mcp_config.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n    });\n  });\n\n  describe('Phase 2: Special JSON formats', () => {\n    it('should install MCP configuration for Zed with flat context_servers format', async () => {\n      const result = await service.run({\n        tool: 'zed',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.zed', 'settings.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.context_servers).toBeDefined();\n      expect(config.context_servers['dotcontext']).toBeDefined();\n      // Should have flat command, not nested command.path\n      expect(config.context_servers['dotcontext'].command).toBe('npx');\n      expect(config.context_servers['dotcontext'].args).toBeDefined();\n      expect(config.context_servers['dotcontext'].settings).toBeUndefined();\n    });\n\n    it('should install MCP configuration for JetBrains with servers array', async () => {\n      const result = await service.run({\n        tool: 'jetbrains',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.jb-mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      // Should use servers array, not mcpServers object\n      expect(config.servers).toBeDefined();\n      expect(Array.isArray(config.servers)).toBe(true);\n      const aiContext = config.servers.find((s: { name: string }) => s.name === 'dotcontext');\n      expect(aiContext).toBeDefined();\n      expect(aiContext.command).toBe('npx');\n      expect(config.mcpServers).toBeUndefined();\n    });\n\n    it('should merge JetBrains servers array without duplicates', async () => {\n      // Create existing config with another server\n      const configPath = path.join(tempDir, '.jb-mcp.json');\n      await fs.ensureDir(path.dirname(configPath));\n      await fs.writeJson(configPath, {\n        servers: [\n          { name: 'other-server', command: 'other', args: [] },\n        ],\n      });\n\n      await service.run({\n        tool: 'jetbrains',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      const config = await fs.readJson(configPath);\n      expect(config.servers.length).toBe(2);\n      expect(config.servers.some((s: { name: string }) => s.name === 'other-server')).toBe(true);\n      expect(config.servers.some((s: { name: string }) => s.name === 'dotcontext')).toBe(true);\n    });\n  });\n\n  describe('New tools', () => {\n    it('should install MCP configuration for Trae', async () => {\n      const result = await service.run({\n        tool: 'trae',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.trae', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n      expect(config.mcpServers['dotcontext'].command).toBe('npx');\n    });\n\n    it('should install MCP configuration for Kilo Code with mcp format', async () => {\n      const result = await service.run({\n        tool: 'kilo',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.kilo', 'mcp.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      // Should use 'mcp' key with type/command/enabled format\n      expect(config.mcp).toBeDefined();\n      expect(config.mcp['dotcontext']).toBeDefined();\n      expect(config.mcp['dotcontext'].type).toBe('local');\n      expect(config.mcp['dotcontext'].command).toEqual(['npx', '-y', '@dotcontext/mcp@latest']);\n      expect(config.mcp['dotcontext'].enabled).toBe(true);\n      expect(config.mcpServers).toBeUndefined();\n    });\n\n    it('should install MCP configuration for GitHub Copilot CLI', async () => {\n      const result = await service.run({\n        tool: 'copilot-cli',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      expect(result.filesCreated).toBe(1);\n      const configPath = path.join(tempDir, '.copilot', 'mcp-config.json');\n      expect(await fs.pathExists(configPath)).toBe(true);\n\n      const config = await fs.readJson(configPath);\n      expect(config.mcpServers['dotcontext']).toBeDefined();\n      expect(config.mcpServers['dotcontext'].command).toBe('npx');\n    });\n  });\n\n  describe('Merge behavior for new tools', () => {\n    it('should preserve existing servers when installing VS Code', async () => {\n      const configPath = path.join(tempDir, '.vscode', 'mcp.json');\n      await fs.ensureDir(path.dirname(configPath));\n      await fs.writeJson(configPath, {\n        servers: {\n          'other-server': { type: 'stdio', command: 'other', args: [] }\n        }\n      });\n\n      await service.run({\n        tool: 'vscode',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      const config = await fs.readJson(configPath);\n      expect(config.servers['other-server']).toBeDefined();\n      expect(config.servers['dotcontext']).toBeDefined();\n    });\n\n    it('should preserve existing context_servers when installing Zed', async () => {\n      const configPath = path.join(tempDir, '.zed', 'settings.json');\n      await fs.ensureDir(path.dirname(configPath));\n      await fs.writeJson(configPath, {\n        context_servers: {\n          'other-server': { command: 'other', args: [] }\n        }\n      });\n\n      await service.run({\n        tool: 'zed',\n        global: false,\n        repoPath: tempDir,\n      });\n\n      const config = await fs.readJson(configPath);\n      expect(config.context_servers['other-server']).toBeDefined();\n      expect(config.context_servers['dotcontext']).toBeDefined();\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/cli/mcpInstallService.ts",
    "content": "/**\n * MCP Install Service\n *\n * CLI-facing installation service for configuring the dotcontext MCP server\n * in various AI tools. This belongs to the operator surface, not the\n * reusable harness runtime.\n */\n\nimport * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs-extra';\nimport {\n  BaseDependencies,\n  OperationResult,\n  createEmptyResult,\n  addError,\n} from '../shared/types';\nimport { TOOL_REGISTRY, getToolById, ToolDefinition } from '../shared/toolRegistry';\nimport type { TranslateFn } from '../../utils/i18n';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type MCPInstallServiceDependencies = BaseDependencies;\n\nexport interface MCPInstallOptions {\n  /** Specific tool to install for (e.g., 'claude', 'cursor') */\n  tool?: string;\n  /** Install globally (home directory) vs locally (project directory) */\n  global?: boolean;\n  /** Preview changes without writing */\n  dryRun?: boolean;\n  /** Verbose output */\n  verbose?: boolean;\n  /** Repository path for local installation */\n  repoPath?: string;\n}\n\nexport interface MCPInstallResult extends OperationResult {\n  installations: MCPInstallation[];\n}\n\nexport interface MCPInstallation {\n  tool: string;\n  toolDisplayName: string;\n  configPath: string;\n  action: 'created' | 'updated' | 'skipped';\n  dryRun: boolean;\n}\n\nexport interface MCPInstallToolChoice {\n  name: string;\n  value: string;\n}\n\nexport interface MCPInstallToolPrompt {\n  message: string;\n  choices: MCPInstallToolChoice[];\n}\n\nexport interface ResolveMcpInstallToolSelectionOptions {\n  selectedTool?: string;\n  isInteractive: boolean;\n  service: Pick<MCPInstallService, 'getSupportedTools' | 'detectInstalledTools'>;\n  t: TranslateFn;\n  promptTool?: (prompt: MCPInstallToolPrompt) => Promise<string>;\n}\n\n// ============================================================================\n// MCP Configuration Templates\n// ============================================================================\n\ninterface MCPServerConfig {\n  command: string;\n  args: string[];\n  env?: Record<string, string>;\n}\n\ninterface MCPConfigTemplate {\n  toolId: string;\n  /** Config file path relative to home or repo root */\n  globalConfigPath: string;\n  localConfigPath: string;\n  /** Function to generate config content */\n  generateConfig: (existingConfig: unknown, mcpServer: MCPServerConfig) => unknown;\n  /** Function to check if MCP is already configured */\n  isConfigured: (config: unknown) => boolean;\n  /** Optional parser for non-JSON config formats */\n  parseConfig?: (content: string) => unknown;\n  /** Optional serializer for non-JSON config formats */\n  serializeConfig?: (config: unknown) => string;\n}\n\n/**\n * Standard MCP server configuration for dotcontext\n */\nconst AI_CONTEXT_MCP_SERVER: MCPServerConfig = {\n  command: 'npx',\n  args: ['-y', '@dotcontext/mcp@latest'],\n  env: {},\n};\n\nconst parseJsonConfig = (content: string): unknown => JSON.parse(content);\nconst serializeJsonConfig = (config: unknown): string => JSON.stringify(config, null, 2);\n\nfunction buildCodexTomlConfig(existingConfig: string, server: MCPServerConfig): string {\n  const lines = [\n    '[mcp_servers.dotcontext]',\n    `command = ${JSON.stringify(server.command)}`,\n    `args = [${server.args.map(arg => JSON.stringify(arg)).join(', ')}]`,\n  ];\n\n  if (server.env && Object.keys(server.env).length > 0) {\n    lines.push('[mcp_servers.dotcontext.env]');\n    for (const [key, value] of Object.entries(server.env)) {\n      lines.push(`${key} = ${JSON.stringify(value)}`);\n    }\n  }\n\n  const trimmed = existingConfig.trimEnd();\n  const block = lines.join('\\n');\n  return trimmed ? `${trimmed}\\n\\n${block}\\n` : `${block}\\n`;\n}\n\n/**\n * MCP configuration templates for each supported tool\n */\nconst MCP_CONFIG_TEMPLATES: MCPConfigTemplate[] = [\n  // Claude Code\n  {\n    toolId: 'claude',\n    globalConfigPath: '.claude.json',\n    localConfigPath: '.mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Cursor AI\n  {\n    toolId: 'cursor',\n    globalConfigPath: '.cursor/mcp.json',\n    localConfigPath: '.cursor/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': {\n            type: 'stdio',\n            ...server,\n          },\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Windsurf (Codeium)\n  {\n    toolId: 'windsurf',\n    globalConfigPath: '.codeium/windsurf/mcp_config.json',\n    localConfigPath: '.codeium/windsurf/mcp_config.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Continue.dev — standalone per-server file\n  {\n    toolId: 'continue',\n    globalConfigPath: '.continue/mcpServers/dotcontext.json',\n    localConfigPath: '.continue/mcpServers/dotcontext.json',\n    generateConfig: (_existing, server) => {\n      return {\n        command: server.command,\n        args: server.args,\n        env: server.env || {},\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      return !!c?.command;\n    },\n  },\n\n  // Claude Desktop - Cross-platform paths\n  {\n    toolId: 'claude-desktop',\n    globalConfigPath: process.platform === 'darwin'\n      ? path.join('Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n      : path.join('AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'),\n    localConfigPath: '.claude-desktop/mcp_servers.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // VS Code (GitHub Copilot)\n  {\n    toolId: 'vscode',\n    globalConfigPath: '.vscode/mcp.json',\n    localConfigPath: '.vscode/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        servers: {\n          ...(config.servers as Record<string, unknown> || {}),\n          'dotcontext': {\n            type: 'stdio',\n            ...server,\n          },\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.servers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Roo Code\n  {\n    toolId: 'roo',\n    globalConfigPath: '.roo/mcp_settings.json',\n    localConfigPath: '.roo/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Amazon Q Developer CLI\n  {\n    toolId: 'amazonq',\n    globalConfigPath: '.aws/amazonq/mcp.json',\n    localConfigPath: '.amazonq/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Gemini CLI\n  {\n    toolId: 'gemini-cli',\n    globalConfigPath: '.gemini/settings.json',\n    localConfigPath: '.gemini/settings.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Codex CLI - TOML config\n  {\n    toolId: 'codex',\n    globalConfigPath: '.codex/config.toml',\n    localConfigPath: '.codex/config.toml',\n    generateConfig: (existing, server) => buildCodexTomlConfig(\n      typeof existing === 'string' ? existing : '',\n      server\n    ),\n    isConfigured: (config) => (\n      typeof config === 'string' && /^\\s*\\[mcp_servers\\.dotcontext\\]\\s*$/m.test(config)\n    ),\n    parseConfig: (content) => content,\n    serializeConfig: (config) => (typeof config === 'string' ? config : String(config ?? '')),\n  },\n\n  // Kiro\n  {\n    toolId: 'kiro',\n    globalConfigPath: '.kiro/settings/mcp.json',\n    localConfigPath: '.kiro/settings/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Zed Editor - Uses context_servers instead of mcpServers\n  {\n    toolId: 'zed',\n    globalConfigPath: '.config/zed/settings.json',\n    localConfigPath: '.zed/settings.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        context_servers: {\n          ...(config.context_servers as Record<string, unknown> || {}),\n          'dotcontext': {\n            command: server.command,\n            args: server.args,\n            env: server.env || {},\n          },\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.context_servers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // JetBrains IDEs - Uses servers array with name field\n  {\n    toolId: 'jetbrains',\n    globalConfigPath: '.config/JetBrains/mcp.json',\n    localConfigPath: '.jb-mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      const existingServers = (config.servers as Array<Record<string, unknown>>) || [];\n      const filtered = existingServers.filter(s => s.name !== 'dotcontext');\n      return {\n        servers: [\n          ...filtered,\n          {\n            name: 'dotcontext',\n            command: server.command,\n            args: server.args,\n            env: server.env || {},\n          },\n        ],\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.servers as Array<Record<string, unknown>>;\n      return Array.isArray(servers) && servers.some(s => s.name === 'dotcontext');\n    },\n  },\n\n  // Trae AI (ByteDance)\n  {\n    toolId: 'trae',\n    globalConfigPath: '.trae/mcp.json',\n    localConfigPath: '.trae/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n\n  // Kilo Code\n  {\n    toolId: 'kilo',\n    globalConfigPath: '.kilo/mcp.json',\n    localConfigPath: '.kilo/mcp.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcp: {\n          ...(config.mcp as Record<string, unknown> || {}),\n          'dotcontext': {\n            type: 'local',\n            command: [server.command, ...server.args],\n            enabled: true,\n          },\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const mcp = c?.mcp as Record<string, unknown>;\n      return !!mcp?.['dotcontext'];\n    },\n  },\n\n  // GitHub Copilot CLI\n  {\n    toolId: 'copilot-cli',\n    globalConfigPath: '.copilot/mcp-config.json',\n    localConfigPath: '.copilot/mcp-config.json',\n    generateConfig: (existing, server) => {\n      const config = (existing as Record<string, unknown>) || {};\n      return {\n        ...config,\n        mcpServers: {\n          ...(config.mcpServers as Record<string, unknown> || {}),\n          'dotcontext': server,\n        },\n      };\n    },\n    isConfigured: (config) => {\n      const c = config as Record<string, unknown>;\n      const servers = c?.mcpServers as Record<string, unknown>;\n      return !!servers?.['dotcontext'];\n    },\n  },\n];\n\n// ============================================================================\n// Service Implementation\n// ============================================================================\n\nexport class MCPInstallService {\n  constructor(private deps: MCPInstallServiceDependencies) {}\n\n  /**\n   * Get list of tools that support MCP installation\n   */\n  getSupportedTools(): ToolDefinition[] {\n    return MCP_CONFIG_TEMPLATES.map(t => getToolById(t.toolId)).filter(Boolean) as ToolDefinition[];\n  }\n\n  /**\n   * Get list of supported tool IDs\n   */\n  getSupportedToolIds(): string[] {\n    return MCP_CONFIG_TEMPLATES.map(t => t.toolId);\n  }\n\n  /**\n   * Detect which supported tools are installed on the system\n   */\n  async detectInstalledTools(): Promise<string[]> {\n    const installed: string[] = [];\n    const homeDir = os.homedir();\n\n    for (const template of MCP_CONFIG_TEMPLATES) {\n      const tool = getToolById(template.toolId);\n      if (!tool) continue;\n\n      // Check if tool directory exists in home\n      const toolDir = path.join(homeDir, tool.directoryPrefix);\n      if (await fs.pathExists(toolDir)) {\n        installed.push(template.toolId);\n      }\n    }\n\n    return installed;\n  }\n\n  /**\n   * Install MCP configuration for specified tools\n   */\n  async run(options: MCPInstallOptions = {}): Promise<MCPInstallResult> {\n    const result: MCPInstallResult = {\n      ...createEmptyResult(),\n      installations: [],\n    };\n\n    const { tool, global: isGlobal = true, dryRun = false, verbose = false, repoPath } = options;\n\n    // Determine base path\n    const basePath = isGlobal ? os.homedir() : (repoPath ? path.resolve(repoPath) : process.cwd());\n\n    // Determine which tools to install\n    let toolsToInstall: string[];\n    if (tool) {\n      if (tool === 'all') {\n        toolsToInstall = await this.detectInstalledTools();\n        if (toolsToInstall.length === 0) {\n          toolsToInstall = this.getSupportedToolIds();\n        }\n      } else {\n        const template = MCP_CONFIG_TEMPLATES.find(t => t.toolId === tool);\n        if (!template) {\n          this.deps.ui.displayError(\n            this.deps.t('errors.mcp.unsupportedTool', { tool, supported: this.getSupportedToolIds().join(', ') })\n          );\n          return result;\n        }\n        toolsToInstall = [tool];\n      }\n    } else {\n      // No tool specified - install for all detected\n      toolsToInstall = await this.detectInstalledTools();\n      if (toolsToInstall.length === 0) {\n        toolsToInstall = this.getSupportedToolIds();\n      }\n    }\n\n    if (toolsToInstall.length === 0) {\n      this.deps.ui.displayWarning(this.deps.t('warnings.mcp.noToolsDetected'));\n      return result;\n    }\n\n    // Install for each tool\n    for (const toolId of toolsToInstall) {\n      const installation = await this.installForTool(toolId, basePath, isGlobal, dryRun, verbose);\n      result.installations.push(installation);\n\n      if (installation.action === 'created' || installation.action === 'updated') {\n        result.filesCreated++;\n      } else if (installation.action === 'skipped') {\n        result.filesSkipped++;\n      }\n    }\n\n    // Display summary\n    if (!dryRun && result.filesCreated > 0) {\n      this.deps.ui.displaySuccess(\n        this.deps.t('success.mcp.installed', { count: result.filesCreated })\n      );\n    }\n\n    return result;\n  }\n\n  /**\n   * Install MCP configuration for a single tool\n   */\n  private async installForTool(\n    toolId: string,\n    basePath: string,\n    isGlobal: boolean,\n    dryRun: boolean,\n    verbose: boolean\n  ): Promise<MCPInstallation> {\n    const template = MCP_CONFIG_TEMPLATES.find(t => t.toolId === toolId);\n    const toolDef = getToolById(toolId);\n\n    if (!template || !toolDef) {\n      return {\n        tool: toolId,\n        toolDisplayName: toolId,\n        configPath: '',\n        action: 'skipped',\n        dryRun,\n      };\n    }\n\n    const configPath = isGlobal ? template.globalConfigPath : template.localConfigPath;\n    const fullConfigPath = path.join(basePath, configPath);\n    const parseConfig = template.parseConfig ?? parseJsonConfig;\n    const serializeConfig = template.serializeConfig ?? serializeJsonConfig;\n\n    // Read existing config if it exists\n    let existingConfig: unknown = {};\n    const configExists = await fs.pathExists(fullConfigPath);\n    if (configExists) {\n      try {\n        const content = await fs.readFile(fullConfigPath, 'utf-8');\n        existingConfig = parseConfig(content);\n      } catch {\n        // File exists but could not be parsed, we'll overwrite while preserving custom formats when possible.\n        existingConfig = {};\n      }\n    }\n\n    // Check if already configured\n    if (template.isConfigured(existingConfig)) {\n      if (verbose) {\n        this.deps.ui.displayInfo(\n          toolDef.displayName,\n          this.deps.t('info.mcp.alreadyConfigured', { tool: toolDef.displayName })\n        );\n      }\n      return {\n        tool: toolId,\n        toolDisplayName: toolDef.displayName,\n        configPath: fullConfigPath,\n        action: 'skipped',\n        dryRun,\n      };\n    }\n\n    // Generate new config\n    const newConfig = template.generateConfig(existingConfig, AI_CONTEXT_MCP_SERVER);\n\n    if (dryRun) {\n      const serializedConfig = serializeConfig(newConfig);\n      this.deps.ui.displayInfo(\n        toolDef.displayName,\n        this.deps.t('info.mcp.wouldInstall', { tool: toolDef.displayName, path: fullConfigPath })\n      );\n      if (verbose) {\n        console.log(serializedConfig);\n      }\n      return {\n        tool: toolId,\n        toolDisplayName: toolDef.displayName,\n        configPath: fullConfigPath,\n        action: 'created',\n        dryRun: true,\n      };\n    }\n\n    // Write config\n    try {\n      await fs.ensureDir(path.dirname(fullConfigPath));\n      await fs.writeFile(fullConfigPath, serializeConfig(newConfig), 'utf-8');\n\n      if (verbose) {\n        this.deps.ui.displayInfo(\n          toolDef.displayName,\n          this.deps.t('info.mcp.installed', { tool: toolDef.displayName, path: fullConfigPath })\n        );\n      }\n\n      const action = configExists ? 'updated' : 'created';\n      return {\n        tool: toolId,\n        toolDisplayName: toolDef.displayName,\n        configPath: fullConfigPath,\n        action,\n        dryRun: false,\n      };\n    } catch (error) {\n      this.deps.ui.displayError(\n        this.deps.t('errors.mcp.installFailed', { tool: toolDef.displayName }),\n        error as Error\n      );\n      return {\n        tool: toolId,\n        toolDisplayName: toolDef.displayName,\n        configPath: fullConfigPath,\n        action: 'skipped',\n        dryRun: false,\n      };\n    }\n  }\n}\n\nexport function buildMcpInstallToolChoices(\n  supportedTools: Array<Pick<ToolDefinition, 'id' | 'displayName'>>,\n  detectedTools: string[],\n  t: TranslateFn\n): MCPInstallToolChoice[] {\n  const detectedSet = new Set(detectedTools);\n  const orderedTools = [\n    ...supportedTools.filter((tool) => detectedSet.has(tool.id)),\n    ...supportedTools.filter((tool) => !detectedSet.has(tool.id)),\n  ];\n\n  return [\n    {\n      name: t('commands.mcpInstall.allDetected'),\n      value: 'all',\n    },\n    ...orderedTools.map((tool) => ({\n      name: detectedSet.has(tool.id)\n        ? `${tool.displayName} (${t('labels.detected')})`\n        : tool.displayName,\n      value: tool.id,\n    })),\n  ];\n}\n\nexport async function resolveMcpInstallToolSelection(\n  options: ResolveMcpInstallToolSelectionOptions\n): Promise<string> {\n  const { selectedTool, isInteractive, service, t, promptTool } = options;\n\n  if (selectedTool) {\n    return selectedTool;\n  }\n\n  if (!isInteractive) {\n    return 'all';\n  }\n\n  if (!promptTool) {\n    throw new Error('Interactive MCP install selection requires a prompt handler.');\n  }\n\n  const supportedTools = service.getSupportedTools();\n  const detectedTools = await service.detectInstalledTools();\n\n  return promptTool({\n    message: t('commands.mcpInstall.selectTool'),\n    choices: buildMcpInstallToolChoices(supportedTools, detectedTools, t),\n  });\n}\n"
  },
  {
    "path": "src/services/cli/stateDetector.ts",
    "content": "/**\n * StateDetector - Detects the current state of the project's context documentation.\n *\n * States:\n * - new: No .context directory exists\n * - unfilled: .context exists but files have `status: unfilled` front matter\n * - ready: .context exists with filled content\n * - outdated: .context is older than source code\n *\n * This supports local operator workflows and belongs to the CLI boundary.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport { needsFill, getFilledStats } from '../../utils/frontMatter';\n\nexport type ProjectState = 'new' | 'unfilled' | 'ready' | 'outdated';\n\nexport interface StateDetectionResult {\n  state: ProjectState;\n  contextDir: string;\n  details: {\n    hasContextDir: boolean;\n    totalFiles: number;\n    filledFiles: number;\n    unfilledFiles: number;\n    codeLastModified?: Date;\n    docsLastModified?: Date;\n    daysBehind?: number;\n  };\n}\n\nexport interface StateDetectorOptions {\n  projectPath: string;\n  contextDirName?: string;\n  sourceDirs?: string[];\n}\n\nconst DEFAULT_SOURCE_DIRS = ['src', 'lib', 'app', 'packages'];\n\nexport class StateDetector {\n  private projectPath: string;\n  private contextDirName: string;\n  private sourceDirs: string[];\n\n  constructor(options: StateDetectorOptions) {\n    this.projectPath = options.projectPath;\n    this.contextDirName = options.contextDirName || '.context';\n    this.sourceDirs = options.sourceDirs || DEFAULT_SOURCE_DIRS;\n  }\n\n  get contextDir(): string {\n    return path.join(this.projectPath, this.contextDirName);\n  }\n\n  /**\n   * Detect the current state of the project\n   */\n  async detect(): Promise<StateDetectionResult> {\n    const hasContextDir = await fs.pathExists(this.contextDir);\n\n    if (!hasContextDir) {\n      return {\n        state: 'new',\n        contextDir: this.contextDir,\n        details: {\n          hasContextDir: false,\n          totalFiles: 0,\n          filledFiles: 0,\n          unfilledFiles: 0\n        }\n      };\n    }\n\n    // Get stats about filled/unfilled files\n    const stats = await getFilledStats(this.contextDir);\n\n    // If there are unfilled files, state is 'unfilled'\n    if (stats.unfilled > 0) {\n      return {\n        state: 'unfilled',\n        contextDir: this.contextDir,\n        details: {\n          hasContextDir: true,\n          totalFiles: stats.total,\n          filledFiles: stats.filled,\n          unfilledFiles: stats.unfilled\n        }\n      };\n    }\n\n    // Check if docs are outdated compared to code\n    const codeLastModified = await this.getNewestMtime(this.sourceDirs);\n    const docsLastModified = await this.getContextMtime();\n\n    if (codeLastModified && docsLastModified && codeLastModified > docsLastModified) {\n      const daysBehind = Math.floor((codeLastModified.getTime() - docsLastModified.getTime()) / (1000 * 60 * 60 * 24));\n\n      return {\n        state: 'outdated',\n        contextDir: this.contextDir,\n        details: {\n          hasContextDir: true,\n          totalFiles: stats.total,\n          filledFiles: stats.filled,\n          unfilledFiles: stats.unfilled,\n          codeLastModified,\n          docsLastModified,\n          daysBehind\n        }\n      };\n    }\n\n    // Everything is ready\n    return {\n      state: 'ready',\n      contextDir: this.contextDir,\n      details: {\n        hasContextDir: true,\n        totalFiles: stats.total,\n        filledFiles: stats.filled,\n        unfilledFiles: stats.unfilled,\n        codeLastModified,\n        docsLastModified\n      }\n    };\n  }\n\n  /**\n   * Get the newest modification time from source directories\n   */\n  private async getNewestMtime(dirs: string[]): Promise<Date | undefined> {\n    let newest: Date | undefined;\n\n    for (const dir of dirs) {\n      const fullPath = path.join(this.projectPath, dir);\n\n      if (!await fs.pathExists(fullPath)) {\n        continue;\n      }\n\n      try {\n        const files = await glob(`${fullPath}/**/*.{ts,tsx,js,jsx,py,go,rs,java,rb}`, {\n          ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**']\n        });\n\n        for (const file of files) {\n          const stat = await fs.stat(file);\n          if (!newest || stat.mtime > newest) {\n            newest = stat.mtime;\n          }\n        }\n      } catch {\n        // Ignore errors for individual directories\n      }\n    }\n\n    return newest;\n  }\n\n  /**\n   * Get the newest modification time from .context directory\n   */\n  private async getContextMtime(): Promise<Date | undefined> {\n    let newest: Date | undefined;\n\n    try {\n      const files = await glob(`${this.contextDir}/**/*.md`);\n\n      for (const file of files) {\n        const stat = await fs.stat(file);\n        if (!newest || stat.mtime > newest) {\n          newest = stat.mtime;\n        }\n      }\n    } catch {\n      // Ignore errors\n    }\n\n    return newest;\n  }\n\n  /**\n   * Get a human-readable description of the state\n   */\n  static describeState(result: StateDetectionResult): string {\n    switch (result.state) {\n      case 'new':\n        return 'No context documentation found';\n      case 'unfilled':\n        return `${result.details.unfilledFiles} of ${result.details.totalFiles} files need to be filled`;\n      case 'outdated':\n        return `Documentation is ${result.details.daysBehind} day(s) behind code`;\n      case 'ready':\n        return `${result.details.totalFiles} documentation files up to date`;\n    }\n  }\n}\n\nexport { StateDetector as default };\n"
  },
  {
    "path": "src/services/export/contextExportService.ts",
    "content": "/**\n * Context Export Service\n *\n * Unified export of docs, agents, and skills to AI tool directories.\n * Orchestrates ExportRulesService, SyncService, and SkillExportService.\n */\n\nimport * as path from 'path';\nimport {\n  BaseDependencies,\n  OperationResult,\n  createEmptyResult,\n} from '../shared';\nimport { ExportRulesService } from './exportRulesService';\nimport { SkillExportService } from './skillExportService';\nimport { SyncService } from '../sync';\nimport type { PresetName, SyncRunResult } from '../sync/types';\n\nexport type ContextExportServiceDependencies = BaseDependencies;\n\nexport interface ContextExportOptions {\n  /** Target preset (e.g., 'claude', 'cursor', 'all') */\n  preset?: string;\n  /** Skip docs export */\n  skipDocs?: boolean;\n  /** Skip agents export */\n  skipAgents?: boolean;\n  /** Skip skills export */\n  skipSkills?: boolean;\n  /** Index mode for docs: 'readme' exports only README.md files, 'all' exports all matching files */\n  docsIndexMode?: 'readme' | 'all';\n  /** Sync mode for agents: 'symlink' (default) or 'markdown' */\n  agentMode?: 'symlink' | 'markdown';\n  /** Include built-in skills */\n  includeBuiltInSkills?: boolean;\n  /** Force overwrite existing files */\n  force?: boolean;\n  /** Preview changes without writing */\n  dryRun?: boolean;\n  /** Verbose output */\n  verbose?: boolean;\n}\n\nexport interface ContextExportResult extends OperationResult {\n  docsExported: number;\n  agentsExported: number;\n  skillsExported: number;\n  targets: string[];\n}\n\nexport class ContextExportService {\n  constructor(private deps: ContextExportServiceDependencies) {}\n\n  /**\n   * Run unified export operation\n   */\n  async run(repoPath: string, options: ContextExportOptions = {}): Promise<ContextExportResult> {\n    const absolutePath = path.resolve(repoPath);\n    const fs = await import('fs-extra');\n\n    const result: ContextExportResult = {\n      ...createEmptyResult(),\n      docsExported: 0,\n      agentsExported: 0,\n      skillsExported: 0,\n      targets: [],\n    };\n\n    const preset = options.preset || 'all';\n    const errors: Array<{ type: string; error: string }> = [];\n\n    // Export docs - only if .context/docs exists\n    const docsPath = path.join(absolutePath, '.context/docs');\n    if (!options.skipDocs && await fs.pathExists(docsPath)) {\n      try {\n        this.deps.ui.startSpinner('Exporting docs...');\n        const docsService = new ExportRulesService(this.deps);\n        const docsResult = await docsService.run(absolutePath, {\n          preset,\n          indexMode: options.docsIndexMode || 'readme',\n          force: options.force,\n          dryRun: options.dryRun,\n          verbose: options.verbose,\n        });\n        result.docsExported = docsResult.filesCreated;\n        result.targets.push(...docsResult.targets);\n        this.deps.ui.stopSpinner();\n      } catch (error) {\n        errors.push({ type: 'docs', error: error instanceof Error ? error.message : String(error) });\n        this.deps.ui.stopSpinner();\n      }\n    } else if (!options.skipDocs) {\n      // Docs directory doesn't exist - skip silently\n    }\n\n    // Export agents - only if .context/agents exists\n    const agentsPath = path.join(absolutePath, '.context/agents');\n    if (!options.skipAgents && await fs.pathExists(agentsPath)) {\n      try {\n        this.deps.ui.startSpinner('Exporting agents...');\n        const syncService = new SyncService(this.deps);\n        const syncResult: SyncRunResult = await syncService.run({\n          source: agentsPath,\n          preset: preset as PresetName,\n          mode: options.agentMode || 'symlink',\n          force: options.force || false,\n          dryRun: options.dryRun || false,\n          verbose: options.verbose || false,\n        });\n        result.agentsExported = syncResult.filesCreated;\n        result.targets.push(...syncResult.targets.map((target) => target.targetPath));\n        this.deps.ui.stopSpinner();\n      } catch (error) {\n        const errorMsg = error instanceof Error ? error.message : String(error);\n        if (!errorMsg.includes('sourceMissing') && !errorMsg.includes('no agents')) {\n          errors.push({ type: 'agents', error: errorMsg });\n        }\n        this.deps.ui.stopSpinner();\n      }\n    } else if (!options.skipAgents) {\n      // Agents directory doesn't exist - skip silently\n    }\n\n    // Export skills - only if .context/skills exists\n    const skillsPath = path.join(absolutePath, '.context/skills');\n    if (!options.skipSkills && await fs.pathExists(skillsPath)) {\n      try {\n        this.deps.ui.startSpinner('Exporting skills...');\n        const skillsService = new SkillExportService(this.deps);\n        const skillsResult = await skillsService.run(absolutePath, {\n          preset,\n          includeBuiltIn: options.includeBuiltInSkills,\n          force: options.force,\n          dryRun: options.dryRun,\n          verbose: options.verbose,\n        });\n        result.skillsExported = skillsResult.filesCreated;\n        result.targets.push(...skillsResult.targets);\n        this.deps.ui.stopSpinner();\n      } catch (error) {\n        errors.push({ type: 'skills', error: error instanceof Error ? error.message : String(error) });\n        this.deps.ui.stopSpinner();\n      }\n    } else if (!options.skipSkills) {\n      // Skills directory doesn't exist - skip silently\n    }\n\n    // Update error count\n    result.filesFailed = errors.length;\n    for (const err of errors) {\n      result.errors.push({ file: err.type, error: err.error });\n    }\n\n    // Calculate total created\n    result.filesCreated = result.docsExported + result.agentsExported + result.skillsExported;\n\n    // Display summary\n    if (!options.dryRun && result.filesCreated > 0) {\n      this.deps.ui.displaySuccess(\n        `Context exported: ${result.docsExported} docs, ${result.agentsExported} agents, ${result.skillsExported} skills`\n      );\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/services/export/exportRulesService.ts",
    "content": "/**\n * Export Rules Service\n *\n * Bidirectional sync: export rules from .context to AI tool directories.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  BaseDependencies,\n  OperationResult,\n  createEmptyResult,\n  addError,\n  globFiles,\n  resolveAbsolutePath,\n  ensureParentDirectory,\n  ensureDirectory,\n  pathExists,\n  getBasename,\n  displayOperationSummary,\n  getRulesExportPresets,\n} from '../shared';\n\nexport type ExportRulesServiceDependencies = BaseDependencies;\n\nexport interface ExportTarget {\n  name: string;\n  path: string;\n  format: 'single' | 'directory';\n  fileExtension?: string;\n  filename?: string;\n  description: string;\n}\n\nexport interface ExportOptions {\n  source?: string;\n  targets?: string[];\n  preset?: string;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n  /** Index mode: 'readme' exports only README.md files, 'all' exports all matching files */\n  indexMode?: 'readme' | 'all';\n}\n\nexport interface ExportResult extends OperationResult {\n  targets: string[];\n}\n\ninterface RuleFile {\n  name: string;\n  content: string;\n  path: string;\n}\n\n/**\n * Build export presets from the unified tool registry\n */\nfunction buildExportPresets(): Record<string, ExportTarget[]> {\n  const registryPresets = getRulesExportPresets();\n  const presets: Record<string, ExportTarget[]> = {};\n\n  // Add presets from registry\n  for (const [toolId, targets] of Object.entries(registryPresets)) {\n    presets[toolId] = targets;\n  }\n\n  // Add cursor legacy .cursorrules file (in addition to directory)\n  if (presets.cursor) {\n    presets.cursor = [\n      { name: 'cursorrules', path: '.cursorrules', format: 'single', description: 'Cursor AI rules file' },\n      ...presets.cursor,\n    ];\n  }\n\n  // Add universal AGENTS.md\n  presets.agents = [\n    { name: 'agents-md', path: 'AGENTS.md', format: 'single', description: 'Universal AGENTS.md file' },\n  ];\n\n  // Build 'all' preset\n  presets.all = deduplicateTargets(\n    Object.entries(presets)\n    .filter(([key]) => key !== 'all')\n    .flatMap(([, targets]) => targets)\n  );\n\n  return presets;\n}\n\nfunction deduplicateTargets(targets: ExportTarget[]): ExportTarget[] {\n  const seen = new Set<string>();\n  const deduplicated: ExportTarget[] = [];\n\n  for (const target of targets) {\n    const key = `${target.path}:${target.format}:${target.fileExtension || ''}`;\n    if (seen.has(key)) continue;\n    seen.add(key);\n    deduplicated.push(target);\n  }\n\n  return deduplicated;\n}\n\n/**\n * Export presets for different AI tools (derived from tool registry)\n */\nexport const EXPORT_PRESETS: Record<string, ExportTarget[]> = buildExportPresets();\n\nexport class ExportRulesService {\n  constructor(private deps: ExportRulesServiceDependencies) {}\n\n  /**\n   * Export rules to AI tool directories\n   */\n  async run(repoPath: string, options: ExportOptions = {}): Promise<ExportResult> {\n    const absolutePath = path.resolve(repoPath);\n    const sourcePath = resolveAbsolutePath(options.source, '.context/docs', absolutePath);\n\n    const result: ExportResult = {\n      ...createEmptyResult(),\n      targets: [],\n    };\n\n    // Determine targets\n    const targets = this.resolveTargets(options);\n    if (targets.length === 0) {\n      this.deps.ui.displayError(this.deps.t('errors.export.noTargets'));\n      return result;\n    }\n\n    // Read source rules based on indexMode\n    const rules = await this.readSourceRules(sourcePath, options.indexMode);\n    if (rules.length === 0) {\n      this.deps.ui.displayError(this.deps.t('errors.export.noRules'));\n      return result;\n    }\n\n    // Combine rules into single content\n    const combinedContent = this.combineRules(rules);\n\n    // Export to each target\n    for (const target of targets) {\n      await this.exportToTarget(absolutePath, target, rules, combinedContent, options, result);\n    }\n\n    // Display summary\n    if (!options.dryRun && result.filesCreated > 0) {\n      this.deps.ui.displaySuccess(\n        this.deps.t('success.export.completed', { count: result.filesCreated })\n      );\n    }\n\n    if (options.verbose) {\n      displayOperationSummary(result, {\n        dryRun: options.dryRun,\n        labels: { created: 'Exported' },\n      });\n    }\n\n    return result;\n  }\n\n  /**\n   * Export to a single target\n   */\n  private async exportToTarget(\n    repoPath: string,\n    target: ExportTarget,\n    rules: RuleFile[],\n    combinedContent: string,\n    options: ExportOptions,\n    result: ExportResult\n  ): Promise<void> {\n    const targetPath = path.join(repoPath, target.path);\n\n    try {\n      this.deps.ui.startSpinner(\n        this.deps.t('spinner.export.exporting', { target: target.name })\n      );\n\n      if (options.dryRun) {\n        this.deps.ui.updateSpinner(\n          this.deps.t('spinner.export.dryRun', { target: targetPath }),\n          'success'\n        );\n        result.filesSkipped++;\n        this.deps.ui.stopSpinner();\n        return;\n      }\n\n      // Check if target exists and force is not set\n      if (await pathExists(targetPath) && !options.force) {\n        this.deps.ui.updateSpinner(\n          this.deps.t('spinner.export.skipped', { target: targetPath }),\n          'warn'\n        );\n        result.filesSkipped++;\n        this.deps.ui.stopSpinner();\n        return;\n      }\n\n      // Export based on format\n      if (target.format === 'single') {\n        await ensureParentDirectory(targetPath);\n        await fs.writeFile(targetPath, combinedContent, 'utf-8');\n      } else {\n        await this.exportToDirectory(targetPath, rules, target.fileExtension);\n      }\n\n      result.filesCreated++;\n      result.targets.push(targetPath);\n      this.deps.ui.updateSpinner(\n        this.deps.t('spinner.export.exported', { target: targetPath }),\n        'success'\n      );\n    } catch (error) {\n      addError(result, target.name, error);\n      this.deps.ui.updateSpinner(\n        this.deps.t('spinner.export.failed', { target: target.name }),\n        'fail'\n      );\n    } finally {\n      this.deps.ui.stopSpinner();\n    }\n  }\n\n  /**\n   * Resolve export targets from options\n   *\n   * Supports three formats:\n   * 1. Preset name via `preset` option (e.g., 'all', 'cursor')\n   * 2. Preset names in `targets` array (e.g., ['claude', 'cursor'])\n   * 3. Direct paths in `targets` array (e.g., ['.custom/rules'])\n   */\n  private resolveTargets(options: ExportOptions): ExportTarget[] {\n    if (options.preset) {\n      const preset = EXPORT_PRESETS[options.preset.toLowerCase()];\n      if (preset) return preset;\n    }\n\n    if (options.targets?.length) {\n      const resolved: ExportTarget[] = [];\n\n      for (const t of options.targets) {\n        // First check if target is a preset name\n        const preset = EXPORT_PRESETS[t.toLowerCase()];\n        if (preset) {\n          resolved.push(...preset);\n        } else {\n          // Treat as a direct path\n          resolved.push({\n            name: path.basename(t),\n            path: t,\n            format: 'single' as const,\n            description: 'Custom target',\n          });\n        }\n      }\n\n      return deduplicateTargets(resolved);\n    }\n\n    // Default: export to common targets\n    return deduplicateTargets([\n      EXPORT_PRESETS.cursor[0],\n      ...EXPORT_PRESETS.claude,\n      ...EXPORT_PRESETS.github,\n    ]);\n  }\n\n  /**\n   * Read source rules from .context/docs\n   * @param sourcePath - Path to the source directory\n   * @param indexMode - 'readme' to only read README.md files, 'all' to read all matching files\n   */\n  private async readSourceRules(sourcePath: string, indexMode?: 'readme' | 'all'): Promise<RuleFile[]> {\n    // Check if source directory exists\n    if (!await fs.pathExists(sourcePath)) {\n      return [];\n    }\n\n    const rules: RuleFile[] = [];\n\n    try {\n      if (indexMode === 'readme') {\n        // Only read README.md files (indices)\n        return await this.readReadmeIndexFiles(sourcePath);\n      }\n\n      // Default behavior: read all matching files\n      const files = await globFiles(`**/*.md`, sourcePath, { absolute: true });\n\n      for (const file of files) {\n        const basename = getBasename(file).toLowerCase();\n        const isRuleFile =\n          basename.includes('rules') ||\n          basename.includes('instructions') ||\n          basename.includes('conventions') ||\n          basename === 'readme.md' ||\n          basename === 'readme';\n\n        if (isRuleFile) {\n          try {\n            const content = await fs.readFile(file, 'utf-8');\n            rules.push({ name: getBasename(file), content, path: file });\n          } catch {\n            // Skip unreadable files\n          }\n        }\n      }\n    } catch {\n      // Source path doesn't exist\n    }\n\n    return rules;\n  }\n\n  /**\n   * Read only README.md files as indices\n   * This is useful when you want to export just the index files that reference other docs\n   */\n  private async readReadmeIndexFiles(sourcePath: string): Promise<RuleFile[]> {\n    // Check if source directory exists\n    if (!await fs.pathExists(sourcePath)) {\n      return [];\n    }\n\n    const rules: RuleFile[] = [];\n\n    try {\n      const files = await globFiles(`**/README.md`, sourcePath, { absolute: true });\n\n      for (const file of files) {\n        try {\n          const content = await fs.readFile(file, 'utf-8');\n          // Use relative path from sourcePath for naming\n          const relativePath = path.relative(sourcePath, file);\n          const dirName = path.dirname(relativePath);\n          const name = dirName === '.' ? 'README' : `${dirName}/README`;\n          rules.push({ name, content, path: file });\n        } catch {\n          // Skip unreadable files\n        }\n      }\n    } catch {\n      // Source path doesn't exist\n    }\n\n    return rules;\n  }\n\n  /**\n   * Combine multiple rules into a single content block\n   */\n  private combineRules(rules: RuleFile[]): string {\n    const lines = [\n      '# Project Rules and Guidelines',\n      '',\n      `> Auto-generated from .context/docs on ${new Date().toISOString()}`,\n      '',\n    ];\n\n    for (const rule of rules) {\n      lines.push(`## ${rule.name}`, '', rule.content, '');\n    }\n\n    return lines.join('\\n');\n  }\n\n  /**\n   * Export rules to a directory (multiple files)\n   */\n  private async exportToDirectory(\n    targetPath: string,\n    rules: RuleFile[],\n    fileExtension = '.md'\n  ): Promise<void> {\n    await ensureDirectory(targetPath);\n\n    for (const rule of rules) {\n      const filePath = path.join(targetPath, `${rule.name}${fileExtension}`);\n      await fs.writeFile(filePath, rule.content, 'utf-8');\n    }\n  }\n\n  /**\n   * Get available presets\n   */\n  getAvailablePresets(): string[] {\n    return Object.keys(EXPORT_PRESETS);\n  }\n}\n"
  },
  {
    "path": "src/services/export/index.ts",
    "content": "export { ExportRulesService, EXPORT_PRESETS } from './exportRulesService';\nexport type { ExportOptions, ExportResult, ExportTarget, ExportRulesServiceDependencies } from './exportRulesService';\n\nexport { SkillExportService, SKILL_EXPORT_PRESETS } from './skillExportService';\nexport type { SkillExportOptions, SkillExportResult, SkillExportTarget, SkillExportServiceDependencies } from './skillExportService';\n\nexport { ContextExportService } from './contextExportService';\nexport type { ContextExportOptions, ContextExportResult, ContextExportServiceDependencies } from './contextExportService';\n"
  },
  {
    "path": "src/services/export/skillExportService.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { SkillExportService } from './skillExportService';\nimport { minimalUI, mockTranslate } from '../shared';\n\ndescribe('SkillExportService', () => {\n  let tempDir: string;\n  let repoPath: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-skill-export-'));\n    repoPath = path.join(tempDir, 'repo');\n    await fs.ensureDir(repoPath);\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  it('exports portable skill frontmatter and falls back to the built-in template body', async () => {\n    const scaffoldPath = path.join(repoPath, '.context', 'skills', 'commit-message', 'SKILL.md');\n    await fs.outputFile(\n      scaffoldPath,\n      [\n        '---',\n        'name: Commit Message',\n        'description: Generate commit messages for this repository',\n        'phases: [E, C]',\n        '---',\n        '',\n      ].join('\\n'),\n      'utf-8'\n    );\n\n    const service = new SkillExportService({\n      ui: minimalUI as any,\n      t: mockTranslate,\n      version: 'test',\n    });\n\n    const result = await service.run(repoPath, {\n      targets: ['.out/skills'],\n      skills: ['commit-message'],\n      force: true,\n    });\n\n    expect(result.filesCreated).toBe(1);\n\n    const exportedPath = path.join(repoPath, '.out', 'skills', 'commit-message', 'SKILL.md');\n    const content = await fs.readFile(exportedPath, 'utf-8');\n\n    expect(content).toContain('name: commit-message');\n    expect(content).toContain('description: Generate commit messages for this repository');\n    expect(content).not.toContain('phases:');\n    expect(content).not.toContain('type: skill');\n    expect(content).not.toContain('status: unfilled');\n    expect(content).toContain('## Workflow');\n    expect(content).toContain('## Resource Strategy');\n    expect(content).not.toContain('## When to Use');\n  });\n});\n"
  },
  {
    "path": "src/services/export/skillExportService.ts",
    "content": "/**\n * Skill Export Service\n *\n * Export skills from .context/skills to AI tool skill directories.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  BaseDependencies,\n  OperationResult,\n  createEmptyResult,\n  addError,\n  ensureDirectory,\n  pathExists,\n  displayOperationSummary,\n  getSkillsExportPresets,\n} from '../shared';\nimport { createSkillRegistry, Skill, BUILT_IN_SKILLS, getBuiltInSkillTemplates, SKILL_TO_PHASES, wrapWithPortableFrontmatter } from '../../workflow/skills';\n\nexport type SkillExportServiceDependencies = BaseDependencies;\n\nexport interface SkillExportTarget {\n  name: string;\n  path: string;\n  description: string;\n}\n\nexport interface SkillExportOptions {\n  targets?: string[];\n  preset?: string;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n  /** Only export specific skills */\n  skills?: string[];\n  /** Include built-in skills even if not scaffolded */\n  includeBuiltIn?: boolean;\n}\n\nexport interface SkillExportResult extends OperationResult {\n  targets: string[];\n  skillsExported: string[];\n}\n\n/**\n * Build skill export presets from the unified tool registry\n */\nfunction buildSkillExportPresets(): Record<string, SkillExportTarget[]> {\n  const registryPresets = getSkillsExportPresets();\n  const presets: Record<string, SkillExportTarget[]> = {};\n\n  for (const [toolId, targets] of Object.entries(registryPresets)) {\n    presets[toolId] = targets;\n  }\n\n  // Build 'all' preset\n  presets.all = Object.entries(presets)\n    .filter(([key]) => key !== 'all')\n    .flatMap(([, targets]) => targets);\n\n  return presets;\n}\n\n/**\n * Skill export presets for different AI tools (derived from tool registry)\n */\nexport const SKILL_EXPORT_PRESETS: Record<string, SkillExportTarget[]> = buildSkillExportPresets();\n\nexport class SkillExportService {\n  constructor(private deps: SkillExportServiceDependencies) {}\n\n  /**\n   * Export skills to AI tool directories\n   */\n  async run(repoPath: string, options: SkillExportOptions = {}): Promise<SkillExportResult> {\n    const absolutePath = path.resolve(repoPath);\n\n    const result: SkillExportResult = {\n      ...createEmptyResult(),\n      targets: [],\n      skillsExported: [],\n    };\n\n    // Determine targets\n    const targets = this.resolveTargets(options);\n    if (targets.length === 0) {\n      this.deps.ui.displayError('No export targets specified');\n      return result;\n    }\n\n    // Get skills to export\n    const skills = await this.getSkillsToExport(absolutePath, options);\n    if (skills.length === 0) {\n      this.deps.ui.displayWarning('No skills found to export');\n      return result;\n    }\n\n    // Export to each target\n    for (const target of targets) {\n      await this.exportToTarget(absolutePath, target, skills, options, result);\n    }\n\n    // Display summary\n    if (!options.dryRun && result.filesCreated > 0) {\n      this.deps.ui.displaySuccess(\n        `Exported ${result.skillsExported.length} skills to ${result.targets.length} targets`\n      );\n    }\n\n    if (options.verbose) {\n      displayOperationSummary(result, {\n        dryRun: options.dryRun,\n        labels: { created: 'Exported' },\n      });\n    }\n\n    return result;\n  }\n\n  /**\n   * Get skills to export\n   */\n  private async getSkillsToExport(repoPath: string, options: SkillExportOptions): Promise<Skill[]> {\n    const fs = await import('fs-extra');\n    const skillsPath = path.join(repoPath, '.context', 'skills');\n    const skillsExist = await fs.pathExists(skillsPath);\n\n    // If skills directory doesn't exist and includeBuiltIn is not enabled, return empty\n    if (!skillsExist && !options.includeBuiltIn) {\n      return [];\n    }\n\n    const registry = createSkillRegistry(repoPath);\n    const discovered = await registry.discoverAll();\n\n    let skills = discovered.all;\n\n    // If includeBuiltIn, add any built-in skills that aren't scaffolded\n    if (options.includeBuiltIn) {\n      const templates = getBuiltInSkillTemplates();\n      const existingSlugs = new Set(skills.map(s => s.slug));\n\n      for (const skillType of BUILT_IN_SKILLS) {\n        if (!existingSlugs.has(skillType)) {\n          const template = templates[skillType];\n          skills.push({\n            slug: skillType,\n            path: path.join(repoPath, '.context', 'skills', skillType, 'SKILL.md'),\n            metadata: {\n              name: skillType,\n              description: template.description,\n              phases: SKILL_TO_PHASES[skillType],\n            },\n            content: template.content,\n            resources: [],\n            isBuiltIn: true,\n          });\n        }\n      }\n    }\n\n    // Filter by specific skills if provided\n    if (options.skills?.length) {\n      skills = skills.filter(s => options.skills!.includes(s.slug));\n    }\n\n    return skills;\n  }\n\n  /**\n   * Export to a single target\n   */\n  private async exportToTarget(\n    repoPath: string,\n    target: SkillExportTarget,\n    skills: Skill[],\n    options: SkillExportOptions,\n    result: SkillExportResult\n  ): Promise<void> {\n    const targetPath = path.join(repoPath, target.path);\n\n    try {\n      this.deps.ui.startSpinner(`Exporting skills to ${target.name}...`);\n\n      if (options.dryRun) {\n        this.deps.ui.updateSpinner(`[DRY-RUN] Would export to ${targetPath}`, 'success');\n        result.filesSkipped++;\n        this.deps.ui.stopSpinner();\n        return;\n      }\n\n      // Create target directory\n      await ensureDirectory(targetPath);\n\n      // Export each skill\n      for (const skill of skills) {\n        await this.exportSkill(targetPath, skill, options, result);\n      }\n\n      result.targets.push(targetPath);\n      this.deps.ui.updateSpinner(`Exported to ${targetPath}`, 'success');\n    } catch (error) {\n      addError(result, target.name, error);\n      this.deps.ui.updateSpinner(`Failed to export to ${target.name}`, 'fail');\n    } finally {\n      this.deps.ui.stopSpinner();\n    }\n  }\n\n  /**\n   * Export a single skill to target directory\n   */\n  private async exportSkill(\n    targetPath: string,\n    skill: Skill,\n    options: SkillExportOptions,\n    result: SkillExportResult\n  ): Promise<void> {\n    const skillDir = path.join(targetPath, skill.slug);\n    const skillFile = path.join(skillDir, 'SKILL.md');\n\n    // Check if exists and force not set\n    if (await pathExists(skillFile) && !options.force) {\n      result.filesSkipped++;\n      return;\n    }\n\n    // Create skill directory\n    await ensureDirectory(skillDir);\n\n    // Generate SKILL.md content\n    const content = this.generateSkillContent(skill);\n    await fs.writeFile(skillFile, content, 'utf-8');\n\n    // Copy resources if any\n    for (const resource of skill.resources) {\n      const relativePath = path.relative(path.dirname(skill.path), resource);\n      const destPath = path.join(skillDir, relativePath);\n      await ensureDirectory(path.dirname(destPath));\n\n      try {\n        await fs.copy(resource, destPath);\n      } catch {\n        // Skip if resource can't be copied\n      }\n    }\n\n    result.filesCreated++;\n    if (!result.skillsExported.includes(skill.slug)) {\n      result.skillsExported.push(skill.slug);\n    }\n  }\n\n  /**\n   * Generate SKILL.md content with frontmatter\n   */\n  private generateSkillContent(skill: Skill): string {\n    const templates = getBuiltInSkillTemplates();\n    const fallbackContent = skill.isBuiltIn ? templates[skill.slug as keyof typeof templates]?.content : undefined;\n    const content = skill.content.trim() ? skill.content : fallbackContent || '';\n    return wrapWithPortableFrontmatter(skill.slug, skill.metadata.description, content);\n  }\n\n  /**\n   * Resolve export targets from options\n   *\n   * Supports three formats:\n   * 1. Preset name via `preset` option (e.g., 'all', 'claude')\n   * 2. Preset names in `targets` array (e.g., ['claude', 'gemini'])\n   * 3. Direct paths in `targets` array (e.g., ['.custom/skills'])\n   */\n  private resolveTargets(options: SkillExportOptions): SkillExportTarget[] {\n    if (options.preset) {\n      const preset = SKILL_EXPORT_PRESETS[options.preset.toLowerCase()];\n      if (preset) return preset;\n    }\n\n    if (options.targets?.length) {\n      const resolved: SkillExportTarget[] = [];\n\n      for (const t of options.targets) {\n        // First check if target is a preset name\n        const preset = SKILL_EXPORT_PRESETS[t.toLowerCase()];\n        if (preset) {\n          resolved.push(...preset);\n        } else {\n          // Treat as a direct path\n          resolved.push({\n            name: path.basename(t),\n            path: t,\n            description: 'Custom target',\n          });\n        }\n      }\n\n      return resolved;\n    }\n\n    // Default: export to all supported tools\n    return SKILL_EXPORT_PRESETS.all;\n  }\n\n  /**\n   * Get available presets\n   */\n  getAvailablePresets(): string[] {\n    return Object.keys(SKILL_EXPORT_PRESETS);\n  }\n}\n"
  },
  {
    "path": "src/services/harness/agentsService.ts",
    "content": "/**\n * Harness Agents Service\n *\n * Transport-agnostic agent discovery and orchestration logic.\n */\n\nimport {\n  PHASE_NAMES_EN,\n  ROLE_DISPLAY_NAMES,\n  agentOrchestrator,\n  documentLinker,\n  AgentType,\n  AGENT_TYPES,\n  createPlanLinker,\n} from '../../workflow';\nimport type { PrevcPhase, PrevcRole } from '../../workflow';\n\nexport interface HarnessAgentsServiceOptions {\n  repoPath: string;\n}\n\nexport class HarnessAgentsService {\n  constructor(private readonly options: HarnessAgentsServiceOptions) {}\n\n  private get repoPath(): string {\n    return this.options.repoPath || process.cwd();\n  }\n\n  async discover(): Promise<Record<string, unknown>> {\n    const linker = createPlanLinker(this.repoPath);\n    const agents = await linker.discoverAgents();\n\n    const builtIn = agents.filter(a => !a.isCustom);\n    const custom = agents.filter(a => a.isCustom);\n\n    return {\n      success: true,\n      totalAgents: agents.length,\n      builtInCount: builtIn.length,\n      customCount: custom.length,\n      agents: {\n        builtIn: builtIn.map(a => a.type),\n        custom: custom.map(a => ({ type: a.type, path: a.path })),\n      },\n    };\n  }\n\n  async getInfo(agentType: string): Promise<Record<string, unknown>> {\n    const linker = createPlanLinker(this.repoPath);\n    const info = await linker.getAgentInfo(agentType);\n\n    return {\n      success: true,\n      agent: info,\n    };\n  }\n\n  orchestrate(params: {\n    task?: string;\n    phase?: PrevcPhase;\n    role?: PrevcRole;\n  }): Record<string, unknown> {\n    let agents: AgentType[] = [];\n    let source = '';\n\n    if (params.task) {\n      agents = agentOrchestrator.selectAgentsByTask(params.task);\n      source = `task: \"${params.task}\"`;\n    } else if (params.phase) {\n      agents = agentOrchestrator.getAgentsForPhase(params.phase);\n      source = `phase: ${params.phase} (${PHASE_NAMES_EN[params.phase]})`;\n    } else if (params.role) {\n      agents = agentOrchestrator.getAgentsForRole(params.role);\n      source = `role: ${ROLE_DISPLAY_NAMES[params.role]}`;\n    } else {\n      throw new Error('Provide task, phase, or role parameter');\n    }\n\n    return {\n      source,\n      agents: agents.map((agent) => ({\n        type: agent,\n        description: agentOrchestrator.getAgentDescription(agent),\n        docs: documentLinker.getDocPathsForAgent(agent),\n      })),\n      count: agents.length,\n    };\n  }\n\n  getSequence(params: {\n    phases?: PrevcPhase[];\n    task?: string;\n    includeReview?: boolean;\n  }): Record<string, unknown> {\n    let sequence: AgentType[];\n\n    if (params.phases && params.phases.length > 0) {\n      sequence = agentOrchestrator.getAgentHandoffSequence(params.phases);\n    } else {\n      sequence = agentOrchestrator.getTaskAgentSequence(\n        params.task!,\n        params.includeReview !== false\n      );\n    }\n\n    return {\n      task: params.task,\n      sequence: sequence.map((agent, index) => ({\n        order: index + 1,\n        agent,\n        description: agentOrchestrator.getAgentDescription(agent),\n        primaryDoc: documentLinker.getPrimaryDocForAgent(agent)?.path || null,\n      })),\n      totalAgents: sequence.length,\n    };\n  }\n\n  getDocs(agent: AgentType): Record<string, unknown> {\n    if (!agentOrchestrator.isValidAgentType(agent)) {\n      throw new Error(`Invalid agent type \"${agent}\". Valid types: ${AGENT_TYPES.join(', ')}`);\n    }\n\n    const docs = documentLinker.getDocsForAgent(agent);\n\n    return {\n      agent,\n      description: agentOrchestrator.getAgentDescription(agent),\n      documentation: docs.map((doc) => ({\n        type: doc.type,\n        title: doc.title,\n        path: doc.path,\n        description: doc.description,\n      })),\n    };\n  }\n\n  getPhaseDocs(phase: PrevcPhase): Record<string, unknown> {\n    const docs = documentLinker.getDocsForPhase(phase);\n    const agents = agentOrchestrator.getAgentsForPhase(phase);\n\n    return {\n      phase,\n      phaseName: PHASE_NAMES_EN[phase],\n      documentation: docs.map((doc) => ({\n        type: doc.type,\n        title: doc.title,\n        path: doc.path,\n        description: doc.description,\n      })),\n      recommendedAgents: agents.map((agent) => ({\n        type: agent,\n        description: agentOrchestrator.getAgentDescription(agent),\n      })),\n    };\n  }\n\n  listTypes(): Record<string, unknown> {\n    const agents = agentOrchestrator.getAllAgentTypes().map((agent) => ({\n      type: agent,\n      description: agentOrchestrator.getAgentDescription(agent),\n      primaryDoc: documentLinker.getPrimaryDocForAgent(agent)?.title || null,\n    }));\n\n    return {\n      agents,\n      total: agents.length,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/contextService.ts",
    "content": "/**\n * Harness Context Service\n *\n * Transport-agnostic context, semantic analysis, and scaffold orchestration logic.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport {\n  checkScaffoldingTool,\n  initializeContextTool,\n  scaffoldPlanTool,\n  fillScaffoldingTool,\n  listFilesToFillTool,\n  fillSingleFileTool,\n  getCodebaseMapTool,\n} from './contextTools';\nimport { SemanticContextBuilder, type ContextFormat } from '../semantic/contextBuilder';\nimport { CodebaseAnalyzer } from '../semantic/codebaseAnalyzer';\nimport { QAService } from '../qa';\nimport {\n  getContextLayoutByClassification,\n  toolExecutionContext,\n  type ContextLayoutEntry,\n} from '../shared';\nimport { HarnessWorkflowStateService } from './workflowStateService';\n\nexport interface HarnessContextServiceOptions {\n  repoPath: string;\n  contextBuilder: SemanticContextBuilder;\n}\n\nexport interface HarnessContextInitResult {\n  result: Record<string, unknown>;\n  scaffold: {\n    filesGenerated?: number;\n    pendingFiles?: string[];\n    repoPath?: string;\n    enhancementPrompt?: string;\n    nextSteps?: string[];\n  } | null;\n}\n\nexport interface HarnessContextPlanScaffoldResult {\n  result: Record<string, unknown>;\n  scaffold: {\n    filesGenerated?: number;\n    pendingFiles?: string[];\n    repoPath?: string;\n    enhancementPrompt?: string;\n    nextSteps?: string[];\n  } | null;\n}\n\nexport interface HarnessBootstrapStatusResult {\n  success: true;\n  repoPath: string;\n  outputDir: string;\n  layout: {\n    versioned: ContextLayoutEntry[];\n    local: ContextLayoutEntry[];\n    runtime: ContextLayoutEntry[];\n  };\n  scaffold: {\n    initialized: boolean;\n    docs: boolean;\n    agents: boolean;\n    skills: boolean;\n    sensors: boolean;\n    plans: boolean;\n    qa: boolean;\n  };\n  runtime: {\n    workflow: boolean;\n    harness: boolean;\n    harnessBinding: boolean;\n    sessionCount: number;\n    traceCount: number;\n    artifactSessionCount: number;\n  };\n  readiness: {\n    scaffoldReady: boolean;\n    skillsReady: boolean;\n    sensorsReady: boolean;\n    workflowReady: boolean;\n    harnessReady: boolean;\n    configurationReady: boolean;\n    runtimeReady: boolean;\n    complete: boolean;\n  };\n  nextSteps: string[];\n}\n\nexport class HarnessContextService {\n  constructor(private readonly options: HarnessContextServiceOptions) {}\n\n  private get repoPath(): string {\n    return this.options.repoPath;\n  }\n\n  async check(repoPath?: string): Promise<unknown> {\n    return checkScaffoldingTool.execute!(\n      { repoPath: repoPath || this.repoPath },\n      toolExecutionContext\n    );\n  }\n\n  async bootstrapStatus(repoPath?: string): Promise<HarnessBootstrapStatusResult> {\n    const resolvedRepoPath = path.resolve(repoPath || this.repoPath);\n    const outputDir = path.join(resolvedRepoPath, '.context');\n    const scaffoldStatus = await this.check(resolvedRepoPath) as Record<string, unknown>;\n    const workflowStateService = new HarnessWorkflowStateService({ contextPath: outputDir });\n\n    const harnessDir = path.join(outputDir, 'harness');\n    const qaDir = path.join(outputDir, 'docs', 'qa');\n    const sessionsDir = path.join(harnessDir, 'sessions');\n    const tracesDir = path.join(harnessDir, 'traces');\n    const artifactsDir = path.join(harnessDir, 'artifacts');\n\n    const [qa, workflowActive, harnessBinding, sessionCount, traceCount, artifactSessionCount] = await Promise.all([\n      fs.pathExists(qaDir).then((exists) => exists ? fs.readdir(qaDir).then((entries) => entries.some((entry) => entry.endsWith('.md') && entry.toLowerCase() !== 'readme.md')) : false),\n      workflowStateService.exists(),\n      workflowStateService.getBinding().then((binding) => Boolean(binding)),\n      fs.pathExists(sessionsDir).then((exists) => exists ? fs.readdir(sessionsDir).then((entries) => entries.filter((entry) => entry.endsWith('.json')).length) : 0),\n      fs.pathExists(tracesDir).then((exists) => exists ? fs.readdir(tracesDir).then((entries) => entries.filter((entry) => entry.endsWith('.jsonl')).length) : 0),\n      fs.pathExists(artifactsDir).then((exists) => exists ? fs.readdir(artifactsDir).then((entries) => entries.length) : 0),\n    ]);\n\n    const scaffold = {\n      initialized: Boolean(scaffoldStatus.initialized),\n      docs: Boolean(scaffoldStatus.docs),\n      agents: Boolean(scaffoldStatus.agents),\n      skills: Boolean(scaffoldStatus.skills),\n      sensors: Boolean(scaffoldStatus.sensors),\n      plans: Boolean(scaffoldStatus.plans),\n      qa,\n    };\n\n    const runtime = {\n      workflow: Boolean(scaffoldStatus.workflow) || workflowActive,\n      harness: Boolean(scaffoldStatus.harness),\n      harnessBinding,\n      sessionCount,\n      traceCount,\n      artifactSessionCount,\n    };\n\n    const readiness = {\n      scaffoldReady: scaffold.initialized && (scaffold.docs || scaffold.agents || scaffold.skills || scaffold.plans),\n      skillsReady: scaffold.skills,\n      sensorsReady: scaffold.sensors,\n      workflowReady: runtime.workflow,\n      harnessReady: runtime.harness && (runtime.harnessBinding || runtime.sessionCount > 0),\n      configurationReady: false,\n      runtimeReady: false,\n      complete: false,\n    };\n    readiness.configurationReady = readiness.scaffoldReady && readiness.skillsReady && readiness.sensorsReady;\n    readiness.runtimeReady = readiness.workflowReady && readiness.harnessReady;\n    readiness.complete = readiness.configurationReady && readiness.runtimeReady;\n\n    const nextSteps: string[] = [];\n    if (!scaffold.initialized) {\n      nextSteps.push('Run context init to scaffold .context assets.');\n    } else {\n      if (!scaffold.skills) {\n        nextSteps.push('Regenerate or create .context/skills so skills are available to agents.');\n      }\n      if (!scaffold.sensors) {\n        nextSteps.push('Regenerate or create .context/harness/sensors.json so quality sensors are customizable.');\n      }\n      if (!runtime.workflow) {\n        nextSteps.push('Run workflow-init to create .context/workflow and enable PREVC execution.');\n      }\n      if (runtime.workflow && !runtime.harnessBinding) {\n        nextSteps.push('Create or refresh the workflow harness binding so workflow and harness stay connected.');\n      }\n      if (!runtime.harness) {\n        nextSteps.push('Run a workflow or harness action to materialize .context/harness runtime state.');\n      }\n    }\n    if (nextSteps.length === 0) {\n      nextSteps.push('Bootstrap is complete. The repository has scaffolded context, workflow state, and active harness runtime artifacts.');\n    }\n\n    return {\n      success: true,\n      repoPath: resolvedRepoPath,\n      outputDir,\n      layout: {\n        versioned: getContextLayoutByClassification('versioned'),\n        local: getContextLayoutByClassification('local'),\n        runtime: getContextLayoutByClassification('runtime'),\n      },\n      scaffold,\n      runtime,\n      readiness,\n      nextSteps,\n    };\n  }\n\n  async init(params: {\n    repoPath?: string;\n    type?: 'docs' | 'agents' | 'both';\n    outputDir?: string;\n    semantic?: boolean;\n    include?: string[];\n    exclude?: string[];\n    autoFill?: boolean;\n    skipContentGeneration?: boolean;\n    generateQA?: boolean;\n  }): Promise<HarnessContextInitResult> {\n    const repoPath = params.repoPath || this.repoPath;\n    const result = await initializeContextTool.execute!(\n      {\n        repoPath,\n        type: params.type,\n        outputDir: params.outputDir,\n        semantic: params.semantic,\n        include: params.include,\n        exclude: params.exclude,\n        autoFill: params.autoFill,\n        skipContentGeneration: params.skipContentGeneration,\n        generateQA: params.generateQA,\n      },\n      toolExecutionContext\n    ) as Record<string, unknown>;\n\n    const pendingWrites = result.pendingWrites as Array<{ filePath: string }> | undefined;\n    if (pendingWrites && pendingWrites.length > 0) {\n      return {\n        result,\n        scaffold: {\n          filesGenerated: pendingWrites.length,\n          pendingFiles: pendingWrites.map(p => p.filePath),\n          repoPath,\n          enhancementPrompt: `SCAFFOLDING CREATED - CONTENT REQUIRED\n\nNEXT ACTIONS REQUIRED:\n1. Fill scaffold files with content using fillSingle for each pending file\n2. Initialize a PREVC workflow to enable structured development\n\nWORKFLOW:\nStep 1: Use context with action \"fillSingle\" for each file in pendingFiles array\nStep 2: Use workflow-init with name parameter to create workflow (creates .context/workflow/)\n\nSkip workflow-init ONLY if making trivial changes (typos, single-line edits).`,\n          nextSteps: [\n            'REQUIRED: Call context with action \"fillSingle\" for each file in pendingFiles',\n            'RECOMMENDED: Call workflow-init with name parameter after filling files',\n            'OPTIONAL: Skip workflow-init only for trivial changes (typos, single edits)'\n          ],\n        },\n      };\n    }\n\n    return {\n      result,\n      scaffold: {\n        enhancementPrompt: `✓ SCAFFOLDING READY\n\nRECOMMENDED NEXT ACTION:\nInitialize a PREVC workflow to enable structured development.\n\nUse workflow-init with name parameter to create workflow structure.\nThis creates .context/workflow/ and enables phase-gated execution (Plan → Review → Execute → Verify → Complete).\n\nSkip ONLY for trivial changes (typos, single-line edits).`,\n        nextSteps: [\n          'RECOMMENDED: Call workflow-init with name parameter to enable PREVC phases',\n          'OPTIONAL: Skip workflow-init only for trivial changes'\n        ],\n      },\n    };\n  }\n\n  async fill(params: {\n    repoPath?: string;\n    outputDir?: string;\n    target?: 'docs' | 'agents' | 'skills' | 'plans' | 'sensors' | 'all';\n    offset?: number;\n    limit?: number;\n  }): Promise<unknown> {\n    return fillScaffoldingTool.execute!(\n      {\n        repoPath: params.repoPath || this.repoPath,\n        outputDir: params.outputDir,\n        target: params.target,\n        offset: params.offset,\n        limit: params.limit\n      },\n      toolExecutionContext\n    );\n  }\n\n  async fillSingle(params: { repoPath?: string; filePath: string }): Promise<unknown> {\n    return fillSingleFileTool.execute!(\n      { repoPath: params.repoPath || this.repoPath, filePath: params.filePath },\n      toolExecutionContext\n    );\n  }\n\n  async listToFill(params: {\n    repoPath?: string;\n    outputDir?: string;\n    target?: 'docs' | 'agents' | 'skills' | 'plans' | 'sensors' | 'all';\n  }): Promise<unknown> {\n    return listFilesToFillTool.execute!(\n      {\n        repoPath: params.repoPath || this.repoPath,\n        outputDir: params.outputDir,\n        target: params.target\n      },\n      toolExecutionContext\n    );\n  }\n\n  async getMap(params: { repoPath?: string; section?: string }): Promise<unknown> {\n    return getCodebaseMapTool.execute!(\n      {\n        repoPath: params.repoPath || this.repoPath,\n        section: params.section as any\n      },\n      toolExecutionContext\n    );\n  }\n\n  async buildSemantic(params: {\n    repoPath?: string;\n    contextType?: 'documentation' | 'playbook' | 'plan' | 'compact';\n    targetFile?: string;\n    options?: {\n      useLSP?: boolean;\n      maxContextLength?: number;\n      includeDocumentation?: boolean;\n      includeSignatures?: boolean;\n    };\n  }): Promise<string> {\n    const repoPath = params.repoPath || this.repoPath;\n    const isLocalBuilder = !!params.options;\n    const builder = isLocalBuilder\n      ? new SemanticContextBuilder(params.options)\n      : this.options.contextBuilder;\n\n    try {\n      const contextType = (params.contextType || 'compact') as ContextFormat;\n\n      switch (contextType) {\n        case 'documentation':\n          return await builder.buildDocumentationContext(repoPath, params.targetFile);\n        case 'playbook':\n          return await builder.buildPlaybookContext(repoPath, params.targetFile || 'generic');\n        case 'plan':\n          return await builder.buildPlanContext(repoPath, params.targetFile);\n        case 'compact':\n        default:\n          return await builder.buildCompactContext(repoPath);\n      }\n    } finally {\n      if (isLocalBuilder) {\n        await builder.shutdown();\n      }\n    }\n  }\n\n  async scaffoldPlan(params: {\n    planName: string;\n    repoPath?: string;\n    outputDir?: string;\n    title?: string;\n    summary?: string;\n    semantic?: boolean;\n    autoFill?: boolean;\n  }): Promise<HarnessContextPlanScaffoldResult> {\n    const repoPath = params.repoPath || this.repoPath;\n    const result = await scaffoldPlanTool.execute!(\n      {\n        planName: params.planName,\n        repoPath,\n        outputDir: params.outputDir,\n        title: params.title,\n        summary: params.summary,\n        semantic: params.semantic,\n        autoFill: params.autoFill\n      },\n      toolExecutionContext\n    ) as Record<string, unknown>;\n\n    const planPath = result.planPath as string | undefined;\n    const planSlug = planPath\n      ? path.basename(planPath, path.extname(planPath))\n      : params.planName;\n    const requiresPlanFill = result.complete !== true;\n\n    return {\n      result,\n      scaffold: result.success && planPath\n        ? {\n            filesGenerated: requiresPlanFill ? 1 : 0,\n            pendingFiles: requiresPlanFill ? [planPath] : [],\n            repoPath,\n            enhancementPrompt: buildPlanWorkflowPrompt({\n              planPath,\n              planSlug,\n              requiresPlanFill,\n            }),\n            nextSteps: buildPlanWorkflowNextSteps({\n              planPath,\n              planSlug,\n              requiresPlanFill,\n            }),\n          }\n        : null,\n    };\n  }\n\n  async searchQA(params: {\n    repoPath?: string;\n    query: string;\n    options?: {\n      useLSP?: boolean;\n      maxContextLength?: number;\n      includeDocumentation?: boolean;\n      includeSignatures?: boolean;\n    };\n  }): Promise<Record<string, unknown>> {\n    const qaService = new QAService(params.options);\n    try {\n      const results = await qaService.search(params.repoPath || this.repoPath, params.query);\n      return {\n        query: params.query,\n        results,\n        count: results.length,\n      };\n    } finally {\n      await qaService.shutdown();\n    }\n  }\n\n  async generateQA(params: {\n    repoPath?: string;\n    options?: {\n      useLSP?: boolean;\n      maxContextLength?: number;\n      includeDocumentation?: boolean;\n      includeSignatures?: boolean;\n    };\n  }): Promise<Record<string, unknown>> {\n    const qaService = new QAService(params.options);\n    try {\n      const result = await qaService.generateFromCodebase(params.repoPath || this.repoPath);\n      return {\n        generated: result.generated.length,\n        skipped: result.skipped,\n        projectType: result.topicDetection.projectType,\n        topics: result.topicDetection.topics.map((t) => t.slug),\n        files: result.generated.map((e) => `${e.slug}.md`),\n      };\n    } finally {\n      await qaService.shutdown();\n    }\n  }\n\n  async getFlow(params: {\n    repoPath?: string;\n    entryFile: string;\n    entryFunction?: string;\n    options?: {\n      useLSP?: boolean;\n      maxContextLength?: number;\n      includeDocumentation?: boolean;\n      includeSignatures?: boolean;\n    };\n  }): Promise<Record<string, unknown>> {\n    const analyzer = new CodebaseAnalyzer(params.options);\n    try {\n      const flow = await analyzer.traceFlow(\n        params.repoPath || this.repoPath,\n        params.entryFile,\n        params.entryFunction\n      );\n      return {\n        entryPoint: flow.entryPoint,\n        nodeCount: flow.nodes.length,\n        edgeCount: flow.edges.length,\n        mermaid: flow.mermaidDiagram,\n        nodes: flow.nodes.slice(0, 20),\n        edges: flow.edges.slice(0, 30),\n      };\n    } finally {\n      await analyzer.shutdown();\n    }\n  }\n\n  async detectPatterns(params: {\n    repoPath?: string;\n    options?: {\n      useLSP?: boolean;\n      maxContextLength?: number;\n      includeDocumentation?: boolean;\n      includeSignatures?: boolean;\n    };\n  }): Promise<Record<string, unknown>> {\n    const analyzer = new CodebaseAnalyzer(params.options);\n    try {\n      const patterns = await analyzer.detectFunctionalPatterns(params.repoPath || this.repoPath);\n      return {\n        hasAuthPattern: patterns.hasAuthPattern,\n        hasDatabasePattern: patterns.hasDatabasePattern,\n        hasApiPattern: patterns.hasApiPattern,\n        hasCachePattern: patterns.hasCachePattern,\n        hasQueuePattern: patterns.hasQueuePattern,\n        hasWebSocketPattern: patterns.hasWebSocketPattern,\n        hasLoggingPattern: patterns.hasLoggingPattern,\n        hasValidationPattern: patterns.hasValidationPattern,\n        hasErrorHandlingPattern: patterns.hasErrorHandlingPattern,\n        hasTestingPattern: patterns.hasTestingPattern,\n        patterns: patterns.patterns.map((p) => ({\n          type: p.type,\n          confidence: p.confidence,\n          description: p.description,\n          indicatorCount: p.indicators.length,\n        })),\n      };\n    } finally {\n      await analyzer.shutdown();\n    }\n  }\n}\n\nfunction buildPlanWorkflowPrompt(params: {\n  planPath: string;\n  planSlug: string;\n  requiresPlanFill: boolean;\n}): string {\n  const { planPath, planSlug, requiresPlanFill } = params;\n  const fillStep = requiresPlanFill\n    ? `1. Fill or refine the plan via context({ action: \"fillSingle\", filePath: \"${planPath}\" })\n2. Start the harness-backed workflow via workflow-init({ name: \"${planSlug}\" })\n3. Link the plan into the active workflow via plan({ action: \"link\", planSlug: \"${planSlug}\" })\n4. Confirm harness state via workflow-status()`\n    : `1. Start the harness-backed workflow via workflow-init({ name: \"${planSlug}\" })\n2. Link the plan into the active workflow via plan({ action: \"link\", planSlug: \"${planSlug}\" })\n3. Confirm harness state via workflow-status()`;\n\n  return `PLAN CREATED - START HARNESS WORKFLOW\n\nThe plan file exists, but planning is not operational until PREVC is started through workflow-init.\n\nworkflow-init is the MCP entry point that creates the canonical harness workflow state at:\n.context/harness/workflows/prevc.json\n\nNEXT ACTIONS:\n${fillStep}\n\nSkip workflow-init only for trivial work that does not need PREVC tracking.`;\n}\n\nfunction buildPlanWorkflowNextSteps(params: {\n  planPath: string;\n  planSlug: string;\n  requiresPlanFill: boolean;\n}): string[] {\n  const { planPath, planSlug, requiresPlanFill } = params;\n  const steps: string[] = [];\n\n  if (requiresPlanFill) {\n    steps.push(`REQUIRED: Call context({ action: \"fillSingle\", filePath: \"${planPath}\" }) to complete the plan`);\n  }\n\n  steps.push(`REQUIRED: Call workflow-init({ name: \"${planSlug}\" }) to start the harness-backed PREVC workflow`);\n  steps.push(`REQUIRED: Call plan({ action: \"link\", planSlug: \"${planSlug}\" }) after workflow-init so gates use the plan`);\n  steps.push('THEN: Call workflow-status() to confirm the workflow and harness binding are active');\n\n  return steps;\n}\n"
  },
  {
    "path": "src/services/harness/contextTools.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport {\n  fillSingleFileTool,\n  getCodebaseMapTool,\n  initializeContextTool,\n  listFilesToFillTool,\n} from './contextTools';\nimport { toolExecutionContext } from '../shared';\n\ndescribe('contextTools sensors scaffolding', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-context-tools-'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'context-tools-test',\n      version: '1.0.0',\n      scripts: {\n        build: 'tsc -p tsconfig.json',\n        test: 'jest --runInBand',\n        lint: 'eslint .',\n      },\n    }, { spaces: 2 });\n    await fs.ensureDir(path.join(tempDir, 'src'));\n    await fs.writeFile(path.join(tempDir, 'src', 'index.ts'), 'export const ok = true;\\n', 'utf-8');\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('includes bootstrap sensors.json in pending writes and listToFill', async () => {\n    const result = await initializeContextTool.execute!(\n      {\n        repoPath: tempDir,\n        type: 'docs',\n        generateQA: false,\n        skipContentGeneration: true,\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    const sensorsPath = path.join(tempDir, '.context', 'harness', 'sensors.json');\n\n    expect(result.pendingWrites.some((item: { filePath: string }) => item.filePath === sensorsPath)).toBe(true);\n\n    const listed = await listFilesToFillTool.execute!(\n      {\n        repoPath: tempDir,\n        target: 'sensors',\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(listed.files).toHaveLength(1);\n    expect(listed.files[0].relativePath).toBe(path.join('harness', 'sensors.json'));\n    expect(listed.files[0].type).toBe('sensor');\n  });\n\n  it('keeps Q&A helper docs opt-in during init', async () => {\n    const result = await initializeContextTool.execute!(\n      {\n        repoPath: tempDir,\n        type: 'docs',\n        skipContentGeneration: true,\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(result.qaGenerated).toBe(0);\n    expect(result.checklist).toContain('[ ] Generate optional Q&A helper docs (opt-in)');\n    await expect(fs.pathExists(path.join(tempDir, '.context', 'docs', 'qa'))).resolves.toBe(false);\n  });\n\n  it('generates a repo-specific harness policy during init', async () => {\n    await initializeContextTool.execute!(\n      {\n        repoPath: tempDir,\n        type: 'docs',\n        generateQA: false,\n        skipContentGeneration: true,\n      },\n      toolExecutionContext\n    );\n\n    const policy = await fs.readJson(path.join(tempDir, '.context', 'harness', 'policy.json'));\n    const protectedPaths = policy.rules\n      .flatMap((rule: { when?: { paths?: string[] } }) => rule.when?.paths ?? []);\n\n    expect(protectedPaths).toEqual(expect.arrayContaining(['src/**', 'package.json']));\n    expect(protectedPaths).not.toEqual(expect.arrayContaining(['src/services/mcp/**', 'src/workflow/**']));\n  });\n\n  it('returns JSON-specific guidance when filling sensors.json', async () => {\n    await initializeContextTool.execute!(\n      {\n        repoPath: tempDir,\n        type: 'docs',\n        generateQA: false,\n        skipContentGeneration: true,\n      },\n      toolExecutionContext\n    );\n\n    const sensorsPath = path.join(tempDir, '.context', 'harness', 'sensors.json');\n    const result = await fillSingleFileTool.execute!(\n      {\n        repoPath: tempDir,\n        filePath: sensorsPath,\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(result.success).toBe(true);\n    expect(result.fileType).toBe('sensor');\n    expect(result.currentCatalog.sensors.map((sensor: { id: string }) => sensor.id)).toEqual(['build', 'test', 'lint']);\n    expect(result.instructions).toContain('complete JSON sensor catalog');\n    expect(result.instructions).toContain('set source to \"manual\"');\n  });\n\n  it('skips manual sensors catalogs from listToFill', async () => {\n    await initializeContextTool.execute!(\n      {\n        repoPath: tempDir,\n        type: 'docs',\n        generateQA: false,\n        skipContentGeneration: true,\n      },\n      toolExecutionContext\n    );\n\n    const sensorsPath = path.join(tempDir, '.context', 'harness', 'sensors.json');\n    const catalog = await fs.readJson(sensorsPath);\n    catalog.source = 'manual';\n    await fs.writeJson(sensorsPath, catalog, { spaces: 2 });\n\n    const listed = await listFilesToFillTool.execute!(\n      {\n        repoPath: tempDir,\n        target: 'sensors',\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(listed.files).toEqual([]);\n  });\n\n  it('auto-builds and auto-refreshes semantic snapshot sections through getMap', async () => {\n    const legacySummaryPath = path.join(tempDir, '.context', 'docs', 'codebase-map.json');\n    await fs.ensureDir(path.dirname(legacySummaryPath));\n    await fs.writeJson(legacySummaryPath, {\n      legacy: true,\n      note: 'must be ignored when no snapshot manifest exists',\n    }, { spaces: 2 });\n\n    const missingResult = await getCodebaseMapTool.execute!(\n      {\n        repoPath: tempDir,\n        section: 'keyFiles',\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(missingResult.success).toBe(true);\n    expect(missingResult.source).toBe('snapshot');\n    expect(missingResult.fresh).toBe(true);\n    expect(missingResult.refreshed).toBe(true);\n    expect(missingResult.refreshReason).toBe('missing');\n    expect(Array.isArray(missingResult.data)).toBe(true);\n    expect(typeof missingResult.mapPath).toBe('string');\n    expect((missingResult.mapPath as string)).toMatch(\n      new RegExp(`\\\\.context${path.sep}cache${path.sep}semantic${path.sep}versions${path.sep}.+${path.sep}key-files\\\\.json$`)\n    );\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'cache', 'semantic', 'manifest.json'))).toBe(true);\n\n    const beforeGeneratedAt = missingResult.generatedAt as string | null;\n    await fs.writeFile(path.join(tempDir, 'src', 'index.ts'), 'export const ok = false;\\n', 'utf-8');\n    await new Promise((resolve) => setTimeout(resolve, 10));\n\n    const staleResult = await getCodebaseMapTool.execute!(\n      {\n        repoPath: tempDir,\n        section: 'stats',\n      },\n      toolExecutionContext\n    ) as Record<string, any>;\n\n    expect(staleResult.success).toBe(true);\n    expect(staleResult.source).toBe('snapshot');\n    expect(staleResult.fresh).toBe(true);\n    expect(staleResult.refreshed).toBe(true);\n    expect(staleResult.refreshReason).toBe('stale');\n    expect(staleResult.data.totalFiles).toBeGreaterThan(0);\n    expect(staleResult.generatedAt).not.toBe(beforeGeneratedAt);\n  });\n});\n"
  },
  {
    "path": "src/services/harness/contextTools.ts",
    "content": "import * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport * as path from 'path';\nimport { TreeSitterLayer } from '../semantic/treeSitter/treeSitterLayer';\nimport { SemanticContextBuilder } from '../semantic/contextBuilder';\nimport { DEFAULT_EXCLUDE_PATTERNS } from '../semantic/types';\nimport { FileMapper } from '../../utils/fileMapper';\nimport { needsFill } from '../../utils/frontMatter';\nimport { getScaffoldStructure, serializeStructureForAI } from '../../generators/shared/scaffoldStructures';\nimport { DocumentationGenerator } from '../../generators/documentation/documentationGenerator';\nimport { SemanticSnapshotService } from '../semantic';\nimport { AgentGenerator } from '../../generators/agents/agentGenerator';\nimport { SkillGenerator } from '../../generators/skills/skillGenerator';\nimport { PlanGenerator } from '../../generators/plans/planGenerator';\nimport {\n  StackDetector,\n  classifyProject,\n  getAgentsForProjectType,\n  getDocsForProjectType,\n  getSkillsForProjectType,\n  type ProjectType,\n  type ProjectClassification,\n} from '../stack';\nimport { QAService } from '../qa';\nimport { HarnessPolicyService } from './policyService';\nimport { HarnessSensorCatalogService } from './sensorCatalogService';\nimport { getUntrackedContextLayoutEntries } from '../shared';\nimport { createSkillRegistry } from '../../workflow/skills';\nimport { ensureGitignorePatterns } from '../../utils/gitignoreManager';\n\ntype ToolContext = unknown;\n\ninterface InternalTool<TInput, TOutput> {\n  description: string;\n  execute(input: TInput, context?: ToolContext): Promise<TOutput>;\n}\n\nfunction createInternalTool<TInput, TOutput>(\n  description: string,\n  execute: (input: TInput, context?: ToolContext) => Promise<TOutput>\n): InternalTool<TInput, TOutput> {\n  return { description, execute };\n}\n\ntype ScaffoldTarget = 'docs' | 'agents' | 'skills' | 'plans' | 'sensors' | 'all';\ntype ScaffoldFileType = 'doc' | 'agent' | 'skill' | 'plan' | 'sensor';\n\ninterface ScaffoldFileInfo {\n  path: string;\n  relativePath: string;\n  type: ScaffoldFileType;\n  documentName: string;\n}\n\ninterface RequiredAction {\n  order: number;\n  actionType: 'WRITE_FILE' | 'CALL_TOOL' | 'VERIFY';\n  filePath: string;\n  fileType: ScaffoldFileType;\n  instructions: string;\n  status: 'pending' | 'in_progress' | 'completed' | 'skipped';\n}\n\nasync function collectScaffoldFiles(\n  outputDir: string,\n  target: ScaffoldTarget = 'all'\n): Promise<ScaffoldFileInfo[]> {\n  const files: ScaffoldFileInfo[] = [];\n\n  if (target === 'all' || target === 'docs') {\n    const docsDir = path.join(outputDir, 'docs');\n    if (await fs.pathExists(docsDir)) {\n      const docFiles = await glob('**/*.md', { cwd: docsDir, nodir: true });\n      for (const file of docFiles.sort()) {\n        const filePath = path.join(docsDir, file);\n        if (!await needsFill(filePath)) continue;\n        files.push({\n          path: filePath,\n          relativePath: path.join('docs', file),\n          type: 'doc',\n          documentName: path.basename(file, '.md'),\n        });\n      }\n    }\n  }\n\n  if (target === 'all' || target === 'agents') {\n    const agentsDir = path.join(outputDir, 'agents');\n    if (await fs.pathExists(agentsDir)) {\n      const agentFiles = await glob('**/*.md', { cwd: agentsDir, nodir: true });\n      for (const file of agentFiles.sort()) {\n        const filePath = path.join(agentsDir, file);\n        if (!await needsFill(filePath)) continue;\n        files.push({\n          path: filePath,\n          relativePath: path.join('agents', file),\n          type: 'agent',\n          documentName: path.basename(file, '.md'),\n        });\n      }\n    }\n  }\n\n  if (target === 'all' || target === 'skills') {\n    const skillsDir = path.join(outputDir, 'skills');\n    if (await fs.pathExists(skillsDir)) {\n      const skillFiles = await glob('**/SKILL.md', { cwd: skillsDir, nodir: true });\n      for (const file of skillFiles.sort()) {\n        const skillPath = path.join(skillsDir, file);\n        if (!await needsFill(skillPath)) continue;\n        const segments = file.split(path.sep);\n\n        files.push({\n          path: skillPath,\n          relativePath: path.join('skills', file),\n          type: 'skill',\n          documentName: segments[0],\n        });\n      }\n    }\n  }\n\n  if (target === 'all' || target === 'plans') {\n    const plansDir = path.join(outputDir, 'plans');\n    if (await fs.pathExists(plansDir)) {\n      const planFiles = await glob('**/*.md', { cwd: plansDir, nodir: true });\n      for (const file of planFiles.sort()) {\n        const filePath = path.join(plansDir, file);\n        if (!await needsFill(filePath)) continue;\n        files.push({\n          path: filePath,\n          relativePath: path.join('plans', file),\n          type: 'plan',\n          documentName: path.basename(file, '.md'),\n        });\n      }\n    }\n  }\n\n  if (target === 'all' || target === 'sensors') {\n    const sensorsPath = path.join(outputDir, 'harness', 'sensors.json');\n    if (await fs.pathExists(sensorsPath) && await sensorCatalogNeedsFill(sensorsPath)) {\n      files.push({\n        path: sensorsPath,\n        relativePath: path.join('harness', 'sensors.json'),\n        type: 'sensor',\n        documentName: 'sensors',\n      });\n    }\n  }\n\n  return files;\n}\n\nfunction resolveScaffoldFileInfo(outputDir: string, filePath: string): ScaffoldFileInfo {\n  const resolvedOutputDir = path.resolve(outputDir);\n  const resolvedFilePath = path.resolve(filePath);\n  const relativePath = path.relative(resolvedOutputDir, resolvedFilePath);\n  const segments = relativePath.split(path.sep);\n\n  if (segments[0] === 'skills' && segments.length >= 3 && segments[2] === 'SKILL.md') {\n    return {\n      path: resolvedFilePath,\n      relativePath,\n      type: 'skill',\n      documentName: segments[1],\n    };\n  }\n\n  if (segments[0] === 'agents') {\n    return {\n      path: resolvedFilePath,\n      relativePath,\n      type: 'agent',\n      documentName: path.basename(resolvedFilePath, '.md'),\n    };\n  }\n\n  if (segments[0] === 'plans') {\n    return {\n      path: resolvedFilePath,\n      relativePath,\n      type: 'plan',\n      documentName: path.basename(resolvedFilePath, '.md'),\n    };\n  }\n\n  if (segments[0] === 'harness' && segments[1] === 'sensors.json') {\n    return {\n      path: resolvedFilePath,\n      relativePath,\n      type: 'sensor',\n      documentName: 'sensors',\n    };\n  }\n\n  return {\n    path: resolvedFilePath,\n    relativePath,\n    type: 'doc',\n    documentName: path.basename(resolvedFilePath, '.md'),\n  };\n}\n\nfunction getDocFillInstructions(fileName: string): string {\n  return `Fill ${fileName} with repository-specific documentation. Use concrete paths, conventions, and examples from this codebase instead of placeholders.`;\n}\n\nfunction getAgentFillInstructions(agentType: string): string {\n  return `Fill the ${agentType} playbook with concrete responsibilities, relevant files, workflows, and quality checks used in this repository.`;\n}\n\nfunction getSkillFillInstructions(skillSlug: string): string {\n  return `Fill the ${skillSlug} skill with project-specific guidance, examples, and references to real files and conventions from this repository.`;\n}\n\nfunction getSensorFillInstructions(): string {\n  return 'Review .context/harness/sensors.json and rewrite it as a project-specific sensor catalog. Keep the JSON schema valid, keep version at 1, set source to \"manual\", preserve stack metadata, and keep only commands that make sense for this repository.';\n}\n\nasync function sensorCatalogNeedsFill(filePath: string): Promise<boolean> {\n  try {\n    const content = await fs.readJson(filePath) as { source?: string };\n    return content.source !== 'manual';\n  } catch {\n    return false;\n  }\n}\n\nasync function hasContent(dirPath: string): Promise<boolean> {\n  try {\n    const entries = await fs.readdir(dirPath);\n    return entries.length > 0;\n  } catch {\n    return false;\n  }\n}\n\nasync function hasSkillContent(skillsDir: string): Promise<boolean> {\n  try {\n    const entries = await fs.readdir(skillsDir);\n    for (const entry of entries) {\n      const skillDir = path.join(skillsDir, entry);\n      const stat = await fs.stat(skillDir).catch(() => null);\n      if (!stat?.isDirectory()) continue;\n      if (await fs.pathExists(path.join(skillDir, 'SKILL.md'))) {\n        return true;\n      }\n    }\n    return false;\n  } catch {\n    return false;\n  }\n}\n\nasync function hasHarnessRuntimeContent(harnessDir: string): Promise<boolean> {\n  const runtimeEntries = ['sessions', 'traces', 'artifacts', 'contracts', 'workflows', 'replays', 'datasets'];\n  for (const entry of runtimeEntries) {\n    if (await hasContent(path.join(harnessDir, entry))) {\n      return true;\n    }\n  }\n  return false;\n}\n\nlet treeSitterInstance: TreeSitterLayer | null = null;\n\nfunction getTreeSitterLayer(): TreeSitterLayer {\n  if (!treeSitterInstance) {\n    treeSitterInstance = new TreeSitterLayer();\n  }\n  return treeSitterInstance;\n}\n\nlet sharedContextBuilder: SemanticContextBuilder | null = null;\nlet cachedContext: { repoPath: string; context: string } | null = null;\n\nexport async function getOrBuildContext(repoPath: string): Promise<string> {\n  if (cachedContext && cachedContext.repoPath === repoPath) {\n    return cachedContext.context;\n  }\n\n  if (sharedContextBuilder) {\n    await sharedContextBuilder.shutdown();\n    sharedContextBuilder = null;\n  }\n\n  let builderOptions: { exclude?: string[] } = {};\n  const configPath = path.join(repoPath, '.context', 'config.json');\n  if (await fs.pathExists(configPath)) {\n    try {\n      const config = await fs.readJson(configPath);\n      if (Array.isArray(config.exclude) && config.exclude.length > 0) {\n        builderOptions = {\n          exclude: [...new Set([...DEFAULT_EXCLUDE_PATTERNS, ...config.exclude])]\n        };\n      }\n    } catch {\n      // Ignore malformed config and fall back to defaults.\n    }\n  }\n\n  sharedContextBuilder = new SemanticContextBuilder(builderOptions);\n  const context = await sharedContextBuilder.buildDocumentationContext(repoPath);\n  cachedContext = { repoPath, context };\n  return context;\n}\n\nexport async function cleanupSharedContext(): Promise<void> {\n  if (sharedContextBuilder) {\n    await sharedContextBuilder.shutdown();\n    sharedContextBuilder = null;\n  }\n  cachedContext = null;\n}\n\nexport const readFileTool = createInternalTool<\n  { filePath: string; encoding?: 'utf-8' | 'ascii' | 'binary' },\n  { success: boolean; content?: string; path: string; size?: number; error?: string }\n>(\n  'Read the contents of a file from the filesystem',\n  async (input) => {\n    const { filePath, encoding = 'utf-8' } = input;\n    try {\n      const content = await fs.readFile(filePath, encoding as BufferEncoding);\n      return {\n        success: true,\n        content,\n        path: filePath,\n        size: content.length\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error),\n        path: filePath\n      };\n    }\n  }\n);\n\nexport const listFilesTool = createInternalTool<\n  { pattern: string; cwd?: string; ignore?: string[] },\n  { success: boolean; files?: string[]; count?: number; pattern: string; error?: string }\n>(\n  'List files matching a glob pattern in the repository',\n  async (input) => {\n    const { pattern, cwd, ignore } = input;\n    try {\n      const files = await glob(pattern, {\n        cwd: cwd || process.cwd(),\n        ignore: ignore || ['node_modules/**', '.git/**', 'dist/**'],\n        absolute: false\n      });\n      return {\n        success: true,\n        files,\n        count: files.length,\n        pattern\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error),\n        pattern\n      };\n    }\n  }\n);\n\nexport const analyzeSymbolsTool = createInternalTool<\n  { filePath: string; symbolTypes?: Array<'class' | 'interface' | 'function' | 'type' | 'enum'> },\n  Record<string, unknown>\n>(\n  'Analyze symbols in a source file',\n  async (input) => {\n    const { filePath, symbolTypes } = input;\n    try {\n      const treeSitter = getTreeSitterLayer();\n      const analysis = await treeSitter.analyzeFile(filePath);\n      let symbols = analysis.symbols;\n      if (symbolTypes && symbolTypes.length > 0) {\n        symbols = symbols.filter((symbol) =>\n          symbolTypes.includes(symbol.kind as 'class' | 'interface' | 'function' | 'type' | 'enum')\n        );\n      }\n\n      return {\n        success: true,\n        filePath,\n        language: analysis.language,\n        symbols: symbols.map((symbol) => ({\n          name: symbol.name,\n          kind: symbol.kind,\n          line: symbol.location.line,\n          exported: symbol.exported,\n          documentation: symbol.documentation\n        })),\n        imports: analysis.imports.map((imp) => ({\n          source: imp.source,\n          specifiers: imp.specifiers\n        })),\n        exports: analysis.exports.map((exp) => ({\n          name: exp.name,\n          isDefault: exp.isDefault\n        }))\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error),\n        filePath\n      };\n    }\n  }\n);\n\nexport const getFileStructureTool = createInternalTool<\n  { rootPath: string; maxDepth?: number; includePatterns?: string[] },\n  Record<string, unknown>\n>(\n  'Get the directory structure and file listing of a repository',\n  async (input) => {\n    const { rootPath, maxDepth = 3, includePatterns } = input;\n    try {\n      const mapper = new FileMapper([]);\n      const structure = await mapper.mapRepository(rootPath, includePatterns || undefined);\n\n      const filterByDepth = (relativePath: string): boolean => relativePath.split('/').length <= maxDepth;\n\n      return {\n        success: true,\n        rootPath: structure.rootPath,\n        totalFiles: structure.totalFiles,\n        totalSize: structure.totalSize,\n        topLevelDirs: structure.topLevelDirectoryStats.map((dir) => ({\n          name: dir.name,\n          fileCount: dir.fileCount\n        })),\n        files: structure.files\n          .filter((file) => filterByDepth(file.relativePath))\n          .slice(0, 200)\n          .map((file) => ({\n            path: file.relativePath,\n            extension: file.extension,\n            size: file.size\n          }))\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error),\n        rootPath\n      };\n    }\n  }\n);\n\nexport const searchCodeTool = createInternalTool<\n  { pattern: string; fileGlob?: string; maxResults?: number; cwd?: string },\n  Record<string, unknown>\n>(\n  'Search for code patterns across files using regex',\n  async (input) => {\n    const { pattern, fileGlob, maxResults = 50, cwd } = input;\n    try {\n      const files = await glob(fileGlob || '**/*.{ts,tsx,js,jsx,py,go,rs,java}', {\n        cwd: cwd || process.cwd(),\n        ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],\n        absolute: true\n      });\n\n      const regex = new RegExp(pattern, 'gm');\n      const matches: Array<{ file: string; line: number; match: string; context: string }> = [];\n\n      for (const file of files) {\n        if (matches.length >= maxResults) break;\n\n        try {\n          const content = await fs.readFile(file, 'utf-8');\n          const lines = content.split('\\n');\n\n          for (let index = 0; index < lines.length && matches.length < maxResults; index++) {\n            if (regex.test(lines[index])) {\n              matches.push({\n                file: path.relative(cwd || process.cwd(), file),\n                line: index + 1,\n                match: lines[index].trim().slice(0, 200),\n                context: lines.slice(Math.max(0, index - 1), index + 2).join('\\n').slice(0, 500)\n              });\n            }\n            regex.lastIndex = 0;\n          }\n        } catch {\n          // Skip unreadable files.\n        }\n      }\n\n      return {\n        success: true,\n        pattern,\n        matches,\n        totalMatches: matches.length,\n        truncated: matches.length >= maxResults\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error),\n        pattern\n      };\n    }\n  }\n);\n\nexport const checkScaffoldingTool = createInternalTool<\n  { repoPath?: string },\n  Record<string, unknown>\n>(\n  'Check if .context scaffolding exists and return granular status',\n  async (input) => {\n    if (!input.repoPath) {\n      throw new Error('repoPath is required for checkScaffolding');\n    }\n\n    const outputDir = path.resolve(input.repoPath, '.context');\n    try {\n      const sensorsPath = path.join(outputDir, 'harness', 'sensors.json');\n      const [initialized, docs, agents, skills, plans, workflow, harness, sensors] = await Promise.all([\n        fs.pathExists(outputDir),\n        fs.pathExists(path.join(outputDir, 'docs')).then((exists) => exists ? hasContent(path.join(outputDir, 'docs')) : false),\n        fs.pathExists(path.join(outputDir, 'agents')).then((exists) => exists ? hasContent(path.join(outputDir, 'agents')) : false),\n        fs.pathExists(path.join(outputDir, 'skills')).then((exists) => exists ? hasSkillContent(path.join(outputDir, 'skills')) : false),\n        fs.pathExists(path.join(outputDir, 'plans')).then((exists) => exists ? hasContent(path.join(outputDir, 'plans')) : false),\n        fs.pathExists(path.join(outputDir, 'workflow')).then((exists) => exists ? hasContent(path.join(outputDir, 'workflow')) : false),\n        fs.pathExists(path.join(outputDir, 'harness')).then((exists) => exists ? hasHarnessRuntimeContent(path.join(outputDir, 'harness')) : false),\n        fs.pathExists(sensorsPath)\n      ]);\n\n      return {\n        initialized,\n        docs,\n        agents,\n        skills,\n        plans,\n        workflow,\n        harness,\n        sensors,\n        outputDir\n      };\n    } catch (error) {\n      return {\n        initialized: false,\n        docs: false,\n        agents: false,\n        skills: false,\n        plans: false,\n        workflow: false,\n        harness: false,\n        sensors: false,\n        outputDir,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const initializeContextTool = createInternalTool<\n  {\n    repoPath: string;\n    type?: 'docs' | 'agents' | 'both';\n    outputDir?: string;\n    semantic?: boolean;\n    include?: string[];\n    exclude?: string[];\n    projectType?: ProjectType;\n    disableFiltering?: boolean;\n    autoFill?: boolean;\n    skipContentGeneration?: boolean;\n    generateQA?: boolean;\n    generateSkills?: boolean;\n  },\n  Record<string, unknown>\n>(\n  'Initialize .context scaffolding and create template files',\n  async (input) => {\n    const {\n      repoPath,\n      type = 'both',\n      outputDir: customOutputDir,\n      semantic = true,\n      include,\n      exclude = [],\n      projectType: overrideProjectType,\n      disableFiltering = false,\n      autoFill = true,\n      skipContentGeneration = true,\n      generateQA = false,\n      generateSkills = true,\n    } = input;\n\n    const resolvedRepoPath = path.resolve(repoPath);\n    const outputDir = customOutputDir\n      ? path.resolve(customOutputDir)\n      : path.resolve(resolvedRepoPath, '.context');\n    const scaffoldDocs = type === 'docs' || type === 'both';\n    const scaffoldAgents = type === 'agents' || type === 'both';\n\n    try {\n      if (!await fs.pathExists(resolvedRepoPath)) {\n        return {\n          success: false,\n          status: 'error',\n          outputDir,\n          error: `Repository path does not exist: ${resolvedRepoPath}`\n        };\n      }\n\n      await fs.ensureDir(outputDir);\n\n      const runtimeGitignorePatterns = getUntrackedContextLayoutEntries().map((entry) => entry.path);\n      await ensureGitignorePatterns(resolvedRepoPath, runtimeGitignorePatterns, {\n        header: '# dotcontext runtime state'\n      });\n\n      if (exclude.length > 0) {\n        const configPath = path.join(outputDir, 'config.json');\n        let existingConfig: Record<string, unknown> = {};\n        if (await fs.pathExists(configPath)) {\n          try {\n            existingConfig = await fs.readJson(configPath);\n          } catch {\n            existingConfig = {};\n          }\n        }\n        existingConfig.exclude = exclude;\n        await fs.writeJson(configPath, existingConfig, { spaces: 2 });\n      }\n\n      const fileMapper = new FileMapper(exclude);\n      const repoStructure = await fileMapper.mapRepository(resolvedRepoPath, include);\n\n      let classification: ProjectClassification | undefined;\n      let projectType: ProjectType = 'unknown';\n\n      if (!disableFiltering) {\n        const stackDetector = new StackDetector();\n        const stackInfo = await stackDetector.detect(resolvedRepoPath);\n        if (overrideProjectType) {\n          projectType = overrideProjectType;\n          classification = {\n            primaryType: overrideProjectType,\n            secondaryTypes: [],\n            confidence: 'high',\n            reasoning: ['Project type manually specified'],\n          };\n        } else {\n          classification = classifyProject(stackInfo);\n          projectType = classification.primaryType;\n        }\n      }\n\n      const filteredAgents = disableFiltering ? undefined : getAgentsForProjectType(projectType);\n      const filteredDocs = disableFiltering ? undefined : getDocsForProjectType(projectType);\n      const filteredSkills = disableFiltering ? undefined : getSkillsForProjectType(projectType);\n\n      let docsGenerated = 0;\n      if (scaffoldDocs) {\n        const docGenerator = new DocumentationGenerator();\n        docsGenerated = await docGenerator.generateDocumentation(\n          repoStructure,\n          outputDir,\n          { semantic, filteredDocs },\n          false\n        );\n      }\n\n      let skillsGenerated = 0;\n      if (generateSkills) {\n        try {\n          const skillGenerator = new SkillGenerator({\n            repoPath: resolvedRepoPath,\n            outputDir: path.relative(resolvedRepoPath, outputDir),\n          });\n          const skillResult = await skillGenerator.generate({\n            skills: filteredSkills,\n            force: false,\n          });\n          skillsGenerated = skillResult.generatedSkills.length;\n        } catch {\n          skillsGenerated = 0;\n        }\n      }\n\n      let availableSkills: Array<{ slug: string; name: string; description: string; phases: string[] }> = [];\n      if (generateSkills && skillsGenerated > 0) {\n        try {\n          const registry = createSkillRegistry(resolvedRepoPath);\n          const discovered = await registry.discoverAll();\n          availableSkills = discovered.all.map((skill) => ({\n            slug: skill.slug,\n            name: skill.metadata.name,\n            description: skill.metadata.description,\n            phases: skill.metadata.phases || [],\n          }));\n        } catch {\n          availableSkills = [];\n        }\n      }\n\n      let agentsGenerated = 0;\n      if (scaffoldAgents) {\n        const agentGenerator = new AgentGenerator();\n        agentsGenerated = await agentGenerator.generateAgentPrompts(\n          repoStructure,\n          outputDir,\n          { semantic, filteredAgents, availableSkills },\n          false\n        );\n      }\n\n      let qaGenerated = 0;\n      if (generateQA) {\n        try {\n          const qaService = new QAService();\n          const qaResult = await qaService.generateFromCodebase(resolvedRepoPath);\n          qaGenerated = qaResult.generated.length;\n          await qaService.shutdown();\n        } catch {\n          qaGenerated = 0;\n        }\n      }\n\n      const sensorCatalogService = new HarnessSensorCatalogService({\n        repoPath: resolvedRepoPath,\n        contextPath: outputDir,\n      });\n      const sensorCatalog = await sensorCatalogService.bootstrap();\n      const sensorsGenerated = sensorCatalog.sensors.length;\n      const sensorsPath = sensorCatalogService.configPath;\n\n      const policyService = new HarnessPolicyService({ repoPath: resolvedRepoPath });\n      const policyPath = path.join(outputDir, 'harness', 'policy.json');\n      let policyGenerated = false;\n      if (!await fs.pathExists(policyPath)) {\n        await policyService.savePolicy(await policyService.createBootstrapPolicy());\n        policyGenerated = true;\n      }\n\n      const generatedFiles: Array<{ path: string; relativePath: string; type: ScaffoldFileType; instructions: string }> = [];\n\n      if (scaffoldDocs) {\n        const docFiles = await collectScaffoldFiles(outputDir, 'docs');\n        for (const file of docFiles.filter((entry) => path.basename(entry.path).toLowerCase() !== 'readme.md')) {\n          generatedFiles.push({\n            path: file.path,\n            relativePath: file.relativePath,\n            type: 'doc',\n            instructions: getDocFillInstructions(path.basename(file.path))\n          });\n        }\n      }\n\n      if (scaffoldAgents) {\n        const agentFiles = await collectScaffoldFiles(outputDir, 'agents');\n        for (const file of agentFiles) {\n          generatedFiles.push({\n            path: file.path,\n            relativePath: file.relativePath,\n            type: 'agent',\n            instructions: getAgentFillInstructions(file.documentName)\n          });\n        }\n      }\n\n      if (generateSkills) {\n        const skillFiles = await collectScaffoldFiles(outputDir, 'skills');\n        for (const file of skillFiles) {\n          generatedFiles.push({\n            path: file.path,\n            relativePath: file.relativePath,\n            type: 'skill',\n            instructions: getSkillFillInstructions(file.documentName)\n          });\n        }\n      }\n\n      if (autoFill && await sensorCatalogNeedsFill(sensorsPath)) {\n        generatedFiles.push({\n          path: sensorsPath,\n          relativePath: path.relative(outputDir, sensorsPath),\n          type: 'sensor',\n          instructions: getSensorFillInstructions()\n        });\n      }\n\n      const pendingWrites: RequiredAction[] = autoFill\n        ? generatedFiles.map((file, index) => ({\n            order: index + 1,\n            actionType: 'WRITE_FILE',\n            filePath: file.path,\n            fileType: file.type,\n            instructions: file.instructions,\n            status: 'pending'\n          }))\n        : [];\n\n      const codebaseContext = autoFill && !skipContentGeneration\n        ? await getOrBuildContext(resolvedRepoPath).catch(() => undefined)\n        : undefined;\n\n      const qaOutputDir = path.join(outputDir, 'docs', 'qa');\n      const checklist = [\n        ...pendingWrites.map((item) => `[ ] Fill ${path.relative(resolvedRepoPath, item.filePath)}`),\n        sensorsGenerated > 0\n          ? (await sensorCatalogNeedsFill(sensorsPath)\n              ? `[ ] Customize harness sensors in ${path.relative(resolvedRepoPath, sensorsPath)}`\n              : `[x] Loaded ${sensorsGenerated} project-specific harness sensors`)\n          : '[ ] Generate harness sensors',\n        policyGenerated ? '[x] Created harness bootstrap policy' : '[x] Reused existing harness bootstrap policy',\n        qaGenerated > 0\n          ? `[x] Generated ${qaGenerated} optional Q&A helper docs in ${qaOutputDir}`\n          : '[ ] Generate optional Q&A helper docs (opt-in)',\n      ];\n\n      return {\n        success: true,\n        status: pendingWrites.length > 0 ? 'incomplete' : 'success',\n        complete: pendingWrites.length === 0,\n        instruction: pendingWrites.length > 0\n          ? 'Scaffolding created. Fill each pending file with context-aware content before reporting completion.'\n          : 'Scaffolding created successfully.',\n        outputDir,\n        docsGenerated,\n        agentsGenerated,\n        skillsGenerated,\n        qaGenerated,\n        pendingWrites,\n        requiredActions: pendingWrites,\n        checklist,\n        codebaseContext,\n        classification: classification\n          ? {\n              projectType: classification.primaryType,\n              confidence: classification.confidence,\n              reasoning: classification.reasoning,\n            }\n          : undefined,\n        _metadata: {\n          docsGenerated,\n          agentsGenerated,\n          skillsGenerated,\n          qaGenerated,\n          outputDir,\n          classification: classification\n            ? {\n                projectType: classification.primaryType,\n                confidence: classification.confidence,\n                reasoning: classification.reasoning,\n              }\n            : undefined,\n        },\n        nextStep: pendingWrites.length > 0\n          ? {\n              action: 'Call fillSingle for each pending file and then write the generated content.',\n              example: pendingWrites[0]\n                ? `context({ action: \"fillSingle\", filePath: \"${pendingWrites[0].filePath}\" })`\n                : undefined\n            }\n          : undefined\n      };\n    } catch (error) {\n      return {\n        success: false,\n        status: 'error',\n        complete: false,\n        outputDir,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const scaffoldPlanTool = createInternalTool<\n  {\n    planName: string;\n    repoPath?: string;\n    outputDir?: string;\n    title?: string;\n    summary?: string;\n    semantic?: boolean;\n    autoFill?: boolean;\n  },\n  Record<string, unknown>\n>(\n  'Create a plan template in .context/plans/',\n  async (input) => {\n    const {\n      planName,\n      repoPath,\n      outputDir: customOutputDir,\n      title,\n      summary,\n      semantic = true,\n      autoFill = true,\n    } = input;\n\n    if (!repoPath) {\n      throw new Error('repoPath is required for scaffoldPlan');\n    }\n\n    const resolvedRepoPath = path.resolve(repoPath);\n    const outputDir = customOutputDir\n      ? path.resolve(customOutputDir)\n      : path.resolve(resolvedRepoPath, '.context');\n\n    try {\n      const stackDetector = new StackDetector();\n      const stackInfo = await stackDetector.detect(resolvedRepoPath);\n      const classification = classifyProject(stackInfo);\n\n      const filteredAgents = getAgentsForProjectType(classification.primaryType);\n      const filteredDocs = getDocsForProjectType(classification.primaryType);\n\n      const planGenerator = new PlanGenerator();\n      const enableSemantic = autoFill && semantic;\n      const result = await planGenerator.generatePlan({\n        planName,\n        outputDir,\n        title,\n        summary,\n        force: true,\n        verbose: false,\n        semantic: enableSemantic,\n        projectPath: enableSemantic ? resolvedRepoPath : undefined,\n        selectedAgentTypes: filteredAgents,\n        selectedDocKeys: filteredDocs,\n      });\n\n      const planContent = await fs.readFile(result.planPath, 'utf-8');\n      return {\n        success: true,\n        status: autoFill ? 'incomplete' : 'success',\n        complete: !autoFill,\n        instruction: autoFill\n          ? 'Plan scaffold created. Fill it with concrete phases, responsibilities, and acceptance criteria.'\n          : 'Plan scaffold created successfully.',\n        planPath: result.planPath,\n        planContent,\n        classification: {\n          projectType: classification.primaryType,\n          confidence: classification.confidence,\n          reasoning: classification.reasoning,\n        },\n        nextStep: autoFill\n          ? {\n              action: 'Call fillSingle with the generated plan path.',\n              example: `context({ action: \"fillSingle\", filePath: \"${result.planPath}\" })`,\n            }\n          : undefined\n      };\n    } catch (error) {\n      return {\n        success: false,\n        status: 'error',\n        complete: false,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const listFilesToFillTool = createInternalTool<\n  { repoPath: string; outputDir?: string; target?: ScaffoldTarget },\n  Record<string, unknown>\n>(\n  'List scaffold files that still need to be filled',\n  async (input) => {\n    const { repoPath, outputDir: customOutputDir, target = 'all' } = input;\n    const resolvedRepoPath = path.resolve(repoPath);\n    const outputDir = customOutputDir\n      ? path.resolve(customOutputDir)\n      : path.resolve(resolvedRepoPath, '.context');\n\n    try {\n      if (!await fs.pathExists(outputDir)) {\n        return {\n          success: false,\n          error: `Scaffold directory does not exist: ${outputDir}. Run context init first.`\n        };\n      }\n\n      const files = await collectScaffoldFiles(outputDir, target);\n      return {\n        success: true,\n        files,\n        totalCount: files.length,\n        instructions: `Found ${files.length} files to fill. Call fillSingle for each file path.`\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const fillSingleFileTool = createInternalTool<\n  { repoPath: string; filePath: string },\n  Record<string, unknown>\n>(\n  'Build semantic context for filling a single scaffold file',\n  async (input) => {\n    const resolvedRepoPath = path.resolve(input.repoPath);\n    const resolvedFilePath = path.resolve(input.filePath);\n\n    try {\n      if (!await fs.pathExists(resolvedFilePath)) {\n        return {\n          success: false,\n          error: `File does not exist: ${resolvedFilePath}`\n        };\n      }\n\n      const semanticContext = await getOrBuildContext(resolvedRepoPath);\n      const currentContent = await fs.readFile(resolvedFilePath, 'utf-8');\n      const fileInfo = resolveScaffoldFileInfo(path.join(resolvedRepoPath, '.context'), resolvedFilePath);\n      const structure = fileInfo.type === 'sensor'\n        ? undefined\n        : getScaffoldStructure(fileInfo.documentName);\n\n      if (fileInfo.type === 'sensor') {\n        const sensorCatalogService = new HarnessSensorCatalogService({\n          repoPath: resolvedRepoPath,\n          contextPath: path.join(resolvedRepoPath, '.context'),\n        });\n        const currentCatalog = await sensorCatalogService.load();\n\n        return {\n          success: true,\n          filePath: resolvedFilePath,\n          fileType: fileInfo.type,\n          documentName: fileInfo.documentName,\n          semanticContext,\n          currentContent,\n          currentCatalog,\n          instructions: `Use semanticContext and currentContent to rewrite ${resolvedFilePath} as a complete JSON sensor catalog tailored to this repository. Keep version at 1, set source to \"manual\", preserve stack metadata, and keep only relevant sensors with real commands for this project.`\n        };\n      }\n\n      return {\n        success: true,\n        filePath: resolvedFilePath,\n        fileType: fileInfo.type,\n        documentName: fileInfo.documentName,\n        semanticContext,\n        scaffoldStructure: structure ? serializeStructureForAI(structure) : undefined,\n        currentContent,\n        instructions: `Use semanticContext and scaffoldStructure to generate complete markdown content for ${resolvedFilePath}. Write repository-specific content without placeholders.`\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const fillScaffoldingTool = createInternalTool<\n  { repoPath: string; outputDir?: string; target?: ScaffoldTarget; offset?: number; limit?: number },\n  Record<string, unknown>\n>(\n  'Build semantic context for filling scaffold files',\n  async (input) => {\n    const {\n      repoPath,\n      outputDir: customOutputDir,\n      target = 'all',\n      offset = 0,\n      limit = 3,\n    } = input;\n\n    const resolvedRepoPath = path.resolve(repoPath);\n    const outputDir = customOutputDir\n      ? path.resolve(customOutputDir)\n      : path.resolve(resolvedRepoPath, '.context');\n\n    try {\n      if (!await fs.pathExists(resolvedRepoPath)) {\n        return {\n          success: false,\n          error: `Repository path does not exist: ${resolvedRepoPath}`\n        };\n      }\n\n      if (!await fs.pathExists(outputDir)) {\n        return {\n          success: false,\n          error: `Scaffold directory does not exist: ${outputDir}. Run context init first.`\n        };\n      }\n\n      const semanticContext = await getOrBuildContext(resolvedRepoPath);\n      const allFiles = await collectScaffoldFiles(outputDir, target);\n      const effectiveLimit = limit === 0 ? allFiles.length : limit;\n      const paginatedFiles = allFiles.slice(offset, offset + effectiveLimit);\n\n      const files = await Promise.all(\n        paginatedFiles.map(async (fileInfo) => {\n          const currentContent = await fs.readFile(fileInfo.path, 'utf-8');\n          const structure = fileInfo.type === 'sensor'\n            ? undefined\n            : getScaffoldStructure(fileInfo.documentName);\n          return {\n            path: fileInfo.path,\n            relativePath: fileInfo.relativePath,\n            type: fileInfo.type,\n            documentName: fileInfo.documentName,\n            fillInstructions: fileInfo.type === 'sensor' ? getSensorFillInstructions() : undefined,\n            scaffoldStructure: structure ? serializeStructureForAI(structure) : undefined,\n            currentContent\n          };\n        })\n      );\n\n      return {\n        success: true,\n        semanticContext,\n        files,\n        totalCount: allFiles.length,\n        offset,\n        limit: effectiveLimit,\n        hasMore: offset + effectiveLimit < allFiles.length,\n        instructions: 'Generate content for each file using the shared semanticContext and each file-specific scaffoldStructure, then write the result back to the file path.'\n      };\n    } catch (error) {\n      return {\n        success: false,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const getCodebaseMapTool = createInternalTool<\n  { repoPath?: string; section?: string },\n  Record<string, unknown>\n>(\n  'Get persisted semantic snapshot data and auto-refresh it on read',\n  async (input) => {\n    if (!input.repoPath) {\n      throw new Error('repoPath is required for getCodebaseMap');\n    }\n\n    const section = input.section || 'all';\n    try {\n      const snapshotService = new SemanticSnapshotService();\n      const result = await snapshotService.ensureFreshSection(input.repoPath, section as any);\n      return {\n        success: true,\n        section,\n        data: result.data,\n        mapPath: result.path,\n        source: result.source,\n        fresh: result.fresh,\n        refreshed: result.refreshed,\n        refreshReason: result.refreshReason,\n        generatedAt: result.manifest?.generatedAt ?? null,\n        schemaVersion: result.manifest?.schemaVersion ?? null,\n      };\n    } catch (error) {\n      return {\n        success: false,\n        section,\n        error: error instanceof Error ? error.message : String(error)\n      };\n    }\n  }\n);\n\nexport const TOOL_NAMES = [\n  'readFile',\n  'listFiles',\n  'analyzeSymbols',\n  'getFileStructure',\n  'searchCode',\n  'checkScaffolding',\n  'initializeContext',\n  'scaffoldPlan',\n  'fillScaffolding',\n  'listFilesToFill',\n  'fillSingleFile',\n  'getCodebaseMap'\n] as const;\n\nexport type ToolName = (typeof TOOL_NAMES)[number];\n"
  },
  {
    "path": "src/services/harness/datasetService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessExecutionService } from './executionService';\nimport { HarnessDatasetService } from './datasetService';\n\ndescribe('HarnessDatasetService', () => {\n  let tempDir: string;\n  let execution: HarnessExecutionService;\n  let service: HarnessDatasetService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-dataset-'));\n    execution = new HarnessExecutionService({ repoPath: tempDir });\n    service = new HarnessDatasetService({ repoPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('builds failure datasets and clusters repeated signatures', async () => {\n    const first = await execution.createSession({ name: 'first-failure' });\n    const second = await execution.createSession({ name: 'second-failure' });\n\n    await execution.runSensor({\n      id: 'lint',\n      name: 'Lint',\n      severity: 'critical',\n      blocking: true,\n      execute: async () => ({\n        status: 'failed',\n        summary: 'Lint failed',\n        evidence: ['lint output'],\n      }),\n    }, { sessionId: first.id });\n\n    await execution.runSensor({\n      id: 'lint',\n      name: 'Lint',\n      severity: 'critical',\n      blocking: true,\n      execute: async () => ({\n        status: 'failed',\n        summary: 'Lint failed',\n        evidence: ['lint output'],\n      }),\n    }, { sessionId: second.id });\n\n    const dataset = await service.buildFailureDataset({ includeSuccessfulSessions: true });\n    const datasets = await service.listDatasets();\n\n    expect(dataset.sessionCount).toBe(2);\n    expect(dataset.replayCount).toBe(2);\n    expect(dataset.failureCount).toBe(2);\n    expect(dataset.clusterCount).toBe(1);\n    expect(dataset.failures).toHaveLength(2);\n    expect(dataset.clusters[0]?.count).toBe(2);\n    expect(datasets).toHaveLength(1);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'harness', 'replays'))).toBe(false);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'harness', 'datasets', `${dataset.id}.json`))).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/services/harness/datasetService.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\n\nimport { HarnessRuntimeStateService, type HarnessRuntimeStatePort } from './runtimeStateService';\nimport { HarnessReplayService, type HarnessReplayRecord } from './replayService';\nimport { HarnessSensorsService } from './sensorsService';\nimport { HarnessTaskContractsService } from './taskContractsService';\n\nexport type HarnessFailureKind = 'sensor' | 'task' | 'session' | 'trace';\n\nexport interface HarnessFailureRecord {\n  id: string;\n  kind: HarnessFailureKind;\n  sessionId: string;\n  replayId: string;\n  signature: string;\n  message: string;\n  severity: 'warning' | 'critical';\n  createdAt: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessFailureCluster {\n  signature: string;\n  count: number;\n  sessionIds: string[];\n  exampleMessages: string[];\n  firstSeenAt: string;\n  lastSeenAt: string;\n}\n\nexport interface HarnessFailureDataset {\n  id: string;\n  createdAt: string;\n  repoPath: string;\n  sessionCount: number;\n  replayCount: number;\n  failureCount: number;\n  clusterCount: number;\n  failures: HarnessFailureRecord[];\n  clusters: HarnessFailureCluster[];\n}\n\nexport interface HarnessDatasetServiceOptions {\n  repoPath: string;\n  dependencies?: Partial<HarnessDatasetDependencies>;\n}\n\nexport interface BuildHarnessDatasetOptions {\n  sessionIds?: string[];\n  includeSuccessfulSessions?: boolean;\n}\n\nexport interface HarnessDatasetDependencies {\n  stateService: HarnessRuntimeStatePort;\n  replayService: Pick<HarnessReplayService, 'buildReplay'>;\n  taskContractsService: Pick<HarnessTaskContractsService, 'evaluateTaskCompletion'>;\n}\n\nfunction nowIso(): string {\n  return new Date().toISOString();\n}\n\nfunction normalizeSignature(value: string): string {\n  return value\n    .toLowerCase()\n    .replace(/\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/g, ':uuid')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\n\nfunction buildSensorFailures(replay: HarnessReplayRecord): HarnessFailureRecord[] {\n  return replay.sensorRuns\n    .filter(run => run.status === 'failed' || run.status === 'blocked')\n    .map(run => ({\n      id: randomUUID(),\n      kind: 'sensor' as const,\n      sessionId: replay.sessionId,\n      replayId: replay.id,\n      signature: normalizeSignature(`sensor:${run.sensorId}:${run.summary}`),\n      message: run.summary,\n      severity: run.blocking ? 'critical' : 'warning',\n      createdAt: run.createdAt,\n      metadata: {\n        sensorId: run.sensorId,\n        status: run.status,\n        blocking: run.blocking,\n      },\n    }));\n}\n\nasync function buildTaskFailures(\n  replay: HarnessReplayRecord,\n  taskContractsService: Pick<HarnessTaskContractsService, 'evaluateTaskCompletion'>\n): Promise<HarnessFailureRecord[]> {\n  const taskFailures: HarnessFailureRecord[] = [];\n\n  for (const task of replay.tasks.filter(item => item.sessionId === replay.sessionId)) {\n    const result = await taskContractsService.evaluateTaskCompletion(task.id, replay.sessionId);\n    if (result.canComplete) {\n      continue;\n    }\n\n    taskFailures.push({\n      id: randomUUID(),\n      kind: 'task',\n      sessionId: replay.sessionId,\n      replayId: replay.id,\n      signature: normalizeSignature(`task:${result.missingSensors.join(',')}:${result.missingArtifacts.join(',')}`),\n      message: result.blockingFindings.join('; ') || `Task contract blocked: ${task.title}`,\n      severity: 'critical',\n      createdAt: task.updatedAt,\n      metadata: {\n        taskId: task.id,\n        missingSensors: result.missingSensors,\n        missingArtifacts: result.missingArtifacts,\n        blockingFindings: result.blockingFindings,\n      },\n    });\n  }\n\n  return taskFailures;\n}\n\nfunction buildSessionFailure(replay: HarnessReplayRecord): HarnessFailureRecord[] {\n  if (replay.session.status !== 'failed') {\n    return [];\n  }\n\n  const lastErrorTrace = [...replay.traces].reverse().find(trace => trace.level === 'error' || trace.event.includes('failed'));\n  const message = lastErrorTrace?.message || `Session failed: ${replay.session.name}`;\n\n  return [{\n    id: randomUUID(),\n    kind: 'session',\n    sessionId: replay.sessionId,\n    replayId: replay.id,\n    signature: normalizeSignature(`session:${message}`),\n    message,\n    severity: 'critical',\n    createdAt: replay.session.failedAt || replay.session.updatedAt,\n    metadata: {\n      status: replay.session.status,\n      lastErrorTrace: lastErrorTrace?.event ?? null,\n    },\n  }];\n}\n\nfunction buildTraceFailures(replay: HarnessReplayRecord): HarnessFailureRecord[] {\n  return replay.traces\n    .filter(trace => trace.event !== 'sensor.run')\n    .filter(trace => trace.level === 'error' || /failed|blocked/i.test(trace.event) || /failed|blocked/i.test(trace.message))\n    .map(trace => ({\n      id: randomUUID(),\n      kind: 'trace' as const,\n      sessionId: replay.sessionId,\n      replayId: replay.id,\n      signature: normalizeSignature(`trace:${trace.event}:${trace.message}`),\n      message: trace.message,\n      severity: 'warning' as const,\n      createdAt: trace.createdAt,\n      metadata: {\n        event: trace.event,\n        level: trace.level,\n      },\n    }));\n}\n\nfunction clusterFailures(failures: HarnessFailureRecord[]): HarnessFailureCluster[] {\n  const clusterMap = new Map<string, HarnessFailureCluster>();\n\n  for (const failure of failures) {\n    const existing = clusterMap.get(failure.signature);\n    if (!existing) {\n      clusterMap.set(failure.signature, {\n        signature: failure.signature,\n        count: 1,\n        sessionIds: [failure.sessionId],\n        exampleMessages: [failure.message],\n        firstSeenAt: failure.createdAt,\n        lastSeenAt: failure.createdAt,\n      });\n      continue;\n    }\n\n    existing.count += 1;\n    if (!existing.sessionIds.includes(failure.sessionId)) {\n      existing.sessionIds.push(failure.sessionId);\n    }\n    if (existing.exampleMessages.length < 3) {\n      existing.exampleMessages.push(failure.message);\n    }\n    if (failure.createdAt < existing.firstSeenAt) {\n      existing.firstSeenAt = failure.createdAt;\n    }\n    if (failure.createdAt > existing.lastSeenAt) {\n      existing.lastSeenAt = failure.createdAt;\n    }\n  }\n\n  return [...clusterMap.values()].sort((left, right) => right.count - left.count || left.signature.localeCompare(right.signature));\n}\n\nexport class HarnessDatasetService {\n  private readonly stateService: HarnessDatasetDependencies['stateService'];\n  private readonly replayService: HarnessDatasetDependencies['replayService'];\n  private readonly taskContractsService: HarnessDatasetDependencies['taskContractsService'];\n\n  constructor(private readonly options: HarnessDatasetServiceOptions) {\n    const stateService = options.dependencies?.stateService\n      ?? new HarnessRuntimeStateService({ repoPath: options.repoPath });\n    const replayService = options.dependencies?.replayService\n      ?? new HarnessReplayService({\n        repoPath: options.repoPath,\n        dependencies: {\n          stateService,\n          sensorsService: new HarnessSensorsService({ stateService }),\n          contractsService: new HarnessTaskContractsService({\n            repoPath: options.repoPath,\n            stateService,\n          }),\n        },\n      });\n    const taskContractsService = options.dependencies?.taskContractsService\n      ?? new HarnessTaskContractsService({\n        repoPath: options.repoPath,\n        stateService,\n      });\n\n    this.stateService = stateService;\n    this.replayService = replayService;\n    this.taskContractsService = taskContractsService;\n  }\n\n  private get repoPath(): string {\n    return path.resolve(this.options.repoPath);\n  }\n\n  private get datasetsPath(): string {\n    return path.join(this.repoPath, '.context', 'harness', 'datasets');\n  }\n\n  private datasetFile(datasetId: string): string {\n    return path.join(this.datasetsPath, `${datasetId}.json`);\n  }\n\n  private async ensureLayout(): Promise<void> {\n    await fs.ensureDir(this.datasetsPath);\n  }\n\n  async buildFailureDataset(options: BuildHarnessDatasetOptions = {}): Promise<HarnessFailureDataset> {\n    const sessions = options.sessionIds?.length\n      ? await Promise.all(options.sessionIds.map(sessionId => this.stateService.getSession(sessionId)))\n      : await this.stateService.listSessions();\n\n    const selectedSessions = options.includeSuccessfulSessions\n      ? sessions\n      : sessions.filter(session => session.status !== 'completed');\n\n    const replays = await Promise.all(selectedSessions.map(session => this.replayService.buildReplay(session.id, { includePayloads: false })));\n    const sensorFailures = replays.flatMap(replay => buildSensorFailures(replay));\n    const taskFailures: HarnessFailureRecord[] = [];\n    for (const replay of replays) {\n      taskFailures.push(...await buildTaskFailures(replay, this.taskContractsService));\n    }\n    const sessionFailures = replays.flatMap(replay => buildSessionFailure(replay));\n    const traceFailures = replays.flatMap(replay => buildTraceFailures(replay));\n\n    const failures = [...sensorFailures, ...taskFailures, ...sessionFailures, ...traceFailures];\n    const clusters = clusterFailures(failures);\n    const dataset: HarnessFailureDataset = {\n      id: randomUUID(),\n      createdAt: nowIso(),\n      repoPath: this.repoPath,\n      sessionCount: selectedSessions.length,\n      replayCount: replays.length,\n      failureCount: failures.length,\n      clusterCount: clusters.length,\n      failures,\n      clusters,\n    };\n\n    await this.ensureLayout();\n    await fs.writeJson(this.datasetFile(dataset.id), dataset, { spaces: 2 });\n    return dataset;\n  }\n\n  async listDatasets(): Promise<HarnessFailureDataset[]> {\n    await this.ensureLayout();\n    const entries = await fs.readdir(this.datasetsPath);\n    const datasets = await Promise.all(\n      entries\n        .filter(entry => entry.endsWith('.json'))\n        .map(async entry => fs.readJson(path.join(this.datasetsPath, entry)) as Promise<HarnessFailureDataset>)\n    );\n\n    return datasets.sort((left, right) => right.createdAt.localeCompare(left.createdAt));\n  }\n\n  async getDataset(datasetId: string): Promise<HarnessFailureDataset> {\n    const filePath = this.datasetFile(datasetId);\n    if (!(await fs.pathExists(filePath))) {\n      throw new Error(`Dataset not found: ${datasetId}`);\n    }\n\n    return fs.readJson(filePath) as Promise<HarnessFailureDataset>;\n  }\n\n  async getFailureClusters(datasetId: string): Promise<HarnessFailureCluster[]> {\n    const dataset = await this.getDataset(datasetId);\n    return dataset.clusters;\n  }\n}\n"
  },
  {
    "path": "src/services/harness/executionService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessExecutionService } from './executionService';\n\ndescribe('HarnessExecutionService', () => {\n  let tempDir: string;\n  let service: HarnessExecutionService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-execution-'));\n    service = new HarnessExecutionService({ repoPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('enforces policy before running a sensor', async () => {\n    const session = await service.createSession({ name: 'policy-sensor' });\n    await fs.ensureDir(path.join(tempDir, '.context', 'harness'));\n    await fs.writeJson(\n      path.join(tempDir, '.context', 'harness', 'policy.json'),\n      {\n        version: 1,\n        defaultEffect: 'allow',\n        rules: [\n          {\n            id: 'deny-run-sensor',\n            effect: 'deny',\n            when: {\n              tools: ['harness'],\n              actions: ['runSensor'],\n            },\n            reason: 'sensor execution denied',\n          },\n        ],\n      },\n      { spaces: 2 }\n    );\n\n    await expect(service.runSensor({\n      id: 'lint',\n      name: 'Lint',\n      severity: 'critical',\n      blocking: true,\n      execute: async () => ({\n        status: 'passed',\n        summary: 'ok',\n      }),\n    }, {\n      sessionId: session.id,\n    })).rejects.toThrow('Policy blocked harness.runSensor');\n  });\n});\n"
  },
  {
    "path": "src/services/harness/executionService.ts",
    "content": "import {\n  HarnessRuntimeStateService,\n  type AddArtifactInput,\n  type AppendTraceInput,\n  type CheckpointInput,\n  type CreateSessionInput,\n  type HarnessSessionRecord,\n} from './runtimeStateService';\nimport {\n  HarnessSensorsService,\n  type HarnessBackpressurePolicy,\n  type HarnessBackpressureResult,\n  type HarnessSensorDefinition,\n  type HarnessSensorExecutionInput,\n  type HarnessSensorRun,\n} from './sensorsService';\nimport {\n  HarnessTaskContractsService,\n  type HarnessTaskCompletionResult,\n  type HarnessHandoffContract,\n} from './taskContractsService';\nimport {\n  HarnessPolicyService,\n  type HarnessPolicyDocument,\n  type HarnessPolicyEvaluationInput,\n  type HarnessPolicyDecision,\n  type HarnessPolicyDefaultEffect,\n  type CreateHarnessPolicyRuleInput,\n  type HarnessPolicyRule,\n  type HarnessPolicyTarget,\n  type HarnessPolicyEffect,\n} from './policyService';\nimport { HarnessReplayService, type HarnessReplayRecord } from './replayService';\nimport { HarnessDatasetService, type HarnessFailureDataset, type HarnessFailureCluster } from './datasetService';\n\nexport interface HarnessExecutionServiceOptions {\n  repoPath: string;\n}\n\nexport interface HarnessSessionQualitySnapshot {\n  session: HarnessSessionRecord;\n  sensorRuns: HarnessSensorRun[];\n  backpressure: HarnessBackpressureResult;\n  taskEvaluation: HarnessTaskCompletionResult | null;\n}\n\nexport class HarnessExecutionService {\n  readonly state: HarnessRuntimeStateService;\n  readonly sensors: HarnessSensorsService;\n  readonly contracts: HarnessTaskContractsService;\n  readonly policy: HarnessPolicyService;\n  readonly replay: HarnessReplayService;\n  readonly datasets: HarnessDatasetService;\n\n  constructor(private readonly options: HarnessExecutionServiceOptions) {\n    this.state = new HarnessRuntimeStateService({ repoPath: options.repoPath });\n    this.sensors = new HarnessSensorsService({ stateService: this.state });\n    this.contracts = new HarnessTaskContractsService({\n      repoPath: options.repoPath,\n      stateService: this.state,\n    });\n    this.policy = new HarnessPolicyService({ repoPath: options.repoPath });\n    this.replay = new HarnessReplayService({\n      repoPath: options.repoPath,\n      dependencies: {\n        stateService: this.state,\n        sensorsService: this.sensors,\n        contractsService: this.contracts,\n      },\n    });\n    this.datasets = new HarnessDatasetService({\n      repoPath: options.repoPath,\n      dependencies: {\n        stateService: this.state,\n        replayService: this.replay,\n        taskContractsService: this.contracts,\n      },\n    });\n  }\n\n  createSession(input: CreateSessionInput) {\n    return this.state.createSession(input);\n  }\n\n  listSessions() {\n    return this.state.listSessions();\n  }\n\n  getSession(sessionId: string) {\n    return this.state.getSession(sessionId);\n  }\n\n  appendTrace(sessionId: string, input: AppendTraceInput) {\n    return this.state.appendTrace(sessionId, input);\n  }\n\n  listTraces(sessionId: string) {\n    return this.state.listTraces(sessionId);\n  }\n\n  async addArtifact(sessionId: string, input: AddArtifactInput) {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'addArtifact',\n      paths: input.path ? [input.path] : undefined,\n      risk: 'medium',\n      metadata: input.metadata,\n    });\n    return this.state.addArtifact(sessionId, input);\n  }\n\n  listArtifacts(sessionId: string) {\n    return this.state.listArtifacts(sessionId);\n  }\n\n  async checkpointSession(sessionId: string, input: CheckpointInput = {}) {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'checkpoint',\n      risk: input.pause ? 'high' : 'low',\n      metadata: { note: input.note },\n    });\n    return this.state.checkpointSession(sessionId, input);\n  }\n\n  resumeSession(sessionId: string) {\n    return this.state.resumeSession(sessionId);\n  }\n\n  async completeSession(sessionId: string, note?: string) {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'completeSession',\n      risk: 'high',\n      metadata: note ? { note } : undefined,\n    });\n    return this.state.completeSession(sessionId, note);\n  }\n\n  async failSession(sessionId: string, message: string) {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'failSession',\n      risk: 'high',\n      metadata: { message },\n    });\n    return this.state.failSession(sessionId, message);\n  }\n\n  async runSensor(\n    definition: HarnessSensorDefinition,\n    input: HarnessSensorExecutionInput\n  ): Promise<HarnessSensorRun> {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'runSensor',\n      risk: definition.blocking ? 'high' : definition.severity === 'critical' ? 'high' : 'medium',\n      metadata: {\n        sensorId: definition.id,\n        sensorName: definition.name,\n        sessionId: input.sessionId,\n        contractId: input.contractId,\n        ...(input.metadata ?? {}),\n      },\n    });\n    this.sensors.registerSensor(definition);\n    return this.sensors.runSensor(definition.id, input);\n  }\n\n  getSessionSensorRuns(sessionId: string) {\n    return this.sensors.getSessionSensorRuns(sessionId);\n  }\n\n  async createTaskContract(input: Parameters<HarnessTaskContractsService['createTaskContract']>[0]) {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'createTask',\n      risk: 'medium',\n      metadata: { title: input.title },\n    });\n    return this.contracts.createTaskContract(input);\n  }\n\n  listTaskContracts() {\n    return this.contracts.listTaskContracts();\n  }\n\n  evaluateTaskCompletion(taskId: string, sessionId?: string): Promise<HarnessTaskCompletionResult> {\n    return this.contracts.evaluateTaskCompletion(taskId, sessionId);\n  }\n\n  async createHandoffContract(input: Parameters<HarnessTaskContractsService['createHandoffContract']>[0]): Promise<HarnessHandoffContract> {\n    await this.policy.authorize({\n      tool: 'harness',\n      action: 'createHandoff',\n      risk: 'medium',\n      metadata: { from: input.from, to: input.to },\n    });\n    return this.contracts.createHandoffContract(input);\n  }\n\n  listHandoffContracts() {\n    return this.contracts.listHandoffContracts();\n  }\n\n  listPolicies(): Promise<HarnessPolicyRule[]> {\n    return this.policy.listRules();\n  }\n\n  registerPolicy(input: {\n    id?: string;\n    effect: HarnessPolicyEffect;\n    target?: HarnessPolicyTarget;\n    pattern?: string;\n    approvalRole?: string;\n    reason?: string;\n    description?: string;\n    pathPattern?: string;\n    scope?: string;\n    metadata?: Record<string, unknown>;\n  }) {\n    const target = input.target\n      ?? (input.pathPattern ? 'path' : input.scope === 'risk' ? 'risk' : 'action');\n    const pattern = input.pattern ?? input.pathPattern ?? input.scope ?? input.description ?? input.reason ?? 'harness';\n    return this.policy.registerRule({\n      id: input.id ?? `policy-${Date.now()}`,\n      effect: input.effect,\n      target,\n      pattern,\n      approvalRole: input.approvalRole,\n      reason: input.reason ?? input.description,\n    } satisfies CreateHarnessPolicyRuleInput);\n  }\n\n  getPolicy(): Promise<HarnessPolicyDocument> {\n    return this.policy.loadPolicy();\n  }\n\n  setPolicy(input: {\n    defaultEffect?: HarnessPolicyDefaultEffect;\n    rules?: HarnessPolicyRule[];\n  }): Promise<HarnessPolicyDocument> {\n    const policy: HarnessPolicyDocument = {\n      version: 1,\n      defaultEffect: input.defaultEffect ?? 'allow',\n      rules: input.rules ?? [],\n    };\n    return this.policy.setPolicy(policy);\n  }\n\n  async resetPolicy(): Promise<HarnessPolicyDocument> {\n    return this.policy.setPolicy(await this.policy.createBootstrapPolicy());\n  }\n\n  evaluatePolicy(input: HarnessPolicyEvaluationInput): Promise<HarnessPolicyDecision> {\n    return this.policy.evaluate(input);\n  }\n\n  replaySession(sessionId: string): Promise<HarnessReplayRecord> {\n    return this.replay.replaySession(sessionId);\n  }\n\n  listReplays(sessionId?: string): Promise<HarnessReplayRecord[]> {\n    return this.replay.listReplays(sessionId ? { sessionId } : undefined);\n  }\n\n  exportFailureDataset(sessionIds?: string[]): Promise<HarnessFailureDataset> {\n    return this.datasets.buildFailureDataset({\n      sessionIds,\n      includeSuccessfulSessions: true,\n    });\n  }\n\n  listDatasets(): Promise<HarnessFailureDataset[]> {\n    return this.datasets.listDatasets();\n  }\n\n  getDataset(datasetId: string): Promise<HarnessFailureDataset> {\n    return this.datasets.getDataset(datasetId);\n  }\n\n  getFailureClusters(datasetId: string): Promise<HarnessFailureCluster[]> {\n    return this.datasets.getFailureClusters(datasetId);\n  }\n\n  async getSessionQuality(\n    sessionId: string,\n    options: {\n      taskId?: string;\n      policy?: HarnessBackpressurePolicy;\n    } = {}\n  ): Promise<HarnessSessionQualitySnapshot> {\n    const [session, sensorRuns, taskEvaluation] = await Promise.all([\n      this.state.getSession(sessionId),\n      this.sensors.getSessionSensorRuns(sessionId),\n      options.taskId\n        ? this.contracts.evaluateTaskCompletion(options.taskId, sessionId)\n        : Promise.resolve(null),\n    ]);\n\n    const backpressure = this.sensors.evaluateBackpressure(sensorRuns, options.policy);\n    return {\n      session,\n      sensorRuns,\n      backpressure,\n      taskEvaluation,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/index.ts",
    "content": "/**\n * Harness service exports.\n *\n * These services hold transport-agnostic harness logic that can be consumed\n * by MCP, CLI, or future adapters.\n */\n\nexport { HarnessAgentsService, type HarnessAgentsServiceOptions } from './agentsService';\nexport { HarnessPlansService, type HarnessPlansServiceOptions } from './plansService';\nexport {\n  HarnessContextService,\n  type HarnessContextServiceOptions,\n  type HarnessContextInitResult,\n  type HarnessContextPlanScaffoldResult,\n  type HarnessBootstrapStatusResult,\n} from './contextService';\nexport {\n  readFileTool,\n  listFilesTool,\n  analyzeSymbolsTool,\n  getFileStructureTool,\n  searchCodeTool,\n  checkScaffoldingTool,\n  initializeContextTool,\n  scaffoldPlanTool,\n  fillScaffoldingTool,\n  listFilesToFillTool,\n  fillSingleFileTool,\n  getCodebaseMapTool,\n  getOrBuildContext,\n  cleanupSharedContext,\n  type ToolName,\n} from './contextTools';\nexport { HarnessSkillsService, type HarnessSkillsServiceOptions } from './skillsService';\nexport {\n  HarnessRuntimeStateService,\n  type HarnessRuntimeStateServiceOptions,\n  type HarnessSessionStatus,\n  type HarnessTraceLevel,\n  type HarnessArtifactKind,\n  type HarnessSessionRecord,\n  type HarnessSessionCheckpoint,\n  type HarnessTraceRecord,\n  type HarnessArtifactRecord,\n  type HarnessRuntimeStatePort,\n  type CreateSessionInput,\n  type AppendTraceInput,\n  type AddArtifactInput,\n  type CheckpointInput,\n} from './runtimeStateService';\nexport {\n  HarnessSensorsService,\n  type HarnessSensorsServiceOptions,\n  type HarnessSensorSeverity,\n  type HarnessSensorStatus,\n  type HarnessSensorExecutionInput,\n  type HarnessSensorExecutionResult,\n  type HarnessSensorDefinition,\n  type HarnessSensorRun,\n  type HarnessBackpressurePolicy,\n  type HarnessBackpressureResult,\n} from './sensorsService';\nexport {\n  HarnessSensorCatalogService,\n  type HarnessSensorCatalogServiceOptions,\n  type HarnessShellSensorConfig,\n  type HarnessSensorCatalogDocument,\n  type HarnessSensorCatalogSeverity,\n} from './sensorCatalogService';\nexport {\n  HarnessTaskContractsService,\n  type HarnessTaskContractsServiceOptions,\n  type HarnessTaskContractStatus,\n  type HarnessTaskContract,\n  type HarnessHandoffContract,\n  type HarnessTaskCompletionResult,\n} from './taskContractsService';\nexport {\n  HarnessExecutionService,\n  type HarnessExecutionServiceOptions,\n  type HarnessSessionQualitySnapshot,\n} from './executionService';\nexport {\n  HarnessReplayService,\n  type HarnessReplayServiceOptions,\n  type HarnessReplayDependencies,\n  type HarnessReplayEvent,\n  type HarnessReplayEventSource,\n  type HarnessReplayRecord,\n  type ReplaySessionOptions,\n} from './replayService';\nexport {\n  HarnessDatasetService,\n  type HarnessDatasetServiceOptions,\n  type HarnessDatasetDependencies,\n  type BuildHarnessDatasetOptions,\n  type HarnessFailureKind,\n  type HarnessFailureRecord,\n  type HarnessFailureCluster,\n  type HarnessFailureDataset,\n} from './datasetService';\nexport {\n  HarnessPolicyService,\n  HarnessPolicyBlockedError,\n  type HarnessPolicyServiceOptions,\n  type HarnessPolicyTarget,\n  type HarnessPolicyScope,\n  type HarnessPolicyEffect,\n  type HarnessPolicyRisk,\n  type HarnessPolicyRule,\n  type CreateHarnessPolicyRuleInput,\n  type HarnessPolicyEvaluationInput,\n  type HarnessPolicyEvaluationResult,\n} from './policyService';\nexport {\n  HarnessWorkflowStateService,\n  type HarnessWorkflowStateServiceOptions,\n  type HarnessWorkflowRecord,\n  type WorkflowHarnessBinding,\n} from './workflowStateService';\n"
  },
  {
    "path": "src/services/harness/plansService.ts",
    "content": "/**\n * Harness Plans Service\n *\n * Transport-agnostic plan management and execution tracking logic.\n */\n\nimport * as path from 'path';\nimport { WorkflowService } from '../workflow';\nimport {\n  PHASE_NAMES_EN,\n  createPlanLinker,\n  PrevcStatusManager,\n  type PrevcPhase,\n} from '../../workflow';\nimport { GitService } from '../../utils/gitService';\n\nexport interface HarnessPlansServiceOptions {\n  repoPath: string;\n}\n\nexport class HarnessPlansService {\n  private readonly linker;\n\n  constructor(private readonly options: HarnessPlansServiceOptions) {\n    const contextPath = path.join(this.repoPath, '.context');\n    const statusManager = new PrevcStatusManager(contextPath);\n    this.linker = createPlanLinker(this.repoPath, statusManager, true);\n  }\n\n  private get repoPath(): string {\n    return this.options.repoPath;\n  }\n\n  async link(planSlug: string): Promise<Record<string, unknown>> {\n    const ref = await this.linker.linkPlan(planSlug);\n\n    if (!ref) {\n      return {\n        success: false,\n        error: `Plan not found: ${planSlug}`,\n      };\n    }\n\n    const service = new WorkflowService(this.repoPath);\n    const workflowActive = await service.hasWorkflow();\n    if (workflowActive) {\n      await service.markPlanCreated(planSlug);\n    }\n\n    let canAdvanceToReview = false;\n    if (workflowActive) {\n      const gateResult = await service.checkGates();\n      canAdvanceToReview = gateResult.gates.plan_required.passed;\n    }\n\n    const workflowStatePath = path.join(this.repoPath, '.context', 'harness', 'workflows', 'prevc.json');\n    const enhancementPrompt = workflowActive\n      ? `PLAN LINKED TO ACTIVE WORKFLOW\n\nThe linked plan is now attached to the harness-backed PREVC workflow.\n\nNext:\n1. Use workflow-status() to confirm the active phase and harness binding\n2. Continue planning work\n3. Use workflow-advance() when the planning phase is complete`\n      : `PLAN LINKED - WORKFLOW NOT STARTED\n\nThe plan reference was created, but no PREVC workflow is active yet.\nUntil workflow-init runs, the harness does not have canonical workflow state and plan gates are not armed.\n\nNext:\n1. Call workflow-init({ name: \"${planSlug}\" }) to create the harness-backed PREVC workflow\n2. Call plan({ action: \"link\", planSlug: \"${planSlug}\" }) again after workflow-init\n3. Use workflow-status() to verify the plan is bound to the harness workflow`;\n\n    const nextSteps = workflowActive\n      ? [\n          'RECOMMENDED: Call workflow-status() to confirm the linked plan and harness binding',\n          'THEN: Continue planning and call workflow-advance() when ready to leave phase P',\n        ]\n      : [\n          `REQUIRED: Call workflow-init({ name: \"${planSlug}\" }) to start the harness-backed PREVC workflow`,\n          `REQUIRED: Call plan({ action: \"link\", planSlug: \"${planSlug}\" }) again after workflow-init so gates see the plan`,\n          'THEN: Call workflow-status() to confirm the workflow and harness binding are active',\n        ];\n\n    return {\n      success: true,\n      plan: ref,\n      workflowActive,\n      workflowStatePath,\n      planCreatedForGates: workflowActive,\n      canAdvanceToReview,\n      enhancementPrompt,\n      nextSteps,\n    };\n  }\n\n  async getLinked(): Promise<Record<string, unknown>> {\n    const plans = await this.linker.getLinkedPlans();\n    return { success: true, plans };\n  }\n\n  async getDetails(planSlug: string): Promise<Record<string, unknown>> {\n    const plan = await this.linker.getLinkedPlan(planSlug);\n\n    if (!plan) {\n      return {\n        success: false,\n        error: `Plan not found or not linked: ${planSlug}`,\n      };\n    }\n\n    return {\n      success: true,\n      plan: {\n        ...plan,\n        phasesWithPrevc: plan.phases.map(p => ({\n          ...p,\n          prevcPhaseName: PHASE_NAMES_EN[p.prevcPhase],\n        })),\n      },\n    };\n  }\n\n  async getForPhase(phase: PrevcPhase): Promise<Record<string, unknown>> {\n    const plans = await this.linker.getPlansForPhase(phase);\n\n    return {\n      success: true,\n      phase,\n      phaseName: PHASE_NAMES_EN[phase],\n      plans: plans.map(p => ({\n        slug: p.ref.slug,\n        title: p.ref.title,\n        phasesInThisPrevc: p.phases\n          .filter(ph => ph.prevcPhase === phase)\n          .map(ph => ({ id: ph.id, name: ph.name, status: ph.status })),\n        hasPendingWork: this.linker.hasPendingWorkForPhase(p, phase),\n      })),\n    };\n  }\n\n  async updatePhase(planSlug: string, phaseId: string, status: 'pending' | 'in_progress' | 'completed' | 'skipped'): Promise<Record<string, unknown>> {\n    const success = await this.linker.updatePlanPhase(planSlug, phaseId, status);\n    return { success, planSlug, phaseId, status };\n  }\n\n  async recordDecision(params: {\n    planSlug: string;\n    title: string;\n    description: string;\n    phase?: PrevcPhase;\n    alternatives?: string[];\n  }): Promise<Record<string, unknown>> {\n    const decision = await this.linker.recordDecision(params.planSlug, {\n      title: params.title,\n      description: params.description,\n      phase: params.phase,\n      alternatives: params.alternatives,\n      status: 'accepted',\n    });\n\n    return { success: true, decision };\n  }\n\n  async updateStep(params: {\n    planSlug: string;\n    phaseId: string;\n    stepIndex: number;\n    status: 'pending' | 'in_progress' | 'completed' | 'skipped';\n    output?: string;\n    notes?: string;\n  }): Promise<Record<string, unknown>> {\n    const success = await this.linker.updatePlanStep(\n      params.planSlug,\n      params.phaseId,\n      params.stepIndex,\n      params.status,\n      { output: params.output, notes: params.notes }\n    );\n\n    return {\n      success,\n      planSlug: params.planSlug,\n      phaseId: params.phaseId,\n      stepIndex: params.stepIndex,\n      status: params.status,\n    };\n  }\n\n  async getStatus(planSlug: string): Promise<Record<string, unknown>> {\n    const status = await this.linker.getPlanExecutionStatus(planSlug);\n\n    if (!status) {\n      return {\n        success: false,\n        error: 'Plan tracking not found. The plan may not have any execution data yet.',\n      };\n    }\n\n    return { success: true, ...status };\n  }\n\n  async syncMarkdown(planSlug: string): Promise<Record<string, unknown>> {\n    const success = await this.linker.syncPlanMarkdown(planSlug);\n\n    return {\n      success,\n      planSlug,\n      message: success ? 'Plan markdown synced successfully' : 'Failed to sync - plan or tracking not found',\n    };\n  }\n\n  async commitPhase(params: {\n    planSlug: string;\n    phaseId: string;\n    coAuthor?: string;\n    stagePatterns?: string[];\n    dryRun?: boolean;\n  }): Promise<Record<string, unknown>> {\n    const plan = await this.linker.getLinkedPlan(params.planSlug);\n    if (!plan) {\n      return { success: false, error: `Plan not found: ${params.planSlug}` };\n    }\n\n    const phase = plan.phases.find(p => p.id === params.phaseId);\n    if (!phase) {\n      return { success: false, error: `Phase not found: ${params.phaseId}` };\n    }\n\n    const commitMessage = phase.commitCheckpoint ||\n      `chore(plan): complete ${phase.name} for ${params.planSlug}`;\n\n    const stagePatterns = params.stagePatterns || ['.context/**'];\n    const gitService = new GitService(this.repoPath);\n\n    if (!gitService.isGitRepository()) {\n      return { success: false, error: 'Not a git repository' };\n    }\n\n    if (params.dryRun) {\n      const stagedFiles = gitService.stageFiles(stagePatterns);\n      return {\n        success: true,\n        dryRun: true,\n        planSlug: params.planSlug,\n        phaseId: params.phaseId,\n        commitMessage,\n        coAuthor: params.coAuthor,\n        filesWouldBeCommitted: stagedFiles,\n      };\n    }\n\n    const stagedFiles = gitService.stageFiles(stagePatterns);\n    if (stagedFiles.length === 0) {\n      return {\n        success: false,\n        error: 'Nothing to commit: no files match the stage patterns or all files are already committed',\n      };\n    }\n\n    const commitResult = gitService.commit(commitMessage, params.coAuthor);\n    await this.linker.recordPhaseCommit(params.planSlug, params.phaseId, {\n      hash: commitResult.hash,\n      shortHash: commitResult.shortHash,\n      committedBy: params.coAuthor,\n    });\n\n    return {\n      success: true,\n      planSlug: params.planSlug,\n      phaseId: params.phaseId,\n      commit: {\n        hash: commitResult.hash,\n        shortHash: commitResult.shortHash,\n        message: commitMessage,\n        filesCommitted: commitResult.filesCommitted,\n        coAuthor: params.coAuthor,\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/policyService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessPolicyService } from './policyService';\n\ndescribe('HarnessPolicyService', () => {\n  let tempDir: string;\n  let service: HarnessPolicyService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-policy-'));\n    service = new HarnessPolicyService({ repoPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('allows actions by default and persists policy changes', async () => {\n    const decision = await service.evaluate({ action: 'addArtifact', path: 'src/index.ts' });\n    expect(decision.allowed).toBe(true);\n    expect(decision.requiresApproval).toBe(false);\n\n    await service.setPolicy({\n      version: 1,\n      defaultEffect: 'deny',\n      rules: [\n        { id: 'allow-src', target: 'path', pattern: 'src/**', effect: 'allow' },\n      ],\n    });\n\n    await expect(service.assertAllowed({ action: 'addArtifact', path: 'docs/readme.md' })).rejects.toThrow('Policy blocked');\n    await expect(service.assertAllowed({ action: 'addArtifact', path: 'src/index.ts' })).resolves.toBeUndefined();\n  });\n\n  it('can require approval for risk-based operations', async () => {\n    await service.setPolicy({\n      version: 1,\n      defaultEffect: 'allow',\n      rules: [\n        { id: 'approve-high-risk', target: 'risk', pattern: 'high', effect: 'require_approval', approvalRole: 'reviewer' },\n      ],\n    });\n\n    const decision = await service.evaluate({ action: 'completeSession', risk: 'high' });\n    expect(decision.requiresApproval).toBe(true);\n    await expect(service.assertAllowed({ action: 'completeSession', risk: 'high' })).rejects.toThrow('Policy approval required');\n  });\n\n  it('matches declarative tool/action/path rules and respects approval role', async () => {\n    await service.setPolicy({\n      version: 1,\n      defaultEffect: 'allow',\n      rules: [\n        {\n          id: 'mcp-write-review',\n          effect: 'require_approval',\n          when: {\n            tools: ['harness'],\n            actions: ['addArtifact'],\n            paths: ['src/services/mcp/**'],\n            risk: 'medium',\n          },\n          approvalRole: 'reviewer',\n        },\n      ],\n    });\n\n    const denied = await service.evaluate({\n      tool: 'harness',\n      action: 'addArtifact',\n      path: 'src/services/mcp/gateway/harness.ts',\n      risk: 'high',\n    });\n    expect(denied.requiresApproval).toBe(true);\n    expect(denied.allowed).toBe(false);\n\n    const approved = await service.evaluate({\n      tool: 'harness',\n      action: 'addArtifact',\n      path: 'src/services/mcp/gateway/harness.ts',\n      risk: 'high',\n      approval: { approvedBy: 'alice' },\n      approvalRole: 'reviewer',\n    });\n    expect(approved.allowed).toBe(true);\n    expect(approved.blocked).toBe(false);\n  });\n\n  it('builds bootstrap rules from the initialized repository instead of static dotcontext paths', async () => {\n    await fs.ensureDir(path.join(tempDir, 'packages', 'api'));\n    await fs.ensureDir(path.join(tempDir, '.github', 'workflows'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'policy-bootstrap-test',\n      version: '1.0.0',\n      workspaces: ['packages/*'],\n    }, { spaces: 2 });\n    await fs.writeFile(path.join(tempDir, 'packages', 'api', 'index.ts'), 'export const api = true;\\n', 'utf-8');\n    await fs.writeFile(path.join(tempDir, '.github', 'workflows', 'ci.yml'), 'name: ci\\n', 'utf-8');\n\n    const policy = await service.createBootstrapPolicy();\n    const coreRule = policy.rules.find((rule) => rule.id === 'protect-repository-core');\n    const configRule = policy.rules.find((rule) => rule.id === 'protect-repository-config');\n\n    expect(coreRule?.when?.paths).toEqual(expect.arrayContaining(['packages/**']));\n    expect(coreRule?.when?.paths).not.toEqual(expect.arrayContaining(['src/services/mcp/**', 'src/workflow/**']));\n    expect(configRule?.when?.paths).toEqual(expect.arrayContaining(['package.json', '.github/workflows/**']));\n  });\n});\n"
  },
  {
    "path": "src/services/harness/policyService.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { StackDetector, type StackInfo } from '../stack';\n\nexport type HarnessPolicyEffect = 'allow' | 'deny' | 'require_approval';\nexport type HarnessPolicyRisk = 'low' | 'medium' | 'high' | 'critical';\nexport type HarnessPolicyTarget = 'tool' | 'action' | 'path' | 'risk';\nexport type HarnessPolicyScope = HarnessPolicyTarget;\nexport type HarnessPolicyDefaultEffect = 'allow' | 'deny';\n\nexport interface HarnessPolicyRuleSelector {\n  tools?: string[];\n  actions?: string[];\n  paths?: string[];\n  risk?: HarnessPolicyRisk;\n}\n\nexport interface HarnessPolicyRule {\n  id: string;\n  effect: HarnessPolicyEffect;\n  when?: HarnessPolicyRuleSelector;\n  target?: HarnessPolicyTarget;\n  pattern?: string;\n  approvalRole?: string;\n  reason?: string;\n}\n\nexport interface HarnessPolicyDocument {\n  version: 1;\n  defaultEffect: HarnessPolicyDefaultEffect;\n  rules: HarnessPolicyRule[];\n}\n\nexport interface HarnessPolicyApproval {\n  approvedBy?: string;\n  note?: string;\n}\n\nexport interface HarnessPolicyEvaluationInput {\n  tool?: string;\n  action: string;\n  paths?: string[];\n  path?: string;\n  risk?: HarnessPolicyRisk;\n  approval?: HarnessPolicyApproval;\n  approvalRole?: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessPolicyLegacyEnforcementInput {\n  scope: HarnessPolicyScope;\n  target?: string;\n  path?: string;\n  risk?: HarnessPolicyRisk;\n  metadata?: Record<string, unknown>;\n  approval?: HarnessPolicyApproval;\n  approvalRole?: string;\n}\n\nexport interface HarnessPolicyMatch {\n  rule: HarnessPolicyRule;\n  requiresApproval: boolean;\n  blocked: boolean;\n  approved: boolean;\n}\n\nexport interface HarnessPolicyDecision {\n  allowed: boolean;\n  blocked: boolean;\n  requiresApproval: boolean;\n  reasons: string[];\n  matchedRules: HarnessPolicyMatch[];\n  policy: HarnessPolicyDocument;\n}\n\nexport type HarnessPolicyEvaluationResult = HarnessPolicyDecision;\n\nexport interface CreateHarnessPolicyRuleInput {\n  id: string;\n  effect: HarnessPolicyEffect;\n  target: HarnessPolicyTarget;\n  pattern: string;\n  approvalRole?: string;\n  reason?: string;\n}\n\nexport interface HarnessPolicyServiceOptions {\n  repoPath: string;\n}\n\nexport interface CreateHarnessBootstrapPolicyOptions {\n  stackInfo?: StackInfo;\n}\n\nexport class HarnessPolicyBlockedError extends Error {\n  constructor(\n    message: string,\n    public readonly decision: HarnessPolicyDecision\n  ) {\n    super(message);\n    this.name = 'HarnessPolicyBlockedError';\n  }\n}\n\nfunction normalizePath(input: string): string {\n  return input.split(path.sep).join('/');\n}\n\nfunction escapeRegex(input: string): string {\n  return input.replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&');\n}\n\nfunction globToRegExp(pattern: string): RegExp {\n  const normalized = normalizePath(pattern);\n  const regex = normalized\n    .split('**').join('__DOUBLE_STAR__')\n    .split('*').join('__STAR__')\n    .split('?').join('__QMARK__');\n  const escaped = escapeRegex(regex)\n    .replace(/__DOUBLE_STAR__/g, '.*')\n    .replace(/__STAR__/g, '[^/]*')\n    .replace(/__QMARK__/g, '.');\n\n  return new RegExp(`^${escaped}$`, 'i');\n}\n\nfunction matchesAnyPattern(value: string, patterns: string[]): boolean {\n  const normalizedValue = normalizePath(value);\n  return patterns.some((pattern) => {\n    if (pattern.includes('*') || pattern.includes('?')) {\n      return globToRegExp(pattern).test(normalizedValue);\n    }\n\n    const normalizedPattern = normalizePath(pattern);\n    return normalizedValue === normalizedPattern || normalizedValue.includes(normalizedPattern);\n  });\n}\n\nfunction riskRank(risk: HarnessPolicyRisk): number {\n  return {\n    low: 1,\n    medium: 2,\n    high: 3,\n    critical: 4,\n  }[risk];\n}\n\nfunction compareRisk(inputRisk: HarnessPolicyRisk | undefined, ruleRisk: HarnessPolicyRisk | undefined): boolean {\n  if (!ruleRisk) {\n    return true;\n  }\n  if (!inputRisk) {\n    return false;\n  }\n  return riskRank(inputRisk) >= riskRank(ruleRisk);\n}\n\nfunction normalizeRule(rule: HarnessPolicyRule): HarnessPolicyRule {\n  if (rule.when) {\n    return rule;\n  }\n\n  const selector: HarnessPolicyRuleSelector = {};\n  if (rule.target === 'tool' && rule.pattern) {\n    selector.tools = [rule.pattern];\n  } else if (rule.target === 'action' && rule.pattern) {\n    selector.actions = [rule.pattern];\n  } else if (rule.target === 'path' && rule.pattern) {\n    selector.paths = [rule.pattern];\n  } else if (rule.target === 'risk' && rule.pattern) {\n    selector.risk = rule.pattern as HarnessPolicyRisk;\n  }\n\n  return {\n    ...rule,\n    when: selector,\n  };\n}\n\nfunction isLegacyEnforcementInput(\n  input: HarnessPolicyEvaluationInput | HarnessPolicyLegacyEnforcementInput\n): input is HarnessPolicyLegacyEnforcementInput {\n  return (input as HarnessPolicyLegacyEnforcementInput).scope !== undefined;\n}\n\nconst CORE_DIRECTORY_CANDIDATES = [\n  'src',\n  'app',\n  'apps',\n  'lib',\n  'libs',\n  'packages',\n  'modules',\n  'services',\n  'server',\n  'client',\n  'web',\n  'frontend',\n  'backend',\n  'api',\n  'cmd',\n  'pkg',\n  'internal',\n  'bin',\n] as const;\n\nconst ROOT_CONFIG_FILE_CANDIDATES = [\n  'package.json',\n  'package-lock.json',\n  'pnpm-lock.yaml',\n  'yarn.lock',\n  'bun.lockb',\n  'tsconfig.json',\n  'pyproject.toml',\n  'requirements.txt',\n  'setup.py',\n  'go.mod',\n  'go.sum',\n  'Cargo.toml',\n  'Cargo.lock',\n  'pom.xml',\n  'build.gradle',\n  'build.gradle.kts',\n  'settings.gradle',\n  'Gemfile',\n  'composer.json',\n  'Makefile',\n  'turbo.json',\n  'nx.json',\n  'lerna.json',\n  'pnpm-workspace.yaml',\n] as const;\n\nconst ROOT_SOURCE_FILE_EXTENSIONS = new Set([\n  '.ts',\n  '.tsx',\n  '.js',\n  '.jsx',\n  '.mjs',\n  '.cjs',\n  '.py',\n  '.go',\n  '.rs',\n  '.java',\n  '.kt',\n  '.rb',\n  '.php',\n  '.cs',\n  '.swift',\n]);\n\nconst CONFIG_DIRECTORY_PATTERNS: Array<{ path: string; pattern: string }> = [\n  { path: '.github/workflows', pattern: '.github/workflows/**' },\n  { path: '.circleci', pattern: '.circleci/**' },\n  { path: 'docker', pattern: 'docker/**' },\n  { path: 'infra', pattern: 'infra/**' },\n  { path: 'infrastructure', pattern: 'infrastructure/**' },\n  { path: 'terraform', pattern: 'terraform/**' },\n  { path: 'k8s', pattern: 'k8s/**' },\n  { path: 'helm', pattern: 'helm/**' },\n  { path: 'deploy', pattern: 'deploy/**' },\n  { path: 'deployment', pattern: 'deployment/**' },\n  { path: '.devcontainer', pattern: '.devcontainer/**' },\n] as const;\n\nexport class HarnessPolicyService {\n  constructor(private readonly options: HarnessPolicyServiceOptions) {}\n\n  private get repoPath(): string {\n    return path.resolve(this.options.repoPath);\n  }\n\n  private get policyPath(): string {\n    return path.join(this.repoPath, '.context', 'harness', 'policy.json');\n  }\n\n  private toEvaluationInput(\n    input: HarnessPolicyEvaluationInput | HarnessPolicyLegacyEnforcementInput\n  ): HarnessPolicyEvaluationInput {\n    if (!isLegacyEnforcementInput(input)) {\n      return input;\n    }\n\n    return {\n      tool: 'harness',\n      action: input.target ?? input.scope,\n      paths: input.path ? [input.path] : undefined,\n      risk: input.risk,\n      metadata: input.metadata,\n      approval: input.approval,\n      approvalRole: input.approvalRole,\n    };\n  }\n\n  async loadPolicy(): Promise<HarnessPolicyDocument> {\n    if (!(await fs.pathExists(this.policyPath))) {\n      return {\n        version: 1,\n        defaultEffect: 'allow',\n        rules: [],\n      };\n    }\n\n    const loaded = await fs.readJson(this.policyPath) as Partial<HarnessPolicyDocument>;\n    return {\n      version: 1,\n      defaultEffect: loaded.defaultEffect === 'deny' ? 'deny' : 'allow',\n      rules: Array.isArray(loaded.rules) ? (loaded.rules as HarnessPolicyRule[]).map(normalizeRule) : [],\n    };\n  }\n\n  async savePolicy(policy: HarnessPolicyDocument): Promise<HarnessPolicyDocument> {\n    await fs.ensureDir(path.dirname(this.policyPath));\n    const normalized: HarnessPolicyDocument = {\n      version: 1,\n      defaultEffect: policy.defaultEffect === 'deny' ? 'deny' : 'allow',\n      rules: policy.rules.map(normalizeRule),\n    };\n    await fs.writeJson(this.policyPath, normalized, { spaces: 2 });\n    return normalized;\n  }\n\n  async listRules(): Promise<HarnessPolicyRule[]> {\n    const policy = await this.loadPolicy();\n    return policy.rules.map(normalizeRule);\n  }\n\n  private evaluateWithPolicy(input: HarnessPolicyEvaluationInput, policy: HarnessPolicyDocument): HarnessPolicyDecision {\n    const currentPolicy = policy ?? {\n      version: 1,\n      defaultEffect: 'allow',\n      rules: [],\n    };\n    const matchedRules: HarnessPolicyMatch[] = [];\n    const reasons: string[] = [];\n    const tool = input.tool ?? 'harness';\n    const paths = input.paths ?? (input.path ? [input.path] : []);\n\n    for (const rawRule of currentPolicy.rules) {\n      const rule = normalizeRule(rawRule);\n      const selector = rule.when ?? {};\n      const toolMatch = !selector.tools || selector.tools.length === 0\n        ? true\n        : matchesAnyPattern(tool, selector.tools);\n      const actionMatch = !selector.actions || selector.actions.length === 0\n        ? true\n        : matchesAnyPattern(input.action, selector.actions);\n      const pathMatch = !selector.paths || selector.paths.length === 0\n        ? true\n        : paths.some((value) => matchesAnyPattern(value, selector.paths!));\n      const riskMatch = compareRisk(input.risk, selector.risk);\n\n      if (!(toolMatch && actionMatch && pathMatch && riskMatch)) {\n        continue;\n      }\n\n      const approvedByProvided = Boolean(input.approval?.approvedBy);\n      const approvalRoleSatisfied = !rule.approvalRole || input.approvalRole === rule.approvalRole;\n      const approved = approvedByProvided && approvalRoleSatisfied;\n      const requiresApproval = rule.effect === 'require_approval' && !approved;\n      const blocked = rule.effect === 'deny' || requiresApproval;\n\n      matchedRules.push({\n        rule,\n        requiresApproval,\n        blocked,\n        approved,\n      });\n\n      if (rule.reason) {\n        reasons.push(rule.reason);\n      } else if (requiresApproval && rule.approvalRole) {\n        reasons.push(`Policy rule ${rule.id} requires approval by role: ${rule.approvalRole}`);\n      } else {\n        reasons.push(`Policy rule matched: ${rule.id}`);\n      }\n    }\n\n    const blocked = matchedRules.some((match) => match.blocked);\n    const requiresApproval = matchedRules.some((match) => match.requiresApproval);\n    const allowed = !blocked && (currentPolicy.defaultEffect === 'allow' || matchedRules.length > 0);\n\n    if (!allowed && matchedRules.length === 0) {\n      reasons.push(`Default policy effect is ${currentPolicy.defaultEffect}`);\n    }\n\n    return {\n      allowed,\n      blocked,\n      requiresApproval,\n      reasons,\n      matchedRules,\n      policy: currentPolicy,\n    };\n  }\n\n  async evaluate(input: HarnessPolicyEvaluationInput, policy?: HarnessPolicyDocument): Promise<HarnessPolicyDecision> {\n    const currentPolicy = policy ?? await this.loadPolicy();\n    return this.evaluateWithPolicy(input, currentPolicy);\n  }\n\n  async evaluateAndAuthorize(\n    input: HarnessPolicyEvaluationInput,\n    policy?: HarnessPolicyDocument\n  ): Promise<HarnessPolicyDecision> {\n    const currentPolicy = policy ?? await this.loadPolicy();\n    return this.evaluateWithPolicy(input, currentPolicy);\n  }\n\n  async authorize(\n    input: HarnessPolicyEvaluationInput,\n    policy?: HarnessPolicyDocument\n  ): Promise<HarnessPolicyDecision> {\n    const decision = await this.evaluateAndAuthorize(input, policy);\n    if (decision.requiresApproval) {\n      throw new HarnessPolicyBlockedError(\n        `Policy approval required for ${(input.tool ?? 'harness')}.${input.action}`,\n        decision\n      );\n    }\n    if (decision.blocked || !decision.allowed) {\n      throw new HarnessPolicyBlockedError(\n        `Policy blocked ${(input.tool ?? 'harness')}.${input.action}`,\n        decision\n      );\n    }\n    return decision;\n  }\n\n  async enforce(\n    input: HarnessPolicyEvaluationInput | HarnessPolicyLegacyEnforcementInput,\n    policy?: HarnessPolicyDocument\n  ): Promise<HarnessPolicyDecision> {\n    return this.authorize(this.toEvaluationInput(input), policy);\n  }\n\n  async setPolicyFromRules(input: {\n    defaultEffect?: HarnessPolicyDefaultEffect;\n    rules?: HarnessPolicyRule[];\n  }): Promise<HarnessPolicyDocument> {\n    return this.savePolicy({\n      version: 1,\n      defaultEffect: input.defaultEffect ?? 'allow',\n      rules: (input.rules ?? []).map(normalizeRule),\n    });\n  }\n\n  async setPolicy(policy: HarnessPolicyDocument): Promise<HarnessPolicyDocument> {\n    return this.savePolicy({\n      version: 1,\n      defaultEffect: policy.defaultEffect,\n      rules: policy.rules.map(normalizeRule),\n    });\n  }\n\n  async setPolicyDocument(policy: HarnessPolicyDocument): Promise<HarnessPolicyDocument> {\n    return this.setPolicy(policy);\n  }\n\n  async registerRule(input: CreateHarnessPolicyRuleInput): Promise<HarnessPolicyRule> {\n    const policy = await this.loadPolicy();\n    const rule: HarnessPolicyRule = {\n      id: input.id,\n      effect: input.effect,\n      target: input.target,\n      pattern: input.pattern,\n      approvalRole: input.approvalRole,\n      reason: input.reason,\n    };\n    policy.rules = policy.rules.filter((existing) => existing.id !== rule.id);\n    policy.rules.push(rule);\n    await this.savePolicy(policy);\n    return normalizeRule(rule);\n  }\n\n  async evaluateLegacy(input: HarnessPolicyEvaluationInput): Promise<HarnessPolicyEvaluationResult> {\n    return this.evaluate(input);\n  }\n\n  async assertAllowed(input: HarnessPolicyEvaluationInput): Promise<void> {\n    try {\n      await this.authorize(input);\n    } catch (error) {\n      if (error instanceof HarnessPolicyBlockedError) {\n        const message = error.decision.requiresApproval\n          ? `Policy approval required for ${(input.tool ?? 'harness')}.${input.action}`\n          : `Policy blocked ${(input.tool ?? 'harness')}.${input.action}`;\n        throw new HarnessPolicyBlockedError(message, error.decision);\n      }\n\n      throw error;\n    }\n  }\n\n  async evaluatePolicy(input: HarnessPolicyEvaluationInput): Promise<HarnessPolicyEvaluationResult> {\n    return this.evaluate(input);\n  }\n\n  async evaluateRule(input: HarnessPolicyEvaluationInput): Promise<HarnessPolicyEvaluationResult> {\n    return this.evaluate(input);\n  }\n\n  async getPolicy(): Promise<HarnessPolicyDocument> {\n    return this.loadPolicy();\n  }\n\n  private async detectStackInfo(): Promise<StackInfo | undefined> {\n    try {\n      return await new StackDetector().detect(this.repoPath);\n    } catch {\n      return undefined;\n    }\n  }\n\n  private async readTopLevelEntries(): Promise<{ files: string[]; directories: string[] }> {\n    try {\n      const entries = await fs.readdir(this.repoPath);\n      const files: string[] = [];\n      const directories: string[] = [];\n\n      await Promise.all(entries.map(async (entry) => {\n        const fullPath = path.join(this.repoPath, entry);\n        const stats = await fs.stat(fullPath);\n        if (stats.isDirectory()) {\n          directories.push(entry);\n        } else {\n          files.push(entry);\n        }\n      }));\n\n      return {\n        files: files.sort(),\n        directories: directories.sort(),\n      };\n    } catch {\n      return {\n        files: [],\n        directories: [],\n      };\n    }\n  }\n\n  private getCorePathPatterns(topLevelEntries: { files: string[]; directories: string[] }): string[] {\n    const directorySet = new Set(topLevelEntries.directories);\n    const paths = CORE_DIRECTORY_CANDIDATES\n      .filter((directory) => directorySet.has(directory))\n      .map((directory) => `${directory}/**`);\n\n    if (paths.length > 0) {\n      return [...new Set(paths)];\n    }\n\n    return topLevelEntries.files\n      .filter((file) => ROOT_SOURCE_FILE_EXTENSIONS.has(path.extname(file).toLowerCase()))\n      .sort();\n  }\n\n  private async getConfigPathPatterns(topLevelEntries: { files: string[]; directories: string[] }): Promise<string[]> {\n    const fileSet = new Set(topLevelEntries.files);\n    const patterns: string[] = ROOT_CONFIG_FILE_CANDIDATES\n      .filter((file) => fileSet.has(file))\n      .slice();\n\n    for (const file of topLevelEntries.files) {\n      if (file === 'Dockerfile' || file.startsWith('Dockerfile.')) {\n        patterns.push(file);\n      }\n      if (/^docker-compose\\.(ya?ml)$/i.test(file)) {\n        patterns.push(file);\n      }\n    }\n\n    for (const candidate of CONFIG_DIRECTORY_PATTERNS) {\n      if (await fs.pathExists(path.join(this.repoPath, candidate.path))) {\n        patterns.push(candidate.pattern);\n      }\n    }\n\n    return [...new Set(patterns)].sort();\n  }\n\n  private buildCoreRuleReason(stackInfo?: StackInfo): string {\n    const descriptors = [\n      stackInfo?.isMonorepo ? 'monorepo' : null,\n      stackInfo?.primaryLanguage ?? null,\n      stackInfo?.frameworks[0] ?? null,\n    ].filter((value): value is string => Boolean(value));\n\n    if (descriptors.length === 0) {\n      return 'High-risk changes to core repository paths require approval.';\n    }\n\n    return `High-risk changes to core ${descriptors.join(' ')} paths require approval.`;\n  }\n\n  async createBootstrapPolicy(\n    options: CreateHarnessBootstrapPolicyOptions = {}\n  ): Promise<HarnessPolicyDocument> {\n    const stackInfo = options.stackInfo ?? await this.detectStackInfo();\n    const topLevelEntries = await this.readTopLevelEntries();\n    const corePaths = this.getCorePathPatterns(topLevelEntries);\n    const configPaths = await this.getConfigPathPatterns(topLevelEntries);\n    const rules: HarnessPolicyRule[] = [];\n\n    if (corePaths.length > 0) {\n      rules.push({\n        id: 'protect-repository-core',\n        effect: 'require_approval',\n        when: {\n          paths: corePaths,\n          risk: 'high',\n        },\n        reason: this.buildCoreRuleReason(stackInfo),\n      });\n    }\n\n    if (configPaths.length > 0) {\n      rules.push({\n        id: 'protect-repository-config',\n        effect: 'require_approval',\n        when: {\n          paths: configPaths,\n          risk: 'high',\n        },\n        reason: 'High-risk changes to repository automation and build configuration require approval.',\n      });\n    }\n\n    rules.push({\n      id: 'block-secrets',\n      effect: 'deny',\n      when: {\n        paths: ['**/.env*', '**/*.pem', '**/*.key', '**/*secret*'],\n      },\n      reason: 'Secret-like files are blocked by policy.',\n    });\n\n    return {\n      version: 1,\n      defaultEffect: 'allow',\n      rules,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/replayService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessExecutionService } from './executionService';\nimport { HarnessReplayService } from './replayService';\n\ndescribe('HarnessReplayService', () => {\n  let tempDir: string;\n  let execution: HarnessExecutionService;\n  let service: HarnessReplayService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-replay-'));\n    execution = new HarnessExecutionService({ repoPath: tempDir });\n    service = new HarnessReplayService({ repoPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('builds a replay without persisting it', async () => {\n    const session = await execution.createSession({ name: 'transient-replay' });\n    await execution.appendTrace(session.id, {\n      level: 'info',\n      event: 'custom.step',\n      message: 'Step one',\n    });\n\n    const replay = await service.buildReplay(session.id);\n\n    expect(replay.sessionId).toBe(session.id);\n    expect(replay.eventCount).toBeGreaterThan(0);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'harness', 'replays', `${replay.id}.json`))).toBe(false);\n    expect(await service.listReplays({ sessionId: session.id })).toHaveLength(0);\n  });\n\n  it('replays a session into a durable ordered event log', async () => {\n    const session = await execution.createSession({ name: 'replay-run' });\n    await execution.appendTrace(session.id, {\n      level: 'info',\n      event: 'custom.step',\n      message: 'Step one',\n    });\n    await execution.addArtifact(session.id, {\n      name: 'evidence.txt',\n      kind: 'file',\n      path: 'evidence.txt',\n    });\n    await execution.checkpointSession(session.id, { note: 'checkpoint' });\n\n    const replay = await service.replaySession(session.id);\n    const list = await service.listReplays({ sessionId: session.id });\n\n    expect(replay.eventCount).toBeGreaterThanOrEqual(4);\n    expect(replay.events.map(event => event.source)).toContain('checkpoint');\n    expect(list).toHaveLength(1);\n    expect(list[0].sessionId).toBe(session.id);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'harness', 'replays', `${replay.id}.json`))).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/services/harness/replayService.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\n\nimport type {\n  HarnessArtifactRecord,\n  HarnessSessionCheckpoint,\n  HarnessSessionRecord,\n  HarnessTraceRecord,\n  HarnessRuntimeStatePort,\n} from './runtimeStateService';\nimport { HarnessRuntimeStateService as DefaultHarnessRuntimeStateService } from './runtimeStateService';\nimport { HarnessSensorsService, type HarnessSensorRun } from './sensorsService';\nimport {\n  HarnessTaskContractsService,\n  type HarnessHandoffContract,\n  type HarnessTaskContract,\n} from './taskContractsService';\n\nexport type HarnessReplayEventSource =\n  | 'session'\n  | 'trace'\n  | 'artifact'\n  | 'checkpoint'\n  | 'sensor'\n  | 'task'\n  | 'handoff';\n\nexport interface HarnessReplayEvent {\n  id: string;\n  sessionId: string;\n  createdAt: string;\n  source: HarnessReplayEventSource;\n  label: string;\n  payload?: Record<string, unknown>;\n}\n\nexport interface HarnessReplayRecord {\n  id: string;\n  sessionId: string;\n  repoPath: string;\n  createdAt: string;\n  replayedAt: string;\n  fidelity: 'complete' | 'partial';\n  eventCount: number;\n  summary: string;\n  events: HarnessReplayEvent[];\n  session: HarnessSessionRecord;\n  artifacts: HarnessArtifactRecord[];\n  checkpoints: HarnessSessionCheckpoint[];\n  traces: HarnessTraceRecord[];\n  sensorRuns: HarnessSensorRun[];\n  tasks: HarnessTaskContract[];\n  handoffs: HarnessHandoffContract[];\n}\n\nexport interface HarnessReplayServiceOptions {\n  repoPath: string;\n  dependencies?: Partial<HarnessReplayDependencies>;\n}\n\nexport interface ReplaySessionOptions {\n  includePayloads?: boolean;\n  maxEvents?: number;\n}\n\nexport interface HarnessReplayDependencies {\n  stateService: HarnessRuntimeStatePort;\n  sensorsService: Pick<HarnessSensorsService, 'getSessionSensorRuns'>;\n  contractsService: Pick<HarnessTaskContractsService, 'listTaskContracts' | 'listHandoffContracts'>;\n}\n\nfunction nowIso(): string {\n  return new Date().toISOString();\n}\n\nfunction sortByCreatedAt<T extends { createdAt: string }>(items: T[]): T[] {\n  return [...items].sort((left, right) => left.createdAt.localeCompare(right.createdAt));\n}\n\nexport class HarnessReplayService {\n  private readonly stateService: HarnessReplayDependencies['stateService'];\n  private readonly sensorsService: HarnessReplayDependencies['sensorsService'];\n  private readonly contractsService: HarnessReplayDependencies['contractsService'];\n\n  constructor(private readonly options: HarnessReplayServiceOptions) {\n    const stateService = options.dependencies?.stateService\n      ?? new DefaultHarnessRuntimeStateService({ repoPath: options.repoPath });\n    const sensorsService = options.dependencies?.sensorsService\n      ?? new HarnessSensorsService({ stateService });\n    const contractsService = options.dependencies?.contractsService\n      ?? new HarnessTaskContractsService({\n        repoPath: options.repoPath,\n        stateService,\n      });\n\n    this.stateService = stateService;\n    this.sensorsService = sensorsService;\n    this.contractsService = contractsService;\n  }\n\n  private get repoPath(): string {\n    return path.resolve(this.options.repoPath);\n  }\n\n  private get replaysPath(): string {\n    return path.join(this.repoPath, '.context', 'harness', 'replays');\n  }\n\n  private replayFile(replayId: string): string {\n    return path.join(this.replaysPath, `${replayId}.json`);\n  }\n\n  private async ensureLayout(): Promise<void> {\n    await fs.ensureDir(this.replaysPath);\n  }\n\n  private async saveReplay(replay: HarnessReplayRecord): Promise<void> {\n    await this.ensureLayout();\n    await fs.writeJson(this.replayFile(replay.id), replay, { spaces: 2 });\n  }\n\n  private async readReplay(replayId: string): Promise<HarnessReplayRecord> {\n    const filePath = this.replayFile(replayId);\n    if (!(await fs.pathExists(filePath))) {\n      throw new Error(`Replay not found: ${replayId}`);\n    }\n\n    return fs.readJson(filePath) as Promise<HarnessReplayRecord>;\n  }\n\n  async buildReplay(\n    sessionId: string,\n    options: ReplaySessionOptions = {}\n  ): Promise<HarnessReplayRecord> {\n    const session = await this.stateService.getSession(sessionId);\n    const [traces, artifacts, checkpoints, sensorRuns, tasks, handoffs] = await Promise.all([\n      this.stateService.listTraces(sessionId),\n      this.stateService.listArtifacts(sessionId),\n      this.stateService.listCheckpoints(sessionId),\n      this.sensorsService.getSessionSensorRuns(sessionId),\n      this.contractsService.listTaskContracts(),\n      this.contractsService.listHandoffContracts(),\n    ]);\n\n    const sessionTasks = tasks.filter((task) => task.sessionId === sessionId);\n    const sessionHandoffs = handoffs.filter((handoff) => handoff.sessionId === sessionId);\n\n    const events: HarnessReplayEvent[] = [\n      {\n        id: randomUUID(),\n        sessionId,\n        createdAt: session.createdAt,\n        source: 'session',\n        label: `session:${session.name}`,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              id: session.id,\n              status: session.status,\n              metadata: session.metadata ?? null,\n            },\n      },\n      ...traces.map((trace) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: trace.createdAt,\n        source: 'trace' as const,\n        label: trace.event,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              level: trace.level,\n              message: trace.message,\n              data: trace.data ?? null,\n            },\n      })),\n      ...artifacts.map((artifact) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: artifact.createdAt,\n        source: 'artifact' as const,\n        label: artifact.name,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              kind: artifact.kind,\n              path: artifact.path ?? null,\n              metadata: artifact.metadata ?? null,\n            },\n      })),\n      ...checkpoints.map((checkpoint) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: checkpoint.createdAt,\n        source: 'checkpoint' as const,\n        label: checkpoint.note || checkpoint.id,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              artifactIds: checkpoint.artifactIds,\n              data: checkpoint.data ?? null,\n            },\n      })),\n      ...sensorRuns.map((run) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: run.createdAt,\n        source: 'sensor' as const,\n        label: run.sensorId,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              status: run.status,\n              summary: run.summary,\n              severity: run.severity,\n              blocking: run.blocking,\n            },\n      })),\n      ...sessionTasks.map((task) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: task.createdAt,\n        source: 'task' as const,\n        label: task.title,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              status: task.status,\n              requiredSensors: task.requiredSensors,\n              requiredArtifacts: task.requiredArtifacts,\n              acceptanceCriteria: task.acceptanceCriteria,\n            },\n      })),\n      ...sessionHandoffs.map((handoff) => ({\n        id: randomUUID(),\n        sessionId,\n        createdAt: handoff.createdAt,\n        source: 'handoff' as const,\n        label: `${handoff.from} -> ${handoff.to}`,\n        payload: options.includePayloads === false\n          ? undefined\n          : {\n              artifacts: handoff.artifacts,\n              evidence: handoff.evidence,\n            },\n      })),\n    ];\n\n    const orderedEvents = sortByCreatedAt(events).slice(0, options.maxEvents ?? Number.MAX_SAFE_INTEGER);\n    const replay: HarnessReplayRecord = {\n      id: randomUUID(),\n      sessionId,\n      repoPath: this.repoPath,\n      createdAt: nowIso(),\n      replayedAt: nowIso(),\n      fidelity: orderedEvents.length >= events.length ? 'complete' : 'partial',\n      eventCount: orderedEvents.length,\n      summary: `Replayed ${orderedEvents.length} events for session ${session.name}`,\n      events: orderedEvents,\n      session,\n      artifacts,\n      checkpoints,\n      traces,\n      sensorRuns,\n      tasks: sessionTasks,\n      handoffs: sessionHandoffs,\n    };\n\n    return replay;\n  }\n\n  async replaySession(\n    sessionId: string,\n    options: ReplaySessionOptions = {}\n  ): Promise<HarnessReplayRecord> {\n    const replay = await this.buildReplay(sessionId, options);\n    await this.saveReplay(replay);\n    return replay;\n  }\n\n  async listReplays(filter?: { sessionId?: string }): Promise<HarnessReplayRecord[]> {\n    await this.ensureLayout();\n    const entries = await fs.readdir(this.replaysPath);\n    const replays = await Promise.all(\n      entries\n        .filter((entry) => entry.endsWith('.json'))\n        .map(async (entry) => fs.readJson(path.join(this.replaysPath, entry)) as Promise<HarnessReplayRecord>)\n    );\n\n    const filtered = filter?.sessionId\n      ? replays.filter((replay) => replay.sessionId === filter.sessionId)\n      : replays;\n\n    return filtered.sort((left, right) => right.createdAt.localeCompare(left.createdAt));\n  }\n\n  async getReplay(replayId: string): Promise<HarnessReplayRecord> {\n    return this.readReplay(replayId);\n  }\n}\n"
  },
  {
    "path": "src/services/harness/runtimeStateService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {\n  HarnessRuntimeStateService,\n} from './runtimeStateService';\n\ndescribe('HarnessRuntimeStateService', () => {\n  let tempDir: string;\n  let service: HarnessRuntimeStateService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-runtime-'));\n    service = new HarnessRuntimeStateService({ repoPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('persists sessions, traces, artifacts, checkpoints, and resume state', async () => {\n    const session = await service.createSession({\n      name: 'feature-run',\n      metadata: { source: 'test' },\n    });\n\n    const trace = await service.appendTrace(session.id, {\n      level: 'info',\n      event: 'task.started',\n      message: 'Task started',\n      data: { step: 1 },\n    });\n\n    const artifact = await service.addArtifact(session.id, {\n      name: 'design-note',\n      kind: 'text',\n      content: 'hello world',\n      metadata: { author: 'agent' },\n    });\n\n    const checkpointed = await service.checkpointSession(session.id, {\n      note: 'after first pass',\n      artifactIds: [artifact.id],\n      pause: true,\n      data: { stage: 'review' },\n    });\n\n    const resumed = await service.resumeSession(session.id);\n    const completed = await service.completeSession(session.id, 'done');\n\n    const storedSession = await service.getSession(session.id);\n    const traces = await service.listTraces(session.id);\n    const artifacts = await service.listArtifacts(session.id);\n\n    expect(session.status).toBe('active');\n    expect(trace.event).toBe('task.started');\n    expect(artifact.name).toBe('design-note');\n    expect(checkpointed.checkpoints).toHaveLength(1);\n    expect(resumed.status).toBe('active');\n    expect(completed.status).toBe('completed');\n    expect(storedSession.status).toBe('completed');\n    expect(storedSession.checkpointCount).toBe(1);\n    expect(traces.map((entry) => entry.event)).toEqual([\n      'session.created',\n      'task.started',\n      'artifact.added',\n      'session.paused',\n      'session.resumed',\n      'session.completed',\n    ]);\n    expect(artifacts).toHaveLength(1);\n    expect(artifacts[0].content).toBe('hello world');\n\n    const sessionFile = path.join(tempDir, '.context', 'harness', 'sessions', `${session.id}.json`);\n    const traceFile = path.join(tempDir, '.context', 'harness', 'traces', `${session.id}.jsonl`);\n    const artifactFile = path.join(tempDir, '.context', 'harness', 'artifacts', session.id);\n\n    expect(await fs.pathExists(sessionFile)).toBe(true);\n    expect(await fs.pathExists(traceFile)).toBe(true);\n    expect(await fs.pathExists(artifactFile)).toBe(true);\n  });\n\n  it('lists sessions sorted by recency', async () => {\n    const first = await service.createSession({ name: 'first' });\n    await service.completeSession(first.id);\n    const second = await service.createSession({ name: 'second' });\n\n    const sessions = await service.listSessions();\n    expect(sessions.map((item) => item.name)).toEqual(['second', 'first']);\n  });\n\n  it('rejects resuming completed sessions', async () => {\n    const session = await service.createSession({ name: 'finished' });\n    await service.completeSession(session.id);\n\n    await expect(service.resumeSession(session.id)).rejects.toThrow('Cannot resume a completed session');\n  });\n});\n"
  },
  {
    "path": "src/services/harness/runtimeStateService.ts",
    "content": "/**\n * Harness Runtime State Service\n *\n * Transport-agnostic persistence for sessions, artifacts, traces, and checkpoints.\n * The storage layout lives under .context/harness so future adapters can share state.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\n\nexport type HarnessSessionStatus = 'active' | 'paused' | 'completed' | 'failed';\nexport type HarnessTraceLevel = 'debug' | 'info' | 'warn' | 'error';\nexport type HarnessArtifactKind = 'text' | 'json' | 'file';\n\nexport interface HarnessRuntimeStateServiceOptions {\n  repoPath: string;\n}\n\nexport interface HarnessSessionCheckpoint {\n  id: string;\n  note?: string;\n  data?: unknown;\n  artifactIds: string[];\n  createdAt: string;\n}\n\nexport interface HarnessSessionRecord {\n  id: string;\n  name: string;\n  status: HarnessSessionStatus;\n  repoPath: string;\n  createdAt: string;\n  updatedAt: string;\n  startedAt: string;\n  completedAt?: string;\n  failedAt?: string;\n  lastTraceAt?: string;\n  lastCheckpointAt?: string;\n  traceCount: number;\n  artifactCount: number;\n  checkpointCount: number;\n  checkpoints: HarnessSessionCheckpoint[];\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessTraceRecord {\n  id: string;\n  sessionId: string;\n  level: HarnessTraceLevel;\n  event: string;\n  message: string;\n  createdAt: string;\n  data?: Record<string, unknown>;\n}\n\nexport interface HarnessArtifactRecord {\n  id: string;\n  sessionId: string;\n  name: string;\n  kind: HarnessArtifactKind;\n  createdAt: string;\n  content?: unknown;\n  path?: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessRuntimeStatePort {\n  getSession(sessionId: string): Promise<HarnessSessionRecord>;\n  listSessions(): Promise<HarnessSessionRecord[]>;\n  appendTrace(sessionId: string, input: AppendTraceInput): Promise<HarnessTraceRecord>;\n  listTraces(sessionId: string): Promise<HarnessTraceRecord[]>;\n  addArtifact(sessionId: string, input: AddArtifactInput): Promise<HarnessArtifactRecord>;\n  listArtifacts(sessionId: string): Promise<HarnessArtifactRecord[]>;\n  checkpointSession(sessionId: string, input?: CheckpointInput): Promise<HarnessSessionRecord>;\n  listCheckpoints(sessionId: string): Promise<HarnessSessionCheckpoint[]>;\n}\n\nexport interface CreateSessionInput {\n  name: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface AppendTraceInput {\n  level: HarnessTraceLevel;\n  event: string;\n  message: string;\n  data?: Record<string, unknown>;\n}\n\nexport interface AddArtifactInput {\n  name: string;\n  kind?: HarnessArtifactKind;\n  content?: unknown;\n  path?: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface CheckpointInput {\n  note?: string;\n  data?: unknown;\n  artifactIds?: string[];\n  pause?: boolean;\n}\n\nfunction nowIso(): string {\n  return new Date().toISOString();\n}\n\nfunction normalizeContent(content: unknown): unknown {\n  if (content === undefined) {\n    return undefined;\n  }\n\n  if (typeof content === 'string') {\n    return content;\n  }\n\n  return content;\n}\n\nexport class HarnessRuntimeStateService {\n  constructor(private readonly options: HarnessRuntimeStateServiceOptions) {}\n\n  private get repoPath(): string {\n    return this.options.repoPath || process.cwd();\n  }\n\n  private get contextPath(): string {\n    return path.join(this.repoPath, '.context');\n  }\n\n  private get harnessPath(): string {\n    return path.join(this.contextPath, 'harness');\n  }\n\n  private get sessionsPath(): string {\n    return path.join(this.harnessPath, 'sessions');\n  }\n\n  private get tracesPath(): string {\n    return path.join(this.harnessPath, 'traces');\n  }\n\n  private get artifactsPath(): string {\n    return path.join(this.harnessPath, 'artifacts');\n  }\n\n  private sessionFile(sessionId: string): string {\n    return path.join(this.sessionsPath, `${sessionId}.json`);\n  }\n\n  private traceFile(sessionId: string): string {\n    return path.join(this.tracesPath, `${sessionId}.jsonl`);\n  }\n\n  private artifactFile(sessionId: string, artifactId: string): string {\n    return path.join(this.artifactsPath, sessionId, `${artifactId}.json`);\n  }\n\n  private async ensureLayout(): Promise<void> {\n    await fs.ensureDir(this.sessionsPath);\n    await fs.ensureDir(this.tracesPath);\n    await fs.ensureDir(this.artifactsPath);\n  }\n\n  private async readSession(sessionId: string): Promise<HarnessSessionRecord> {\n    const file = this.sessionFile(sessionId);\n    if (!(await fs.pathExists(file))) {\n      throw new Error(`Harness session not found: ${sessionId}`);\n    }\n\n    return fs.readJson(file) as Promise<HarnessSessionRecord>;\n  }\n\n  private async saveSession(session: HarnessSessionRecord): Promise<void> {\n    await this.ensureLayout();\n    await fs.writeJson(this.sessionFile(session.id), session, { spaces: 2 });\n  }\n\n  private async appendTraceLine(sessionId: string, trace: HarnessTraceRecord): Promise<void> {\n    await this.ensureLayout();\n    await fs.appendFile(this.traceFile(sessionId), `${JSON.stringify(trace)}\\n`, 'utf8');\n  }\n\n  private async recordTrace(sessionId: string, trace: HarnessTraceRecord): Promise<HarnessTraceRecord> {\n    const session = await this.readSession(sessionId);\n    await this.appendTraceLine(sessionId, trace);\n\n    session.traceCount += 1;\n    session.lastTraceAt = trace.createdAt;\n    session.updatedAt = trace.createdAt;\n    await this.saveSession(session);\n\n    return trace;\n  }\n\n  async createSession(input: CreateSessionInput): Promise<HarnessSessionRecord> {\n    const createdAt = nowIso();\n    const session: HarnessSessionRecord = {\n      id: randomUUID(),\n      name: input.name,\n      status: 'active',\n      repoPath: this.repoPath,\n      createdAt,\n      updatedAt: createdAt,\n      startedAt: createdAt,\n      traceCount: 0,\n      artifactCount: 0,\n      checkpointCount: 0,\n      checkpoints: [],\n      metadata: input.metadata,\n    };\n\n    await this.saveSession(session);\n    await this.recordTrace(session.id, {\n      id: randomUUID(),\n      sessionId: session.id,\n      level: 'info',\n      event: 'session.created',\n      message: `Session created: ${input.name}`,\n      createdAt,\n      data: input.metadata ? { metadata: input.metadata } : undefined,\n    });\n\n    return this.readSession(session.id);\n  }\n\n  async listSessions(): Promise<HarnessSessionRecord[]> {\n    await this.ensureLayout();\n    const entries = await fs.readdir(this.sessionsPath);\n    const sessions = await Promise.all(\n      entries\n        .filter((entry) => entry.endsWith('.json'))\n        .map(async (entry) => fs.readJson(path.join(this.sessionsPath, entry)) as Promise<HarnessSessionRecord>)\n    );\n\n    return sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n  }\n\n  async getSession(sessionId: string): Promise<HarnessSessionRecord> {\n    return this.readSession(sessionId);\n  }\n\n  async appendTrace(sessionId: string, input: AppendTraceInput): Promise<HarnessTraceRecord> {\n    const createdAt = nowIso();\n    const trace: HarnessTraceRecord = {\n      id: randomUUID(),\n      sessionId,\n      level: input.level,\n      event: input.event,\n      message: input.message,\n      createdAt,\n      data: input.data,\n    };\n\n    return this.recordTrace(sessionId, trace);\n  }\n\n  async addArtifact(sessionId: string, input: AddArtifactInput): Promise<HarnessArtifactRecord> {\n    const session = await this.readSession(sessionId);\n    const createdAt = nowIso();\n    const artifact: HarnessArtifactRecord = {\n      id: randomUUID(),\n      sessionId,\n      name: input.name,\n      kind: input.kind || 'text',\n      createdAt,\n      content: normalizeContent(input.content),\n      path: input.path,\n      metadata: input.metadata,\n    };\n\n    await fs.ensureDir(path.dirname(this.artifactFile(sessionId, artifact.id)));\n    await fs.writeJson(this.artifactFile(sessionId, artifact.id), artifact, { spaces: 2 });\n\n    session.artifactCount += 1;\n    session.updatedAt = createdAt;\n    await this.saveSession(session);\n    await this.recordTrace(sessionId, {\n      id: randomUUID(),\n      sessionId,\n      level: 'info',\n      event: 'artifact.added',\n      message: `Artifact recorded: ${input.name}`,\n      createdAt,\n      data: {\n        artifactId: artifact.id,\n        kind: artifact.kind,\n        path: artifact.path,\n      },\n    });\n\n    return artifact;\n  }\n\n  async listCheckpoints(sessionId: string): Promise<HarnessSessionCheckpoint[]> {\n    const session = await this.readSession(sessionId);\n    return [...session.checkpoints].sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n  }\n\n  async listArtifacts(sessionId: string): Promise<HarnessArtifactRecord[]> {\n    const dir = path.join(this.artifactsPath, sessionId);\n    if (!(await fs.pathExists(dir))) {\n      return [];\n    }\n\n    const entries = await fs.readdir(dir);\n    const artifacts = await Promise.all(\n      entries\n        .filter((entry) => entry.endsWith('.json'))\n        .map(async (entry) => fs.readJson(path.join(dir, entry)) as Promise<HarnessArtifactRecord>)\n    );\n\n    return artifacts.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n  }\n\n  async listTraces(sessionId: string): Promise<HarnessTraceRecord[]> {\n    const file = this.traceFile(sessionId);\n    if (!(await fs.pathExists(file))) {\n      return [];\n    }\n\n    const content = await fs.readFile(file, 'utf8');\n    return content\n      .split('\\n')\n      .map((line) => line.trim())\n      .filter(Boolean)\n      .map((line) => JSON.parse(line) as HarnessTraceRecord);\n  }\n\n  async checkpointSession(sessionId: string, input: CheckpointInput = {}): Promise<HarnessSessionRecord> {\n    const session = await this.readSession(sessionId);\n    const createdAt = nowIso();\n    const checkpoint: HarnessSessionCheckpoint = {\n      id: randomUUID(),\n      note: input.note,\n      data: input.data,\n      artifactIds: input.artifactIds || [],\n      createdAt,\n    };\n\n    session.checkpoints.push(checkpoint);\n    session.checkpointCount = session.checkpoints.length;\n    session.lastCheckpointAt = createdAt;\n    session.updatedAt = createdAt;\n    session.status = input.pause ? 'paused' : session.status;\n\n    await this.saveSession(session);\n    await this.recordTrace(sessionId, {\n      id: randomUUID(),\n      sessionId,\n      level: 'info',\n      event: input.pause ? 'session.paused' : 'session.checkpointed',\n      message: input.note ? `Checkpoint recorded: ${input.note}` : 'Checkpoint recorded',\n      createdAt,\n      data: {\n        checkpointId: checkpoint.id,\n        artifactIds: checkpoint.artifactIds,\n        payload: input.data,\n      },\n    });\n\n    return this.readSession(sessionId);\n  }\n\n  async resumeSession(sessionId: string): Promise<HarnessSessionRecord> {\n    const session = await this.readSession(sessionId);\n    const createdAt = nowIso();\n\n    if (session.status === 'completed' || session.status === 'failed') {\n      throw new Error(`Cannot resume a ${session.status} session: ${sessionId}`);\n    }\n\n    session.status = 'active';\n    session.updatedAt = createdAt;\n    await this.saveSession(session);\n    await this.recordTrace(sessionId, {\n      id: randomUUID(),\n      sessionId,\n      level: 'info',\n      event: 'session.resumed',\n      message: 'Session resumed',\n      createdAt,\n    });\n\n    return this.readSession(sessionId);\n  }\n\n  async completeSession(sessionId: string, note?: string): Promise<HarnessSessionRecord> {\n    const session = await this.readSession(sessionId);\n    const createdAt = nowIso();\n    session.status = 'completed';\n    session.completedAt = createdAt;\n    session.updatedAt = createdAt;\n    await this.saveSession(session);\n    await this.recordTrace(sessionId, {\n      id: randomUUID(),\n      sessionId,\n      level: 'info',\n      event: 'session.completed',\n      message: note ? `Session completed: ${note}` : 'Session completed',\n      createdAt,\n    });\n    return this.readSession(sessionId);\n  }\n\n  async failSession(sessionId: string, message: string): Promise<HarnessSessionRecord> {\n    const session = await this.readSession(sessionId);\n    const createdAt = nowIso();\n    session.status = 'failed';\n    session.failedAt = createdAt;\n    session.updatedAt = createdAt;\n    await this.saveSession(session);\n    await this.recordTrace(sessionId, {\n      id: randomUUID(),\n      sessionId,\n      level: 'error',\n      event: 'session.failed',\n      message,\n      createdAt,\n    });\n    return this.readSession(sessionId);\n  }\n}\n"
  },
  {
    "path": "src/services/harness/sensorCatalogService.ts",
    "content": "/**\n * Harness Sensor Catalog Service\n *\n * Bootstraps a user-editable sensor catalog under .context/harness/sensors.json\n * and resolves the effective shell-based sensors for workflow/harness runtime.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { StackDetector, type StackInfo } from '../stack/stackDetector';\n\nexport type HarnessSensorCatalogSeverity = 'critical' | 'warning' | 'info';\n\nexport interface HarnessShellSensorConfig {\n  id: string;\n  name: string;\n  description?: string;\n  severity: HarnessSensorCatalogSeverity;\n  blocking?: boolean;\n  enabled?: boolean;\n  command: string;\n  script?: string;\n}\n\nexport interface HarnessSensorCatalogDocument {\n  version: 1;\n  generatedAt: string;\n  source: 'bootstrap' | 'manual';\n  stack?: {\n    primaryLanguage: string | null;\n    languages: string[];\n    frameworks: string[];\n    buildTools: string[];\n    testFrameworks: string[];\n    packageManager: string | null;\n  };\n  sensors: HarnessShellSensorConfig[];\n}\n\nexport interface HarnessSensorCatalogServiceOptions {\n  repoPath: string;\n  contextPath?: string;\n}\n\nexport class HarnessSensorCatalogService {\n  constructor(private readonly options: HarnessSensorCatalogServiceOptions) {}\n\n  private get repoPath(): string {\n    return path.resolve(this.options.repoPath);\n  }\n\n  private get contextPath(): string {\n    return this.options.contextPath\n      ? path.resolve(this.options.contextPath)\n      : path.join(this.repoPath, '.context');\n  }\n\n  get configPath(): string {\n    return path.join(this.contextPath, 'harness', 'sensors.json');\n  }\n\n  async bootstrap(force: boolean = false): Promise<HarnessSensorCatalogDocument> {\n    const existing = await this.load();\n    if (existing && !force) {\n      return existing;\n    }\n\n    const stack = await new StackDetector().detect(this.repoPath);\n    const document: HarnessSensorCatalogDocument = {\n      version: 1,\n      generatedAt: new Date().toISOString(),\n      source: 'bootstrap',\n      stack: this.serializeStack(stack),\n      sensors: this.detectSensorsSync(),\n    };\n\n    await fs.ensureDir(path.dirname(this.configPath));\n    await fs.writeJson(this.configPath, document, { spaces: 2 });\n    return document;\n  }\n\n  async load(): Promise<HarnessSensorCatalogDocument | null> {\n    if (!(await fs.pathExists(this.configPath))) {\n      return null;\n    }\n\n    return fs.readJson(this.configPath) as Promise<HarnessSensorCatalogDocument>;\n  }\n\n  loadSync(): HarnessSensorCatalogDocument | null {\n    if (!fs.existsSync(this.configPath)) {\n      return null;\n    }\n\n    return fs.readJsonSync(this.configPath) as HarnessSensorCatalogDocument;\n  }\n\n  resolveEffectiveSensorsSync(): HarnessShellSensorConfig[] {\n    const document = this.loadSync();\n    const sourceSensors = document?.sensors ?? this.detectSensorsSync();\n    return sourceSensors\n      .filter((sensor) => sensor.enabled !== false)\n      .map((sensor) => ({\n        ...sensor,\n        blocking: sensor.blocking ?? sensor.severity === 'critical',\n      }));\n  }\n\n  detectSensorsSync(): HarnessShellSensorConfig[] {\n    const packageJsonPath = path.join(this.repoPath, 'package.json');\n    const hasPackageJson = fs.existsSync(packageJsonPath);\n    const hasPyProject = fs.existsSync(path.join(this.repoPath, 'pyproject.toml'));\n    const hasRequirements = fs.existsSync(path.join(this.repoPath, 'requirements.txt'));\n    const hasSetupPy = fs.existsSync(path.join(this.repoPath, 'setup.py'));\n    const hasGoMod = fs.existsSync(path.join(this.repoPath, 'go.mod'));\n    const hasCargoToml = fs.existsSync(path.join(this.repoPath, 'Cargo.toml'));\n    const hasMaven = fs.existsSync(path.join(this.repoPath, 'pom.xml'));\n    const hasGradle = fs.existsSync(path.join(this.repoPath, 'build.gradle'))\n      || fs.existsSync(path.join(this.repoPath, 'build.gradle.kts'))\n      || fs.existsSync(path.join(this.repoPath, 'gradlew'));\n\n    if (hasPackageJson) {\n      return this.detectNodeSensors(packageJsonPath);\n    }\n\n    if (hasGoMod) {\n      return [\n        {\n          id: 'build',\n          name: 'Build',\n          description: 'Compile Go packages',\n          severity: 'critical',\n          command: 'go build ./...',\n        },\n        {\n          id: 'test',\n          name: 'Test',\n          description: 'Run Go test suite',\n          severity: 'critical',\n          command: 'go test ./...',\n        },\n        {\n          id: 'lint',\n          name: 'Vet',\n          description: 'Run go vet static checks',\n          severity: 'warning',\n          command: 'go vet ./...',\n        },\n      ];\n    }\n\n    if (hasCargoToml) {\n      return [\n        {\n          id: 'build',\n          name: 'Build',\n          description: 'Compile Rust crates',\n          severity: 'critical',\n          command: 'cargo build',\n        },\n        {\n          id: 'test',\n          name: 'Test',\n          description: 'Run Rust test suite',\n          severity: 'critical',\n          command: 'cargo test',\n        },\n      ];\n    }\n\n    if (hasMaven) {\n      return [\n        {\n          id: 'build',\n          name: 'Build',\n          description: 'Build Maven project without tests',\n          severity: 'critical',\n          command: 'mvn -q -DskipTests package',\n        },\n        {\n          id: 'test',\n          name: 'Test',\n          description: 'Run Maven test suite',\n          severity: 'critical',\n          command: 'mvn -q test',\n        },\n      ];\n    }\n\n    if (hasGradle) {\n      return [\n        {\n          id: 'build',\n          name: 'Build',\n          description: 'Build Gradle project without tests',\n          severity: 'critical',\n          command: './gradlew build -x test',\n        },\n        {\n          id: 'test',\n          name: 'Test',\n          description: 'Run Gradle test suite',\n          severity: 'critical',\n          command: './gradlew test',\n        },\n      ];\n    }\n\n    if (hasPyProject || hasRequirements || hasSetupPy) {\n      const sensors: HarnessShellSensorConfig[] = [\n        {\n          id: 'test',\n          name: 'Test',\n          description: 'Run Python test suite with pytest',\n          severity: 'critical',\n          command: 'python -m pytest',\n        },\n      ];\n\n      const hasMypyConfig = fs.existsSync(path.join(this.repoPath, 'mypy.ini'))\n        || fs.existsSync(path.join(this.repoPath, '.mypy.ini'));\n      const hasRuffConfig = fs.existsSync(path.join(this.repoPath, 'ruff.toml'))\n        || fs.existsSync(path.join(this.repoPath, '.ruff.toml'));\n      const hasFlake8Config = fs.existsSync(path.join(this.repoPath, '.flake8'))\n        || fs.existsSync(path.join(this.repoPath, 'setup.cfg'));\n\n      if (hasMypyConfig) {\n        sensors.push({\n          id: 'typecheck',\n          name: 'Typecheck',\n          description: 'Run mypy type checks',\n          severity: 'critical',\n          command: 'python -m mypy .',\n        });\n      }\n\n      if (hasRuffConfig) {\n        sensors.push({\n          id: 'lint',\n          name: 'Lint',\n          description: 'Run Ruff lint checks',\n          severity: 'warning',\n          command: 'python -m ruff check .',\n        });\n      } else if (hasFlake8Config) {\n        sensors.push({\n          id: 'lint',\n          name: 'Lint',\n          description: 'Run Flake8 lint checks',\n          severity: 'warning',\n          command: 'python -m flake8 .',\n        });\n      }\n\n      return sensors;\n    }\n\n    return [];\n  }\n\n  private detectNodeSensors(packageJsonPath: string): HarnessShellSensorConfig[] {\n    const packageJson = fs.readJsonSync(packageJsonPath) as { scripts?: Record<string, string> };\n    const scripts = packageJson.scripts || {};\n    const sensors: HarnessShellSensorConfig[] = [];\n\n    if (scripts.build) {\n      sensors.push({\n        id: 'build',\n        name: 'Build',\n        description: 'Run package build script',\n        severity: 'critical',\n        command: 'npm run build',\n        script: 'build',\n      });\n    }\n\n    if (scripts.test) {\n      sensors.push({\n        id: 'test',\n        name: 'Test',\n        description: 'Run package test script',\n        severity: 'critical',\n        command: 'npm test -- --runInBand',\n        script: 'test',\n      });\n    }\n\n    if (scripts.lint) {\n      sensors.push({\n        id: 'lint',\n        name: 'Lint',\n        description: 'Run package lint script',\n        severity: 'warning',\n        command: 'npm run lint',\n        script: 'lint',\n      });\n    }\n\n    if (scripts.typecheck) {\n      sensors.push({\n        id: 'typecheck',\n        name: 'Typecheck',\n        description: 'Run package typecheck script',\n        severity: 'critical',\n        command: 'npm run typecheck',\n        script: 'typecheck',\n      });\n    }\n\n    if (scripts.test && fs.existsSync(path.join(this.repoPath, 'src', 'tests', 'integrity', 'postRefactoringIntegrity.test.ts'))) {\n      sensors.push({\n        id: 'structural',\n        name: 'Structural Integrity',\n        description: 'Run repository structural integrity suite',\n        severity: 'critical',\n        command: 'npm test -- --runInBand --runTestsByPath src/tests/integrity/postRefactoringIntegrity.test.ts',\n      });\n    }\n\n    return sensors;\n  }\n\n  private serializeStack(stack: StackInfo): HarnessSensorCatalogDocument['stack'] {\n    return {\n      primaryLanguage: stack.primaryLanguage,\n      languages: stack.languages,\n      frameworks: stack.frameworks,\n      buildTools: stack.buildTools,\n      testFrameworks: stack.testFrameworks,\n      packageManager: stack.packageManager,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/sensorsService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { HarnessRuntimeStateService } from './runtimeStateService';\nimport { HarnessSensorsService } from './sensorsService';\n\ndescribe('HarnessSensorsService', () => {\n  let tempDir: string;\n  let stateService: HarnessRuntimeStateService;\n  let service: HarnessSensorsService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-sensors-'));\n    stateService = new HarnessRuntimeStateService({ repoPath: tempDir });\n    service = new HarnessSensorsService({ stateService });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('runs sensors and records traces', async () => {\n    service.registerSensor({\n      id: 'typecheck',\n      name: 'Typecheck',\n      severity: 'critical',\n      execute: async () => ({\n        status: 'failed',\n        summary: 'Type errors found',\n        evidence: ['tsc output'],\n      }),\n    });\n\n    const session = await stateService.createSession({ name: 'quality-run' });\n    const run = await service.runSensor('typecheck', { sessionId: session.id });\n    const traces = await stateService.listTraces(session.id);\n    const storedRuns = await service.getSessionSensorRuns(session.id);\n\n    expect(run.status).toBe('failed');\n    expect(traces.some((trace) => trace.event === 'sensor.run')).toBe(true);\n    expect(storedRuns).toHaveLength(1);\n    expect(storedRuns[0].sensorId).toBe('typecheck');\n    expect(service.evaluateBackpressure([run]).blocked).toBe(true);\n  });\n});\n\n"
  },
  {
    "path": "src/services/harness/sensorsService.ts",
    "content": "/**\n * Harness Sensors Service\n *\n * Registers and executes quality sensors, then persists the run as a trace\n * entry in the shared harness runtime state.\n */\n\nimport { randomUUID } from 'crypto';\nimport type {\n  AppendTraceInput,\n  HarnessRuntimeStatePort,\n  HarnessTraceRecord,\n} from './runtimeStateService';\n\nexport type HarnessSensorSeverity = 'info' | 'warning' | 'critical';\nexport type HarnessSensorStatus = 'passed' | 'failed' | 'skipped' | 'blocked';\n\nexport interface HarnessSensorExecutionInput {\n  sessionId: string;\n  contractId?: string;\n  context?: unknown;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessSensorExecutionResult {\n  status: HarnessSensorStatus;\n  summary: string;\n  evidence?: string[];\n  output?: unknown;\n  details?: Record<string, unknown>;\n}\n\nexport interface HarnessSensorDefinition {\n  id: string;\n  name: string;\n  description?: string;\n  severity?: HarnessSensorSeverity;\n  blocking?: boolean;\n  execute: (input: HarnessSensorExecutionInput) => Promise<HarnessSensorExecutionResult> | HarnessSensorExecutionResult;\n}\n\nexport interface HarnessSensorRun extends HarnessSensorExecutionResult {\n  id: string;\n  sensorId: string;\n  sessionId: string;\n  contractId?: string;\n  severity: HarnessSensorSeverity;\n  blocking: boolean;\n  createdAt: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessBackpressurePolicy {\n  blockOnWarnings?: boolean;\n  requireEvidence?: boolean;\n}\n\nexport interface HarnessBackpressureResult {\n  blocked: boolean;\n  reasons: string[];\n  blockingRuns: HarnessSensorRun[];\n}\n\nexport interface HarnessSensorsServiceOptions {\n  stateService: HarnessRuntimeStatePort;\n}\n\nexport class HarnessSensorsService {\n  private readonly definitions = new Map<string, HarnessSensorDefinition>();\n\n  constructor(private readonly options: HarnessSensorsServiceOptions) {}\n\n  registerSensor(definition: HarnessSensorDefinition): HarnessSensorDefinition {\n    this.definitions.set(definition.id, definition);\n    return definition;\n  }\n\n  listSensors(): HarnessSensorDefinition[] {\n    return [...this.definitions.values()].sort((a, b) => a.id.localeCompare(b.id));\n  }\n\n  getSensor(sensorId: string): HarnessSensorDefinition | undefined {\n    return this.definitions.get(sensorId);\n  }\n\n  async runSensor(\n    sensorId: string,\n    input: HarnessSensorExecutionInput\n  ): Promise<HarnessSensorRun> {\n    const definition = this.getSensor(sensorId);\n    if (!definition) {\n      throw new Error(`Sensor not found: ${sensorId}`);\n    }\n\n    const createdAt = new Date().toISOString();\n    const result = await definition.execute(input);\n    const severity = definition.severity ?? 'critical';\n    const run: HarnessSensorRun = {\n      id: randomUUID(),\n      sensorId,\n      sessionId: input.sessionId,\n      contractId: input.contractId,\n      severity,\n      blocking: definition.blocking ?? severity === 'critical',\n      createdAt,\n      status: result.status,\n      summary: result.summary,\n      evidence: result.evidence,\n      output: result.output,\n      details: result.details,\n      metadata: input.metadata,\n    };\n\n    const traceInput: AppendTraceInput = {\n      level: result.status === 'passed' ? 'info' : result.status === 'skipped' ? 'debug' : result.status === 'blocked' ? 'warn' : severity === 'critical' ? 'error' : 'warn',\n      event: 'sensor.run',\n      message: `${definition.name}: ${result.summary}`,\n      data: { run },\n    };\n\n    await this.options.stateService.appendTrace(input.sessionId, traceInput);\n    return run;\n  }\n\n  async getSessionSensorRuns(sessionId: string): Promise<HarnessSensorRun[]> {\n    const traces = await this.options.stateService.listTraces(sessionId);\n    return traces\n      .filter((trace) => trace.event === 'sensor.run' && trace.data?.run)\n      .map((trace) => trace.data!.run as HarnessSensorRun);\n  }\n\n  evaluateBackpressure(\n    runs: HarnessSensorRun[],\n    policy: HarnessBackpressurePolicy = {}\n  ): HarnessBackpressureResult {\n    const blockingRuns = runs.filter((run) => {\n      if (run.status === 'blocked') {\n        return true;\n      }\n      if (run.status === 'failed' && run.blocking) {\n        return true;\n      }\n      if (policy.blockOnWarnings && run.severity === 'warning' && run.status !== 'passed') {\n        return true;\n      }\n      if (policy.requireEvidence && run.status !== 'passed' && (!run.evidence || run.evidence.length === 0)) {\n        return true;\n      }\n      return false;\n    });\n\n    return {\n      blocked: blockingRuns.length > 0,\n      reasons: blockingRuns.map((run) => `${run.sensorId}: ${run.summary}`),\n      blockingRuns,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/skillsService.ts",
    "content": "/**\n * Harness Skills Service\n *\n * Transport-agnostic skill discovery, export, and fill orchestration logic.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { VERSION } from '../../version';\nimport { PHASE_NAMES_EN, type PrevcPhase } from '../../workflow';\nimport { getOrBuildContext } from './contextTools';\nimport { minimalUI, mockTranslate } from '../shared';\nimport { needsFill } from '../../utils/frontMatter';\n\nexport interface HarnessSkillsServiceOptions {\n  repoPath: string;\n}\n\nfunction getSkillFillInstructions(skillSlug: string): string {\n  const instructions: Record<string, string> = {\n    'commit-message': `Fill this skill with:\n- Commit message format conventions for this project\n- Examples of good commit messages from the codebase\n- Branch naming conventions if applicable\n- Semantic versioning guidelines`,\n    'pr-review': `Fill this skill with:\n- PR review checklist specific to this codebase\n- Code quality standards to check\n- Testing requirements before merge\n- Documentation expectations`,\n    'code-review': `Fill this skill with:\n- Code review guidelines for this project\n- Common patterns to look for\n- Security and performance considerations\n- Style and convention checks`,\n    'test-generation': `Fill this skill with:\n- Testing framework and conventions used\n- Test file organization patterns\n- Mocking strategies for this codebase\n- Coverage requirements`,\n    'documentation': `Fill this skill with:\n- Documentation standards for this project\n- JSDoc/TSDoc conventions\n- README structure expectations\n- API documentation guidelines`,\n    'refactoring': `Fill this skill with:\n- Refactoring patterns common in this codebase\n- Code smell detection guidelines\n- Safe refactoring procedures\n- Testing requirements after refactoring`,\n    'bug-investigation': `Fill this skill with:\n- Debugging workflow for this codebase\n- Common bug patterns and their fixes\n- Logging and error handling conventions\n- Test verification steps`,\n    'feature-breakdown': `Fill this skill with:\n- Feature decomposition approach\n- Task estimation guidelines\n- Dependency identification process\n- Integration points to consider`,\n    'api-design': `Fill this skill with:\n- API design patterns used in this project\n- Endpoint naming conventions\n- Request/response format standards\n- Versioning and deprecation policies`,\n    'security-audit': `Fill this skill with:\n- Security checklist for this codebase\n- Common vulnerabilities to check\n- Authentication/authorization patterns\n- Data validation requirements`,\n  };\n\n  return `${instructions[skillSlug] || `Fill this skill with project-specific content for ${skillSlug}:\n- Identify relevant patterns from the codebase\n- Include specific examples from the project\n- Add conventions and best practices\n- Reference important files and components`}\n\nThen apply these writing constraints:\n- Keep activation language in the frontmatter description instead of adding a \"When to Use\" section in the body\n- Write the body in imperative form\n- Keep the body concise and split large details into \\`references/\\` only when needed\n- Add \\`scripts/\\` or \\`assets/\\` only if they materially reduce repeated work\n- Do not create extra docs such as README, quick-reference, or changelog files inside the skill folder`;\n}\n\nfunction buildSkillFillPrompt(\n  skills: Array<{\n    skillPath: string;\n    skillSlug: string;\n    skillName: string;\n    description: string;\n    instructions: string;\n  }>,\n  semanticContext?: string\n): string {\n  const lines: string[] = [];\n  lines.push('# Skill Fill Instructions', '', 'You MUST fill each of the following skill files with codebase-specific content.', '');\n\n  if (semanticContext) {\n    lines.push('## Codebase Context', '', 'Use this semantic context to understand the codebase:', '', '```');\n    lines.push(semanticContext.length > 6000 ? semanticContext.substring(0, 6000) + '\\n...(truncated)' : semanticContext);\n    lines.push('```', '');\n  }\n\n  lines.push('## Skills to Fill', '');\n\n  for (const skill of skills) {\n    lines.push(`### ${skill.skillName} (${skill.skillSlug})`);\n    lines.push(`**Path:** \\`${skill.skillPath}\\``);\n    if (skill.description) lines.push(`**Description:** ${skill.description}`);\n    lines.push('', '**Fill Instructions:**', skill.instructions, '');\n  }\n\n  lines.push(\n    '## Action Required',\n    '',\n    'For each skill listed above:',\n    '1. Read the current skill template',\n    '2. Generate codebase-specific content based on the instructions and context',\n    '3. Write the filled content to the skill file',\n    '',\n    'Each skill should be personalized with:',\n    '- Specific examples from this codebase',\n    '- Project-specific conventions and patterns',\n    '- References to relevant files and components',\n    '- Concise instructions written in imperative form',\n    '- Progressive disclosure: keep SKILL.md lean and move large details to references only when necessary',\n    '',\n    'Hard requirements:',\n    '- Preserve the existing internal scaffold frontmatter',\n    '- Do not add a \"When to Use\" section in the body; improve the description if trigger wording is missing',\n    '- Avoid creating extra documentation files inside the skill folder',\n    '',\n    'DO NOT leave placeholder text. Each skill must have meaningful, project-specific content.'\n  );\n\n  return lines.join('\\n');\n}\n\nexport class HarnessSkillsService {\n  constructor(private readonly options: HarnessSkillsServiceOptions) {}\n\n  private get repoPath(): string {\n    return this.options.repoPath || process.cwd();\n  }\n\n  private getRegistry() {\n    const { createSkillRegistry, BUILT_IN_SKILLS } = require('../../workflow/skills');\n    return { createSkillRegistry, BUILT_IN_SKILLS };\n  }\n\n  async list(includeContent?: boolean): Promise<Record<string, unknown>> {\n    const { createSkillRegistry } = this.getRegistry();\n    const registry = createSkillRegistry(this.repoPath);\n    const discovered = await registry.discoverAll();\n\n    const format = (skill: any) => ({\n      slug: skill.slug,\n      name: skill.metadata.name,\n      description: skill.metadata.description,\n      phases: skill.metadata.phases || [],\n      isBuiltIn: skill.isBuiltIn,\n      ...(includeContent ? { content: skill.content } : {}),\n    });\n\n    return {\n      success: true,\n      totalSkills: discovered.all.length,\n      builtInCount: discovered.builtIn.length,\n      customCount: discovered.custom.length,\n      skills: {\n        builtIn: discovered.builtIn.map(format),\n        custom: discovered.custom.map(format),\n      },\n    };\n  }\n\n  async getContent(skillSlug: string): Promise<Record<string, unknown>> {\n    const { createSkillRegistry, BUILT_IN_SKILLS } = this.getRegistry();\n    const registry = createSkillRegistry(this.repoPath);\n    const content = await registry.getSkillContent(skillSlug);\n\n    if (!content) {\n      return {\n        success: false,\n        error: `Skill not found: ${skillSlug}`,\n        availableSkills: BUILT_IN_SKILLS,\n      };\n    }\n\n    const skill = await registry.getSkillMetadata(skillSlug);\n    return {\n      success: true,\n      skill: {\n        slug: skillSlug,\n        name: skill?.metadata.name,\n        description: skill?.metadata.description,\n        phases: skill?.metadata.phases,\n        isBuiltIn: skill?.isBuiltIn,\n      },\n      content,\n    };\n  }\n\n  async getForPhase(phase: PrevcPhase): Promise<Record<string, unknown>> {\n    const { createSkillRegistry } = this.getRegistry();\n    const registry = createSkillRegistry(this.repoPath);\n    const skills = await registry.getSkillsForPhase(phase);\n\n    return {\n      success: true,\n      phase,\n      phaseName: PHASE_NAMES_EN[phase],\n      skills: skills.map((s: any) => ({\n        slug: s.slug,\n        name: s.metadata.name,\n        description: s.metadata.description,\n        isBuiltIn: s.isBuiltIn,\n      })),\n      count: skills.length,\n    };\n  }\n\n  async scaffold(params: {\n    skills?: string[];\n    includeBuiltIn?: boolean;\n  }): Promise<Record<string, unknown>> {\n    const { createSkillGenerator } = require('../../generators/skills');\n    const generator = createSkillGenerator({ repoPath: this.repoPath });\n    const result = await generator.generate({ skills: params.skills, force: params.includeBuiltIn });\n\n    return {\n      success: true,\n      skillsDir: result.skillsDir,\n      generated: result.generatedSkills,\n      skipped: result.skippedSkills,\n      indexPath: result.indexPath,\n    };\n  }\n\n  async export(params: {\n    preset?: string;\n    includeBuiltIn?: boolean;\n  }): Promise<Record<string, unknown>> {\n    const { SkillExportService } = require('../export/skillExportService');\n    const exportService = new SkillExportService({\n      ui: minimalUI,\n      t: mockTranslate,\n      version: VERSION,\n    });\n\n    const result = await exportService.run(this.repoPath, {\n      preset: params.preset,\n      includeBuiltIn: params.includeBuiltIn,\n      force: false,\n    });\n\n    return {\n      success: result.filesCreated > 0,\n      targets: result.targets,\n      skillsExported: result.skillsExported,\n      filesCreated: result.filesCreated,\n      filesSkipped: result.filesSkipped,\n    };\n  }\n\n  async fill(params: {\n    skills?: string[];\n    includeBuiltIn?: boolean;\n  }): Promise<Record<string, unknown>> {\n    const { createSkillRegistry } = this.getRegistry();\n    const registry = createSkillRegistry(this.repoPath);\n    const skillsDir = path.join(this.repoPath, '.context', 'skills');\n\n    if (!await fs.pathExists(skillsDir)) {\n      return {\n        success: false,\n        error: 'Skills directory does not exist. Run skill({ action: \"scaffold\" }) first.',\n        skillsDir,\n      };\n    }\n\n    const discovered = await registry.discoverAll();\n    let skillsToFill = discovered.all;\n\n    if (params.skills && params.skills.length > 0) {\n      skillsToFill = skillsToFill.filter((s: { slug: string }) => params.skills!.includes(s.slug));\n    }\n\n    if (!params.includeBuiltIn) {\n      const filtered = await Promise.all(\n        skillsToFill.map(async (skill: { path: string }) => ({\n          skill,\n          shouldFill: !await fs.pathExists(skill.path) || await needsFill(skill.path),\n        }))\n      );\n      skillsToFill = filtered.filter(entry => entry.shouldFill).map(entry => entry.skill);\n    }\n\n    if (skillsToFill.length === 0) {\n      return {\n        success: true,\n        message: 'No skills need filling. Use includeBuiltIn: true to refill existing skills.',\n        skillsDir,\n      };\n    }\n\n    let semanticContext: string | undefined;\n    try {\n      semanticContext = await getOrBuildContext(this.repoPath);\n    } catch {\n      semanticContext = undefined;\n    }\n\n    const fillInstructions = skillsToFill.map((skill: { path: string; slug: string; metadata: { name?: string; description?: string }; isBuiltIn: boolean }) => ({\n      skillPath: skill.path,\n      skillSlug: skill.slug,\n      skillName: skill.metadata.name || skill.slug,\n      description: skill.metadata.description || '',\n      isBuiltIn: skill.isBuiltIn,\n      instructions: getSkillFillInstructions(skill.slug),\n    }));\n\n    return {\n      success: true,\n      skillsToFill: fillInstructions,\n      semanticContext,\n      fillPrompt: buildSkillFillPrompt(fillInstructions, semanticContext),\n      instructions: 'IMPORTANT: You MUST now fill each skill file using the semantic context and fill instructions provided. Write the content to each skillPath.',\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/taskContractsService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { HarnessRuntimeStateService } from './runtimeStateService';\nimport { HarnessSensorsService } from './sensorsService';\nimport { HarnessTaskContractsService } from './taskContractsService';\n\ndescribe('HarnessTaskContractsService', () => {\n  let tempDir: string;\n  let stateService: HarnessRuntimeStateService;\n  let sensorsService: HarnessSensorsService;\n  let service: HarnessTaskContractsService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-contracts-'));\n    stateService = new HarnessRuntimeStateService({ repoPath: tempDir });\n    sensorsService = new HarnessSensorsService({ stateService });\n    service = new HarnessTaskContractsService({ repoPath: tempDir, stateService });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('persists contracts and evaluates completion against sensors and artifacts', async () => {\n    sensorsService.registerSensor({\n      id: 'build',\n      name: 'Build',\n      severity: 'critical',\n      execute: async () => ({\n        status: 'passed',\n        summary: 'Build succeeded',\n        evidence: ['build log'],\n      }),\n    });\n\n    const session = await stateService.createSession({ name: 'task-run' });\n    await stateService.addArtifact(session.id, {\n      name: 'plans/alpha.md',\n      kind: 'file',\n      path: 'plans/alpha.md',\n    });\n    await sensorsService.runSensor('build', { sessionId: session.id });\n\n    const task = await service.createTaskContract({\n      title: 'Implement alpha',\n      sessionId: session.id,\n      requiredSensors: ['build'],\n      requiredArtifacts: ['plans/alpha.md'],\n      expectedOutputs: ['src/alpha.ts'],\n      acceptanceCriteria: ['build passes'],\n    });\n\n    const evaluation = await service.evaluateTaskCompletion(task.id, session.id);\n    const tasks = await service.listTaskContracts();\n    const handoff = await service.createHandoffContract({\n      from: 'planner',\n      to: 'executor',\n      sessionId: session.id,\n      taskId: task.id,\n      artifacts: ['plans/alpha.md'],\n      evidence: ['build log'],\n    });\n\n    expect(evaluation.canComplete).toBe(true);\n    expect(evaluation.missingSensors).toHaveLength(0);\n    expect(evaluation.missingArtifacts).toHaveLength(0);\n    expect(tasks).toHaveLength(1);\n    expect(tasks[0].title).toBe('Implement alpha');\n    expect(handoff.from).toBe('planner');\n\n    const contractPath = path.join(tempDir, '.context', 'harness', 'contracts', 'tasks', `${task.id}.json`);\n    expect(await fs.pathExists(contractPath)).toBe(true);\n  });\n});\n\n"
  },
  {
    "path": "src/services/harness/taskContractsService.ts",
    "content": "/**\n * Harness Task Contracts Service\n *\n * Persists structured task and handoff contracts and evaluates whether a task\n * can be completed based on required sensors and artifacts.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\nimport type {\n  HarnessArtifactRecord,\n  HarnessRuntimeStatePort,\n  HarnessTraceRecord,\n} from './runtimeStateService';\nimport type { HarnessSensorRun } from './sensorsService';\n\nexport type HarnessTaskContractStatus =\n  | 'draft'\n  | 'ready'\n  | 'in_progress'\n  | 'blocked'\n  | 'completed'\n  | 'failed';\n\nexport interface HarnessTaskContract {\n  id: string;\n  title: string;\n  description?: string;\n  sessionId?: string;\n  owner?: string;\n  status: HarnessTaskContractStatus;\n  inputs: string[];\n  expectedOutputs: string[];\n  acceptanceCriteria: string[];\n  requiredSensors: string[];\n  requiredArtifacts: string[];\n  createdAt: string;\n  updatedAt: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessHandoffContract {\n  id: string;\n  from: string;\n  to: string;\n  sessionId?: string;\n  taskId?: string;\n  artifacts: string[];\n  evidence: string[];\n  createdAt: string;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface HarnessTaskCompletionResult {\n  taskId: string;\n  sessionId?: string;\n  canComplete: boolean;\n  missingSensors: string[];\n  missingArtifacts: string[];\n  blockingFindings: string[];\n  matchedSensorRuns: HarnessSensorRun[];\n  matchedArtifacts: HarnessArtifactRecord[];\n}\n\nexport interface HarnessTaskContractsServiceOptions {\n  repoPath: string;\n  stateService: HarnessRuntimeStatePort;\n}\n\nexport class HarnessTaskContractsService {\n  constructor(private readonly options: HarnessTaskContractsServiceOptions) {}\n\n  private get contractsPath(): string {\n    return path.join(path.resolve(this.options.repoPath), '.context', 'harness', 'contracts');\n  }\n\n  private get tasksPath(): string {\n    return path.join(this.contractsPath, 'tasks');\n  }\n\n  private get handoffsPath(): string {\n    return path.join(this.contractsPath, 'handoffs');\n  }\n\n  private async ensureLayout(): Promise<void> {\n    await Promise.all([\n      fs.ensureDir(this.tasksPath),\n      fs.ensureDir(this.handoffsPath),\n    ]);\n  }\n\n  private async taskFile(taskId: string): Promise<string> {\n    await this.ensureLayout();\n    return path.join(this.tasksPath, `${taskId}.json`);\n  }\n\n  private async handoffFile(handoffId: string): Promise<string> {\n    await this.ensureLayout();\n    return path.join(this.handoffsPath, `${handoffId}.json`);\n  }\n\n  async createTaskContract(input: {\n    title: string;\n    description?: string;\n    sessionId?: string;\n    owner?: string;\n    inputs?: string[];\n    expectedOutputs?: string[];\n    acceptanceCriteria?: string[];\n    requiredSensors?: string[];\n    requiredArtifacts?: string[];\n    status?: HarnessTaskContractStatus;\n    metadata?: Record<string, unknown>;\n  }): Promise<HarnessTaskContract> {\n    const now = new Date().toISOString();\n    const contract: HarnessTaskContract = {\n      id: randomUUID(),\n      title: input.title,\n      description: input.description,\n      sessionId: input.sessionId,\n      owner: input.owner,\n      status: input.status ?? 'draft',\n      inputs: input.inputs ?? [],\n      expectedOutputs: input.expectedOutputs ?? [],\n      acceptanceCriteria: input.acceptanceCriteria ?? [],\n      requiredSensors: input.requiredSensors ?? [],\n      requiredArtifacts: input.requiredArtifacts ?? [],\n      createdAt: now,\n      updatedAt: now,\n      metadata: input.metadata,\n    };\n\n    const filePath = await this.taskFile(contract.id);\n    await fs.writeJson(filePath, contract, { spaces: 2 });\n    if (contract.sessionId) {\n      await this.options.stateService.appendTrace(contract.sessionId, {\n        level: 'info',\n        event: 'task.contract.created',\n        message: `Task contract created: ${contract.title}`,\n        data: { contract },\n      });\n    }\n\n    return contract;\n  }\n\n  async listTaskContracts(): Promise<HarnessTaskContract[]> {\n    await this.ensureLayout();\n    const files = await fs.readdir(this.tasksPath);\n    const contracts = await Promise.all(\n      files\n        .filter((file) => file.endsWith('.json'))\n        .map(async (file) => fs.readJson(path.join(this.tasksPath, file)) as Promise<HarnessTaskContract>)\n    );\n\n    return contracts.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n  }\n\n  async getTaskContract(taskId: string): Promise<HarnessTaskContract | null> {\n    const filePath = await this.taskFile(taskId);\n    if (!(await fs.pathExists(filePath))) {\n      return null;\n    }\n\n    return fs.readJson(filePath) as Promise<HarnessTaskContract>;\n  }\n\n  async updateTaskContract(\n    taskId: string,\n    patch: Partial<Omit<HarnessTaskContract, 'id' | 'createdAt' | 'updatedAt'>>\n  ): Promise<HarnessTaskContract> {\n    const contract = await this.getTaskContract(taskId);\n    if (!contract) {\n      throw new Error(`Task contract not found: ${taskId}`);\n    }\n\n    const updated: HarnessTaskContract = {\n      ...contract,\n      ...patch,\n      updatedAt: new Date().toISOString(),\n    };\n\n    await fs.writeJson(await this.taskFile(taskId), updated, { spaces: 2 });\n    return updated;\n  }\n\n  async createHandoffContract(input: {\n    from: string;\n    to: string;\n    sessionId?: string;\n    taskId?: string;\n    artifacts?: string[];\n    evidence?: string[];\n    metadata?: Record<string, unknown>;\n  }): Promise<HarnessHandoffContract> {\n    const contract: HarnessHandoffContract = {\n      id: randomUUID(),\n      from: input.from,\n      to: input.to,\n      sessionId: input.sessionId,\n      taskId: input.taskId,\n      artifacts: input.artifacts ?? [],\n      evidence: input.evidence ?? [],\n      createdAt: new Date().toISOString(),\n      metadata: input.metadata,\n    };\n\n    const filePath = await this.handoffFile(contract.id);\n    await fs.writeJson(filePath, contract, { spaces: 2 });\n    if (contract.sessionId) {\n      await this.options.stateService.appendTrace(contract.sessionId, {\n        level: 'info',\n        event: 'handoff.contract.created',\n        message: `${contract.from} -> ${contract.to}`,\n        data: { contract },\n      });\n    }\n\n    return contract;\n  }\n\n  async listHandoffContracts(): Promise<HarnessHandoffContract[]> {\n    await this.ensureLayout();\n    const files = await fs.readdir(this.handoffsPath);\n    const contracts = await Promise.all(\n      files\n        .filter((file) => file.endsWith('.json'))\n        .map(async (file) => fs.readJson(path.join(this.handoffsPath, file)) as Promise<HarnessHandoffContract>)\n    );\n\n    return contracts.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n  }\n\n  async evaluateTaskCompletion(taskId: string, sessionId?: string): Promise<HarnessTaskCompletionResult> {\n    const contract = await this.getTaskContract(taskId);\n    if (!contract) {\n      throw new Error(`Task contract not found: ${taskId}`);\n    }\n\n    const traces: HarnessTraceRecord[] = sessionId ? await this.options.stateService.listTraces(sessionId) : [];\n    const sensorRuns = traces\n      .filter((trace) => trace.event === 'sensor.run' && trace.data?.run)\n      .map((trace) => trace.data!.run as HarnessSensorRun);\n    const latestRunsBySensor = new Map<string, HarnessSensorRun>();\n    for (const run of sensorRuns) {\n      const current = latestRunsBySensor.get(run.sensorId);\n      if (!current || current.createdAt < run.createdAt) {\n        latestRunsBySensor.set(run.sensorId, run);\n      }\n    }\n    const artifacts = sessionId ? await this.options.stateService.listArtifacts(sessionId) : [];\n\n    const matchedSensorRuns = contract.requiredSensors\n      .map((sensorId) => latestRunsBySensor.get(sensorId))\n      .filter((run): run is HarnessSensorRun => Boolean(run))\n      .filter((run) => run.status === 'passed');\n    const missingSensors = contract.requiredSensors.filter(\n      (sensorId) => !matchedSensorRuns.some((run) => run.sensorId === sensorId)\n    );\n\n    const artifactPaths = new Set([\n      ...artifacts.map((artifact) => artifact.path || artifact.name),\n    ]);\n    const missingArtifacts = contract.requiredArtifacts.filter((artifactPath) => !artifactPaths.has(artifactPath));\n    const matchedArtifacts = artifacts.filter(\n      (artifact) => contract.requiredArtifacts.includes(artifact.path || artifact.name)\n    );\n\n    const blockingFindings: string[] = [];\n    if (missingSensors.length > 0) {\n      blockingFindings.push(`Missing required sensors: ${missingSensors.join(', ')}`);\n    }\n    if (missingArtifacts.length > 0) {\n      blockingFindings.push(`Missing required artifacts: ${missingArtifacts.join(', ')}`);\n    }\n\n    return {\n      taskId,\n      sessionId,\n      canComplete: blockingFindings.length === 0,\n      missingSensors,\n      missingArtifacts,\n      blockingFindings,\n      matchedSensorRuns,\n      matchedArtifacts,\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/harness/workflowStateService.ts",
    "content": "/**\n * Harness Workflow State Service\n *\n * Canonical persistence for workflow orchestration state. PREVC status now\n * lives under .context/harness/workflows and legacy status.yaml is only read\n * for migration.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport type { PrevcStatus } from '../../workflow/types';\n\nexport interface HarnessWorkflowStateServiceOptions {\n  contextPath: string;\n}\n\nexport interface WorkflowHarnessBinding {\n  workflowName: string;\n  sessionId: string;\n  activeTaskId?: string;\n  createdAt: string;\n  updatedAt: string;\n}\n\nexport interface HarnessWorkflowRecord {\n  version: 2;\n  workflowType: 'prevc';\n  updatedAt: string;\n  status: PrevcStatus;\n  binding: WorkflowHarnessBinding | null;\n}\n\nexport class HarnessWorkflowStateService {\n  constructor(private readonly options: HarnessWorkflowStateServiceOptions) {}\n\n  private get contextPath(): string {\n    return path.resolve(this.options.contextPath);\n  }\n\n  private get workflowsPath(): string {\n    return path.join(this.contextPath, 'harness', 'workflows');\n  }\n\n  private get currentPath(): string {\n    return path.join(this.workflowsPath, 'prevc.json');\n  }\n\n  private get archivePath(): string {\n    return path.join(this.workflowsPath, 'archive');\n  }\n\n  private get legacyBindingPath(): string {\n    return path.join(this.contextPath, 'workflow', 'harness-session.json');\n  }\n\n  private async ensureLayout(): Promise<void> {\n    await fs.ensureDir(this.workflowsPath);\n  }\n\n  private normalizeBinding(value: unknown): WorkflowHarnessBinding | null {\n    if (!value || typeof value !== 'object') {\n      return null;\n    }\n\n    const candidate = value as Partial<WorkflowHarnessBinding>;\n    if (\n      typeof candidate.workflowName !== 'string' ||\n      candidate.workflowName.length === 0 ||\n      typeof candidate.sessionId !== 'string' ||\n      candidate.sessionId.length === 0 ||\n      typeof candidate.createdAt !== 'string' ||\n      candidate.createdAt.length === 0 ||\n      typeof candidate.updatedAt !== 'string' ||\n      candidate.updatedAt.length === 0\n    ) {\n      return null;\n    }\n\n    return {\n      workflowName: candidate.workflowName,\n      sessionId: candidate.sessionId,\n      activeTaskId: typeof candidate.activeTaskId === 'string' && candidate.activeTaskId.length > 0\n        ? candidate.activeTaskId\n        : undefined,\n      createdAt: candidate.createdAt,\n      updatedAt: candidate.updatedAt,\n    };\n  }\n\n  private normalizeRecord(\n    record: unknown,\n    legacyBinding: WorkflowHarnessBinding | null\n  ): HarnessWorkflowRecord {\n    const candidate = record as Partial<HarnessWorkflowRecord> & { status?: PrevcStatus };\n    if (!candidate || typeof candidate !== 'object' || !candidate.status) {\n      throw new Error('Invalid harness workflow record');\n    }\n\n    return {\n      version: 2,\n      workflowType: 'prevc',\n      updatedAt: typeof candidate.updatedAt === 'string' && candidate.updatedAt.length > 0\n        ? candidate.updatedAt\n        : new Date().toISOString(),\n      status: candidate.status,\n      binding: this.normalizeBinding(candidate.binding) ?? legacyBinding,\n    };\n  }\n\n  private async readLegacyBinding(): Promise<WorkflowHarnessBinding | null> {\n    if (!(await fs.pathExists(this.legacyBindingPath))) {\n      return null;\n    }\n\n    try {\n      const binding = await fs.readJson(this.legacyBindingPath);\n      return this.normalizeBinding(binding);\n    } catch {\n      return null;\n    }\n  }\n\n  private readLegacyBindingSync(): WorkflowHarnessBinding | null {\n    if (!fs.existsSync(this.legacyBindingPath)) {\n      return null;\n    }\n\n    try {\n      const binding = fs.readJsonSync(this.legacyBindingPath);\n      return this.normalizeBinding(binding);\n    } catch {\n      return null;\n    }\n  }\n\n  private async writeRecord(record: HarnessWorkflowRecord): Promise<void> {\n    await this.ensureLayout();\n    await fs.writeJson(this.currentPath, record, { spaces: 2 });\n    if (await fs.pathExists(this.legacyBindingPath)) {\n      await fs.remove(this.legacyBindingPath);\n    }\n  }\n\n  async exists(): Promise<boolean> {\n    return fs.pathExists(this.currentPath);\n  }\n\n  async loadRecord(): Promise<HarnessWorkflowRecord> {\n    const raw = await fs.readJson(this.currentPath);\n    const legacyBinding = await this.readLegacyBinding();\n    const record = this.normalizeRecord(raw, legacyBinding);\n\n    const shouldRewrite =\n      (raw as Partial<HarnessWorkflowRecord>).version !== 2 ||\n      (legacyBinding !== null && this.normalizeBinding((raw as Partial<HarnessWorkflowRecord>).binding) === null);\n\n    if (shouldRewrite) {\n      await this.writeRecord(record);\n    }\n\n    return record;\n  }\n\n  async load(): Promise<PrevcStatus> {\n    const record = await this.loadRecord();\n    return record.status;\n  }\n\n  loadRecordSync(): HarnessWorkflowRecord {\n    const raw = fs.readJsonSync(this.currentPath);\n    const legacyBinding = this.readLegacyBindingSync();\n    return this.normalizeRecord(raw, legacyBinding);\n  }\n\n  loadSync(): PrevcStatus {\n    const record = this.loadRecordSync();\n    return record.status;\n  }\n\n  async save(status: PrevcStatus): Promise<void> {\n    const binding = (await this.exists())\n      ? (await this.loadRecord()).binding\n      : await this.readLegacyBinding();\n    const record: HarnessWorkflowRecord = {\n      version: 2,\n      workflowType: 'prevc',\n      updatedAt: new Date().toISOString(),\n      status,\n      binding,\n    };\n    await this.writeRecord(record);\n  }\n\n  async getBinding(): Promise<WorkflowHarnessBinding | null> {\n    if (!(await this.exists())) {\n      return this.readLegacyBinding();\n    }\n\n    return (await this.loadRecord()).binding;\n  }\n\n  async saveBinding(binding: WorkflowHarnessBinding | null): Promise<void> {\n    if (!(await this.exists())) {\n      throw new Error('Workflow status not found. Initialize a workflow before binding a harness session.');\n    }\n\n    const record = await this.loadRecord();\n    record.binding = binding;\n    record.updatedAt = new Date().toISOString();\n    await this.writeRecord(record);\n  }\n\n  async remove(): Promise<void> {\n    if (await fs.pathExists(this.currentPath)) {\n      await fs.remove(this.currentPath);\n    }\n    if (await fs.pathExists(this.legacyBindingPath)) {\n      await fs.remove(this.legacyBindingPath);\n    }\n  }\n\n  async archive(name: string): Promise<void> {\n    await fs.ensureDir(this.archivePath);\n    const safeName = name.replace(/[^a-zA-Z0-9-_]/g, '-');\n    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n    if (await fs.pathExists(this.currentPath)) {\n      await fs.move(\n        this.currentPath,\n        path.join(this.archivePath, `${safeName}-${timestamp}.json`)\n      );\n    }\n    if (await fs.pathExists(this.legacyBindingPath)) {\n      await fs.move(\n        this.legacyBindingPath,\n        path.join(this.archivePath, `${safeName}-${timestamp}.legacy-binding.json`)\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "src/services/import/agentsDetector.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport type { RuleFileInfo, DetectionResult } from './types';\nimport { AGENT_SOURCES } from './presets';\n\nfunction normalizeAgentFilename(filePath: string): string {\n  return path.basename(filePath).replace(/\\.agent\\.md$/i, '.md');\n}\n\nfunction normalizeAgentName(filePath: string): string {\n  return normalizeAgentFilename(filePath).replace(/\\.md$/i, '');\n}\n\nexport class AgentsDetector {\n  /**\n   * Detect agent files in the repository and common locations\n   */\n  async detectAgents(repoPath: string, autoDetect: boolean = true): Promise<DetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: RuleFileInfo[] = [];\n    const sources: string[] = [];\n\n    if (!autoDetect) {\n      return { files: detectedFiles, sources };\n    }\n\n    // Check each agent source\n    for (const source of AGENT_SOURCES) {\n      for (const sourcePath of source.paths) {\n        const fullPath = path.isAbsolute(sourcePath)\n          ? sourcePath\n          : path.join(absoluteRepoPath, sourcePath);\n\n        try {\n          if (await fs.pathExists(fullPath)) {\n            const stat = await fs.stat(fullPath);\n            \n            if (stat.isDirectory()) {\n              // Find all markdown files in directory\n              const files = await glob('**/*.md', {\n                cwd: fullPath,\n                absolute: true,\n                ignore: ['node_modules/**', '.git/**']\n              });\n\n              for (const file of files) {\n                detectedFiles.push({\n                  name: normalizeAgentName(file),\n                  sourcePath: file,\n                  relativePath: path.relative(absoluteRepoPath, file),\n                  filename: normalizeAgentFilename(file),\n                  type: 'generic'\n                });\n              }\n              if (files.length > 0) {\n                sources.push(fullPath);\n              }\n            }\n          }\n        } catch (error) {\n          // Skip inaccessible paths\n          continue;\n        }\n      }\n\n      // Also try glob patterns from repo root\n      for (const pattern of source.patterns) {\n        try {\n          const matches = await glob(pattern, {\n            cwd: absoluteRepoPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']\n          });\n\n          for (const match of matches) {\n            const exists = detectedFiles.some(f => f.sourcePath === match);\n            if (!exists) {\n              detectedFiles.push({\n                name: normalizeAgentName(match),\n                sourcePath: match,\n                relativePath: path.relative(absoluteRepoPath, match),\n                filename: normalizeAgentFilename(match),\n                type: 'generic'\n              });\n              const dir = path.dirname(match);\n              if (!sources.includes(dir)) {\n                sources.push(dir);\n              }\n            }\n          }\n        } catch (error) {\n          // Skip pattern if it fails\n          continue;\n        }\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)]\n    };\n  }\n\n  /**\n   * Detect agents from specific source paths\n   */\n  async detectFromPaths(sourcePaths: string[], repoPath: string): Promise<DetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: RuleFileInfo[] = [];\n    const sources: string[] = [];\n\n    for (const sourcePath of sourcePaths) {\n      const fullPath = path.isAbsolute(sourcePath)\n        ? sourcePath\n        : path.join(absoluteRepoPath, sourcePath);\n\n      try {\n        if (!(await fs.pathExists(fullPath))) {\n          continue;\n        }\n\n        const stat = await fs.stat(fullPath);\n        \n        if (stat.isFile() && fullPath.endsWith('.md')) {\n          detectedFiles.push({\n            name: normalizeAgentName(fullPath),\n            sourcePath: fullPath,\n            relativePath: path.relative(absoluteRepoPath, fullPath),\n            filename: normalizeAgentFilename(fullPath),\n            type: 'generic'\n          });\n          sources.push(path.dirname(fullPath));\n        } else if (stat.isDirectory()) {\n          // Find all markdown files in directory\n          const files = await glob('**/*.md', {\n            cwd: fullPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**']\n          });\n\n          for (const file of files) {\n            detectedFiles.push({\n              name: normalizeAgentName(file),\n              sourcePath: file,\n              relativePath: path.relative(absoluteRepoPath, file),\n              filename: normalizeAgentFilename(file),\n              type: 'generic'\n            });\n          }\n          if (files.length > 0) {\n            sources.push(fullPath);\n          }\n        }\n      } catch (error) {\n        // Skip inaccessible paths\n        continue;\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)]\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/import/importAgentsService.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from '../../utils/theme';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type {\n  ImportAgentsCommandFlags,\n  ImportServiceDependencies,\n  ImportAgentsOptions,\n  ImportResult,\n  RuleFileInfo\n} from './types';\nimport { AgentsDetector } from './agentsDetector';\n\nexport class ImportAgentsService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n  private readonly detector: AgentsDetector;\n\n  constructor(dependencies: ImportServiceDependencies) {\n    this.ui = dependencies.ui;\n    this.t = dependencies.t;\n    this.version = dependencies.version;\n    this.detector = new AgentsDetector();\n  }\n\n  async run(rawOptions: ImportAgentsCommandFlags, repoPath: string = process.cwd()): Promise<ImportResult> {\n    const options = await this.resolveOptions(rawOptions, repoPath);\n\n    this.displayConfig(options);\n\n    // Detect agents\n    let detectionResult;\n    if (options.autoDetect && options.sourcePaths.length === 0) {\n      this.ui.startSpinner(this.t('spinner.import.detectingAgents'));\n      detectionResult = await this.detector.detectAgents(repoPath, true);\n      this.ui.stopSpinner();\n    } else if (options.sourcePaths.length > 0) {\n      this.ui.startSpinner(this.t('spinner.import.scanningPaths'));\n      detectionResult = await this.detector.detectFromPaths(options.sourcePaths, repoPath);\n      this.ui.stopSpinner();\n    } else {\n      detectionResult = { files: [], sources: [] };\n    }\n\n    if (detectionResult.files.length === 0) {\n      this.ui.displayWarning(this.t('warnings.import.noAgentsFound'));\n      return {\n        targetPath: options.targetPath,\n        filesCreated: 0,\n        filesSkipped: 0,\n        filesFailed: 0,\n        errors: [],\n      };\n    }\n\n    this.ui.displayInfo(\n      this.t('info.import.foundAgents'),\n      this.t('info.import.foundAgentsDetail', { count: detectionResult.files.length })\n    );\n\n    if (options.verbose) {\n      detectionResult.files.forEach(file => {\n        console.log(`  ${colors.secondaryDim(symbols.pointer)} ${colors.primary(file.sourcePath)}`);\n      });\n    }\n\n    // Import files\n    const result = await this.importFiles(detectionResult.files, options);\n\n    this.displaySummary(result, options.dryRun);\n\n    if (!options.dryRun) {\n      this.ui.displaySuccess(this.t('success.import.completed'));\n    }\n\n    return result;\n  }\n\n  private async resolveOptions(\n    rawOptions: ImportAgentsCommandFlags,\n    repoPath: string\n  ): Promise<ImportAgentsOptions> {\n    const targetPath = rawOptions.target\n      ? path.resolve(rawOptions.target)\n      : path.resolve(repoPath, '.context/agents');\n\n    const sourcePaths = rawOptions.source || [];\n    const autoDetect = rawOptions.autoDetect !== false;\n\n    return {\n      sourcePaths,\n      targetPath,\n      force: Boolean(rawOptions.force),\n      dryRun: Boolean(rawOptions.dryRun),\n      verbose: Boolean(rawOptions.verbose),\n      autoDetect\n    };\n  }\n\n  private async importFiles(\n    files: RuleFileInfo[],\n    options: ImportAgentsOptions\n  ): Promise<ImportResult> {\n    const result: ImportResult = {\n      targetPath: options.targetPath,\n      filesCreated: 0,\n      filesSkipped: 0,\n      filesFailed: 0,\n      errors: []\n    };\n\n    if (!options.dryRun) {\n      await fs.ensureDir(options.targetPath);\n    }\n\n    this.ui.startSpinner(this.t('spinner.import.importing', { path: options.targetPath }));\n\n    for (const file of files) {\n      try {\n        const targetFile = path.join(options.targetPath, file.filename);\n        const exists = await fs.pathExists(targetFile);\n\n        if (exists && !options.force) {\n          result.filesSkipped++;\n          if (options.verbose) {\n            console.log(`  ${colors.secondaryDim('Skipped (exists):')} ${targetFile}`);\n          }\n          continue;\n        }\n\n        if (exists && options.force && !options.dryRun) {\n          await fs.remove(targetFile);\n        }\n\n        if (!options.dryRun) {\n          // Read source file\n          const content = await fs.readFile(file.sourcePath, 'utf-8');\n          \n          // Write to target (agents are already markdown, just copy)\n          await fs.writeFile(targetFile, content);\n        }\n\n        result.filesCreated++;\n\n        if (options.verbose) {\n          console.log(`  ${colors.success(symbols.success)} ${colors.primary(targetFile)}`);\n        }\n      } catch (error) {\n        result.filesFailed++;\n        result.errors.push({\n          file: file.filename,\n          error: error instanceof Error ? error.message : String(error)\n        });\n        if (options.verbose) {\n          console.log(`  ${colors.error(symbols.error)} ${file.filename}: ${error instanceof Error ? error.message : String(error)}`);\n        }\n      }\n    }\n\n    this.ui.updateSpinner(\n      this.t('spinner.import.complete', {\n        path: options.targetPath,\n        count: result.filesCreated\n      }),\n      'success'\n    );\n    this.ui.stopSpinner();\n\n    return result;\n  }\n\n  private displayConfig(options: ImportAgentsOptions): void {\n    console.log('');\n    console.log(typography.header('Import Agents Configuration'));\n    console.log('');\n    console.log(typography.labeledValue('Target', options.targetPath));\n    console.log(typography.labeledValue('Auto-detect', options.autoDetect ? 'Yes' : 'No'));\n    if (options.sourcePaths.length > 0) {\n      console.log(`  ${colors.secondary('Source Paths')}`);\n      options.sourcePaths.forEach(p => {\n        console.log(`    ${colors.secondaryDim(symbols.pointer)} ${colors.primary(p)}`);\n      });\n    }\n    if (options.dryRun) {\n      console.log('');\n      console.log(typography.warning('DRY RUN - No changes will be made'));\n    }\n    console.log('');\n  }\n\n  private displaySummary(result: ImportResult, dryRun: boolean): void {\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header('Import Summary'));\n    console.log('');\n\n    const status = result.filesFailed > 0\n      ? colors.error(symbols.error)\n      : colors.success(symbols.success);\n\n    console.log(`${status} ${colors.primary(result.targetPath)}`);\n    console.log(\n      `    ${colors.secondary(`Created: ${result.filesCreated}, Skipped: ${result.filesSkipped}, Failed: ${result.filesFailed}`)}`\n    );\n\n    if (result.errors.length > 0) {\n      result.errors.forEach(err => {\n        console.log(`    ${colors.error(symbols.error)} ${colors.secondaryDim(`${err.file} - ${err.error}`)}`);\n      });\n    }\n\n    console.log('');\n    console.log(typography.labeledValue('Created', `${result.filesCreated}${dryRun ? ' (dry run)' : ''}`));\n    console.log(typography.labeledValue('Skipped', result.filesSkipped.toString()));\n    console.log(typography.labeledValue('Failed', result.filesFailed.toString()));\n    console.log('');\n  }\n}\n"
  },
  {
    "path": "src/services/import/importRulesService.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from '../../utils/theme';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type {\n  ImportRulesCommandFlags,\n  ImportServiceDependencies,\n  ImportRulesOptions,\n  ImportResult,\n  RuleFileInfo,\n  ImportFormat\n} from './types';\nimport { RulesDetector } from './rulesDetector';\n\nexport class ImportRulesService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n  private readonly detector: RulesDetector;\n\n  constructor(dependencies: ImportServiceDependencies) {\n    this.ui = dependencies.ui;\n    this.t = dependencies.t;\n    this.version = dependencies.version;\n    this.detector = new RulesDetector();\n  }\n\n  async run(rawOptions: ImportRulesCommandFlags, repoPath: string = process.cwd()): Promise<ImportResult> {\n    const options = await this.resolveOptions(rawOptions, repoPath);\n\n    this.displayConfig(options);\n\n    // Detect rules\n    let detectionResult;\n    if (options.autoDetect && options.sourcePaths.length === 0) {\n      this.ui.startSpinner(this.t('spinner.import.detectingRules'));\n      detectionResult = await this.detector.detectRules(repoPath, true);\n      this.ui.stopSpinner();\n    } else if (options.sourcePaths.length > 0) {\n      this.ui.startSpinner(this.t('spinner.import.scanningPaths'));\n      detectionResult = await this.detector.detectFromPaths(options.sourcePaths, repoPath);\n      this.ui.stopSpinner();\n    } else {\n      detectionResult = { files: [], sources: [] };\n    }\n\n    if (detectionResult.files.length === 0) {\n      this.ui.displayWarning(this.t('warnings.import.noRulesFound'));\n      return {\n        targetPath: options.targetPath,\n        filesCreated: 0,\n        filesSkipped: 0,\n        filesFailed: 0,\n        errors: [],\n      };\n    }\n\n    this.ui.displayInfo(\n      this.t('info.import.foundRules'),\n      this.t('info.import.foundRulesDetail', { count: detectionResult.files.length })\n    );\n\n    if (options.verbose) {\n      detectionResult.files.forEach(file => {\n        console.log(`  ${colors.secondaryDim(symbols.pointer)} ${colors.primary(file.sourcePath)}`);\n      });\n    }\n\n    // Import files\n    const result = await this.importFiles(detectionResult.files, options);\n\n    this.displaySummary(result, options.dryRun);\n\n    if (!options.dryRun) {\n      this.ui.displaySuccess(this.t('success.import.completed'));\n    }\n\n    return result;\n  }\n\n  private async resolveOptions(\n    rawOptions: ImportRulesCommandFlags,\n    repoPath: string\n  ): Promise<ImportRulesOptions> {\n    const targetPath = rawOptions.target\n      ? path.resolve(rawOptions.target)\n      : path.resolve(repoPath, '.context/docs');\n\n    const sourcePaths = rawOptions.source || [];\n    const format: ImportFormat = (rawOptions.format as ImportFormat) || 'markdown';\n    const autoDetect = rawOptions.autoDetect !== false;\n\n    return {\n      sourcePaths,\n      targetPath,\n      format,\n      force: Boolean(rawOptions.force),\n      dryRun: Boolean(rawOptions.dryRun),\n      verbose: Boolean(rawOptions.verbose),\n      autoDetect\n    };\n  }\n\n  private async importFiles(\n    files: RuleFileInfo[],\n    options: ImportRulesOptions\n  ): Promise<ImportResult> {\n    const result: ImportResult = {\n      targetPath: options.targetPath,\n      filesCreated: 0,\n      filesSkipped: 0,\n      filesFailed: 0,\n      errors: []\n    };\n\n    if (!options.dryRun) {\n      await fs.ensureDir(options.targetPath);\n    }\n\n    this.ui.startSpinner(this.t('spinner.import.importing', { path: options.targetPath }));\n\n    for (const file of files) {\n      try {\n        const targetFile = path.join(options.targetPath, file.filename);\n        const exists = await fs.pathExists(targetFile);\n\n        if (exists && !options.force) {\n          result.filesSkipped++;\n          if (options.verbose) {\n            console.log(`  ${colors.secondaryDim('Skipped (exists):')} ${targetFile}`);\n          }\n          continue;\n        }\n\n        if (exists && options.force && !options.dryRun) {\n          await fs.remove(targetFile);\n        }\n\n        if (!options.dryRun) {\n          // Read source file\n          const content = await fs.readFile(file.sourcePath, 'utf-8');\n          \n          // Format content based on format option\n          const formattedContent = this.formatContent(content, file, options.format);\n          \n          // Write to target\n          await fs.writeFile(targetFile, formattedContent);\n        }\n\n        result.filesCreated++;\n\n        if (options.verbose) {\n          console.log(`  ${colors.success(symbols.success)} ${colors.primary(targetFile)}`);\n        }\n      } catch (error) {\n        result.filesFailed++;\n        result.errors.push({\n          file: file.filename,\n          error: error instanceof Error ? error.message : String(error)\n        });\n        if (options.verbose) {\n          console.log(`  ${colors.error(symbols.error)} ${file.filename}: ${error instanceof Error ? error.message : String(error)}`);\n        }\n      }\n    }\n\n    this.ui.updateSpinner(\n      this.t('spinner.import.complete', {\n        path: options.targetPath,\n        count: result.filesCreated\n      }),\n      'success'\n    );\n    this.ui.stopSpinner();\n\n    return result;\n  }\n\n  private formatContent(content: string, file: RuleFileInfo, format: ImportFormat): string {\n    switch (format) {\n      case 'markdown':\n        return this.formatAsMarkdown(content, file);\n      case 'formatted':\n        return this.formatAsFormatted(content, file);\n      case 'raw':\n      default:\n        return content;\n    }\n  }\n\n  private formatAsMarkdown(content: string, file: RuleFileInfo): string {\n    const header = `# ${file.name}\\n\\n`;\n    const sourceInfo = `> **Source:** \\`${file.relativePath}\\`\\n`;\n    const typeInfo = `> **Type:** ${file.type}\\n\\n`;\n    const separator = '---\\n\\n';\n    \n    return `${header}${sourceInfo}${typeInfo}${separator}${content}`;\n  }\n\n  private formatAsFormatted(content: string, file: RuleFileInfo): string {\n    const frontMatter = `---\\nsource: ${file.relativePath}\\ntype: ${file.type}\\n---\\n\\n`;\n    return `${frontMatter}${content}`;\n  }\n\n  private displayConfig(options: ImportRulesOptions): void {\n    console.log('');\n    console.log(typography.header('Import Rules Configuration'));\n    console.log('');\n    console.log(typography.labeledValue('Target', options.targetPath));\n    console.log(typography.labeledValue('Format', options.format));\n    console.log(typography.labeledValue('Auto-detect', options.autoDetect ? 'Yes' : 'No'));\n    if (options.sourcePaths.length > 0) {\n      console.log(`  ${colors.secondary('Source Paths')}`);\n      options.sourcePaths.forEach(p => {\n        console.log(`    ${colors.secondaryDim(symbols.pointer)} ${colors.primary(p)}`);\n      });\n    }\n    if (options.dryRun) {\n      console.log('');\n      console.log(typography.warning('DRY RUN - No changes will be made'));\n    }\n    console.log('');\n  }\n\n  private displaySummary(result: ImportResult, dryRun: boolean): void {\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header('Import Summary'));\n    console.log('');\n\n    const status = result.filesFailed > 0\n      ? colors.error(symbols.error)\n      : colors.success(symbols.success);\n\n    console.log(`${status} ${colors.primary(result.targetPath)}`);\n    console.log(\n      `    ${colors.secondary(`Created: ${result.filesCreated}, Skipped: ${result.filesSkipped}, Failed: ${result.filesFailed}`)}`\n    );\n\n    if (result.errors.length > 0) {\n      result.errors.forEach(err => {\n        console.log(`    ${colors.error(symbols.error)} ${colors.secondaryDim(`${err.file} - ${err.error}`)}`);\n      });\n    }\n\n    console.log('');\n    console.log(typography.labeledValue('Created', `${result.filesCreated}${dryRun ? ' (dry run)' : ''}`));\n    console.log(typography.labeledValue('Skipped', result.filesSkipped.toString()));\n    console.log(typography.labeledValue('Failed', result.filesFailed.toString()));\n    console.log('');\n  }\n}\n"
  },
  {
    "path": "src/services/import/index.ts",
    "content": "export * from './types';\nexport * from './presets';\nexport { RulesDetector } from './rulesDetector';\nexport { AgentsDetector } from './agentsDetector';\nexport { ImportRulesService } from './importRulesService';\nexport { ImportAgentsService } from './importAgentsService';\n"
  },
  {
    "path": "src/services/import/presets.ts",
    "content": "/**\n * Import Presets\n *\n * Rule and agent source definitions for import/detection services.\n * Derived from the unified tool registry with some legacy naming for backward compatibility.\n */\n\nimport type { RuleSource } from './types';\nimport { getRulesImportSources, getAgentsImportSources } from '../shared';\n\n/**\n * Build rule sources from the unified tool registry\n * Maintains legacy naming for backward compatibility\n */\nfunction buildRuleSources(): RuleSource[] {\n  const registrySources = getRulesImportSources();\n\n  // Add generic AI rules files (not in registry)\n  const genericSource: RuleSource = {\n    name: 'generic',\n    paths: ['AI_RULES.md', 'CODING_RULES.md', 'AI_INSTRUCTIONS.md', 'CLAUDE.md', 'AGENTS.md'],\n    patterns: ['**/AI_RULES.md', '**/CODING_RULES.md', '**/AI_INSTRUCTIONS.md', '**/CLAUDE.md', '**/AGENTS.md'],\n    description: 'Generic AI coding rules files'\n  };\n\n  return [...registrySources, genericSource];\n}\n\n/**\n * Build agent sources from the unified tool registry\n */\nfunction buildAgentSources(): RuleSource[] {\n  return getAgentsImportSources();\n}\n\n/**\n * Rule sources for import/detection (derived from tool registry)\n */\nexport const RULE_SOURCES: RuleSource[] = buildRuleSources();\n\n/**\n * Agent sources for import/detection (derived from tool registry)\n */\nexport const AGENT_SOURCES: RuleSource[] = buildAgentSources();\n\nexport function getRuleSourceByName(name: string): RuleSource | undefined {\n  return RULE_SOURCES.find(s => s.name === name);\n}\n\nexport function getAgentSourceByName(name: string): RuleSource | undefined {\n  return AGENT_SOURCES.find(s => s.name === name);\n}\n\nexport function getAllRuleSourceNames(): string[] {\n  return RULE_SOURCES.map(s => s.name);\n}\n\nexport function getAllAgentSourceNames(): string[] {\n  return AGENT_SOURCES.map(s => s.name);\n}\n"
  },
  {
    "path": "src/services/import/rulesDetector.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport type { RuleFileInfo, DetectionResult, RuleType } from './types';\nimport { RULE_SOURCES } from './presets';\n\nexport class RulesDetector {\n  /**\n   * Detect rules files in the repository and common locations\n   */\n  async detectRules(repoPath: string, autoDetect: boolean = true): Promise<DetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: RuleFileInfo[] = [];\n    const sources: string[] = [];\n\n    if (!autoDetect) {\n      return { files: detectedFiles, sources };\n    }\n\n    // Check each rule source\n    for (const source of RULE_SOURCES) {\n      for (const sourcePath of source.paths) {\n        const fullPath = path.isAbsolute(sourcePath)\n          ? sourcePath\n          : path.join(absoluteRepoPath, sourcePath);\n\n        try {\n          if (await fs.pathExists(fullPath)) {\n            const stat = await fs.stat(fullPath);\n            \n            if (stat.isFile()) {\n              // Single file\n              const ruleType = this.determineRuleType(source.name);\n              detectedFiles.push({\n                name: path.basename(fullPath, path.extname(fullPath)),\n                sourcePath: fullPath,\n                relativePath: path.relative(absoluteRepoPath, fullPath),\n                filename: this.generateTargetFilename(fullPath, ruleType),\n                type: ruleType\n              });\n              sources.push(fullPath);\n            } else if (stat.isDirectory()) {\n              // Directory - search for files\n              const files = await this.findFilesInDirectory(fullPath, source.patterns);\n              for (const file of files) {\n                const ruleType = this.determineRuleType(source.name);\n                detectedFiles.push({\n                  name: path.basename(file, path.extname(file)),\n                  sourcePath: file,\n                  relativePath: path.relative(absoluteRepoPath, file),\n                  filename: this.generateTargetFilename(file, ruleType),\n                  type: ruleType\n                });\n              }\n              if (files.length > 0) {\n                sources.push(fullPath);\n              }\n            }\n          }\n        } catch (error) {\n          // Skip inaccessible paths\n          continue;\n        }\n      }\n\n      // Also try glob patterns from repo root\n      for (const pattern of source.patterns) {\n        try {\n          const matches = await glob(pattern, {\n            cwd: absoluteRepoPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']\n          });\n\n          for (const match of matches) {\n            const exists = detectedFiles.some(f => f.sourcePath === match);\n            if (!exists) {\n              const ruleType = this.determineRuleType(source.name);\n              detectedFiles.push({\n                name: path.basename(match, path.extname(match)),\n                sourcePath: match,\n                relativePath: path.relative(absoluteRepoPath, match),\n                filename: this.generateTargetFilename(match, ruleType),\n                type: ruleType\n              });\n              if (!sources.includes(path.dirname(match))) {\n                sources.push(path.dirname(match));\n              }\n            }\n          }\n        } catch (error) {\n          // Skip pattern if it fails\n          continue;\n        }\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)]\n    };\n  }\n\n  /**\n   * Detect rules from specific source paths\n   */\n  async detectFromPaths(sourcePaths: string[], repoPath: string): Promise<DetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: RuleFileInfo[] = [];\n    const sources: string[] = [];\n\n    for (const sourcePath of sourcePaths) {\n      const fullPath = path.isAbsolute(sourcePath)\n        ? sourcePath\n        : path.join(absoluteRepoPath, sourcePath);\n\n      try {\n        if (!(await fs.pathExists(fullPath))) {\n          continue;\n        }\n\n        const stat = await fs.stat(fullPath);\n        \n        if (stat.isFile()) {\n          const ruleType = this.determineRuleTypeFromPath(fullPath);\n          detectedFiles.push({\n            name: path.basename(fullPath, path.extname(fullPath)),\n            sourcePath: fullPath,\n            relativePath: path.relative(absoluteRepoPath, fullPath),\n            filename: this.generateTargetFilename(fullPath, ruleType),\n            type: ruleType\n          });\n          sources.push(path.dirname(fullPath));\n        } else if (stat.isDirectory()) {\n          // Find all markdown and text files in directory\n          const files = await glob('**/*.{md,txt,mdx}', {\n            cwd: fullPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**']\n          });\n\n          for (const file of files) {\n            const ruleType = this.determineRuleTypeFromPath(file);\n            detectedFiles.push({\n              name: path.basename(file, path.extname(file)),\n              sourcePath: file,\n              relativePath: path.relative(absoluteRepoPath, file),\n              filename: this.generateTargetFilename(file, ruleType),\n              type: ruleType\n            });\n          }\n          if (files.length > 0) {\n            sources.push(fullPath);\n          }\n        }\n      } catch (error) {\n        // Skip inaccessible paths\n        continue;\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)]\n    };\n  }\n\n  private async findFilesInDirectory(dirPath: string, patterns: string[]): Promise<string[]> {\n    const files: string[] = [];\n\n    for (const pattern of patterns) {\n      try {\n        const matches = await glob(pattern, {\n          cwd: dirPath,\n          absolute: true,\n          ignore: ['node_modules/**', '.git/**']\n        });\n        files.push(...matches);\n      } catch {\n        // Skip pattern if it fails\n      }\n    }\n\n    return [...new Set(files)];\n  }\n\n  private determineRuleType(sourceName: string): RuleType {\n    if (sourceName.includes('cursorrules') || sourceName === 'cursorrules') return 'cursorrules';\n    if (sourceName.includes('claude')) return 'claude-memory';\n    if (sourceName.includes('github') || sourceName.includes('copilot')) return 'github-copilot';\n    if (sourceName.includes('windsurf') || sourceName === 'windsurfrules') return 'windsurfrules';\n    if (sourceName.includes('cline') || sourceName === 'clinerules') return 'clinerules';\n    if (sourceName.includes('aider')) return 'aider';\n    if (sourceName.includes('continue')) return 'continue';\n    if (sourceName.includes('codex')) return 'codex';\n    if (sourceName.includes('gemini')) return 'gemini';\n    if (sourceName.includes('antigravity')) return 'antigravity';\n    if (sourceName.includes('trae')) return 'trae';\n    if (sourceName.includes('zed')) return 'zed';\n    return 'generic';\n  }\n\n  private determineRuleTypeFromPath(filePath: string): RuleType {\n    const normalized = filePath.toLowerCase();\n    if (normalized.includes('.cursorrules') || normalized.includes('.cursor/')) return 'cursorrules';\n    if (normalized.includes('.claude/') || normalized.includes('memory')) return 'claude-memory';\n    if (normalized.includes('.github/') || normalized.includes('copilot')) return 'github-copilot';\n    if (normalized.includes('.windsurfrules') || normalized.includes('.windsurf/')) return 'windsurfrules';\n    if (normalized.includes('.clinerules') || normalized.includes('.cline/')) return 'clinerules';\n    if (normalized.includes('.aider') || normalized.includes('conventions.md')) return 'aider';\n    if (normalized.includes('.continuerules') || normalized.includes('.continue/')) return 'continue';\n    if (normalized.includes('.codex/')) return 'codex';\n    if (normalized.endsWith('/gemini.md') || normalized === 'gemini.md') return 'gemini';\n    if (normalized.includes('.agent/') || normalized.includes('.agents/')) return 'antigravity';\n    if (normalized.includes('.trae/')) return 'trae';\n    if (normalized.includes('.zed/')) return 'zed';\n    return 'generic';\n  }\n\n  private generateTargetFilename(sourcePath: string, type: RuleType): string {\n    const basename = path.basename(sourcePath, path.extname(sourcePath));\n    const ext = '.md';\n    \n    // Generate meaningful filename based on rule type\n    const prefixMap: Record<RuleType, string> = {\n      'cursorrules': 'cursor-rules',\n      'claude-memory': 'claude-memory',\n      'github-copilot': 'github-copilot-rules',\n      'windsurfrules': 'windsurf-rules',\n      'clinerules': 'cline-rules',\n      'aider': 'aider-conventions',\n      'continue': 'continue-rules',\n      'codex': 'codex-instructions',\n      'gemini': 'gemini-instructions',\n      'antigravity': 'antigravity-rules',\n      'trae': 'trae-rules',\n      'zed': 'zed-settings',\n      'generic': 'rules'\n    };\n    \n    const prefix = prefixMap[type] || 'rules';\n\n    // If basename is generic, use prefix, otherwise combine\n    const genericNames = ['rules', 'cursorrules', 'memory', 'windsurfrules', 'clinerules', 'continuerules', 'config', 'settings', 'instructions'];\n    const finalName = genericNames.includes(basename.toLowerCase())\n      ? prefix\n      : `${prefix}-${basename}`;\n\n    return `${finalName}${ext}`;\n  }\n}\n"
  },
  {
    "path": "src/services/import/types.ts",
    "content": "import type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\n\nexport type RuleType = \n  | 'cursorrules' \n  | 'claude-memory' \n  | 'github-copilot' \n  | 'windsurfrules'\n  | 'clinerules'\n  | 'aider'\n  | 'continue'\n  | 'codex'\n  | 'gemini'\n  | 'antigravity'\n  | 'trae'\n  | 'zed'\n  | 'generic';\n\nexport type ImportFormat = 'markdown' | 'raw' | 'formatted';\n\nexport interface RuleFileInfo {\n  name: string;\n  sourcePath: string;\n  relativePath: string;\n  filename: string;\n  type: RuleType;\n  content?: string;\n}\n\nexport interface ImportRulesCommandFlags {\n  source?: string[];\n  target?: string;\n  format?: ImportFormat;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n  autoDetect?: boolean;\n}\n\nexport interface ImportAgentsCommandFlags {\n  source?: string[];\n  target?: string;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n  autoDetect?: boolean;\n}\n\nexport interface ImportServiceDependencies {\n  ui: CLIInterface;\n  t: TranslateFn;\n  version: string;\n}\n\nexport interface ImportRulesOptions {\n  sourcePaths: string[];\n  targetPath: string;\n  format: ImportFormat;\n  force: boolean;\n  dryRun: boolean;\n  verbose: boolean;\n  autoDetect: boolean;\n}\n\nexport interface ImportAgentsOptions {\n  sourcePaths: string[];\n  targetPath: string;\n  force: boolean;\n  dryRun: boolean;\n  verbose: boolean;\n  autoDetect: boolean;\n}\n\nexport interface ImportResult {\n  targetPath: string;\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  errors: Array<{ file: string; error: string }>;\n}\n\nexport interface DetectionResult {\n  files: RuleFileInfo[];\n  sources: string[];\n}\n\nexport interface RuleSource {\n  name: string;\n  paths: string[];\n  patterns: string[];\n  description: string;\n}\n"
  },
  {
    "path": "src/services/mcp/README.md",
    "content": "# MCP Tools Reference\n\nThis directory documents the MCP adapter surface over the harness runtime.\n\nCLI-only concerns such as editor installation and local operator setup belong to the CLI boundary and should not be added here.\n\nHarness-domain concerns should live behind reusable services. The MCP layer should increasingly act as a transport adapter, not as the center of the product model.\n\n## Simplified Tool Structure\n\nThe MCP tools follow a simple, explicit pattern:\n\n1. **Scaffolding**: `context({ action: \"init\" })`\n2. **Content**: `context({ action: \"fillSingle\", filePath })`\n3. **Workflow**: `workflow-init({ name: \"feature-name\" })`\n\n## Available Tools (9 total)\n\n### Gateway Tools (5)\n\n| Tool | Description |\n|------|-------------|\n| `explore` | File and code exploration (read, list, analyze, search, getStructure) |\n| `context` | Context scaffolding, semantic context, and optional Q&A/flow helpers (check, bootstrapStatus, init, fill, fillSingle, listToFill, getMap, buildSemantic, scaffoldPlan, searchQA, generateQA, getFlow, detectPatterns) |\n| `sync` | Import/export synchronization with AI tools |\n| `plan` | Plan management and execution tracking |\n| `agent` | Agent orchestration and discovery |\n| `skill` | Skill management for on-demand expertise |\n\n### Dedicated Workflow Tools (4)\n\n| Tool | Description |\n|------|-------------|\n| `workflow-init` | Initialize a PREVC workflow (creates `.context/workflow/`) |\n| `workflow-status` | Get current workflow status |\n| `workflow-advance` | Advance to next phase |\n| `workflow-manage` | Manage handoffs, collaboration, documents, gates |\n\n## Tool Decision Tree\n\n### Step 1: Create Scaffolding\n\n```\nDoes .context/ folder exist?\n├─ No → Use context({ action: \"init\" })\n│   ├─ Creates .context/docs/ templates\n│   ├─ Creates .context/agents/ playbooks\n│   ├─ Creates .context/skills/ definitions\n│   └─ Returns list of files needing content\n└─ Yes → Skip to Step 2\n```\n\n### Step 2: Fill Content (Optional but Recommended)\n\n```\nDo the template files have content?\n├─ No → For each file:\n│   └─ Use context({ action: \"fillSingle\", filePath: \"path\" })\n│       ├─ Returns semantic context from codebase\n│       ├─ Returns scaffold structure with guidance\n│       └─ AI generates content based on context\n└─ Yes → Skip to Step 3\n\nNote: generated Q&A files in `.context/docs/qa/` are optional helper artifacts created only by `context({ action: \"init\", generateQA: true })`. They do not appear in `listToFill`/`fill` unless you create custom nested docs there with `status: unfilled`.\nNote: `.context/harness/sensors.json` is bootstrap-generated during `init` and can appear in `listToFill`/`fill` until it is customized for the project.\nNote: `searchQA` performs keyword ranking over those generated helper docs. It is not embedding-based semantic search.\n```\n\n### Step 3: Initialize Workflow\n\n```\nDo you need structured development?\n├─ Yes → Use workflow-init({ name: \"feature-name\" })\n│   ├─ Creates .context/workflow/ folder\n│   ├─ Initializes phase tracking\n│   └─ Configures gates based on scale\n└─ No → Start coding directly\n```\n\n## Tool Relationships\n\n```\nSimplified Flow:\n  context({ action: \"init\" })\n    └─ Returns: pendingFiles[]\n       └─ For each file:\n          └─ context({ action: \"fillSingle\", filePath })\n             └─ Write enhanced content\n                └─ workflow-init({ name: \"feature\" })\n                   └─ Start development\n```\n\n## Key Points\n\n- **No all-in-one tool**: Each step is explicit\n- **Composable**: Mix and match based on needs\n- **workflow-init creates folder**: No manual folder creation needed\n- **Skip workflow for trivial changes**: Typos, simple edits don't need workflow\n- **context init does NOT create workflow folder**: Use workflow-init for that\n\n## What Each Tool Creates\n\n| Tool | Creates |\n|------|---------|\n| `context({ action: \"init\" })` | `.context/docs/`, `.context/agents/`, `.context/skills/` |\n| `workflow-init({ name })` | `.context/workflow/` |\n\n## Removed Tools\n\nThe following tools have been removed in favor of the simplified pattern:\n\n- `project-setup` - Use `context({ action: \"init\" })` + `workflow-init` instead\n- `project-report` - Use `context({ action: \"getMap\" })` for codebase info\n\n## Migration Guide\n\nIf you were using `project-setup`, here's how to migrate:\n\n**Before (all-in-one):**\n```\nproject-setup({ featureName: \"my-feature\", template: \"feature\" })\n```\n\n**After (explicit steps):**\n```\n1. context({ action: \"init\" })              // Create scaffolding\n2. context({ action: \"fillSingle\", ... })   // Fill each scaffold file that still needs content\n3. workflow-init({ name: \"my-feature\" })    // Start workflow\n```\n\nThis gives you more control and makes each step's purpose clear.\n"
  },
  {
    "path": "src/services/mcp/actionLogger.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessRuntimeStateService } from '../harness/runtimeStateService';\nimport { WorkflowService } from '../workflow/workflowService';\nimport { logMcpAction } from './actionLogger';\n\ndescribe('logMcpAction', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-action-logger-'));\n    await fs.ensureDir(path.join(tempDir, '.context'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'mcp-action-logger-test',\n      version: '1.0.0',\n      scripts: {\n        build: 'node -e \"process.exit(0)\"',\n      },\n    }, { spaces: 2 });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('records MCP activity in a harness session when no workflow binding exists', async () => {\n    await logMcpAction(tempDir, {\n      tool: 'context',\n      action: 'check',\n      status: 'success',\n      details: {\n        prompt: 'sensitive prompt',\n        nested: {\n          content: 'secret content',\n        },\n      },\n    });\n\n    const state = new HarnessRuntimeStateService({ repoPath: tempDir });\n    const sessions = await state.listSessions();\n\n    expect(sessions).toHaveLength(1);\n    expect(sessions[0].name).toBe('mcp-activity');\n    expect(sessions[0].metadata?.transport).toBe('mcp');\n\n    const traces = await state.listTraces(sessions[0].id);\n    const mcpTrace = traces.find((trace) => trace.event === 'mcp.tool.succeeded');\n    expect(mcpTrace).toBeDefined();\n    expect(mcpTrace?.data?.tool).toBe('context');\n    expect((mcpTrace?.data as any).details.prompt).toBe('[redacted]');\n    expect((mcpTrace?.data as any).details.nested.content).toBe('[redacted]');\n\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'workflow', 'actions.jsonl'))).toBe(false);\n  });\n\n  it('reuses the workflow harness session when one is active', async () => {\n    const workflow = new WorkflowService(tempDir);\n    await workflow.init({\n      name: 'workflow-alpha',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    const before = await workflow.getHarnessStatus();\n    expect(before).not.toBeNull();\n\n    await logMcpAction(tempDir, {\n      tool: 'workflow-status',\n      action: 'read',\n      status: 'success',\n      details: {\n        repoPath: tempDir,\n      },\n    });\n\n    const after = await workflow.getHarnessStatus();\n    expect(after).not.toBeNull();\n    expect(after?.session.id).toBe(before?.session.id);\n    expect(after?.session.traceCount).toBeGreaterThan(before?.session.traceCount || 0);\n    expect(after?.sensorRuns).toEqual(before?.sensorRuns || []);\n\n    const state = new HarnessRuntimeStateService({ repoPath: tempDir });\n    const traces = await state.listTraces(after!.session.id);\n    expect(traces.some((trace) => trace.event === 'mcp.tool.succeeded')).toBe(true);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'workflow', 'actions.jsonl'))).toBe(false);\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/actionLogger.ts",
    "content": "/**\n * MCP Action Logger\n *\n * Records MCP tool activity into harness session traces instead of maintaining\n * a separate workflow-local actions.jsonl log.\n */\n\nimport * as fs from 'fs-extra';\n\nimport { HarnessRuntimeStateService } from '../harness/runtimeStateService';\nimport { HarnessWorkflowStateService } from '../harness/workflowStateService';\nimport { resolveContextRoot } from '../shared/contextRootResolver';\n\ntype ActionStatus = 'success' | 'error';\n\nexport interface MCPActionLogEntry {\n  timestamp: string;\n  tool: string;\n  action: string;\n  status: ActionStatus;\n  details?: Record<string, unknown>;\n  error?: string;\n}\n\nconst SENSITIVE_KEYS = new Set([\n  'apiKey',\n  'token',\n  'secret',\n  'password',\n  'authorization',\n  'prompt',\n  'content',\n  'messages',\n  'semanticContext',\n]);\n\nconst MAX_DEPTH = 4;\nconst MAX_ARRAY = 20;\nconst MAX_STRING = 200;\nconst MCP_ACTIVITY_NAME = 'mcp-activity';\n\nconst sessionCache = new Map<string, string>();\n\nasync function resolveContextPath(repoPath: string): Promise<string> {\n  const resolution = await resolveContextRoot({\n    startPath: repoPath,\n    validate: false,\n  });\n  return resolution.contextPath;\n}\n\nfunction sanitizeValue(value: unknown, depth: number = 0): unknown {\n  if (value === null || value === undefined) return value;\n  if (depth >= MAX_DEPTH) return '[truncated]';\n\n  if (typeof value === 'string') {\n    if (value.length <= MAX_STRING) return value;\n    return `${value.slice(0, MAX_STRING)}...`;\n  }\n\n  if (typeof value === 'number' || typeof value === 'boolean') {\n    return value;\n  }\n\n  if (Array.isArray(value)) {\n    const trimmed = value.slice(0, MAX_ARRAY).map((item) => sanitizeValue(item, depth + 1));\n    if (value.length > MAX_ARRAY) {\n      trimmed.push(`...(${value.length - MAX_ARRAY} more items)`);\n    }\n    return trimmed;\n  }\n\n  if (typeof value === 'object') {\n    const result: Record<string, unknown> = {};\n    for (const [key, entryValue] of Object.entries(value)) {\n      if (SENSITIVE_KEYS.has(key)) {\n        result[key] = '[redacted]';\n      } else {\n        result[key] = sanitizeValue(entryValue, depth + 1);\n      }\n    }\n    return result;\n  }\n\n  return String(value);\n}\n\nfunction sanitizeDetails(details?: Record<string, unknown>): Record<string, unknown> | undefined {\n  if (!details) return undefined;\n  return sanitizeValue(details) as Record<string, unknown>;\n}\n\nasync function resolveWorkflowSessionId(contextPath: string): Promise<string | null> {\n  const workflowState = new HarnessWorkflowStateService({ contextPath });\n  if (!(await workflowState.exists())) {\n    return null;\n  }\n\n  try {\n    const binding = await workflowState.getBinding();\n    return binding?.sessionId ?? null;\n  } catch {\n    return null;\n  }\n}\n\nasync function resolveMcpActivitySessionId(\n  repoPath: string,\n  state: HarnessRuntimeStateService\n): Promise<string> {\n  const cached = sessionCache.get(repoPath);\n  if (cached) {\n    try {\n      await state.getSession(cached);\n      return cached;\n    } catch {\n      sessionCache.delete(repoPath);\n    }\n  }\n\n  const existing = (await state.listSessions()).find((session) =>\n    session.name === MCP_ACTIVITY_NAME &&\n    session.metadata?.transport === 'mcp'\n  );\n\n  if (existing) {\n    sessionCache.set(repoPath, existing.id);\n    return existing.id;\n  }\n\n  const created = await state.createSession({\n    name: MCP_ACTIVITY_NAME,\n    metadata: {\n      transport: 'mcp',\n      purpose: 'tool-audit',\n    },\n  });\n  sessionCache.set(repoPath, created.id);\n  return created.id;\n}\n\nexport async function logMcpAction(\n  repoPath: string,\n  entry: Omit<MCPActionLogEntry, 'timestamp'> & { timestamp?: string }\n): Promise<void> {\n  try {\n    const contextPath = await resolveContextPath(repoPath);\n    if (!(await fs.pathExists(contextPath))) {\n      return;\n    }\n\n    const state = new HarnessRuntimeStateService({ repoPath });\n    const workflowSessionId = await resolveWorkflowSessionId(contextPath);\n    let sessionId = workflowSessionId;\n    if (sessionId) {\n      try {\n        await state.getSession(sessionId);\n      } catch {\n        sessionId = null;\n      }\n    }\n    if (!sessionId) {\n      sessionId = await resolveMcpActivitySessionId(repoPath, state);\n    }\n    const timestamp = entry.timestamp || new Date().toISOString();\n\n    await state.appendTrace(sessionId, {\n      level: entry.status === 'error' ? 'error' : 'info',\n      event: entry.status === 'error' ? 'mcp.tool.failed' : 'mcp.tool.succeeded',\n      message: `${entry.tool}.${entry.action} ${entry.status}`,\n      data: {\n        transport: 'mcp',\n        tool: entry.tool,\n        action: entry.action,\n        status: entry.status,\n        timestamp,\n        ...(entry.details ? { details: sanitizeDetails(entry.details) } : {}),\n        ...(entry.error ? { error: entry.error } : {}),\n      },\n    });\n  } catch {\n    // Logging should never block tool execution.\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/agent.ts",
    "content": "/**\n * Agent Gateway Handler\n *\n * Handles agent orchestration and discovery operations.\n * Replaces: discoverAgents, getAgentInfo, orchestrateAgents, getAgentSequence,\n *           getAgentDocs, getPhaseDocs, listAgentTypes\n */\n\nimport {\n  AgentType,\n} from '../../../workflow';\nimport { HarnessAgentsService } from '../../harness';\n\nimport type { AgentParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface AgentOptions {\n  repoPath: string;\n}\n\n/**\n * Handles agent gateway actions for orchestration and discovery.\n */\nexport async function handleAgent(\n  params: AgentParams,\n  options: AgentOptions\n): Promise<MCPToolResponse> {\n  const repoPath = options.repoPath || process.cwd();\n  const service = new HarnessAgentsService({ repoPath });\n\n  try {\n    switch (params.action) {\n      case 'discover': {\n        return createJsonResponse(await service.discover());\n      }\n\n      case 'getInfo': {\n        return createJsonResponse(await service.getInfo(params.agentType!));\n      }\n\n      case 'orchestrate': {\n        return createJsonResponse(service.orchestrate({\n          task: params.task,\n          phase: params.phase,\n          role: params.role,\n        }));\n      }\n\n      case 'getSequence': {\n        return createJsonResponse(service.getSequence({\n          phases: params.phases,\n          task: params.task,\n          includeReview: params.includeReview,\n        }));\n      }\n\n      case 'getDocs': {\n        return createJsonResponse(service.getDocs(params.agent! as AgentType));\n      }\n\n      case 'getPhaseDocs': {\n        return createJsonResponse(service.getPhaseDocs(params.phase!));\n      }\n\n      case 'listTypes': {\n        return createJsonResponse(service.listTypes());\n      }\n\n      default:\n        return createErrorResponse(`Unknown agent action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/context.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { SemanticContextBuilder } from '../../semantic/contextBuilder';\nimport { handleContext } from './context';\n\nfunction parseResponse(response: { content: Array<{ text: string }> }) {\n  return JSON.parse(response.content[0].text);\n}\n\ndescribe('handleContext', () => {\n  let tempDir: string;\n  let contextBuilder: SemanticContextBuilder;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-mcp-context-'));\n    contextBuilder = new SemanticContextBuilder();\n\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'context-gateway-test',\n      version: '1.0.0',\n    }, { spaces: 2 });\n\n    await fs.ensureDir(path.join(tempDir, 'src'));\n    await fs.writeFile(\n      path.join(tempDir, 'src', 'index.ts'),\n      'export function greet(name: string) { return `hello ${name}`; }\\n',\n      'utf-8'\n    );\n  });\n\n  afterEach(async () => {\n    await contextBuilder.shutdown();\n    await fs.remove(tempDir);\n  });\n\n  it('passes generateQA through context init and skips QA generation when disabled', async () => {\n    const response = await handleContext(\n      {\n        action: 'init',\n        repoPath: tempDir,\n        type: 'docs',\n        skipContentGeneration: true,\n        generateQA: false,\n      },\n      {\n        repoPath: tempDir,\n        contextBuilder,\n      }\n    );\n\n    const payload = parseResponse(response);\n\n    expect(payload.qaGenerated).toBe(0);\n    expect(payload.qaNote).toBeUndefined();\n    await expect(fs.pathExists(path.join(tempDir, '.context', 'docs', 'qa'))).resolves.toBe(false);\n  });\n\n  it('directs scaffoldPlan responses to start workflow-init on the harness', async () => {\n    const response = await handleContext(\n      {\n        action: 'scaffoldPlan',\n        repoPath: tempDir,\n        planName: 'feature-rollout',\n        autoFill: false,\n      },\n      {\n        repoPath: tempDir,\n        contextBuilder,\n      }\n    );\n\n    const payload = parseResponse(response);\n\n    expect(payload.success).toBe(true);\n    expect(payload._actionRequired).toBe(true);\n    expect(payload.enhancementPrompt).toContain('workflow-init({ name: \"feature-rollout\" })');\n    expect(payload.enhancementPrompt).toContain('.context/harness/workflows/prevc.json');\n    expect(payload.nextSteps).toContain(\n      'REQUIRED: Call workflow-init({ name: \"feature-rollout\" }) to start the harness-backed PREVC workflow'\n    );\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/gateway/context.ts",
    "content": "/**\n * Context Gateway Handler\n *\n * Handles context scaffolding and semantic context operations.\n * Replaces: checkScaffolding, initializeContext, fillScaffolding, listFilesToFill,\n *           fillSingleFile, getCodebaseMap, buildSemanticContext, scaffoldPlan\n *\n * New actions: searchQA, generateQA, getFlow, detectPatterns\n */\n\nimport { SemanticContextBuilder } from '../../semantic/contextBuilder';\nimport { HarnessContextService } from '../../harness';\n\nimport type { ContextParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse, createTextResponse, createScaffoldResponse } from './response';\n\nexport interface ContextOptions {\n  repoPath: string;\n  contextBuilder: SemanticContextBuilder;\n}\n\n/**\n * Handles context gateway actions for scaffolding and semantic context.\n */\nexport async function handleContext(\n  params: ContextParams,\n  options: ContextOptions\n): Promise<MCPToolResponse> {\n   const repoPath = params.repoPath || options.repoPath;\n   const service = new HarnessContextService({\n     repoPath: options.repoPath,\n     contextBuilder: options.contextBuilder,\n   });\n\n  try {\n    switch (params.action) {\n      case 'check': {\n        return createJsonResponse(await service.check(repoPath));\n      }\n\n      case 'bootstrapStatus': {\n        return createJsonResponse(await service.bootstrapStatus(repoPath));\n      }\n\n      case 'init': {\n        const { result, scaffold } = await service.init({\n          repoPath,\n          type: params.type,\n          outputDir: params.outputDir,\n          semantic: params.semantic,\n          include: params.include,\n          exclude: params.exclude,\n          autoFill: params.autoFill,\n          skipContentGeneration: params.skipContentGeneration,\n          generateQA: params.generateQA,\n        });\n        return createScaffoldResponse(result, scaffold || {});\n      }\n\n      case 'fill': {\n        return createJsonResponse(await service.fill({\n          repoPath,\n          outputDir: params.outputDir,\n          target: params.target,\n          offset: params.offset,\n          limit: params.limit,\n        }));\n      }\n\n      case 'fillSingle': {\n        return createJsonResponse(await service.fillSingle({\n          repoPath,\n          filePath: params.filePath!,\n        }));\n      }\n\n      case 'listToFill': {\n        return createJsonResponse(await service.listToFill({\n          repoPath,\n          outputDir: params.outputDir,\n          target: params.target,\n        }));\n      }\n\n      case 'getMap': {\n        return createJsonResponse(await service.getMap({\n          repoPath,\n          section: params.section,\n        }));\n      }\n\n      case 'buildSemantic': {\n        return createTextResponse(await service.buildSemantic({\n          repoPath,\n          contextType: params.contextType,\n          targetFile: params.targetFile,\n          options: params.options,\n        }));\n      }\n\n      case 'scaffoldPlan': {\n        const { result, scaffold } = await service.scaffoldPlan({\n          planName: params.planName!,\n          repoPath,\n          outputDir: params.outputDir,\n          title: params.title,\n          summary: params.summary,\n          semantic: params.semantic,\n          autoFill: params.autoFill,\n        });\n        return scaffold\n          ? createScaffoldResponse(result, scaffold)\n          : createJsonResponse(result);\n      }\n\n      case 'searchQA': {\n        if (!params.query) {\n          return createErrorResponse('Query is required for searchQA action');\n        }\n        return createJsonResponse(await service.searchQA({\n          repoPath,\n          query: params.query,\n          options: params.options,\n        }));\n      }\n\n      case 'generateQA': {\n        return createJsonResponse(await service.generateQA({\n          repoPath,\n          options: params.options,\n        }));\n      }\n\n      case 'getFlow': {\n        if (!params.entryFile) {\n          return createErrorResponse('entryFile is required for getFlow action');\n        }\n        return createJsonResponse(await service.getFlow({\n          repoPath,\n          entryFile: params.entryFile,\n          entryFunction: params.entryFunction,\n          options: params.options,\n        }));\n      }\n\n      case 'detectPatterns': {\n        return createJsonResponse(await service.detectPatterns({\n          repoPath,\n          options: params.options,\n        }));\n      }\n\n      default:\n        return createErrorResponse(`Unknown context action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/explore.ts",
    "content": "/**\n * Explore Gateway Handler\n *\n * Handles file and code exploration operations.\n * Replaces: readFile, listFiles, analyzeSymbols, searchCode, getFileStructure\n */\n\nimport {\n  readFileTool,\n  listFilesTool,\n  analyzeSymbolsTool,\n  getFileStructureTool,\n  searchCodeTool,\n} from '../../harness/contextTools';\n\nimport type { ExploreParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\nimport { toolContext } from './shared';\n\nexport interface ExploreOptions {\n  repoPath: string;\n}\n\n/**\n * Handles explore gateway actions for file and code exploration.\n */\nexport async function handleExplore(\n  params: ExploreParams,\n  options: ExploreOptions\n): Promise<MCPToolResponse> {\n  try {\n    switch (params.action) {\n      case 'read': {\n        const result = await readFileTool.execute!(\n          { filePath: params.filePath!, encoding: params.encoding },\n          toolContext\n        );\n        return createJsonResponse(result);\n      }\n\n      case 'list': {\n        const result = await listFilesTool.execute!(\n          {\n            pattern: params.pattern!,\n            cwd: params.cwd || options.repoPath,\n            ignore: params.ignore\n          },\n          toolContext\n        );\n        return createJsonResponse(result);\n      }\n\n      case 'analyze': {\n        const result = await analyzeSymbolsTool.execute!(\n          { filePath: params.filePath!, symbolTypes: params.symbolTypes },\n          toolContext\n        );\n        return createJsonResponse(result);\n      }\n\n      case 'search': {\n        const result = await searchCodeTool.execute!(\n          {\n            pattern: params.pattern!,\n            fileGlob: params.fileGlob,\n            maxResults: params.maxResults || 50,\n            cwd: params.cwd || options.repoPath\n          } as any,\n          toolContext\n        );\n        return createJsonResponse(result);\n      }\n\n      case 'getStructure': {\n        const result = await getFileStructureTool.execute!(\n          {\n            rootPath: params.rootPath || options.repoPath || '.',\n            maxDepth: params.maxDepth ?? 3,\n            includePatterns: params.includePatterns\n          },\n          toolContext\n        );\n        return createJsonResponse(result);\n      }\n\n      default:\n        return createErrorResponse(`Unknown explore action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/harness.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { handleHarness } from './harness';\n\ndescribe('handleHarness', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-harness-'));\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('creates sessions and records sensor-backed quality state', async () => {\n    const created = await handleHarness(\n      { action: 'createSession', name: 'runtime-check' },\n      { repoPath: tempDir }\n    );\n    const session = JSON.parse(created.content[0].text).session;\n\n    await handleHarness(\n      {\n        action: 'recordSensor',\n        sessionId: session.id,\n        sensorId: 'lint',\n        sensorName: 'Lint',\n        sensorSeverity: 'critical',\n        sensorBlocking: true,\n        sensorStatus: 'failed',\n        summary: 'Lint failed',\n        evidence: ['lint.log'],\n      },\n      { repoPath: tempDir }\n    );\n\n    const quality = await handleHarness(\n      { action: 'getSessionQuality', sessionId: session.id },\n      { repoPath: tempDir }\n    );\n    const payload = JSON.parse(quality.content[0].text);\n\n    expect(payload.success).toBe(true);\n    expect(payload.quality.backpressure.blocked).toBe(true);\n    expect(payload.quality.sensorRuns).toHaveLength(1);\n  });\n\n  it('registers and evaluates policy rules', async () => {\n    await handleHarness(\n      {\n        action: 'registerPolicy',\n        scope: 'artifact',\n        effect: 'deny',\n        pathPattern: 'src/secrets/*',\n        description: 'secret paths blocked',\n      },\n      { repoPath: tempDir }\n    );\n\n    const response = await handleHarness(\n      {\n        action: 'evaluatePolicy',\n        scope: 'artifact',\n        path: 'src/secrets/token.txt',\n      },\n      { repoPath: tempDir }\n    );\n    const payload = JSON.parse(response.content[0].text);\n\n    expect(payload.success).toBe(true);\n    expect(payload.evaluation.allowed).toBe(false);\n    expect(payload.evaluation.blocked).toBe(true);\n  });\n\n  it('passes approval metadata through policy evaluation', async () => {\n    await handleHarness(\n      {\n        action: 'registerPolicy',\n        effect: 'require_approval',\n        target: 'path',\n        pathPattern: 'src/secrets/*',\n        approvalRole: 'security',\n        description: 'secret paths require approval',\n      },\n      { repoPath: tempDir }\n    );\n\n    const response = await handleHarness(\n      {\n        action: 'evaluatePolicy',\n        scope: 'artifact',\n        path: 'src/secrets/token.txt',\n        approvedBy: 'alice',\n        approvalRole: 'security',\n      },\n      { repoPath: tempDir }\n    );\n    const payload = JSON.parse(response.content[0].text);\n\n    expect(payload.success).toBe(true);\n    expect(payload.evaluation.allowed).toBe(true);\n    expect(payload.evaluation.requiresApproval).toBe(false);\n  });\n\n  it('gets, sets, and resets policy documents', async () => {\n    const setResponse = await handleHarness(\n      {\n        action: 'setPolicy',\n        policy: {\n          defaultEffect: 'deny',\n          rules: [\n            {\n              id: 'allow-src',\n              effect: 'allow',\n              target: 'path',\n              pattern: 'src/**',\n            },\n          ],\n        },\n      },\n      { repoPath: tempDir }\n    );\n    const setPayload = JSON.parse(setResponse.content[0].text);\n    expect(setPayload.success).toBe(true);\n    expect(setPayload.policy.defaultEffect).toBe('deny');\n\n    const getResponse = await handleHarness(\n      { action: 'getPolicy' },\n      { repoPath: tempDir }\n    );\n    const getPayload = JSON.parse(getResponse.content[0].text);\n    expect(getPayload.success).toBe(true);\n    expect(getPayload.policy.rules).toHaveLength(1);\n\n    const resetResponse = await handleHarness(\n      { action: 'resetPolicy' },\n      { repoPath: tempDir }\n    );\n    const resetPayload = JSON.parse(resetResponse.content[0].text);\n    expect(resetPayload.success).toBe(true);\n    expect(Array.isArray(resetPayload.policy.rules)).toBe(true);\n    expect(resetPayload.policy.rules.length).toBeGreaterThan(0);\n  });\n\n  it('replays sessions and builds failure datasets', async () => {\n    const created = await handleHarness(\n      { action: 'createSession', name: 'replay-dataset-run' },\n      { repoPath: tempDir }\n    );\n    const session = JSON.parse(created.content[0].text).session;\n\n    await handleHarness(\n      {\n        action: 'recordSensor',\n        sessionId: session.id,\n        sensorId: 'build',\n        sensorName: 'Build',\n        sensorSeverity: 'critical',\n        sensorBlocking: true,\n        sensorStatus: 'failed',\n        summary: 'Build failed',\n        evidence: ['build.log'],\n      },\n      { repoPath: tempDir }\n    );\n\n    const replayResponse = await handleHarness(\n      {\n        action: 'replaySession',\n        sessionId: session.id,\n        includePayloads: true,\n      },\n      { repoPath: tempDir }\n    );\n    const replayPayload = JSON.parse(replayResponse.content[0].text);\n\n    expect(replayPayload.success).toBe(true);\n    expect(replayPayload.replay.events.length).toBeGreaterThan(0);\n\n    const datasetResponse = await handleHarness(\n      {\n        action: 'buildDataset',\n        includeSuccessfulSessions: true,\n      },\n      { repoPath: tempDir }\n    );\n    const datasetPayload = JSON.parse(datasetResponse.content[0].text);\n\n    expect(datasetPayload.success).toBe(true);\n    expect(datasetPayload.dataset.failureCount).toBeGreaterThan(0);\n    expect(datasetPayload.dataset.clusterCount).toBeGreaterThan(0);\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/gateway/harness.ts",
    "content": "import {\n  HarnessExecutionService,\n  HarnessReplayService,\n  HarnessDatasetService,\n  type HarnessPolicyEffect,\n  type HarnessPolicyTarget,\n} from '../../harness';\n\nimport type { HarnessParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createErrorResponse, createJsonResponse } from './response';\n\nexport interface HarnessOptions {\n  repoPath: string;\n}\n\nfunction normalizePolicyEffect(effect?: string): HarnessPolicyEffect {\n  if (effect === 'allow' || effect === 'deny' || effect === 'require_approval') {\n    return effect;\n  }\n\n  if (effect === 'warn' || effect === 'review') {\n    return 'require_approval';\n  }\n\n  return 'allow';\n}\n\nfunction normalizePolicyTarget(target?: string, pathPattern?: string, risk?: string): HarnessPolicyTarget {\n  if (target === 'tool' || target === 'action' || target === 'path' || target === 'risk') {\n    return target;\n  }\n\n  if (pathPattern) {\n    return 'path';\n  }\n\n  if (risk) {\n    return 'risk';\n  }\n\n  return 'action';\n}\n\nexport async function handleHarness(\n  params: HarnessParams,\n  options: HarnessOptions\n): Promise<MCPToolResponse> {\n  const service = new HarnessExecutionService({ repoPath: options.repoPath });\n  const replayService = new HarnessReplayService({ repoPath: options.repoPath });\n  const datasetService = new HarnessDatasetService({ repoPath: options.repoPath });\n\n  try {\n    const inferPolicyAction = () => {\n      switch (params.scope) {\n        case 'artifact':\n          return 'addArtifact';\n        case 'sensor':\n          return 'runSensor';\n        case 'handoff':\n          return 'createHandoff';\n        case 'task':\n          return 'createTask';\n        case 'workflow':\n        default:\n          return 'workflow';\n      }\n    };\n\n    switch (params.action) {\n      case 'createSession':\n        return createJsonResponse({\n          success: true,\n          session: await service.createSession({\n            name: params.name!,\n            metadata: params.metadata,\n          }),\n        });\n      case 'listSessions':\n        return createJsonResponse({\n          success: true,\n          sessions: await service.listSessions(),\n        });\n      case 'getSession':\n        return createJsonResponse({\n          success: true,\n          session: await service.getSession(params.sessionId!),\n        });\n      case 'appendTrace':\n        return createJsonResponse({\n          success: true,\n          trace: await service.appendTrace(params.sessionId!, {\n            level: params.level!,\n            event: params.event!,\n            message: params.message!,\n            data: params.data,\n          }),\n        });\n      case 'listTraces':\n        return createJsonResponse({\n          success: true,\n          traces: await service.listTraces(params.sessionId!),\n        });\n      case 'addArtifact':\n        return createJsonResponse({\n          success: true,\n          artifact: await service.addArtifact(params.sessionId!, {\n            name: params.name!,\n            kind: params.kind,\n            content: params.content,\n            path: params.path,\n            metadata: params.metadata,\n          }),\n        });\n      case 'listArtifacts':\n        return createJsonResponse({\n          success: true,\n          artifacts: await service.listArtifacts(params.sessionId!),\n        });\n      case 'checkpoint':\n        return createJsonResponse({\n          success: true,\n          session: await service.checkpointSession(params.sessionId!, {\n            note: params.note,\n            data: params.data,\n            artifactIds: params.artifactIds,\n            pause: params.pause,\n          }),\n        });\n      case 'resumeSession':\n        return createJsonResponse({\n          success: true,\n          session: await service.resumeSession(params.sessionId!),\n        });\n      case 'completeSession':\n        return createJsonResponse({\n          success: true,\n          session: await service.completeSession(params.sessionId!, params.note),\n        });\n      case 'failSession':\n        return createJsonResponse({\n          success: true,\n          session: await service.failSession(params.sessionId!, params.message!),\n        });\n      case 'recordSensor':\n        return createJsonResponse({\n          success: true,\n          sensorRun: await service.runSensor({\n            id: params.sensorId!,\n            name: params.sensorName || params.sensorId!,\n            description: params.description,\n            severity: params.sensorSeverity,\n            blocking: params.sensorBlocking,\n            execute: async () => ({\n              status: params.sensorStatus!,\n              summary: params.summary!,\n              evidence: params.evidence,\n              output: params.output,\n              details: params.details,\n            }),\n          }, {\n            sessionId: params.sessionId!,\n            contractId: params.taskId,\n            context: params.data,\n            metadata: params.metadata,\n          }),\n        });\n      case 'getSessionQuality':\n        return createJsonResponse({\n          success: true,\n          quality: await service.getSessionQuality(params.sessionId!, {\n            taskId: params.taskId,\n            policy: {\n              blockOnWarnings: params.blockOnWarnings,\n              requireEvidence: params.requireEvidence,\n            },\n          }),\n        });\n      case 'createTask':\n        return createJsonResponse({\n          success: true,\n          task: await service.createTaskContract({\n            title: params.title!,\n            description: params.description,\n            sessionId: params.sessionId,\n            owner: params.owner,\n            inputs: params.inputs,\n            expectedOutputs: params.expectedOutputs,\n            acceptanceCriteria: params.acceptanceCriteria,\n            requiredSensors: params.requiredSensors,\n            requiredArtifacts: params.requiredArtifacts,\n            status: params.status,\n            metadata: params.metadata,\n          }),\n        });\n      case 'listTasks':\n        return createJsonResponse({\n          success: true,\n          tasks: await service.listTaskContracts(),\n        });\n      case 'evaluateTask':\n        return createJsonResponse({\n          success: true,\n          evaluation: await service.evaluateTaskCompletion(params.taskId!, params.sessionId),\n        });\n      case 'createHandoff':\n        return createJsonResponse({\n          success: true,\n          handoff: await service.createHandoffContract({\n            from: params.from!,\n            to: params.to!,\n            sessionId: params.sessionId,\n            taskId: params.taskId,\n            artifacts: params.artifacts,\n            evidence: params.evidence,\n            metadata: params.metadata,\n          }),\n        });\n      case 'listHandoffs':\n        return createJsonResponse({\n          success: true,\n          handoffs: await service.listHandoffContracts(),\n        });\n      case 'replaySession':\n        return createJsonResponse({\n          success: true,\n          replay: await replayService.replaySession(params.sessionId!, {\n            includePayloads: params.includePayloads,\n            maxEvents: params.maxEvents,\n          }),\n        });\n      case 'listReplays':\n        return createJsonResponse({\n          success: true,\n          replays: await replayService.listReplays(\n            params.sessionId ? { sessionId: params.sessionId } : undefined\n          ),\n        });\n      case 'getReplay':\n        return createJsonResponse({\n          success: true,\n          replay: await replayService.getReplay(params.replayId!),\n        });\n      case 'buildDataset':\n        return createJsonResponse({\n          success: true,\n          dataset: await datasetService.buildFailureDataset({\n            sessionIds: params.sessionIds,\n            includeSuccessfulSessions: params.includeSuccessfulSessions,\n          }),\n        });\n      case 'listDatasets':\n        return createJsonResponse({\n          success: true,\n          datasets: await datasetService.listDatasets(),\n        });\n      case 'getDataset':\n        return createJsonResponse({\n          success: true,\n          dataset: await datasetService.getDataset(params.datasetId!),\n        });\n      case 'getFailureClusters':\n        return createJsonResponse({\n          success: true,\n          clusters: await datasetService.getFailureClusters(params.datasetId!),\n        });\n      case 'registerPolicy':\n        if (!params.effect) {\n          return createErrorResponse('registerPolicy requires effect');\n        }\n        const effect: HarnessPolicyEffect = params.effect === 'allow' || params.effect === 'deny'\n          ? params.effect\n          : 'require_approval';\n        const target: HarnessPolicyTarget = params.target === 'tool'\n          || params.target === 'action'\n          || params.target === 'path'\n          || params.target === 'risk'\n          ? params.target\n          : params.pathPattern\n            ? 'path'\n            : params.risk\n              ? 'risk'\n              : 'action';\n        return createJsonResponse({\n          success: true,\n          rule: await service.registerPolicy({\n            id: params.name || `policy-${Date.now()}`,\n            effect,\n            target,\n            pattern: params.pattern || params.pathPattern || params.risk || inferPolicyAction(),\n            approvalRole: params.owner,\n            reason: params.description,\n          }),\n        });\n      case 'listPolicies':\n        return createJsonResponse({\n          success: true,\n          rules: await service.listPolicies(),\n        });\n      case 'getPolicy':\n        return createJsonResponse({\n          success: true,\n          policy: await service.getPolicy(),\n        });\n      case 'setPolicy':\n        if (!params.policy) {\n          return createErrorResponse('setPolicy requires policy');\n        }\n        return createJsonResponse({\n          success: true,\n          policy: await service.setPolicy({\n            defaultEffect: params.policy.defaultEffect,\n            rules: (params.policy.rules ?? []).map((rule, index) => ({\n              id: rule.id ?? `policy-${Date.now()}-${index}`,\n              effect: normalizePolicyEffect(rule.effect),\n              target: normalizePolicyTarget(rule.target, rule.pathPattern, rule.scope === 'risk' ? 'high' : undefined),\n              pattern: rule.pattern ?? rule.pathPattern ?? rule.scope ?? 'harness',\n              approvalRole: rule.approvalRole,\n              reason: rule.reason ?? rule.description,\n            })),\n          }),\n        });\n      case 'resetPolicy':\n        return createJsonResponse({\n          success: true,\n          policy: await service.resetPolicy(),\n        });\n      case 'evaluatePolicy':\n        const evaluationTarget = normalizePolicyTarget(params.target, params.pathPattern, params.risk);\n        const evaluationPath = params.pathPattern || params.path || (evaluationTarget === 'path' ? params.pattern : undefined);\n        const evaluationApproval = params.approvedBy || params.approvalRole || params.approvalNote\n          ? {\n            approvedBy: params.approvedBy,\n            note: params.approvalNote,\n          }\n          : undefined;\n        return createJsonResponse({\n          success: true,\n          evaluation: await service.evaluatePolicy({\n            tool: params.scope === 'workflow' ? 'workflow' : 'harness',\n            action: evaluationTarget === 'action'\n              ? (params.pattern || inferPolicyAction())\n              : inferPolicyAction(),\n            paths: evaluationPath ? [evaluationPath] : undefined,\n            risk: evaluationTarget === 'risk'\n              && (params.pattern === 'low' || params.pattern === 'medium' || params.pattern === 'high' || params.pattern === 'critical')\n              ? params.pattern\n              : params.risk,\n            metadata: params.metadata,\n            approval: evaluationApproval,\n            approvalRole: params.approvalRole,\n          }),\n        });\n      default:\n        return createErrorResponse(`Unknown harness action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/index.ts",
    "content": "/**\n * Gateway Tools Module\n *\n * Exports all gateway handlers, types, and response utilities.\n * This module consolidates MCP tools into gateway and dedicated workflow entry points.\n *\n * Note: Project tools (project-setup, project-report) have been removed.\n * Use context({ action: \"init\" }) for scaffolding and workflow-init for workflows instead.\n */\n\n// Response types and helpers\nexport {\n  type MCPToolResponse,\n  createJsonResponse,\n  createErrorResponse,\n  createTextResponse,\n} from './response';\n\n// Shared utilities\nexport { minimalUI, mockTranslate, toolContext } from './shared';\n\n// Type definitions\nexport type {\n  ExploreAction,\n  ContextAction,\n  SyncAction,\n  PlanAction,\n  AgentAction,\n  SkillAction,\n  HarnessAction,\n  ExploreParams,\n  ContextParams,\n  SyncParams,\n  PlanParams,\n  AgentParams,\n  SkillParams,\n  HarnessParams,\n} from './types';\n\n// Consolidated gateway handlers\nexport { handleExplore, type ExploreOptions } from './explore';\nexport { handleContext, type ContextOptions } from './context';\n\n// Dedicated workflow handlers (split from consolidated gateway)\nexport { handleWorkflowInit, type WorkflowInitParams, type WorkflowInitOptions } from './workflowInit';\nexport { handleWorkflowStatus, type WorkflowStatusParams, type WorkflowStatusOptions } from './workflowStatus';\nexport { handleWorkflowAdvance, type WorkflowAdvanceParams, type WorkflowAdvanceOptions } from './workflowAdvance';\nexport { handleWorkflowManage, type WorkflowManageParams, type WorkflowManageOptions } from './workflowManage';\n\nexport { handleSync, type SyncOptions } from './sync';\nexport { handlePlan, type PlanOptions } from './plan';\nexport { handleAgent, type AgentOptions } from './agent';\nexport { handleSkill, type SkillOptions } from './skill';\nexport { handleHarness, type HarnessOptions } from './harness';\n\n// Metrics\nexport {\n  handleMetricsAction,\n  recordContextQuery,\n  recordFileRead,\n  getMetrics,\n  getMetricsSummary,\n  resetMetrics,\n  type SessionMetrics,\n  type MetricsSummary,\n} from './metrics';\n"
  },
  {
    "path": "src/services/mcp/gateway/metrics.test.ts",
    "content": "import {\n  getMetricsSummary,\n  recordFileRead,\n  resetMetrics,\n} from './metrics';\n\ndescribe('metrics recommendations', () => {\n  beforeEach(() => {\n    resetMetrics();\n  });\n\n  afterEach(() => {\n    resetMetrics();\n  });\n\n  it('prioritizes semantic context and snapshots over Q&A generation for broad file reads', () => {\n    for (let index = 0; index < 25; index += 1) {\n      recordFileRead();\n    }\n\n    const summary = getMetricsSummary();\n\n    expect(summary.recommendations).toContain(\n      'Use buildSemantic or getMap to reduce broad file reads'\n    );\n    expect(summary.recommendations).toContain(\n      'Treat generateQA/searchQA as optional helpers; start with semantic context and snapshots'\n    );\n  });\n\n  it('does not suggest searchQA before the session has enough file-read pressure', () => {\n    for (let index = 0; index < 5; index += 1) {\n      recordFileRead();\n    }\n\n    const summary = getMetricsSummary();\n\n    expect(summary.recommendations).not.toContain(\n      'Use getMap or buildSemantic to inspect the codebase before reading many files'\n    );\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/gateway/metrics.ts",
    "content": "/**\n * Metrics Service\n *\n * Tracks context tool usage vs file reads to measure improvement.\n * Helps evaluate the effectiveness of pre-computed context.\n */\n\n/**\n * Session metrics structure\n */\nexport interface SessionMetrics {\n  sessionId: string;\n  startTime: string;\n  contextQueries: number;\n  qaSearches: number;\n  flowQueries: number;\n  patternDetections: number;\n  fileReads: number;\n  tokensEstimate: number;\n  lastUpdated: string;\n}\n\n/**\n * Metrics summary\n */\nexport interface MetricsSummary {\n  contextQueries: number;\n  fileReads: number;\n  ratio: number;\n  efficiency: 'high' | 'medium' | 'low';\n  tokensSaved: number;\n  recommendations: string[];\n}\n\n/**\n * In-memory metrics storage for the current session\n */\nclass MetricsStore {\n  private metrics: SessionMetrics;\n\n  constructor() {\n    this.metrics = this.createNewSession();\n  }\n\n  private createNewSession(): SessionMetrics {\n    return {\n      sessionId: `session_${Date.now()}`,\n      startTime: new Date().toISOString(),\n      contextQueries: 0,\n      qaSearches: 0,\n      flowQueries: 0,\n      patternDetections: 0,\n      fileReads: 0,\n      tokensEstimate: 0,\n      lastUpdated: new Date().toISOString(),\n    };\n  }\n\n  recordContextQuery(type: 'qa' | 'flow' | 'pattern' | 'other'): void {\n    this.metrics.contextQueries++;\n    this.metrics.lastUpdated = new Date().toISOString();\n\n    switch (type) {\n      case 'qa':\n        this.metrics.qaSearches++;\n        break;\n      case 'flow':\n        this.metrics.flowQueries++;\n        break;\n      case 'pattern':\n        this.metrics.patternDetections++;\n        break;\n    }\n  }\n\n  recordFileRead(estimatedTokens: number = 1000): void {\n    this.metrics.fileReads++;\n    this.metrics.tokensEstimate += estimatedTokens;\n    this.metrics.lastUpdated = new Date().toISOString();\n  }\n\n  getMetrics(): SessionMetrics {\n    return { ...this.metrics };\n  }\n\n  getSummary(): MetricsSummary {\n    const { contextQueries, fileReads, tokensEstimate } = this.metrics;\n\n    // Calculate ratio (higher is better - more context queries, fewer file reads)\n    const ratio = fileReads > 0 ? contextQueries / fileReads : contextQueries > 0 ? Infinity : 0;\n\n    // Estimate tokens saved (average Q&A response ~500 tokens vs file read ~1000 tokens)\n    const tokensSaved = contextQueries * 500;\n\n    // Determine efficiency level\n    let efficiency: 'high' | 'medium' | 'low';\n    if (ratio >= 2 || (contextQueries > 5 && fileReads < 10)) {\n      efficiency = 'high';\n    } else if (ratio >= 0.5 || contextQueries > 0) {\n      efficiency = 'medium';\n    } else {\n      efficiency = 'low';\n    }\n\n    // Generate recommendations\n    const recommendations: string[] = [];\n\n    if (fileReads > 20 && contextQueries < 5) {\n      recommendations.push('Use buildSemantic or getMap to reduce broad file reads');\n    } else if (this.metrics.qaSearches === 0 && fileReads > 8) {\n      recommendations.push('Use getMap or buildSemantic to inspect the codebase before reading many files');\n    }\n\n    if (efficiency === 'low') {\n      recommendations.push('Treat generateQA/searchQA as optional helpers; start with semantic context and snapshots');\n    }\n\n    if (this.metrics.flowQueries === 0 && fileReads > 5) {\n      recommendations.push('Use getFlow to understand execution paths instead of tracing through files');\n    }\n\n    if (this.metrics.patternDetections === 0 && fileReads > 10) {\n      recommendations.push('Use detectPatterns to understand codebase capabilities');\n    }\n\n    return {\n      contextQueries,\n      fileReads,\n      ratio: Number.isFinite(ratio) ? Math.round(ratio * 100) / 100 : -1,\n      efficiency,\n      tokensSaved,\n      recommendations: recommendations.slice(0, 3),\n    };\n  }\n\n  reset(): void {\n    this.metrics = this.createNewSession();\n  }\n}\n\n// Singleton instance\nconst metricsStore = new MetricsStore();\n\n/**\n * Record a context query (Q&A search, flow query, pattern detection)\n */\nexport function recordContextQuery(type: 'qa' | 'flow' | 'pattern' | 'other'): void {\n  metricsStore.recordContextQuery(type);\n}\n\n/**\n * Record a file read operation\n */\nexport function recordFileRead(estimatedTokens?: number): void {\n  metricsStore.recordFileRead(estimatedTokens);\n}\n\n/**\n * Get current session metrics\n */\nexport function getMetrics(): SessionMetrics {\n  return metricsStore.getMetrics();\n}\n\n/**\n * Get metrics summary with recommendations\n */\nexport function getMetricsSummary(): MetricsSummary {\n  return metricsStore.getSummary();\n}\n\n/**\n * Reset metrics for a new session\n */\nexport function resetMetrics(): void {\n  metricsStore.reset();\n}\n\n/**\n * Handle metrics gateway action\n */\nexport function handleMetricsAction(action: 'get' | 'summary' | 'reset'): object {\n  switch (action) {\n    case 'get':\n      return getMetrics();\n    case 'summary':\n      return getMetricsSummary();\n    case 'reset':\n      resetMetrics();\n      return { success: true, message: 'Metrics reset' };\n    default:\n      return { error: `Unknown metrics action: ${action}` };\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/plan.ts",
    "content": "/**\n * Plan Gateway Handler\n *\n * Handles plan management and execution tracking operations.\n * Replaces: linkPlan, getLinkedPlans, getPlanDetails, getPlansForPhase,\n *           updatePlanPhase, recordDecision, updatePlanStep, getPlanExecutionStatus,\n *           syncPlanMarkdown\n */\n\nimport { HarnessPlansService } from '../../harness';\n\nimport type { PlanParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface PlanOptions {\n  repoPath: string;\n}\n\n/**\n * Handles plan gateway actions for plan management and execution tracking.\n */\nexport async function handlePlan(\n  params: PlanParams,\n  options: PlanOptions\n): Promise<MCPToolResponse> {\n  const repoPath = options.repoPath;\n  const service = new HarnessPlansService({ repoPath });\n\n  try {\n    switch (params.action) {\n      case 'link': {\n        return createJsonResponse(await service.link(params.planSlug!));\n      }\n\n      case 'getLinked': {\n        return createJsonResponse(await service.getLinked());\n      }\n\n      case 'getDetails': {\n        return createJsonResponse(await service.getDetails(params.planSlug!));\n      }\n\n      case 'getForPhase': {\n        return createJsonResponse(await service.getForPhase(params.phase!));\n      }\n\n      case 'updatePhase': {\n        return createJsonResponse(await service.updatePhase(\n          params.planSlug!,\n          params.phaseId!,\n          params.status!\n        ));\n      }\n\n      case 'recordDecision': {\n        return createJsonResponse(await service.recordDecision({\n          planSlug: params.planSlug!,\n          title: params.title!,\n          description: params.description!,\n          phase: params.phase,\n          alternatives: params.alternatives,\n        }));\n      }\n\n      case 'updateStep': {\n        return createJsonResponse(await service.updateStep({\n          planSlug: params.planSlug!,\n          phaseId: params.phaseId!,\n          stepIndex: params.stepIndex!,\n          status: params.status!,\n          output: params.output,\n          notes: params.notes,\n        }));\n      }\n\n      case 'getStatus': {\n        return createJsonResponse(await service.getStatus(params.planSlug!));\n      }\n\n      case 'syncMarkdown': {\n        return createJsonResponse(await service.syncMarkdown(params.planSlug!));\n      }\n\n      case 'commitPhase': {\n        if (!params.planSlug || !params.phaseId) {\n          return createJsonResponse({\n            success: false,\n            error: 'planSlug and phaseId are required for commitPhase action',\n          });\n        }\n\n        return createJsonResponse(await service.commitPhase({\n          planSlug: params.planSlug,\n          phaseId: params.phaseId,\n          coAuthor: params.coAuthor,\n          stagePatterns: params.stagePatterns,\n          dryRun: params.dryRun,\n        }));\n      }\n\n      default:\n        return createErrorResponse(`Unknown plan action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/response.ts",
    "content": "/**\n * MCP Response Helpers\n *\n * Standardized response creation for MCP tool handlers.\n */\n\n/**\n * MCP Tool Response type that matches the MCP SDK CallToolResult interface.\n * Uses index signature for forward compatibility with SDK extensions.\n */\nexport interface MCPToolResponse {\n  [x: string]: unknown;\n  content: Array<{\n    type: 'text';\n    text: string;\n    annotations?: {\n      audience?: ('user' | 'assistant')[];\n      priority?: number;\n    };\n  }>;\n  isError?: boolean;\n}\n\n/**\n * Creates a successful JSON response for MCP tool handlers.\n */\nexport function createJsonResponse(data: unknown): MCPToolResponse {\n  return {\n    content: [{\n      type: 'text' as const,\n      text: JSON.stringify(data, null, 2)\n    }]\n  };\n}\n\n/**\n * Creates an error response for MCP tool handlers.\n */\nexport function createErrorResponse(error: unknown): MCPToolResponse {\n  return {\n    content: [{\n      type: 'text' as const,\n      text: JSON.stringify({\n        success: false,\n        error: error instanceof Error ? error.message : String(error)\n      }, null, 2)\n    }],\n    isError: true\n  };\n}\n\n/**\n * Creates a plain text response for MCP tool handlers.\n */\nexport function createTextResponse(text: string): MCPToolResponse {\n  return {\n    content: [{\n      type: 'text' as const,\n      text\n    }]\n  };\n}\n\n/**\n * Creates a scaffold response that includes the enhancement prompt.\n * This ensures AI agents always receive instructions to enhance generated scaffolding.\n */\nexport function createScaffoldResponse(\n  data: Record<string, unknown>,\n  options: {\n    filesGenerated?: number;\n    pendingFiles?: string[];\n    repoPath?: string;\n    enhancementPrompt?: string;\n    nextSteps?: string[];\n  } = {}\n): MCPToolResponse {\n  const { filesGenerated = 0, pendingFiles = [], repoPath, enhancementPrompt: customPrompt, nextSteps: customNextSteps } = options;\n  const hasFilesToEnhance = filesGenerated > 0 || pendingFiles.length > 0;\n  const hasCustomPrompt = customPrompt || customNextSteps;\n\n  // Build enhanced response with clear action signals\n  const enhancedData = {\n    // Original data\n    ...data,\n\n    // Action signals (appear first for visibility)\n    ...(hasFilesToEnhance || hasCustomPrompt) && {\n      _actionRequired: true,\n      _status: hasCustomPrompt && !hasFilesToEnhance ? 'ready' : 'incomplete',\n      _warning: hasCustomPrompt && !hasFilesToEnhance ? 'ACTION SUGGESTED' : 'SCAFFOLDING REQUIRES ENHANCEMENT',\n\n      // Enhancement instructions\n      enhancementPrompt: customPrompt || buildEnhancementPrompt(pendingFiles, repoPath),\n\n      // Clear next steps\n      nextSteps: customNextSteps || [\n        'Call context({ action: \"listToFill\" }) to get files needing content',\n        'For each file, call context({ action: \"fillSingle\", filePath: \"...\" })',\n        'Generate content based on the semantic context returned',\n        'Write enhanced content using the Write tool',\n      ],\n\n      // Files needing enhancement\n      ...(pendingFiles.length > 0 && {\n        pendingEnhancement: pendingFiles,\n        pendingCount: pendingFiles.length,\n      }),\n    },\n  };\n\n  return {\n    content: [{\n      type: 'text' as const,\n      text: JSON.stringify(enhancedData, null, 2)\n    }]\n  };\n}\n\n/**\n * Build enhancement prompt for scaffold files.\n */\nfunction buildEnhancementPrompt(pendingFiles: string[], repoPath?: string): string {\n  const filesList = pendingFiles.slice(0, 5).map((f, i) => `${i + 1}. ${f}`).join('\\n');\n  const moreFiles = pendingFiles.length > 5 ? `\\n... and ${pendingFiles.length - 5} more files` : '';\n\n  return `IMPORTANT ENHANCEMENT REQUIRED\n\nScaffolding has been created but files need codebase-specific content.\n\nFiles to enhance:\n${filesList}${moreFiles}\n\nREQUIRED WORKFLOW:\n1. Call context({ action: \"fillSingle\", filePath: \"<file>\" }) for each file\n2. Use the returned semantic context to generate rich content\n3. Write the enhanced content to the file\n\n${repoPath ? `Repository: ${repoPath}` : ''}\n\nDO NOT report completion until ALL files have been enhanced.`;\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/shared.ts",
    "content": "/**\n * Shared Utilities for Gateway Handlers\n *\n * Common utilities used across multiple gateway handlers.\n */\n\nexport {\n  minimalUI,\n  mockTranslate,\n  toolExecutionContext as toolContext,\n} from '../../shared';\n"
  },
  {
    "path": "src/services/mcp/gateway/skill.ts",
    "content": "/**\n * Skill Gateway Handler\n *\n * Handles skill management operations.\n * Replaces: listSkills, getSkillContent, getSkillsForPhase, scaffoldSkills,\n *           exportSkills, fillSkills\n */\n\nimport { HarnessSkillsService } from '../../harness';\n\nimport type { SkillParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface SkillOptions {\n  repoPath: string;\n}\n\n/**\n * Handles skill gateway actions for skill management.\n */\nexport async function handleSkill(\n  params: SkillParams,\n  options: SkillOptions\n): Promise<MCPToolResponse> {\n  const repoPath = options.repoPath || process.cwd();\n  const service = new HarnessSkillsService({ repoPath });\n\n  try {\n    switch (params.action) {\n      case 'list': {\n        return createJsonResponse(await service.list(params.includeContent));\n      }\n\n      case 'getContent': {\n        return createJsonResponse(await service.getContent(params.skillSlug!));\n      }\n\n      case 'getForPhase': {\n        return createJsonResponse(await service.getForPhase(params.phase!));\n      }\n\n      case 'scaffold': {\n        return createJsonResponse(await service.scaffold({\n          skills: params.skills,\n          includeBuiltIn: params.includeBuiltIn,\n        }));\n      }\n\n      case 'export': {\n        return createJsonResponse(await service.export({\n          preset: params.preset,\n          includeBuiltIn: params.includeBuiltIn,\n        }));\n      }\n\n      case 'fill': {\n        return createJsonResponse(await service.fill({\n          skills: params.skills,\n          includeBuiltIn: params.includeBuiltIn,\n        }));\n      }\n\n      default:\n        return createErrorResponse(`Unknown skill action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/sync.ts",
    "content": "/**\n * Sync Gateway Handler\n *\n * Handles import/export synchronization operations.\n * Replaces: exportRules, exportDocs, exportAgents, exportContext, exportSkills,\n *           reverseQuickSync, importDocs, importAgents, importSkills\n */\n\nimport { VERSION } from '../../../version';\nimport { ExportRulesService, ContextExportService } from '../../export';\nimport { ReverseQuickSyncService, ImportSkillsService } from '../../reverseSync';\nimport { ImportRulesService, ImportAgentsService } from '../../import';\nimport { SyncService } from '../../sync';\nimport type { PresetName } from '../../sync/types';\n\nimport type { SyncParams } from './types';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\nimport { minimalUI, mockTranslate } from './shared';\n\nexport interface SyncOptions {\n  repoPath: string;\n}\n\n/**\n * Handles sync gateway actions for import/export operations.\n */\nexport async function handleSync(\n  params: SyncParams,\n  options: SyncOptions\n): Promise<MCPToolResponse> {\n  const repoPath = params.repoPath || options.repoPath || process.cwd();\n\n  try {\n    switch (params.action) {\n      case 'exportRules': {\n        const exportService = new ExportRulesService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await exportService.run(repoPath, {\n          preset: params.preset,\n          force: params.force,\n          dryRun: params.dryRun\n        });\n\n        return createJsonResponse({\n          success: true,\n          filesCreated: result.filesCreated,\n          filesSkipped: result.filesSkipped,\n          filesFailed: result.filesFailed,\n          targets: result.targets,\n          errors: result.errors,\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      case 'exportDocs': {\n        const exportService = new ExportRulesService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await exportService.run(repoPath, {\n          preset: params.preset,\n          indexMode: params.indexMode,\n          force: params.force,\n          dryRun: params.dryRun\n        });\n\n        return createJsonResponse({\n          success: true,\n          filesCreated: result.filesCreated,\n          filesSkipped: result.filesSkipped,\n          filesFailed: result.filesFailed,\n          targets: result.targets,\n          errors: result.errors,\n          indexMode: params.indexMode || 'readme',\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      case 'exportAgents': {\n        const syncService = new SyncService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        await syncService.run({\n          source: '.context/agents',\n          preset: (params.preset || 'all') as PresetName,\n          mode: (params.mode || 'symlink') as 'symlink' | 'markdown',\n          force: params.force || false,\n          dryRun: params.dryRun || false,\n        });\n\n        return createJsonResponse({\n          success: true,\n          preset: params.preset,\n          mode: params.mode,\n          dryRun: params.dryRun || false,\n          message: `Agents exported to ${params.preset} targets`,\n        });\n      }\n\n      case 'exportContext': {\n        const contextExportService = new ContextExportService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await contextExportService.run(repoPath, {\n          preset: params.preset,\n          skipDocs: params.skipDocs,\n          skipAgents: params.skipAgents,\n          skipSkills: params.skipSkills,\n          docsIndexMode: params.docsIndexMode,\n          agentMode: params.agentMode,\n          includeBuiltInSkills: params.includeBuiltInSkills,\n          force: params.force,\n          dryRun: params.dryRun,\n        });\n\n        return createJsonResponse({\n          success: true,\n          docsExported: result.docsExported,\n          agentsExported: result.agentsExported,\n          skillsExported: result.skillsExported,\n          targets: result.targets,\n          errors: result.errors,\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      case 'exportSkills': {\n        const { SkillExportService } = require('../../export/skillExportService');\n        const exportService = new SkillExportService({\n          ui: minimalUI,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await exportService.run(repoPath, {\n          preset: params.preset,\n          includeBuiltIn: params.includeBuiltIn,\n          force: params.force,\n        });\n\n        return createJsonResponse({\n          success: result.filesCreated > 0,\n          targets: result.targets,\n          skillsExported: result.skillsExported,\n          filesCreated: result.filesCreated,\n          filesSkipped: result.filesSkipped,\n        });\n      }\n\n      case 'reverseSync': {\n        const service = new ReverseQuickSyncService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await service.run(repoPath, {\n          skipRules: params.skipRules || false,\n          skipAgents: params.skipAgents || false,\n          skipSkills: params.skipSkills || false,\n          mergeStrategy: params.mergeStrategy || 'skip',\n          dryRun: params.dryRun || false,\n          force: params.force || false,\n          metadata: params.addMetadata !== false,\n        });\n\n        return createJsonResponse({\n          success: true,\n          ...result,\n        });\n      }\n\n      case 'importDocs': {\n        const service = new ImportRulesService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        await service.run({\n          autoDetect: params.autoDetect !== false,\n          force: params.force || false,\n          dryRun: params.dryRun || false,\n        }, repoPath);\n\n        return createJsonResponse({\n          success: true,\n          message: 'Docs imported successfully',\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      case 'importAgents': {\n        const service = new ImportAgentsService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        await service.run({\n          autoDetect: params.autoDetect !== false,\n          force: params.force || false,\n          dryRun: params.dryRun || false,\n        }, repoPath);\n\n        return createJsonResponse({\n          success: true,\n          message: 'Agents imported successfully',\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      case 'importSkills': {\n        const service = new ImportSkillsService({\n          ui: minimalUI as any,\n          t: mockTranslate,\n          version: VERSION,\n        });\n\n        const result = await service.run({\n          autoDetect: params.autoDetect !== false,\n          mergeStrategy: params.mergeStrategy || 'skip',\n          force: params.force || false,\n          dryRun: params.dryRun || false,\n        }, repoPath);\n\n        return createJsonResponse({\n          success: true,\n          skillsImported: result.filesCreated,\n          filesSkipped: result.filesSkipped,\n          filesFailed: result.filesFailed,\n          dryRun: params.dryRun || false,\n        });\n      }\n\n      default:\n        return createErrorResponse(`Unknown sync action: ${params.action}`);\n    }\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/types.ts",
    "content": "/**\n * Gateway Tool Types\n *\n * Type definitions for MCP gateway tool parameters and responses.\n *\n * Note: Some gateways use dedicated tools instead of action-based routing:\n * - Workflow: Split into workflow-init, workflow-status, workflow-advance, workflow-manage\n * - Project: Removed - use context init + workflow-init instead\n */\n\nimport type { PrevcPhase, PrevcRole, AgentType } from '../../../workflow';\n\n// Action types for each gateway\n// Note: Workflow uses dedicated tools (workflow-init, workflow-status, workflow-advance, workflow-manage)\n// Note: Project tools removed - use context init + workflow-init instead\nexport type ExploreAction = 'read' | 'list' | 'analyze' | 'search' | 'getStructure';\nexport type ContextAction = 'check' | 'bootstrapStatus' | 'init' | 'fill' | 'fillSingle' | 'listToFill' | 'getMap' | 'buildSemantic' | 'scaffoldPlan' | 'searchQA' | 'generateQA' | 'getFlow' | 'detectPatterns';\nexport type SyncAction = 'exportRules' | 'exportDocs' | 'exportAgents' | 'exportContext' | 'exportSkills' | 'reverseSync' | 'importDocs' | 'importAgents' | 'importSkills';\nexport type PlanAction = 'link' | 'getLinked' | 'getDetails' | 'getForPhase' | 'updatePhase' | 'recordDecision' | 'updateStep' | 'getStatus' | 'syncMarkdown' | 'commitPhase';\nexport type AgentAction = 'discover' | 'getInfo' | 'orchestrate' | 'getSequence' | 'getDocs' | 'getPhaseDocs' | 'listTypes';\nexport type SkillAction = 'list' | 'getContent' | 'getForPhase' | 'scaffold' | 'export' | 'fill';\nexport type HarnessAction =\n  | 'createSession'\n  | 'listSessions'\n  | 'getSession'\n  | 'appendTrace'\n  | 'listTraces'\n  | 'addArtifact'\n  | 'listArtifacts'\n  | 'checkpoint'\n  | 'resumeSession'\n  | 'completeSession'\n  | 'failSession'\n  | 'recordSensor'\n  | 'getSessionQuality'\n  | 'createTask'\n  | 'listTasks'\n  | 'evaluateTask'\n  | 'createHandoff'\n  | 'listHandoffs'\n  | 'replaySession'\n  | 'listReplays'\n  | 'getReplay'\n  | 'buildDataset'\n  | 'listDatasets'\n  | 'getDataset'\n  | 'getFailureClusters'\n  | 'registerPolicy'\n  | 'listPolicies'\n  | 'evaluatePolicy'\n  | 'getPolicy'\n  | 'setPolicy'\n  | 'resetPolicy';\n\n// Parameter interfaces for each gateway\nexport interface ExploreParams {\n  action: ExploreAction;\n  filePath?: string;\n  pattern?: string;\n  cwd?: string;\n  encoding?: 'utf-8' | 'ascii' | 'binary';\n  ignore?: string[];\n  symbolTypes?: Array<'class' | 'interface' | 'function' | 'type' | 'enum'>;\n  fileGlob?: string;\n  maxResults?: number;\n  rootPath?: string;\n  maxDepth?: number;\n  includePatterns?: string[];\n}\n\nexport interface ContextParams {\n  action: ContextAction;\n  repoPath?: string;\n  outputDir?: string;\n  type?: 'docs' | 'agents' | 'both';\n  semantic?: boolean;\n  include?: string[];\n  exclude?: string[];\n  autoFill?: boolean;\n  skipContentGeneration?: boolean;\n  generateQA?: boolean;\n  target?: 'docs' | 'agents' | 'skills' | 'plans' | 'sensors' | 'all';\n  offset?: number;\n  limit?: number;\n  filePath?: string;\n  section?: string;\n  contextType?: 'documentation' | 'playbook' | 'plan' | 'compact';\n  targetFile?: string;\n  options?: {\n    useLSP?: boolean;\n    maxContextLength?: number;\n    includeDocumentation?: boolean;\n    includeSignatures?: boolean;\n  };\n  planName?: string;\n  title?: string;\n  summary?: string;\n  // Q&A and flow parameters\n  query?: string;\n  entryFile?: string;\n  entryFunction?: string;\n}\n\n// Note: WorkflowParams removed - workflow uses dedicated tools with their own param types\n// Note: ProjectParams removed - project tools have been removed from MCP\n\nexport interface SyncParams {\n  action: SyncAction;\n  preset?: string;\n  force?: boolean;\n  dryRun?: boolean;\n  indexMode?: 'readme' | 'all';\n  mode?: 'symlink' | 'markdown';\n  skipDocs?: boolean;\n  skipAgents?: boolean;\n  skipSkills?: boolean;\n  skipRules?: boolean;\n  docsIndexMode?: 'readme' | 'all';\n  agentMode?: 'symlink' | 'markdown';\n  includeBuiltInSkills?: boolean;\n  mergeStrategy?: 'skip' | 'overwrite' | 'merge' | 'rename';\n  autoDetect?: boolean;\n  addMetadata?: boolean;\n  repoPath?: string;\n  includeBuiltIn?: boolean;\n}\n\nexport interface PlanParams {\n  action: PlanAction;\n  planSlug?: string;\n  phaseId?: string;\n  status?: 'pending' | 'in_progress' | 'completed' | 'skipped';\n  phase?: PrevcPhase;\n  title?: string;\n  description?: string;\n  alternatives?: string[];\n  stepIndex?: number;\n  output?: string;\n  notes?: string;\n  // commitPhase action parameters\n  coAuthor?: string;\n  stagePatterns?: string[];\n  dryRun?: boolean;\n}\n\nexport interface AgentParams {\n  action: AgentAction;\n  agentType?: string;\n  task?: string;\n  phase?: PrevcPhase;\n  role?: PrevcRole;\n  includeReview?: boolean;\n  phases?: PrevcPhase[];\n  agent?: AgentType;\n}\n\nexport interface SkillParams {\n  action: SkillAction;\n  skillSlug?: string;\n  phase?: PrevcPhase;\n  skills?: string[];\n  includeContent?: boolean;\n  includeBuiltIn?: boolean;\n  preset?: string;\n}\n\nexport interface HarnessParams {\n  action: HarnessAction;\n  sessionId?: string;\n  taskId?: string;\n  name?: string;\n  title?: string;\n  description?: string;\n  owner?: string;\n  status?: 'draft' | 'ready' | 'in_progress' | 'blocked' | 'completed' | 'failed';\n  metadata?: Record<string, unknown>;\n  level?: 'debug' | 'info' | 'warn' | 'error';\n  event?: string;\n  message?: string;\n  data?: Record<string, unknown>;\n  kind?: 'text' | 'json' | 'file';\n  content?: unknown;\n  path?: string;\n  note?: string;\n  artifactIds?: string[];\n  pause?: boolean;\n  sensorId?: string;\n  sensorName?: string;\n  sensorSeverity?: 'info' | 'warning' | 'critical';\n  sensorBlocking?: boolean;\n  sensorStatus?: 'passed' | 'failed' | 'skipped' | 'blocked';\n  summary?: string;\n  evidence?: string[];\n  output?: unknown;\n  details?: Record<string, unknown>;\n  blockOnWarnings?: boolean;\n  requireEvidence?: boolean;\n  inputs?: string[];\n  expectedOutputs?: string[];\n  acceptanceCriteria?: string[];\n  requiredSensors?: string[];\n  requiredArtifacts?: string[];\n  from?: string;\n  to?: string;\n  artifacts?: string[];\n  replayId?: string;\n  includePayloads?: boolean;\n  maxEvents?: number;\n  datasetId?: string;\n  sessionIds?: string[];\n  includeSuccessfulSessions?: boolean;\n  scope?: 'sensor' | 'artifact' | 'handoff' | 'workflow' | 'task' | 'risk';\n  effect?: 'allow' | 'deny' | 'require_approval';\n  target?: 'tool' | 'action' | 'path' | 'risk';\n  pattern?: string;\n  pathPattern?: string;\n  approvalRole?: string;\n  approvedBy?: string;\n  approvalNote?: string;\n  risk?: 'low' | 'medium' | 'high' | 'critical';\n  policy?: {\n    defaultEffect?: 'allow' | 'deny';\n    rules?: Array<{\n      id?: string;\n      effect: 'allow' | 'deny' | 'require_approval';\n      target?: 'tool' | 'action' | 'path' | 'risk';\n      pattern?: string;\n      pathPattern?: string;\n      approvalRole?: string;\n      reason?: string;\n      description?: string;\n      scope?: 'sensor' | 'artifact' | 'handoff' | 'workflow' | 'task' | 'risk';\n    }>;\n  };\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowAdvance.ts",
    "content": "/**\n * Workflow Advance Handler\n *\n * Handles advancing PREVC workflow to the next phase.\n */\n\nimport * as path from 'path';\nimport { WorkflowService } from '../../workflow';\nimport {\n  PHASE_NAMES_EN,\n  WorkflowGateError,\n} from '../../../workflow';\nimport { HarnessWorkflowBlockedError } from '../../workflow';\nimport { HarnessPolicyBlockedError } from '../../harness';\n\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface WorkflowAdvanceParams {\n  outputs?: string[];\n  force?: boolean;\n  repoPath?: string;\n}\n\nexport interface WorkflowAdvanceOptions {\n  repoPath: string;\n}\n\n/**\n * Advance workflow to the next PREVC phase (P→R→E→V→C).\n *\n * Enforces gates:\n * - P→R: Requires plan if require_plan=true\n * - R→E: Requires approval if require_approval=true\n *\n * Use force=true to bypass gates, or use workflow-manage({ action: 'setAutonomous' }).\n */\nexport async function handleWorkflowAdvance(\n  params: WorkflowAdvanceParams,\n  options: WorkflowAdvanceOptions\n): Promise<MCPToolResponse> {\n  try {\n    // Resolve repo path: use explicit param, then options\n    // options.repoPath is guaranteed to be valid by MCP server initialization\n    const repoPath = path.resolve(params.repoPath || options.repoPath);\n    const contextPath = path.join(repoPath, '.context');\n\n    // Create service\n    const service = await WorkflowService.create(repoPath);\n\n    if (!(await service.hasWorkflow())) {\n      return createJsonResponse({\n        success: false,\n        error: 'No workflow found. Initialize a workflow first.',\n        suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n        workflowStatePath: path.join(contextPath, 'harness', 'workflows', 'prevc.json')\n      });\n    }\n\n    try {\n      const nextPhase = await service.advance(params.outputs, { force: params.force });\n\n      if (nextPhase) {\n        const orchestration = await service.getPhaseOrchestration(nextPhase);\n        const phaseName = PHASE_NAMES_EN[nextPhase];\n        const startAgent = orchestration.startWith;\n\n        const response: Record<string, unknown> = {\n          success: true,\n          message: `Advanced to ${phaseName} phase`,\n          nextPhase: {\n            code: nextPhase,\n            name: phaseName,\n          },\n          orchestration,\n          // NEW: Quick start guidance for immediate action\n          quickStart: {\n            message: `Ready to start ${phaseName} phase`,\n            firstStep: `Call agent({ action: \"orchestrate\", phase: \"${nextPhase}\" }) to discover agents`,\n            agentPlaybook: `.context/agents/${startAgent}.md`,\n            nextActions: [\n              `1. Discover agents: agent({ action: \"orchestrate\", phase: \"${nextPhase}\" })`,\n              `2. Review sequence: agent({ action: \"getSequence\", phases: [\"${nextPhase}\"] })`,\n              `3. Begin with ${startAgent} - follow playbook at .context/agents/${startAgent}.md`,\n              `4. Use workflow-manage to execute handoffs between agents`,\n              `5. Call workflow-advance when phase is complete`,\n            ],\n          },\n        };\n\n        return createJsonResponse(response);\n      } else {\n        return createJsonResponse({\n          success: true,\n          message: 'Workflow completed!',\n          isComplete: true\n        });\n      }\n    } catch (error) {\n      const caughtError = error instanceof Error ? error : new Error(String(error));\n\n      if (caughtError instanceof HarnessPolicyBlockedError) {\n        return createJsonResponse({\n          success: false,\n          error: caughtError.message,\n          blockedBy: 'policy',\n          reasons: caughtError.decision.reasons,\n          policy: caughtError.decision.policy,\n        });\n      }\n\n      if (caughtError instanceof HarnessWorkflowBlockedError) {\n        return createJsonResponse({\n          success: false,\n          error: caughtError.message,\n          blockedBy: 'harness',\n          reasons: caughtError.reasons,\n          harness: caughtError.harnessStatus,\n          resolution: [\n            'Use workflow-manage({ action: \"runSensors\", sensors: [...] }) to run required sensors',\n            'Use workflow-manage({ action: \"recordArtifact\", ... }) to attach missing artifacts',\n            'Use workflow-manage({ action: \"defineTask\", ... }) to refresh the active task contract if it is stale',\n            'Use workflow-advance({ force: true }) only if you intentionally want to bypass harness checks',\n          ],\n        });\n      }\n\n      if (caughtError instanceof WorkflowGateError) {\n        const blockedGate = caughtError.message.includes('plan') ? 'plan_required' : 'approval_required';\n\n        return createJsonResponse({\n          success: false,\n          error: caughtError.message,\n          gate: caughtError.gate,\n          transition: caughtError.transition,\n          blockedGate,\n          hint: caughtError.hint,\n          resolution: blockedGate === 'plan_required'\n            ? 'Create and link a plan: plan({ action: \"link\", planSlug: \"plan-name\" })'\n            : 'Approve plan: workflow-manage({ action: \"approvePlan\", planSlug: \"plan-name\" })',\n          alternative: 'Use workflow-advance({ force: true }) to bypass gate',\n          autonomousMode: 'Or use workflow-manage({ action: \"setAutonomous\", enabled: true })'\n        });\n      }\n      throw caughtError;\n    }\n  } catch (error) {\n    const caughtError = error instanceof Error ? error : new Error(String(error));\n    return createErrorResponse(caughtError);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowInit.ts",
    "content": "/**\n * Workflow Init Handler\n *\n * Handles PREVC workflow initialization.\n * Primary entry point for AI to start workflows.\n */\n\nimport * as path from 'path';\nimport { WorkflowService } from '../../workflow';\nimport {\n  getScaleName,\n  ProjectScale,\n  PrevcPhase,\n} from '../../../workflow';\n\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface WorkflowInitParams {\n  name: string;\n  description?: string;\n  scale?: 'QUICK' | 'SMALL' | 'MEDIUM' | 'LARGE';\n  autonomous?: boolean;\n  require_plan?: boolean;\n  require_approval?: boolean;\n  archive_previous?: boolean;\n  repoPath?: string;\n}\n\nexport interface WorkflowInitOptions {\n  repoPath: string;\n}\n\n/**\n * Initialize a PREVC workflow.\n *\n * This is the primary method for AI to start workflows.\n * Use this after .context/ exists (use project-setup for first-time setup).\n */\nexport async function handleWorkflowInit(\n  params: WorkflowInitParams,\n  options: WorkflowInitOptions\n): Promise<MCPToolResponse> {\n  try {\n    // Resolve repo path: use explicit param, then options\n    // options.repoPath is guaranteed to be valid by MCP server initialization\n    const repoPath = path.resolve(params.repoPath || options.repoPath);\n    const contextPath = path.join(repoPath, '.context');\n\n    // Create service\n    const service = await WorkflowService.create(repoPath);\n\n    const status = await service.init({\n      name: params.name,\n      description: params.description,\n      scale: params.scale,\n      autonomous: params.autonomous,\n      requirePlan: params.require_plan,\n      requireApproval: params.require_approval,\n      archivePrevious: params.archive_previous,\n    });\n\n    const workflowStatePath = path.join(contextPath, 'harness', 'workflows', 'prevc.json');\n    const settings = await service.getSettings();\n    const scale = getScaleName(status.project.scale as ProjectScale);\n    const isAutonomous = settings.autonomous_mode;\n    const requiresPlan = settings.require_plan;\n    const orchestration = await service.getPhaseOrchestration(status.project.current_phase);\n    const harness = await service.getHarnessStatus();\n\n    // Build actionable enhancement prompt based on workflow state\n    const enhancementPrompt = buildWorkflowEnhancementPrompt({\n      name: params.name,\n      scale,\n      currentPhase: status.project.current_phase,\n      isAutonomous,\n      requiresPlan,\n    });\n\n    // Build next steps based on workflow configuration\n    const nextSteps = buildWorkflowNextSteps({\n      currentPhase: status.project.current_phase,\n      isAutonomous,\n      requiresPlan,\n    });\n\n    return createJsonResponse({\n      success: true,\n      message: `Workflow initialized: ${params.name}`,\n      scale,\n      currentPhase: status.project.current_phase,\n      phases: Object.keys(status.phases).filter(\n        (p) => status.phases[p as PrevcPhase].status !== 'skipped'\n      ),\n      settings: {\n        autonomous_mode: settings.autonomous_mode,\n        require_plan: settings.require_plan,\n        require_approval: settings.require_approval,\n      },\n      workflowStatePath,\n      contextPath,\n      orchestration,\n      harness: harness ? {\n        sessionId: harness.binding.sessionId,\n        sessionStatus: harness.session.status,\n        harnessPath: path.join(contextPath, 'harness'),\n      } : null,\n\n      // Action signals for AI agents\n      _actionRequired: true,\n      _status: 'workflow_active',\n      enhancementPrompt,\n      nextSteps,\n    });\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n\n/**\n * Build enhancement prompt for workflow initialization.\n */\nfunction buildWorkflowEnhancementPrompt(options: {\n  name: string;\n  scale: string;\n  currentPhase: string;\n  isAutonomous: boolean;\n  requiresPlan: boolean;\n}): string {\n  const { name, scale, currentPhase, isAutonomous, requiresPlan } = options;\n\n  if (isAutonomous) {\n    return `✓ WORKFLOW ACTIVE - AUTONOMOUS MODE\n\nWorkflow \"${name}\" initialized (${scale} scale).\nCurrent phase: ${currentPhase} (Plan)\n\nAUTONOMOUS MODE ENABLED - All gates bypassed.\n\nAGENT ORCHESTRATION:\n1. Use agent tool to discover agents: agent({ action: \"orchestrate\", phase: \"P\" })\n2. Use workflow-manage for handoffs: workflow-manage({ action: \"handoff\", from: \"agent-1\", to: \"agent-2\", artifacts: [...] })\n3. Collaborate when needed: workflow-manage({ action: \"collaborate\", topic: \"architecture\", participants: [...] })\n\nNEXT ACTIONS:\n1. Begin implementation work directly\n2. Use workflow-advance to move through phases as work progresses\n3. Use workflow-status to check current state at any time\n\nYou may proceed without creating formal plans or waiting for approvals.`;\n  }\n\n  if (requiresPlan) {\n    return `WORKFLOW ACTIVE - PLAN REQUIRED\n\nWorkflow \"${name}\" initialized (${scale} scale).\nCurrent phase: ${currentPhase} (Plan)\n\nGATE: Plan required before advancing to Review phase.\n\nAGENT ORCHESTRATION:\nThe Plan phase involves multiple agents working together:\n1. Discover planning agents: agent({ action: \"orchestrate\", phase: \"P\" })\n2. Get recommended sequence: agent({ action: \"getSequence\", phases: [\"P\"] })\n3. Start with first agent and execute handoffs between agents\n4. Example handoff: workflow-manage({ action: \"handoff\", from: \"architect-specialist\", to: \"documentation-writer\", artifacts: [\"design.md\"] })\n\nREQUIRED ACTIONS:\n1. Create a plan using context with action \"scaffoldPlan\" and planName parameter\n2. Fill the plan content using context with action \"fillSingle\"\n3. Link the plan using plan with action \"link\" and planSlug parameter\n4. Advance to Review phase using workflow-advance\n\nDo NOT attempt to advance without linking a plan - the gate will block you.`;\n  }\n\n  return `✓ WORKFLOW ACTIVE\n\nWorkflow \"${name}\" initialized (${scale} scale).\nCurrent phase: ${currentPhase} (Plan)\n\nAGENT ORCHESTRATION AVAILABLE:\nThis workflow supports multi-agent orchestration. Use these tools:\n- agent({ action: \"orchestrate\", phase: \"P\" }) - Discover agents for current phase\n- agent({ action: \"getSequence\", task: \"your task description\" }) - Get recommended agent sequence\n- workflow-manage({ action: \"handoff\", from: \"agent-1\", to: \"agent-2\", artifacts: [...] }) - Execute handoffs\n- workflow-manage({ action: \"collaborate\", topic: \"topic\", participants: [...] }) - Start collaboration\n\nRECOMMENDED ACTIONS:\n1. Create a plan using context with action \"scaffoldPlan\" (recommended for ${scale} scale)\n2. Or advance directly using workflow-advance if planning is not needed\n\nUse workflow-status to check current state at any time.`;\n}\n\n/**\n * Build next steps array for workflow initialization.\n */\nfunction buildWorkflowNextSteps(options: {\n  currentPhase: string;\n  isAutonomous: boolean;\n  requiresPlan: boolean;\n}): string[] {\n  const { isAutonomous, requiresPlan } = options;\n\n  if (isAutonomous) {\n    return [\n      'ENABLED: Begin implementation work directly (autonomous mode)',\n      'ACTION: Call agent({ action: \"orchestrate\", phase: \"P\" }) to discover agents',\n      'ACTION: Use workflow-manage({ action: \"handoff\", ... }) to execute agent transitions',\n      'OPTIONAL: Call workflow-advance to track phase progression',\n      'OPTIONAL: Call workflow-status to check current state',\n    ];\n  }\n\n  if (requiresPlan) {\n    return [\n      'REQUIRED: Call context with action \"scaffoldPlan\" to create a plan',\n      'RECOMMENDED: Call agent({ action: \"orchestrate\", phase: \"P\" }) to discover planning agents',\n      'RECOMMENDED: Use workflow-manage({ action: \"handoff\", ... }) for agent collaboration',\n      'REQUIRED: Call plan with action \"link\" to link plan to workflow',\n      'THEN: Call workflow-advance to move to Review phase',\n    ];\n  }\n\n  return [\n    'RECOMMENDED: Call agent({ action: \"orchestrate\", phase: \"P\" }) to discover agents',\n    'RECOMMENDED: Call context with action \"scaffoldPlan\" to create a plan',\n    'ALTERNATIVE: Call agent({ action: \"getSequence\", task: \"your task\" }) for task-based sequence',\n    'OPTIONAL: Use workflow-manage({ action: \"handoff\", ... }) for multi-agent work',\n    'ALTERNATIVE: Call workflow-advance to skip planning phase',\n    'OPTIONAL: Call workflow-status to check current state',\n  ];\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowManage.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { handlePlan } from './plan';\nimport { handleWorkflowAdvance } from './workflowAdvance';\nimport { handleWorkflowInit } from './workflowInit';\nimport { handleWorkflowManage } from './workflowManage';\n\nfunction parseResponse(response: { content: Array<{ text: string }> }) {\n  return JSON.parse(response.content[0].text);\n}\n\ndescribe('workflow MCP harness integration', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-mcp-workflow-'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'mcp-workflow-test',\n      version: '1.0.0',\n      scripts: {\n        build: 'node -e \"process.exit(0)\"',\n      },\n    }, { spaces: 2 });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('exposes defineTask, recordArtifact, and runSensors through workflow-manage', async () => {\n    await handleWorkflowInit({\n      name: 'delta',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    const task = parseResponse(await handleWorkflowManage({\n      action: 'defineTask',\n      taskTitle: 'Implement delta',\n      requiredSensors: ['build'],\n      requiredArtifacts: ['handoff-summary'],\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    const artifact = parseResponse(await handleWorkflowManage({\n      action: 'recordArtifact',\n      name: 'handoff-summary',\n      kind: 'text',\n      content: 'ready',\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    const sensors = parseResponse(await handleWorkflowManage({\n      action: 'runSensors',\n      sensors: ['build'],\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(task.success).toBe(true);\n    expect(task.task.title).toBe('Implement delta');\n    expect(artifact.success).toBe(true);\n    expect(artifact.artifact.name).toBe('handoff-summary');\n    expect(sensors.success).toBe(true);\n    expect(sensors.backpressure.blocked).toBe(false);\n  });\n\n  it('returns structured harness blocking info from workflow-advance', async () => {\n    await handleWorkflowInit({\n      name: 'epsilon',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    await handleWorkflowManage({\n      action: 'defineTask',\n      taskTitle: 'Implement epsilon',\n      requiredSensors: ['build'],\n      requiredArtifacts: ['handoff-summary'],\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    const response = parseResponse(await handleWorkflowAdvance({\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(response.success).toBe(false);\n    expect(response.blockedBy).toBe('harness');\n    expect(response.reasons).toContain('Missing required sensors: build');\n    expect(response.reasons).toContain('Missing required artifacts: handoff-summary');\n  });\n\n  it('returns structured policy blocking info from workflow-manage and workflow-advance', async () => {\n    await handleWorkflowInit({\n      name: 'zeta',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    await fs.ensureDir(path.join(tempDir, '.context', 'harness'));\n    await fs.writeJson(\n      path.join(tempDir, '.context', 'harness', 'policy.json'),\n      {\n        version: 1,\n        defaultEffect: 'allow',\n        rules: [\n          {\n            id: 'deny-artifact-record',\n            effect: 'deny',\n            when: {\n              tools: ['workflow'],\n              actions: ['recordArtifact'],\n            },\n            reason: 'artifact writes blocked for test',\n          },\n          {\n            id: 'deny-advance',\n            effect: 'deny',\n            when: {\n              tools: ['workflow'],\n              actions: ['advance'],\n            },\n            reason: 'advance blocked for test',\n          },\n        ],\n      },\n      { spaces: 2 }\n    );\n\n    const artifactResponse = parseResponse(await handleWorkflowManage({\n      action: 'recordArtifact',\n      name: 'handoff-summary',\n      kind: 'text',\n      content: 'ready',\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    const advanceResponse = parseResponse(await handleWorkflowAdvance({\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(artifactResponse.success).toBe(false);\n    expect(artifactResponse.blockedBy).toBe('policy');\n    expect(artifactResponse.reasons).toContain('artifact writes blocked for test');\n\n    expect(advanceResponse.success).toBe(false);\n    expect(advanceResponse.blockedBy).toBe('policy');\n    expect(advanceResponse.reasons).toContain('advance blocked for test');\n  });\n\n  it('persists plan approval metadata to the linked plan index and workflow state', async () => {\n    await handleWorkflowInit({\n      name: 'approval-persistence',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    await fs.ensureDir(path.join(tempDir, '.context', 'plans'));\n    await fs.writeFile(\n      path.join(tempDir, '.context', 'plans', 'core-plan.md'),\n      '# Core Plan\\n\\n> Approval persistence test.\\n',\n      'utf-8'\n    );\n\n    const linkResponse = parseResponse(await handlePlan({\n      action: 'link',\n      planSlug: 'core-plan',\n    }, { repoPath: tempDir }));\n\n    expect(linkResponse.success).toBe(true);\n    expect(linkResponse.workflowActive).toBe(true);\n    expect(linkResponse.planCreatedForGates).toBe(true);\n\n    const approveResponse = parseResponse(await handleWorkflowManage({\n      action: 'approvePlan',\n      planSlug: 'core-plan',\n      approver: 'reviewer',\n      notes: 'approved for execution',\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(approveResponse.success).toBe(true);\n    expect(approveResponse.plan.approval_status).toBe('approved');\n    expect(approveResponse.plan.approved_by).toBe('reviewer');\n    expect(approveResponse.plan.approved_at).toBeDefined();\n    expect(approveResponse.approval.plan_approved).toBe(true);\n\n    const plansIndex = await fs.readJson(path.join(tempDir, '.context', 'workflow', 'plans.json'));\n    const persistedPlan = [...plansIndex.active, ...plansIndex.completed].find((plan: { slug: string }) => plan.slug === 'core-plan');\n    expect(persistedPlan.approval_status).toBe('approved');\n    expect(persistedPlan.approved_by).toBe('reviewer');\n    expect(persistedPlan.approved_at).toBeDefined();\n\n    const relinkResponse = parseResponse(await handlePlan({\n      action: 'link',\n      planSlug: 'core-plan',\n    }, { repoPath: tempDir }));\n\n    expect(relinkResponse.success).toBe(true);\n\n    const relinkedPlansIndex = await fs.readJson(path.join(tempDir, '.context', 'workflow', 'plans.json'));\n    const relinkedPlan = [...relinkedPlansIndex.active, ...relinkedPlansIndex.completed].find((plan: { slug: string }) => plan.slug === 'core-plan');\n    expect(relinkedPlan.approval_status).toBe('approved');\n    expect(relinkedPlan.approved_by).toBe('reviewer');\n    expect(relinkedPlan.approved_at).toBeDefined();\n\n    const workflowState = await fs.readJson(path.join(tempDir, '.context', 'harness', 'workflows', 'prevc.json'));\n    expect(workflowState.status.approval.plan_approved).toBe(true);\n    expect(workflowState.status.approval.approved_by).toBe('reviewer');\n    expect(workflowState.status.approval.approved_at).toBeDefined();\n  });\n\n  it('instructs the caller to start workflow-init before relying on a linked plan', async () => {\n    await fs.ensureDir(path.join(tempDir, '.context', 'plans'));\n    await fs.writeFile(\n      path.join(tempDir, '.context', 'plans', 'standalone-plan.md'),\n      '# Standalone Plan\\n\\n> Created before workflow initialization.\\n',\n      'utf-8'\n    );\n\n    const linkResponse = parseResponse(await handlePlan({\n      action: 'link',\n      planSlug: 'standalone-plan',\n    }, { repoPath: tempDir }));\n\n    expect(linkResponse.success).toBe(true);\n    expect(linkResponse.workflowActive).toBe(false);\n    expect(linkResponse.planCreatedForGates).toBe(false);\n    expect(linkResponse.enhancementPrompt).toContain('workflow-init({ name: \"standalone-plan\" })');\n    expect(linkResponse.enhancementPrompt).toContain('Call plan({ action: \"link\", planSlug: \"standalone-plan\" }) again');\n    expect(linkResponse.workflowStatePath).toContain('.context/harness/workflows/prevc.json');\n    expect(linkResponse.nextSteps).toContain(\n      'REQUIRED: Call workflow-init({ name: \"standalone-plan\" }) to start the harness-backed PREVC workflow'\n    );\n  });\n\n  it('rejects approval when the requested plan slug diverges from the linked workflow plan', async () => {\n    await handleWorkflowInit({\n      name: 'approval-mismatch',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    await fs.ensureDir(path.join(tempDir, '.context', 'plans'));\n    await fs.writeFile(\n      path.join(tempDir, '.context', 'plans', 'primary-plan.md'),\n      '# Primary Plan\\n\\n> Canonical workflow plan.\\n',\n      'utf-8'\n    );\n    await fs.writeFile(\n      path.join(tempDir, '.context', 'plans', 'other-plan.md'),\n      '# Other Plan\\n\\n> Divergent plan.\\n',\n      'utf-8'\n    );\n\n    const linkResponse = parseResponse(await handlePlan({\n      action: 'link',\n      planSlug: 'primary-plan',\n    }, { repoPath: tempDir }));\n\n    expect(linkResponse.success).toBe(true);\n\n    const approveResponse = parseResponse(await handleWorkflowManage({\n      action: 'approvePlan',\n      planSlug: 'other-plan',\n      approver: 'reviewer',\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(approveResponse.success).toBe(false);\n    expect(approveResponse.error).toContain('Plan slug mismatch');\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowManage.ts",
    "content": "/**\n * Workflow Manage Handler\n *\n * Handles workflow management operations: handoffs, collaboration, documents, gates, approvals.\n */\n\nimport * as path from 'path';\nimport { WorkflowService } from '../../workflow';\nimport {\n  PHASE_NAMES_EN,\n  ROLE_DISPLAY_NAMES,\n  createPlanLinker,\n} from '../../../workflow';\nimport { HarnessPolicyBlockedError } from '../../harness';\n\nimport type { PrevcRole } from '../../../workflow';\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface WorkflowManageParams {\n  action: 'handoff' | 'collaborate' | 'createDoc' | 'getGates' | 'approvePlan' | 'setAutonomous' | 'checkpoint' | 'recordArtifact' | 'defineTask' | 'runSensors';\n  from?: string;\n  to?: string;\n  artifacts?: string[];\n  topic?: string;\n  participants?: PrevcRole[];\n  type?: 'prd' | 'tech-spec' | 'architecture' | 'adr' | 'test-plan' | 'changelog';\n  docName?: string;\n  planSlug?: string;\n  approver?: PrevcRole;\n  notes?: string;\n  enabled?: boolean;\n  reason?: string;\n  name?: string;\n  kind?: 'text' | 'json' | 'file';\n  content?: unknown;\n  filePath?: string;\n  taskTitle?: string;\n  taskDescription?: string;\n  owner?: string;\n  inputs?: string[];\n  expectedOutputs?: string[];\n  acceptanceCriteria?: string[];\n  requiredSensors?: string[];\n  requiredArtifacts?: string[];\n  sensors?: string[];\n  data?: unknown;\n  artifactIds?: string[];\n  pause?: boolean;\n  repoPath?: string;\n}\n\nexport interface WorkflowManageOptions {\n  repoPath: string;\n}\n\n/**\n * Manage workflow operations: handoffs, collaboration, documents, gates, approvals.\n */\nexport async function handleWorkflowManage(\n  params: WorkflowManageParams,\n  options: WorkflowManageOptions\n): Promise<MCPToolResponse> {\n  try {\n    // Resolve repo path: use explicit param, then options\n    // options.repoPath is guaranteed to be valid by MCP server initialization\n    const repoPath = path.resolve(params.repoPath || options.repoPath);\n\n    // Create service\n    const service = await WorkflowService.create(repoPath);\n\n    switch (params.action) {\n      case 'handoff': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        // Validate required parameters for agent handoff\n        if (!params.from || !params.to) {\n          return createJsonResponse({\n            success: false,\n            error: 'handoff requires from and to agent names',\n          });\n        }\n\n        await service.handoff(params.from, params.to, params.artifacts || []);\n\n        // Get next agent suggestion\n        const nextSuggestion = service.getNextAgentSuggestion(params.to);\n\n        return createJsonResponse({\n          success: true,\n          message: `Handoff complete: ${params.from} → ${params.to}`,\n          handoff: {\n            from: params.from,\n            to: params.to,\n            artifacts: params.artifacts || [],\n          },\n          nextSuggestion,\n        });\n      }\n\n      case 'collaborate': {\n        const session = await service.startCollaboration(\n          params.topic!,\n          params.participants\n        );\n        const sessionStatus = session.getStatus();\n\n        return createJsonResponse({\n          success: true,\n          message: `Collaboration session started: ${params.topic}`,\n          sessionId: sessionStatus.id,\n          topic: sessionStatus.topic,\n          participants: sessionStatus.participants.map((p) => ({\n            role: p,\n            displayName: ROLE_DISPLAY_NAMES[p],\n          })),\n        });\n      }\n\n      case 'createDoc': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        const docPath = `.context/workflow/docs/${params.type}-${params.docName?.toLowerCase().replace(/\\s+/g, '-')}.md`;\n\n        return createJsonResponse({\n          success: true,\n          message: `Document template ready: ${params.type}`,\n          documentType: params.type,\n          suggestedPath: docPath,\n          name: params.docName,\n        });\n      }\n\n      case 'getGates': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        const gateResult = await service.checkGates();\n        const settings = await service.getSettings();\n        const approval = await service.getApproval();\n        const summary = await service.getSummary();\n\n        return createJsonResponse({\n          success: true,\n          currentPhase: {\n            code: summary.currentPhase,\n            name: PHASE_NAMES_EN[summary.currentPhase],\n          },\n          canAdvance: gateResult.canAdvance,\n          gates: gateResult.gates,\n          blockingReason: gateResult.blockingReason,\n          hint: gateResult.hint,\n          settings: {\n            autonomous_mode: settings.autonomous_mode,\n            require_plan: settings.require_plan,\n            require_approval: settings.require_approval,\n          },\n          approval: approval ? {\n            plan_created: approval.plan_created,\n            plan_approved: approval.plan_approved,\n            approved_by: approval.approved_by,\n            approved_at: approval.approved_at,\n          } : null,\n        });\n      }\n\n      case 'approvePlan': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        const workflowStatus = await service.getStatus();\n        const planLinker = createPlanLinker(repoPath);\n        const plans = await planLinker.getLinkedPlans();\n        const linkedPlanSlugs = [...plans.active, ...plans.completed].map((plan) => plan.slug);\n        const canonicalPlanSlug = workflowStatus.project.plan\n          || plans.primary\n          || (plans.active.length === 1 ? plans.active[0].slug : null);\n        const requestedPlanSlug = params.planSlug || canonicalPlanSlug;\n\n        if (params.planSlug && canonicalPlanSlug && params.planSlug !== canonicalPlanSlug) {\n          return createJsonResponse({\n            success: false,\n            error: `Plan slug mismatch: workflow is linked to ${canonicalPlanSlug}, but approvePlan received ${params.planSlug}.`,\n            hint: 'Pass the workflow-linked plan slug or re-link the plan before approving.',\n          });\n        }\n\n        if (!requestedPlanSlug) {\n          return createJsonResponse({\n            success: false,\n            error: 'No linked plan is available to approve.',\n            hint: 'Link a plan first using plan({ action: \"link\" }) or pass planSlug explicitly.',\n          });\n        }\n\n        if (!linkedPlanSlugs.includes(requestedPlanSlug)) {\n          return createJsonResponse({\n            success: false,\n            error: `Linked plan not found: ${requestedPlanSlug}`,\n            hint: 'Link the plan first using plan({ action: \"link\" }).',\n          });\n        }\n\n        const currentApproval = await service.getApproval();\n        if (!currentApproval?.plan_created) {\n          return createJsonResponse({\n            success: false,\n            error: 'No plan is linked to approve. Link a plan first using plan({ action: \"link\" }).',\n            hint: 'Use context({ action: \"scaffoldPlan\" }) to create a plan, then plan({ action: \"link\" }) to link it.'\n          });\n        }\n\n        const approval = await service.approvePlan(\n          params.approver || 'reviewer',\n          params.notes\n        );\n\n        const syncedPlan = await planLinker.updatePlanApproval(requestedPlanSlug, {\n          approvalStatus: 'approved',\n          approvedAt: approval.approved_at,\n          approvedBy: approval.approved_by ? String(approval.approved_by) : undefined,\n        });\n\n        if (!syncedPlan) {\n          return createJsonResponse({\n            success: false,\n            error: `Unable to persist approval metadata for linked plan: ${requestedPlanSlug}`,\n          });\n        }\n\n        const gateResult = await service.checkGates();\n\n        return createJsonResponse({\n          success: true,\n          message: 'Plan approved successfully',\n          plan: {\n            slug: syncedPlan.slug,\n            approval_status: syncedPlan.approval_status,\n            approved_by: syncedPlan.approved_by,\n            approved_at: syncedPlan.approved_at,\n          },\n          approval: {\n            plan_approved: approval.plan_approved,\n            approved_by: approval.approved_by,\n            approved_at: approval.approved_at,\n            approval_notes: approval.approval_notes,\n          },\n          canAdvanceToExecution: gateResult.gates.approval_required.passed,\n        });\n      }\n\n      case 'setAutonomous': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        const settings = await service.setAutonomousMode(params.enabled!);\n\n        return createJsonResponse({\n          success: true,\n          message: `Autonomous mode ${params.enabled ? 'enabled' : 'disabled'}${params.reason ? `: ${params.reason}` : ''}`,\n          settings: {\n            autonomous_mode: settings.autonomous_mode,\n            require_plan: settings.require_plan,\n            require_approval: settings.require_approval,\n          },\n          effect: params.enabled\n            ? 'All workflow gates are now bypassed. Use workflow-advance() freely.'\n            : 'Workflow gates are now enforced based on settings.',\n        });\n      }\n\n      case 'checkpoint': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        const result = await service.checkpointHarnessSession(\n          params.notes,\n          params.data,\n          params.artifactIds,\n          params.pause\n        );\n\n        return createJsonResponse({\n          success: true,\n          message: params.pause ? 'Harness session checkpointed and paused' : 'Harness session checkpointed',\n          sessionId: result.binding.sessionId,\n          sessionStatus: result.session.status,\n          checkpointCount: result.session.checkpointCount,\n        });\n      }\n\n      case 'recordArtifact': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        if (!params.name) {\n          return createJsonResponse({\n            success: false,\n            error: 'recordArtifact requires name',\n          });\n        }\n\n        const artifact = await service.recordHarnessArtifact({\n          name: params.name,\n          kind: params.kind,\n          content: params.content,\n          path: params.filePath,\n          metadata: { action: 'workflow-manage.recordArtifact' },\n        });\n\n        return createJsonResponse({\n          success: true,\n          message: `Artifact recorded: ${artifact.name}`,\n          artifact,\n        });\n      }\n\n      case 'defineTask': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        if (!params.taskTitle) {\n          return createJsonResponse({\n            success: false,\n            error: 'defineTask requires taskTitle',\n          });\n        }\n\n        const task = await service.defineHarnessTask({\n          title: params.taskTitle,\n          description: params.taskDescription,\n          owner: params.owner,\n          inputs: params.inputs,\n          expectedOutputs: params.expectedOutputs,\n          acceptanceCriteria: params.acceptanceCriteria,\n          requiredSensors: params.requiredSensors,\n          requiredArtifacts: params.requiredArtifacts,\n        });\n\n        return createJsonResponse({\n          success: true,\n          message: `Harness task defined: ${task.title}`,\n          task,\n        });\n      }\n\n      case 'runSensors': {\n        if (!(await service.hasWorkflow())) {\n          return createJsonResponse({\n            success: false,\n            error: 'No workflow found. Initialize a workflow first.',\n            suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n          });\n        }\n\n        if (!params.sensors || params.sensors.length === 0) {\n          return createJsonResponse({\n            success: false,\n            error: 'runSensors requires sensors',\n          });\n        }\n\n        const result = await service.runHarnessSensors(params.sensors, {\n          action: 'workflow-manage.runSensors',\n        });\n\n        return createJsonResponse({\n          success: true,\n          message: `Executed ${result.runs.length} sensors`,\n          runs: result.runs,\n          backpressure: result.backpressure,\n        });\n      }\n\n      default:\n        return createErrorResponse(`Unknown workflow manage action: ${params.action}`);\n    }\n  } catch (error) {\n    const caughtError = error instanceof Error ? error : new Error(String(error));\n\n    if (caughtError instanceof HarnessPolicyBlockedError) {\n      return createJsonResponse({\n        success: false,\n        error: caughtError.message,\n        blockedBy: 'policy',\n        reasons: caughtError.decision.reasons,\n        policy: caughtError.decision.policy,\n      });\n    }\n\n    return createErrorResponse(caughtError);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowStatus.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { handleWorkflowInit } from './workflowInit';\nimport { handleWorkflowManage } from './workflowManage';\nimport { handleWorkflowStatus } from './workflowStatus';\n\nfunction parseResponse(response: { content: Array<{ text: string }> }) {\n  return JSON.parse(response.content[0].text);\n}\n\ndescribe('workflowStatus', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-workflow-status-'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'workflow-status-test',\n      version: '1.0.0',\n      scripts: {\n        build: 'node -e \"process.exit(0)\"',\n      },\n    }, { spaces: 2 });\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('surfaces harness task information for active workflows', async () => {\n    await handleWorkflowInit({\n      name: 'theta',\n      scale: 'SMALL',\n      autonomous: true,\n      repoPath: tempDir,\n    }, { repoPath: tempDir });\n\n    const defined = parseResponse(await handleWorkflowManage({\n      action: 'defineTask',\n      taskTitle: 'Implement theta',\n      requiredSensors: ['build'],\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    const response = parseResponse(await handleWorkflowStatus({\n      repoPath: tempDir,\n    }, { repoPath: tempDir }));\n\n    expect(response.success).toBe(true);\n    expect(response.harness).toBeTruthy();\n    expect(response.harness.binding.activeTaskId).toBe(defined.task.id);\n    expect(response.harness.taskContracts).toHaveLength(1);\n    expect(response.harness.taskContracts[0].title).toBe('Implement theta');\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/gateway/workflowStatus.ts",
    "content": "/**\n * Workflow Status Handler\n *\n * Handles PREVC workflow status queries.\n */\n\nimport * as path from 'path';\nimport { WorkflowService } from '../../workflow';\nimport {\n  PHASE_NAMES_EN,\n  getScaleName,\n  ProjectScale,\n} from '../../../workflow';\n\nimport type { MCPToolResponse } from './response';\nimport { createJsonResponse, createErrorResponse } from './response';\n\nexport interface WorkflowStatusParams {\n  repoPath?: string;\n}\n\nexport interface WorkflowStatusOptions {\n  repoPath: string;\n}\n\n/**\n * Get current PREVC workflow status including phase, gates, and linked plans.\n */\nexport async function handleWorkflowStatus(\n  params: WorkflowStatusParams,\n  options: WorkflowStatusOptions\n): Promise<MCPToolResponse> {\n  try {\n    // Resolve repo path: use explicit param, then options\n    // options.repoPath is guaranteed to be valid by MCP server initialization\n    const repoPath = path.resolve(params.repoPath || options.repoPath);\n    const contextPath = path.join(repoPath, '.context');\n\n    // Create service\n    const service = await WorkflowService.create(repoPath);\n\n    if (!(await service.hasWorkflow())) {\n      return createJsonResponse({\n        success: false,\n        error: 'No workflow found. Initialize a workflow first.',\n        suggestion: 'Use workflow-init({ name: \"feature-name\" }) to start.',\n        note: 'Workflows enable structured PREVC phases. Skip for trivial changes.',\n        workflowStatePath: path.join(contextPath, 'harness', 'workflows', 'prevc.json')\n      });\n    }\n\n    const summary = await service.getSummary();\n    const status = await service.getStatus();\n    const workflowStatePath = path.join(contextPath, 'harness', 'workflows', 'prevc.json');\n    const orchestration = await service.getPhaseOrchestration(summary.currentPhase);\n    const harness = await service.getHarnessStatus();\n\n    return createJsonResponse({\n      success: true,\n      name: summary.name,\n      scale: getScaleName(summary.scale as ProjectScale),\n      currentPhase: {\n        code: summary.currentPhase,\n        name: PHASE_NAMES_EN[summary.currentPhase],\n      },\n      progress: summary.progress,\n      isComplete: summary.isComplete,\n      phases: status.phases,\n      agents: status.agents,\n      roles: status.roles,\n      orchestration,\n      harness,\n      workflowStatePath,\n    });\n  } catch (error) {\n    return createErrorResponse(error);\n  }\n}\n"
  },
  {
    "path": "src/services/mcp/gatewayTools.ts",
    "content": "/**\n * Gateway Tools\n *\n * Re-exports all gateway handlers, types, and response utilities from the\n * modular gateway directory. This file provides backward compatibility\n * while the actual implementation is split into focused modules.\n *\n * Note: Project tools (project-setup, project-report) have been removed.\n * Use context({ action: \"init\" }) for scaffolding and workflow-init for workflows.\n *\n * @module gatewayTools\n * @see {@link ./gateway/index.ts} for the modular implementation\n */\n\n// Re-export everything from the gateway module\nexport {\n  // Response types and helpers\n  type MCPToolResponse,\n  createJsonResponse,\n  createErrorResponse,\n  createTextResponse,\n\n  // Shared utilities\n  minimalUI,\n  mockTranslate,\n  toolContext,\n\n  // Action types\n  type ExploreAction,\n  type ContextAction,\n  type SyncAction,\n  type PlanAction,\n  type AgentAction,\n  type SkillAction,\n  type HarnessAction,\n\n  // Parameter types\n  type ExploreParams,\n  type ContextParams,\n  type SyncParams,\n  type PlanParams,\n  type AgentParams,\n  type SkillParams,\n  type HarnessParams,\n\n  // Consolidated gateway handlers\n  handleExplore,\n  handleContext,\n  handleSync,\n  handlePlan,\n  handleAgent,\n  handleSkill,\n  handleHarness,\n\n  // Dedicated workflow handlers\n  handleWorkflowInit,\n  handleWorkflowStatus,\n  handleWorkflowAdvance,\n  handleWorkflowManage,\n\n  // Options types\n  type ExploreOptions,\n  type ContextOptions,\n  type SyncOptions,\n  type PlanOptions,\n  type AgentOptions,\n  type SkillOptions,\n  type HarnessOptions,\n\n  // Dedicated workflow handler types\n  type WorkflowInitParams,\n  type WorkflowStatusParams,\n  type WorkflowAdvanceParams,\n  type WorkflowManageParams,\n  type WorkflowInitOptions,\n  type WorkflowStatusOptions,\n  type WorkflowAdvanceOptions,\n  type WorkflowManageOptions,\n} from './gateway';\n"
  },
  {
    "path": "src/services/mcp/index.ts",
    "content": "/**\n * MCP transport adapter exports.\n *\n * Keep this surface focused on the protocol adapter that exposes harness\n * capabilities over Model Context Protocol. CLI-side installation flows live\n * under src/services/cli/.\n */\n\nexport { AIContextMCPServer, startMCPServer, type MCPServerOptions } from './mcpServer';\n\n// Consolidated gateway tool handlers\nexport {\n  handleExplore,\n  handleContext,\n  handleSync,\n  handlePlan,\n  handleAgent,\n  handleSkill,\n  handleHarness,\n} from './gatewayTools';\n\n// Dedicated workflow tool handlers\nexport {\n  handleWorkflowInit,\n  handleWorkflowStatus,\n  handleWorkflowAdvance,\n  handleWorkflowManage,\n} from './gatewayTools';\n\n// Note: Project tool handlers (handleProjectSetup, handleProjectReport) removed\n// Use context({ action: \"init\" }) for scaffolding and workflow-init for workflows\n\n// Consolidated gateway tool action types\nexport type {\n  ExploreAction,\n  ContextAction,\n  SyncAction,\n  PlanAction,\n  AgentAction,\n  SkillAction,\n  HarnessAction,\n} from './gatewayTools';\n\n// Consolidated gateway tool parameter types\nexport type {\n  ExploreParams,\n  ContextParams,\n  SyncParams,\n  PlanParams,\n  AgentParams,\n  SkillParams,\n  HarnessParams,\n} from './gatewayTools';\n\n// Dedicated workflow handler parameter types\nexport type {\n  WorkflowInitParams,\n  WorkflowStatusParams,\n  WorkflowAdvanceParams,\n  WorkflowManageParams,\n} from './gatewayTools';\n\n// Note: Project handler parameter types (ProjectSetupParams, ProjectReportParams) removed\n\n// Response types and helpers\nexport {\n  createJsonResponse,\n  createErrorResponse,\n  type MCPToolResponse,\n} from './gatewayTools';\n"
  },
  {
    "path": "src/services/mcp/mcpInstallService.ts",
    "content": "/**\n * @deprecated Import from ../cli/mcpInstallService instead.\n *\n * This shim preserves the previous path while the codebase is being split\n * into CLI-facing and harness-facing boundaries.\n */\n\nexport {\n  MCPInstallService,\n  type MCPInstallServiceDependencies,\n  type MCPInstallOptions,\n  type MCPInstallResult,\n  type MCPInstallation,\n} from '../cli/mcpInstallService';\n"
  },
  {
    "path": "src/services/mcp/mcpServer.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { AIContextMCPServer } from './mcpServer';\n\n// We can't fully test the MCP server without a transport,\n// but we can test instantiation and configuration\n\ndescribe('AIContextMCPServer', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-test-'));\n    // Create test files\n    await fs.writeFile(path.join(tempDir, 'test.ts'), 'export const x = 1;');\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  describe('constructor', () => {\n    it('should create server with default options', () => {\n      const server = new AIContextMCPServer();\n      expect(server).toBeInstanceOf(AIContextMCPServer);\n    });\n\n    it('should create server with custom options', () => {\n      const server = new AIContextMCPServer({\n        name: 'test-server',\n        repoPath: tempDir,\n        verbose: true\n      });\n      expect(server).toBeInstanceOf(AIContextMCPServer);\n    });\n  });\n\n  describe('tool registration', () => {\n    it('should register all expected tools', () => {\n      // The server registers tools in constructor\n      // We verify this through the fact that construction succeeds\n      // and logs \"Registered 6 tools\" when verbose\n      const server = new AIContextMCPServer({ verbose: false });\n      expect(server).toBeInstanceOf(AIContextMCPServer);\n    });\n  });\n\n  describe('resource registration', () => {\n    it('should register resource templates', () => {\n      // The server registers resources in constructor\n      // We verify this through the fact that construction succeeds\n      const server = new AIContextMCPServer({ verbose: false });\n      expect(server).toBeInstanceOf(AIContextMCPServer);\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/mcp/mcpServer.ts",
    "content": "/**\n * MCP Server - Model Context Protocol server for dotcontext integration.\n *\n * Exposes consolidated gateway tools plus dedicated workflow entry points for\n * reduced context and simpler tool selection for AI agents.\n *\n * Simplified workflow: context init → fillSingle → workflow-init\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { readFileTool } from '../harness/contextTools';\nimport { PathValidator, SecurityError } from '../../utils/pathSecurity';\nimport { SemanticContextBuilder, type ContextFormat } from '../semantic/contextBuilder';\nimport { ContextCache } from '../semantic/contextCache';\nimport { VERSION } from '../../version';\nimport { WorkflowService } from '../workflow';\nimport { logMcpAction } from './actionLogger';\nimport {\n  PREVC_ROLES,\n  getScaleName,\n  ProjectScale,\n  AGENT_TYPES,\n} from '../../workflow';\n\nimport {\n  handleExplore,\n  handleContext,\n  handleSync,\n  handlePlan,\n  handleAgent,\n  handleSkill,\n  handleHarness,\n  handleWorkflowInit,\n  handleWorkflowStatus,\n  handleWorkflowAdvance,\n  handleWorkflowManage,\n  type ExploreParams,\n  type ContextParams,\n  type SyncParams,\n  type PlanParams,\n  type AgentParams,\n  type SkillParams,\n  type HarnessParams,\n  type WorkflowInitParams,\n  type WorkflowStatusParams,\n  type WorkflowAdvanceParams,\n  type WorkflowManageParams,\n  type MCPToolResponse,\n} from './gatewayTools';\n\nexport interface MCPServerOptions {\n  /** Default repository path for tools */\n  repoPath?: string;\n  /** Server name */\n  name?: string;\n  /** Enable verbose logging */\n  verbose?: boolean;\n  /** Optional injected SemanticContextBuilder for testing */\n  contextBuilder?: SemanticContextBuilder;\n}\n\nexport class AIContextMCPServer {\n  private server: McpServer;\n  private contextBuilder: SemanticContextBuilder;\n  private readonly contextCache: ContextCache;\n  private options: MCPServerOptions;\n  private transport: StdioServerTransport | null = null;\n  private initialRepoPath: string | null = null;\n  private cachedRepoPath: string | null = null;\n\n  constructor(options: MCPServerOptions = {}) {\n    this.options = {\n      name: 'dotcontext',\n      verbose: false,\n      ...options\n    };\n\n    this.server = new McpServer({\n      name: this.options.name!,\n      version: VERSION\n    });\n\n    // Support dependency injection for testing, with default fallback\n    this.contextBuilder = options.contextBuilder ?? new SemanticContextBuilder();\n    this.contextCache = new ContextCache();\n\n    // Initialize and cache the correct repo path\n    void this.initializeRepoPath();\n\n    this.registerGatewayTools();\n    this.registerResources();\n  }\n\n  /**\n   * Register consolidated gateway tools and dedicated workflow tools.\n   *\n   * Project tools removed - use context({ action: \"init\" }) + workflow-init instead.\n   *\n   * NOTE: repoPath is determined dynamically via getRepoPath() at runtime,\n   * which uses smart initialization and client path caching.\n   */\n  private registerGatewayTools(): void {\n    const wrap = <TParams>(\n      toolName: string,\n      handler: (params: TParams) => Promise<MCPToolResponse>\n    ) => this.wrapWithActionLogging(toolName, handler);\n\n    // Gateway 1: explore - File and code exploration\n    this.server.registerTool('explore', {\n      description: `File and code exploration. Actions:\n- read: Read file contents (params: filePath, encoding?)\n- list: List files matching pattern (params: pattern, cwd?, ignore?)\n- analyze: Analyze symbols in a file (params: filePath, symbolTypes?)\n- search: Search code with regex (params: pattern, fileGlob?, maxResults?, cwd?)\n- getStructure: Get directory structure (params: rootPath?, maxDepth?, includePatterns?)`,\n      inputSchema: {\n        action: z.enum(['read', 'list', 'analyze', 'search', 'getStructure'])\n          .describe('Action to perform'),\n        filePath: z.string().optional()\n          .describe('(read, analyze) File path to read or analyze'),\n        pattern: z.string().optional()\n          .describe('(list, search) Glob pattern for list, regex pattern for search'),\n        cwd: z.string().optional()\n          .describe('(list, search) Working directory'),\n        encoding: z.enum(['utf-8', 'ascii', 'binary']).optional()\n          .describe('(read) File encoding'),\n        ignore: z.array(z.string()).optional()\n          .describe('(list) Patterns to ignore'),\n        symbolTypes: z.array(z.enum(['class', 'interface', 'function', 'type', 'enum'])).optional()\n          .describe('(analyze) Types of symbols to extract'),\n        fileGlob: z.string().optional()\n          .describe('(search) Glob pattern to filter files'),\n        maxResults: z.number().optional()\n          .describe('(search) Maximum results to return'),\n        rootPath: z.string().optional()\n          .describe('(getStructure) Root path for structure'),\n        maxDepth: z.number().optional()\n          .describe('(getStructure) Maximum directory depth'),\n        includePatterns: z.array(z.string()).optional()\n          .describe('(getStructure) Include patterns'),\n      }\n    }, wrap('explore', async (params): Promise<MCPToolResponse> => {\n      // explore uses cwd for file operations, not repoPath for context resolution\n      return handleExplore(params as ExploreParams, { repoPath: this.getRepoPath() });\n    }));\n\n    // Gateway 2: context - Context scaffolding and semantic context\n    this.server.registerTool('context', {\n      description: `Context scaffolding and semantic context. Actions:\n- check: Check if .context scaffolding exists (params: repoPath?)\n- bootstrapStatus: Summarize scaffold, workflow, and harness bootstrap readiness (params: repoPath?)\n- init: Initialize .context scaffolding (params: repoPath?, type?, outputDir?, semantic?, autoFill?, skipContentGeneration?, generateQA?) Q&A helper docs are opt-in.\n- fill: Fill scaffolding with AI content (params: repoPath?, outputDir?, target?, offset?, limit?) Generated Q&A helper docs under .context/docs/qa are only created when generateQA is enabled and are not returned unless you add custom unfilled docs there. Bootstrap .context/harness/sensors.json is returned until customized.\n- fillSingle: Fill a single scaffold file (params: repoPath?, filePath)\n- listToFill: List files that need filling (params: repoPath?, outputDir?, target?)\n- getMap: Get codebase map section with on-read auto-refresh (params: repoPath?, section?)\n- buildSemantic: Build semantic context (params: repoPath?, contextType?, targetFile?, options?)\n- scaffoldPlan: Create a plan template (params: planName, repoPath?, title?, summary?, autoFill?) Plan creation does NOT start execution. For non-trivial work, immediately follow with workflow-init so PREVC starts on the harness and persists state under .context/harness/workflows/prevc.json.\n- searchQA: Search generated Q&A helper docs with keyword ranking (params: repoPath?, query)\n- generateQA: Generate optional Q&A helper docs from the codebase (params: repoPath?, options?)\n- getFlow: Trace a code path from an entry file/function (params: repoPath?, entryFile, entryFunction?, options?)\n- detectPatterns: Detect functional patterns in the codebase (params: repoPath?, options?)\n\n**Important:** Agents should provide repoPath on the FIRST call, then it will be cached:\n1. First call: context({ action: \"check\", repoPath: \"/path/to/project\" })\n2. Subsequent calls can omit repoPath - it will use cached value from step 1\n3. After context init, call fillSingle for each pending file\n4. If you create a plan with scaffoldPlan, call workflow-init immediately after planning to start the harness-backed PREVC workflow (unless trivial change)`,\n      inputSchema: {\n        action: z.enum(['check', 'bootstrapStatus', 'init', 'fill', 'fillSingle', 'listToFill', 'getMap', 'buildSemantic', 'scaffoldPlan', 'searchQA', 'generateQA', 'getFlow', 'detectPatterns'])\n          .describe('Action to perform'),\n        repoPath: z.string().optional()\n          .describe('Repository path (defaults to cwd)'),\n        outputDir: z.string().optional()\n          .describe('Output directory (default: ./.context)'),\n        type: z.enum(['docs', 'agents', 'both']).optional()\n          .describe('(init) Type of scaffolding to create'),\n        semantic: z.boolean().optional()\n          .describe('(init, scaffoldPlan) Enable semantic analysis'),\n        include: z.array(z.string()).optional()\n          .describe('(init) Include patterns'),\n        exclude: z.array(z.string()).optional()\n          .describe('(init) Exclude patterns'),\n        autoFill: z.boolean().optional()\n          .describe('(init, scaffoldPlan) Auto-fill with codebase content'),\n        skipContentGeneration: z.boolean().optional()\n          .describe('(init) Skip pre-generating content'),\n        generateQA: z.boolean().optional()\n          .describe('(init) Generate optional Q&A helper docs under .context/docs/qa; these are pre-filled and do not require fillSingle'),\n        target: z.enum(['docs', 'agents', 'skills', 'plans', 'sensors', 'all']).optional()\n          .describe('(fill, listToFill) Which scaffolding to target, including nested skills and harness sensors'),\n        offset: z.number().optional()\n          .describe('(fill) Skip first N files'),\n        limit: z.number().optional()\n          .describe('(fill) Max files to return'),\n        filePath: z.string().optional()\n          .describe('(fillSingle) Absolute path to scaffold file'),\n        section: z.enum([\n          'all', 'meta', 'stack', 'structure', 'architecture',\n          'functionalPatterns', 'dependencies', 'stats', 'keyFiles', 'navigation'\n        ]).optional()\n          .describe('(getMap) Section to retrieve; the snapshot auto-refreshes on read'),\n        contextType: z.enum(['documentation', 'playbook', 'plan', 'compact']).optional()\n          .describe('(buildSemantic) Type of context to build'),\n        targetFile: z.string().optional()\n          .describe('(buildSemantic) Target file for focused context'),\n        options: z.object({\n          useLSP: z.boolean().optional(),\n          maxContextLength: z.number().optional(),\n          includeDocumentation: z.boolean().optional(),\n          includeSignatures: z.boolean().optional()\n        }).optional()\n          .describe('(buildSemantic, generateQA, searchQA, getFlow, detectPatterns) Builder/analyzer options'),\n        planName: z.string().optional()\n          .describe('(scaffoldPlan) Name of the plan'),\n        title: z.string().optional()\n          .describe('(scaffoldPlan) Plan title'),\n        summary: z.string().optional()\n          .describe('(scaffoldPlan) Plan summary/goal'),\n        query: z.string().optional()\n          .describe('(searchQA) Query string used to rank generated Q&A helper docs'),\n        entryFile: z.string().optional()\n          .describe('(getFlow) Entry file path for flow tracing'),\n        entryFunction: z.string().optional()\n          .describe('(getFlow) Optional entry function for flow tracing'),\n      }\n    }, wrap('context', async (params): Promise<MCPToolResponse> => {\n      return handleContext(params as ContextParams, { repoPath: this.getRepoPath((params as ContextParams).repoPath), contextBuilder: this.contextBuilder });\n    }));\n\n    // Dedicated Workflow Tools (split from consolidated workflow gateway)\n\n    // Tool 3a: workflow-init - Initialize PREVC workflow\n    this.server.registerTool('workflow-init', {\n      description: `Initialize a PREVC workflow for structured development.\n\nThis is the harness-backed entry point for planned work. If the user asked to create a plan for non-trivial work, do not stop at scaffoldPlan: call workflow-init so the plan runs under PREVC with canonical state in .context/harness/workflows/prevc.json.\n\n**What it does:**\n- Creates .context/workflow/ folder (automatically, if it doesn't exist)\n- Initializes workflow status file with phase tracking\n- Creates canonical harness workflow state and session binding\n- Detects project scale and configures gates\n- Sets up PREVC phases (Plan → Review → Execute → Verify → Complete)\n\n**Prerequisites:**\n- .context/ folder must exist (use context with action \"init\" first)\n- Scaffolding files should be filled (use context with action \"fillSingle\")\n\n**When to use:**\n- Starting a new feature or bug fix after scaffolding is set up\n- Need structured, phase-gated development\n- Working on non-trivial changes\n\n**Don't use if:**\n- Making trivial changes (typo fixes, single-line edits)\n- Just exploring/researching code\n- User explicitly wants to skip workflow`,\n      inputSchema: {\n        name: z.string().describe('Workflow/feature name (required)'),\n        description: z.string().optional()\n          .describe('Task description for scale detection'),\n        scale: z.enum(['QUICK', 'SMALL', 'MEDIUM', 'LARGE']).optional()\n          .describe(`Project scale - AI should evaluate based on task characteristics:\n\nSCALE EVALUATION CRITERIA:\n• QUICK: Single file changes, bug fixes, typos (~5 min)\n  - Phases: E → V only\n  - Example: \"Fix typo in button text\"\n\n• SMALL: Simple features, no architecture changes (~15 min)\n  - Phases: P → E → V\n  - Example: \"Add email validation to form\"\n\n• MEDIUM: Regular features with design decisions (~30 min)\n  - Phases: P → R → E → V\n  - Example: \"Implement user profile page\"\n\n• LARGE: Complex features, systems, compliance (~1+ hour)\n  - Phases: P → R → E → V → C (full workflow)\n  - Examples: \"Build OAuth system\", \"Add GDPR compliance\"\n\nGUIDANCE:\n- Analyze task complexity, architectural impact, and review needs\n- Use LARGE for security/compliance requirements\n- When uncertain, prefer MEDIUM\n- Omit scale only if unable to evaluate (auto-detect fallback)`),\n        autonomous: z.boolean().optional()\n          .describe('Skip all workflow gates (default: scale-dependent)'),\n        require_plan: z.boolean().optional()\n          .describe('Require plan before P→R'),\n        require_approval: z.boolean().optional()\n          .describe('Require approval before R→E'),\n        archive_previous: z.boolean().optional()\n          .describe('Archive existing workflow'),\n      }\n    }, wrap('workflow-init', async (params): Promise<MCPToolResponse> => {\n      return handleWorkflowInit(params as WorkflowInitParams, { repoPath: this.getRepoPath((params as WorkflowInitParams).repoPath) });\n    }));\n\n    // Tool 3b: workflow-status - Get current workflow status\n    this.server.registerTool('workflow-status', {\n      description: `Get current PREVC workflow status including phase, gates, and linked plans.\n\nReturns: Current phase, all phase statuses, gate settings, linked plans, agent activity.`,\n      inputSchema: {\n        // No required parameters\n      }\n    }, wrap('workflow-status', async (params): Promise<MCPToolResponse> => {\n      return handleWorkflowStatus(params as WorkflowStatusParams, { repoPath: this.getRepoPath((params as WorkflowStatusParams).repoPath) });\n    }));\n\n    // Tool 3c: workflow-advance - Advance to next phase\n    this.server.registerTool('workflow-advance', {\n      description: `Advance workflow to the next PREVC phase (P→R→E→V→C).\n\nEnforces gates:\n- P→R: Requires plan if require_plan=true\n- R→E: Requires approval if require_approval=true\n\nUse force=true to bypass gates, or use workflow-manage({ action: 'setAutonomous' }).`,\n      inputSchema: {\n        outputs: z.array(z.string()).optional()\n          .describe('Artifact paths produced in current phase'),\n        force: z.boolean().optional()\n          .describe('Force advancement even if gates block'),\n      }\n    }, wrap('workflow-advance', async (params): Promise<MCPToolResponse> => {\n      return handleWorkflowAdvance(params as WorkflowAdvanceParams, { repoPath: this.getRepoPath((params as WorkflowAdvanceParams).repoPath) });\n    }));\n\n    // Tool 3d: workflow-manage - Manage workflow operations\n    this.server.registerTool('workflow-manage', {\n      description: `Manage workflow operations: handoffs, collaboration, documents, gates, approvals, and harness runtime state.\n\nActions:\n- handoff: Transfer work between agents (params: from, to, artifacts)\n- collaborate: Start collaboration session (params: topic, participants?)\n- createDoc: Create workflow document (params: type, docName)\n- getGates: Check gate status\n- approvePlan: Approve linked plan (params: planSlug?, approver?, notes?)\n- setAutonomous: Toggle autonomous mode (params: enabled, reason?)\n- checkpoint: Record a harness checkpoint (params: notes?, data?, artifactIds?, pause?)\n- recordArtifact: Attach an artifact to the active harness session (params: name, kind?, filePath?, content?)\n- defineTask: Define the active harness task contract (params: taskTitle, taskDescription?, expectedOutputs?, acceptanceCriteria?, requiredSensors?, requiredArtifacts?)\n- runSensors: Execute harness sensors for the active session (params: sensors)`,\n      inputSchema: {\n        action: z.enum(['handoff', 'collaborate', 'createDoc', 'getGates', 'approvePlan', 'setAutonomous', 'checkpoint', 'recordArtifact', 'defineTask', 'runSensors'])\n          .describe('Action to perform'),\n        from: z.string().optional()\n          .describe('(handoff) Agent handing off (e.g., feature-developer)'),\n        to: z.string().optional()\n          .describe('(handoff) Agent receiving (e.g., code-reviewer)'),\n        artifacts: z.array(z.string()).optional()\n          .describe('(handoff) Artifacts to hand off'),\n        topic: z.string().optional()\n          .describe('(collaborate) Collaboration topic'),\n        participants: z.array(z.enum(PREVC_ROLES as unknown as [string, ...string[]])).optional()\n          .describe('(collaborate) Participating roles'),\n        type: z.enum(['prd', 'tech-spec', 'architecture', 'adr', 'test-plan', 'changelog']).optional()\n          .describe('(createDoc) Document type'),\n        docName: z.string().optional()\n          .describe('(createDoc) Document name'),\n        planSlug: z.string().optional()\n          .describe('(approvePlan) Plan to approve'),\n        approver: z.enum(PREVC_ROLES as unknown as [string, ...string[]]).optional()\n          .describe('(approvePlan) Approving role'),\n        notes: z.string().optional()\n          .describe('(approvePlan) Approval notes'),\n        enabled: z.boolean().optional()\n          .describe('(setAutonomous) Enable/disable'),\n        reason: z.string().optional()\n          .describe('(setAutonomous) Reason for change'),\n        name: z.string().optional()\n          .describe('(recordArtifact) Artifact name'),\n        kind: z.enum(['text', 'json', 'file']).optional()\n          .describe('(recordArtifact) Artifact kind'),\n        content: z.any().optional()\n          .describe('(recordArtifact, checkpoint) Structured content or payload'),\n        filePath: z.string().optional()\n          .describe('(recordArtifact) Artifact file path'),\n        taskTitle: z.string().optional()\n          .describe('(defineTask) Task title'),\n        taskDescription: z.string().optional()\n          .describe('(defineTask) Task description'),\n        owner: z.string().optional()\n          .describe('(defineTask) Task owner'),\n        inputs: z.array(z.string()).optional()\n          .describe('(defineTask) Required inputs'),\n        expectedOutputs: z.array(z.string()).optional()\n          .describe('(defineTask) Expected outputs'),\n        acceptanceCriteria: z.array(z.string()).optional()\n          .describe('(defineTask) Acceptance criteria'),\n        requiredSensors: z.array(z.string()).optional()\n          .describe('(defineTask) Required sensors'),\n        requiredArtifacts: z.array(z.string()).optional()\n          .describe('(defineTask) Required artifacts'),\n        sensors: z.array(z.string()).optional()\n          .describe('(runSensors) Sensors to execute'),\n        data: z.any().optional()\n          .describe('(checkpoint) Optional checkpoint payload'),\n        artifactIds: z.array(z.string()).optional()\n          .describe('(checkpoint) Artifact IDs associated with the checkpoint'),\n        pause: z.boolean().optional()\n          .describe('(checkpoint) Pause the active harness session after checkpoint'),\n      }\n    }, wrap('workflow-manage', async (params): Promise<MCPToolResponse> => {\n      return handleWorkflowManage(params as WorkflowManageParams, { repoPath: this.getRepoPath((params as WorkflowManageParams).repoPath) });\n    }));\n\n    // Gateway 5: sync - Import/export synchronization\n    this.server.registerTool('sync', {\n      description: `Import/export synchronization with AI tools. Actions:\n- exportRules: Export rules to AI tools (params: preset?, force?, dryRun?)\n- exportDocs: Export docs to AI tools (params: preset?, indexMode?, force?, dryRun?)\n- exportAgents: Export agents to AI tools (params: preset?, mode?, force?, dryRun?)\n- exportContext: Export all context (params: preset?, skipDocs?, skipAgents?, skipSkills?, docsIndexMode?, agentMode?, force?, dryRun?)\n- exportSkills: Export skills to AI tools (params: preset?, includeBuiltIn?, force?)\n- reverseSync: Import from AI tools to .context/ (params: skipRules?, skipAgents?, skipSkills?, mergeStrategy?, dryRun?, force?, addMetadata?)\n- importDocs: Import docs from AI tools (params: autoDetect?, force?, dryRun?)\n- importAgents: Import agents from AI tools (params: autoDetect?, force?, dryRun?)\n- importSkills: Import skills from AI tools (params: autoDetect?, mergeStrategy?, force?, dryRun?)`,\n      inputSchema: {\n        action: z.enum(['exportRules', 'exportDocs', 'exportAgents', 'exportContext', 'exportSkills', 'reverseSync', 'importDocs', 'importAgents', 'importSkills'])\n          .describe('Action to perform'),\n        preset: z.string().optional()\n          .describe('Target AI tool preset'),\n        force: z.boolean().optional()\n          .describe('Overwrite existing files'),\n        dryRun: z.boolean().optional()\n          .describe('Preview without writing'),\n        indexMode: z.enum(['readme', 'all']).optional()\n          .describe('(exportDocs) Index mode'),\n        mode: z.enum(['symlink', 'markdown']).optional()\n          .describe('(exportAgents) Sync mode'),\n        skipDocs: z.boolean().optional()\n          .describe('(exportContext) Skip docs'),\n        skipAgents: z.boolean().optional()\n          .describe('(exportContext, reverseSync) Skip agents'),\n        skipSkills: z.boolean().optional()\n          .describe('(exportContext, reverseSync) Skip skills'),\n        skipRules: z.boolean().optional()\n          .describe('(reverseSync) Skip rules'),\n        docsIndexMode: z.enum(['readme', 'all']).optional()\n          .describe('(exportContext) Docs index mode'),\n        agentMode: z.enum(['symlink', 'markdown']).optional()\n          .describe('(exportContext) Agent sync mode'),\n        includeBuiltInSkills: z.boolean().optional()\n          .describe('(exportContext) Include built-in skills'),\n        includeBuiltIn: z.boolean().optional()\n          .describe('(exportSkills) Include built-in skills'),\n        mergeStrategy: z.enum(['skip', 'overwrite', 'merge', 'rename']).optional()\n          .describe('(reverseSync, importSkills) Conflict handling'),\n        autoDetect: z.boolean().optional()\n          .describe('(import*) Auto-detect files'),\n        addMetadata: z.boolean().optional()\n          .describe('(reverseSync) Add frontmatter metadata'),\n        repoPath: z.string().optional()\n          .describe('Repository path'),\n      }\n    }, wrap('sync', async (params): Promise<MCPToolResponse> => {\n      return handleSync(params as SyncParams, { repoPath: this.getRepoPath((params as SyncParams).repoPath) });\n    }));\n\n    // Gateway 6: plan - Plan management and execution tracking\n    this.server.registerTool('plan', {\n      description: `Plan management and execution tracking for PREVC workflows.\n\nThis tool does not start workflows. When a user asks for a plan for non-trivial work, start the harness-backed workflow with workflow-init first, or immediately after scaffoldPlan. If you link a plan before workflow-init, re-link it after workflow-init so PREVC gates and harness state use that plan.\n\nActions:\n- link: Link plan to workflow (params: planSlug)\n- getLinked: Get all linked plans\n- getDetails: Get detailed plan info (params: planSlug)\n- getForPhase: Get plans for PREVC phase (params: phase)\n- updatePhase: Update plan phase status (params: planSlug, phaseId, status)\n- recordDecision: Record a decision (params: planSlug, title, description, phase?, alternatives?)\n- updateStep: Update step status (params: planSlug, phaseId, stepIndex, status, output?, notes?)\n- getStatus: Get plan execution status (params: planSlug)\n- syncMarkdown: Sync tracking to markdown (params: planSlug)\n- commitPhase: Create git commit for completed phase (params: planSlug, phaseId, coAuthor?, stagePatterns?, dryRun?)`,\n      inputSchema: {\n        action: z.enum(['link', 'getLinked', 'getDetails', 'getForPhase', 'updatePhase', 'recordDecision', 'updateStep', 'getStatus', 'syncMarkdown', 'commitPhase'])\n          .describe('Action to perform'),\n        planSlug: z.string().optional()\n          .describe('Plan slug/identifier'),\n        phaseId: z.string().optional()\n          .describe('(updatePhase, updateStep, commitPhase) Phase ID'),\n        status: z.enum(['pending', 'in_progress', 'completed', 'skipped']).optional()\n          .describe('(updatePhase, updateStep) New status'),\n        phase: z.enum(['P', 'R', 'E', 'V', 'C']).optional()\n          .describe('(getForPhase, recordDecision) PREVC phase'),\n        title: z.string().optional()\n          .describe('(recordDecision) Decision title'),\n        description: z.string().optional()\n          .describe('(recordDecision) Decision description'),\n        alternatives: z.array(z.string()).optional()\n          .describe('(recordDecision) Considered alternatives'),\n        stepIndex: z.number().optional()\n          .describe('(updateStep) Step number (1-based)'),\n        output: z.string().optional()\n          .describe('(updateStep) Step output artifact'),\n        notes: z.string().optional()\n          .describe('(updateStep) Execution notes'),\n        coAuthor: z.string().optional()\n          .describe('(commitPhase) Agent name for Co-Authored-By footer'),\n        stagePatterns: z.array(z.string()).optional()\n          .describe('(commitPhase) Patterns for files to stage (default: [\".context/**\"])'),\n        dryRun: z.boolean().optional()\n          .describe('(commitPhase) Preview without committing'),\n      }\n    }, wrap('plan', async (params): Promise<MCPToolResponse> => {\n      return handlePlan(params as PlanParams, { repoPath: this.getRepoPath() });\n    }));\n\n    // Gateway 7: agent - Agent orchestration and discovery\n    this.server.registerTool('agent', {\n      description: `Agent orchestration and discovery. Actions:\n- discover: Discover all agents (built-in + custom)\n- getInfo: Get agent details (params: agentType)\n- orchestrate: Select agents for task/phase/role (params: task?, phase?, role?)\n- getSequence: Get agent handoff sequence (params: task, includeReview?, phases?)\n- getDocs: Get agent documentation (params: agent)\n- getPhaseDocs: Get phase documentation (params: phase)\n- listTypes: List all agent types`,\n      inputSchema: {\n        action: z.enum(['discover', 'getInfo', 'orchestrate', 'getSequence', 'getDocs', 'getPhaseDocs', 'listTypes'])\n          .describe('Action to perform'),\n        agentType: z.string().optional()\n          .describe('(getInfo) Agent type identifier'),\n        task: z.string().optional()\n          .describe('(orchestrate, getSequence) Task description'),\n        phase: z.enum(['P', 'R', 'E', 'V', 'C']).optional()\n          .describe('(orchestrate, getPhaseDocs) PREVC phase'),\n        role: z.enum(PREVC_ROLES as unknown as [string, ...string[]]).optional()\n          .describe('(orchestrate) PREVC role'),\n        includeReview: z.boolean().optional()\n          .describe('(getSequence) Include code review'),\n        phases: z.array(z.enum(['P', 'R', 'E', 'V', 'C'])).optional()\n          .describe('(getSequence) Phases to include'),\n        agent: z.enum(AGENT_TYPES as unknown as [string, ...string[]]).optional()\n          .describe('(getDocs) Agent type for docs'),\n      }\n    }, wrap('agent', async (params): Promise<MCPToolResponse> => {\n      return handleAgent(params as AgentParams, { repoPath: this.getRepoPath() });\n    }));\n\n    // Gateway 8: skill - Skill management\n    this.server.registerTool('skill', {\n      description: `Skill management for on-demand expertise. Actions:\n- list: List all skills (params: includeContent?)\n- getContent: Get skill content (params: skillSlug)\n- getForPhase: Get skills for PREVC phase (params: phase)\n- scaffold: Generate skill files (params: skills?, force?)\n- export: Export skills to AI tools (params: preset?, includeBuiltIn?, force?)\n- fill: Fill skills with codebase content (params: skills?, force?)`,\n      inputSchema: {\n        action: z.enum(['list', 'getContent', 'getForPhase', 'scaffold', 'export', 'fill'])\n          .describe('Action to perform'),\n        skillSlug: z.string().optional()\n          .describe('(getContent) Skill identifier'),\n        phase: z.enum(['P', 'R', 'E', 'V', 'C']).optional()\n          .describe('(getForPhase) PREVC phase'),\n        skills: z.array(z.string()).optional()\n          .describe('(scaffold, fill) Specific skills to process'),\n        includeContent: z.boolean().optional()\n          .describe('(list) Include full content'),\n        includeBuiltIn: z.boolean().optional()\n          .describe('(export, fill) Include built-in skills'),\n        preset: z.string().optional()\n          .describe('(export) Target AI tool preset'),\n        force: z.boolean().optional()\n          .describe('(scaffold, export) Overwrite existing'),\n      }\n    }, wrap('skill', async (params): Promise<MCPToolResponse> => {\n      return handleSkill(params as SkillParams, { repoPath: this.getRepoPath() });\n    }));\n\n    // Gateway 9: harness - Explicit harness runtime operations\n    this.server.registerTool('harness', {\n      description: `Harness runtime operations. Actions:\n- createSession: Create durable harness session (params: name, metadata?)\n- listSessions: List harness sessions\n- getSession: Get a harness session (params: sessionId)\n- appendTrace: Append trace event (params: sessionId, level, event, message, data?)\n- listTraces: List trace events for a session (params: sessionId)\n- addArtifact: Add artifact to a session (params: sessionId, name, kind?, content?, path?, metadata?)\n- listArtifacts: List session artifacts (params: sessionId)\n- checkpoint: Record session checkpoint (params: sessionId, note?, data?, artifactIds?, pause?)\n- resumeSession: Resume paused session (params: sessionId)\n- completeSession: Complete session (params: sessionId, note?)\n- failSession: Fail session (params: sessionId, message)\n- recordSensor: Record sensor result for session (params: sessionId, sensorId, sensorStatus, summary, sensorSeverity?, sensorBlocking?, evidence?)\n- getSessionQuality: Evaluate backpressure and task completion (params: sessionId, taskId?, blockOnWarnings?, requireEvidence?)\n- createTask: Create task contract (params: title, sessionId?, expectedOutputs?, acceptanceCriteria?, requiredSensors?, requiredArtifacts?)\n- listTasks: List task contracts\n- evaluateTask: Evaluate task completion (params: taskId, sessionId?)\n- createHandoff: Create handoff contract (params: from, to, sessionId?, taskId?, artifacts?, evidence?)\n- listHandoffs: List handoff contracts\n- replaySession: Replay a durable session timeline (params: sessionId, includePayloads?, maxEvents?)\n- listReplays: List generated replays (params: sessionId?)\n- getReplay: Get replay by id (params: replayId)\n- buildDataset: Build a failure dataset from sessions (params: sessionIds?, includeSuccessfulSessions?)\n- listDatasets: List failure datasets\n- getDataset: Get failure dataset by id (params: datasetId)\n- getFailureClusters: Get clusters for a dataset (params: datasetId)\n- registerPolicy: Register policy rule (params: scope, effect, target?, pattern?, pathPattern?, risk?, description?)\n- listPolicies: List policy rules\n- getPolicy: Retrieve current policy document\n- setPolicy: Replace policy document (params: policy)\n- resetPolicy: Reset policy to bootstrap defaults\n- evaluatePolicy: Evaluate policy against runtime input (params: scope, pattern?, target?, path?, pathPattern?, risk?, approvedBy?, approvalRole?, approvalNote?)`,\n      inputSchema: {\n        action: z.enum([\n          'createSession',\n          'listSessions',\n          'getSession',\n          'appendTrace',\n          'listTraces',\n          'addArtifact',\n          'listArtifacts',\n          'checkpoint',\n          'resumeSession',\n          'completeSession',\n          'failSession',\n          'recordSensor',\n          'getSessionQuality',\n          'createTask',\n          'listTasks',\n          'evaluateTask',\n          'createHandoff',\n          'listHandoffs',\n          'replaySession',\n          'listReplays',\n          'getReplay',\n          'buildDataset',\n          'listDatasets',\n          'getDataset',\n          'getFailureClusters',\n          'registerPolicy',\n          'listPolicies',\n          'getPolicy',\n          'setPolicy',\n          'resetPolicy',\n          'evaluatePolicy',\n        ]).describe('Action to perform'),\n        sessionId: z.string().optional(),\n        taskId: z.string().optional(),\n        name: z.string().optional(),\n        title: z.string().optional(),\n        description: z.string().optional(),\n        owner: z.string().optional(),\n        status: z.enum(['draft', 'ready', 'in_progress', 'blocked', 'completed', 'failed']).optional(),\n        metadata: z.record(z.string(), z.unknown()).optional(),\n        level: z.enum(['debug', 'info', 'warn', 'error']).optional(),\n        event: z.string().optional(),\n        message: z.string().optional(),\n        data: z.record(z.string(), z.unknown()).optional(),\n        kind: z.enum(['text', 'json', 'file']).optional(),\n        content: z.unknown().optional(),\n        path: z.string().optional(),\n        note: z.string().optional(),\n        artifactIds: z.array(z.string()).optional(),\n        pause: z.boolean().optional(),\n        sensorId: z.string().optional(),\n        sensorName: z.string().optional(),\n        sensorSeverity: z.enum(['info', 'warning', 'critical']).optional(),\n        sensorBlocking: z.boolean().optional(),\n        sensorStatus: z.enum(['passed', 'failed', 'skipped', 'blocked']).optional(),\n        summary: z.string().optional(),\n        evidence: z.array(z.string()).optional(),\n        output: z.unknown().optional(),\n        details: z.record(z.string(), z.unknown()).optional(),\n        blockOnWarnings: z.boolean().optional(),\n        requireEvidence: z.boolean().optional(),\n        inputs: z.array(z.string()).optional(),\n        expectedOutputs: z.array(z.string()).optional(),\n        acceptanceCriteria: z.array(z.string()).optional(),\n        requiredSensors: z.array(z.string()).optional(),\n        requiredArtifacts: z.array(z.string()).optional(),\n        from: z.string().optional(),\n        to: z.string().optional(),\n        artifacts: z.array(z.string()).optional(),\n        replayId: z.string().optional(),\n        includePayloads: z.boolean().optional(),\n        maxEvents: z.number().optional(),\n        datasetId: z.string().optional(),\n        sessionIds: z.array(z.string()).optional(),\n        includeSuccessfulSessions: z.boolean().optional(),\n        scope: z.enum(['sensor', 'artifact', 'handoff', 'workflow', 'task', 'risk']).optional(),\n        effect: z.enum(['allow', 'deny', 'require_approval']).optional(),\n        target: z.enum(['tool', 'action', 'path', 'risk']).optional(),\n        pattern: z.string().optional(),\n        pathPattern: z.string().optional(),\n        approvalRole: z.string().optional(),\n        approvedBy: z.string().optional(),\n        approvalNote: z.string().optional(),\n        risk: z.enum(['low', 'medium', 'high', 'critical']).optional(),\n        policy: z.object({\n          defaultEffect: z.enum(['allow', 'deny']).optional(),\n          rules: z.array(z.object({\n            id: z.string().optional(),\n            effect: z.enum(['allow', 'deny', 'require_approval']),\n            target: z.enum(['tool', 'action', 'path', 'risk']).optional(),\n            pattern: z.string().optional(),\n            pathPattern: z.string().optional(),\n            approvalRole: z.string().optional(),\n            reason: z.string().optional(),\n            description: z.string().optional(),\n            scope: z.enum(['sensor', 'artifact', 'handoff', 'workflow', 'task', 'risk']).optional(),\n          })).optional(),\n        }).optional(),\n      }\n    }, wrap('harness', async (params): Promise<MCPToolResponse> => {\n      return handleHarness(params as HarnessParams, { repoPath: this.getRepoPath() });\n    }));\n\n    this.log('Registered consolidated MCP gateway and workflow tools');\n  }\n\n  /**\n   * Register semantic context resources\n   * Uses initialRepoPath if found, otherwise process.cwd()\n   */\n  private registerResources(): void {\n    const repoPath = this.initialRepoPath ?? process.cwd();\n\n    // Register context resources as templates with URI patterns\n    this.server.registerResource(\n      'codebase-context',\n      `context://codebase/{contextType}`,\n      {\n        description: 'Semantic context for the codebase. Use contextType: documentation, playbook, plan, or compact',\n        mimeType: 'text/markdown'\n      },\n      async (uri) => {\n        // Extract context type from URI\n        const match = uri.pathname.match(/\\/([^/]+)$/);\n        const contextType = (match?.[1] || 'compact') as ContextFormat;\n\n        let context: string;\n\n        // Check cache first\n        const cached = await this.contextCache.get(repoPath, contextType);\n        if (cached) {\n          context = cached;\n        } else {\n          switch (contextType) {\n            case 'documentation':\n              context = await this.contextBuilder.buildDocumentationContext(repoPath);\n              break;\n            case 'playbook':\n              context = await this.contextBuilder.buildPlaybookContext(repoPath, 'generic');\n              break;\n            case 'plan':\n              context = await this.contextBuilder.buildPlanContext(repoPath);\n              break;\n            case 'compact':\n            default:\n              context = await this.contextBuilder.buildCompactContext(repoPath);\n              break;\n          }\n          // Store in cache for subsequent calls\n          await this.contextCache.set(repoPath, contextType, context);\n        }\n\n        return {\n          contents: [{\n            uri: uri.href,\n            mimeType: 'text/markdown',\n            text: context\n          }]\n        };\n      }\n    );\n\n    // Register file resource template\n    this.server.registerResource(\n      'file-content',\n      `file://{path}`,\n      {\n        description: 'Read file contents from the repository',\n        mimeType: 'text/plain'\n      },\n      async (uri) => {\n        const filePath = uri.pathname;\n        const result = await readFileTool.execute!(\n          { filePath },\n          { toolCallId: '', messages: [] }\n        ) as { success: boolean; content?: string; error?: string };\n\n        if (!result.success) {\n          throw new Error(result.error || 'Failed to read file');\n        }\n\n        return {\n          contents: [{\n            uri: uri.href,\n            mimeType: 'text/plain',\n            text: result.content || ''\n          }]\n        };\n      }\n    );\n\n    this.log('Registered 2 resource templates');\n\n    // Register PREVC workflow resources\n    this.registerWorkflowResources();\n  }\n\n  /**\n   * Register PREVC workflow resources\n   * Uses initialRepoPath if found, otherwise process.cwd()\n   */\n  private registerWorkflowResources(): void {\n    const repoPath = this.initialRepoPath ?? process.cwd();\n\n    // workflow://status - Current workflow status\n    this.server.registerResource(\n      'workflow-status',\n      'workflow://status',\n      {\n        description: 'Current PREVC workflow status including phases, roles, and progress',\n        mimeType: 'application/json'\n      },\n      async () => {\n        try {\n          const service = new WorkflowService(repoPath);\n\n          if (!(await service.hasWorkflow())) {\n            return {\n              contents: [{\n                uri: 'workflow://status',\n                mimeType: 'application/json',\n                text: JSON.stringify({ error: 'No workflow found' }, null, 2)\n              }]\n            };\n          }\n\n          const summary = await service.getSummary();\n          const status = await service.getStatus();\n\n          return {\n            contents: [{\n              uri: 'workflow://status',\n              mimeType: 'application/json',\n              text: JSON.stringify({\n                name: summary.name,\n                scale: getScaleName(summary.scale as ProjectScale),\n                currentPhase: summary.currentPhase,\n                progress: summary.progress,\n                isComplete: summary.isComplete,\n                phases: status.phases,\n                roles: status.roles,\n              }, null, 2)\n            }]\n          };\n        } catch (error) {\n          return {\n            contents: [{\n              uri: 'workflow://status',\n              mimeType: 'application/json',\n              text: JSON.stringify({\n                error: error instanceof Error ? error.message : String(error)\n              }, null, 2)\n            }]\n          };\n        }\n      }\n    );\n\n    this.log('Registered 1 workflow resource');\n  }\n\n  /**\n   * Initialize repo path with smart detection and caching\n   *\n   * Strategy:\n   * 1. If explicit options.repoPath provided, try to find project root from there\n   * 2. Otherwise search upward from process.cwd() for .context or .git\n   * 3. Set as initialRepoPath if found (for resources)\n   * 4. First valid repoPath from client gets cached for all subsequent tool calls\n   * 5. Fallback to process.cwd() if nothing found (allows flexible MCP usage)\n   */\n  private async initializeRepoPath(): Promise<void> {\n    const startPath = this.options.repoPath || process.cwd();\n    const foundRoot = await this.findProjectRoot(startPath);\n\n    if (foundRoot) {\n      this.initialRepoPath = path.resolve(foundRoot);\n      this.log(`Server initialized with project root: ${this.initialRepoPath}`);\n    } else {\n      // No project found - will use process.cwd() as fallback\n      // This allows flexible MCP server usage without strict project detection\n      this.initialRepoPath = null;\n      this.log(`No project root found. Will use process.cwd() or first valid client-provided path.`);\n    }\n  }\n\n  /**\n   * Find project root by searching upward for .context or .git\n   */\n  private async findProjectRoot(startPath: string): Promise<string | null> {\n    let currentPath = path.resolve(startPath);\n    const root = path.parse(currentPath).root;\n\n    // Search upward for .context or .git\n    while (currentPath !== root) {\n      if (\n        await fs.pathExists(path.join(currentPath, '.context')) ||\n        await fs.pathExists(path.join(currentPath, '.git'))\n      ) {\n        return currentPath;\n      }\n      currentPath = path.dirname(currentPath);\n    }\n\n    // Not found\n    return null;\n  }\n\n  /**\n   * Cache a valid repoPath from client\n   * Only cache if it contains .context and we haven't cached yet\n   */\n  private cacheRepoPathIfValid(repoPath: string): void {\n    if (this.cachedRepoPath) {\n      return; // Already cached\n    }\n\n    const contextPath = path.join(repoPath, '.context');\n    if (fs.existsSync(contextPath)) {\n      this.cachedRepoPath = path.resolve(repoPath);\n      process.stderr.write(`[mcp] ✓ Cached repoPath for this session: ${this.cachedRepoPath}\\n`);\n      this.log(`Cached valid repoPath: ${this.cachedRepoPath}`);\n    }\n  }\n\n  /**\n   * Get the effective repo path for a tool call\n   * Priority: 1) explicit param, 2) cached path, 3) initial path, 4) process.cwd()\n   */\n  private getRepoPath(paramsRepoPath?: string): string {\n    if (paramsRepoPath) {\n      const resolved = path.resolve(paramsRepoPath);\n      this.cacheRepoPathIfValid(resolved);\n      return resolved;\n    }\n\n    if (this.cachedRepoPath) {\n      this.log(`Using cached repoPath: ${this.cachedRepoPath}`);\n      return this.cachedRepoPath;\n    }\n\n    if (this.initialRepoPath) {\n      this.log(`Using initial repoPath: ${this.initialRepoPath}`);\n      return this.initialRepoPath;\n    }\n\n    // Fallback to current working directory\n    const cwd = process.cwd();\n    this.log(`Using fallback cwd: ${cwd}`);\n    return cwd;\n  }\n\n  private wrapWithActionLogging<TParams>(\n    toolName: string,\n    handler: (params: TParams) => Promise<MCPToolResponse>\n  ): (params: TParams) => Promise<MCPToolResponse> {\n    return async (params: TParams) => {\n      const resolvedRepoPath = this.getRepoPath((params as { repoPath?: string })?.repoPath);\n      const action = typeof (params as { action?: string })?.action === 'string'\n        ? (params as { action?: string }).action!\n        : toolName;\n\n      // Validate file paths to prevent path traversal attacks\n      try {\n        this.validatePathParams(params, resolvedRepoPath);\n      } catch (error) {\n        if (error instanceof SecurityError) {\n          this.log(`[SECURITY] Path traversal blocked: ${error.message} (tool: ${toolName}, path: ${error.attemptedPath})`);\n          const errorResponse: MCPToolResponse = {\n            content: [{ type: 'text', text: JSON.stringify({ success: false, error: `Security: ${error.message}` }) }],\n            isError: true,\n          };\n          await this.logToolError(resolvedRepoPath, toolName, action, params, error);\n          return errorResponse;\n        }\n        throw error;\n      }\n\n      try {\n        const response = await handler(params);\n        await this.logToolResponse(resolvedRepoPath, toolName, action, params, response);\n        return response;\n      } catch (error) {\n        await this.logToolError(resolvedRepoPath, toolName, action, params, error);\n        throw error;\n      }\n    };\n  }\n\n  /**\n   * Validate path-related parameters against the workspace boundary.\n   * Throws SecurityError if any path escapes the workspace.\n   */\n  private validatePathParams<TParams>(params: TParams, repoPath: string): void {\n    const validator = new PathValidator(repoPath);\n    const pathKeys: Array<keyof { filePath?: string; rootPath?: string; cwd?: string }> = ['filePath', 'rootPath', 'cwd'];\n\n    for (const key of pathKeys) {\n      const value = (params as Record<string, unknown>)[key as string];\n      if (typeof value === 'string' && value.length > 0) {\n        validator.validatePath(value);\n      }\n    }\n  }\n\n  private async logToolResponse<TParams>(\n    repoPath: string,\n    toolName: string,\n    action: string,\n    params: TParams,\n    response: MCPToolResponse\n  ): Promise<void> {\n    const payload = this.parseResponsePayload(response);\n    const success = typeof payload?.success === 'boolean'\n      ? payload.success\n      : !response.isError;\n    const errorMessage = typeof payload?.error === 'string' ? payload.error : undefined;\n    const resultSummary = payload ? this.buildResultSummary(payload) : undefined;\n\n    await logMcpAction(repoPath, {\n      tool: toolName,\n      action,\n      status: success ? 'success' : 'error',\n      details: {\n        params,\n        ...(resultSummary ? { result: resultSummary } : {}),\n      },\n      ...(success ? {} : { error: errorMessage || 'Tool reported failure' }),\n    });\n  }\n\n  private async logToolError<TParams>(\n    repoPath: string,\n    toolName: string,\n    action: string,\n    params: TParams,\n    error: unknown\n  ): Promise<void> {\n    const message = error instanceof Error ? error.message : String(error);\n\n    await logMcpAction(repoPath, {\n      tool: toolName,\n      action,\n      status: 'error',\n      details: { params },\n      error: message,\n    });\n  }\n\n  private parseResponsePayload(response: MCPToolResponse): Record<string, unknown> | null {\n    const text = response.content?.[0]?.text;\n    if (!text) return null;\n    try {\n      const parsed = JSON.parse(text);\n      return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : null;\n    } catch {\n      return null;\n    }\n  }\n\n  private buildResultSummary(payload: Record<string, unknown>): Record<string, unknown> | null {\n    const summaryKeys = [\n      'success',\n      'message',\n      'currentPhase',\n      'nextPhase',\n      'phase',\n      'scale',\n      'planSlug',\n      'count',\n      'total',\n      'status',\n    ];\n    const summary: Record<string, unknown> = {};\n\n    for (const key of summaryKeys) {\n      if (key in payload) {\n        summary[key] = payload[key];\n      }\n    }\n\n    return Object.keys(summary).length > 0 ? summary : null;\n  }\n\n  /**\n   * Start the MCP server with stdio transport\n   */\n  async start(): Promise<void> {\n    this.transport = new StdioServerTransport();\n    await this.server.connect(this.transport);\n    this.log('MCP Server started on stdio');\n  }\n\n  /**\n   * Stop the MCP server\n   */\n  async stop(): Promise<void> {\n    await this.server.close();\n    await this.contextBuilder.shutdown();\n    this.log('MCP Server stopped');\n  }\n\n  /**\n   * Log message to stderr (not stdout, to avoid polluting MCP messages)\n   */\n  private log(message: string): void {\n    if (this.options.verbose) {\n      process.stderr.write(`[mcp] ${message}\\n`);\n    }\n  }\n}\n\n/**\n * Create and start an MCP server\n */\nexport async function startMCPServer(options: MCPServerOptions = {}): Promise<AIContextMCPServer> {\n  const server = new AIContextMCPServer(options);\n  await server.start();\n  return server;\n}\n"
  },
  {
    "path": "src/services/qa/index.ts",
    "content": "/**\n * Q&A Service Module\n *\n * Exports Q&A generation and search functionality.\n */\n\nexport { QAService, type QAEntry, type QASearchResult, type QAGenerationResult } from './qaService';\nexport { TopicDetector, type QATopic, type TopicDetectionResult, type ProjectType } from './topicDetector';\nexport { PatternInferer } from './patternInferer';\n"
  },
  {
    "path": "src/services/qa/patternInferer.ts",
    "content": "/**\n * Pattern Inferer\n *\n * Backward-compatible adapter for persisted codebase summaries.\n * Functional patterns are now stored directly in the summary artifact.\n */\n\nimport type { CodebaseMap } from '../../generators/documentation/codebaseMapGenerator';\nimport type { DetectedFunctionalPatterns } from '../semantic/types';\nimport { createEmptyFunctionalPatterns } from '../../generators/documentation/codebaseMapGenerator';\n\nexport class PatternInferer {\n  inferFromMap(map: CodebaseMap): DetectedFunctionalPatterns {\n    return map.functionalPatterns ?? createEmptyFunctionalPatterns();\n  }\n}\n"
  },
  {
    "path": "src/services/qa/qaService.ts",
    "content": "/**\n * Q&A Service\n *\n * Generates and manages Q&A content for the codebase.\n * Pre-answers common questions to reduce token usage during sessions.\n *\n * OPTIMIZATION: Uses a persisted semantic snapshot when available to avoid\n * re-analyzing the codebase.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { CodebaseAnalyzer } from '../semantic/codebaseAnalyzer';\nimport { SemanticSnapshotService } from '../semantic/semanticSnapshotService';\nimport { StackDetector, type StackInfo } from '../stack/stackDetector';\nimport { TopicDetector, type QATopic, type TopicDetectionResult } from './topicDetector';\nimport type { DetectedFunctionalPatterns } from '../semantic/types';\nimport type { CodebaseMap } from '../../generators/documentation/codebaseMapGenerator';\n\n/**\n * Q&A Entry structure\n */\nexport interface QAEntry {\n  slug: string;\n  question: string;\n  answer: string;\n  category: string;\n  generatedAt: string;\n  relevantFiles: string[];\n}\n\n/**\n * Q&A Search result\n */\nexport interface QASearchResult {\n  entry: QAEntry;\n  score: number;\n  matchReason: string;\n}\n\n/**\n * Q&A Generation result\n */\nexport interface QAGenerationResult {\n  generated: QAEntry[];\n  skipped: string[];\n  topicDetection: TopicDetectionResult;\n}\n\n/**\n * Q&A Service for generating and searching Q&A content\n */\nexport class QAService {\n  private analyzer: CodebaseAnalyzer;\n  private stackDetector: StackDetector;\n  private topicDetector: TopicDetector;\n  private snapshotService: SemanticSnapshotService;\n\n  constructor(options?: { useLSP?: boolean }) {\n    this.analyzer = new CodebaseAnalyzer(options);\n    this.stackDetector = new StackDetector();\n    this.topicDetector = new TopicDetector();\n    this.snapshotService = new SemanticSnapshotService();\n  }\n\n  /**\n   * Generate Q&A from codebase analysis\n   *\n   * OPTIMIZATION: Uses a persisted semantic snapshot when available.\n   * If the snapshot doesn't exist or is stale, falls back to full analysis.\n   */\n  async generateFromCodebase(repoPath: string): Promise<QAGenerationResult> {\n    const absolutePath = path.resolve(repoPath);\n\n    let stack: StackInfo;\n    let patterns: DetectedFunctionalPatterns;\n\n    try {\n      const snapshot = await this.snapshotService.ensureFreshSummary(absolutePath);\n      stack = this.convertMapStackToStackInfo(snapshot.summary);\n      patterns = snapshot.summary.functionalPatterns;\n    } catch {\n      stack = await this.stackDetector.detect(absolutePath);\n      patterns = await this.analyzer.detectFunctionalPatterns(absolutePath);\n    }\n\n    // Determine relevant topics\n    const topicDetection = this.topicDetector.detectTopics(stack, patterns);\n\n    // Generate Q&A for each topic\n    const generated: QAEntry[] = [];\n    const skipped: string[] = [];\n\n    for (const topic of topicDetection.topics) {\n      try {\n        const entry = await this.generateQAForTopic(\n          absolutePath,\n          topic,\n          stack,\n          patterns,\n          topicDetection\n        );\n        if (entry) {\n          generated.push(entry);\n        } else {\n          skipped.push(topic.slug);\n        }\n      } catch {\n        skipped.push(topic.slug);\n      }\n    }\n\n    // Save generated Q&A\n    await this.saveQAEntries(absolutePath, generated, topicDetection);\n\n    return {\n      generated,\n      skipped,\n      topicDetection,\n    };\n  }\n\n  /**\n   * Search Q&A entries for a query\n   */\n  async search(repoPath: string, query: string): Promise<QASearchResult[]> {\n    const qaDir = path.join(repoPath, '.context', 'docs', 'qa');\n\n    if (!(await fs.pathExists(qaDir))) {\n      return [];\n    }\n\n    const results: QASearchResult[] = [];\n    const files = await fs.readdir(qaDir);\n    const queryLower = query.toLowerCase();\n    const queryWords = queryLower.split(/\\s+/).filter((w) => w.length > 2);\n\n    for (const file of files) {\n      if (!file.endsWith('.md') || file === 'README.md') continue;\n\n      try {\n        const content = await fs.readFile(path.join(qaDir, file), 'utf-8');\n        const entry = this.parseQAEntry(content, file);\n\n        if (!entry) continue;\n\n        // Calculate relevance score\n        const { score, reason } = this.calculateRelevance(entry, queryLower, queryWords);\n\n        if (score > 0) {\n          results.push({\n            entry,\n            score,\n            matchReason: reason,\n          });\n        }\n      } catch {\n        // Skip files that can't be read\n      }\n    }\n\n    // Sort by score descending\n    results.sort((a, b) => b.score - a.score);\n\n    return results.slice(0, 10);\n  }\n\n  /**\n   * Get all Q&A entries\n   */\n  async getAll(repoPath: string): Promise<QAEntry[]> {\n    const qaDir = path.join(repoPath, '.context', 'docs', 'qa');\n\n    if (!(await fs.pathExists(qaDir))) {\n      return [];\n    }\n\n    const entries: QAEntry[] = [];\n    const files = await fs.readdir(qaDir);\n\n    for (const file of files) {\n      if (!file.endsWith('.md') || file === 'README.md') continue;\n\n      try {\n        const content = await fs.readFile(path.join(qaDir, file), 'utf-8');\n        const entry = this.parseQAEntry(content, file);\n        if (entry) {\n          entries.push(entry);\n        }\n      } catch {\n        // Skip files that can't be read\n      }\n    }\n\n    return entries;\n  }\n\n  /**\n   * Get a specific Q&A entry by slug\n   */\n  async getBySlug(repoPath: string, slug: string): Promise<QAEntry | null> {\n    const qaFile = path.join(repoPath, '.context', 'docs', 'qa', `${slug}.md`);\n\n    if (!(await fs.pathExists(qaFile))) {\n      return null;\n    }\n\n    try {\n      const content = await fs.readFile(qaFile, 'utf-8');\n      return this.parseQAEntry(content, `${slug}.md`);\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Generate Q&A entry for a specific topic\n   */\n  private async generateQAForTopic(\n    repoPath: string,\n    topic: QATopic,\n    stack: StackInfo,\n    patterns: DetectedFunctionalPatterns,\n    topicResult: TopicDetectionResult\n  ): Promise<QAEntry | null> {\n    const relevantFiles: string[] = [];\n    let answer = '';\n\n    switch (topic.slug) {\n      case 'getting-started':\n        answer = this.generateGettingStartedAnswer(stack, repoPath);\n        break;\n\n      case 'project-structure':\n        answer = await this.generateProjectStructureAnswer(repoPath, stack);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['src/', 'lib/', 'app/'])));\n        break;\n\n      case 'cli-commands':\n        answer = this.generateCLICommandsAnswer(stack);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['bin/', 'cli', 'commands/'])));\n        break;\n\n      case 'cli-arguments':\n        answer = this.generateCLIArgumentsAnswer(stack);\n        break;\n\n      case 'routing':\n        answer = await this.generateRoutingAnswer(repoPath, stack, patterns);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['routes/', 'api/', 'pages/'])));\n        break;\n\n      case 'middleware':\n        answer = await this.generateMiddlewareAnswer(repoPath, patterns);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['middleware/', 'middlewares/'])));\n        break;\n\n      case 'authentication':\n        answer = this.generateAuthAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'auth', repoPath));\n        break;\n\n      case 'database':\n        answer = this.generateDatabaseAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'database', repoPath));\n        break;\n\n      case 'api-endpoints':\n        answer = await this.generateAPIEndpointsAnswer(repoPath, patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'api', repoPath));\n        break;\n\n      case 'testing':\n        answer = this.generateTestingAnswer(stack, patterns);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['test/', 'tests/', '__tests__/', '*.test.', '*.spec.'])));\n        break;\n\n      case 'error-handling':\n        answer = this.generateErrorHandlingAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'error-handling', repoPath));\n        break;\n\n      case 'caching':\n        answer = this.generateCachingAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'cache', repoPath));\n        break;\n\n      case 'realtime':\n        answer = this.generateRealtimeAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'websocket', repoPath));\n        break;\n\n      case 'background-jobs':\n        answer = this.generateBackgroundJobsAnswer(patterns);\n        relevantFiles.push(...this.getPatternFiles(patterns, 'queue', repoPath));\n        break;\n\n      case 'deployment':\n        answer = this.generateDeploymentAnswer(stack);\n        relevantFiles.push(...(await this.findRelevantFiles(repoPath, ['Dockerfile', 'docker-compose', '.github/workflows/'])));\n        break;\n\n      default:\n        return null;\n    }\n\n    if (!answer) return null;\n\n    return {\n      slug: topic.slug,\n      question: topic.question,\n      answer,\n      category: topic.category,\n      generatedAt: new Date().toISOString(),\n      relevantFiles: [...new Set(relevantFiles)].slice(0, 10),\n    };\n  }\n\n  // Answer generation methods\n\n  private generateGettingStartedAnswer(stack: StackInfo, repoPath: string): string {\n    const lines: string[] = [];\n\n    lines.push('## Getting Started\\n');\n\n    // Prerequisites\n    lines.push('### Prerequisites\\n');\n    if (stack.primaryLanguage === 'typescript' || stack.primaryLanguage === 'javascript') {\n      lines.push('- Node.js (LTS version recommended)');\n      if (stack.packageManager) {\n        lines.push(`- ${stack.packageManager}`);\n      }\n    } else if (stack.primaryLanguage === 'python') {\n      lines.push('- Python 3.8+');\n      if (stack.packageManager === 'poetry') {\n        lines.push('- Poetry');\n      } else if (stack.packageManager === 'pipenv') {\n        lines.push('- Pipenv');\n      }\n    }\n    lines.push('');\n\n    // Installation\n    lines.push('### Installation\\n');\n    lines.push('```bash');\n    lines.push('# Clone the repository');\n    lines.push('git clone <repository-url>');\n    lines.push(`cd ${path.basename(repoPath)}`);\n    lines.push('');\n\n    // Install dependencies\n    if (stack.packageManager === 'yarn') {\n      lines.push('# Install dependencies');\n      lines.push('yarn install');\n    } else if (stack.packageManager === 'pnpm') {\n      lines.push('# Install dependencies');\n      lines.push('pnpm install');\n    } else if (stack.packageManager === 'bun') {\n      lines.push('# Install dependencies');\n      lines.push('bun install');\n    } else if (stack.primaryLanguage === 'typescript' || stack.primaryLanguage === 'javascript') {\n      lines.push('# Install dependencies');\n      lines.push('npm install');\n    } else if (stack.packageManager === 'poetry') {\n      lines.push('# Install dependencies');\n      lines.push('poetry install');\n    } else if (stack.packageManager === 'pipenv') {\n      lines.push('# Install dependencies');\n      lines.push('pipenv install');\n    } else if (stack.primaryLanguage === 'python') {\n      lines.push('# Install dependencies');\n      lines.push('pip install -r requirements.txt');\n    }\n    lines.push('```\\n');\n\n    // Running\n    lines.push('### Running\\n');\n    lines.push('```bash');\n    if (stack.buildTools.includes('vite')) {\n      lines.push('# Development');\n      lines.push('npm run dev');\n    } else if (stack.frameworks.includes('nextjs')) {\n      lines.push('# Development');\n      lines.push('npm run dev');\n    } else if (stack.frameworks.includes('express') || stack.frameworks.includes('nestjs') || stack.frameworks.includes('fastify')) {\n      lines.push('# Development');\n      lines.push('npm run dev');\n      lines.push('');\n      lines.push('# Production');\n      lines.push('npm run build && npm start');\n    } else {\n      lines.push('# See package.json for available scripts');\n      lines.push('npm run <script-name>');\n    }\n    lines.push('```');\n\n    return lines.join('\\n');\n  }\n\n  private async generateProjectStructureAnswer(repoPath: string, stack: StackInfo): Promise<string> {\n    const lines: string[] = [];\n    lines.push('## Project Structure\\n');\n\n    // Try to read actual directory structure\n    try {\n      const entries = await fs.readdir(repoPath, { withFileTypes: true });\n      const dirs = entries\n        .filter((e) => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules')\n        .map((e) => e.name)\n        .slice(0, 15);\n\n      if (dirs.length > 0) {\n        lines.push('```');\n        for (const dir of dirs) {\n          lines.push(`${dir}/`);\n        }\n        lines.push('```\\n');\n      }\n    } catch {\n      // Ignore errors\n    }\n\n    // Add framework-specific structure hints\n    if (stack.frameworks.includes('nextjs')) {\n      lines.push('### Next.js Structure\\n');\n      lines.push('- `app/` or `pages/` - Routes and pages');\n      lines.push('- `components/` - Reusable UI components');\n      lines.push('- `lib/` - Utility functions');\n      lines.push('- `public/` - Static assets');\n    } else if (stack.frameworks.includes('nestjs')) {\n      lines.push('### NestJS Structure\\n');\n      lines.push('- `src/` - Source code');\n      lines.push('  - `modules/` - Feature modules');\n      lines.push('  - `common/` - Shared code');\n      lines.push('  - `main.ts` - Application entry');\n    } else if (stack.frameworks.includes('express')) {\n      lines.push('### Express Structure\\n');\n      lines.push('- `src/` - Source code');\n      lines.push('- `routes/` - Route handlers');\n      lines.push('- `middleware/` - Express middleware');\n      lines.push('- `controllers/` - Request handlers');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateCLICommandsAnswer(stack: StackInfo): string {\n    const lines: string[] = [];\n    lines.push('## CLI Commands\\n');\n\n    if (stack.cliLibraries?.includes('commander')) {\n      lines.push('This CLI is built with Commander.js.\\n');\n      lines.push('Run `<command> --help` to see available commands and options.\\n');\n    } else if (stack.cliLibraries?.includes('yargs')) {\n      lines.push('This CLI is built with Yargs.\\n');\n      lines.push('Run `<command> --help` to see available commands and options.\\n');\n    } else if (stack.cliLibraries?.includes('oclif')) {\n      lines.push('This CLI is built with oclif.\\n');\n      lines.push('Run `<command> help` to see available commands.\\n');\n    }\n\n    lines.push('### Common Commands\\n');\n    lines.push('See the documentation or source code for specific commands.');\n\n    return lines.join('\\n');\n  }\n\n  private generateCLIArgumentsAnswer(stack: StackInfo): string {\n    const lines: string[] = [];\n    lines.push('## CLI Arguments and Options\\n');\n\n    if (stack.cliLibraries?.includes('commander')) {\n      lines.push('### Commander.js Pattern\\n');\n      lines.push('```bash');\n      lines.push('command [options] <required-arg> [optional-arg]');\n      lines.push('```\\n');\n      lines.push('- Options: `--flag` or `-f`');\n      lines.push('- Option with value: `--name <value>`');\n    } else if (stack.cliLibraries?.includes('yargs')) {\n      lines.push('### Yargs Pattern\\n');\n      lines.push('```bash');\n      lines.push('command <positional> --flag --option=value');\n      lines.push('```');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private async generateRoutingAnswer(\n    repoPath: string,\n    stack: StackInfo,\n    patterns: DetectedFunctionalPatterns\n  ): Promise<string> {\n    const lines: string[] = [];\n    lines.push('## Routing\\n');\n\n    if (stack.frameworks.includes('nextjs')) {\n      lines.push('### Next.js App Router\\n');\n      lines.push('Routes are defined by the folder structure in `app/`:\\n');\n      lines.push('- `app/page.tsx` → `/`');\n      lines.push('- `app/about/page.tsx` → `/about`');\n      lines.push('- `app/blog/[slug]/page.tsx` → `/blog/:slug`');\n    } else if (stack.frameworks.includes('express') || stack.frameworks.includes('fastify')) {\n      lines.push(`### ${stack.frameworks.includes('express') ? 'Express' : 'Fastify'} Routing\\n`);\n      lines.push('Routes are typically defined in route files:\\n');\n      lines.push('```typescript');\n      lines.push('router.get(\\'/path\\', handler);');\n      lines.push('router.post(\\'/path\\', handler);');\n      lines.push('```');\n    } else if (stack.frameworks.includes('nestjs')) {\n      lines.push('### NestJS Routing\\n');\n      lines.push('Routes are defined using decorators:\\n');\n      lines.push('```typescript');\n      lines.push('@Controller(\\'users\\')');\n      lines.push('class UsersController {');\n      lines.push('  @Get()');\n      lines.push('  findAll() { }');\n      lines.push('}');\n      lines.push('```');\n    }\n\n    // Add API pattern indicators\n    if (patterns.hasApiPattern) {\n      const apiIndicators = patterns.patterns.find((p) => p.type === 'api')?.indicators;\n      if (apiIndicators?.length) {\n        lines.push('\\n### Detected Route Files\\n');\n        for (const ind of apiIndicators.slice(0, 5)) {\n          if (ind.file) {\n            lines.push(`- \\`${path.relative(repoPath, ind.file)}\\``);\n          }\n        }\n      }\n    }\n\n    return lines.join('\\n');\n  }\n\n  private async generateMiddlewareAnswer(\n    repoPath: string,\n    patterns: DetectedFunctionalPatterns\n  ): Promise<string> {\n    const lines: string[] = [];\n    lines.push('## Middleware\\n');\n    lines.push('Middleware functions process requests before they reach route handlers.\\n');\n\n    if (patterns.hasAuthPattern) {\n      lines.push('### Authentication Middleware');\n      lines.push('The codebase includes authentication middleware.');\n    }\n\n    if (patterns.hasValidationPattern) {\n      lines.push('\\n### Validation Middleware');\n      lines.push('Request validation is implemented using middleware.');\n    }\n\n    if (patterns.hasLoggingPattern) {\n      lines.push('\\n### Logging Middleware');\n      lines.push('Request logging is handled by middleware.');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateAuthAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Authentication\\n');\n\n    const authPattern = patterns.patterns.find((p) => p.type === 'auth');\n    if (!authPattern) {\n      lines.push('Authentication patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of authPattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateDatabaseAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Database\\n');\n\n    const dbPattern = patterns.patterns.find((p) => p.type === 'database');\n    if (!dbPattern) {\n      lines.push('Database patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of dbPattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private async generateAPIEndpointsAnswer(\n    repoPath: string,\n    patterns: DetectedFunctionalPatterns\n  ): Promise<string> {\n    const lines: string[] = [];\n    lines.push('## API Endpoints\\n');\n\n    const apiPattern = patterns.patterns.find((p) => p.type === 'api');\n    if (!apiPattern) {\n      lines.push('API patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Detected API Files\\n');\n    for (const ind of apiPattern.indicators.slice(0, 8)) {\n      if (ind.file) {\n        lines.push(`- \\`${path.relative(repoPath, ind.file)}\\` - ${ind.reason}`);\n      }\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateTestingAnswer(stack: StackInfo, patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Testing\\n');\n\n    if (stack.testFrameworks.length > 0) {\n      lines.push(`### Test Frameworks: ${stack.testFrameworks.join(', ')}\\n`);\n    }\n\n    lines.push('### Running Tests\\n');\n    lines.push('```bash');\n    if (stack.testFrameworks.includes('jest')) {\n      lines.push('npm test');\n      lines.push('npm test -- --watch');\n      lines.push('npm test -- --coverage');\n    } else if (stack.testFrameworks.includes('vitest')) {\n      lines.push('npm test');\n      lines.push('npm test -- --watch');\n    } else if (stack.testFrameworks.includes('pytest')) {\n      lines.push('pytest');\n      lines.push('pytest --cov');\n    } else {\n      lines.push('npm test');\n    }\n    lines.push('```');\n\n    return lines.join('\\n');\n  }\n\n  private generateErrorHandlingAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Error Handling\\n');\n\n    const errorPattern = patterns.patterns.find((p) => p.type === 'error-handling');\n    if (!errorPattern) {\n      lines.push('Error handling patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of errorPattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateCachingAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Caching\\n');\n\n    const cachePattern = patterns.patterns.find((p) => p.type === 'cache');\n    if (!cachePattern) {\n      lines.push('Caching patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of cachePattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateRealtimeAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Real-time Features\\n');\n\n    const wsPattern = patterns.patterns.find((p) => p.type === 'websocket');\n    if (!wsPattern) {\n      lines.push('WebSocket patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of wsPattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateBackgroundJobsAnswer(patterns: DetectedFunctionalPatterns): string {\n    const lines: string[] = [];\n    lines.push('## Background Jobs\\n');\n\n    const queuePattern = patterns.patterns.find((p) => p.type === 'queue');\n    if (!queuePattern) {\n      lines.push('Queue/job patterns detected in the codebase.');\n      return lines.join('\\n');\n    }\n\n    lines.push('### Implementation Details\\n');\n    for (const ind of queuePattern.indicators.slice(0, 5)) {\n      lines.push(`- ${ind.reason}`);\n    }\n\n    return lines.join('\\n');\n  }\n\n  private generateDeploymentAnswer(stack: StackInfo): string {\n    const lines: string[] = [];\n    lines.push('## Deployment\\n');\n\n    if (stack.hasDocker) {\n      lines.push('### Docker\\n');\n      lines.push('This project includes Docker configuration.\\n');\n      lines.push('```bash');\n      lines.push('docker build -t app .');\n      lines.push('docker run -p 3000:3000 app');\n      lines.push('```\\n');\n    }\n\n    if (stack.hasCI) {\n      lines.push('### CI/CD\\n');\n      lines.push('CI/CD pipelines are configured for this project.');\n      lines.push('Check `.github/workflows/` or equivalent for pipeline configuration.');\n    }\n\n    return lines.join('\\n');\n  }\n\n  // Helper methods\n\n  private getPatternFiles(patterns: DetectedFunctionalPatterns, type: string, repoPath: string): string[] {\n    const pattern = patterns.patterns.find((p) => p.type === type);\n    if (!pattern) return [];\n    return pattern.indicators\n      .filter((i) => i.file)\n      .map((i) => path.relative(repoPath, i.file))\n      .slice(0, 5);\n  }\n\n  private async findRelevantFiles(repoPath: string, patterns: string[]): Promise<string[]> {\n    const results: string[] = [];\n    const excludeDirs = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '__pycache__', 'vendor'];\n\n    try {\n      const entries = await fs.readdir(repoPath, { withFileTypes: true, recursive: true });\n\n      for (const entry of entries) {\n        const relativePath = path.relative(repoPath, path.join(entry.parentPath || '', entry.name));\n\n        // Skip excluded directories\n        if (excludeDirs.some((dir) => relativePath.startsWith(dir + path.sep) || relativePath === dir)) {\n          continue;\n        }\n\n        for (const pattern of patterns) {\n          if (relativePath.includes(pattern)) {\n            results.push(relativePath);\n            break;\n          }\n        }\n\n        if (results.length >= 10) break;\n      }\n    } catch {\n      // Ignore errors\n    }\n\n    return results;\n  }\n\n  private async saveQAEntries(\n    repoPath: string,\n    entries: QAEntry[],\n    topicResult: TopicDetectionResult\n  ): Promise<void> {\n    const qaDir = path.join(repoPath, '.context', 'docs', 'qa');\n    await fs.ensureDir(qaDir);\n\n    // Save individual entries\n    for (const entry of entries) {\n      const content = this.formatQAEntry(entry);\n      await fs.writeFile(path.join(qaDir, `${entry.slug}.md`), content);\n    }\n\n    // Generate README index\n    const readme = this.generateQAReadme(entries, topicResult);\n    await fs.writeFile(path.join(qaDir, 'README.md'), readme);\n  }\n\n  private formatQAEntry(entry: QAEntry): string {\n    const lines: string[] = [];\n\n    lines.push('---');\n    lines.push(`slug: ${entry.slug}`);\n    lines.push(`category: ${entry.category}`);\n    lines.push(`generatedAt: ${entry.generatedAt}`);\n    if (entry.relevantFiles.length > 0) {\n      lines.push(`relevantFiles:`);\n      for (const file of entry.relevantFiles) {\n        lines.push(`  - ${file}`);\n      }\n    }\n    lines.push('---\\n');\n\n    lines.push(`# ${entry.question}\\n`);\n    lines.push(entry.answer);\n\n    return lines.join('\\n');\n  }\n\n  private generateQAReadme(entries: QAEntry[], topicResult: TopicDetectionResult): string {\n    const lines: string[] = [];\n\n    lines.push('# Q&A Index\\n');\n    lines.push(`Project type: **${topicResult.projectType}**\\n`);\n    lines.push(`Generated: ${new Date().toISOString()}\\n`);\n\n    // Group by category\n    const byCategory = new Map<string, QAEntry[]>();\n    for (const entry of entries) {\n      if (!byCategory.has(entry.category)) {\n        byCategory.set(entry.category, []);\n      }\n      byCategory.get(entry.category)!.push(entry);\n    }\n\n    for (const [category, catEntries] of byCategory) {\n      lines.push(`## ${category.charAt(0).toUpperCase() + category.slice(1)}\\n`);\n      for (const entry of catEntries) {\n        lines.push(`- [${entry.question}](./${entry.slug}.md)`);\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private parseQAEntry(content: string, filename: string): QAEntry | null {\n    try {\n      const slug = filename.replace('.md', '');\n\n      // Parse frontmatter\n      const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n      let category = 'general';\n      let generatedAt = '';\n      const relevantFiles: string[] = [];\n\n      if (frontmatterMatch) {\n        const fm = frontmatterMatch[1];\n        const categoryMatch = fm.match(/category:\\s*(.+)/);\n        if (categoryMatch) category = categoryMatch[1].trim();\n\n        const dateMatch = fm.match(/generatedAt:\\s*(.+)/);\n        if (dateMatch) generatedAt = dateMatch[1].trim();\n\n        const filesMatch = fm.match(/relevantFiles:\\n([\\s\\S]*?)(?=\\n\\w|$)/);\n        if (filesMatch) {\n          const files = filesMatch[1].match(/- (.+)/g);\n          if (files) {\n            relevantFiles.push(...files.map((f) => f.replace('- ', '').trim()));\n          }\n        }\n      }\n\n      // Parse question from first heading\n      const questionMatch = content.match(/^#\\s+(.+)/m);\n      const question = questionMatch ? questionMatch[1] : slug;\n\n      // Get answer (everything after the first heading)\n      const answerStart = content.indexOf('\\n', content.indexOf('# '));\n      const answer = answerStart > 0 ? content.slice(answerStart).trim() : '';\n\n      return {\n        slug,\n        question,\n        answer,\n        category,\n        generatedAt,\n        relevantFiles,\n      };\n    } catch {\n      return null;\n    }\n  }\n\n  private calculateRelevance(\n    entry: QAEntry,\n    queryLower: string,\n    queryWords: string[]\n  ): { score: number; reason: string } {\n    let score = 0;\n    const reasons: string[] = [];\n\n    // Exact slug match\n    if (entry.slug.toLowerCase().includes(queryLower.replace(/\\s+/g, '-'))) {\n      score += 10;\n      reasons.push('slug match');\n    }\n\n    // Question match\n    const questionLower = entry.question.toLowerCase();\n    if (questionLower.includes(queryLower)) {\n      score += 8;\n      reasons.push('question match');\n    }\n\n    // Word matches in question\n    for (const word of queryWords) {\n      if (questionLower.includes(word)) {\n        score += 2;\n        reasons.push(`word \"${word}\" in question`);\n      }\n    }\n\n    // Answer match\n    const answerLower = entry.answer.toLowerCase();\n    for (const word of queryWords) {\n      if (answerLower.includes(word)) {\n        score += 1;\n        reasons.push(`word \"${word}\" in answer`);\n      }\n    }\n\n    // Category match\n    if (entry.category.toLowerCase().includes(queryLower)) {\n      score += 3;\n      reasons.push('category match');\n    }\n\n    return {\n      score,\n      reason: reasons.slice(0, 3).join(', ') || 'partial match',\n    };\n  }\n\n  /**\n   * Convert persisted summary stack section to StackInfo format\n   */\n  private convertMapStackToStackInfo(map: CodebaseMap): StackInfo {\n    return {\n      primaryLanguage: map.stack.primaryLanguage,\n      languages: map.stack.languages,\n      frameworks: map.stack.frameworks,\n      buildTools: map.stack.buildTools,\n      testFrameworks: map.stack.testFrameworks,\n      packageManager: map.stack.packageManager,\n      isMonorepo: map.stack.isMonorepo,\n      hasDocker: map.stack.hasDocker,\n      hasCI: map.stack.hasCI,\n      files: [],\n      hasBinField: map.stack.hasBinField,\n      cliLibraries: map.stack.cliLibraries || [],\n      hasMainExport: map.stack.hasMainExport,\n      hasTypesField: map.stack.hasTypesField,\n    };\n  }\n\n  /**\n   * Shutdown analyzer resources\n   */\n  async shutdown(): Promise<void> {\n    await this.analyzer.shutdown();\n  }\n}\n"
  },
  {
    "path": "src/services/qa/topicDetector.ts",
    "content": "/**\n * Topic Detector Service\n *\n * Dynamically detects Q&A topics based on:\n * 1. Stack detection - Language, frameworks, build tools\n * 2. Project type - CLI, web API, library, mobile app, etc.\n * 3. Code patterns found - What's actually in the codebase\n */\n\nimport type { StackInfo } from '../stack/stackDetector';\nimport type { DetectedFunctionalPatterns } from '../semantic/types';\n\n/**\n * Q&A Topic definition\n */\nexport interface QATopic {\n  slug: string;\n  question: string;\n  category: 'getting-started' | 'architecture' | 'features' | 'operations' | 'testing';\n  priority: number;\n}\n\n/**\n * Topic detection result\n */\nexport interface TopicDetectionResult {\n  topics: QATopic[];\n  projectType: ProjectType;\n  detectionReasons: Map<string, string[]>;\n}\n\nexport type ProjectType =\n  | 'cli'\n  | 'web-api'\n  | 'web-app'\n  | 'library'\n  | 'mobile-app'\n  | 'desktop-app'\n  | 'fullstack'\n  | 'unknown';\n\n/**\n * Detects Q&A topics based on stack and patterns\n */\nexport class TopicDetector {\n  /**\n   * Get Q&A topics for the given stack and patterns\n   */\n  detectTopics(\n    stack: StackInfo,\n    patterns: DetectedFunctionalPatterns\n  ): TopicDetectionResult {\n    const topics: QATopic[] = [];\n    const detectionReasons = new Map<string, string[]>();\n    const projectType = this.detectProjectType(stack);\n\n    // Base topics for all projects\n    topics.push({\n      slug: 'getting-started',\n      question: 'How do I set up and run this project?',\n      category: 'getting-started',\n      priority: 1,\n    });\n\n    topics.push({\n      slug: 'project-structure',\n      question: 'How is the codebase organized?',\n      category: 'architecture',\n      priority: 2,\n    });\n\n    // CLI-specific topics\n    if (this.isCLI(stack)) {\n      detectionReasons.set('cli-commands', [\n        stack.hasBinField ? 'Package has bin field' : '',\n        stack.cliLibraries?.length ? `Uses CLI libraries: ${stack.cliLibraries.join(', ')}` : '',\n        stack.frameworks.includes('cli') ? 'CLI framework detected' : '',\n      ].filter(Boolean));\n\n      topics.push({\n        slug: 'cli-commands',\n        question: 'What commands are available?',\n        category: 'features',\n        priority: 3,\n      });\n\n      topics.push({\n        slug: 'cli-arguments',\n        question: 'How do I pass arguments and options?',\n        category: 'features',\n        priority: 4,\n      });\n    }\n\n    // Web framework specific topics\n    if (this.hasWebFramework(stack)) {\n      detectionReasons.set('routing', [\n        `Web framework detected: ${stack.frameworks.find((f) =>\n          ['nextjs', 'nuxt', 'express', 'nestjs', 'fastify', 'koa', 'hapi'].includes(f)\n        )}`,\n      ]);\n\n      topics.push({\n        slug: 'routing',\n        question: 'How does routing work?',\n        category: 'architecture',\n        priority: 5,\n      });\n\n      topics.push({\n        slug: 'middleware',\n        question: 'How does middleware work?',\n        category: 'architecture',\n        priority: 6,\n      });\n    }\n\n    // Pattern-based topics\n\n    // Authentication\n    if (patterns.hasAuthPattern) {\n      const authIndicators = patterns.patterns\n        .find((p) => p.type === 'auth')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('authentication', authIndicators || ['Auth patterns detected']);\n\n      topics.push({\n        slug: 'authentication',\n        question: 'How does authentication work?',\n        category: 'features',\n        priority: 7,\n      });\n    }\n\n    // Database\n    if (patterns.hasDatabasePattern) {\n      const dbIndicators = patterns.patterns\n        .find((p) => p.type === 'database')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('database', dbIndicators || ['Database patterns detected']);\n\n      topics.push({\n        slug: 'database',\n        question: 'How is data stored and accessed?',\n        category: 'features',\n        priority: 8,\n      });\n    }\n\n    // API endpoints\n    if (patterns.hasApiPattern) {\n      const apiIndicators = patterns.patterns\n        .find((p) => p.type === 'api')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('api-endpoints', apiIndicators || ['API patterns detected']);\n\n      topics.push({\n        slug: 'api-endpoints',\n        question: 'What API endpoints are available?',\n        category: 'features',\n        priority: 9,\n      });\n    }\n\n    // Caching\n    if (patterns.hasCachePattern) {\n      const cacheIndicators = patterns.patterns\n        .find((p) => p.type === 'cache')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('caching', cacheIndicators || ['Cache patterns detected']);\n\n      topics.push({\n        slug: 'caching',\n        question: 'How does caching work?',\n        category: 'operations',\n        priority: 10,\n      });\n    }\n\n    // Error handling\n    if (patterns.hasErrorHandlingPattern) {\n      const errorIndicators = patterns.patterns\n        .find((p) => p.type === 'error-handling')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('error-handling', errorIndicators || ['Error handling patterns detected']);\n\n      topics.push({\n        slug: 'error-handling',\n        question: 'How are errors handled?',\n        category: 'operations',\n        priority: 11,\n      });\n    }\n\n    // Testing\n    if (patterns.hasTestingPattern || stack.testFrameworks.length > 0) {\n      const testIndicators = [\n        ...stack.testFrameworks.map((f) => `Test framework: ${f}`),\n        ...(patterns.patterns.find((p) => p.type === 'testing')?.indicators.slice(0, 2).map((i) => i.reason) || []),\n      ];\n\n      detectionReasons.set('testing', testIndicators);\n\n      topics.push({\n        slug: 'testing',\n        question: 'How do I run and write tests?',\n        category: 'testing',\n        priority: 12,\n      });\n    }\n\n    // Real-time features\n    if (patterns.hasWebSocketPattern) {\n      const wsIndicators = patterns.patterns\n        .find((p) => p.type === 'websocket')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('realtime', wsIndicators || ['WebSocket patterns detected']);\n\n      topics.push({\n        slug: 'realtime',\n        question: 'How do real-time features work?',\n        category: 'features',\n        priority: 13,\n      });\n    }\n\n    // Background jobs\n    if (patterns.hasQueuePattern) {\n      const queueIndicators = patterns.patterns\n        .find((p) => p.type === 'queue')\n        ?.indicators.slice(0, 3)\n        .map((i) => i.reason);\n\n      detectionReasons.set('background-jobs', queueIndicators || ['Queue patterns detected']);\n\n      topics.push({\n        slug: 'background-jobs',\n        question: 'How do background jobs work?',\n        category: 'operations',\n        priority: 14,\n      });\n    }\n\n    // Library-specific topics\n    if (projectType === 'library') {\n      topics.push({\n        slug: 'api-reference',\n        question: 'What is the public API?',\n        category: 'features',\n        priority: 15,\n      });\n\n      topics.push({\n        slug: 'installation',\n        question: 'How do I install and import this library?',\n        category: 'getting-started',\n        priority: 2,\n      });\n    }\n\n    // Frontend-specific topics\n    if (this.hasFrontendFramework(stack)) {\n      topics.push({\n        slug: 'components',\n        question: 'How are UI components organized?',\n        category: 'architecture',\n        priority: 16,\n      });\n\n      topics.push({\n        slug: 'state-management',\n        question: 'How is application state managed?',\n        category: 'architecture',\n        priority: 17,\n      });\n    }\n\n    // DevOps topics\n    if (stack.hasDocker || stack.hasCI) {\n      topics.push({\n        slug: 'deployment',\n        question: 'How do I deploy this project?',\n        category: 'operations',\n        priority: 18,\n      });\n    }\n\n    // Sort by priority\n    topics.sort((a, b) => a.priority - b.priority);\n\n    return {\n      topics,\n      projectType,\n      detectionReasons,\n    };\n  }\n\n  /**\n   * Detect project type based on stack\n   */\n  private detectProjectType(stack: StackInfo): ProjectType {\n    // CLI detection\n    if (this.isCLI(stack)) {\n      return 'cli';\n    }\n\n    // Mobile app detection\n    if (\n      stack.frameworks.some((f) =>\n        ['react-native', 'flutter', 'capacitor', 'ionic', 'android-native', 'ios-native'].includes(f)\n      )\n    ) {\n      return 'mobile-app';\n    }\n\n    // Desktop app detection\n    if (stack.frameworks.some((f) => ['electron', 'tauri', 'neutralino'].includes(f))) {\n      return 'desktop-app';\n    }\n\n    // Fullstack detection (has both frontend and backend indicators)\n    const hasFrontend = this.hasFrontendFramework(stack);\n    const hasBackend = stack.frameworks.some((f) =>\n      ['express', 'fastify', 'nestjs', 'koa', 'hapi', 'django', 'rails', 'laravel'].includes(f)\n    );\n\n    if (hasFrontend && hasBackend) {\n      return 'fullstack';\n    }\n\n    // Web app (frontend only)\n    if (hasFrontend) {\n      return 'web-app';\n    }\n\n    // Web API (backend only)\n    if (hasBackend) {\n      return 'web-api';\n    }\n\n    // Library detection\n    if (stack.hasMainExport || stack.hasTypesField) {\n      return 'library';\n    }\n\n    return 'unknown';\n  }\n\n  /**\n   * Check if the project is a CLI\n   */\n  private isCLI(stack: StackInfo): boolean {\n    return (\n      Boolean(stack.hasBinField) ||\n      Boolean(stack.cliLibraries?.length) ||\n      stack.frameworks.includes('cli')\n    );\n  }\n\n  /**\n   * Check if the project has a web framework\n   */\n  private hasWebFramework(stack: StackInfo): boolean {\n    return stack.frameworks.some((f) =>\n      [\n        'nextjs',\n        'nuxt',\n        'express',\n        'nestjs',\n        'fastify',\n        'koa',\n        'hapi',\n        'django',\n        'flask',\n        'fastapi',\n        'rails',\n        'laravel',\n        'phoenix',\n      ].includes(f)\n    );\n  }\n\n  /**\n   * Check if the project has a frontend framework\n   */\n  private hasFrontendFramework(stack: StackInfo): boolean {\n    return stack.frameworks.some((f) =>\n      ['nextjs', 'nuxt', 'angular', 'vue', 'svelte', 'gatsby', 'remix', 'astro'].includes(f)\n    );\n  }\n}\n"
  },
  {
    "path": "src/services/quickSync/index.ts",
    "content": "export {\n  QuickSyncService,\n  createQuickSyncService,\n} from './quickSyncService';\n\nexport type {\n  QuickSyncServiceDependencies,\n  QuickSyncOptions,\n  QuickSyncResult,\n} from './quickSyncService';\n"
  },
  {
    "path": "src/services/quickSync/quickSyncService.ts",
    "content": "/**\n * Quick Sync Service\n *\n * Unified sync operation that synchronizes agents, exports skills,\n * and optionally updates documentation in one command.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport { SyncService } from '../sync';\nimport { SkillExportService, ExportRulesService } from '../export';\nimport { StateDetector } from '../state';\nimport { createSkillRegistry } from '../../workflow/skills';\n\nexport interface QuickSyncServiceDependencies {\n  ui: CLIInterface;\n  t: TranslateFn;\n  version: string;\n}\n\nexport interface QuickSyncOptions {\n  /** Skip agents sync */\n  skipAgents?: boolean;\n  /** Skip skills export */\n  skipSkills?: boolean;\n  /** Skip docs update prompt */\n  skipDocs?: boolean;\n  /** Force overwrite */\n  force?: boolean;\n  /** Dry run mode */\n  dryRun?: boolean;\n  /** Verbose output */\n  verbose?: boolean;\n  /** Selected agent sync targets (e.g., ['claude', 'github']). If not set, syncs to all. */\n  agentTargets?: string[];\n  /** Selected skill export targets (e.g., ['claude', 'gemini']). If not set, exports to all. */\n  skillTargets?: string[];\n  /** Selected doc export targets (e.g., ['cursor', 'claude']). If not set, exports to all. */\n  docTargets?: string[];\n  /** LLM config for docs update */\n  llmConfig?: {\n    provider?: string;\n    model?: string;\n    apiKey?: string;\n    baseUrl?: string;\n  };\n}\n\nexport interface QuickSyncResult {\n  agentsSynced: number;\n  skillsExported: number;\n  docsUpdated: boolean;\n  errors: string[];\n}\n\nexport class QuickSyncService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n\n  constructor(deps: QuickSyncServiceDependencies) {\n    this.ui = deps.ui;\n    this.t = deps.t;\n    this.version = deps.version;\n  }\n\n  /**\n   * Run unified sync operation\n   */\n  async run(repoPath: string, options: QuickSyncOptions = {}): Promise<QuickSyncResult> {\n    const absolutePath = path.resolve(repoPath);\n\n    const result: QuickSyncResult = {\n      agentsSynced: 0,\n      skillsExported: 0,\n      docsUpdated: false,\n      errors: [],\n    };\n\n    // Step 1: Sync agents\n    if (!options.skipAgents) {\n      try {\n        this.ui.startSpinner(this.t('prompts.quickSync.syncing.agents'));\n\n        const agentsPath = path.join(absolutePath, '.context', 'agents');\n        if (await fs.pathExists(agentsPath)) {\n          const syncService = new SyncService({\n            ui: this.ui,\n            t: this.t,\n            version: this.version,\n          });\n\n          // Use selected targets (preset names) or default to 'all' preset\n          // SyncService now understands preset names in the target array\n          const hasCustomTargets = options.agentTargets && options.agentTargets.length > 0;\n\n          await syncService.run({\n            source: agentsPath,\n            preset: hasCustomTargets ? undefined : 'all',\n            target: hasCustomTargets ? options.agentTargets : undefined,\n            force: options.force,\n            dryRun: options.dryRun,\n            verbose: false,\n          });\n\n          // Count synced files\n          const files = await fs.readdir(agentsPath);\n          result.agentsSynced = files.filter(f => f.endsWith('.md')).length;\n\n          const targetInfo = hasCustomTargets\n            ? `to ${options.agentTargets!.join(', ')}`\n            : 'to all targets';\n          this.ui.updateSpinner(`${result.agentsSynced} agents synced ${targetInfo}`, 'success');\n        } else {\n          this.ui.updateSpinner('No agents to sync', 'info');\n        }\n      } catch (error) {\n        this.ui.updateSpinner('Failed to sync agents', 'fail');\n        result.errors.push(error instanceof Error ? error.message : String(error));\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Step 2: Export skills\n    if (!options.skipSkills) {\n      try {\n        this.ui.startSpinner(this.t('prompts.quickSync.syncing.skills'));\n\n        const skillsPath = path.join(absolutePath, '.context', 'skills');\n        if (await fs.pathExists(skillsPath)) {\n          const skillExportService = new SkillExportService({\n            ui: this.ui,\n            t: this.t,\n            version: this.version,\n          });\n\n          // Use selected targets (preset names) or default to 'all' preset\n          // SkillExportService now understands preset names in the targets array\n          const hasCustomTargets = options.skillTargets && options.skillTargets.length > 0;\n\n          const exportResult = await skillExportService.run(absolutePath, {\n            preset: hasCustomTargets ? undefined : 'all',\n            targets: hasCustomTargets ? options.skillTargets : undefined,\n            force: options.force,\n            dryRun: options.dryRun,\n            verbose: false,\n            includeBuiltIn: true,\n          });\n\n          result.skillsExported = exportResult.skillsExported.length;\n          const targetInfo = hasCustomTargets\n            ? `to ${options.skillTargets!.join(', ')}`\n            : 'to all targets';\n          this.ui.updateSpinner(`${result.skillsExported} skills exported ${targetInfo}`, 'success');\n        } else {\n          this.ui.updateSpinner('No skills to export', 'info');\n        }\n      } catch (error) {\n        this.ui.updateSpinner('Failed to export skills', 'fail');\n        result.errors.push(error instanceof Error ? error.message : String(error));\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Step 3: Export docs/rules\n    if (!options.skipDocs) {\n      try {\n        this.ui.startSpinner(this.t('prompts.quickSync.syncing.rules'));\n\n        const docsPath = path.join(absolutePath, '.context', 'docs');\n        if (await fs.pathExists(docsPath)) {\n          const exportRulesService = new ExportRulesService({\n            ui: this.ui,\n            t: this.t,\n            version: this.version,\n          });\n\n          const hasCustomTargets = options.docTargets && options.docTargets.length > 0;\n\n          await exportRulesService.run(absolutePath, {\n            source: docsPath,\n            preset: hasCustomTargets ? undefined : 'all',\n            targets: hasCustomTargets ? options.docTargets : undefined,\n            force: options.force,\n            dryRun: options.dryRun,\n          });\n\n          const targetInfo = hasCustomTargets\n            ? `to ${options.docTargets!.join(', ')}`\n            : 'to all targets';\n          this.ui.updateSpinner(`Rules exported ${targetInfo}`, 'success');\n        } else {\n          this.ui.updateSpinner('No docs to export', 'info');\n        }\n      } catch (error) {\n        this.ui.updateSpinner('Failed to export rules', 'fail');\n        result.errors.push(error instanceof Error ? error.message : String(error));\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Step 4: Check docs status\n    if (!options.skipDocs) {\n      try {\n        this.ui.startSpinner(this.t('prompts.quickSync.syncing.docs'));\n\n        const detector = new StateDetector({ projectPath: absolutePath });\n        const state = await detector.detect();\n\n        if (state.state === 'outdated' && state.details.daysBehind) {\n          this.ui.updateSpinner(\n            this.t('prompts.quickSync.docsOutdated', { days: state.details.daysBehind }),\n            'warn'\n          );\n          this.ui.stopSpinner();\n\n          // Return info about outdated docs - caller can handle prompting\n          result.docsUpdated = false;\n        } else {\n          this.ui.updateSpinner('Docs up to date', 'success');\n          result.docsUpdated = true;\n        }\n      } catch (error) {\n        this.ui.updateSpinner('Failed to check docs', 'fail');\n        result.errors.push(error instanceof Error ? error.message : String(error));\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Get quick stats for project\n   */\n  async getStats(repoPath: string): Promise<{\n    docs: number;\n    agents: number;\n    skills: number;\n    daysOld?: number;\n  }> {\n    const absolutePath = path.resolve(repoPath);\n\n    let docs = 0;\n    let agents = 0;\n    let skills = 0;\n    let daysOld: number | undefined;\n\n    // Count docs\n    const docsPath = path.join(absolutePath, '.context', 'docs');\n    if (await fs.pathExists(docsPath)) {\n      const files = await fs.readdir(docsPath);\n      docs = files.filter(f => f.endsWith('.md')).length;\n    }\n\n    // Count agents\n    const agentsPath = path.join(absolutePath, '.context', 'agents');\n    if (await fs.pathExists(agentsPath)) {\n      const files = await fs.readdir(agentsPath);\n      agents = files.filter(f => f.endsWith('.md')).length;\n    }\n\n    // Count skills\n    const skillsPath = path.join(absolutePath, '.context', 'skills');\n    if (await fs.pathExists(skillsPath)) {\n      try {\n        const registry = createSkillRegistry(absolutePath);\n        const discovered = await registry.discoverAll();\n        skills = discovered.all.length;\n      } catch {\n        // Fallback to directory count\n        const dirs = await fs.readdir(skillsPath);\n        skills = dirs.filter(d => !d.startsWith('.') && d !== 'README.md').length;\n      }\n    }\n\n    // Get days old\n    const detector = new StateDetector({ projectPath: absolutePath });\n    const state = await detector.detect();\n    if (state.state === 'outdated') {\n      daysOld = state.details.daysBehind;\n    }\n\n    return { docs, agents, skills, daysOld };\n  }\n}\n\n/**\n * Factory function\n */\nexport function createQuickSyncService(deps: QuickSyncServiceDependencies): QuickSyncService {\n  return new QuickSyncService(deps);\n}\n"
  },
  {
    "path": "src/services/report/index.ts",
    "content": "export { ReportService } from './reportService';\nexport type { ReportOptions, WorkflowReport, ReportServiceDependencies } from './reportService';\n"
  },
  {
    "path": "src/services/report/reportService.ts",
    "content": "/**\n * Report Service\n *\n * Generates comprehensive progress reports for PREVC workflows.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  BaseDependencies,\n  displayProgressBar,\n  createBox,\n} from '../shared';\nimport { WorkflowService } from '../workflow';\nimport { StackDetector } from '../stack';\nimport {\n  PrevcStatus,\n  PrevcPhase,\n  PHASE_NAMES_EN,\n  ROLE_DISPLAY_NAMES_EN,\n  getScaleName,\n  ProjectScale,\n} from '../../workflow';\n\nexport type ReportServiceDependencies = BaseDependencies;\n\nexport interface ReportOptions {\n  format?: 'markdown' | 'json' | 'console';\n  output?: string;\n  verbose?: boolean;\n  includeStack?: boolean;\n  includeDecisions?: boolean;\n}\n\nexport interface WorkflowReport {\n  generated: string;\n  project: {\n    name: string;\n    scale: string;\n    description?: string;\n  };\n  progress: {\n    percentage: number;\n    completed: number;\n    total: number;\n    currentPhase: string;\n    isComplete: boolean;\n  };\n  phases: PhaseReport[];\n  timeline: TimelineEvent[];\n  stack?: StackReport;\n  recommendations: string[];\n}\n\ninterface PhaseReport {\n  phase: string;\n  name: string;\n  status: string;\n  startedAt?: string;\n  completedAt?: string;\n  outputs: string[];\n  roles: RoleReport[];\n}\n\ninterface RoleReport {\n  role: string;\n  name: string;\n  status: string;\n  outputs: string[];\n}\n\ninterface TimelineEvent {\n  timestamp: string;\n  event: string;\n  phase?: string;\n  role?: string;\n}\n\ninterface StackReport {\n  primaryLanguage: string | null;\n  languages: string[];\n  frameworks: string[];\n  testFrameworks: string[];\n}\n\nexport class ReportService {\n  constructor(private deps: ReportServiceDependencies) {}\n\n  /**\n   * Generate a workflow progress report\n   */\n  async generate(repoPath: string, options: ReportOptions = {}): Promise<WorkflowReport> {\n    const absolutePath = path.resolve(repoPath);\n    const workflowService = this.createWorkflowService(absolutePath);\n\n    if (!(await workflowService.hasWorkflow())) {\n      throw new Error(this.deps.t('errors.report.noWorkflow'));\n    }\n\n    const [status, summary, actions] = await Promise.all([\n      workflowService.getStatus(),\n      workflowService.getSummary(),\n      workflowService.getRecommendedActions(),\n    ]);\n\n    const report: WorkflowReport = {\n      generated: new Date().toISOString(),\n      project: {\n        name: status.project.name,\n        scale: getScaleName(status.project.scale as ProjectScale),\n      },\n      progress: {\n        percentage: summary.progress.percentage,\n        completed: summary.progress.completed,\n        total: summary.progress.total,\n        currentPhase: summary.currentPhase,\n        isComplete: summary.isComplete,\n      },\n      phases: this.buildPhasesReport(status),\n      timeline: this.buildTimeline(status),\n      recommendations: actions,\n    };\n\n    if (options.includeStack) {\n      report.stack = await this.detectStack(absolutePath);\n    }\n\n    return report;\n  }\n\n  /**\n   * Output report in requested format\n   */\n  async output(report: WorkflowReport, options: ReportOptions = {}): Promise<void> {\n    const format = options.format || 'console';\n    const outputFn = {\n      json: () => this.outputJson(report, options),\n      markdown: () => this.outputMarkdown(report, options),\n      console: () => this.outputConsole(report),\n    }[format];\n\n    await outputFn?.();\n  }\n\n  /**\n   * Create a silent workflow service instance\n   */\n  private createWorkflowService(repoPath: string): WorkflowService {\n    return new WorkflowService(repoPath, {\n      ui: {\n        displaySuccess: () => {},\n        displayError: () => {},\n        displayInfo: () => {},\n      },\n    });\n  }\n\n  /**\n   * Detect technology stack\n   */\n  private async detectStack(repoPath: string): Promise<StackReport> {\n    const detector = new StackDetector();\n    const stack = await detector.detect(repoPath);\n    return {\n      primaryLanguage: stack.primaryLanguage,\n      languages: stack.languages,\n      frameworks: stack.frameworks,\n      testFrameworks: stack.testFrameworks,\n    };\n  }\n\n  /**\n   * Build phases report from status\n   */\n  private buildPhasesReport(status: PrevcStatus): PhaseReport[] {\n    return Object.entries(status.phases).map(([phase, phaseStatus]) => ({\n      phase,\n      name: PHASE_NAMES_EN[phase as PrevcPhase] || phase,\n      status: phaseStatus.status,\n      startedAt: phaseStatus.started_at,\n      completedAt: phaseStatus.completed_at,\n      outputs: this.mapOutputs(phaseStatus.outputs),\n      roles: this.buildRolesForPhase(status, phase),\n    }));\n  }\n\n  /**\n   * Build roles for a specific phase\n   */\n  private buildRolesForPhase(status: PrevcStatus, phase: string): RoleReport[] {\n    return Object.entries(status.roles || {})\n      .filter(([, roleStatus]) => roleStatus?.phase === phase)\n      .map(([role, roleStatus]) => ({\n        role,\n        name: ROLE_DISPLAY_NAMES_EN[role as keyof typeof ROLE_DISPLAY_NAMES_EN] || role,\n        status: roleStatus?.status || 'pending',\n        outputs: roleStatus?.outputs || [],\n      }));\n  }\n\n  /**\n   * Map outputs to string array\n   */\n  private mapOutputs(outputs?: Array<{ path: string } | string>): string[] {\n    return (outputs || []).map(o => (typeof o === 'string' ? o : o.path));\n  }\n\n  /**\n   * Build timeline from status\n   */\n  private buildTimeline(status: PrevcStatus): TimelineEvent[] {\n    const events: TimelineEvent[] = [];\n\n    // Workflow start\n    if (status.project.started) {\n      events.push({ timestamp: status.project.started, event: 'Workflow started' });\n    }\n\n    // Phase events\n    for (const [phase, phaseStatus] of Object.entries(status.phases)) {\n      if (phaseStatus.started_at) {\n        events.push({ timestamp: phaseStatus.started_at, event: `Phase ${phase} started`, phase });\n      }\n      if (phaseStatus.completed_at) {\n        events.push({ timestamp: phaseStatus.completed_at, event: `Phase ${phase} completed`, phase });\n      }\n    }\n\n    // Role events\n    for (const [role, roleStatus] of Object.entries(status.roles || {})) {\n      if (roleStatus?.last_active) {\n        events.push({\n          timestamp: roleStatus.last_active,\n          event: `Role ${role} active`,\n          phase: roleStatus.phase,\n          role,\n        });\n      }\n    }\n\n    return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n  }\n\n  /**\n   * Output report as JSON\n   */\n  private async outputJson(report: WorkflowReport, options: ReportOptions): Promise<void> {\n    const json = JSON.stringify(report, null, 2);\n    await this.writeOutput(json, options);\n  }\n\n  /**\n   * Output report as Markdown\n   */\n  private async outputMarkdown(report: WorkflowReport, options: ReportOptions): Promise<void> {\n    const md = this.generateMarkdown(report);\n    await this.writeOutput(md, options);\n  }\n\n  /**\n   * Write output to file or console\n   */\n  private async writeOutput(content: string, options: ReportOptions): Promise<void> {\n    if (options.output) {\n      await fs.writeFile(options.output, content, 'utf-8');\n      this.deps.ui.displaySuccess(this.deps.t('success.report.saved', { path: options.output }));\n    } else {\n      console.log(content);\n    }\n  }\n\n  /**\n   * Output report to console with visual dashboard\n   */\n  private outputConsole(report: WorkflowReport): void {\n    console.log('\\n' + this.generateVisualDashboard(report) + '\\n');\n  }\n\n  /**\n   * Generate visual dashboard string\n   */\n  generateVisualDashboard(report: WorkflowReport): string {\n    const width = 50;\n    const innerWidth = width - 2;\n\n    // Build phase status line\n    const phaseDisplay = report.phases\n      .map(p => `${this.getStatusIcon(p.status)} ${p.phase}`)\n      .join(' → ');\n\n    // Build content lines\n    const content = [\n      this.centerText(report.project.name, innerWidth),\n      this.centerText(`[${report.project.scale}]`, innerWidth),\n      '─'.repeat(innerWidth),\n      this.centerText(displayProgressBar(report.progress.completed, report.progress.total, { width: innerWidth - 10 }), innerWidth),\n      this.centerText(`Progress: ${report.progress.percentage}% (${report.progress.completed}/${report.progress.total} phases)`, innerWidth),\n      '─'.repeat(innerWidth),\n      this.centerText(phaseDisplay, innerWidth),\n    ];\n\n    // Current phase indicator\n    if (!report.progress.isComplete) {\n      const current = report.phases.find(p => p.status === 'in_progress');\n      if (current) {\n        content.push(this.centerText(`↑ Current: ${current.name}`, innerWidth));\n      }\n    }\n\n    // Add phase details\n    content.push('─'.repeat(innerWidth));\n    for (const phase of report.phases) {\n      const icon = this.getStatusLabel(phase.status);\n      content.push(this.padText(` ${icon} ${phase.phase}: ${phase.name}`, innerWidth));\n      if (phase.outputs.length > 0 && phase.status === 'completed') {\n        content.push(this.padText(`    Outputs: ${phase.outputs.length} file(s)`, innerWidth));\n      }\n    }\n\n    // Recommendations\n    if (report.recommendations.length > 0) {\n      content.push('─'.repeat(innerWidth));\n      content.push(this.padText(' Next Actions:', innerWidth));\n      for (const action of report.recommendations.slice(0, 3)) {\n        content.push(this.padText(`  - ${action.slice(0, innerWidth - 6)}`, innerWidth));\n      }\n    }\n\n    const box = createBox(content, { width, title: 'Workflow Dashboard' });\n    return report.progress.isComplete ? box + '\\n\\nWorkflow Complete.' : box;\n  }\n\n  private getStatusIcon(status: string): string {\n    const icons: Record<string, string> = {\n      completed: '[x]',\n      in_progress: '[>]',\n      skipped: '[-]',\n      pending: '[ ]',\n    };\n    return icons[status] || '[ ]';\n  }\n\n  private getStatusLabel(status: string): string {\n    const labels: Record<string, string> = {\n      completed: '[done]',\n      in_progress: '[...]',\n      skipped: '[skip]',\n      pending: '[wait]',\n    };\n    return labels[status] || '[wait]';\n  }\n\n  private centerText(text: string, width: number): string {\n    const padding = Math.max(0, width - text.length);\n    const left = Math.floor(padding / 2);\n    return ' '.repeat(left) + text + ' '.repeat(padding - left);\n  }\n\n  private padText(text: string, width: number): string {\n    return text.length >= width ? text.slice(0, width) : text + ' '.repeat(width - text.length);\n  }\n\n  /**\n   * Generate Markdown report\n   */\n  private generateMarkdown(report: WorkflowReport): string {\n    const sections = [\n      `# Workflow Report: ${report.project.name}`,\n      '',\n      `> Generated: ${new Date(report.generated).toLocaleString()}`,\n      '',\n      this.generateSummaryTable(report),\n      this.generatePhasesSection(report.phases),\n      this.generateTimelineSection(report.timeline),\n      report.stack ? this.generateStackSection(report.stack) : '',\n      this.generateRecommendationsSection(report.recommendations),\n    ];\n\n    return sections.filter(Boolean).join('\\n');\n  }\n\n  private generateSummaryTable(report: WorkflowReport): string {\n    return [\n      '## Summary',\n      '',\n      '| Property | Value |',\n      '|----------|-------|',\n      `| Scale | ${report.project.scale} |`,\n      `| Progress | ${report.progress.percentage}% |`,\n      `| Current Phase | ${report.progress.currentPhase} |`,\n      `| Status | ${report.progress.isComplete ? 'Complete' : 'In Progress'} |`,\n      '',\n    ].join('\\n');\n  }\n\n  private generatePhasesSection(phases: PhaseReport[]): string {\n    return [\n      '## Phases',\n      '',\n      ...phases.flatMap(phase => [\n        `### ${this.getStatusLabel(phase.status)} ${phase.phase} - ${phase.name}`,\n        '',\n        `**Status:** ${phase.status}`,\n        phase.startedAt ? `**Started:** ${new Date(phase.startedAt).toLocaleString()}` : '',\n        phase.completedAt ? `**Completed:** ${new Date(phase.completedAt).toLocaleString()}` : '',\n        phase.outputs.length > 0 ? `\\n**Outputs:**\\n${phase.outputs.map(o => `- ${o}`).join('\\n')}` : '',\n        '',\n      ].filter(Boolean)),\n    ].join('\\n');\n  }\n\n  private generateTimelineSection(timeline: TimelineEvent[]): string {\n    if (timeline.length === 0) return '';\n    return [\n      '## Timeline',\n      '',\n      ...timeline.map(e => `- **${new Date(e.timestamp).toLocaleString()}**: ${e.event}`),\n      '',\n    ].join('\\n');\n  }\n\n  private generateStackSection(stack: StackReport): string {\n    return [\n      '## Technology Stack',\n      '',\n      `- **Primary Language:** ${stack.primaryLanguage || 'Unknown'}`,\n      `- **Languages:** ${stack.languages.join(', ') || 'None detected'}`,\n      `- **Frameworks:** ${stack.frameworks.join(', ') || 'None detected'}`,\n      `- **Test Frameworks:** ${stack.testFrameworks.join(', ') || 'None detected'}`,\n      '',\n    ].join('\\n');\n  }\n\n  private generateRecommendationsSection(recommendations: string[]): string {\n    if (recommendations.length === 0) return '';\n    return [\n      '## Recommended Actions',\n      '',\n      ...recommendations.map(a => `- [ ] ${a}`),\n      '',\n    ].join('\\n');\n  }\n}\n"
  },
  {
    "path": "src/services/reverseSync/importSkillsService.ts",
    "content": "/**\n * Import Skills Service\n *\n * Imports skills from AI tool directories to .context/skills/\n * Follows the pattern of ImportAgentsService with merge strategy support.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from '../../utils/theme';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type {\n  ImportSkillsCommandFlags,\n  ImportSkillsOptions,\n  SkillFileInfo,\n  MergeStrategy,\n  ImportMetadata,\n  ReverseSyncServiceDependencies,\n  ImportAction,\n} from './types';\nimport { SkillsDetector } from './skillsDetector';\nimport { VERSION } from '../../version';\n\nexport interface ImportSkillsResult {\n  targetPath: string;\n  filesCreated: number;\n  filesSkipped: number;\n  filesMerged: number;\n  filesRenamed: number;\n  filesFailed: number;\n  errors: Array<{ file: string; error: string }>;\n  importedFiles: Array<{\n    sourcePath: string;\n    targetPath: string;\n    action: ImportAction;\n  }>;\n}\n\nexport class ImportSkillsService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n  private readonly detector: SkillsDetector;\n\n  constructor(dependencies: ReverseSyncServiceDependencies) {\n    this.ui = dependencies.ui;\n    this.t = dependencies.t;\n    this.version = dependencies.version;\n    this.detector = new SkillsDetector();\n  }\n\n  async run(\n    rawOptions: ImportSkillsCommandFlags,\n    repoPath: string = process.cwd()\n  ): Promise<ImportSkillsResult> {\n    const options = await this.resolveOptions(rawOptions, repoPath);\n\n    // Detect skills\n    let detectionResult;\n    if (options.autoDetect && options.sourcePaths.length === 0) {\n      this.ui.startSpinner('Detecting skills from AI tool directories...');\n      detectionResult = await this.detector.detectSkills(repoPath, true);\n      this.ui.stopSpinner();\n    } else if (options.sourcePaths.length > 0) {\n      this.ui.startSpinner('Scanning specified paths for skills...');\n      detectionResult = await this.detector.detectFromPaths(options.sourcePaths, repoPath);\n      this.ui.stopSpinner();\n    } else {\n      detectionResult = { files: [], sources: [] };\n    }\n\n    if (detectionResult.files.length === 0) {\n      this.ui.displayWarning('No skills found in AI tool directories');\n      return this.createEmptyResult(options.targetPath);\n    }\n\n    this.ui.displayInfo(\n      'Skills found',\n      `${detectionResult.files.length} skill(s) detected`\n    );\n\n    if (options.verbose) {\n      detectionResult.files.forEach((file) => {\n        console.log(\n          `  ${colors.secondaryDim(symbols.pointer)} ${colors.primary(file.relativePath)}`\n        );\n      });\n    }\n\n    // Import files\n    const result = await this.importFiles(detectionResult.files, options);\n\n    this.displaySummary(result, options.dryRun);\n\n    return result;\n  }\n\n  /**\n   * Import skills with specific files (used by orchestrator)\n   */\n  async importSkillFiles(\n    files: SkillFileInfo[],\n    options: ImportSkillsOptions\n  ): Promise<ImportSkillsResult> {\n    return this.importFiles(files, options);\n  }\n\n  private async resolveOptions(\n    rawOptions: ImportSkillsCommandFlags,\n    repoPath: string\n  ): Promise<ImportSkillsOptions> {\n    const targetPath = rawOptions.target\n      ? path.resolve(rawOptions.target)\n      : path.resolve(repoPath, '.context/skills');\n\n    const sourcePaths = rawOptions.source || [];\n    const autoDetect = rawOptions.autoDetect !== false;\n\n    return {\n      sourcePaths,\n      targetPath,\n      force: Boolean(rawOptions.force),\n      dryRun: Boolean(rawOptions.dryRun),\n      verbose: Boolean(rawOptions.verbose),\n      autoDetect,\n      mergeStrategy: rawOptions.mergeStrategy || 'skip',\n      addMetadata: rawOptions.metadata !== false,\n    };\n  }\n\n  private async importFiles(\n    files: SkillFileInfo[],\n    options: ImportSkillsOptions\n  ): Promise<ImportSkillsResult> {\n    const result: ImportSkillsResult = {\n      targetPath: options.targetPath,\n      filesCreated: 0,\n      filesSkipped: 0,\n      filesMerged: 0,\n      filesRenamed: 0,\n      filesFailed: 0,\n      errors: [],\n      importedFiles: [],\n    };\n\n    if (!options.dryRun) {\n      await fs.ensureDir(options.targetPath);\n    }\n\n    this.ui.startSpinner(`Importing skills to ${options.targetPath}...`);\n\n    for (const file of files) {\n      try {\n        const importResult = await this.importSingleSkill(file, options);\n\n        result.importedFiles.push({\n          sourcePath: file.sourcePath,\n          targetPath: importResult.targetPath,\n          action: importResult.action,\n        });\n\n        switch (importResult.action) {\n          case 'created':\n          case 'overwritten':\n            result.filesCreated++;\n            break;\n          case 'skipped':\n            result.filesSkipped++;\n            break;\n          case 'merged':\n            result.filesMerged++;\n            break;\n          case 'renamed':\n            result.filesRenamed++;\n            break;\n          case 'failed':\n            result.filesFailed++;\n            result.errors.push({\n              file: file.filename,\n              error: importResult.error || 'Unknown error',\n            });\n            break;\n        }\n\n        if (options.verbose) {\n          this.logImportAction(file, importResult);\n        }\n      } catch (error) {\n        result.filesFailed++;\n        result.errors.push({\n          file: file.filename,\n          error: error instanceof Error ? error.message : String(error),\n        });\n\n        if (options.verbose) {\n          console.log(\n            `  ${colors.error(symbols.error)} ${file.filename}: ${\n              error instanceof Error ? error.message : String(error)\n            }`\n          );\n        }\n      }\n    }\n\n    this.ui.updateSpinner(\n      `Imported ${result.filesCreated} skill(s) to ${options.targetPath}`,\n      'success'\n    );\n    this.ui.stopSpinner();\n\n    return result;\n  }\n\n  private async importSingleSkill(\n    skill: SkillFileInfo,\n    options: ImportSkillsOptions\n  ): Promise<{ action: ImportAction; targetPath: string; error?: string }> {\n    // Determine target path - preserve skill directory structure\n    // e.g., .claude/skills/commit-message/SKILL.md -> .context/skills/commit-message/SKILL.md\n    const targetDir = path.join(options.targetPath, skill.slug);\n    const targetFile = path.join(targetDir, skill.filename);\n\n    const exists = await fs.pathExists(targetFile);\n\n    // Handle existing files based on merge strategy\n    if (exists) {\n      if (options.force || options.mergeStrategy === 'overwrite') {\n        // Overwrite strategy\n        if (!options.dryRun) {\n          await fs.ensureDir(targetDir);\n          const content = await this.prepareContent(skill, options);\n          await fs.writeFile(targetFile, content);\n        }\n        return { action: 'overwritten', targetPath: targetFile };\n      }\n\n      if (options.mergeStrategy === 'skip') {\n        return { action: 'skipped', targetPath: targetFile };\n      }\n\n      if (options.mergeStrategy === 'merge') {\n        if (!options.dryRun) {\n          await this.mergeContent(skill, targetFile, options);\n        }\n        return { action: 'merged', targetPath: targetFile };\n      }\n\n      if (options.mergeStrategy === 'rename') {\n        const renamedPath = await this.getRenamedPath(targetFile, skill.sourceTool);\n        if (!options.dryRun) {\n          await fs.ensureDir(path.dirname(renamedPath));\n          const content = await this.prepareContent(skill, options);\n          await fs.writeFile(renamedPath, content);\n        }\n        return { action: 'renamed', targetPath: renamedPath };\n      }\n    }\n\n    // Create new file\n    if (!options.dryRun) {\n      await fs.ensureDir(targetDir);\n      const content = await this.prepareContent(skill, options);\n      await fs.writeFile(targetFile, content);\n    }\n\n    return { action: 'created', targetPath: targetFile };\n  }\n\n  private async prepareContent(\n    skill: SkillFileInfo,\n    options: ImportSkillsOptions\n  ): Promise<string> {\n    const originalContent = await fs.readFile(skill.sourcePath, 'utf-8');\n\n    if (!options.addMetadata) {\n      return originalContent;\n    }\n\n    // Add import metadata as frontmatter\n    return this.addImportMetadata(originalContent, skill);\n  }\n\n  private addImportMetadata(content: string, skill: SkillFileInfo): string {\n    const metadata: ImportMetadata = {\n      source_tool: skill.sourceTool,\n      source_path: skill.relativePath,\n      imported_at: new Date().toISOString(),\n      ai_context_version: VERSION,\n    };\n\n    // Check if content already has frontmatter\n    const lines = content.split('\\n');\n    const hasFrontmatter = lines[0]?.trim() === '---';\n\n    if (hasFrontmatter) {\n      // Find end of existing frontmatter\n      const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === '---');\n      if (endIndex > 0) {\n        // Insert our metadata into existing frontmatter\n        const existingFrontmatter = lines.slice(1, endIndex);\n        const body = lines.slice(endIndex + 1).join('\\n');\n\n        const newFrontmatter = [\n          '---',\n          ...existingFrontmatter,\n          `source_tool: ${metadata.source_tool}`,\n          `source_path: ${metadata.source_path}`,\n          `imported_at: ${metadata.imported_at}`,\n          `ai_context_version: ${metadata.ai_context_version}`,\n          '---',\n        ];\n\n        return newFrontmatter.join('\\n') + '\\n' + body;\n      }\n    }\n\n    // Add new frontmatter\n    const frontmatter = [\n      '---',\n      `source_tool: ${metadata.source_tool}`,\n      `source_path: ${metadata.source_path}`,\n      `imported_at: ${metadata.imported_at}`,\n      `ai_context_version: ${metadata.ai_context_version}`,\n      '---',\n      '',\n    ];\n\n    return frontmatter.join('\\n') + content;\n  }\n\n  private async mergeContent(\n    skill: SkillFileInfo,\n    existingPath: string,\n    options: ImportSkillsOptions\n  ): Promise<void> {\n    const existingContent = await fs.readFile(existingPath, 'utf-8');\n    const newContent = await this.prepareContent(skill, options);\n\n    if (existingContent.includes(newContent)) {\n      return;\n    }\n\n    const separator = [\n      '',\n      '---',\n      `<!-- Imported from ${skill.sourceTool} on ${new Date().toISOString()} -->`,\n      '',\n    ].join('\\n');\n\n    const mergedContent = existingContent + separator + newContent;\n\n    await fs.writeFile(existingPath, mergedContent);\n  }\n\n  private async getRenamedPath(originalPath: string, sourceTool: string): Promise<string> {\n    const dir = path.dirname(originalPath);\n    const ext = path.extname(originalPath);\n    const baseName = path.basename(originalPath, ext);\n\n    // Try {name}-{tool}.md\n    let newPath = path.join(dir, `${baseName}-${sourceTool}${ext}`);\n    if (!(await fs.pathExists(newPath))) {\n      return newPath;\n    }\n\n    // Try {name}-{tool}-{n}.md\n    let counter = 2;\n    while (await fs.pathExists(newPath)) {\n      newPath = path.join(dir, `${baseName}-${sourceTool}-${counter}${ext}`);\n      counter++;\n      if (counter > 100) {\n        throw new Error('Could not find unique filename');\n      }\n    }\n\n    return newPath;\n  }\n\n  private logImportAction(\n    skill: SkillFileInfo,\n    result: { action: ImportAction; targetPath: string }\n  ): void {\n    const actionSymbols: Record<ImportAction, string> = {\n      created: colors.success(symbols.success),\n      overwritten: colors.warning('↻'),\n      skipped: colors.secondaryDim('○'),\n      merged: colors.accent('⊕'),\n      renamed: colors.accent('→'),\n      failed: colors.error(symbols.error),\n    };\n\n    const actionLabels: Record<ImportAction, string> = {\n      created: 'Created',\n      overwritten: 'Overwritten',\n      skipped: 'Skipped',\n      merged: 'Merged',\n      renamed: 'Renamed',\n      failed: 'Failed',\n    };\n\n    console.log(\n      `  ${actionSymbols[result.action]} ${colors.secondaryDim(actionLabels[result.action])}: ${colors.primary(result.targetPath)}`\n    );\n  }\n\n  private displaySummary(result: ImportSkillsResult, dryRun: boolean): void {\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header('Skills Import Summary'));\n    console.log('');\n\n    const status =\n      result.filesFailed > 0 ? colors.error(symbols.error) : colors.success(symbols.success);\n\n    console.log(`${status} ${colors.primary(result.targetPath)}`);\n\n    const counts: string[] = [];\n    if (result.filesCreated > 0) counts.push(`Created: ${result.filesCreated}`);\n    if (result.filesSkipped > 0) counts.push(`Skipped: ${result.filesSkipped}`);\n    if (result.filesMerged > 0) counts.push(`Merged: ${result.filesMerged}`);\n    if (result.filesRenamed > 0) counts.push(`Renamed: ${result.filesRenamed}`);\n    if (result.filesFailed > 0) counts.push(`Failed: ${result.filesFailed}`);\n\n    console.log(`    ${colors.secondary(counts.join(', '))}`);\n\n    if (result.errors.length > 0) {\n      result.errors.forEach((err) => {\n        console.log(\n          `    ${colors.error(symbols.error)} ${colors.secondaryDim(`${err.file} - ${err.error}`)}`\n        );\n      });\n    }\n\n    console.log('');\n    if (dryRun) {\n      console.log(typography.warning('DRY RUN - No changes were made'));\n    }\n    console.log('');\n  }\n\n  private createEmptyResult(targetPath: string): ImportSkillsResult {\n    return {\n      targetPath,\n      filesCreated: 0,\n      filesSkipped: 0,\n      filesMerged: 0,\n      filesRenamed: 0,\n      filesFailed: 0,\n      errors: [],\n      importedFiles: [],\n    };\n  }\n}\n"
  },
  {
    "path": "src/services/reverseSync/index.ts",
    "content": "/**\n * Reverse Sync Module\n *\n * Exports all public APIs for the reverse sync feature\n */\n\n// Types\nexport * from './types';\n\n// Presets\nexport {\n  SKILL_SOURCES,\n  TOOL_NAME_MAP,\n  TOOL_DISPLAY_NAMES,\n  TOOL_CAPABILITIES,\n  ALL_TOOL_IDS,\n  getSkillSourceByName,\n  getAllSkillSourceNames,\n  getToolIdFromPath,\n  getToolDisplayName,\n  getToolCapabilities,\n} from './presets';\n\n// Detectors\nexport { SkillsDetector } from './skillsDetector';\nexport { ToolDetector, formatDetectionSummary } from './toolDetector';\n\n// Services\nexport { ImportSkillsService, type ImportSkillsResult } from './importSkillsService';\nexport {\n  ReverseQuickSyncService,\n  createReverseQuickSyncService,\n} from './reverseQuickSyncService';\n"
  },
  {
    "path": "src/services/reverseSync/presets.ts",
    "content": "/**\n * Reverse Sync Presets\n *\n * Configuration for skill sources and tool mappings.\n * Derived from the unified tool registry.\n */\n\nimport type { RuleSource } from '../import/types';\nimport {\n  getSkillsImportSources,\n  getDirectoryPrefixMap,\n  getDisplayNameMap,\n  getCapabilitiesMap,\n  getAllToolIds,\n  getToolIdFromPath as registryGetToolIdFromPath,\n  getToolDisplayName as registryGetToolDisplayName,\n  getToolCapabilities as registryGetToolCapabilities,\n  ToolCapabilities,\n} from '../shared';\n\n// ============================================================================\n// Skill Sources (derived from tool registry)\n// ============================================================================\n\n/**\n * Sources for skill detection (derived from tool registry)\n */\nexport const SKILL_SOURCES: RuleSource[] = getSkillsImportSources();\n\n// ============================================================================\n// Tool Name Mappings (derived from tool registry)\n// ============================================================================\n\n/**\n * Map directory prefixes to canonical tool identifiers (derived from tool registry)\n */\nexport const TOOL_NAME_MAP: Record<string, string> = getDirectoryPrefixMap();\n\n/**\n * Human-readable display names for AI tools (derived from tool registry)\n */\nexport const TOOL_DISPLAY_NAMES: Record<string, string> = getDisplayNameMap();\n\n/**\n * Tool capabilities - which content types each tool supports (derived from tool registry)\n */\nexport const TOOL_CAPABILITIES: Record<string, ToolCapabilities> = getCapabilitiesMap();\n\n/**\n * All known tool identifiers (derived from tool registry)\n */\nexport const ALL_TOOL_IDS = getAllToolIds();\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Get a skill source by name\n */\nexport function getSkillSourceByName(name: string): RuleSource | undefined {\n  return SKILL_SOURCES.find((s) => s.name === name);\n}\n\n/**\n * Get all skill source names\n */\nexport function getAllSkillSourceNames(): string[] {\n  return SKILL_SOURCES.map((s) => s.name);\n}\n\n/**\n * Extract tool ID from a file path (delegates to tool registry)\n * @param filePath - Relative or absolute path\n * @returns Tool ID or 'unknown' if not recognized\n */\nexport function getToolIdFromPath(filePath: string): string {\n  return registryGetToolIdFromPath(filePath);\n}\n\n/**\n * Get display name for a tool ID (delegates to tool registry)\n */\nexport function getToolDisplayName(toolId: string): string {\n  return registryGetToolDisplayName(toolId);\n}\n\n/**\n * Get tool capabilities (delegates to tool registry)\n */\nexport function getToolCapabilities(toolId: string): ToolCapabilities {\n  return registryGetToolCapabilities(toolId);\n}\n"
  },
  {
    "path": "src/services/reverseSync/reverseQuickSyncService.ts",
    "content": "/**\n * Reverse Quick Sync Service\n *\n * Orchestrates the import of rules, agents, and skills from AI tool\n * directories into .context/. This is the inverse of QuickSyncService.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from '../../utils/theme';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type {\n  ReverseSyncServiceDependencies,\n  ReverseSyncOptions,\n  ReverseSyncResult,\n  ReverseSyncCommandFlags,\n  ToolDetectionResult,\n  MergeStrategy,\n  ImportAction,\n} from './types';\nimport type { ImportFormat } from '../import/types';\nimport { ToolDetector, formatDetectionSummary } from './toolDetector';\nimport { SkillsDetector } from './skillsDetector';\nimport { ImportSkillsService } from './importSkillsService';\nimport { ImportRulesService } from '../import/importRulesService';\nimport { ImportAgentsService } from '../import/importAgentsService';\nimport { getToolIdFromPath } from './presets';\nimport { VERSION } from '../../version';\n\nexport class ReverseQuickSyncService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n  private readonly toolDetector: ToolDetector;\n  private readonly skillsDetector: SkillsDetector;\n  private readonly importSkillsService: ImportSkillsService;\n  private readonly importRulesService: ImportRulesService;\n  private readonly importAgentsService: ImportAgentsService;\n\n  constructor(deps: ReverseSyncServiceDependencies) {\n    this.ui = deps.ui;\n    this.t = deps.t;\n    this.version = deps.version;\n    this.toolDetector = new ToolDetector();\n    this.skillsDetector = new SkillsDetector();\n    this.importSkillsService = new ImportSkillsService(deps);\n    this.importRulesService = new ImportRulesService(deps);\n    this.importAgentsService = new ImportAgentsService(deps);\n  }\n\n  /**\n   * Run unified reverse sync operation\n   */\n  async run(repoPath: string, options: ReverseSyncCommandFlags = {}): Promise<ReverseSyncResult> {\n    const absolutePath = path.resolve(repoPath);\n    const resolvedOptions = this.resolveOptions(options, absolutePath);\n\n    const result: ReverseSyncResult = {\n      rulesImported: 0,\n      agentsImported: 0,\n      skillsImported: 0,\n      filesSkipped: 0,\n      filesMerged: 0,\n      filesRenamed: 0,\n      filesFailed: 0,\n      errors: [],\n      importedFiles: [],\n    };\n\n    // Step 1: Detect available tools\n    this.ui.startSpinner('Detecting AI tool configurations...');\n    const detection = await this.toolDetector.detect(absolutePath);\n    this.ui.stopSpinner();\n\n    if (detection.summary.totalFiles === 0) {\n      this.ui.displayWarning('No AI tool configuration files found');\n      return result;\n    }\n\n    // Display detection summary\n    console.log('');\n    console.log(formatDetectionSummary(detection));\n    console.log('');\n\n    // Step 2: Import rules\n    if (!resolvedOptions.skipRules && detection.summary.totalRules > 0) {\n      try {\n        this.ui.startSpinner('Importing rules...');\n\n        const rulesResult = await this.importRulesService.run(\n          {\n            force: resolvedOptions.force,\n            dryRun: resolvedOptions.dryRun,\n            verbose: resolvedOptions.verbose,\n            format: resolvedOptions.format,\n            autoDetect: true,\n          },\n          absolutePath\n        );\n\n        result.rulesImported = rulesResult.filesCreated;\n        result.filesSkipped += rulesResult.filesSkipped;\n        result.filesFailed += rulesResult.filesFailed;\n        result.errors.push(...rulesResult.errors);\n        this.ui.updateSpinner(`Rules imported: ${result.rulesImported}`, 'success');\n      } catch (error) {\n        this.ui.updateSpinner('Failed to import rules', 'fail');\n        result.errors.push({\n          file: 'rules',\n          error: error instanceof Error ? error.message : String(error),\n        });\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Step 3: Import agents\n    if (!resolvedOptions.skipAgents && detection.summary.totalAgents > 0) {\n      try {\n        this.ui.startSpinner('Importing agents...');\n\n        const agentsResult = await this.importAgentsService.run(\n          {\n            force: resolvedOptions.force,\n            dryRun: resolvedOptions.dryRun,\n            verbose: resolvedOptions.verbose,\n            autoDetect: true,\n          },\n          absolutePath\n        );\n\n        result.agentsImported = agentsResult.filesCreated;\n        result.filesSkipped += agentsResult.filesSkipped;\n        result.filesFailed += agentsResult.filesFailed;\n        result.errors.push(...agentsResult.errors);\n        this.ui.updateSpinner(`Agents imported: ${result.agentsImported}`, 'success');\n      } catch (error) {\n        this.ui.updateSpinner('Failed to import agents', 'fail');\n        result.errors.push({\n          file: 'agents',\n          error: error instanceof Error ? error.message : String(error),\n        });\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Step 4: Import skills\n    if (!resolvedOptions.skipSkills && detection.summary.totalSkills > 0) {\n      try {\n        this.ui.startSpinner('Importing skills...');\n\n        const skillsResult = await this.importSkillsService.run(\n          {\n            force: resolvedOptions.force,\n            dryRun: resolvedOptions.dryRun,\n            verbose: resolvedOptions.verbose,\n            autoDetect: true,\n            mergeStrategy: resolvedOptions.mergeStrategy,\n            metadata: resolvedOptions.addMetadata,\n          },\n          absolutePath\n        );\n\n        result.skillsImported = skillsResult.filesCreated;\n        result.filesSkipped += skillsResult.filesSkipped;\n        result.filesMerged += skillsResult.filesMerged;\n        result.filesRenamed += skillsResult.filesRenamed;\n        result.filesFailed += skillsResult.filesFailed;\n        result.errors.push(...skillsResult.errors);\n        result.importedFiles.push(\n          ...skillsResult.importedFiles.map((f) => ({\n            ...f,\n            type: 'skill' as const,\n          }))\n        );\n\n        this.ui.updateSpinner(`Skills imported: ${result.skillsImported}`, 'success');\n      } catch (error) {\n        this.ui.updateSpinner('Failed to import skills', 'fail');\n        result.errors.push({\n          file: 'skills',\n          error: error instanceof Error ? error.message : String(error),\n        });\n      } finally {\n        this.ui.stopSpinner();\n      }\n    }\n\n    // Display final summary\n    this.displaySummary(result, resolvedOptions);\n\n    return result;\n  }\n\n  /**\n   * Detect available tools without importing\n   */\n  async detect(repoPath: string): Promise<ToolDetectionResult> {\n    return this.toolDetector.detect(repoPath);\n  }\n\n  /**\n   * Preview what would be imported (dry-run)\n   */\n  async preview(repoPath: string, options: ReverseSyncCommandFlags = {}): Promise<ReverseSyncResult> {\n    return this.run(repoPath, { ...options, dryRun: true });\n  }\n\n  private resolveOptions(\n    options: ReverseSyncCommandFlags,\n    repoPath: string\n  ): ReverseSyncOptions {\n    return {\n      repoPath,\n      targetPath: path.join(repoPath, '.context'),\n      dryRun: Boolean(options.dryRun),\n      force: Boolean(options.force),\n      skipAgents: Boolean(options.skipAgents),\n      skipSkills: Boolean(options.skipSkills),\n      skipRules: Boolean(options.skipRules),\n      mergeStrategy: options.mergeStrategy || 'skip',\n      verbose: Boolean(options.verbose),\n      format: options.format || 'formatted',\n      addMetadata: options.metadata !== false,\n      sourceTools: options.sourceTools,\n    };\n  }\n\n  private displaySummary(result: ReverseSyncResult, options: ReverseSyncOptions): void {\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header('Reverse Sync Summary'));\n    console.log('');\n\n    const totalImported = result.rulesImported + result.agentsImported + result.skillsImported;\n    const hasErrors = result.filesFailed > 0 || result.errors.length > 0;\n    const status = hasErrors ? colors.error(symbols.error) : colors.success(symbols.success);\n\n    console.log(`${status} ${colors.primary(options.targetPath)}`);\n    console.log('');\n\n    // Display counts\n    if (result.rulesImported > 0) {\n      console.log(typography.labeledValue('Rules', result.rulesImported.toString()));\n    }\n    if (result.agentsImported > 0) {\n      console.log(typography.labeledValue('Agents', result.agentsImported.toString()));\n    }\n    if (result.skillsImported > 0) {\n      console.log(typography.labeledValue('Skills', result.skillsImported.toString()));\n    }\n\n    console.log('');\n    console.log(typography.labeledValue('Total imported', totalImported.toString()));\n\n    if (result.filesSkipped > 0) {\n      console.log(typography.labeledValue('Skipped', result.filesSkipped.toString()));\n    }\n    if (result.filesMerged > 0) {\n      console.log(typography.labeledValue('Merged', result.filesMerged.toString()));\n    }\n    if (result.filesRenamed > 0) {\n      console.log(typography.labeledValue('Renamed', result.filesRenamed.toString()));\n    }\n    if (result.filesFailed > 0) {\n      console.log(typography.labeledValue('Failed', result.filesFailed.toString()));\n    }\n\n    // Display errors\n    if (result.errors.length > 0) {\n      console.log('');\n      console.log(colors.error('Errors:'));\n      result.errors.forEach((err) => {\n        console.log(`  ${colors.error(symbols.error)} ${err.file}: ${err.error}`);\n      });\n    }\n\n    console.log('');\n    if (options.dryRun) {\n      console.log(typography.warning('DRY RUN - No changes were made'));\n      console.log('');\n    }\n  }\n}\n\n/**\n * Factory function\n */\nexport function createReverseQuickSyncService(\n  deps: ReverseSyncServiceDependencies\n): ReverseQuickSyncService {\n  return new ReverseQuickSyncService(deps);\n}\n"
  },
  {
    "path": "src/services/reverseSync/skillsDetector.ts",
    "content": "/**\n * Skills Detector\n *\n * Detects skill files from AI tool directories\n * Pattern follows AgentsDetector from import/agentsDetector.ts\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport type { SkillFileInfo, SkillDetectionResult, SkillMetadata } from './types';\nimport { SKILL_SOURCES, getToolIdFromPath } from './presets';\n\nexport class SkillsDetector {\n  /**\n   * Detect skill files in the repository from known AI tool locations\n   */\n  async detectSkills(repoPath: string, autoDetect: boolean = true): Promise<SkillDetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: SkillFileInfo[] = [];\n    const sources: string[] = [];\n    const seenPaths = new Set<string>();\n\n    if (!autoDetect) {\n      return { files: detectedFiles, sources };\n    }\n\n    // Check each skill source\n    for (const source of SKILL_SOURCES) {\n      // Check explicit paths\n      for (const sourcePath of source.paths) {\n        const fullPath = path.isAbsolute(sourcePath)\n          ? sourcePath\n          : path.join(absoluteRepoPath, sourcePath);\n\n        try {\n          if (await fs.pathExists(fullPath)) {\n            const stat = await fs.stat(fullPath);\n\n            if (stat.isDirectory()) {\n              // Find all SKILL.md files and other markdown files\n              const files = await glob('**/SKILL.md', {\n                cwd: fullPath,\n                absolute: true,\n                ignore: ['node_modules/**', '.git/**'],\n              });\n\n              for (const file of files) {\n                if (seenPaths.has(file)) continue;\n                seenPaths.add(file);\n\n                const skillInfo = await this.createSkillFileInfo(file, absoluteRepoPath);\n                if (skillInfo) {\n                  detectedFiles.push(skillInfo);\n                }\n              }\n\n              if (files.length > 0 && !sources.includes(fullPath)) {\n                sources.push(fullPath);\n              }\n            }\n          }\n        } catch {\n          // Skip inaccessible paths\n          continue;\n        }\n      }\n\n      // Also try glob patterns from repo root\n      for (const pattern of source.patterns) {\n        try {\n          const matches = await glob(pattern, {\n            cwd: absoluteRepoPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],\n          });\n\n          for (const match of matches) {\n            if (seenPaths.has(match)) continue;\n            seenPaths.add(match);\n\n            const skillInfo = await this.createSkillFileInfo(match, absoluteRepoPath);\n            if (skillInfo) {\n              detectedFiles.push(skillInfo);\n              const dir = path.dirname(match);\n              if (!sources.includes(dir)) {\n                sources.push(dir);\n              }\n            }\n          }\n        } catch {\n          // Skip pattern if it fails\n          continue;\n        }\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)],\n    };\n  }\n\n  /**\n   * Detect skills from specific source paths\n   */\n  async detectFromPaths(sourcePaths: string[], repoPath: string): Promise<SkillDetectionResult> {\n    const absoluteRepoPath = path.resolve(repoPath);\n    const detectedFiles: SkillFileInfo[] = [];\n    const sources: string[] = [];\n    const seenPaths = new Set<string>();\n\n    for (const sourcePath of sourcePaths) {\n      const fullPath = path.isAbsolute(sourcePath)\n        ? sourcePath\n        : path.join(absoluteRepoPath, sourcePath);\n\n      try {\n        if (!(await fs.pathExists(fullPath))) {\n          continue;\n        }\n\n        const stat = await fs.stat(fullPath);\n\n        if (stat.isFile() && fullPath.endsWith('.md')) {\n          if (seenPaths.has(fullPath)) continue;\n          seenPaths.add(fullPath);\n\n          const skillInfo = await this.createSkillFileInfo(fullPath, absoluteRepoPath);\n          if (skillInfo) {\n            detectedFiles.push(skillInfo);\n            sources.push(path.dirname(fullPath));\n          }\n        } else if (stat.isDirectory()) {\n          // Find all markdown files in directory\n          const files = await glob('**/SKILL.md', {\n            cwd: fullPath,\n            absolute: true,\n            ignore: ['node_modules/**', '.git/**'],\n          });\n\n          for (const file of files) {\n            if (seenPaths.has(file)) continue;\n            seenPaths.add(file);\n\n            const skillInfo = await this.createSkillFileInfo(file, absoluteRepoPath);\n            if (skillInfo) {\n              detectedFiles.push(skillInfo);\n            }\n          }\n\n          if (files.length > 0) {\n            sources.push(fullPath);\n          }\n        }\n      } catch {\n        // Skip inaccessible paths\n        continue;\n      }\n    }\n\n    return {\n      files: detectedFiles.sort((a, b) => a.name.localeCompare(b.name)),\n      sources: [...new Set(sources)],\n    };\n  }\n\n  /**\n   * Create a SkillFileInfo from a file path\n   */\n  private async createSkillFileInfo(\n    filePath: string,\n    repoPath: string\n  ): Promise<SkillFileInfo | null> {\n    try {\n      const relativePath = path.relative(repoPath, filePath);\n      const filename = path.basename(filePath);\n      const sourceTool = getToolIdFromPath(relativePath);\n\n      // Derive name and slug from path\n      // For skills, typically the parent directory name is the skill slug\n      // e.g., .claude/skills/commit-message/SKILL.md -> slug: commit-message\n      const dirName = path.basename(path.dirname(filePath));\n      const slug = filename === 'SKILL.md' ? dirName : path.basename(filename, '.md');\n      const name = this.slugToName(slug);\n\n      // Try to parse metadata from content\n      let metadata: SkillMetadata | undefined;\n      try {\n        const content = await fs.readFile(filePath, 'utf-8');\n        metadata = this.parseSkillMetadata(content);\n      } catch {\n        // Metadata parsing is optional\n      }\n\n      return {\n        name: metadata?.name || name,\n        slug,\n        sourcePath: filePath,\n        relativePath,\n        filename,\n        sourceTool,\n        metadata,\n      };\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Parse skill metadata from YAML frontmatter\n   */\n  private parseSkillMetadata(content: string): SkillMetadata | undefined {\n    const lines = content.split('\\n');\n\n    if (lines[0]?.trim() !== '---') {\n      return undefined;\n    }\n\n    const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === '---');\n\n    if (endIndex === -1) {\n      return undefined;\n    }\n\n    const frontMatterLines = lines.slice(1, endIndex);\n    const metadata: SkillMetadata = {};\n\n    for (const line of frontMatterLines) {\n      const match = line.match(/^(\\w+):\\s*(.*)$/);\n      if (match) {\n        const key = match[1];\n        const value = match[2].trim();\n\n        switch (key) {\n          case 'name':\n            metadata.name = value;\n            break;\n          case 'description':\n            metadata.description = value;\n            break;\n          case 'phases':\n            // Parse array format: [P, E, V] or comma-separated\n            metadata.phases = this.parseArrayValue(value);\n            break;\n          case 'triggers':\n            metadata.triggers = this.parseArrayValue(value);\n            break;\n          case 'tags':\n            metadata.tags = this.parseArrayValue(value);\n            break;\n        }\n      }\n    }\n\n    return Object.keys(metadata).length > 0 ? metadata : undefined;\n  }\n\n  /**\n   * Parse an array value from YAML (simplified)\n   * Handles: [a, b, c] or \"a, b, c\"\n   */\n  private parseArrayValue(value: string): string[] {\n    // Remove brackets if present\n    let cleaned = value.replace(/^\\[|\\]$/g, '').trim();\n    // Split by comma\n    return cleaned.split(',').map((s) => s.trim()).filter(Boolean);\n  }\n\n  /**\n   * Convert a slug to a human-readable name\n   * e.g., \"commit-message\" -> \"Commit Message\"\n   */\n  private slugToName(slug: string): string {\n    return slug\n      .split('-')\n      .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n      .join(' ');\n  }\n}\n"
  },
  {
    "path": "src/services/reverseSync/toolDetector.ts",
    "content": "/**\n * Tool Detector\n *\n * High-level detector that identifies which AI tools are present\n * and aggregates results from rules, agents, and skills detectors.\n */\n\nimport type { ToolPresence, ToolDetectionResult, SkillFileInfo } from './types';\nimport type { RuleFileInfo } from '../import/types';\nimport { RulesDetector } from '../import/rulesDetector';\nimport { AgentsDetector } from '../import/agentsDetector';\nimport { SkillsDetector } from './skillsDetector';\nimport {\n  ALL_TOOL_IDS,\n  TOOL_DISPLAY_NAMES,\n  TOOL_CAPABILITIES,\n  getToolIdFromPath,\n  getToolDisplayName,\n} from './presets';\n\nexport class ToolDetector {\n  private rulesDetector: RulesDetector;\n  private agentsDetector: AgentsDetector;\n  private skillsDetector: SkillsDetector;\n\n  constructor() {\n    this.rulesDetector = new RulesDetector();\n    this.agentsDetector = new AgentsDetector();\n    this.skillsDetector = new SkillsDetector();\n  }\n\n  /**\n   * Detect all AI tools present in the repository\n   * Returns a high-level summary grouped by tool\n   */\n  async detect(repoPath: string): Promise<ToolDetectionResult> {\n    // Run all detectors in parallel\n    const [rulesResult, agentsResult, skillsResult] = await Promise.all([\n      this.rulesDetector.detectRules(repoPath, true),\n      this.agentsDetector.detectAgents(repoPath, true),\n      this.skillsDetector.detectSkills(repoPath, true),\n    ]);\n\n    // Group results by tool\n    const tools = this.groupByTool(\n      rulesResult.files,\n      agentsResult.files,\n      skillsResult.files\n    );\n\n    // Calculate summary\n    const summary = {\n      toolsFound: tools.filter((t) => t.detected).length,\n      totalRules: rulesResult.files.length,\n      totalAgents: agentsResult.files.length,\n      totalSkills: skillsResult.files.length,\n      totalFiles:\n        rulesResult.files.length + agentsResult.files.length + skillsResult.files.length,\n    };\n\n    return { tools, summary };\n  }\n\n  /**\n   * Get detection for a specific tool\n   */\n  async detectTool(repoPath: string, toolId: string): Promise<ToolPresence | null> {\n    const result = await this.detect(repoPath);\n    return result.tools.find((t) => t.id === toolId) || null;\n  }\n\n  /**\n   * Group detected files by their source tool\n   */\n  private groupByTool(\n    rules: RuleFileInfo[],\n    agents: RuleFileInfo[],\n    skills: SkillFileInfo[]\n  ): ToolPresence[] {\n    // Initialize tool presence map with all known tools\n    const toolMap = new Map<string, ToolPresence>();\n\n    for (const toolId of ALL_TOOL_IDS) {\n      const capabilities = TOOL_CAPABILITIES[toolId] || {\n        rules: false,\n        agents: false,\n        skills: false,\n      };\n\n      toolMap.set(toolId, {\n        id: toolId,\n        displayName: getToolDisplayName(toolId),\n        detected: false,\n        paths: {\n          rules: [],\n          agents: [],\n          skills: [],\n        },\n        counts: {\n          rules: 0,\n          agents: 0,\n          skills: 0,\n          total: 0,\n        },\n      });\n    }\n\n    // Group rules by tool\n    for (const rule of rules) {\n      const toolId = this.getToolIdForRule(rule);\n      const tool = toolMap.get(toolId);\n      if (tool) {\n        tool.paths.rules.push(rule.relativePath);\n        tool.counts.rules++;\n        tool.counts.total++;\n        tool.detected = true;\n      }\n    }\n\n    // Group agents by tool\n    for (const agent of agents) {\n      const toolId = getToolIdFromPath(agent.relativePath);\n      const tool = toolMap.get(toolId);\n      if (tool) {\n        tool.paths.agents.push(agent.relativePath);\n        tool.counts.agents++;\n        tool.counts.total++;\n        tool.detected = true;\n      }\n    }\n\n    // Group skills by tool\n    for (const skill of skills) {\n      const toolId = skill.sourceTool;\n      const tool = toolMap.get(toolId);\n      if (tool) {\n        tool.paths.skills.push(skill.relativePath);\n        tool.counts.skills++;\n        tool.counts.total++;\n        tool.detected = true;\n      }\n    }\n\n    // Return sorted by detection status (detected first), then by name\n    return Array.from(toolMap.values()).sort((a, b) => {\n      if (a.detected !== b.detected) {\n        return a.detected ? -1 : 1;\n      }\n      return a.displayName.localeCompare(b.displayName);\n    });\n  }\n\n  /**\n   * Determine tool ID for a rule file\n   * Uses the rule type and path to identify the tool\n   */\n  private getToolIdForRule(rule: RuleFileInfo): string {\n    // First try to get tool from path\n    const toolFromPath = getToolIdFromPath(rule.relativePath);\n    if (toolFromPath !== 'unknown') {\n      return toolFromPath;\n    }\n\n    // Fall back to rule type mapping\n    const typeToTool: Record<string, string> = {\n      cursorrules: 'cursor',\n      'claude-memory': 'claude',\n      'github-copilot': 'github',\n      windsurfrules: 'windsurf',\n      clinerules: 'cline',\n      continue: 'continue',\n      aider: 'aider',\n      codex: 'codex',\n      gemini: 'gemini',\n      antigravity: 'antigravity',\n      trae: 'trae',\n      zed: 'zed',\n      generic: 'unknown',\n    };\n\n    return typeToTool[rule.type] || 'unknown';\n  }\n}\n\n/**\n * Format detection result for display\n */\nexport function formatDetectionSummary(result: ToolDetectionResult): string {\n  const lines: string[] = [];\n\n  lines.push('Detected AI Tools:');\n  lines.push('');\n\n  for (const tool of result.tools) {\n    if (tool.detected) {\n      const parts: string[] = [];\n      if (tool.counts.rules > 0) parts.push(`${tool.counts.rules} rules`);\n      if (tool.counts.agents > 0) parts.push(`${tool.counts.agents} agents`);\n      if (tool.counts.skills > 0) parts.push(`${tool.counts.skills} skills`);\n\n      lines.push(`  ✓ ${tool.displayName} (${parts.join(', ')})`);\n    } else {\n      lines.push(`  ○ ${tool.displayName} (not found)`);\n    }\n  }\n\n  lines.push('');\n  lines.push(\n    `Total: ${result.summary.toolsFound} tools, ${result.summary.totalFiles} files`\n  );\n\n  return lines.join('\\n');\n}\n"
  },
  {
    "path": "src/services/reverseSync/types.ts",
    "content": "/**\n * Reverse Sync Types\n *\n * Types and interfaces for the Reverse Quick Sync feature\n * which imports from AI tool directories into .context/\n */\n\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type { ImportFormat, RuleFileInfo } from '../import/types';\n\n// ============================================================================\n// Merge Strategy\n// ============================================================================\n\nexport type MergeStrategy = 'skip' | 'overwrite' | 'merge' | 'rename';\n\n// ============================================================================\n// Tool Detection\n// ============================================================================\n\n/**\n * Presence summary for a single AI tool\n */\nexport interface ToolPresence {\n  /** Tool identifier (e.g., 'claude', 'cursor', 'github') */\n  id: string;\n  /** Display name (e.g., 'Claude Code', 'Cursor AI') */\n  displayName: string;\n  /** Whether this tool was detected in the repository */\n  detected: boolean;\n  /** Paths found for each content type */\n  paths: {\n    rules: string[];\n    agents: string[];\n    skills: string[];\n  };\n  /** Count of files found */\n  counts: {\n    rules: number;\n    agents: number;\n    skills: number;\n    total: number;\n  };\n}\n\n/**\n * Aggregated result from tool detection\n */\nexport interface ToolDetectionResult {\n  /** List of all tools with their detection status */\n  tools: ToolPresence[];\n  /** Overall summary */\n  summary: {\n    toolsFound: number;\n    totalRules: number;\n    totalAgents: number;\n    totalSkills: number;\n    totalFiles: number;\n  };\n}\n\n// ============================================================================\n// Skills\n// ============================================================================\n\n/**\n * Skill file metadata parsed from frontmatter\n */\nexport interface SkillMetadata {\n  name?: string;\n  description?: string;\n  phases?: string[];\n  triggers?: string[];\n  tags?: string[];\n}\n\n/**\n * Information about a detected skill file\n */\nexport interface SkillFileInfo {\n  /** Skill name/title */\n  name: string;\n  /** Slug identifier */\n  slug: string;\n  /** Absolute path to the skill file */\n  sourcePath: string;\n  /** Relative path from repo root */\n  relativePath: string;\n  /** Filename */\n  filename: string;\n  /** Source AI tool identifier */\n  sourceTool: string;\n  /** Parsed metadata from frontmatter */\n  metadata?: SkillMetadata;\n}\n\n/**\n * Result from skill detection\n */\nexport interface SkillDetectionResult {\n  files: SkillFileInfo[];\n  sources: string[];\n}\n\n// ============================================================================\n// Import Metadata (Frontmatter)\n// ============================================================================\n\n/**\n * Metadata added to imported files as YAML frontmatter\n */\nexport interface ImportMetadata {\n  /** Source AI tool identifier */\n  source_tool: string;\n  /** Original file path */\n  source_path: string;\n  /** ISO timestamp of import */\n  imported_at: string;\n  /** Version of ai-coders-context that performed the import */\n  ai_context_version: string;\n  /** Merge strategy used (if applicable) */\n  merge_strategy?: MergeStrategy;\n  /** Original filename (if renamed) */\n  original_filename?: string;\n}\n\n// ============================================================================\n// Service Options and Results\n// ============================================================================\n\n/**\n * Command line flags for reverse-sync\n */\nexport interface ReverseSyncCommandFlags {\n  dryRun?: boolean;\n  force?: boolean;\n  skipAgents?: boolean;\n  skipSkills?: boolean;\n  skipRules?: boolean;\n  mergeStrategy?: MergeStrategy;\n  verbose?: boolean;\n  format?: ImportFormat;\n  metadata?: boolean;\n  sourceTools?: string[];\n}\n\n/**\n * Resolved options for reverse sync operation\n */\nexport interface ReverseSyncOptions {\n  repoPath: string;\n  targetPath: string;\n  dryRun: boolean;\n  force: boolean;\n  skipAgents: boolean;\n  skipSkills: boolean;\n  skipRules: boolean;\n  mergeStrategy: MergeStrategy;\n  verbose: boolean;\n  format: ImportFormat;\n  addMetadata: boolean;\n  sourceTools?: string[];\n}\n\n/**\n * Import action taken for a file\n */\nexport type ImportAction = 'created' | 'overwritten' | 'merged' | 'renamed' | 'skipped' | 'failed';\n\n/**\n * Content type being imported\n */\nexport type ImportContentType = 'rule' | 'agent' | 'skill';\n\n/**\n * Record of a single imported file\n */\nexport interface ImportedFileRecord {\n  sourcePath: string;\n  targetPath: string;\n  type: ImportContentType;\n  action: ImportAction;\n  sourceTool?: string;\n  error?: string;\n}\n\n/**\n * Result from reverse sync operation\n */\nexport interface ReverseSyncResult {\n  rulesImported: number;\n  agentsImported: number;\n  skillsImported: number;\n  filesSkipped: number;\n  filesMerged: number;\n  filesRenamed: number;\n  filesFailed: number;\n  errors: Array<{ file: string; error: string }>;\n  importedFiles: ImportedFileRecord[];\n}\n\n/**\n * Options for importing skills\n */\nexport interface ImportSkillsOptions {\n  sourcePaths: string[];\n  targetPath: string;\n  force: boolean;\n  dryRun: boolean;\n  verbose: boolean;\n  autoDetect: boolean;\n  mergeStrategy: MergeStrategy;\n  addMetadata: boolean;\n}\n\n/**\n * Command flags for import-skills\n */\nexport interface ImportSkillsCommandFlags {\n  source?: string[];\n  target?: string;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n  autoDetect?: boolean;\n  mergeStrategy?: MergeStrategy;\n  metadata?: boolean;\n}\n\n// ============================================================================\n// Service Dependencies\n// ============================================================================\n\nexport interface ReverseSyncServiceDependencies {\n  ui: CLIInterface;\n  t: TranslateFn;\n  version: string;\n}\n\n// Re-export for convenience\nexport type { RuleFileInfo, ImportFormat };\n"
  },
  {
    "path": "src/services/semantic/codebaseAnalyzer.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { CodebaseAnalyzer } from './codebaseAnalyzer';\nimport type { SemanticContext, FileAnalysis, ExtractedSymbol } from './types';\n\n// Mock TreeSitterLayer\njest.mock('./treeSitter/treeSitterLayer', () => ({\n  TreeSitterLayer: jest.fn().mockImplementation(() => ({\n    analyzeFile: jest.fn().mockImplementation((filePath: string): FileAnalysis => {\n      const filename = path.basename(filePath, path.extname(filePath));\n      return {\n        filePath,\n        language: 'typescript',\n        symbols: [\n          {\n            name: `${filename}Class`,\n            kind: 'class',\n            exported: true,\n            location: { file: filePath, line: 1, column: 0 },\n          },\n          {\n            name: `${filename}Function`,\n            kind: 'function',\n            exported: true,\n            location: { file: filePath, line: 10, column: 0 },\n          },\n        ],\n        imports: [],\n        exports: [{ name: `${filename}Class`, isDefault: false, isReExport: false }],\n      };\n    }),\n    clearCache: jest.fn(),\n  })),\n}));\n\n// Mock LSPLayer\nconst mockGetTypeInfo = jest.fn().mockResolvedValue({\n  name: 'TestClass',\n  fullType: 'class TestClass { ... }',\n  documentation: 'A test class',\n});\nconst mockFindImplementations = jest.fn().mockResolvedValue([\n  { file: '/test/impl.ts', line: 5, column: 0 },\n]);\nconst mockFindReferences = jest.fn().mockResolvedValue([\n  { file: '/test/usage.ts', line: 10, column: 4 },\n  { file: '/test/other.ts', line: 20, column: 8 },\n]);\nconst mockShutdown = jest.fn().mockResolvedValue(undefined);\n\njest.mock('./lsp/lspLayer', () => ({\n  LSPLayer: jest.fn().mockImplementation(() => ({\n    getTypeInfo: mockGetTypeInfo,\n    findImplementations: mockFindImplementations,\n    findReferences: mockFindReferences,\n    shutdown: mockShutdown,\n  })),\n}));\n\n// Mock glob\njest.mock('glob', () => ({\n  glob: jest.fn().mockImplementation(async (pattern: string, options: { cwd: string }) => {\n    // Return mock files based on the pattern\n    if (pattern.includes('.ts')) {\n      return [\n        path.join(options.cwd, 'src', 'services', 'userService.ts'),\n        path.join(options.cwd, 'src', 'controllers', 'userController.ts'),\n        path.join(options.cwd, 'src', 'models', 'user.ts'),\n        path.join(options.cwd, 'src', 'utils', 'helper.ts'),\n      ];\n    }\n    return [];\n  }),\n}));\n\nfunction createTempOutput(prefix: string): Promise<string> {\n  return fs.mkdtemp(path.join(os.tmpdir(), prefix));\n}\n\ndescribe('CodebaseAnalyzer', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await createTempOutput('dotcontext-analyzer-');\n    jest.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    if (tempDir) {\n      await fs.remove(tempDir);\n    }\n  });\n\n  describe('constructor', () => {\n    it('should not create LSPLayer when useLSP is false', () => {\n      const { LSPLayer } = require('./lsp/lspLayer');\n\n      new CodebaseAnalyzer({ useLSP: false });\n\n      expect(LSPLayer).not.toHaveBeenCalled();\n    });\n\n    it('should create LSPLayer when useLSP is true', () => {\n      const { LSPLayer } = require('./lsp/lspLayer');\n\n      new CodebaseAnalyzer({ useLSP: true });\n\n      expect(LSPLayer).toHaveBeenCalled();\n    });\n\n    it('should use default options when none provided', () => {\n      const { LSPLayer } = require('./lsp/lspLayer');\n\n      // Default useLSP is false\n      new CodebaseAnalyzer();\n\n      expect(LSPLayer).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('analyze', () => {\n    it('should analyze files with Tree-sitter by default', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context.stats.totalFiles).toBeGreaterThan(0);\n      expect(context.symbols.classes.length).toBeGreaterThan(0);\n      expect(context.symbols.functions.length).toBeGreaterThan(0);\n    });\n\n    it('should build dependency graph from imports', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context.dependencies.graph).toBeDefined();\n      expect(context.dependencies.reverseGraph).toBeDefined();\n    });\n\n    it('should detect architecture layers', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n\n      const context = await analyzer.analyze(tempDir);\n\n      // Should detect layers based on directory patterns\n      const layerNames = context.architecture.layers.map(l => l.name);\n      expect(layerNames.some(name => ['Services', 'Controllers', 'Models', 'Utils'].includes(name))).toBe(true);\n    });\n\n    it('should detect patterns based on naming conventions', async () => {\n      // Override the mock to return pattern-matching class names\n      const { TreeSitterLayer } = require('./treeSitter/treeSitterLayer');\n      TreeSitterLayer.mockImplementationOnce(() => ({\n        analyzeFile: jest.fn().mockImplementation((filePath: string): FileAnalysis => ({\n          filePath,\n          language: 'typescript',\n          symbols: [\n            {\n              name: 'UserFactory',\n              kind: 'class',\n              exported: true,\n              location: { file: filePath, line: 1, column: 0 },\n            },\n            {\n              name: 'UserService',\n              kind: 'class',\n              exported: true,\n              location: { file: filePath, line: 20, column: 0 },\n            },\n            {\n              name: 'UserRepository',\n              kind: 'class',\n              exported: true,\n              location: { file: filePath, line: 40, column: 0 },\n            },\n          ],\n          imports: [],\n          exports: [],\n        })),\n        clearCache: jest.fn(),\n      }));\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      const context = await analyzer.analyze(tempDir);\n\n      const patternNames = context.architecture.patterns.map(p => p.name);\n      expect(patternNames).toContain('Factory');\n      expect(patternNames).toContain('Service Layer');\n      expect(patternNames).toContain('Repository');\n    });\n\n    it('should find entry points based on file names', async () => {\n      // Override glob to return an entry point file\n      const { glob } = require('glob');\n      glob.mockImplementationOnce(async (pattern: string, options: { cwd: string }) => {\n        if (pattern.includes('.ts')) {\n          return [\n            path.join(options.cwd, 'src', 'index.ts'),\n            path.join(options.cwd, 'src', 'main.ts'),\n            path.join(options.cwd, 'src', 'services', 'userService.ts'),\n          ];\n        }\n        return [];\n      });\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context.architecture.entryPoints.some(ep => ep.includes('index.ts') || ep.includes('main.ts'))).toBe(true);\n    });\n\n    it('should calculate analysis time', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context.stats.analysisTimeMs).toBeGreaterThanOrEqual(0);\n    });\n  });\n\n  describe('enhanceWithLSP', () => {\n    it('should add type info to exported symbols when LSP is enabled', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n\n      const context = await analyzer.analyze(tempDir);\n\n      // LSP enhancement should have been called for exported symbols\n      expect(mockGetTypeInfo).toHaveBeenCalled();\n    });\n\n    it('should find implementations for interfaces/classes', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n\n      await analyzer.analyze(tempDir);\n\n      expect(mockFindImplementations).toHaveBeenCalled();\n    });\n\n    it('should find references for exported symbols', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n\n      await analyzer.analyze(tempDir);\n\n      expect(mockFindReferences).toHaveBeenCalled();\n    });\n\n    it('should handle LSP errors gracefully', async () => {\n      // Make LSP methods throw errors\n      mockGetTypeInfo.mockRejectedValueOnce(new Error('LSP connection lost'));\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n\n      // Should not throw, but continue with analysis\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context).toBeDefined();\n      expect(context.stats.totalFiles).toBeGreaterThan(0);\n    });\n\n    it('should limit LSP calls to first 100 exported symbols', async () => {\n      // Create more than 100 symbols\n      const { TreeSitterLayer } = require('./treeSitter/treeSitterLayer');\n      const manySymbols: ExtractedSymbol[] = [];\n      for (let i = 0; i < 150; i++) {\n        manySymbols.push({\n          name: `Symbol${i}`,\n          kind: 'function',\n          exported: true,\n          location: { file: '/test/file.ts', line: i + 1, column: 0 },\n        });\n      }\n\n      TreeSitterLayer.mockImplementationOnce(() => ({\n        analyzeFile: jest.fn().mockImplementation((filePath: string): FileAnalysis => ({\n          filePath,\n          language: 'typescript',\n          symbols: manySymbols,\n          imports: [],\n          exports: [],\n        })),\n        clearCache: jest.fn(),\n      }));\n\n      mockGetTypeInfo.mockClear();\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n      await analyzer.analyze(tempDir);\n\n      // Should be limited to 100 * number of files analyzed\n      // But each file contributes, so we check it's reasonable\n      expect(mockGetTypeInfo.mock.calls.length).toBeLessThanOrEqual(400);\n    });\n  });\n\n  describe('shutdown', () => {\n    it('should call LSPLayer shutdown when LSP is enabled', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: true });\n\n      await analyzer.shutdown();\n\n      expect(mockShutdown).toHaveBeenCalled();\n    });\n\n    it('should not throw when LSP is disabled', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n\n      // Should not throw\n      await expect(analyzer.shutdown()).resolves.not.toThrow();\n    });\n  });\n\n  describe('getSummary', () => {\n    it('should generate markdown summary', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      const context = await analyzer.analyze(tempDir);\n\n      const summary = analyzer.getSummary(context, tempDir);\n\n      expect(summary).toContain('## Codebase Analysis Summary');\n      expect(summary).toContain('**Total Files**:');\n      expect(summary).toContain('**Total Symbols**:');\n      expect(summary).toContain('### Language Breakdown');\n    });\n\n    it('should include architecture layers in summary', async () => {\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      const context = await analyzer.analyze(tempDir);\n\n      const summary = analyzer.getSummary(context, tempDir);\n\n      expect(summary).toContain('### Architecture Layers');\n    });\n\n    it('should include detected patterns in summary', async () => {\n      // Override to get pattern-matching classes\n      const { TreeSitterLayer } = require('./treeSitter/treeSitterLayer');\n      TreeSitterLayer.mockImplementationOnce(() => ({\n        analyzeFile: jest.fn().mockImplementation((filePath: string): FileAnalysis => ({\n          filePath,\n          language: 'typescript',\n          symbols: [\n            {\n              name: 'UserFactory',\n              kind: 'class',\n              exported: true,\n              location: { file: filePath, line: 1, column: 0 },\n            },\n          ],\n          imports: [],\n          exports: [],\n        })),\n        clearCache: jest.fn(),\n      }));\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      const context = await analyzer.analyze(tempDir);\n\n      const summary = analyzer.getSummary(context, tempDir);\n\n      expect(summary).toContain('### Detected Patterns');\n    });\n  });\n\n  describe('clearCache', () => {\n    it('should call clearCache on TreeSitterLayer', () => {\n      const { TreeSitterLayer } = require('./treeSitter/treeSitterLayer');\n      const mockClearCache = jest.fn();\n      TreeSitterLayer.mockImplementationOnce(() => ({\n        analyzeFile: jest.fn().mockResolvedValue({\n          filePath: '/test/file.ts',\n          language: 'typescript',\n          symbols: [],\n          imports: [],\n          exports: [],\n        }),\n        clearCache: mockClearCache,\n      }));\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: false });\n      analyzer.clearCache();\n\n      expect(mockClearCache).toHaveBeenCalled();\n    });\n  });\n\n  describe('options handling', () => {\n    it('should respect maxFiles option', async () => {\n      const { glob } = require('glob');\n      const manyFiles = Array.from({ length: 100 }, (_, i) =>\n        path.join(tempDir, `file${i}.ts`)\n      );\n      glob.mockImplementationOnce(async () => manyFiles);\n\n      const analyzer = new CodebaseAnalyzer({ useLSP: false, maxFiles: 10 });\n      const context = await analyzer.analyze(tempDir);\n\n      expect(context.stats.totalFiles).toBeLessThanOrEqual(10);\n    });\n\n    it('should respect languages option', async () => {\n      const analyzer = new CodebaseAnalyzer({\n        useLSP: false,\n        languages: ['typescript', 'python'],\n      });\n\n      const context = await analyzer.analyze(tempDir);\n\n      // Should complete without error\n      expect(context).toBeDefined();\n    });\n\n    it('should respect exclude patterns', async () => {\n      const analyzer = new CodebaseAnalyzer({\n        useLSP: false,\n        exclude: ['node_modules', 'dist', 'build'],\n      });\n\n      const context = await analyzer.analyze(tempDir);\n\n      // Should complete without error\n      expect(context).toBeDefined();\n    });\n\n    it('should respect include patterns', async () => {\n      const { glob } = require('glob');\n      glob.mockImplementationOnce(async (pattern: string, options: { cwd: string }) => {\n        return [\n          path.join(options.cwd, 'src', 'services', 'userService.ts'),\n          path.join(options.cwd, 'src', 'controllers', 'userController.ts'),\n          path.join(options.cwd, 'tests', 'testFile.ts'),\n        ];\n      });\n\n      const analyzer = new CodebaseAnalyzer({\n        useLSP: false,\n        include: ['src'],\n      });\n\n      const context = await analyzer.analyze(tempDir);\n\n      // Files should be filtered to only include 'src' paths\n      // Due to mock implementation, we check it doesn't error\n      expect(context).toBeDefined();\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/semantic/codebaseAnalyzer.ts",
    "content": "/**\n * CodebaseAnalyzer - Main orchestrator for semantic code analysis\n *\n * Combines Tree-sitter for fast syntactic analysis with optional LSP\n * for deeper semantic understanding.\n */\n\nimport { glob } from 'glob';\nimport * as path from 'path';\nimport * as fs from 'fs/promises';\nimport { TreeSitterLayer } from './treeSitter/treeSitterLayer';\nimport { LSPLayer } from './lsp/lspLayer';\nimport {\n  SemanticContext,\n  FileAnalysis,\n  ExtractedSymbol,\n  ArchitectureLayer,\n  DetectedPattern,\n  AnalyzerOptions,\n  DependencyInfo,\n  DEFAULT_EXCLUDE_PATTERNS,\n  LANGUAGE_EXTENSIONS,\n  FunctionalPattern,\n  FunctionalPatternType,\n  DetectedFunctionalPatterns,\n  PatternIndicator,\n  FlowNode,\n  FlowEdge,\n  ExecutionFlow,\n} from './types';\n\nconst DEFAULT_OPTIONS: Required<AnalyzerOptions> = {\n  useLSP: false,\n  languages: ['typescript', 'javascript', 'python', 'go'],\n  exclude: DEFAULT_EXCLUDE_PATTERNS,\n  include: [],\n  maxFiles: 5000,\n  cacheEnabled: true,\n};\n\nexport class CodebaseAnalyzer {\n  private treeSitter: TreeSitterLayer;\n  private lspLayer?: LSPLayer;\n  private options: Required<AnalyzerOptions>;\n\n  constructor(options: AnalyzerOptions = {}) {\n    this.treeSitter = new TreeSitterLayer();\n    this.options = { ...DEFAULT_OPTIONS, ...options };\n\n    // Create LSPLayer if LSP mode is enabled\n    if (this.options.useLSP) {\n      this.lspLayer = new LSPLayer();\n    }\n  }\n\n  async analyze(projectPath: string): Promise<SemanticContext> {\n    const startTime = Date.now();\n\n    // 1. Find all code files\n    const files = await this.findCodeFiles(projectPath);\n\n    // 2. Analyze with Tree-sitter\n    const fileAnalyses = await this.analyzeFiles(files);\n\n    // 3. Build base context\n    const context = this.buildBaseContext(fileAnalyses, projectPath);\n\n    // 4. Enhance with LSP if enabled (adds type info, references)\n    if (this.lspLayer) {\n      await this.enhanceWithLSP(context, projectPath);\n    }\n\n    // 5. Detect architecture and patterns\n    context.architecture = this.detectArchitecture(fileAnalyses, projectPath);\n\n    // 6. Calculate stats\n    context.stats.analysisTimeMs = Date.now() - startTime;\n\n    return context;\n  }\n\n  private async findCodeFiles(projectPath: string): Promise<string[]> {\n    const extensions = Object.keys(LANGUAGE_EXTENSIONS);\n    const patterns = extensions.map((ext) => `**/*${ext}`);\n\n    const ignorePatterns = this.options.exclude.map((p) => `**/${p}/**`);\n\n    const allFiles: string[] = [];\n\n    for (const pattern of patterns) {\n      try {\n        const matches = await glob(pattern, {\n          cwd: projectPath,\n          ignore: ignorePatterns,\n          absolute: true,\n          nodir: true,\n        });\n        allFiles.push(...matches);\n      } catch {\n        // Ignore glob errors for individual patterns\n      }\n    }\n\n    // Apply include filter if specified\n    let filteredFiles = allFiles;\n    if (this.options.include.length > 0) {\n      filteredFiles = allFiles.filter((file) =>\n        this.options.include.some((pattern) => file.includes(pattern))\n      );\n    }\n\n    // Limit number of files\n    return filteredFiles.slice(0, this.options.maxFiles);\n  }\n\n  private async analyzeFiles(files: string[]): Promise<Map<string, FileAnalysis>> {\n    const analyses = new Map<string, FileAnalysis>();\n    const batchSize = 50;\n\n    for (let i = 0; i < files.length; i += batchSize) {\n      const batch = files.slice(i, i + batchSize);\n      const results = await Promise.all(\n        batch.map((file) => this.treeSitter.analyzeFile(file))\n      );\n\n      for (const analysis of results) {\n        analyses.set(analysis.filePath, analysis);\n      }\n    }\n\n    return analyses;\n  }\n\n  private buildBaseContext(\n    analyses: Map<string, FileAnalysis>,\n    projectPath: string\n  ): SemanticContext {\n    const symbols = {\n      classes: [] as ExtractedSymbol[],\n      interfaces: [] as ExtractedSymbol[],\n      functions: [] as ExtractedSymbol[],\n      types: [] as ExtractedSymbol[],\n      enums: [] as ExtractedSymbol[],\n    };\n\n    const dependencyGraph = new Map<string, string[]>();\n    const reverseDependencyGraph = new Map<string, string[]>();\n    const languageCount: Record<string, number> = {};\n\n    for (const [file, analysis] of analyses) {\n      // Count by language\n      const ext = path.extname(file);\n      languageCount[ext] = (languageCount[ext] || 0) + 1;\n\n      // Categorize symbols\n      for (const symbol of analysis.symbols) {\n        switch (symbol.kind) {\n          case 'class':\n            symbols.classes.push(symbol);\n            break;\n          case 'interface':\n            symbols.interfaces.push(symbol);\n            break;\n          case 'function':\n            symbols.functions.push(symbol);\n            break;\n          case 'type':\n            symbols.types.push(symbol);\n            break;\n          case 'enum':\n            symbols.enums.push(symbol);\n            break;\n        }\n      }\n\n      // Build dependency graph\n      const importedFiles = analysis.imports\n        .map((imp) => this.resolveImportPath(file, imp.source, projectPath))\n        .filter((f): f is string => f !== null);\n\n      dependencyGraph.set(file, importedFiles);\n\n      for (const importedFile of importedFiles) {\n        if (!reverseDependencyGraph.has(importedFile)) {\n          reverseDependencyGraph.set(importedFile, []);\n        }\n        reverseDependencyGraph.get(importedFile)!.push(file);\n      }\n    }\n\n    return {\n      symbols,\n      dependencies: {\n        graph: dependencyGraph,\n        reverseGraph: reverseDependencyGraph,\n      },\n      architecture: {\n        layers: [],\n        patterns: [],\n        entryPoints: [],\n        publicAPI: [],\n      },\n      stats: {\n        totalFiles: analyses.size,\n        totalSymbols: Object.values(symbols).flat().length,\n        languageBreakdown: languageCount,\n        analysisTimeMs: 0,\n      },\n    };\n  }\n\n  private resolveImportPath(\n    fromFile: string,\n    importSource: string,\n    projectPath: string\n  ): string | null {\n    // Skip external packages\n    if (!importSource.startsWith('.') && !importSource.startsWith('/')) {\n      return null;\n    }\n\n    const dir = path.dirname(fromFile);\n    const resolved = path.resolve(dir, importSource);\n\n    // Try common extensions\n    const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.js', '.py', '.go'];\n\n    for (const ext of extensions) {\n      const withExt = resolved + ext;\n      if (this.fileExistsSync(withExt)) {\n        return withExt;\n      }\n    }\n\n    // Try without extension (might already have it)\n    if (this.fileExistsSync(resolved)) {\n      return resolved;\n    }\n\n    return null;\n  }\n\n  private fileExistsSync(filePath: string): boolean {\n    try {\n      require('fs').accessSync(filePath);\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  private detectArchitecture(\n    analyses: Map<string, FileAnalysis>,\n    projectPath: string\n  ): SemanticContext['architecture'] {\n    const layers = this.detectLayers(analyses, projectPath);\n    const patterns = this.detectPatterns(analyses);\n    const entryPoints = this.findEntryPoints(analyses, projectPath);\n    const publicAPI = this.findPublicAPI(analyses);\n\n    // Calculate layer dependencies\n    this.calculateLayerDependencies(layers, analyses, projectPath);\n\n    return { layers, patterns, entryPoints, publicAPI };\n  }\n\n  private detectLayers(\n    analyses: Map<string, FileAnalysis>,\n    projectPath: string\n  ): ArchitectureLayer[] {\n    const layerHeuristics = [\n      {\n        name: 'Services',\n        patterns: [/services?/i, /use-?cases?/i, /application/i],\n        description: 'Business logic and orchestration',\n      },\n      {\n        name: 'Controllers',\n        patterns: [/controllers?/i, /handlers?/i, /routes?/i, /api/i],\n        description: 'Request handling and routing',\n      },\n      {\n        name: 'Models',\n        patterns: [/models?/i, /entities/i, /domain/i, /schemas?/i],\n        description: 'Data structures and domain objects',\n      },\n      {\n        name: 'Repositories',\n        patterns: [/repositor/i, /data/i, /database/i, /persistence/i],\n        description: 'Data access and persistence',\n      },\n      {\n        name: 'Utils',\n        patterns: [/utils?/i, /helpers?/i, /lib/i, /common/i, /shared/i],\n        description: 'Shared utilities and helpers',\n      },\n      {\n        name: 'Generators',\n        patterns: [/generators?/i, /builders?/i, /factories?/i],\n        description: 'Content and object generation',\n      },\n      {\n        name: 'Components',\n        patterns: [/components?/i, /views?/i, /pages?/i, /screens?/i],\n        description: 'UI components and views',\n      },\n      {\n        name: 'Config',\n        patterns: [/config/i, /settings?/i, /constants?/i],\n        description: 'Configuration and constants',\n      },\n    ];\n\n    const layers: ArchitectureLayer[] = [];\n    const filesByLayer = new Map<string, string[]>();\n\n    for (const [file] of analyses) {\n      const relativePath = path.relative(projectPath, file);\n\n      for (const heuristic of layerHeuristics) {\n        if (heuristic.patterns.some((p) => p.test(relativePath))) {\n          if (!filesByLayer.has(heuristic.name)) {\n            filesByLayer.set(heuristic.name, []);\n          }\n          filesByLayer.get(heuristic.name)!.push(file);\n          break;\n        }\n      }\n    }\n\n    for (const [layerName, files] of filesByLayer) {\n      const heuristic = layerHeuristics.find((h) => h.name === layerName)!;\n      const layerSymbols: ExtractedSymbol[] = [];\n      const directories = new Set<string>();\n\n      for (const file of files) {\n        const analysis = analyses.get(file);\n        if (analysis) {\n          layerSymbols.push(...analysis.symbols);\n          directories.add(path.dirname(path.relative(projectPath, file)));\n        }\n      }\n\n      if (layerSymbols.length > 0) {\n        layers.push({\n          name: layerName,\n          description: heuristic.description,\n          directories: [...directories],\n          symbols: layerSymbols,\n          dependsOn: [],\n        });\n      }\n    }\n\n    return layers;\n  }\n\n  private detectPatterns(analyses: Map<string, FileAnalysis>): DetectedPattern[] {\n    const patterns: DetectedPattern[] = [];\n    const allSymbols = [...analyses.values()].flatMap((a) => a.symbols);\n\n    // Factory Pattern\n    const factories = allSymbols.filter(\n      (s) => /Factory$/i.test(s.name) && s.kind === 'class'\n    );\n    if (factories.length > 0) {\n      patterns.push({\n        name: 'Factory',\n        confidence: 0.9,\n        locations: factories.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Creates instances of related objects without specifying concrete classes',\n      });\n    }\n\n    // Singleton Pattern\n    const singletons = allSymbols.filter(\n      (s) => s.kind === 'class' && /Singleton|Instance/i.test(s.name)\n    );\n    if (singletons.length > 0) {\n      patterns.push({\n        name: 'Singleton',\n        confidence: 0.7,\n        locations: singletons.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Ensures a class has only one instance',\n      });\n    }\n\n    // Repository Pattern\n    const repositories = allSymbols.filter(\n      (s) => /Repository$/i.test(s.name) && (s.kind === 'class' || s.kind === 'interface')\n    );\n    if (repositories.length > 0) {\n      patterns.push({\n        name: 'Repository',\n        confidence: 0.9,\n        locations: repositories.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Abstracts data access logic',\n      });\n    }\n\n    // Service Layer Pattern\n    const services = allSymbols.filter(\n      (s) => /Service$/i.test(s.name) && s.kind === 'class'\n    );\n    if (services.length > 0) {\n      patterns.push({\n        name: 'Service Layer',\n        confidence: 0.85,\n        locations: services.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Encapsulates business logic in service classes',\n      });\n    }\n\n    // Controller Pattern\n    const controllers = allSymbols.filter(\n      (s) => /Controller$/i.test(s.name) && s.kind === 'class'\n    );\n    if (controllers.length > 0) {\n      patterns.push({\n        name: 'Controller',\n        confidence: 0.9,\n        locations: controllers.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Handles incoming requests and returns responses',\n      });\n    }\n\n    // Builder Pattern\n    const builders = allSymbols.filter(\n      (s) => /Builder$/i.test(s.name) && s.kind === 'class'\n    );\n    if (builders.length > 0) {\n      patterns.push({\n        name: 'Builder',\n        confidence: 0.85,\n        locations: builders.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Separates object construction from its representation',\n      });\n    }\n\n    // Observer Pattern (event emitters)\n    const observers = allSymbols.filter(\n      (s) =>\n        (s.kind === 'class' || s.kind === 'interface') &&\n        /Observer|Listener|Emitter|Handler$/i.test(s.name)\n    );\n    if (observers.length > 0) {\n      patterns.push({\n        name: 'Observer',\n        confidence: 0.75,\n        locations: observers.map((s) => ({ file: s.location.file, symbol: s.name })),\n        description: 'Defines a subscription mechanism to notify multiple objects',\n      });\n    }\n\n    return patterns;\n  }\n\n  private findEntryPoints(\n    analyses: Map<string, FileAnalysis>,\n    projectPath: string\n  ): string[] {\n    const entryPoints: string[] = [];\n    const entryPatterns = [\n      /^(index|main|app|server|cli)\\.(ts|js|tsx|jsx)$/,\n      /src\\/(index|main|app)\\.(ts|js)$/,\n      /^bin\\//,\n    ];\n\n    for (const [file] of analyses) {\n      const relativePath = path.relative(projectPath, file);\n      const basename = path.basename(file);\n\n      if (entryPatterns.some((p) => p.test(basename) || p.test(relativePath))) {\n        entryPoints.push(relativePath);\n      }\n    }\n\n    return entryPoints;\n  }\n\n  private findPublicAPI(analyses: Map<string, FileAnalysis>): ExtractedSymbol[] {\n    const publicSymbols: ExtractedSymbol[] = [];\n\n    for (const [, analysis] of analyses) {\n      for (const symbol of analysis.symbols) {\n        if (\n          symbol.exported &&\n          (symbol.kind === 'class' ||\n            symbol.kind === 'interface' ||\n            symbol.kind === 'function' ||\n            symbol.kind === 'type')\n        ) {\n          publicSymbols.push(symbol);\n        }\n      }\n    }\n\n    // Sort by name for consistency\n    return publicSymbols.sort((a, b) => a.name.localeCompare(b.name));\n  }\n\n  private calculateLayerDependencies(\n    layers: ArchitectureLayer[],\n    analyses: Map<string, FileAnalysis>,\n    projectPath: string\n  ): void {\n    const fileToLayer = new Map<string, string>();\n\n    for (const layer of layers) {\n      for (const dir of layer.directories) {\n        for (const [file] of analyses) {\n          const relFile = path.relative(projectPath, file);\n          if (relFile.startsWith(dir)) {\n            fileToLayer.set(file, layer.name);\n          }\n        }\n      }\n    }\n\n    for (const layer of layers) {\n      const dependsOn = new Set<string>();\n\n      for (const symbol of layer.symbols) {\n        const file = symbol.location.file;\n        const analysis = analyses.get(file);\n        if (!analysis) continue;\n\n        for (const imp of analysis.imports) {\n          const resolved = this.resolveImportPath(file, imp.source, projectPath);\n          if (resolved) {\n            const depLayer = fileToLayer.get(resolved);\n            if (depLayer && depLayer !== layer.name) {\n              dependsOn.add(depLayer);\n            }\n          }\n        }\n      }\n\n      layer.dependsOn = [...dependsOn];\n    }\n  }\n\n  /**\n   * Get a summary suitable for documentation generation\n   */\n  getSummary(context: SemanticContext, projectPath: string): string {\n    const { symbols, architecture, stats } = context;\n\n    const lines: string[] = [\n      `## Codebase Analysis Summary\\n`,\n      `**Total Files**: ${stats.totalFiles}`,\n      `**Total Symbols**: ${stats.totalSymbols}`,\n      `**Analysis Time**: ${stats.analysisTimeMs}ms\\n`,\n      `### Language Breakdown\\n`,\n    ];\n\n    for (const [ext, count] of Object.entries(stats.languageBreakdown)) {\n      lines.push(`- ${ext}: ${count} files`);\n    }\n\n    if (architecture.layers.length > 0) {\n      lines.push(`\\n### Architecture Layers\\n`);\n      for (const layer of architecture.layers) {\n        const symbolCount = layer.symbols.length;\n        const deps = layer.dependsOn.length > 0 ? ` → ${layer.dependsOn.join(', ')}` : '';\n        lines.push(`- **${layer.name}** (${symbolCount} symbols)${deps}`);\n        lines.push(`  - ${layer.description}`);\n      }\n    }\n\n    if (architecture.patterns.length > 0) {\n      lines.push(`\\n### Detected Patterns\\n`);\n      for (const pattern of architecture.patterns) {\n        const confidence = Math.round(pattern.confidence * 100);\n        lines.push(\n          `- **${pattern.name}** (${confidence}% confidence): ${pattern.locations.length} occurrences`\n        );\n      }\n    }\n\n    if (architecture.entryPoints.length > 0) {\n      lines.push(`\\n### Entry Points\\n`);\n      for (const ep of architecture.entryPoints) {\n        lines.push(`- \\`${ep}\\``);\n      }\n    }\n\n    return lines.join('\\n');\n  }\n\n  clearCache(): void {\n    this.treeSitter.clearCache();\n  }\n\n  /**\n   * Shutdown LSP servers gracefully\n   */\n  async shutdown(): Promise<void> {\n    if (this.lspLayer) {\n      await this.lspLayer.shutdown();\n    }\n  }\n\n  /**\n   * Enhance Tree-sitter analysis with LSP semantic information\n   * Adds type info, references, and implementations to key symbols\n   */\n  private async enhanceWithLSP(\n    context: SemanticContext,\n    projectPath: string\n  ): Promise<void> {\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n      ...context.symbols.types,\n    ];\n\n    // Prioritize symbols to enhance (exported and important ones first)\n    const prioritizedSymbols = allSymbols\n      .filter((s) => s.exported)\n      .slice(0, 100); // Limit to avoid excessive LSP calls\n\n    for (const symbol of prioritizedSymbols) {\n      try {\n        // Get type information via LSP hover\n        const typeInfo = await this.lspLayer!.getTypeInfo(\n          symbol.location.file,\n          symbol.location.line,\n          symbol.location.column || 0,\n          projectPath\n        );\n\n        if (typeInfo) {\n          symbol.typeInfo = typeInfo;\n        }\n\n        // For interfaces and classes, find implementations\n        if (symbol.kind === 'interface' || symbol.kind === 'class') {\n          const implementations = await this.lspLayer!.findImplementations(\n            symbol.location.file,\n            symbol.location.line,\n            symbol.location.column || 0,\n            projectPath\n          );\n\n          if (implementations.length > 0) {\n            symbol.implementations = implementations;\n          }\n        }\n\n        // Find references for exported symbols\n        const references = await this.lspLayer!.findReferences(\n          symbol.location.file,\n          symbol.location.line,\n          symbol.location.column || 0,\n          projectPath\n        );\n\n        if (references.length > 0) {\n          symbol.references = references;\n        }\n      } catch {\n        // LSP errors are non-fatal, continue with other symbols\n      }\n    }\n  }\n\n  /**\n   * Detect functional patterns in the codebase\n   * These patterns indicate functional capabilities like auth, database, API, etc.\n   */\n  async detectFunctionalPatterns(projectPath: string): Promise<DetectedFunctionalPatterns> {\n    const files = await this.findCodeFiles(projectPath);\n    const analyses = await this.analyzeFiles(files);\n\n    const patterns: FunctionalPattern[] = [];\n    const allSymbols = [...analyses.values()].flatMap((a) => a.symbols);\n    const allImports = [...analyses.values()].flatMap((a) =>\n      a.imports.map((imp) => ({ ...imp, file: a.filePath }))\n    );\n\n    // Authentication pattern detection\n    const authPattern = this.detectAuthPattern(allSymbols, allImports, files);\n    if (authPattern) patterns.push(authPattern);\n\n    // Database pattern detection\n    const dbPattern = this.detectDatabasePattern(allSymbols, allImports, files);\n    if (dbPattern) patterns.push(dbPattern);\n\n    // API pattern detection\n    const apiPattern = this.detectApiPattern(allSymbols, allImports, files);\n    if (apiPattern) patterns.push(apiPattern);\n\n    // Cache pattern detection\n    const cachePattern = this.detectCachePattern(allSymbols, allImports, files);\n    if (cachePattern) patterns.push(cachePattern);\n\n    // Queue/messaging pattern detection\n    const queuePattern = this.detectQueuePattern(allSymbols, allImports, files);\n    if (queuePattern) patterns.push(queuePattern);\n\n    // WebSocket pattern detection\n    const wsPattern = this.detectWebSocketPattern(allSymbols, allImports, files);\n    if (wsPattern) patterns.push(wsPattern);\n\n    // Logging pattern detection\n    const loggingPattern = this.detectLoggingPattern(allSymbols, allImports, files);\n    if (loggingPattern) patterns.push(loggingPattern);\n\n    // Validation pattern detection\n    const validationPattern = this.detectValidationPattern(allSymbols, allImports, files);\n    if (validationPattern) patterns.push(validationPattern);\n\n    // Error handling pattern detection\n    const errorPattern = this.detectErrorHandlingPattern(allSymbols, allImports, files);\n    if (errorPattern) patterns.push(errorPattern);\n\n    // Testing pattern detection\n    const testingPattern = this.detectTestingPattern(allSymbols, allImports, files);\n    if (testingPattern) patterns.push(testingPattern);\n\n    return {\n      hasAuthPattern: patterns.some((p) => p.type === 'auth'),\n      hasDatabasePattern: patterns.some((p) => p.type === 'database'),\n      hasApiPattern: patterns.some((p) => p.type === 'api'),\n      hasCachePattern: patterns.some((p) => p.type === 'cache'),\n      hasQueuePattern: patterns.some((p) => p.type === 'queue'),\n      hasWebSocketPattern: patterns.some((p) => p.type === 'websocket'),\n      hasLoggingPattern: patterns.some((p) => p.type === 'logging'),\n      hasValidationPattern: patterns.some((p) => p.type === 'validation'),\n      hasErrorHandlingPattern: patterns.some((p) => p.type === 'error-handling'),\n      hasTestingPattern: patterns.some((p) => p.type === 'testing'),\n      patterns,\n    };\n  }\n\n  private detectAuthPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for auth-related symbols\n    const authSymbols = symbols.filter((s) =>\n      /^(auth|login|logout|verify|jwt|token|session|password|credential|oauth)/i.test(s.name)\n    );\n    for (const sym of authSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Auth-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for auth-related imports\n    const authImports = imports.filter((imp) =>\n      /^(jsonwebtoken|passport|bcrypt|argon2|@auth|next-auth|express-session|cookie-session)/i.test(\n        imp.source\n      )\n    );\n    for (const imp of authImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Auth library import: ${imp.source}`,\n      });\n    }\n\n    // Check for auth-related files\n    const authFiles = files.filter((f) =>\n      /\\/(auth|login|session|middleware)[\\./]/i.test(f)\n    );\n    for (const f of authFiles) {\n      indicators.push({\n        file: f,\n        reason: 'Auth-related file path',\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'auth',\n      confidence: Math.min(1, indicators.length * 0.15),\n      indicators: indicators.slice(0, 10),\n      description: 'Authentication and authorization functionality',\n    };\n  }\n\n  private detectDatabasePattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for database-related symbols\n    const dbSymbols = symbols.filter((s) =>\n      /^(repository|model|entity|schema|migration|query|database|db|prisma|sequelize|mongoose)/i.test(\n        s.name\n      )\n    );\n    for (const sym of dbSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Database-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for database-related imports\n    const dbImports = imports.filter((imp) =>\n      /^(prisma|@prisma|sequelize|mongoose|typeorm|knex|pg|mysql|mysql2|sqlite3|better-sqlite3|drizzle-orm|@supabase)/i.test(\n        imp.source\n      )\n    );\n    for (const imp of dbImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Database library import: ${imp.source}`,\n      });\n    }\n\n    // Check for database-related files\n    const dbFiles = files.filter((f) =>\n      /\\/(models?|repositories|entities|migrations?|schemas?|database)[\\./]/i.test(f)\n    );\n    for (const f of dbFiles) {\n      indicators.push({\n        file: f,\n        reason: 'Database-related file path',\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'database',\n      confidence: Math.min(1, indicators.length * 0.15),\n      indicators: indicators.slice(0, 10),\n      description: 'Database access and ORM functionality',\n    };\n  }\n\n  private detectApiPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for API-related symbols\n    const apiSymbols = symbols.filter((s) =>\n      /^(controller|handler|router|route|endpoint|api|rest|graphql|resolver)/i.test(s.name)\n    );\n    for (const sym of apiSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `API-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for API framework imports\n    const apiImports = imports.filter((imp) =>\n      /^(express|fastify|koa|hapi|@nestjs|next|nuxt|graphql|apollo|@apollo|trpc|@trpc)/i.test(\n        imp.source\n      )\n    );\n    for (const imp of apiImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `API framework import: ${imp.source}`,\n      });\n    }\n\n    // Check for API-related files\n    const apiFiles = files.filter((f) =>\n      /\\/(routes?|controllers?|handlers?|api|endpoints?|resolvers?)[\\./]/i.test(f)\n    );\n    for (const f of apiFiles) {\n      indicators.push({\n        file: f,\n        reason: 'API-related file path',\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'api',\n      confidence: Math.min(1, indicators.length * 0.15),\n      indicators: indicators.slice(0, 10),\n      description: 'API endpoints and routing',\n    };\n  }\n\n  private detectCachePattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    _files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for cache-related symbols\n    const cacheSymbols = symbols.filter((s) =>\n      /^(cache|redis|memcache|lru|ttl)/i.test(s.name)\n    );\n    for (const sym of cacheSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Cache-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for cache-related imports\n    const cacheImports = imports.filter((imp) =>\n      /^(redis|ioredis|memcached|lru-cache|node-cache|cache-manager)/i.test(imp.source)\n    );\n    for (const imp of cacheImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Cache library import: ${imp.source}`,\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'cache',\n      confidence: Math.min(1, indicators.length * 0.2),\n      indicators: indicators.slice(0, 10),\n      description: 'Caching and data memoization',\n    };\n  }\n\n  private detectQueuePattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    _files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for queue-related symbols\n    const queueSymbols = symbols.filter((s) =>\n      /^(queue|worker|job|task|consumer|producer|message|event)/i.test(s.name)\n    );\n    for (const sym of queueSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Queue-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for queue-related imports\n    const queueImports = imports.filter((imp) =>\n      /^(bull|bullmq|bee-queue|agenda|amqplib|rabbitmq|kafka|kafkajs|sqs)/i.test(imp.source)\n    );\n    for (const imp of queueImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Queue library import: ${imp.source}`,\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'queue',\n      confidence: Math.min(1, indicators.length * 0.2),\n      indicators: indicators.slice(0, 10),\n      description: 'Message queues and background jobs',\n    };\n  }\n\n  private detectWebSocketPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    _files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for WebSocket-related symbols\n    const wsSymbols = symbols.filter((s) =>\n      /^(websocket|socket|ws|realtime|broadcast|subscribe)/i.test(s.name)\n    );\n    for (const sym of wsSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `WebSocket-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for WebSocket-related imports\n    const wsImports = imports.filter((imp) =>\n      /^(ws|socket\\.io|@socket\\.io|sockjs|pusher|ably)/i.test(imp.source)\n    );\n    for (const imp of wsImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `WebSocket library import: ${imp.source}`,\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'websocket',\n      confidence: Math.min(1, indicators.length * 0.2),\n      indicators: indicators.slice(0, 10),\n      description: 'Real-time WebSocket communication',\n    };\n  }\n\n  private detectLoggingPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    _files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for logging-related symbols\n    const logSymbols = symbols.filter((s) =>\n      /^(logger|log|logging|telemetry|metrics)/i.test(s.name)\n    );\n    for (const sym of logSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Logging-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for logging-related imports\n    const logImports = imports.filter((imp) =>\n      /^(winston|pino|bunyan|log4js|morgan|@sentry|newrelic|datadog)/i.test(imp.source)\n    );\n    for (const imp of logImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Logging library import: ${imp.source}`,\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'logging',\n      confidence: Math.min(1, indicators.length * 0.2),\n      indicators: indicators.slice(0, 10),\n      description: 'Logging and observability',\n    };\n  }\n\n  private detectValidationPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    _files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for validation-related symbols\n    const validSymbols = symbols.filter((s) =>\n      /^(valid|schema|sanitize|parse|check|assert)/i.test(s.name)\n    );\n    for (const sym of validSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Validation-related symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for validation-related imports\n    const validImports = imports.filter((imp) =>\n      /^(zod|yup|joi|ajv|class-validator|validator|io-ts|valibot)/i.test(imp.source)\n    );\n    for (const imp of validImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Validation library import: ${imp.source}`,\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'validation',\n      confidence: Math.min(1, indicators.length * 0.2),\n      indicators: indicators.slice(0, 10),\n      description: 'Input validation and schema enforcement',\n    };\n  }\n\n  private detectErrorHandlingPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for error-related symbols\n    const errorSymbols = symbols.filter((s) =>\n      /^(error|exception|handler|catch|fallback)/i.test(s.name)\n    );\n    for (const sym of errorSymbols) {\n      indicators.push({\n        file: sym.location.file,\n        symbol: sym.name,\n        line: sym.location.line,\n        reason: `Error handling symbol: ${sym.name}`,\n      });\n    }\n\n    // Check for error-related files\n    const errorFiles = files.filter((f) =>\n      /\\/(errors?|exceptions?)[\\./]/i.test(f)\n    );\n    for (const f of errorFiles) {\n      indicators.push({\n        file: f,\n        reason: 'Error handling file path',\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'error-handling',\n      confidence: Math.min(1, indicators.length * 0.15),\n      indicators: indicators.slice(0, 10),\n      description: 'Error handling and exception management',\n    };\n  }\n\n  private detectTestingPattern(\n    symbols: ExtractedSymbol[],\n    imports: Array<{ source: string; specifiers: string[]; file: string }>,\n    files: string[]\n  ): FunctionalPattern | null {\n    const indicators: PatternIndicator[] = [];\n\n    // Check for test-related imports\n    const testImports = imports.filter((imp) =>\n      /^(jest|vitest|mocha|chai|@testing-library|cypress|playwright|supertest)/i.test(imp.source)\n    );\n    for (const imp of testImports) {\n      indicators.push({\n        file: imp.file,\n        reason: `Test library import: ${imp.source}`,\n      });\n    }\n\n    // Check for test files\n    const testFiles = files.filter((f) =>\n      /\\.(test|spec)\\.(ts|tsx|js|jsx)$/.test(f) || /\\/__tests__\\//.test(f)\n    );\n    for (const f of testFiles.slice(0, 5)) {\n      indicators.push({\n        file: f,\n        reason: 'Test file',\n      });\n    }\n\n    if (indicators.length === 0) return null;\n\n    return {\n      type: 'testing',\n      confidence: Math.min(1, indicators.length * 0.15),\n      indicators: indicators.slice(0, 10),\n      description: 'Testing infrastructure and test files',\n    };\n  }\n\n  /**\n   * Trace execution flow from an entry point\n   * Uses tree-sitter to extract call expressions and build flow graph\n   */\n  async traceFlow(\n    projectPath: string,\n    entryFile: string,\n    entryFunction?: string\n  ): Promise<ExecutionFlow> {\n    const absoluteEntryFile = path.isAbsolute(entryFile)\n      ? entryFile\n      : path.join(projectPath, entryFile);\n\n    const nodes: FlowNode[] = [];\n    const edges: FlowEdge[] = [];\n    const visited = new Set<string>();\n\n    // Analyze entry file\n    const analysis = await this.treeSitter.analyzeFile(absoluteEntryFile);\n\n    // Find entry point\n    let entrySymbol = analysis.symbols[0];\n    if (entryFunction) {\n      const found = analysis.symbols.find(\n        (s) => s.name === entryFunction && (s.kind === 'function' || s.kind === 'method')\n      );\n      if (found) entrySymbol = found;\n    }\n\n    if (!entrySymbol) {\n      return {\n        entryPoint: {\n          file: absoluteEntryFile,\n          symbol: 'unknown',\n          line: 1,\n          type: 'entry',\n        },\n        nodes: [],\n        edges: [],\n        mermaidDiagram: 'flowchart TD\\n    A[Entry] --> B[No symbols found]',\n      };\n    }\n\n    const entryNode: FlowNode = {\n      file: absoluteEntryFile,\n      symbol: entrySymbol.name,\n      line: entrySymbol.location.line,\n      type: 'entry',\n    };\n    nodes.push(entryNode);\n\n    // Build flow graph using file content analysis\n    await this.traceCallsFromFile(\n      absoluteEntryFile,\n      entryNode,\n      nodes,\n      edges,\n      visited,\n      projectPath,\n      3 // Max depth\n    );\n\n    // Generate Mermaid diagram\n    const mermaidDiagram = this.generateMermaidDiagram(nodes, edges);\n\n    return {\n      entryPoint: entryNode,\n      nodes,\n      edges,\n      mermaidDiagram,\n    };\n  }\n\n  private async traceCallsFromFile(\n    filePath: string,\n    currentNode: FlowNode,\n    nodes: FlowNode[],\n    edges: FlowEdge[],\n    visited: Set<string>,\n    projectPath: string,\n    maxDepth: number\n  ): Promise<void> {\n    if (maxDepth <= 0) return;\n\n    const nodeKey = `${filePath}:${currentNode.symbol}`;\n    if (visited.has(nodeKey)) return;\n    visited.add(nodeKey);\n\n    try {\n      const content = await fs.readFile(filePath, 'utf-8');\n      const analysis = await this.treeSitter.analyzeFile(filePath);\n\n      // Extract function calls using regex (simple approximation)\n      // This could be enhanced with tree-sitter AST traversal\n      const callPattern = /(\\w+)\\s*\\(/g;\n      let match;\n      const calls: string[] = [];\n\n      while ((match = callPattern.exec(content)) !== null) {\n        const funcName = match[1];\n        // Skip common keywords and built-ins\n        if (!/^(if|for|while|switch|return|await|async|function|const|let|var|new|typeof|instanceof|import|export|class|interface|type)$/.test(funcName)) {\n          if (!calls.includes(funcName)) {\n            calls.push(funcName);\n          }\n        }\n      }\n\n      // Match calls to symbols in the analysis\n      for (const call of calls.slice(0, 20)) {\n        // Check if it's a local symbol\n        const localSymbol = analysis.symbols.find((s) => s.name === call);\n        if (localSymbol) {\n          const callNode: FlowNode = {\n            file: filePath,\n            symbol: call,\n            line: localSymbol.location.line,\n            type: 'call',\n          };\n\n          if (!nodes.find((n) => n.file === callNode.file && n.symbol === callNode.symbol)) {\n            nodes.push(callNode);\n            edges.push({ from: currentNode, to: callNode });\n\n            await this.traceCallsFromFile(\n              filePath,\n              callNode,\n              nodes,\n              edges,\n              visited,\n              projectPath,\n              maxDepth - 1\n            );\n          }\n        }\n\n        // Check if it's an imported symbol\n        for (const imp of analysis.imports) {\n          if (imp.specifiers.includes(call)) {\n            const resolvedPath = this.resolveImportPath(filePath, imp.source, projectPath);\n            if (resolvedPath) {\n              const importedAnalysis = await this.treeSitter.analyzeFile(resolvedPath);\n              const importedSymbol = importedAnalysis.symbols.find((s) => s.name === call);\n\n              if (importedSymbol) {\n                const callNode: FlowNode = {\n                  file: resolvedPath,\n                  symbol: call,\n                  line: importedSymbol.location.line,\n                  type: 'call',\n                };\n\n                if (!nodes.find((n) => n.file === callNode.file && n.symbol === callNode.symbol)) {\n                  nodes.push(callNode);\n                  edges.push({ from: currentNode, to: callNode });\n\n                  await this.traceCallsFromFile(\n                    resolvedPath,\n                    callNode,\n                    nodes,\n                    edges,\n                    visited,\n                    projectPath,\n                    maxDepth - 1\n                  );\n                }\n              }\n            }\n          }\n        }\n      }\n    } catch {\n      // File read or analysis error, skip\n    }\n  }\n\n  private generateMermaidDiagram(nodes: FlowNode[], edges: FlowEdge[]): string {\n    if (nodes.length === 0) {\n      return 'flowchart TD\\n    A[No nodes]';\n    }\n\n    const lines: string[] = ['flowchart TD'];\n    const nodeIds = new Map<string, string>();\n\n    // Assign IDs to nodes\n    nodes.forEach((node, index) => {\n      const key = `${node.file}:${node.symbol}`;\n      nodeIds.set(key, `N${index}`);\n    });\n\n    // Add nodes\n    for (const node of nodes) {\n      const key = `${node.file}:${node.symbol}`;\n      const id = nodeIds.get(key)!;\n      const label = `${node.symbol}`;\n      const shape = node.type === 'entry' ? `([${label}])` : `[${label}]`;\n      lines.push(`    ${id}${shape}`);\n    }\n\n    // Add edges\n    for (const edge of edges) {\n      const fromKey = `${edge.from.file}:${edge.from.symbol}`;\n      const toKey = `${edge.to.file}:${edge.to.symbol}`;\n      const fromId = nodeIds.get(fromKey);\n      const toId = nodeIds.get(toKey);\n\n      if (fromId && toId) {\n        const label = edge.label ? `|${edge.label}|` : '';\n        lines.push(`    ${fromId} -->${label} ${toId}`);\n      }\n    }\n\n    return lines.join('\\n');\n  }\n}\n"
  },
  {
    "path": "src/services/semantic/contextBuilder.ts",
    "content": "/**\n * SemanticContextBuilder - Generates optimized context strings for LLM prompts\n *\n * Uses pre-computed semantic analysis to provide rich context without\n * requiring the LLM to explore the codebase with tools.\n */\n\nimport * as path from 'path';\nimport { CodebaseAnalyzer } from './codebaseAnalyzer';\nimport type {\n  SemanticContext,\n  ExtractedSymbol,\n  ArchitectureLayer,\n  DetectedPattern,\n  AnalyzerOptions,\n} from './types';\nimport { DEFAULT_EXCLUDE_PATTERNS } from './types';\n\nexport interface ContextBuilderOptions extends AnalyzerOptions {\n  /** Maximum symbols to include per category */\n  maxSymbolsPerCategory?: number;\n  /** Include full documentation strings */\n  includeDocumentation?: boolean;\n  /** Include parameter and return type info */\n  includeSignatures?: boolean;\n  /** Maximum total context length (chars) */\n  maxContextLength?: number;\n}\n\nexport type ContextFormat = 'documentation' | 'playbook' | 'plan' | 'compact';\n\nconst DEFAULT_OPTIONS: Required<ContextBuilderOptions> = {\n  useLSP: false,\n  languages: ['typescript', 'javascript', 'python', 'go'],\n  exclude: DEFAULT_EXCLUDE_PATTERNS,\n  include: [],\n  maxFiles: 5000,\n  cacheEnabled: true,\n  maxSymbolsPerCategory: 50,\n  includeDocumentation: true,\n  includeSignatures: true,\n  maxContextLength: 32000,\n};\n\nexport class SemanticContextBuilder {\n  private analyzer: CodebaseAnalyzer;\n  private options: Required<ContextBuilderOptions>;\n  private cachedContext: SemanticContext | null = null;\n  private cachedProjectPath: string | null = null;\n\n  constructor(options: ContextBuilderOptions = {}) {\n    this.options = { ...DEFAULT_OPTIONS, ...options };\n    this.analyzer = new CodebaseAnalyzer(this.options);\n  }\n\n  /**\n   * Analyze the codebase and cache the result\n   */\n  async analyze(projectPath: string): Promise<SemanticContext> {\n    if (this.cachedContext && this.cachedProjectPath === projectPath) {\n      return this.cachedContext;\n    }\n\n    this.cachedContext = await this.analyzer.analyze(projectPath);\n    this.cachedProjectPath = projectPath;\n    return this.cachedContext;\n  }\n\n  /**\n   * Build context string for documentation generation\n   */\n  async buildDocumentationContext(\n    projectPath: string,\n    targetFile?: string\n  ): Promise<string> {\n    const context = await this.analyze(projectPath);\n    const sections: string[] = [];\n\n    // Header\n    sections.push('# Codebase Context for Documentation\\n');\n\n    // Architecture overview\n    sections.push(this.formatArchitectureOverview(context));\n\n    // Public API (most important for docs)\n    sections.push(this.formatPublicAPI(context, projectPath));\n\n    // If target file specified, add focused context\n    if (targetFile) {\n      sections.push(this.formatTargetFileContext(context, targetFile, projectPath));\n    }\n\n    // Key symbols by category\n    sections.push(this.formatSymbolIndex(context, projectPath));\n\n    // Dependency overview\n    sections.push(this.formatDependencyOverview(context, projectPath));\n\n    return this.truncateToLimit(sections.join('\\n'));\n  }\n\n  /**\n   * Build context string for agent playbook generation\n   */\n  async buildPlaybookContext(\n    projectPath: string,\n    agentType: string\n  ): Promise<string> {\n    const context = await this.analyze(projectPath);\n    const sections: string[] = [];\n\n    // Header\n    sections.push(`# Codebase Context for ${agentType} Agent\\n`);\n\n    // Relevant layers for this agent type\n    const relevantLayers = this.getRelevantLayersForAgent(agentType, context);\n    sections.push(this.formatRelevantLayers(relevantLayers, projectPath));\n\n    // Relevant patterns\n    const relevantPatterns = this.getRelevantPatternsForAgent(agentType, context);\n    sections.push(this.formatRelevantPatterns(relevantPatterns));\n\n    // Key files for this agent type\n    sections.push(this.formatKeyFilesForAgent(agentType, context, projectPath));\n\n    // Relevant symbols\n    sections.push(this.formatRelevantSymbolsForAgent(agentType, context, projectPath));\n\n    return this.truncateToLimit(sections.join('\\n'));\n  }\n\n  /**\n   * Build context string for development plan generation\n   */\n  async buildPlanContext(\n    projectPath: string,\n    planGoal?: string\n  ): Promise<string> {\n    const context = await this.analyze(projectPath);\n    const sections: string[] = [];\n\n    // Header\n    sections.push('# Codebase Context for Development Planning\\n');\n    if (planGoal) {\n      sections.push(`**Plan Goal**: ${planGoal}\\n`);\n    }\n\n    // Full architecture overview\n    sections.push(this.formatFullArchitecture(context, projectPath));\n\n    // Detected patterns\n    sections.push(this.formatAllPatterns(context));\n\n    // Entry points\n    sections.push(this.formatEntryPoints(context));\n\n    // Layer dependencies (important for planning)\n    sections.push(this.formatLayerDependencies(context));\n\n    // Symbol summary by layer\n    sections.push(this.formatSymbolsByLayer(context, projectPath));\n\n    return this.truncateToLimit(sections.join('\\n'));\n  }\n\n  /**\n   * Build a compact context suitable for any purpose\n   */\n  async buildCompactContext(projectPath: string): Promise<string> {\n    const context = await this.analyze(projectPath);\n    const sections: string[] = [];\n\n    sections.push('# Codebase Summary\\n');\n    sections.push(this.formatArchitectureOverview(context));\n    sections.push(this.formatCompactSymbolList(context, projectPath));\n\n    return this.truncateToLimit(sections.join('\\n'));\n  }\n\n  /**\n   * Build context string for skill personalization\n   */\n  async buildSkillContext(\n    projectPath: string,\n    skillType: string,\n    docsContext?: string,\n    agentsContext?: string\n  ): Promise<string> {\n    const context = await this.analyze(projectPath);\n    const sections: string[] = [];\n\n    // Header\n    sections.push(`# Codebase Context for ${skillType} Skill\\n`);\n\n    // Relevant patterns for this skill\n    const relevantPatterns = this.getRelevantPatternsForSkill(skillType, context);\n    if (relevantPatterns.length > 0) {\n      sections.push(this.formatRelevantPatterns(relevantPatterns));\n    }\n\n    // Key files for this skill\n    sections.push(this.formatKeyFilesForSkill(skillType, context, projectPath));\n\n    // Relevant symbols for this skill\n    sections.push(this.formatRelevantSymbolsForSkill(skillType, context, projectPath));\n\n    // Include docs context if provided\n    if (docsContext) {\n      sections.push('## Project Documentation\\n');\n      sections.push(docsContext);\n      sections.push('');\n    }\n\n    // Include agents context if provided\n    if (agentsContext) {\n      sections.push('## Agent Playbooks\\n');\n      sections.push(agentsContext);\n      sections.push('');\n    }\n\n    return this.truncateToLimit(sections.join('\\n'));\n  }\n\n  /**\n   * Get raw semantic context for custom processing\n   */\n  async getSemanticContext(projectPath: string): Promise<SemanticContext> {\n    return this.analyze(projectPath);\n  }\n\n  // ============ Formatting Methods ============\n\n  private formatArchitectureOverview(context: SemanticContext): string {\n    const { architecture } = context;\n    if (architecture.layers.length === 0) {\n      return '';\n    }\n\n    const lines = ['## Architecture\\n'];\n\n    for (const layer of architecture.layers.slice(0, 8)) {\n      const dirs = layer.directories.length > 0\n        ? layer.directories.join(', ')\n        : 'various locations';\n      const deps = layer.dependsOn.length > 0\n        ? ` → depends on: ${layer.dependsOn.join(', ')}`\n        : '';\n      lines.push(`- **${layer.name}**: \\`${dirs}\\`${deps}`);\n    }\n\n    lines.push('');\n    return lines.join('\\n');\n  }\n\n  private formatPublicAPI(context: SemanticContext, projectPath: string): string {\n    const { architecture } = context;\n    if (architecture.publicAPI.length === 0) {\n      return '';\n    }\n\n    const lines = ['## Public API\\n'];\n    const limited = architecture.publicAPI.slice(0, this.options.maxSymbolsPerCategory);\n\n    for (const symbol of limited) {\n      lines.push(this.formatSymbolLine(symbol, projectPath));\n    }\n\n    if (architecture.publicAPI.length > limited.length) {\n      lines.push(`... and ${architecture.publicAPI.length - limited.length} more exports`);\n    }\n\n    lines.push('');\n    return lines.join('\\n');\n  }\n\n  private formatSymbolIndex(context: SemanticContext, projectPath: string): string {\n    const { symbols } = context;\n    const lines = ['## Symbol Index\\n'];\n\n    const categories = [\n      { name: 'Classes', items: symbols.classes },\n      { name: 'Interfaces', items: symbols.interfaces },\n      { name: 'Functions', items: symbols.functions },\n      { name: 'Types', items: symbols.types },\n      { name: 'Enums', items: symbols.enums },\n    ];\n\n    for (const cat of categories) {\n      if (cat.items.length === 0) continue;\n\n      lines.push(`### ${cat.name}\\n`);\n      const limited = cat.items.slice(0, this.options.maxSymbolsPerCategory);\n\n      for (const symbol of limited) {\n        lines.push(this.formatSymbolLine(symbol, projectPath));\n      }\n\n      if (cat.items.length > limited.length) {\n        lines.push(`... and ${cat.items.length - limited.length} more`);\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatSymbolLine(symbol: ExtractedSymbol, projectPath: string): string {\n    const relPath = path.relative(projectPath, symbol.location.file);\n    const loc = `${relPath}:${symbol.location.line}`;\n    const exported = symbol.exported ? ' (exported)' : '';\n\n    let signature = '';\n    if (this.options.includeSignatures && symbol.parameters) {\n      const params = symbol.parameters\n        .map((p) => `${p.name}${p.type ? `: ${p.type}` : ''}`)\n        .join(', ');\n      const ret = symbol.returnType ? `: ${symbol.returnType}` : '';\n      signature = `(${params})${ret}`;\n    }\n\n    let doc = '';\n    if (this.options.includeDocumentation && symbol.documentation) {\n      const shortDoc = symbol.documentation.split('\\n')[0].slice(0, 80);\n      doc = ` - ${shortDoc}`;\n    }\n\n    return `- \\`${symbol.name}\\`${signature}${exported} @ ${loc}${doc}`;\n  }\n\n  private formatTargetFileContext(\n    context: SemanticContext,\n    targetFile: string,\n    projectPath: string\n  ): string {\n    const lines = [`## Target File: ${targetFile}\\n`];\n\n    // Find symbols in or related to target file\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n      ...context.symbols.types,\n      ...context.symbols.enums,\n    ];\n\n    const relatedSymbols = allSymbols.filter((s) => {\n      const relPath = path.relative(projectPath, s.location.file);\n      return relPath.includes(targetFile) || targetFile.includes(relPath);\n    });\n\n    if (relatedSymbols.length > 0) {\n      lines.push('### Symbols in this file:\\n');\n      for (const symbol of relatedSymbols) {\n        lines.push(this.formatSymbolLine(symbol, projectPath));\n      }\n    }\n\n    lines.push('');\n    return lines.join('\\n');\n  }\n\n  private formatDependencyOverview(context: SemanticContext, projectPath: string): string {\n    const { dependencies } = context;\n    const lines = ['## Key Dependencies\\n'];\n\n    // Find most imported files\n    const importCounts = new Map<string, number>();\n    for (const [, importers] of dependencies.reverseGraph) {\n      for (const importer of importers) {\n        const count = importCounts.get(importer) || 0;\n        importCounts.set(importer, count + 1);\n      }\n    }\n\n    const sorted = [...importCounts.entries()]\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, 15);\n\n    for (const [file, count] of sorted) {\n      const relPath = path.relative(projectPath, file);\n      lines.push(`- \\`${relPath}\\`: imported by ${count} files`);\n    }\n\n    lines.push('');\n    return lines.join('\\n');\n  }\n\n  private formatRelevantLayers(\n    layers: ArchitectureLayer[],\n    projectPath: string\n  ): string {\n    if (layers.length === 0) return '';\n\n    const lines = ['## Relevant Layers\\n'];\n\n    for (const layer of layers) {\n      lines.push(`### ${layer.name}\\n`);\n      lines.push(`${layer.description}\\n`);\n      lines.push(`**Directories**: \\`${layer.directories.join('`, `')}\\`\\n`);\n      lines.push(`**Key Exports**:\\n`);\n\n      const keySymbols = layer.symbols\n        .filter((s) => s.exported)\n        .slice(0, 10);\n\n      for (const symbol of keySymbols) {\n        lines.push(this.formatSymbolLine(symbol, projectPath));\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatRelevantPatterns(patterns: DetectedPattern[]): string {\n    if (patterns.length === 0) return '';\n\n    const lines = ['## Detected Patterns\\n'];\n\n    for (const pattern of patterns) {\n      const confidence = Math.round(pattern.confidence * 100);\n      lines.push(`### ${pattern.name} (${confidence}% confidence)\\n`);\n      lines.push(`${pattern.description}\\n`);\n      lines.push('**Locations**:');\n      for (const loc of pattern.locations.slice(0, 5)) {\n        lines.push(`- \\`${loc.symbol}\\` in ${loc.file}`);\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatKeyFilesForAgent(\n    agentType: string,\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const keyPatterns = this.getKeyPatternsForAgent(agentType);\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n    ];\n\n    const relevantFiles = new Set<string>();\n    for (const symbol of allSymbols) {\n      const relPath = path.relative(projectPath, symbol.location.file);\n      if (keyPatterns.some((p) => p.test(relPath) || p.test(symbol.name))) {\n        relevantFiles.add(relPath);\n      }\n    }\n\n    if (relevantFiles.size === 0) return '';\n\n    const lines = ['## Key Files\\n'];\n    for (const file of [...relevantFiles].slice(0, 20)) {\n      lines.push(`- \\`${file}\\``);\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatRelevantSymbolsForAgent(\n    agentType: string,\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const keyPatterns = this.getKeyPatternsForAgent(agentType);\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n    ];\n\n    const relevantSymbols = allSymbols.filter((s) => {\n      const relPath = path.relative(projectPath, s.location.file);\n      return keyPatterns.some((p) => p.test(relPath) || p.test(s.name));\n    });\n\n    if (relevantSymbols.length === 0) return '';\n\n    const lines = ['## Relevant Symbols\\n'];\n    for (const symbol of relevantSymbols.slice(0, 30)) {\n      lines.push(this.formatSymbolLine(symbol, projectPath));\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatFullArchitecture(\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const { architecture } = context;\n    const lines = ['## Architecture\\n'];\n\n    if (architecture.layers.length > 0) {\n      lines.push('### Layers\\n');\n      for (const layer of architecture.layers) {\n        lines.push(`**${layer.name}**`);\n        lines.push(`- ${layer.description}`);\n        lines.push(`- Directories: \\`${layer.directories.join('`, `')}\\``);\n        // Show key exported symbols with their file locations instead of just count\n        const keySymbols = layer.symbols.filter(s => s.exported).slice(0, 5);\n        if (keySymbols.length > 0) {\n          const symbolRefs = keySymbols.map(s => {\n            const relPath = path.relative(projectPath, s.location.file);\n            return `\\`${s.name}\\` (${relPath}:${s.location.line})`;\n          });\n          lines.push(`- Key exports: ${symbolRefs.join(', ')}`);\n        }\n        if (layer.dependsOn.length > 0) {\n          lines.push(`- Depends on: ${layer.dependsOn.join(', ')}`);\n        }\n        lines.push('');\n      }\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatAllPatterns(context: SemanticContext): string {\n    const { architecture } = context;\n    if (architecture.patterns.length === 0) return '';\n\n    const lines = ['## Design Patterns\\n'];\n    for (const pattern of architecture.patterns) {\n      const confidence = Math.round(pattern.confidence * 100);\n      lines.push(\n        `- **${pattern.name}** (${confidence}%): ${pattern.locations.length} occurrences - ${pattern.description}`\n      );\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatEntryPoints(context: SemanticContext): string {\n    const { architecture } = context;\n    if (architecture.entryPoints.length === 0) return '';\n\n    const lines = ['## Entry Points\\n'];\n    for (const ep of architecture.entryPoints) {\n      lines.push(`- \\`${ep}\\``);\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatLayerDependencies(context: SemanticContext): string {\n    const { architecture } = context;\n    const layersWithDeps = architecture.layers.filter(\n      (l) => l.dependsOn.length > 0\n    );\n\n    if (layersWithDeps.length === 0) return '';\n\n    const lines = ['## Layer Dependencies\\n'];\n    for (const layer of layersWithDeps) {\n      lines.push(`- ${layer.name} → ${layer.dependsOn.join(', ')}`);\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatSymbolsByLayer(\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const { architecture } = context;\n    if (architecture.layers.length === 0) return '';\n\n    const lines = ['## Symbols by Layer\\n'];\n\n    for (const layer of architecture.layers) {\n      const dirs = layer.directories.length > 0\n        ? ` (\\`${layer.directories.join('`, `')}\\`)`\n        : '';\n      lines.push(`### ${layer.name}${dirs}\\n`);\n\n      const exported = layer.symbols.filter((s) => s.exported).slice(0, 15);\n      for (const symbol of exported) {\n        lines.push(this.formatSymbolLine(symbol, projectPath));\n      }\n\n      if (layer.symbols.length > exported.length) {\n        const remaining = layer.symbols.length - exported.length;\n        lines.push(`... and ${remaining} more in this layer`);\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  private formatCompactSymbolList(\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const lines = ['## Key Symbols\\n'];\n\n    const allExported = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n    ].filter((s) => s.exported);\n\n    for (const symbol of allExported.slice(0, 30)) {\n      const relPath = path.relative(projectPath, symbol.location.file);\n      lines.push(`- ${symbol.kind}: \\`${symbol.name}\\` @ ${relPath}:${symbol.location.line}`);\n    }\n\n    lines.push('');\n    return lines.join('\\n');\n  }\n\n  // ============ Helper Methods ============\n\n  private getRelevantLayersForAgent(\n    agentType: string,\n    context: SemanticContext\n  ): ArchitectureLayer[] {\n    const layerPriority: Record<string, string[]> = {\n      'code-reviewer': ['Services', 'Controllers', 'Utils'],\n      'bug-fixer': ['Services', 'Controllers', 'Utils', 'Models'],\n      'feature-developer': ['Services', 'Controllers', 'Models', 'Components'],\n      'refactoring-specialist': ['Services', 'Utils', 'Models'],\n      'test-writer': ['Services', 'Controllers', 'Utils'],\n      'documentation-writer': ['Services', 'Controllers', 'Models', 'Utils'],\n      'performance-optimizer': ['Services', 'Repositories', 'Utils'],\n      'security-auditor': ['Controllers', 'Services', 'Config'],\n      'backend-specialist': ['Services', 'Controllers', 'Repositories', 'Models'],\n      'frontend-specialist': ['Components', 'Utils', 'Services'],\n      'architect-specialist': ['Services', 'Controllers', 'Models', 'Config'],\n      'devops-specialist': ['Config', 'Utils'],\n      'database-specialist': ['Repositories', 'Models', 'Services'],\n      'mobile-specialist': ['Components', 'Services', 'Utils'],\n    };\n\n    const priority = layerPriority[agentType] || ['Services', 'Utils'];\n    return context.architecture.layers.filter((l) =>\n      priority.includes(l.name)\n    );\n  }\n\n  private getRelevantPatternsForAgent(\n    agentType: string,\n    context: SemanticContext\n  ): DetectedPattern[] {\n    const patternPriority: Record<string, string[]> = {\n      'code-reviewer': ['Service Layer', 'Repository', 'Factory'],\n      'refactoring-specialist': ['Factory', 'Builder', 'Singleton'],\n      'architect-specialist': ['Service Layer', 'Repository', 'Controller', 'Factory'],\n      'backend-specialist': ['Service Layer', 'Repository', 'Controller'],\n      'database-specialist': ['Repository'],\n    };\n\n    const priority = patternPriority[agentType];\n    if (!priority) return context.architecture.patterns;\n\n    return context.architecture.patterns.filter((p) =>\n      priority.includes(p.name)\n    );\n  }\n\n  private getKeyPatternsForAgent(agentType: string): RegExp[] {\n    const patterns: Record<string, RegExp[]> = {\n      'code-reviewer': [/\\.(ts|js|tsx|jsx)$/, /service/i, /controller/i],\n      'bug-fixer': [/error/i, /exception/i, /handler/i, /\\.test\\./i],\n      'feature-developer': [/service/i, /controller/i, /component/i],\n      'refactoring-specialist': [/service/i, /util/i, /helper/i],\n      'test-writer': [/\\.test\\./i, /\\.spec\\./i, /jest/i, /vitest/i],\n      'documentation-writer': [/\\.md$/i, /readme/i, /doc/i],\n      'performance-optimizer': [/service/i, /repository/i, /cache/i],\n      'security-auditor': [/auth/i, /security/i, /credential/i, /\\.env/i],\n      'backend-specialist': [/service/i, /controller/i, /api/i, /route/i],\n      'frontend-specialist': [/component/i, /view/i, /page/i, /\\.tsx$/],\n      'architect-specialist': [/service/i, /factory/i, /config/i],\n      'devops-specialist': [/docker/i, /ci/i, /deploy/i, /config/i],\n      'database-specialist': [/model/i, /schema/i, /migration/i, /repository/i],\n      'mobile-specialist': [/component/i, /screen/i, /\\.tsx$/],\n    };\n\n    return patterns[agentType] || [/\\.(ts|js)$/];\n  }\n\n  private getRelevantPatternsForSkill(\n    skillType: string,\n    context: SemanticContext\n  ): DetectedPattern[] {\n    const patternPriority: Record<string, string[]> = {\n      'commit-message': ['Service Layer', 'Repository'],\n      'pr-review': ['Service Layer', 'Repository', 'Controller'],\n      'code-review': ['Factory', 'Service Layer', 'Repository'],\n      'test-generation': ['Repository', 'Service Layer'],\n      'documentation': ['Service Layer', 'Controller'],\n      'refactoring': ['Factory', 'Builder', 'Service Layer'],\n      'bug-investigation': ['Service Layer', 'Repository'],\n      'feature-breakdown': ['Service Layer', 'Controller', 'Repository'],\n      'api-design': ['Controller', 'Service Layer'],\n      'security-audit': ['Controller', 'Service Layer'],\n    };\n\n    const priority = patternPriority[skillType];\n    if (!priority) return context.architecture.patterns;\n\n    return context.architecture.patterns.filter((p) =>\n      priority.includes(p.name)\n    );\n  }\n\n  private getKeyPatternsForSkill(skillType: string): RegExp[] {\n    const patterns: Record<string, RegExp[]> = {\n      'commit-message': [/\\.git/i, /changelog/i, /package\\.json$/i, /\\.ts$/],\n      'pr-review': [/\\.github/i, /\\.ts$/, /\\.test\\./i, /spec/i],\n      'code-review': [/service/i, /controller/i, /\\.ts$/, /eslint/i],\n      'test-generation': [/\\.test\\./i, /\\.spec\\./i, /jest/i, /vitest/i],\n      'documentation': [/\\.md$/i, /readme/i, /docs/i],\n      'refactoring': [/service/i, /util/i, /helper/i, /\\.ts$/],\n      'bug-investigation': [/error/i, /exception/i, /log/i, /\\.ts$/],\n      'feature-breakdown': [/service/i, /component/i, /controller/i],\n      'api-design': [/api/i, /route/i, /controller/i, /openapi/i, /swagger/i],\n      'security-audit': [/auth/i, /security/i, /\\.env/i, /credential/i],\n    };\n\n    return patterns[skillType] || [/\\.(ts|js)$/];\n  }\n\n  private formatKeyFilesForSkill(\n    skillType: string,\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const keyPatterns = this.getKeyPatternsForSkill(skillType);\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n    ];\n\n    const relevantFiles = new Set<string>();\n    for (const symbol of allSymbols) {\n      const relPath = path.relative(projectPath, symbol.location.file);\n      if (keyPatterns.some((p) => p.test(relPath) || p.test(symbol.name))) {\n        relevantFiles.add(relPath);\n      }\n    }\n\n    if (relevantFiles.size === 0) return '';\n\n    const lines = [`## Key Files for ${skillType}\\n`];\n    for (const file of [...relevantFiles].slice(0, 20)) {\n      lines.push(`- \\`${file}\\``);\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private formatRelevantSymbolsForSkill(\n    skillType: string,\n    context: SemanticContext,\n    projectPath: string\n  ): string {\n    const keyPatterns = this.getKeyPatternsForSkill(skillType);\n    const allSymbols = [\n      ...context.symbols.classes,\n      ...context.symbols.interfaces,\n      ...context.symbols.functions,\n    ];\n\n    const relevantSymbols = allSymbols.filter((s) => {\n      const relPath = path.relative(projectPath, s.location.file);\n      return keyPatterns.some((p) => p.test(relPath) || p.test(s.name));\n    });\n\n    if (relevantSymbols.length === 0) return '';\n\n    const lines = [`## Relevant Symbols for ${skillType}\\n`];\n    for (const symbol of relevantSymbols.slice(0, 25)) {\n      lines.push(this.formatSymbolLine(symbol, projectPath));\n    }\n    lines.push('');\n\n    return lines.join('\\n');\n  }\n\n  private truncateToLimit(content: string): string {\n    if (content.length <= this.options.maxContextLength) {\n      return content;\n    }\n\n    // Truncate at a line boundary\n    const truncated = content.slice(0, this.options.maxContextLength);\n    const lastNewline = truncated.lastIndexOf('\\n');\n    return truncated.slice(0, lastNewline) + '\\n\\n... (truncated)';\n  }\n\n  /**\n   * Clear cached analysis\n   */\n  clearCache(): void {\n    this.cachedContext = null;\n    this.cachedProjectPath = null;\n    this.analyzer.clearCache();\n  }\n\n  /**\n   * Shutdown analyzer (cleanup LSP servers if enabled)\n   */\n  async shutdown(): Promise<void> {\n    await this.analyzer.shutdown();\n  }\n}\n"
  },
  {
    "path": "src/services/semantic/contextCache.test.ts",
    "content": "import * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs-extra';\n\nimport { ContextCache } from './contextCache';\n\ndescribe('ContextCache', () => {\n    let tempDir: string;\n\n    beforeEach(async () => {\n        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'context-cache-test-'));\n        // Create watched directories\n        await fs.ensureDir(path.join(tempDir, 'src'));\n        await fs.ensureDir(path.join(tempDir, '.context'));\n    });\n\n    afterEach(async () => {\n        await fs.remove(tempDir);\n    });\n\n    describe('basic operations', () => {\n        it('should return null for cache miss', async () => {\n            const cache = new ContextCache();\n            const result = await cache.get(tempDir, 'compact');\n            expect(result).toBeNull();\n        });\n\n        it('should store and retrieve cached content', async () => {\n            const cache = new ContextCache();\n            const content = 'cached semantic context';\n\n            await cache.set(tempDir, 'compact', content);\n            const result = await cache.get(tempDir, 'compact');\n\n            expect(result).toBe(content);\n        });\n\n        it('should cache different context types independently', async () => {\n            const cache = new ContextCache();\n\n            await cache.set(tempDir, 'compact', 'compact-content');\n            await cache.set(tempDir, 'documentation', 'doc-content');\n\n            expect(await cache.get(tempDir, 'compact')).toBe('compact-content');\n            expect(await cache.get(tempDir, 'documentation')).toBe('doc-content');\n        });\n\n        it('should track cache size', async () => {\n            const cache = new ContextCache();\n            expect(cache.size).toBe(0);\n\n            await cache.set(tempDir, 'compact', 'content');\n            expect(cache.size).toBe(1);\n\n            await cache.set(tempDir, 'documentation', 'content2');\n            expect(cache.size).toBe(2);\n        });\n    });\n\n    describe('TTL expiration', () => {\n        it('should expire entries after TTL', async () => {\n            // Create cache with very short TTL\n            const cache = new ContextCache({ ttlMs: 50 });\n\n            await cache.set(tempDir, 'compact', 'content');\n            expect(await cache.get(tempDir, 'compact')).toBe('content');\n\n            // Wait for TTL to expire\n            await new Promise(resolve => setTimeout(resolve, 100));\n\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n        });\n\n        it('should not expire entries before TTL', async () => {\n            const cache = new ContextCache({ ttlMs: 5000 });\n\n            await cache.set(tempDir, 'compact', 'content');\n            expect(await cache.get(tempDir, 'compact')).toBe('content');\n        });\n    });\n\n    describe('mtime invalidation', () => {\n        it('should invalidate when watched directory changes', async () => {\n            const cache = new ContextCache({ watchDirs: ['src'] });\n\n            await cache.set(tempDir, 'compact', 'old-content');\n            expect(await cache.get(tempDir, 'compact')).toBe('old-content');\n\n            // Modify the src directory (change its mtime)\n            await new Promise(resolve => setTimeout(resolve, 50));\n            await fs.writeFile(path.join(tempDir, 'src', 'newfile.ts'), 'content');\n\n            // Update directory mtime by creating a file\n            const srcPath = path.join(tempDir, 'src');\n            const now = new Date();\n            await fs.utimes(srcPath, now, now);\n\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n        });\n    });\n\n    describe('invalidation and clearing', () => {\n        it('should invalidate all entries for a repo', async () => {\n            const cache = new ContextCache();\n\n            await cache.set(tempDir, 'compact', 'c1');\n            await cache.set(tempDir, 'documentation', 'c2');\n\n            cache.invalidateRepo(tempDir);\n\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n            expect(await cache.get(tempDir, 'documentation')).toBeNull();\n            expect(cache.size).toBe(0);\n        });\n\n        it('should clear all entries', async () => {\n            const cache = new ContextCache();\n\n            await cache.set(tempDir, 'compact', 'c1');\n            await cache.set(tempDir, 'documentation', 'c2');\n\n            cache.clear();\n\n            expect(cache.size).toBe(0);\n        });\n\n        it('should not affect entries from other repos', async () => {\n            const otherDir = await fs.mkdtemp(path.join(os.tmpdir(), 'other-repo-'));\n            await fs.ensureDir(path.join(otherDir, 'src'));\n\n            const cache = new ContextCache();\n\n            await cache.set(tempDir, 'compact', 'repo1');\n            await cache.set(otherDir, 'compact', 'repo2');\n\n            cache.invalidateRepo(tempDir);\n\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n            expect(await cache.get(otherDir, 'compact')).toBe('repo2');\n\n            await fs.remove(otherDir);\n        });\n    });\n\n    describe('overwrite behavior', () => {\n        it('should overwrite existing entries', async () => {\n            const cache = new ContextCache();\n\n            await cache.set(tempDir, 'compact', 'old');\n            await cache.set(tempDir, 'compact', 'new');\n\n            expect(await cache.get(tempDir, 'compact')).toBe('new');\n        });\n    });\n\n    describe('edge cases', () => {\n        it('should handle repos with no watched directories', async () => {\n            const emptyDir = await fs.mkdtemp(path.join(os.tmpdir(), 'empty-'));\n            const cache = new ContextCache({ watchDirs: ['nonexistent'] });\n\n            await cache.set(emptyDir, 'compact', 'content');\n            expect(await cache.get(emptyDir, 'compact')).toBe('content');\n\n            await fs.remove(emptyDir);\n        });\n\n        it('should handle large content strings', async () => {\n            const cache = new ContextCache();\n            const largeContent = 'x'.repeat(100_000);\n\n            await cache.set(tempDir, 'compact', largeContent);\n            expect(await cache.get(tempDir, 'compact')).toBe(largeContent);\n        });\n    });\n});\n"
  },
  {
    "path": "src/services/semantic/contextCache.ts",
    "content": "/**\n * ContextCache - In-memory cache for semantic context with TTL and mtime invalidation.\n *\n * Caches the result of expensive context-building operations (buildDocumentationContext,\n * buildCompactContext) to avoid redundant computation across multiple MCP tool calls.\n *\n * Invalidation strategy:\n * - TTL-based: entries expire after configurable time (default 5 minutes)\n * - Mtime-based: entries are invalidated when source directories are modified\n *\n * Thread safety: Node.js is single-threaded, so no mutex needed.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\n\n/**\n * A cached context entry with metadata for invalidation.\n */\ninterface CacheEntry {\n    /** The cached context string */\n    content: string;\n    /** Timestamp when this entry was created */\n    createdAt: number;\n    /** Modification time hash of source directories at cache time */\n    mtimeHash: string;\n}\n\nexport interface ContextCacheOptions {\n    /** Time-to-live in milliseconds (default: 5 minutes) */\n    ttlMs?: number;\n    /** Directories to monitor for changes (relative to repo root) */\n    watchDirs?: string[];\n}\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst DEFAULT_WATCH_DIRS = ['src', '.context', 'lib', 'packages'];\n\nexport class ContextCache {\n    private readonly cache = new Map<string, CacheEntry>();\n    private readonly ttlMs: number;\n    private readonly watchDirs: string[];\n\n    constructor(options: ContextCacheOptions = {}) {\n        this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n        this.watchDirs = options.watchDirs ?? DEFAULT_WATCH_DIRS;\n    }\n\n    /**\n     * Get a cached context entry, or null if not found/expired/invalidated.\n     *\n     * @param repoPath - Absolute path to the repository root\n     * @param contextType - Type of context (e.g., 'documentation', 'compact', 'full')\n     * @returns Cached context string or null\n     */\n    async get(repoPath: string, contextType: string): Promise<string | null> {\n        const key = this.buildKey(repoPath, contextType);\n        const entry = this.cache.get(key);\n\n        if (!entry) {\n            return null;\n        }\n\n        // Check TTL expiration\n        if (Date.now() - entry.createdAt > this.ttlMs) {\n            this.cache.delete(key);\n            return null;\n        }\n\n        // Check directory mtime invalidation\n        const currentMtimeHash = await this.computeMtimeHash(repoPath);\n        if (currentMtimeHash !== entry.mtimeHash) {\n            this.cache.delete(key);\n            return null;\n        }\n\n        return entry.content;\n    }\n\n    /**\n     * Store a context entry in the cache.\n     *\n     * @param repoPath - Absolute path to the repository root\n     * @param contextType - Type of context\n     * @param content - The context string to cache\n     */\n    async set(repoPath: string, contextType: string, content: string): Promise<void> {\n        const key = this.buildKey(repoPath, contextType);\n        const mtimeHash = await this.computeMtimeHash(repoPath);\n\n        this.cache.set(key, {\n            content,\n            createdAt: Date.now(),\n            mtimeHash,\n        });\n    }\n\n    /**\n     * Invalidate all entries for a given repository.\n     */\n    invalidateRepo(repoPath: string): void {\n        const prefix = this.normalizeRepoPath(repoPath) + ':';\n        for (const key of this.cache.keys()) {\n            if (key.startsWith(prefix)) {\n                this.cache.delete(key);\n            }\n        }\n    }\n\n    /**\n     * Clear all cached entries.\n     */\n    clear(): void {\n        this.cache.clear();\n    }\n\n    /**\n     * Get the number of cached entries (for monitoring/debugging).\n     */\n    get size(): number {\n        return this.cache.size;\n    }\n\n    /**\n     * Build a unique cache key from repo path and context type.\n     */\n    private buildKey(repoPath: string, contextType: string): string {\n        return `${this.normalizeRepoPath(repoPath)}:${contextType}`;\n    }\n\n    /**\n     * Normalize repo path for consistent cache keys.\n     */\n    private normalizeRepoPath(repoPath: string): string {\n        return path.resolve(repoPath).toLowerCase();\n    }\n\n    /**\n     * Compute a lightweight hash based on directory modification times.\n     * Uses mtime of watched directories as a fast approximation\n     * of whether source files have changed.\n     */\n    private async computeMtimeHash(repoPath: string): Promise<string> {\n        const mtimes: number[] = [];\n\n        for (const dir of this.watchDirs) {\n            const dirPath = path.join(repoPath, dir);\n            try {\n                const stat = await fs.stat(dirPath);\n                mtimes.push(Math.floor(stat.mtimeMs));\n            } catch {\n                // Directory doesn't exist — that's fine, skip it\n                mtimes.push(0);\n            }\n        }\n\n        return mtimes.join('-');\n    }\n}\n"
  },
  {
    "path": "src/services/semantic/index.ts",
    "content": "/**\n * Semantic Analysis Service\n *\n * Provides code analysis capabilities using Tree-sitter for fast syntactic\n * analysis and optional LSP integration for deeper semantic understanding.\n */\n\nexport { CodebaseAnalyzer } from './codebaseAnalyzer';\nexport { TreeSitterLayer } from './treeSitter';\nexport { LSPLayer } from './lsp';\nexport {\n  SemanticContextBuilder,\n  type ContextBuilderOptions,\n  type ContextFormat\n} from './contextBuilder';\nexport {\n  SemanticSnapshotService,\n  type SemanticSnapshotManifest,\n  type SemanticSnapshotSection,\n  type SemanticSnapshotWriteOptions,\n  type SemanticSnapshotWriteResult,\n  type SemanticSnapshotReadOptions,\n  type SemanticSnapshotSectionResult,\n} from './semanticSnapshotService';\n\nexport * from './types';\n"
  },
  {
    "path": "src/services/semantic/lsp/index.ts",
    "content": "export { LSPLayer } from './lspLayer';\n"
  },
  {
    "path": "src/services/semantic/lsp/lspLayer.ts",
    "content": "/**\n * LSP Layer - Language Server Protocol integration for semantic analysis\n *\n * Provides deeper semantic understanding through LSP servers.\n * This layer is lazily initialized and optional.\n */\n\nimport { spawn, ChildProcess } from 'child_process';\nimport * as path from 'path';\nimport {\n  TypeInfo,\n  ReferenceLocation,\n  LSPServerConfig,\n  SupportedLanguage,\n  LANGUAGE_EXTENSIONS,\n} from '../types';\n\ninterface LSPMessage {\n  jsonrpc: '2.0';\n  id?: number;\n  method?: string;\n  params?: unknown;\n  result?: unknown;\n  error?: { code: number; message: string };\n}\n\ninterface PendingRequest {\n  resolve: (value: unknown) => void;\n  reject: (error: Error) => void;\n  timeout: NodeJS.Timeout;\n}\n\nconst LSP_SERVER_CONFIGS: Record<string, LSPServerConfig> = {\n  typescript: {\n    command: 'typescript-language-server',\n    args: ['--stdio'],\n    rootPatterns: ['tsconfig.json', 'package.json'],\n  },\n  javascript: {\n    command: 'typescript-language-server',\n    args: ['--stdio'],\n    rootPatterns: ['package.json', 'jsconfig.json'],\n  },\n  python: {\n    command: 'pylsp',\n    args: [],\n    rootPatterns: ['setup.py', 'pyproject.toml', 'requirements.txt', 'setup.cfg'],\n  },\n};\n\nconst REQUEST_TIMEOUT_MS = 10000;\n\nexport class LSPLayer {\n  private servers: Map<string, ChildProcess> = new Map();\n  private messageId: number = 0;\n  private pendingRequests: Map<number, PendingRequest> = new Map();\n  private initialized: Map<string, boolean> = new Map();\n  private buffers: Map<string, string> = new Map();\n\n  private detectLanguage(filePath: string): SupportedLanguage | null {\n    const ext = path.extname(filePath);\n    return LANGUAGE_EXTENSIONS[ext] || null;\n  }\n\n  private getServerConfig(language: string): LSPServerConfig | null {\n    return LSP_SERVER_CONFIGS[language] || null;\n  }\n\n  async ensureServer(language: string, projectPath: string): Promise<boolean> {\n    if (this.initialized.get(language)) {\n      return true;\n    }\n\n    const config = this.getServerConfig(language);\n    if (!config) {\n      return false;\n    }\n\n    try {\n      // Wrap spawn in a promise to properly handle ENOENT and other spawn errors\n      const serverProcess = await new Promise<ChildProcess>((resolve, reject) => {\n        const proc = spawn(config.command, config.args, {\n          cwd: projectPath,\n          stdio: ['pipe', 'pipe', 'pipe'],\n          env: { ...process.env },\n        });\n\n        // Handle spawn errors (e.g., command not found)\n        proc.on('error', (error) => {\n          reject(error);\n        });\n\n        // Give spawn a moment to fail or succeed\n        // If no error within a short time, assume spawn succeeded\n        setTimeout(() => {\n          if (proc.pid) {\n            resolve(proc);\n          } else {\n            reject(new Error(`Failed to spawn ${config.command}`));\n          }\n        }, 100);\n      });\n\n      this.servers.set(language, serverProcess);\n      this.buffers.set(language, '');\n\n      // Setup message handling\n      serverProcess.stdout?.on('data', (data: Buffer) => {\n        this.handleServerData(language, data);\n      });\n\n      serverProcess.stderr?.on('data', () => {\n        // Silently ignore stderr - LSP servers may output diagnostics\n      });\n\n      // Re-attach error handler for runtime errors (after spawn)\n      serverProcess.on('error', () => {\n        // Silently cleanup on error\n        this.cleanup(language);\n      });\n\n      serverProcess.on('exit', () => {\n        this.cleanup(language);\n      });\n\n      // Initialize the server\n      await this.sendRequest(language, 'initialize', {\n        processId: process.pid,\n        rootUri: `file://${projectPath}`,\n        capabilities: {\n          textDocument: {\n            hover: { contentFormat: ['markdown', 'plaintext'] },\n            definition: { linkSupport: true },\n            references: {},\n            implementation: {},\n          },\n        },\n      });\n\n      this.sendNotification(language, 'initialized', {});\n      this.initialized.set(language, true);\n\n      return true;\n    } catch {\n      // LSP server not available - this is expected if the language server isn't installed\n      // Silently return false and fall back to non-LSP analysis\n      return false;\n    }\n  }\n\n  private handleServerData(language: string, data: Buffer): void {\n    let buffer = (this.buffers.get(language) || '') + data.toString();\n\n    while (true) {\n      const headerMatch = buffer.match(/Content-Length: (\\d+)\\r\\n\\r\\n/);\n      if (!headerMatch) break;\n\n      const contentLength = parseInt(headerMatch[1], 10);\n      const headerEnd = headerMatch.index! + headerMatch[0].length;\n\n      if (buffer.length < headerEnd + contentLength) break;\n\n      const content = buffer.slice(headerEnd, headerEnd + contentLength);\n      buffer = buffer.slice(headerEnd + contentLength);\n\n      try {\n        const message: LSPMessage = JSON.parse(content);\n        this.handleMessage(message);\n      } catch {\n        // Silently ignore malformed messages\n      }\n    }\n\n    this.buffers.set(language, buffer);\n  }\n\n  private handleMessage(message: LSPMessage): void {\n    if (message.id !== undefined && this.pendingRequests.has(message.id)) {\n      const pending = this.pendingRequests.get(message.id)!;\n      this.pendingRequests.delete(message.id);\n      clearTimeout(pending.timeout);\n\n      if (message.error) {\n        pending.reject(new Error(message.error.message));\n      } else {\n        pending.resolve(message.result);\n      }\n    }\n  }\n\n  private sendRequest(language: string, method: string, params: unknown): Promise<unknown> {\n    return new Promise((resolve, reject) => {\n      const id = ++this.messageId;\n\n      const timeout = setTimeout(() => {\n        this.pendingRequests.delete(id);\n        reject(new Error(`LSP request timeout: ${method}`));\n      }, REQUEST_TIMEOUT_MS);\n\n      this.pendingRequests.set(id, { resolve, reject, timeout });\n\n      const message: LSPMessage = {\n        jsonrpc: '2.0',\n        id,\n        method,\n        params,\n      };\n\n      this.sendMessage(language, message);\n    });\n  }\n\n  private sendNotification(language: string, method: string, params: unknown): void {\n    const message: LSPMessage = {\n      jsonrpc: '2.0',\n      method,\n      params,\n    };\n    this.sendMessage(language, message);\n  }\n\n  private sendMessage(language: string, message: LSPMessage): void {\n    const server = this.servers.get(language);\n    if (!server?.stdin?.writable) return;\n\n    const content = JSON.stringify(message);\n    const header = `Content-Length: ${Buffer.byteLength(content)}\\r\\n\\r\\n`;\n\n    try {\n      server.stdin.write(header + content);\n    } catch {\n      // Silently ignore send failures\n    }\n  }\n\n  private cleanup(language: string): void {\n    this.servers.delete(language);\n    this.initialized.delete(language);\n    this.buffers.delete(language);\n\n    // Reject all pending requests for this language\n    for (const [id, pending] of this.pendingRequests) {\n      clearTimeout(pending.timeout);\n      pending.reject(new Error(`LSP server ${language} disconnected`));\n      this.pendingRequests.delete(id);\n    }\n  }\n\n  async getTypeInfo(\n    filePath: string,\n    line: number,\n    column: number,\n    projectPath: string\n  ): Promise<TypeInfo | null> {\n    const language = this.detectLanguage(filePath);\n    if (!language) return null;\n\n    const serverReady = await this.ensureServer(language, projectPath);\n    if (!serverReady) return null;\n\n    try {\n      const result = await this.sendRequest(language, 'textDocument/hover', {\n        textDocument: { uri: `file://${filePath}` },\n        position: { line: line - 1, character: column },\n      });\n\n      if (result && typeof result === 'object' && 'contents' in result) {\n        return this.parseHoverResult((result as { contents: unknown }).contents);\n      }\n    } catch {\n      // LSP request failed - return null to fall back to non-LSP analysis\n    }\n\n    return null;\n  }\n\n  async findReferences(\n    filePath: string,\n    line: number,\n    column: number,\n    projectPath: string\n  ): Promise<ReferenceLocation[]> {\n    const language = this.detectLanguage(filePath);\n    if (!language) return [];\n\n    const serverReady = await this.ensureServer(language, projectPath);\n    if (!serverReady) return [];\n\n    try {\n      const result = await this.sendRequest(language, 'textDocument/references', {\n        textDocument: { uri: `file://${filePath}` },\n        position: { line: line - 1, character: column },\n        context: { includeDeclaration: true },\n      });\n\n      if (Array.isArray(result)) {\n        return result.map((ref: { uri: string; range: { start: { line: number; character: number } } }) => ({\n          file: ref.uri.replace('file://', ''),\n          line: ref.range.start.line + 1,\n          column: ref.range.start.character,\n        }));\n      }\n    } catch {\n      // LSP request failed - return empty array\n    }\n\n    return [];\n  }\n\n  async getDefinition(\n    filePath: string,\n    line: number,\n    column: number,\n    projectPath: string\n  ): Promise<ReferenceLocation | null> {\n    const language = this.detectLanguage(filePath);\n    if (!language) return null;\n\n    const serverReady = await this.ensureServer(language, projectPath);\n    if (!serverReady) return null;\n\n    try {\n      const result = await this.sendRequest(language, 'textDocument/definition', {\n        textDocument: { uri: `file://${filePath}` },\n        position: { line: line - 1, character: column },\n      });\n\n      if (Array.isArray(result) && result.length > 0) {\n        const def = result[0] as { uri: string; range: { start: { line: number; character: number } } };\n        return {\n          file: def.uri.replace('file://', ''),\n          line: def.range.start.line + 1,\n          column: def.range.start.character,\n        };\n      }\n    } catch {\n      // LSP request failed - return null\n    }\n\n    return null;\n  }\n\n  async findImplementations(\n    filePath: string,\n    line: number,\n    column: number,\n    projectPath: string\n  ): Promise<ReferenceLocation[]> {\n    const language = this.detectLanguage(filePath);\n    if (!language) return [];\n\n    const serverReady = await this.ensureServer(language, projectPath);\n    if (!serverReady) return [];\n\n    try {\n      const result = await this.sendRequest(language, 'textDocument/implementation', {\n        textDocument: { uri: `file://${filePath}` },\n        position: { line: line - 1, character: column },\n      });\n\n      if (Array.isArray(result)) {\n        return result.map((impl: { uri: string; range: { start: { line: number; character: number } } }) => ({\n          file: impl.uri.replace('file://', ''),\n          line: impl.range.start.line + 1,\n          column: impl.range.start.character,\n        }));\n      }\n    } catch {\n      // LSP request failed - return empty array\n    }\n\n    return [];\n  }\n\n  private parseHoverResult(contents: unknown): TypeInfo {\n    let text = '';\n\n    if (typeof contents === 'string') {\n      text = contents;\n    } else if (Array.isArray(contents)) {\n      text = contents\n        .map((c) => (typeof c === 'string' ? c : (c as { value?: string }).value || ''))\n        .join('\\n');\n    } else if (contents && typeof contents === 'object') {\n      text = (contents as { value?: string }).value || '';\n    }\n\n    // Extract type from markdown code blocks\n    const codeMatch = text.match(/```\\w*\\n?([\\s\\S]*?)\\n?```/);\n    const typeText = codeMatch ? codeMatch[1] : text;\n\n    return {\n      name: typeText.split('\\n')[0] || 'unknown',\n      fullType: typeText,\n      documentation: text.replace(/```[\\s\\S]*?```/g, '').trim() || undefined,\n    };\n  }\n\n  async shutdown(): Promise<void> {\n    const shutdownPromises: Promise<void>[] = [];\n\n    for (const [language] of this.servers) {\n      shutdownPromises.push(\n        (async () => {\n          try {\n            await this.sendRequest(language, 'shutdown', null);\n            this.sendNotification(language, 'exit', null);\n          } catch {\n            // Ignore shutdown errors\n          }\n          this.cleanup(language);\n        })()\n      );\n    }\n\n    await Promise.all(shutdownPromises);\n  }\n\n  isServerAvailable(language: string): boolean {\n    return this.initialized.get(language) === true;\n  }\n\n  getAvailableLanguages(): string[] {\n    return Object.keys(LSP_SERVER_CONFIGS);\n  }\n}\n"
  },
  {
    "path": "src/services/semantic/semanticSnapshotService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { FileMapper } from '../../utils/fileMapper';\nimport { SemanticSnapshotService } from './semanticSnapshotService';\n\ndescribe('SemanticSnapshotService', () => {\n  let tempDir: string;\n  let repoPath: string;\n  let outputDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-semantic-snapshot-'));\n    repoPath = path.join(tempDir, 'repo');\n    outputDir = path.join(repoPath, '.context');\n\n    await fs.ensureDir(path.join(repoPath, 'src'));\n    await fs.writeJson(path.join(repoPath, 'package.json'), {\n      name: 'snapshot-test',\n      version: '1.0.0',\n      main: 'dist/index.js',\n      types: 'dist/index.d.ts',\n    }, { spaces: 2 });\n    await fs.writeFile(path.join(repoPath, 'src', 'index.ts'), 'export const run = () => true;\\n', 'utf-8');\n    await fs.writeFile(path.join(repoPath, 'src', 'auth.ts'), 'export const login = () => \"ok\";\\n', 'utf-8');\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('writes a symbol-free semantic snapshot and current summary into the semantic cache', async () => {\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n    await fs.ensureDir(path.join(outputDir, 'docs'));\n    await fs.writeJson(path.join(outputDir, 'docs', 'codebase-map.json'), { legacy: true }, { spaces: 2 });\n\n    const result = await service.writeSnapshot(repoStructure, { outputDir });\n    const manifestPath = path.join(outputDir, 'cache', 'semantic', 'manifest.json');\n    const manifest = await fs.readJson(manifestPath);\n\n    expect(result.summary.functionalPatterns.hasAuthPattern).toBe(true);\n    expect(result.summary).not.toHaveProperty('symbols');\n    expect(result.summary).not.toHaveProperty('publicAPI');\n\n    const snapshotDir = path.join(outputDir, 'cache', 'semantic');\n    expect(await fs.pathExists(manifestPath)).toBe(true);\n    expect(await fs.pathExists(path.join(snapshotDir, 'summary.json'))).toBe(true);\n    expect(await fs.pathExists(path.join(snapshotDir, manifest.sections.functionalPatterns))).toBe(true);\n    expect(await fs.pathExists(path.join(snapshotDir, manifest.sections.summary))).toBe(true);\n    expect(await fs.pathExists(path.join(outputDir, 'docs', 'codebase-map.json'))).toBe(false);\n    expect(manifest.publishedSummary).toBe(path.posix.join('cache', 'semantic', 'summary.json'));\n\n    const publishedSummary = await fs.readJson(path.join(snapshotDir, 'summary.json'));\n    expect(publishedSummary).not.toHaveProperty('symbols');\n    expect(publishedSummary).not.toHaveProperty('publicAPI');\n    expect(publishedSummary.meta.analyzer.includesSymbolPayload).toBe(false);\n  });\n\n  it('auto-builds a missing snapshot on demand', async () => {\n    const service = new SemanticSnapshotService();\n\n    const result = await service.ensureFreshSummary(repoPath, { outputDir });\n\n    expect(result.refreshed).toBe(true);\n    expect(result.refreshReason).toBe('missing');\n    expect(result.fresh).toBe(true);\n    expect(result.summary.functionalPatterns.hasAuthPattern).toBe(true);\n    expect(await fs.pathExists(path.join(outputDir, 'cache', 'semantic', 'manifest.json'))).toBe(true);\n  });\n\n  it('invalidates the snapshot when hidden config files change', async () => {\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n\n    await fs.ensureDir(path.join(repoPath, '.github', 'workflows'));\n    await fs.writeFile(path.join(repoPath, '.github', 'workflows', 'ci.yml'), 'name: ci\\n', 'utf-8');\n\n    await service.writeSnapshot(repoStructure, { outputDir });\n    const before = await service.readSummary(repoPath, { allowStale: false });\n    expect(before).not.toBeNull();\n\n    await new Promise((resolve) => setTimeout(resolve, 20));\n    await fs.writeFile(path.join(repoPath, '.github', 'workflows', 'ci.yml'), 'name: changed\\n', 'utf-8');\n\n    const after = await service.readSummary(repoPath, { allowStale: false });\n    expect(after).toBeNull();\n  });\n\n  it('invalidates the snapshot when file contents change without size or mtime changes', async () => {\n    await fs.writeFile(path.join(repoPath, 'src', 'index.ts'), 'export const run = () => 1;\\n', 'utf-8');\n\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n\n    await service.writeSnapshot(repoStructure, { outputDir });\n    const targetPath = path.join(repoPath, 'src', 'index.ts');\n    const originalStats = await fs.stat(targetPath);\n\n    await fs.writeFile(targetPath, 'export const run = () => 2;\\n', 'utf-8');\n    await fs.utimes(targetPath, originalStats.atime, originalStats.mtime);\n\n    const stale = await service.readSummary(repoPath, { outputDir, allowStale: false });\n    expect(stale).toBeNull();\n  });\n\n  it('auto-refreshes a stale snapshot on demand', async () => {\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n\n    const initial = await service.writeSnapshot(repoStructure, { outputDir });\n    await new Promise((resolve) => setTimeout(resolve, 20));\n    await fs.writeFile(path.join(repoPath, 'src', 'index.ts'), 'export const run = () => false;\\n', 'utf-8');\n\n    const refreshed = await service.ensureFreshSummary(repoPath, { outputDir });\n\n    expect(refreshed.refreshed).toBe(true);\n    expect(refreshed.refreshReason).toBe('stale');\n    expect(refreshed.fresh).toBe(true);\n    expect(refreshed.manifest?.repoFingerprint).not.toBe(initial.manifest.repoFingerprint);\n  });\n\n  it('deduplicates concurrent refreshes for the same repo', async () => {\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n    await service.writeSnapshot(repoStructure, { outputDir });\n    await new Promise((resolve) => setTimeout(resolve, 20));\n    await fs.writeFile(path.join(repoPath, 'src', 'index.ts'), 'export const run = () => false;\\n', 'utf-8');\n\n    const originalBuildSnapshotArtifacts = (service as any).buildSnapshotArtifacts.bind(service);\n    const buildSpy = jest.spyOn(service as any, 'buildSnapshotArtifacts').mockImplementation(async (...args) => {\n      await new Promise((resolve) => setTimeout(resolve, 25));\n      return originalBuildSnapshotArtifacts(...args);\n    });\n\n    const [first, second] = await Promise.all([\n      service.ensureFreshSummary(repoPath, { outputDir }),\n      service.ensureFreshSummary(repoPath, { outputDir }),\n    ]);\n\n    expect(buildSpy).toHaveBeenCalledTimes(1);\n    expect(first.refreshReason).toBe('stale');\n    expect(second.refreshReason).toBe('stale');\n    expect(first.manifest?.repoFingerprint).toBe(second.manifest?.repoFingerprint);\n  });\n\n  it('retries refresh until the repo state is stable', async () => {\n    await fs.remove(path.join(repoPath, 'src', 'auth.ts'));\n\n    const service = new SemanticSnapshotService();\n    let mutationInjected = false;\n    const originalBuildSnapshotArtifacts = (service as any).buildSnapshotArtifacts.bind(service);\n    const buildSpy = jest.spyOn(service as any, 'buildSnapshotArtifacts').mockImplementation(async (...args) => {\n      if (!mutationInjected) {\n        mutationInjected = true;\n        await fs.writeFile(path.join(repoPath, 'src', 'auth.ts'), 'export const login = () => \"ok\";\\n', 'utf-8');\n      }\n\n      return originalBuildSnapshotArtifacts(...args);\n    });\n\n    const refreshed = await service.ensureFreshSummary(repoPath, { outputDir });\n\n    expect(buildSpy).toHaveBeenCalledTimes(2);\n    expect(refreshed.summary.functionalPatterns.hasAuthPattern).toBe(true);\n    expect(refreshed.refreshReason).toBe('missing');\n  });\n\n  it('keeps the previous snapshot readable until the new manifest is promoted', async () => {\n    const mapper = new FileMapper();\n    const repoStructure = await mapper.mapRepository(repoPath);\n    const service = new SemanticSnapshotService();\n    const reader = new SemanticSnapshotService();\n\n    const initial = await service.writeSnapshot(repoStructure, { outputDir });\n    await new Promise((resolve) => setTimeout(resolve, 20));\n    await fs.writeFile(path.join(repoPath, 'src', 'index.ts'), 'export const run = () => false;\\n', 'utf-8');\n\n    let observedDuringPublish: { manifest?: { generatedAt?: string } } | null = null;\n    const originalPromoteFile = (service as any).promoteFile.bind(service);\n    jest.spyOn(service as any, 'promoteFile').mockImplementation(async (...args: unknown[]) => {\n      const [sourcePath, targetPath] = args as [string, string];\n      if (targetPath === path.join(outputDir, 'cache', 'semantic', 'manifest.json')) {\n        observedDuringPublish = await reader.readSummary(repoPath, { outputDir, allowStale: true });\n      }\n\n      return originalPromoteFile(sourcePath, targetPath);\n    });\n\n    await service.ensureFreshSummary(repoPath, { outputDir });\n\n    expect(observedDuringPublish).not.toBeNull();\n    const observedGeneratedAt =\n      (observedDuringPublish as { manifest?: { generatedAt?: string } } | null)?.manifest?.generatedAt ?? null;\n    expect(observedGeneratedAt).toBe(initial.manifest.generatedAt);\n  });\n\n  it('does not load a legacy docs/codebase-map.json without a snapshot manifest', async () => {\n    const service = new SemanticSnapshotService();\n    await fs.ensureDir(path.join(outputDir, 'docs'));\n    await fs.writeJson(path.join(outputDir, 'docs', 'codebase-map.json'), {\n      version: '1.0.0',\n      generated: new Date().toISOString(),\n    }, { spaces: 2 });\n\n    const result = await service.readSummary(repoPath, { allowStale: true });\n    expect(result).toBeNull();\n  });\n});\n"
  },
  {
    "path": "src/services/semantic/semanticSnapshotService.ts",
    "content": "import { createHash } from 'crypto';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport * as path from 'path';\n\nimport { RepoStructure } from '../../types';\nimport { CodebaseMapGenerator } from '../../generators/documentation/codebaseMapGenerator';\nimport type {\n  CodebaseMap,\n  SemanticSnapshotMetadata,\n} from '../../generators/documentation/codebaseMapGenerator';\nimport { FileMapper } from '../../utils/fileMapper';\nimport { CodebaseAnalyzer } from './codebaseAnalyzer';\nimport type { AnalyzerOptions, DetectedFunctionalPatterns, SemanticContext } from './types';\nimport { StackDetector } from '../stack/stackDetector';\nimport type { StackInfo } from '../stack/stackDetector';\n\nexport type SemanticSnapshotSection =\n  | 'all'\n  | 'meta'\n  | 'stack'\n  | 'structure'\n  | 'architecture'\n  | 'functionalPatterns'\n  | 'dependencies'\n  | 'stats'\n  | 'keyFiles'\n  | 'navigation';\n\ntype SnapshotFileSection = Exclude<SemanticSnapshotSection, 'all' | 'meta'>;\n\nexport interface SemanticSnapshotManifest extends SemanticSnapshotMetadata {\n  sections: Record<SnapshotFileSection | 'summary', string>;\n  publishedSummary: string;\n}\n\nexport interface SemanticSnapshotWriteOptions {\n  outputDir?: string;\n  semantics?: SemanticContext;\n  stackInfo?: StackInfo;\n  functionalPatterns?: DetectedFunctionalPatterns;\n  analyzerOptions?: AnalyzerOptions;\n  repoFingerprint?: string;\n}\n\nexport interface SemanticSnapshotWriteResult {\n  summary: CodebaseMap;\n  manifest: SemanticSnapshotManifest;\n  snapshotDir: string;\n  publishedSummaryPath: string;\n}\n\nexport interface SemanticSnapshotReadOptions {\n  outputDir?: string;\n  allowStale?: boolean;\n}\n\nexport interface SemanticSnapshotSectionResult {\n  data: unknown;\n  fresh: boolean;\n  source: 'snapshot';\n  path: string;\n  manifest?: SemanticSnapshotManifest;\n}\n\nexport type SemanticSnapshotRefreshReason = 'fresh' | 'stale' | 'missing';\n\nexport interface SemanticSnapshotEnsureSummaryResult {\n  summary: CodebaseMap;\n  fresh: true;\n  source: 'snapshot';\n  path: string;\n  manifest?: SemanticSnapshotManifest;\n  refreshed: boolean;\n  refreshReason: SemanticSnapshotRefreshReason;\n}\n\nexport interface SemanticSnapshotEnsureSectionResult extends SemanticSnapshotSectionResult {\n  fresh: true;\n  refreshed: boolean;\n  refreshReason: SemanticSnapshotRefreshReason;\n}\n\ninterface SnapshotArtifacts {\n  summary: CodebaseMap;\n  manifest: SemanticSnapshotManifest;\n}\n\nconst SNAPSHOT_SCHEMA_VERSION = '2.0.0';\nconst SNAPSHOT_DIRNAME = path.join('cache', 'semantic');\nconst MANIFEST_FILENAME = 'manifest.json';\nconst SUMMARY_FILENAME = 'summary.json';\nconst VERSIONS_DIRNAME = 'versions';\nconst MAX_REFRESH_ATTEMPTS = 3;\nconst MAX_VERSION_HISTORY = 3;\n\nconst SECTION_FILENAMES: Record<SnapshotFileSection, string> = {\n  stack: 'stack.json',\n  structure: 'structure.json',\n  architecture: 'architecture.json',\n  functionalPatterns: 'functional-patterns.json',\n  dependencies: 'dependencies.json',\n  stats: 'stats.json',\n  keyFiles: 'key-files.json',\n  navigation: 'navigation.json',\n};\n\nconst LEGACY_CODEBASE_MAP_PATH = path.join('docs', 'codebase-map.json');\n\nconst FINGERPRINT_IGNORE_PATTERNS = [\n  'node_modules/**',\n  '.git/**',\n  'dist/**',\n  'build/**',\n  'coverage/**',\n  '.context/**',\n  'vendor/**',\n  '__pycache__/**',\n];\n\nconst FINGERPRINT_ROOT_FILES = new Set([\n  'package.json',\n  'package-lock.json',\n  'pnpm-lock.yaml',\n  'yarn.lock',\n  'bun.lockb',\n  'tsconfig.json',\n  'tsconfig.build.json',\n  'jest.config.js',\n  'jest.config.ts',\n  'vitest.config.ts',\n  'vite.config.ts',\n  'webpack.config.js',\n  'next.config.js',\n  'next.config.ts',\n  'nest-cli.json',\n  '.eslintrc',\n  '.eslintrc.js',\n  '.eslintrc.json',\n  '.prettierrc',\n  '.prettierrc.json',\n  '.nvmrc',\n  '.node-version',\n]);\n\nconst FINGERPRINT_CODE_EXTENSIONS = new Set([\n  '.ts',\n  '.tsx',\n  '.js',\n  '.jsx',\n  '.mjs',\n  '.cjs',\n  '.py',\n  '.go',\n  '.json',\n  '.yaml',\n  '.yml',\n  '.toml',\n]);\n\nexport class SemanticSnapshotService {\n  private static readonly inFlightRefreshes = new Map<string, Promise<SemanticSnapshotWriteResult>>();\n\n  async captureRepoFingerprint(repoPath: string): Promise<string> {\n    return this.computeRepoFingerprint(repoPath);\n  }\n\n  async writeSnapshot(\n    repoStructure: RepoStructure,\n    options: SemanticSnapshotWriteOptions = {}\n  ): Promise<SemanticSnapshotWriteResult> {\n    const outputDir = this.resolveOutputDir(repoStructure.rootPath, options.outputDir);\n    const snapshotDir = this.getSnapshotDir(outputDir);\n    const publishedSummaryPath = path.join(snapshotDir, SUMMARY_FILENAME);\n\n    const repoFingerprint =\n      options.repoFingerprint ?? await this.computeRepoFingerprint(repoStructure.rootPath);\n\n    const artifacts = await this.buildSnapshotArtifacts(repoStructure, {\n      ...options,\n      repoFingerprint,\n    });\n    const manifest = await this.publishSnapshotArtifacts({\n      outputDir,\n      snapshotDir,\n      publishedSummaryPath,\n      artifacts,\n    });\n\n    return {\n      summary: artifacts.summary,\n      manifest,\n      snapshotDir,\n      publishedSummaryPath,\n    };\n  }\n\n  async ensureFreshSummary(\n    repoPath: string,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<SemanticSnapshotEnsureSummaryResult> {\n    const current = await this.inspectSummary(repoPath, options);\n    if (current?.fresh) {\n      return {\n        summary: current.summary,\n        fresh: true,\n        source: 'snapshot',\n        path: current.path,\n        manifest: current.manifest,\n        refreshed: false,\n        refreshReason: 'fresh',\n      };\n    }\n\n    const refreshed = await this.refreshSnapshot(repoPath, options);\n    return {\n      summary: refreshed.summary,\n      fresh: true,\n      source: 'snapshot',\n      path: path.join(refreshed.snapshotDir, refreshed.manifest.sections.summary),\n      manifest: refreshed.manifest,\n      refreshed: true,\n      refreshReason: current ? 'stale' : 'missing',\n    };\n  }\n\n  async ensureFreshSection(\n    repoPath: string,\n    section: SemanticSnapshotSection,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<SemanticSnapshotEnsureSectionResult> {\n    const current = await this.inspectSection(repoPath, section, options);\n    if (current?.fresh) {\n      return {\n        ...current,\n        fresh: true,\n        refreshed: false,\n        refreshReason: 'fresh',\n      };\n    }\n\n    const refreshed = await this.refreshSnapshot(repoPath, options);\n    const result = this.buildSectionResult(refreshed, section);\n    return {\n      ...result,\n      fresh: true,\n      refreshed: true,\n      refreshReason: current ? 'stale' : 'missing',\n    };\n  }\n\n  async readSummary(\n    repoPath: string,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<{\n    summary: CodebaseMap;\n    fresh: boolean;\n    source: 'snapshot';\n    path: string;\n    manifest?: SemanticSnapshotManifest;\n  } | null> {\n    const current = await this.inspectSummary(repoPath, options);\n    if (!current) {\n      return null;\n    }\n\n    if (options.allowStale === false && !current.fresh) {\n      return null;\n    }\n\n    return current;\n  }\n\n  async readSection(\n    repoPath: string,\n    section: SemanticSnapshotSection,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<SemanticSnapshotSectionResult | null> {\n    const current = await this.inspectSection(repoPath, section, options);\n    if (!current) {\n      return null;\n    }\n\n    if (options.allowStale === false && !current.fresh) {\n      return null;\n    }\n\n    return current;\n  }\n\n  private async refreshSnapshot(\n    repoPath: string,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<SemanticSnapshotWriteResult> {\n    const outputDir = this.resolveOutputDir(repoPath, options.outputDir);\n    const refreshKey = `${path.resolve(repoPath).toLowerCase()}::${path.resolve(outputDir).toLowerCase()}`;\n    const existing = SemanticSnapshotService.inFlightRefreshes.get(refreshKey);\n    if (existing) {\n      return existing;\n    }\n\n    const refreshPromise = (async () => {\n      const fileMapper = new FileMapper();\n      const snapshotDir = this.getSnapshotDir(outputDir);\n      const publishedSummaryPath = path.join(snapshotDir, SUMMARY_FILENAME);\n\n      for (let attempt = 1; attempt <= MAX_REFRESH_ATTEMPTS; attempt += 1) {\n        const repoFingerprint = await this.computeRepoFingerprint(repoPath);\n        const repoStructure = await fileMapper.mapRepository(repoPath);\n        const artifacts = await this.buildSnapshotArtifacts(repoStructure, {\n          outputDir,\n          repoFingerprint,\n        });\n        const currentFingerprint = await this.computeRepoFingerprint(repoPath);\n\n        if (currentFingerprint !== repoFingerprint) {\n          continue;\n        }\n\n        const manifest = await this.publishSnapshotArtifacts({\n          outputDir,\n          snapshotDir,\n          publishedSummaryPath,\n          artifacts,\n        });\n        const publishedFingerprint = await this.computeRepoFingerprint(repoPath);\n\n        if (publishedFingerprint !== repoFingerprint) {\n          continue;\n        }\n\n        return {\n          summary: artifacts.summary,\n          manifest,\n          snapshotDir,\n          publishedSummaryPath,\n        };\n      }\n\n      throw new Error(\n        `Semantic snapshot refresh could not stabilize for ${repoPath}; repository changed during refresh.`\n      );\n    })();\n\n    SemanticSnapshotService.inFlightRefreshes.set(refreshKey, refreshPromise);\n\n    try {\n      return await refreshPromise;\n    } finally {\n      SemanticSnapshotService.inFlightRefreshes.delete(refreshKey);\n    }\n  }\n\n  private async inspectSummary(\n    repoPath: string,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<{\n    summary: CodebaseMap;\n    fresh: boolean;\n    source: 'snapshot';\n    path: string;\n    manifest?: SemanticSnapshotManifest;\n  } | null> {\n    const outputDir = this.resolveOutputDir(repoPath, options.outputDir);\n    const snapshotDir = this.getSnapshotDir(outputDir);\n    const manifestPath = path.join(snapshotDir, MANIFEST_FILENAME);\n\n    if (!(await fs.pathExists(manifestPath))) {\n      return null;\n    }\n\n    const manifest = await fs.readJson(manifestPath) as SemanticSnapshotManifest;\n    const summaryPath = path.join(snapshotDir, manifest.sections.summary);\n    if (!(await fs.pathExists(summaryPath))) {\n      return null;\n    }\n\n    const fresh = await this.isFresh(repoPath, manifest.repoFingerprint);\n    if (options.allowStale === false && !fresh) {\n      return null;\n    }\n\n    const summary = await fs.readJson(summaryPath) as CodebaseMap;\n    return {\n      summary,\n      fresh,\n      source: 'snapshot',\n      path: summaryPath,\n      manifest,\n    };\n  }\n\n  private async inspectSection(\n    repoPath: string,\n    section: SemanticSnapshotSection,\n    options: SemanticSnapshotReadOptions = {}\n  ): Promise<SemanticSnapshotSectionResult | null> {\n    const outputDir = this.resolveOutputDir(repoPath, options.outputDir);\n    const snapshotDir = this.getSnapshotDir(outputDir);\n    const manifestPath = path.join(snapshotDir, MANIFEST_FILENAME);\n\n    if (!(await fs.pathExists(manifestPath))) {\n      return null;\n    }\n\n    const manifest = await fs.readJson(manifestPath) as SemanticSnapshotManifest;\n    const fresh = await this.isFresh(repoPath, manifest.repoFingerprint);\n    if (options.allowStale === false && !fresh) {\n      return null;\n    }\n\n    return await this.buildSectionResultFromManifest(snapshotDir, manifest, section, fresh);\n  }\n\n  private async buildSnapshotArtifacts(\n    repoStructure: RepoStructure,\n    options: SemanticSnapshotWriteOptions = {}\n  ): Promise<SnapshotArtifacts> {\n    let analyzer: CodebaseAnalyzer | null = null;\n    let semantics = options.semantics;\n    let functionalPatterns = options.functionalPatterns;\n    let stackInfo = options.stackInfo;\n\n    try {\n      if (!semantics || !functionalPatterns) {\n        analyzer = new CodebaseAnalyzer(options.analyzerOptions);\n      }\n\n      if (!semantics) {\n        semantics = await analyzer!.analyze(repoStructure.rootPath);\n      }\n\n      if (!functionalPatterns) {\n        functionalPatterns = await analyzer!.detectFunctionalPatterns(repoStructure.rootPath);\n      }\n\n      if (!stackInfo) {\n        const stackDetector = new StackDetector();\n        stackInfo = await stackDetector.detect(repoStructure.rootPath);\n      }\n    } finally {\n      if (analyzer) {\n        await analyzer.shutdown();\n      }\n    }\n\n    const metadata: SemanticSnapshotMetadata = {\n      schemaVersion: SNAPSHOT_SCHEMA_VERSION,\n      generatedAt: new Date().toISOString(),\n      repoFingerprint:\n        options.repoFingerprint ?? await this.computeRepoFingerprint(repoStructure.rootPath),\n      analyzer: {\n        useLSP: !!options.analyzerOptions?.useLSP,\n        includesSymbolPayload: false,\n      },\n    };\n\n    const generator = new CodebaseMapGenerator();\n    const summary = generator.generate(\n      repoStructure,\n      semantics,\n      stackInfo,\n      functionalPatterns,\n      metadata\n    );\n\n    const manifest: SemanticSnapshotManifest = {\n      ...metadata,\n      sections: {\n        summary: SUMMARY_FILENAME,\n        stack: SECTION_FILENAMES.stack,\n        structure: SECTION_FILENAMES.structure,\n        architecture: SECTION_FILENAMES.architecture,\n        functionalPatterns: SECTION_FILENAMES.functionalPatterns,\n        dependencies: SECTION_FILENAMES.dependencies,\n        stats: SECTION_FILENAMES.stats,\n        keyFiles: SECTION_FILENAMES.keyFiles,\n        navigation: SECTION_FILENAMES.navigation,\n      },\n      publishedSummary: path.join(SNAPSHOT_DIRNAME, SUMMARY_FILENAME),\n    };\n\n    return { summary, manifest };\n  }\n\n  private async publishSnapshotArtifacts(params: {\n    outputDir: string;\n    snapshotDir: string;\n    publishedSummaryPath: string;\n    artifacts: SnapshotArtifacts;\n  }): Promise<SemanticSnapshotManifest> {\n    const { outputDir, snapshotDir, publishedSummaryPath, artifacts } = params;\n    const tempSummaryPath = `${publishedSummaryPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n    const tempManifestPath = path.join(\n      snapshotDir,\n      `${MANIFEST_FILENAME}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`\n    );\n    const versionId = this.createVersionId();\n    const versionDir = path.join(snapshotDir, VERSIONS_DIRNAME, versionId);\n    const versionPrefix = path.posix.join(VERSIONS_DIRNAME, versionId);\n    const manifest = this.buildPublishedManifest(artifacts.manifest, versionPrefix);\n\n    await fs.ensureDir(outputDir);\n    await fs.ensureDir(snapshotDir);\n    await fs.ensureDir(path.dirname(versionDir));\n    await fs.remove(versionDir);\n    await fs.ensureDir(versionDir);\n\n    try {\n      const sectionData = this.getSectionData(artifacts.summary);\n      await fs.writeJson(path.join(versionDir, SUMMARY_FILENAME), artifacts.summary, { spaces: 2 });\n      await Promise.all(\n        Object.entries(SECTION_FILENAMES).map(([section, filename]) =>\n          fs.writeJson(path.join(versionDir, filename), sectionData[section as SnapshotFileSection], {\n            spaces: 2,\n          })\n        )\n      );\n      await fs.writeJson(tempSummaryPath, artifacts.summary, { spaces: 2 });\n      await fs.writeJson(tempManifestPath, manifest, { spaces: 2 });\n\n      await this.promoteFile(tempSummaryPath, publishedSummaryPath);\n      await this.promoteFile(tempManifestPath, path.join(snapshotDir, MANIFEST_FILENAME));\n      await fs.remove(path.join(outputDir, LEGACY_CODEBASE_MAP_PATH));\n    } catch (error) {\n      await fs.remove(tempSummaryPath);\n      await fs.remove(tempManifestPath);\n      await fs.remove(versionDir);\n      throw error;\n    }\n\n    void this.pruneSnapshotVersions(snapshotDir).catch(() => {\n      // Snapshot version pruning is best-effort; stale versions are harmless.\n    });\n\n    return manifest;\n  }\n\n  private buildSectionResult(\n    snapshot: SemanticSnapshotWriteResult,\n    section: SemanticSnapshotSection\n  ): SemanticSnapshotSectionResult {\n    if (section === 'meta') {\n      return {\n        data: snapshot.manifest,\n        fresh: true,\n        source: 'snapshot',\n        path: path.join(snapshot.snapshotDir, MANIFEST_FILENAME),\n        manifest: snapshot.manifest,\n      };\n    }\n\n    const relativeFile = section === 'all'\n      ? snapshot.manifest.sections.summary\n      : snapshot.manifest.sections[section as SnapshotFileSection];\n\n    return {\n      data: this.extractSectionData(snapshot.summary, section),\n      fresh: true,\n      source: 'snapshot',\n      path: path.join(snapshot.snapshotDir, relativeFile),\n      manifest: snapshot.manifest,\n    };\n  }\n\n  private async buildSectionResultFromManifest(\n    snapshotDir: string,\n    manifest: SemanticSnapshotManifest,\n    section: SemanticSnapshotSection,\n    fresh: boolean\n  ): Promise<SemanticSnapshotSectionResult | null> {\n    if (section === 'meta') {\n      return {\n        data: manifest,\n        fresh,\n        source: 'snapshot',\n        path: path.join(snapshotDir, MANIFEST_FILENAME),\n        manifest,\n      };\n    }\n\n    const relativeFile = section === 'all'\n      ? manifest.sections.summary\n      : manifest.sections[section as SnapshotFileSection];\n    const sectionPath = path.join(snapshotDir, relativeFile);\n\n    if (!(await fs.pathExists(sectionPath))) {\n      return null;\n    }\n\n    return {\n      data: await fs.readJson(sectionPath),\n      fresh,\n      source: 'snapshot',\n      path: sectionPath,\n      manifest,\n    };\n  }\n\n  private extractSectionData(map: CodebaseMap, section: SemanticSnapshotSection): unknown {\n    switch (section) {\n      case 'all':\n        return map;\n      case 'meta':\n        return map.meta ?? null;\n      case 'stack':\n        return map.stack;\n      case 'structure':\n        return map.structure;\n      case 'architecture':\n        return map.architecture;\n      case 'functionalPatterns':\n        return map.functionalPatterns;\n      case 'dependencies':\n        return map.dependencies;\n      case 'stats':\n        return map.stats;\n      case 'keyFiles':\n        return map.keyFiles ?? [];\n      case 'navigation':\n        return map.navigation ?? {};\n    }\n  }\n\n  private getSectionData(summary: CodebaseMap): Record<SnapshotFileSection, unknown> {\n    return {\n      stack: summary.stack,\n      structure: summary.structure,\n      architecture: summary.architecture,\n      functionalPatterns: summary.functionalPatterns,\n      dependencies: summary.dependencies,\n      stats: summary.stats,\n      keyFiles: summary.keyFiles ?? [],\n      navigation: summary.navigation ?? {},\n    };\n  }\n\n  private async replaceFile(targetPath: string, sourcePath: string): Promise<void> {\n    const backupPath = `${targetPath}.bak-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n    const targetExists = await fs.pathExists(targetPath);\n\n    if (targetExists) {\n      await fs.rename(targetPath, backupPath);\n    }\n\n    try {\n      await fs.rename(sourcePath, targetPath);\n    } catch (error) {\n      if (targetExists && await fs.pathExists(backupPath)) {\n        await fs.rename(backupPath, targetPath);\n      }\n      throw error;\n    }\n\n    if (targetExists) {\n      await fs.remove(backupPath);\n    }\n  }\n\n  private async promoteFile(sourcePath: string, targetPath: string): Promise<void> {\n    try {\n      await fs.rename(sourcePath, targetPath);\n    } catch {\n      await this.replaceFile(targetPath, sourcePath);\n    }\n  }\n\n  private getSnapshotDir(outputDir: string): string {\n    return path.join(outputDir, SNAPSHOT_DIRNAME);\n  }\n\n  private buildPublishedManifest(\n    manifest: SemanticSnapshotManifest,\n    versionPrefix: string\n  ): SemanticSnapshotManifest {\n    return {\n      ...manifest,\n      publishedSummary: path.posix.join(SNAPSHOT_DIRNAME, SUMMARY_FILENAME),\n      sections: {\n        summary: path.posix.join(versionPrefix, SUMMARY_FILENAME),\n        stack: path.posix.join(versionPrefix, SECTION_FILENAMES.stack),\n        structure: path.posix.join(versionPrefix, SECTION_FILENAMES.structure),\n        architecture: path.posix.join(versionPrefix, SECTION_FILENAMES.architecture),\n        functionalPatterns: path.posix.join(versionPrefix, SECTION_FILENAMES.functionalPatterns),\n        dependencies: path.posix.join(versionPrefix, SECTION_FILENAMES.dependencies),\n        stats: path.posix.join(versionPrefix, SECTION_FILENAMES.stats),\n        keyFiles: path.posix.join(versionPrefix, SECTION_FILENAMES.keyFiles),\n        navigation: path.posix.join(versionPrefix, SECTION_FILENAMES.navigation),\n      },\n    };\n  }\n\n  private createVersionId(): string {\n    return `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`;\n  }\n\n  private async pruneSnapshotVersions(snapshotDir: string): Promise<void> {\n    const versionsDir = path.join(snapshotDir, VERSIONS_DIRNAME);\n    if (!(await fs.pathExists(versionsDir))) {\n      return;\n    }\n\n    const entries = await fs.readdir(versionsDir);\n    const versions = await Promise.all(\n      entries.map(async (entry) => {\n        const absolutePath = path.join(versionsDir, entry);\n        const stats = await fs.stat(absolutePath);\n        return stats.isDirectory()\n          ? { entry, absolutePath, mtimeMs: stats.mtimeMs }\n          : null;\n      })\n    );\n\n    const staleVersions = versions\n      .filter((entry): entry is { entry: string; absolutePath: string; mtimeMs: number } => entry !== null)\n      .sort((left, right) => right.mtimeMs - left.mtimeMs)\n      .slice(MAX_VERSION_HISTORY);\n\n    await Promise.all(staleVersions.map((entry) => fs.remove(entry.absolutePath)));\n  }\n\n  private resolveOutputDir(repoPath: string, outputDir?: string): string {\n    return outputDir\n      ? path.resolve(outputDir)\n      : path.resolve(repoPath, '.context');\n  }\n\n  private async isFresh(repoPath: string, expectedFingerprint: string): Promise<boolean> {\n    return expectedFingerprint === await this.computeRepoFingerprint(repoPath);\n  }\n\n  private async computeRepoFingerprint(repoPath: string): Promise<string> {\n    const files = await glob('**/*', {\n      cwd: repoPath,\n      nodir: true,\n      dot: true,\n      ignore: FINGERPRINT_IGNORE_PATTERNS,\n    });\n\n    const relevantFiles = files\n      .filter((filePath) => this.isFingerprintRelevant(filePath))\n      .sort();\n\n    const hash = createHash('sha1');\n    for (const relativePath of relevantFiles) {\n      const absolutePath = path.join(repoPath, relativePath);\n      try {\n        hash.update(`${relativePath}\\0`);\n        hash.update(await fs.readFile(absolutePath));\n        hash.update('\\0');\n      } catch {\n        hash.update(`${relativePath}:missing\\n`);\n      }\n    }\n\n    return hash.digest('hex');\n  }\n\n  private isFingerprintRelevant(relativePath: string): boolean {\n    if (FINGERPRINT_ROOT_FILES.has(relativePath)) {\n      return true;\n    }\n\n    const topLevel = relativePath.split('/')[0];\n    if (['src', 'lib', 'bin', 'app', 'packages', 'scripts'].includes(topLevel)) {\n      return true;\n    }\n\n    return FINGERPRINT_CODE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());\n  }\n}\n"
  },
  {
    "path": "src/services/semantic/treeSitter/index.ts",
    "content": "export { TreeSitterLayer } from './treeSitterLayer';\n"
  },
  {
    "path": "src/services/semantic/treeSitter/treeSitterLayer.ts",
    "content": "/**\n * Tree-sitter based code analysis layer\n *\n * Provides fast syntactic analysis using tree-sitter parsers.\n * Falls back to regex-based extraction if tree-sitter is not available.\n */\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\nimport {\n  ExtractedSymbol,\n  ImportInfo,\n  ExportInfo,\n  FileAnalysis,\n  LANGUAGE_EXTENSIONS,\n  SupportedLanguage,\n} from '../types';\n\ninterface CacheEntry {\n  mtime: number;\n  analysis: FileAnalysis;\n}\n\nexport class TreeSitterLayer {\n  private cache: Map<string, CacheEntry> = new Map();\n  private treeSitterAvailable: boolean = false;\n  private parsers: Map<string, any> = new Map();\n  public readonly ready: Promise<void>;\n\n  constructor() {\n    this.ready = this.initializeParsers();\n  }\n\n  private async initializeParsers(): Promise<void> {\n    try {\n      // Try to load tree-sitter dynamically\n      const Parser = await import('tree-sitter').catch(() => null);\n      if (!Parser) {\n        this.treeSitterAvailable = false;\n        return;\n      }\n\n      // Try to load language parsers\n      const tsParser = await import('tree-sitter-typescript').catch(() => null);\n      if (tsParser && Parser.default) {\n        const parser = new Parser.default();\n        parser.setLanguage(tsParser.typescript);\n        this.parsers.set('typescript', parser);\n        this.parsers.set('javascript', parser);\n        this.treeSitterAvailable = true;\n      }\n    } catch {\n      this.treeSitterAvailable = false;\n    }\n  }\n\n  async analyzeFile(filePath: string): Promise<FileAnalysis> {\n    // Ensure initialization is complete before checking treeSitterAvailable\n    await this.ready;\n\n    const ext = path.extname(filePath);\n    const language = LANGUAGE_EXTENSIONS[ext];\n\n    if (!language) {\n      return this.emptyAnalysis(filePath, 'unknown');\n    }\n\n    try {\n      const stat = await fs.stat(filePath);\n      const mtime = stat.mtimeMs;\n\n      // Check cache\n      const cached = this.cache.get(filePath);\n      if (cached && cached.mtime === mtime) {\n        return cached.analysis;\n      }\n\n      const content = await fs.readFile(filePath, 'utf-8');\n      let analysis: FileAnalysis;\n\n      if (this.treeSitterAvailable && this.parsers.has(language)) {\n        analysis = this.analyzeWithTreeSitter(filePath, content, language);\n      } else {\n        analysis = this.analyzeWithRegex(filePath, content, language);\n      }\n\n      this.cache.set(filePath, { mtime, analysis });\n      return analysis;\n    } catch (error) {\n      return this.emptyAnalysis(filePath, language);\n    }\n  }\n\n  private analyzeWithTreeSitter(\n    filePath: string,\n    content: string,\n    language: SupportedLanguage\n  ): FileAnalysis {\n    const parser = this.parsers.get(language);\n    if (!parser) {\n      return this.analyzeWithRegex(filePath, content, language);\n    }\n\n    const tree = parser.parse(content);\n    const symbols: ExtractedSymbol[] = [];\n    const imports: ImportInfo[] = [];\n    const exports: ExportInfo[] = [];\n\n    this.extractFromTreeSitter(tree.rootNode, filePath, symbols, imports, exports);\n\n    return { filePath, symbols, imports, exports, language };\n  }\n\n  private extractFromTreeSitter(\n    node: any,\n    filePath: string,\n    symbols: ExtractedSymbol[],\n    imports: ImportInfo[],\n    exports: ExportInfo[]\n  ): void {\n    const cursor = node.walk();\n\n    const visit = (): void => {\n      const currentNode = cursor.currentNode;\n\n      switch (currentNode.type) {\n        case 'class_declaration':\n          symbols.push(this.extractClassFromTree(currentNode, filePath));\n          break;\n        case 'interface_declaration':\n          symbols.push(this.extractInterfaceFromTree(currentNode, filePath));\n          break;\n        case 'function_declaration':\n          symbols.push(this.extractFunctionFromTree(currentNode, filePath));\n          break;\n        case 'type_alias_declaration':\n          symbols.push(this.extractTypeFromTree(currentNode, filePath));\n          break;\n        case 'enum_declaration':\n          symbols.push(this.extractEnumFromTree(currentNode, filePath));\n          break;\n        case 'import_statement':\n          imports.push(this.extractImportFromTree(currentNode));\n          break;\n        case 'export_statement': {\n          const exportInfo = this.extractExportFromTree(currentNode);\n          if (exportInfo) exports.push(exportInfo);\n          break;\n        }\n      }\n\n      if (cursor.gotoFirstChild()) {\n        do {\n          visit();\n        } while (cursor.gotoNextSibling());\n        cursor.gotoParent();\n      }\n    };\n\n    visit();\n  }\n\n  private extractClassFromTree(node: any, filePath: string): ExtractedSymbol {\n    const nameNode = node.childForFieldName?.('name');\n    return {\n      name: nameNode?.text || 'anonymous',\n      kind: 'class',\n      location: {\n        file: filePath,\n        line: node.startPosition.row + 1,\n        column: node.startPosition.column,\n      },\n      exported: this.isNodeExported(node),\n      documentation: this.extractDocComment(node),\n    };\n  }\n\n  private extractInterfaceFromTree(node: any, filePath: string): ExtractedSymbol {\n    const nameNode = node.childForFieldName?.('name');\n    return {\n      name: nameNode?.text || 'anonymous',\n      kind: 'interface',\n      location: {\n        file: filePath,\n        line: node.startPosition.row + 1,\n        column: node.startPosition.column,\n      },\n      exported: this.isNodeExported(node),\n      documentation: this.extractDocComment(node),\n    };\n  }\n\n  private extractFunctionFromTree(node: any, filePath: string): ExtractedSymbol {\n    const nameNode = node.childForFieldName?.('name');\n    return {\n      name: nameNode?.text || 'anonymous',\n      kind: 'function',\n      location: {\n        file: filePath,\n        line: node.startPosition.row + 1,\n        column: node.startPosition.column,\n      },\n      exported: this.isNodeExported(node),\n      documentation: this.extractDocComment(node),\n    };\n  }\n\n  private extractTypeFromTree(node: any, filePath: string): ExtractedSymbol {\n    const nameNode = node.childForFieldName?.('name');\n    return {\n      name: nameNode?.text || 'anonymous',\n      kind: 'type',\n      location: {\n        file: filePath,\n        line: node.startPosition.row + 1,\n        column: node.startPosition.column,\n      },\n      exported: this.isNodeExported(node),\n      documentation: this.extractDocComment(node),\n    };\n  }\n\n  private extractEnumFromTree(node: any, filePath: string): ExtractedSymbol {\n    const nameNode = node.childForFieldName?.('name');\n    return {\n      name: nameNode?.text || 'anonymous',\n      kind: 'enum',\n      location: {\n        file: filePath,\n        line: node.startPosition.row + 1,\n        column: node.startPosition.column,\n      },\n      exported: this.isNodeExported(node),\n      documentation: this.extractDocComment(node),\n    };\n  }\n\n  private extractImportFromTree(node: any): ImportInfo {\n    const sourceNode = node.children?.find((c: any) => c.type === 'string');\n    const source = sourceNode?.text?.replace(/['\"]/g, '') || '';\n\n    const specifiers: string[] = [];\n    let isDefault = false;\n    let isNamespace = false;\n\n    const importClause = node.children?.find((c: any) => c.type === 'import_clause');\n    if (importClause) {\n      for (const child of importClause.children || []) {\n        if (child.type === 'identifier') {\n          specifiers.push(child.text);\n          isDefault = true;\n        } else if (child.type === 'named_imports') {\n          for (const spec of child.children || []) {\n            if (spec.type === 'import_specifier') {\n              const name = spec.childForFieldName?.('name') ||\n                spec.children?.find((c: any) => c.type === 'identifier');\n              if (name) specifiers.push(name.text);\n            }\n          }\n        } else if (child.type === 'namespace_import') {\n          const name = child.children?.find((c: any) => c.type === 'identifier');\n          if (name) specifiers.push(name.text);\n          isNamespace = true;\n        }\n      }\n    }\n\n    return { source, specifiers, isDefault, isNamespace };\n  }\n\n  private extractExportFromTree(node: any): ExportInfo | null {\n    const isDefault = node.children?.some((c: any) => c.type === 'default') || false;\n\n    const declaration = node.children?.find((c: any) =>\n      ['class_declaration', 'function_declaration', 'interface_declaration',\n        'type_alias_declaration', 'enum_declaration', 'variable_declaration'].includes(c.type)\n    );\n\n    if (declaration) {\n      const nameNode = declaration.childForFieldName?.('name');\n      return {\n        name: nameNode?.text || 'default',\n        isDefault,\n        isReExport: false,\n      };\n    }\n\n    return null;\n  }\n\n  private isNodeExported(node: any): boolean {\n    let parent = node.parent;\n    while (parent) {\n      if (parent.type === 'export_statement') return true;\n      parent = parent.parent;\n    }\n    return false;\n  }\n\n  private extractDocComment(node: any): string | undefined {\n    const prev = node.previousNamedSibling;\n    if (prev?.type === 'comment' && prev.text?.startsWith('/**')) {\n      return prev.text\n        .replace(/^\\/\\*\\*\\s*/, '')\n        .replace(/\\s*\\*\\/$/, '')\n        .replace(/^\\s*\\*\\s?/gm, '')\n        .trim();\n    }\n    return undefined;\n  }\n\n  /**\n   * Regex-based fallback for when tree-sitter is not available\n   */\n  private analyzeWithRegex(\n    filePath: string,\n    content: string,\n    language: SupportedLanguage\n  ): FileAnalysis {\n    if (language === 'typescript' || language === 'javascript') {\n      return this.analyzeTypeScriptWithRegex(filePath, content, language);\n    }\n\n    if (language === 'python') {\n      return this.analyzePythonWithRegex(filePath, content);\n    }\n\n    return this.emptyAnalysis(filePath, language);\n  }\n\n  private analyzeTypeScriptWithRegex(\n    filePath: string,\n    content: string,\n    language: SupportedLanguage\n  ): FileAnalysis {\n    const lines = content.split('\\n');\n    const symbols: ExtractedSymbol[] = [];\n    const imports: ImportInfo[] = [];\n    const exports: ExportInfo[] = [];\n\n    // Patterns for TypeScript/JavaScript\n    const classPattern = /^(\\s*)(export\\s+)?(abstract\\s+)?class\\s+(\\w+)/;\n    const interfacePattern = /^(\\s*)(export\\s+)?interface\\s+(\\w+)/;\n    const functionPattern = /^(\\s*)(export\\s+)?(async\\s+)?function\\s+(\\w+)/;\n    const typePattern = /^(\\s*)(export\\s+)?type\\s+(\\w+)/;\n    const enumPattern = /^(\\s*)(export\\s+)?enum\\s+(\\w+)/;\n    const constFunctionPattern = /^(\\s*)(export\\s+)?const\\s+(\\w+)\\s*=\\s*(async\\s+)?\\(/;\n    const arrowFunctionPattern = /^(\\s*)(export\\s+)?const\\s+(\\w+)\\s*=\\s*(async\\s+)?\\([^)]*\\)\\s*(:\\s*\\w+)?\\s*=>/;\n\n    const importPattern = /^import\\s+(?:(\\w+)\\s*,?\\s*)?(?:\\{([^}]+)\\})?\\s*(?:\\*\\s+as\\s+(\\w+))?\\s*from\\s+['\"]([^'\"]+)['\"]/;\n    const exportPattern = /^export\\s+(?:default\\s+)?(?:class|function|interface|type|enum|const)\\s+(\\w+)/;\n    const reExportPattern = /^export\\s+(?:\\{([^}]+)\\}|\\*)\\s+from\\s+['\"]([^'\"]+)['\"]/;\n\n    for (let i = 0; i < lines.length; i++) {\n      const line = lines[i];\n      const lineNum = i + 1;\n\n      // Check for imports\n      const importMatch = line.match(importPattern);\n      if (importMatch) {\n        const [, defaultImport, namedImports, namespaceImport, source] = importMatch;\n        const specifiers: string[] = [];\n\n        if (defaultImport) specifiers.push(defaultImport);\n        if (namedImports) {\n          namedImports.split(',').forEach((s) => {\n            const name = s.trim().split(/\\s+as\\s+/)[0].trim();\n            if (name) specifiers.push(name);\n          });\n        }\n        if (namespaceImport) specifiers.push(namespaceImport);\n\n        imports.push({\n          source,\n          specifiers,\n          isDefault: !!defaultImport,\n          isNamespace: !!namespaceImport,\n        });\n        continue;\n      }\n\n      // Check for re-exports\n      const reExportMatch = line.match(reExportPattern);\n      if (reExportMatch) {\n        const [, namedExports, source] = reExportMatch;\n        if (namedExports) {\n          namedExports.split(',').forEach((s) => {\n            const name = s.trim().split(/\\s+as\\s+/)[0].trim();\n            if (name) {\n              exports.push({ name, isDefault: false, isReExport: true, originalSource: source });\n            }\n          });\n        }\n        continue;\n      }\n\n      // Check for classes\n      const classMatch = line.match(classPattern);\n      if (classMatch) {\n        const [, , exportKeyword, , name] = classMatch;\n        symbols.push({\n          name,\n          kind: 'class',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for interfaces\n      const interfaceMatch = line.match(interfacePattern);\n      if (interfaceMatch) {\n        const [, , exportKeyword, name] = interfaceMatch;\n        symbols.push({\n          name,\n          kind: 'interface',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for functions\n      const functionMatch = line.match(functionPattern);\n      if (functionMatch) {\n        const [, , exportKeyword, , name] = functionMatch;\n        symbols.push({\n          name,\n          kind: 'function',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for arrow functions\n      const arrowMatch = line.match(arrowFunctionPattern) || line.match(constFunctionPattern);\n      if (arrowMatch) {\n        const [, , exportKeyword, name] = arrowMatch;\n        symbols.push({\n          name,\n          kind: 'function',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for types\n      const typeMatch = line.match(typePattern);\n      if (typeMatch) {\n        const [, , exportKeyword, name] = typeMatch;\n        symbols.push({\n          name,\n          kind: 'type',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for enums\n      const enumMatch = line.match(enumPattern);\n      if (enumMatch) {\n        const [, , exportKeyword, name] = enumMatch;\n        symbols.push({\n          name,\n          kind: 'enum',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !!exportKeyword,\n        });\n        continue;\n      }\n\n      // Check for exports\n      const exportMatch = line.match(exportPattern);\n      if (exportMatch) {\n        const [fullMatch, name] = exportMatch;\n        const isDefault = fullMatch.includes('default');\n        exports.push({ name, isDefault, isReExport: false });\n      }\n    }\n\n    return { filePath, symbols, imports, exports, language };\n  }\n\n  private analyzePythonWithRegex(filePath: string, content: string): FileAnalysis {\n    const lines = content.split('\\n');\n    const symbols: ExtractedSymbol[] = [];\n    const imports: ImportInfo[] = [];\n    const exports: ExportInfo[] = [];\n\n    const classPattern = /^class\\s+(\\w+)/;\n    const functionPattern = /^(async\\s+)?def\\s+(\\w+)/;\n    const importPattern = /^(?:from\\s+(\\S+)\\s+)?import\\s+(.+)/;\n\n    for (let i = 0; i < lines.length; i++) {\n      const line = lines[i];\n      const lineNum = i + 1;\n\n      // Check for imports\n      const importMatch = line.match(importPattern);\n      if (importMatch) {\n        const [, fromModule, importedItems] = importMatch;\n        const specifiers = importedItems\n          .split(',')\n          .map((s) => s.trim().split(/\\s+as\\s+/)[0].trim())\n          .filter(Boolean);\n\n        imports.push({\n          source: fromModule || importedItems.trim(),\n          specifiers,\n          isDefault: false,\n          isNamespace: importedItems.includes('*'),\n        });\n        continue;\n      }\n\n      // Check for classes\n      const classMatch = line.match(classPattern);\n      if (classMatch) {\n        const [, name] = classMatch;\n        const isPrivate = name.startsWith('_');\n        symbols.push({\n          name,\n          kind: 'class',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !isPrivate,\n        });\n        continue;\n      }\n\n      // Check for functions\n      const functionMatch = line.match(functionPattern);\n      if (functionMatch) {\n        const [, , name] = functionMatch;\n        const isPrivate = name.startsWith('_');\n        symbols.push({\n          name,\n          kind: 'function',\n          location: { file: filePath, line: lineNum, column: 0 },\n          exported: !isPrivate,\n        });\n      }\n    }\n\n    return { filePath, symbols, imports, exports, language: 'python' };\n  }\n\n  private emptyAnalysis(filePath: string, language: string): FileAnalysis {\n    return {\n      filePath,\n      symbols: [],\n      imports: [],\n      exports: [],\n      language,\n    };\n  }\n\n  clearCache(): void {\n    this.cache.clear();\n  }\n\n  async isTreeSitterAvailable(): Promise<boolean> {\n    await this.ready;\n    return this.treeSitterAvailable;\n  }\n}\n"
  },
  {
    "path": "src/services/semantic/types.ts",
    "content": "/**\n * Semantic analysis types for codebase understanding\n */\n\nexport type SymbolKind = 'class' | 'interface' | 'function' | 'type' | 'variable' | 'enum' | 'method';\n\nexport interface SymbolLocation {\n  file: string;\n  line: number;\n  column: number;\n}\n\nexport interface ParameterInfo {\n  name: string;\n  type?: string;\n  optional?: boolean;\n  defaultValue?: string;\n}\n\nexport interface ExtractedSymbol {\n  name: string;\n  kind: SymbolKind;\n  location: SymbolLocation;\n  exported: boolean;\n  documentation?: string;\n  extends?: string;\n  implements?: string[];\n  parameters?: ParameterInfo[];\n  returnType?: string;\n  members?: ExtractedSymbol[];\n  // LSP-enhanced properties (populated when useLSP is enabled)\n  typeInfo?: TypeInfo;\n  references?: ReferenceLocation[];\n  implementations?: ReferenceLocation[];\n}\n\nexport interface ImportInfo {\n  source: string;\n  specifiers: string[];\n  isDefault: boolean;\n  isNamespace: boolean;\n  alias?: string;\n}\n\nexport interface ExportInfo {\n  name: string;\n  isDefault: boolean;\n  isReExport: boolean;\n  originalSource?: string;\n}\n\nexport interface FileAnalysis {\n  filePath: string;\n  symbols: ExtractedSymbol[];\n  imports: ImportInfo[];\n  exports: ExportInfo[];\n  language: string;\n}\n\nexport interface ArchitectureLayer {\n  name: string;\n  description: string;\n  directories: string[];\n  symbols: ExtractedSymbol[];\n  dependsOn: string[];\n}\n\nexport interface DetectedPattern {\n  name: string;\n  confidence: number;\n  locations: Array<{ file: string; symbol: string }>;\n  description: string;\n}\n\n/**\n * Functional pattern types for Q&A topic detection\n * These patterns indicate functional capabilities in the codebase\n */\nexport type FunctionalPatternType =\n  | 'auth'\n  | 'database'\n  | 'api'\n  | 'cache'\n  | 'queue'\n  | 'websocket'\n  | 'logging'\n  | 'validation'\n  | 'error-handling'\n  | 'testing';\n\nexport interface FunctionalPattern {\n  type: FunctionalPatternType;\n  confidence: number;\n  indicators: PatternIndicator[];\n  description: string;\n}\n\nexport interface PatternIndicator {\n  file: string;\n  symbol?: string;\n  line?: number;\n  reason: string;\n}\n\nexport interface DetectedFunctionalPatterns {\n  hasAuthPattern: boolean;\n  hasDatabasePattern: boolean;\n  hasApiPattern: boolean;\n  hasCachePattern: boolean;\n  hasQueuePattern: boolean;\n  hasWebSocketPattern: boolean;\n  hasLoggingPattern: boolean;\n  hasValidationPattern: boolean;\n  hasErrorHandlingPattern: boolean;\n  hasTestingPattern: boolean;\n  patterns: FunctionalPattern[];\n}\n\n/**\n * Flow tracing types for understanding execution paths\n */\nexport interface FlowNode {\n  file: string;\n  symbol: string;\n  line: number;\n  type: 'entry' | 'call' | 'return' | 'branch';\n}\n\nexport interface FlowEdge {\n  from: FlowNode;\n  to: FlowNode;\n  label?: string;\n}\n\nexport interface ExecutionFlow {\n  entryPoint: FlowNode;\n  nodes: FlowNode[];\n  edges: FlowEdge[];\n  mermaidDiagram: string;\n}\n\nexport interface DependencyInfo {\n  graph: Map<string, string[]>;\n  reverseGraph: Map<string, string[]>;\n}\n\nexport interface ArchitectureInfo {\n  layers: ArchitectureLayer[];\n  patterns: DetectedPattern[];\n  entryPoints: string[];\n  publicAPI: ExtractedSymbol[];\n}\n\nexport interface SemanticStats {\n  totalFiles: number;\n  totalSymbols: number;\n  languageBreakdown: Record<string, number>;\n  analysisTimeMs: number;\n}\n\nexport interface SemanticContext {\n  symbols: {\n    classes: ExtractedSymbol[];\n    interfaces: ExtractedSymbol[];\n    functions: ExtractedSymbol[];\n    types: ExtractedSymbol[];\n    enums: ExtractedSymbol[];\n  };\n  dependencies: DependencyInfo;\n  architecture: ArchitectureInfo;\n  stats: SemanticStats;\n}\n\nexport interface TypeInfo {\n  name: string;\n  fullType: string;\n  documentation?: string;\n}\n\nexport interface ReferenceLocation {\n  file: string;\n  line: number;\n  column: number;\n  context?: string;\n}\n\nexport interface AnalyzerOptions {\n  useLSP?: boolean;\n  languages?: string[];\n  exclude?: string[];\n  include?: string[];\n  maxFiles?: number;\n  cacheEnabled?: boolean;\n}\n\nexport interface LSPServerConfig {\n  command: string;\n  args: string[];\n  rootPatterns?: string[];\n}\n\nexport type SupportedLanguage =\n  | 'typescript'\n  | 'javascript'\n  | 'python';\n\nexport const LANGUAGE_EXTENSIONS: Record<string, SupportedLanguage> = {\n  '.ts': 'typescript',\n  '.tsx': 'typescript',\n  '.js': 'javascript',\n  '.jsx': 'javascript',\n  '.mjs': 'javascript',\n  '.cjs': 'javascript',\n  '.py': 'python',\n  '.pyw': 'python',\n  '.pyi': 'python',\n};\n\nexport const DEFAULT_EXCLUDE_PATTERNS = [\n  'node_modules',\n  'dist',\n  'build',\n  '.git',\n  'vendor',\n  '__pycache__',\n  '.next',\n  '.nuxt',\n  'coverage',\n  '.cache',\n  'target',\n  'venv',\n  '.venv',\n];\n"
  },
  {
    "path": "src/services/shared/__tests__/contextRootResolver.test.ts",
    "content": "/**\n * Unit tests for Simple Context Root Resolver\n *\n * Tests simple path resolution without complex detection strategies.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport * as os from 'os';\nimport {\n  resolveContextPath,\n  resolveSimpleContext,\n  resolveContextRoot,\n} from '../contextRootResolver';\n\ndescribe('contextRootResolver', () => {\n  let testDir: string;\n\n  beforeEach(async () => {\n    // Create temporary test directory\n    testDir = path.join(os.tmpdir(), `context-resolver-test-${Date.now()}`);\n    await fs.ensureDir(testDir);\n  });\n\n  afterEach(async () => {\n    // Cleanup test directory\n    await fs.remove(testDir);\n  });\n\n  describe('resolveContextPath', () => {\n    it('should resolve context path with absolute path', () => {\n      // Act\n      const result = resolveContextPath(testDir);\n\n      // Assert\n      expect(result).toBe(path.join(testDir, '.context'));\n    });\n\n    it('should resolve context path with relative path', () => {\n      // Act\n      const result = resolveContextPath('./myproject');\n\n      // Assert\n      expect(result).toContain('.context');\n      expect(result).toMatch(/myproject[/\\\\]\\.context/);\n    });\n\n    it('should resolve context path with cwd when no path provided', () => {\n      // Act\n      const result = resolveContextPath();\n\n      // Assert\n      expect(result).toBe(path.join(process.cwd(), '.context'));\n    });\n  });\n\n  describe('resolveSimpleContext', () => {\n    it('should detect .context exists', async () => {\n      // Setup: Create .context directory\n      const contextPath = path.join(testDir, '.context');\n      await fs.ensureDir(contextPath);\n\n      // Act\n      const result = await resolveSimpleContext(testDir);\n\n      // Assert\n      expect(result.contextPath).toBe(contextPath);\n      expect(result.projectRoot).toBe(testDir);\n      expect(result.exists).toBe(true);\n    });\n\n    it('should detect .context does not exist', async () => {\n      // Act\n      const result = await resolveSimpleContext(testDir);\n\n      // Assert\n      expect(result.contextPath).toBe(path.join(testDir, '.context'));\n      expect(result.projectRoot).toBe(testDir);\n      expect(result.exists).toBe(false);\n    });\n\n    it('should use cwd when no path provided', async () => {\n      // Act\n      const result = await resolveSimpleContext();\n\n      // Assert\n      expect(result.projectRoot).toBe(process.cwd());\n      expect(result.contextPath).toBe(path.join(process.cwd(), '.context'));\n    });\n\n    it('should resolve absolute paths correctly', async () => {\n      // Setup: Create .context in testDir\n      const contextPath = path.join(testDir, '.context');\n      await fs.ensureDir(contextPath);\n\n      // Act\n      const result = await resolveSimpleContext(testDir);\n\n      // Assert\n      expect(path.isAbsolute(result.contextPath)).toBe(true);\n      expect(path.isAbsolute(result.projectRoot)).toBe(true);\n    });\n  });\n\n  describe('resolveContextRoot (backwards compatibility)', () => {\n    it('should resolve context using startPath', async () => {\n      // Setup: Create .context\n      const contextPath = path.join(testDir, '.context');\n      await fs.ensureDir(contextPath);\n\n      // Act\n      const result = await resolveContextRoot({\n        startPath: testDir,\n        validate: false,\n      });\n\n      // Assert\n      expect(result.contextPath).toBe(contextPath);\n      expect(result.projectRoot).toBe(testDir);\n      expect(result.exists).toBe(true);\n      expect(result.isValid).toBe(true); // Simple: valid if exists\n    });\n\n    it('should use process.cwd() when no startPath provided', async () => {\n      // Act\n      const result = await resolveContextRoot();\n\n      // Assert\n      expect(result.projectRoot).toBe(process.cwd());\n      expect(result.contextPath).toBe(path.join(process.cwd(), '.context'));\n    });\n\n    it('should detect non-existent context correctly', async () => {\n      // Act (testDir exists but has no .context)\n      const result = await resolveContextRoot({\n        startPath: testDir,\n        validate: false,\n      });\n\n      // Assert\n      expect(result.exists).toBe(false);\n      expect(result.isValid).toBe(false); // Simple: invalid if not exists\n    });\n\n    it('should mark context as valid when exists', async () => {\n      // Setup\n      const contextPath = path.join(testDir, '.context');\n      await fs.ensureDir(contextPath);\n\n      // Act\n      const result = await resolveContextRoot({\n        startPath: testDir,\n      });\n\n      // Assert\n      expect(result.exists).toBe(true);\n      expect(result.isValid).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/shared/contentTypeRegistry.ts",
    "content": "/**\n * Content Type Registry\n *\n * Extensible registry for managing different content types in .context/\n * This enables easy addition of new content types (prompts, workflows, etc.)\n * without modifying core export/import logic.\n */\n\n/**\n * Content type definition\n */\nexport interface ContentType {\n  /** Unique identifier (e.g., 'docs', 'agents', 'skills') */\n  id: string;\n  /** Human-readable display name */\n  displayName: string;\n  /** Path relative to .context/ */\n  contextPath: string;\n  /** File pattern for discovery (glob pattern) */\n  filePattern: string;\n  /** Index file name (e.g., 'README.md' for docs) */\n  indexFile?: string;\n  /** Description of this content type */\n  description: string;\n  /** Whether this content type supports export */\n  supportsExport: boolean;\n  /** Whether this content type supports import */\n  supportsImport: boolean;\n}\n\n/**\n * Registry of all content types\n */\nexport const CONTENT_TYPE_REGISTRY: ContentType[] = [\n  {\n    id: 'docs',\n    displayName: 'Documentation',\n    contextPath: '.context/docs',\n    filePattern: '**/*.md',\n    indexFile: 'README.md',\n    description: 'Project documentation, rules, and guidelines',\n    supportsExport: true,\n    supportsImport: true,\n  },\n  {\n    id: 'agents',\n    displayName: 'Agents',\n    contextPath: '.context/agents',\n    filePattern: '**/*.md',\n    description: 'AI agent playbooks and configurations',\n    supportsExport: true,\n    supportsImport: true,\n  },\n  {\n    id: 'skills',\n    displayName: 'Skills',\n    contextPath: '.context/skills',\n    filePattern: '**/SKILL.md',\n    description: 'On-demand expertise and task-specific instructions',\n    supportsExport: true,\n    supportsImport: true,\n  },\n  {\n    id: 'plans',\n    displayName: 'Plans',\n    contextPath: '.context/plans',\n    filePattern: '**/*.md',\n    description: 'Implementation plans and task breakdowns',\n    supportsExport: false,\n    supportsImport: false,\n  },\n  {\n    id: 'sensors',\n    displayName: 'Harness Sensors',\n    contextPath: '.context/harness',\n    filePattern: 'sensors.json',\n    description: 'Project-specific harness sensor catalog and quality checks',\n    supportsExport: false,\n    supportsImport: false,\n  },\n];\n\n/**\n * Get a content type by ID\n */\nexport function getContentType(id: string): ContentType | undefined {\n  return CONTENT_TYPE_REGISTRY.find(ct => ct.id === id);\n}\n\n/**\n * Get all content types that support export\n */\nexport function getExportableContentTypes(): ContentType[] {\n  return CONTENT_TYPE_REGISTRY.filter(ct => ct.supportsExport);\n}\n\n/**\n * Get all content types that support import\n */\nexport function getImportableContentTypes(): ContentType[] {\n  return CONTENT_TYPE_REGISTRY.filter(ct => ct.supportsImport);\n}\n\n/**\n * Get content type IDs\n */\nexport function getContentTypeIds(): string[] {\n  return CONTENT_TYPE_REGISTRY.map(ct => ct.id);\n}\n\n/**\n * Check if a content type exists\n */\nexport function hasContentType(id: string): boolean {\n  return CONTENT_TYPE_REGISTRY.some(ct => ct.id === id);\n}\n\n/**\n * Register a new content type (for future extensibility)\n * Note: This mutates the registry. Use with caution.\n */\nexport function registerContentType(contentType: ContentType): void {\n  if (hasContentType(contentType.id)) {\n    throw new Error(`Content type '${contentType.id}' already exists`);\n  }\n  CONTENT_TYPE_REGISTRY.push(contentType);\n}\n\n/**\n * Get the context path for a content type\n */\nexport function getContextPath(id: string): string | undefined {\n  return getContentType(id)?.contextPath;\n}\n\n/**\n * Get the index file for a content type (if any)\n */\nexport function getIndexFile(id: string): string | undefined {\n  return getContentType(id)?.indexFile;\n}\n"
  },
  {
    "path": "src/services/shared/contextLayout.ts",
    "content": "/**\n * Context Layout Registry\n *\n * Defines which `.context` paths are durable project knowledge and which are\n * local runtime state that should stay out of version control.\n */\n\nexport type ContextLayoutClassification = 'versioned' | 'local' | 'runtime';\n\nexport interface ContextLayoutEntry {\n  id: string;\n  path: string;\n  classification: ContextLayoutClassification;\n  trackedInGit: boolean;\n  description: string;\n}\n\nexport const CONTEXT_LAYOUT_REGISTRY: ContextLayoutEntry[] = [\n  {\n    id: 'docs',\n    path: '.context/docs/**',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Durable project documentation and generated knowledge base content.',\n  },\n  {\n    id: 'agents',\n    path: '.context/agents/**',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Agent playbooks and role definitions maintained as project assets.',\n  },\n  {\n    id: 'skills',\n    path: '.context/skills/**',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Reusable skills and on-demand operating instructions.',\n  },\n  {\n    id: 'plans',\n    path: '.context/plans/**',\n    classification: 'local',\n    trackedInGit: false,\n    description: 'Implementation plans kept as local working artifacts unless the team explicitly decides to version them.',\n  },\n  {\n    id: 'harness-config-sensors',\n    path: '.context/harness/sensors.json',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Project sensor catalog generated at bootstrap and intended to be customized by the team.',\n  },\n  {\n    id: 'harness-config-policy',\n    path: '.context/harness/policy.json',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Harness policy rules and approval constraints treated as project configuration.',\n  },\n  {\n    id: 'context-config',\n    path: '.context/config.json',\n    classification: 'versioned',\n    trackedInGit: true,\n    description: 'Context generation configuration persisted for repeatable scaffolding.',\n  },\n  {\n    id: 'semantic-cache',\n    path: '.context/cache/semantic/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Persisted semantic snapshot cache and versioned summary sections generated from the codebase.',\n  },\n  {\n    id: 'workflow-runtime',\n    path: '.context/workflow/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Local workflow execution state, bindings, and archives.',\n  },\n  {\n    id: 'harness-sessions',\n    path: '.context/harness/sessions/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Session records for local harness execution.',\n  },\n  {\n    id: 'harness-traces',\n    path: '.context/harness/traces/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Append-only execution traces and sensor runs.',\n  },\n  {\n    id: 'harness-artifacts',\n    path: '.context/harness/artifacts/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Artifacts captured during harness sessions.',\n  },\n  {\n    id: 'harness-contracts',\n    path: '.context/harness/contracts/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Task and handoff contracts associated with local execution state.',\n  },\n  {\n    id: 'harness-workflows',\n    path: '.context/harness/workflows/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Canonical workflow runtime state materialized during execution.',\n  },\n  {\n    id: 'harness-replays',\n    path: '.context/harness/replays/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Replay artifacts derived from session execution history.',\n  },\n  {\n    id: 'harness-datasets',\n    path: '.context/harness/datasets/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Generated failure datasets and clustering output.',\n  },\n  {\n    id: 'archives',\n    path: '.context/**/archive/**',\n    classification: 'runtime',\n    trackedInGit: false,\n    description: 'Archived local state and historical runtime artifacts.',\n  },\n];\n\nexport function getContextLayoutByClassification(\n  classification: ContextLayoutClassification\n): ContextLayoutEntry[] {\n  return CONTEXT_LAYOUT_REGISTRY.filter((entry) => entry.classification === classification);\n}\n\nexport function getUntrackedContextLayoutEntries(): ContextLayoutEntry[] {\n  return CONTEXT_LAYOUT_REGISTRY.filter((entry) => !entry.trackedInGit);\n}\n"
  },
  {
    "path": "src/services/shared/contextRootResolver.ts",
    "content": "/**\n * Simple context path resolver\n *\n * Creates .context in the provided path or current working directory.\n * No upward traversal, git detection, or package.json configuration.\n * Simpler, more predictable behavior.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\n/**\n * Resolve the .context path for a given repository path\n * @param repoPath - The repository path (defaults to cwd)\n * @returns Absolute path to .context directory\n */\nexport function resolveContextPath(repoPath?: string): string {\n  if (repoPath && (typeof repoPath !== 'string' || repoPath.trim() === '')) {\n    throw new Error('contextRootResolver: repoPath must be a non-empty string');\n  }\n  const base = repoPath ? path.resolve(repoPath) : process.cwd();\n  return path.join(base, '.context');\n}\n\n/**\n * Simple context resolution result\n */\nexport interface SimpleContextResult {\n  /** Absolute path to .context directory */\n  contextPath: string;\n  /** Absolute path to project root */\n  projectRoot: string;\n  /** Whether .context directory exists */\n  exists: boolean;\n}\n\n/**\n * Resolve context information for a repository\n * @param repoPath - The repository path (defaults to cwd)\n * @returns Simple context resolution result\n */\nexport async function resolveSimpleContext(repoPath?: string): Promise<SimpleContextResult> {\n  if (repoPath && (typeof repoPath !== 'string' || repoPath.trim() === '')) {\n    throw new Error('contextRootResolver: repoPath must be a non-empty string');\n  }\n  const projectRoot = repoPath ? path.resolve(repoPath) : process.cwd();\n  const contextPath = path.join(projectRoot, '.context');\n  const exists = fs.existsSync(contextPath);\n\n  return { contextPath, projectRoot, exists };\n}\n\n/**\n * Get the context path for a repository (simple helper)\n * @param repoPath - The repository path (defaults to cwd)\n * @returns Absolute path to .context directory\n */\nexport async function getContextPath(repoPath: string = process.cwd()): Promise<string> {\n  return resolveContextPath(repoPath);\n}\n\n/**\n * Get the project root for a repository\n * @param repoPath - The repository path (defaults to cwd)\n * @returns Absolute path to project root\n */\nexport async function getProjectRoot(repoPath: string = process.cwd()): Promise<string> {\n  return path.resolve(repoPath);\n}\n\n/**\n * Backwards compatibility: resolve context using old complex API\n * This maintains compatibility with existing code while using simple resolution\n */\nexport interface ContextResolutionResult {\n  contextPath: string;\n  projectRoot: string;\n  exists: boolean;\n  isValid: boolean;\n}\n\nexport async function resolveContextRoot(options?: { startPath?: string; validate?: boolean }): Promise<ContextResolutionResult> {\n  const startPath = options?.startPath || process.cwd();\n  const result = await resolveSimpleContext(startPath);\n\n  return {\n    contextPath: result.contextPath,\n    projectRoot: result.projectRoot,\n    exists: result.exists,\n    isValid: result.exists, // Simple: valid if it exists\n  };\n}\n"
  },
  {
    "path": "src/services/shared/executionContext.ts",
    "content": "/**\n * Shared execution helpers for non-interactive service contexts.\n *\n * These utilities are intentionally transport-agnostic and can be reused by\n * harness services, MCP adapters, or future runtime adapters.\n */\n\n/**\n * Minimal UI interface for services that require UI dependencies.\n * Returns no-op functions for all UI operations in non-interactive contexts.\n */\nexport const minimalUI = {\n  displayOutput: () => {},\n  displaySuccess: () => {},\n  displayError: () => {},\n  displayInfo: () => {},\n  displayWarning: () => {},\n  displayWelcome: () => {},\n  displayPrevcExplanation: () => {},\n  displayStep: () => {},\n  displayBox: () => {},\n  startSpinner: () => {},\n  stopSpinner: () => {},\n  updateSpinner: () => {},\n  prompt: async () => '',\n  confirm: async () => true,\n};\n\n/**\n * Mock translation function for services that require i18n.\n */\nexport const mockTranslate = (key: string) => key;\n\n/**\n * Neutral tool execution context for AI tools.\n */\nexport const toolExecutionContext = { toolCallId: '', messages: [] };\n"
  },
  {
    "path": "src/services/shared/globPatterns.ts",
    "content": "/**\n * Shared Glob Patterns\n *\n * Common glob patterns and utilities used across file detection services.\n * Centralizes ignore patterns to avoid duplication.\n */\n\nimport { glob, type GlobOptions } from 'glob';\n\n/**\n * Common directories to ignore in all glob operations\n */\nexport const COMMON_IGNORES = [\n  'node_modules/**',\n  '.git/**',\n  'dist/**',\n  'build/**',\n  'vendor/**',\n  '__pycache__/**',\n  '.next/**',\n  '.nuxt/**',\n  'coverage/**',\n  '.cache/**',\n  'tmp/**',\n  '.tmp/**',\n] as const;\n\n/**\n * Common source code extensions\n */\nexport const CODE_EXTENSIONS = [\n  'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',\n  'py', 'pyw',\n  'go',\n  'rs',\n  'java', 'kt', 'scala',\n  'c', 'cpp', 'cc', 'h', 'hpp',\n  'cs',\n  'rb',\n  'php',\n  'swift',\n  'dart',\n  'vue', 'svelte',\n] as const;\n\n/**\n * Common documentation extensions\n */\nexport const DOC_EXTENSIONS = ['md', 'mdx', 'txt', 'rst'] as const;\n\n/**\n * Common config file patterns\n */\nexport const CONFIG_PATTERNS = [\n  'package.json',\n  'tsconfig.json',\n  'pyproject.toml',\n  'Cargo.toml',\n  'go.mod',\n  'pom.xml',\n  'build.gradle',\n  'Gemfile',\n  'composer.json',\n] as const;\n\n/**\n * Options for globFiles function\n */\nexport interface GlobFilesOptions {\n  absolute?: boolean;\n  nodir?: boolean;\n  additionalIgnores?: string[];\n  includeHidden?: boolean;\n}\n\n/**\n * Find files matching a glob pattern with common ignores\n */\nexport async function globFiles(\n  pattern: string,\n  cwd: string,\n  options: GlobFilesOptions = {}\n): Promise<string[]> {\n  const {\n    absolute = true,\n    nodir = true,\n    additionalIgnores = [],\n    includeHidden = false,\n  } = options;\n\n  const ignore = [...COMMON_IGNORES, ...additionalIgnores];\n\n  const globOptions: GlobOptions = {\n    cwd,\n    absolute,\n    nodir,\n    ignore,\n    dot: includeHidden,\n  };\n\n  const results = await glob(pattern, globOptions);\n  return results.map(r => String(r));\n}\n\n/**\n * Find files with multiple patterns\n */\nexport async function globMultiple(\n  patterns: string[],\n  cwd: string,\n  options: GlobFilesOptions = {}\n): Promise<string[]> {\n  const results = await Promise.all(\n    patterns.map((pattern) => globFiles(pattern, cwd, options))\n  );\n\n  // Deduplicate results\n  return [...new Set(results.flat())];\n}\n\n/**\n * Build a glob pattern for specific extensions\n */\nexport function buildExtensionPattern(extensions: readonly string[]): string {\n  if (extensions.length === 1) {\n    return `**/*.${extensions[0]}`;\n  }\n  return `**/*.{${extensions.join(',')}}`;\n}\n\n/**\n * Check if a file should be ignored\n */\nexport function shouldIgnore(filePath: string): boolean {\n  const normalizedPath = filePath.replace(/\\\\/g, '/');\n  return COMMON_IGNORES.some((pattern) => {\n    const cleanPattern = pattern.replace('/**', '');\n    return normalizedPath.includes(cleanPattern);\n  });\n}\n"
  },
  {
    "path": "src/services/shared/index.ts",
    "content": "/**\n * Shared Service Utilities\n *\n * Common utilities, types, and helpers used across all services.\n */\n\n// Types\nexport {\n  BaseDependencies,\n  OperationResult,\n  OperationError,\n  FileInfo,\n  DetectionResult,\n  DryRunOptions,\n  createEmptyResult,\n  mergeResults,\n  addError,\n} from './types';\n\n// Glob Patterns\nexport {\n  COMMON_IGNORES,\n  CODE_EXTENSIONS,\n  DOC_EXTENSIONS,\n  CONFIG_PATTERNS,\n  globFiles,\n  globMultiple,\n  buildExtensionPattern,\n  shouldIgnore,\n} from './globPatterns';\n\n// UI Helpers\nexport {\n  withSpinner,\n  displayOperationSummary,\n  displayProgressBar,\n  displayPhaseIndicator,\n  createBox,\n  SpinnerStatus,\n} from './uiHelpers';\n\n// Path Helpers\nexport {\n  ContextPaths,\n  ContextPathsWithResolution,\n  resolveContextPaths,\n  resolveContextPathsAsync,\n  resolveAbsolutePath,\n  ensureDirectory,\n  ensureParentDirectory,\n  getRelativePath,\n  pathExists,\n  isDirectory,\n  isFile,\n  normalizePath,\n  deduplicatePaths,\n  getExtension,\n  getBasename,\n  joinPaths,\n} from './pathHelpers';\n\n// Context Root Resolver\nexport {\n  SimpleContextResult,\n  ContextResolutionResult,\n  resolveContextPath,\n  resolveSimpleContext,\n  resolveContextRoot,\n  getContextPath as getContextPathResolved,\n  getProjectRoot,\n} from './contextRootResolver';\n\n// Tool Registry\nexport {\n  ToolCapabilities,\n  ToolPaths,\n  ToolDefinition,\n  TOOL_REGISTRY,\n  getToolById,\n  getAllToolIds,\n  getToolsWithCapability,\n  getToolIdFromPath,\n  getToolDisplayName,\n  getToolCapabilities,\n  getDirectoryPrefixMap,\n  getDisplayNameMap,\n  getCapabilitiesMap,\n  getRulesExportPresets,\n  getAgentsSyncPresets,\n  getSkillsExportPresets,\n  getRulesImportSources,\n  getAgentsImportSources,\n  getSkillsImportSources,\n} from './toolRegistry';\n\n// Content Type Registry\nexport {\n  ContentType,\n  CONTENT_TYPE_REGISTRY,\n  getContentType,\n  getExportableContentTypes,\n  getImportableContentTypes,\n  getContentTypeIds,\n  hasContentType,\n  registerContentType,\n  getContextPath,\n  getIndexFile,\n} from './contentTypeRegistry';\n\n// Context Layout Registry\nexport {\n  CONTEXT_LAYOUT_REGISTRY,\n  getContextLayoutByClassification,\n  getUntrackedContextLayoutEntries,\n  type ContextLayoutClassification,\n  type ContextLayoutEntry,\n} from './contextLayout';\n\n// Execution helpers\nexport {\n  minimalUI,\n  mockTranslate,\n  toolExecutionContext,\n} from './executionContext';\n"
  },
  {
    "path": "src/services/shared/pathHelpers.ts",
    "content": "/**\n * Shared Path Helpers\n *\n * Common path resolution and context path utilities.\n * Reduces duplication of path handling across services.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  resolveContextRoot,\n  type ContextResolutionResult,\n} from './contextRootResolver';\n\n/**\n * Standard context paths for a repository\n */\nexport interface ContextPaths {\n  absolutePath: string;\n  contextPath: string;\n  docsPath: string;\n  agentsPath: string;\n  plansPath: string;\n  rulesPath: string;\n  workflowPath: string;\n}\n\n/**\n * Extended context paths with resolution details\n */\nexport interface ContextPathsWithResolution extends ContextPaths {\n  resolution: ContextResolutionResult;\n}\n\n/**\n * Resolve all context paths for a repository (synchronous version)\n * Note: Uses a simple strategy without upward traversal or git root detection.\n * For robust context resolution, use resolveContextPathsAsync instead.\n */\nexport function resolveContextPaths(repoPath: string): ContextPaths {\n  const absolutePath = path.resolve(repoPath);\n  const contextPath = path.join(absolutePath, '.context');\n\n  return {\n    absolutePath,\n    contextPath,\n    docsPath: path.join(contextPath, 'docs'),\n    agentsPath: path.join(contextPath, 'agents'),\n    plansPath: path.join(contextPath, 'plans'),\n    rulesPath: path.join(contextPath, 'rules'),\n    workflowPath: path.join(contextPath, 'workflow'),\n  };\n}\n\n/**\n * Resolve all context paths for a repository (async version)\n * Uses simple path resolution: direct subdirectory lookup without upward traversal.\n */\nexport async function resolveContextPathsAsync(\n  repoPath?: string\n): Promise<ContextPathsWithResolution> {\n  const startPath = repoPath || process.cwd();\n  const resolution = await resolveContextRoot({\n    startPath,\n  });\n\n  const paths: ContextPathsWithResolution = {\n    absolutePath: resolution.projectRoot,\n    contextPath: resolution.contextPath,\n    docsPath: path.join(resolution.contextPath, 'docs'),\n    agentsPath: path.join(resolution.contextPath, 'agents'),\n    plansPath: path.join(resolution.contextPath, 'plans'),\n    rulesPath: path.join(resolution.contextPath, 'rules'),\n    workflowPath: path.join(resolution.contextPath, 'workflow'),\n    resolution,\n  };\n\n  return paths;\n}\n\n/**\n * Resolve an absolute path from input\n */\nexport function resolveAbsolutePath(\n  inputPath: string | undefined,\n  defaultPath: string,\n  basePath: string\n): string {\n  const resolved = inputPath || defaultPath;\n  return path.isAbsolute(resolved)\n    ? resolved\n    : path.join(basePath, resolved);\n}\n\n/**\n * Ensure a directory exists\n */\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n  await fs.ensureDir(dirPath);\n}\n\n/**\n * Ensure parent directory of a file exists\n */\nexport async function ensureParentDirectory(filePath: string): Promise<void> {\n  await fs.ensureDir(path.dirname(filePath));\n}\n\n/**\n * Get relative path from base\n */\nexport function getRelativePath(fullPath: string, basePath: string): string {\n  return path.relative(basePath, fullPath);\n}\n\n/**\n * Check if path exists\n */\nexport async function pathExists(targetPath: string): Promise<boolean> {\n  return fs.pathExists(targetPath);\n}\n\n/**\n * Check if path is a directory\n */\nexport async function isDirectory(targetPath: string): Promise<boolean> {\n  try {\n    const stat = await fs.stat(targetPath);\n    return stat.isDirectory();\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Check if path is a file\n */\nexport async function isFile(targetPath: string): Promise<boolean> {\n  try {\n    const stat = await fs.stat(targetPath);\n    return stat.isFile();\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Normalize path separators to forward slashes\n */\nexport function normalizePath(inputPath: string): string {\n  return inputPath.replace(/\\\\/g, '/');\n}\n\n/**\n * Deduplicate an array of paths\n */\nexport function deduplicatePaths(paths: string[]): string[] {\n  return [...new Set(paths.map((p) => path.normalize(p)))];\n}\n\n/**\n * Get file extension without dot\n */\nexport function getExtension(filePath: string): string {\n  const ext = path.extname(filePath);\n  return ext.startsWith('.') ? ext.slice(1) : ext;\n}\n\n/**\n * Get filename without extension\n */\nexport function getBasename(filePath: string): string {\n  return path.basename(filePath, path.extname(filePath));\n}\n\n/**\n * Join paths safely, handling undefined values\n */\nexport function joinPaths(...parts: (string | undefined)[]): string {\n  const validParts = parts.filter((p): p is string => p !== undefined && p !== '');\n  return path.join(...validParts);\n}\n"
  },
  {
    "path": "src/services/shared/toolRegistry.ts",
    "content": "/**\n * Unified Tool Registry\n *\n * Single source of truth for all AI tool configurations.\n * This eliminates duplication across import, export, sync, and reverse sync services.\n */\n\nimport * as path from 'path';\nimport * as os from 'os';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ToolCapabilities {\n  rules: boolean;\n  agents: boolean;\n  skills: boolean;\n}\n\nexport interface ToolPaths {\n  /** Primary export path for rules (relative to repo root) */\n  rulesExport?: string;\n  /** File extension to use when exporting rules directories */\n  rulesFileExtension?: string;\n  /** Import patterns for rules detection */\n  rulesImport?: string[];\n  /** Additional import paths for rules */\n  rulesImportPaths?: string[];\n  /** Export format for rules: 'single' file or 'directory' of files */\n  rulesFormat?: 'single' | 'directory';\n\n  /** Primary export path for agents (relative to repo root) */\n  agentsExport?: string;\n  /** Optional filename suffix to apply before `.md` when exporting agents */\n  agentsFilenameSuffix?: string;\n  /** Import patterns for agents detection */\n  agentsImport?: string[];\n\n  /** Primary export path for skills (relative to repo root) */\n  skillsExport?: string;\n  /** Import patterns for skills detection */\n  skillsImport?: string[];\n}\n\nexport interface ToolDefinition {\n  /** Unique identifier (e.g., 'claude', 'cursor') */\n  id: string;\n  /** Human-readable display name */\n  displayName: string;\n  /** Directory prefix for detection (e.g., '.claude', '.cursor') */\n  directoryPrefix: string;\n  /** What this tool supports */\n  capabilities: ToolCapabilities;\n  /** File paths for import/export operations */\n  paths: ToolPaths;\n  /** Description for CLI/docs */\n  description: string;\n  /** Special file patterns for detection (e.g., '.cursorrules', 'CLAUDE.md') */\n  specialFiles?: string[];\n}\n\n// ============================================================================\n// Tool Registry\n// ============================================================================\n\nexport const TOOL_REGISTRY: ToolDefinition[] = [\n  // Claude Code\n  {\n    id: 'claude',\n    displayName: 'Claude Code',\n    directoryPrefix: '.claude',\n    capabilities: { rules: true, agents: true, skills: true },\n    paths: {\n      rulesExport: 'CLAUDE.md',\n      rulesFormat: 'single',\n      rulesImport: ['**/.claude/memories/**/*.md', '**/.claude/**/*.memory', '**/.claude/settings.json'],\n      rulesImportPaths: ['.claude/memories', path.join(os.homedir(), '.claude', 'memories')],\n      agentsExport: '.claude/agents',\n      agentsImport: ['**/.claude/agents/**/*.md'],\n      skillsExport: '.claude/skills',\n      skillsImport: ['**/.claude/skills/*/SKILL.md', '**/.claude/skills/**/*.md'],\n    },\n    description: 'Claude Code main rules file',\n    specialFiles: ['CLAUDE.md'],\n  },\n\n  // Cursor AI\n  {\n    id: 'cursor',\n    displayName: 'Cursor AI',\n    directoryPrefix: '.cursor',\n    capabilities: { rules: true, agents: true, skills: false },\n    paths: {\n      rulesExport: '.cursor/rules',\n      rulesFormat: 'directory',\n      rulesFileExtension: '.mdc',\n      rulesImport: ['**/.cursorrules', '**/.cursor/.cursorrules', '**/.cursor/rules/**/*.md', '**/.cursor/rules/**/*.mdc'],\n      rulesImportPaths: ['.cursorrules', '.cursor/.cursorrules', '.cursor/rules'],\n      agentsExport: '.cursor/agents',\n      agentsImport: ['**/.cursor/agents/**/*.md'],\n    },\n    description: 'Cursor AI rules and agents',\n    specialFiles: ['.cursorrules'],\n  },\n\n  // GitHub Copilot\n  {\n    id: 'github',\n    displayName: 'GitHub Copilot',\n    directoryPrefix: '.github',\n    capabilities: { rules: true, agents: true, skills: true },\n    paths: {\n      rulesExport: '.github/copilot-instructions.md',\n      rulesFormat: 'single',\n      rulesImport: ['**/.github/copilot-instructions.md', '**/.github/instructions/**/*.instructions.md', '**/.github/copilot/**/*', '**/.github/.copilot/**/*'],\n      rulesImportPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/copilot'],\n      agentsExport: '.github/agents',\n      agentsFilenameSuffix: '.agent',\n      agentsImport: ['**/.github/agents/**/*.md', '**/.github/agents/**/*.agent.md'],\n      skillsExport: '.github/skills',\n      skillsImport: ['**/.github/skills/*/SKILL.md'],\n    },\n    description: 'GitHub Copilot instructions',\n  },\n\n  // Windsurf (Codeium)\n  {\n    id: 'windsurf',\n    displayName: 'Windsurf (Codeium)',\n    directoryPrefix: '.windsurf',\n    capabilities: { rules: true, agents: true, skills: true },\n    paths: {\n      rulesExport: '.windsurf/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.windsurfrules', '**/.windsurf/rules/**/*.md', '**/.windsurf/.windsurfrules'],\n      rulesImportPaths: ['.windsurfrules', '.windsurf/rules', '.windsurf/.windsurfrules'],\n      agentsExport: '.windsurf/agents',\n      agentsImport: ['**/.windsurf/agents/**/*.md'],\n      skillsExport: '.windsurf/skills',\n      skillsImport: ['**/.windsurf/skills/*/SKILL.md'],\n    },\n    description: 'Windsurf rules directory',\n    specialFiles: ['.windsurfrules'],\n  },\n\n  // Cline\n  {\n    id: 'cline',\n    displayName: 'Cline',\n    directoryPrefix: '.cline',\n    capabilities: { rules: true, agents: true, skills: false },\n    paths: {\n      rulesExport: '.cline/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.clinerules', '**/.cline/rules/**/*.md', '**/.cline/.clinerules'],\n      rulesImportPaths: ['.clinerules', '.cline/rules', '.cline/.clinerules'],\n      agentsExport: '.cline/agents',\n      agentsImport: ['**/.cline/agents/**/*.md'],\n    },\n    description: 'Cline VS Code extension',\n    specialFiles: ['.clinerules'],\n  },\n\n  // Continue.dev\n  {\n    id: 'continue',\n    displayName: 'Continue.dev',\n    directoryPrefix: '.continue',\n    capabilities: { rules: true, agents: true, skills: false },\n    paths: {\n      rulesExport: '.continue/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.continuerules', '**/.continue/config.json', '**/.continue/rules/**/*.md'],\n      rulesImportPaths: ['.continuerules', '.continue/config.json', '.continue/rules'],\n      agentsExport: '.continue/agents',\n      agentsImport: ['**/.continue/agents/**/*.md'],\n    },\n    description: 'Continue.dev agents directory',\n    specialFiles: ['.continuerules'],\n  },\n\n  // Google Antigravity\n  {\n    id: 'antigravity',\n    displayName: 'Google Antigravity',\n    directoryPrefix: '.agent',\n    capabilities: { rules: true, agents: true, skills: true },\n    paths: {\n      rulesExport: '.agents/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.agents/rules/**/*.md', '**/.agent/rules/**/*.md', '**/GEMINI.md'],\n      rulesImportPaths: ['.agents/rules', '.agent/rules', path.join(os.homedir(), '.gemini', 'GEMINI.md')],\n      agentsExport: '.agents/agents',\n      agentsImport: ['**/.agents/agents/**/*.md', '**/.agent/agents/**/*.md'],\n      skillsExport: '.agents/workflows',\n      skillsImport: ['**/.agents/workflows/*/SKILL.md', '**/.agents/workflows/**/*.md', '**/.agent/workflows/*/SKILL.md', '**/.agent/workflows/**/*.md'],\n    },\n    description: 'Google Antigravity rules directory',\n  },\n\n  // Trae AI\n  {\n    id: 'trae',\n    displayName: 'Trae AI',\n    directoryPrefix: '.trae',\n    capabilities: { rules: true, agents: true, skills: false },\n    paths: {\n      rulesExport: '.trae/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.trae/rules/**/*.md'],\n      rulesImportPaths: ['.trae/rules'],\n      agentsExport: '.trae/agents',\n      agentsImport: ['**/.trae/agents/**/*.md'],\n    },\n    description: 'Trae AI rules directory',\n  },\n\n  // Gemini CLI\n  {\n    id: 'gemini',\n    displayName: 'Gemini CLI',\n    directoryPrefix: '.gemini',\n    capabilities: { rules: true, agents: false, skills: true },\n    paths: {\n      rulesExport: 'GEMINI.md',\n      rulesFormat: 'single',\n      rulesImport: ['**/GEMINI.md'],\n      rulesImportPaths: ['GEMINI.md'],\n      skillsExport: '.gemini/skills',\n      skillsImport: ['**/.gemini/skills/*/SKILL.md', '**/.gemini/skills/**/*.md'],\n    },\n    description: 'Gemini CLI project instructions',\n  },\n\n  // Codex CLI\n  {\n    id: 'codex',\n    displayName: 'Codex CLI',\n    directoryPrefix: '.codex',\n    capabilities: { rules: true, agents: false, skills: true },\n    paths: {\n      rulesExport: 'AGENTS.md',\n      rulesFormat: 'single',\n      rulesImport: ['**/AGENTS.md', '**/.codex/instructions.md', '**/.codex/config.toml', '**/.codex/**/*.md'],\n      rulesImportPaths: ['AGENTS.md', '.codex/instructions.md', '.codex/config.toml'],\n      skillsExport: '.codex/skills',\n      skillsImport: ['**/.codex/skills/*/SKILL.md', '**/.codex/skills/**/*.md'],\n    },\n    description: 'Codex project instructions',\n  },\n\n  // Agent Skills (Cross-Client) - agentskills.io interoperability standard\n  {\n    id: 'agents',\n    displayName: 'Agent Skills (Cross-Client)',\n    directoryPrefix: '.agents',\n    capabilities: { rules: false, agents: false, skills: true },\n    paths: {\n      skillsExport: '.agents/skills',\n      skillsImport: ['**/.agents/skills/*/SKILL.md', '**/.agents/skills/**/*.md'],\n    },\n    description: 'Cross-client agent skills directory (agentskills.io)',\n  },\n\n  // Aider\n  {\n    id: 'aider',\n    displayName: 'Aider',\n    directoryPrefix: '.aider',\n    capabilities: { rules: true, agents: false, skills: false },\n    paths: {\n      rulesExport: 'CONVENTIONS.md',\n      rulesFormat: 'single',\n      rulesImport: ['**/CONVENTIONS.md', '**/.aider.conf.yml', '**/.aider/conventions.md', '**/.aider/**/*.md'],\n      rulesImportPaths: ['CONVENTIONS.md', '.aider.conf.yml', '.aider/conventions.md'],\n    },\n    description: 'Aider coding conventions',\n    specialFiles: ['CONVENTIONS.md'],\n  },\n\n  // Zed Editor\n  {\n    id: 'zed',\n    displayName: 'Zed Editor',\n    directoryPrefix: '.zed',\n    capabilities: { rules: true, agents: false, skills: false },\n    paths: {\n      rulesExport: '.zed/rules',\n      rulesFormat: 'directory',\n      rulesImport: ['**/.zed/settings.json', '**/.zed/rules/**/*.md'],\n      rulesImportPaths: ['.zed/settings.json', '.zed/rules'],\n    },\n    description: 'Zed editor AI settings',\n  },\n\n  // Claude Desktop\n  {\n    id: 'claude-desktop',\n    displayName: 'Claude Desktop',\n    directoryPrefix: '.claude-desktop',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Claude Desktop MCP configuration',\n  },\n\n  // VS Code (GitHub Copilot)\n  {\n    id: 'vscode',\n    displayName: 'VS Code (GitHub Copilot)',\n    directoryPrefix: '.vscode',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'VS Code GitHub Copilot MCP configuration',\n  },\n\n  // Roo Code\n  {\n    id: 'roo',\n    displayName: 'Roo Code',\n    directoryPrefix: '.roo',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Roo Code MCP configuration',\n  },\n\n  // Warp Terminal\n  {\n    id: 'warp',\n    displayName: 'Warp Terminal',\n    directoryPrefix: '.warp',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Warp Terminal MCP configuration',\n  },\n\n  // Amazon Q Developer CLI\n  {\n    id: 'amazonq',\n    displayName: 'Amazon Q Developer CLI',\n    directoryPrefix: '.amazonq',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Amazon Q Developer CLI MCP configuration',\n  },\n\n  // Gemini CLI\n  {\n    id: 'gemini-cli',\n    displayName: 'Gemini CLI',\n    directoryPrefix: '.gemini',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Gemini CLI MCP configuration',\n  },\n\n  // Kiro\n  {\n    id: 'kiro',\n    displayName: 'Kiro',\n    directoryPrefix: '.kiro',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Kiro MCP configuration',\n  },\n\n  // JetBrains IDEs\n  {\n    id: 'jetbrains',\n    displayName: 'JetBrains IDEs',\n    directoryPrefix: '.idea',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'JetBrains IDEs MCP configuration',\n  },\n\n  // Kilo Code\n  {\n    id: 'kilo',\n    displayName: 'Kilo Code',\n    directoryPrefix: '.kilo',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'Kilo Code MCP configuration',\n  },\n\n  // GitHub Copilot CLI\n  {\n    id: 'copilot-cli',\n    displayName: 'GitHub Copilot CLI',\n    directoryPrefix: '.copilot',\n    capabilities: { rules: false, agents: false, skills: false },\n    paths: {},\n    description: 'GitHub Copilot CLI MCP configuration',\n  },\n];\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Get a tool by its ID\n */\nexport function getToolById(id: string): ToolDefinition | undefined {\n  return TOOL_REGISTRY.find(t => t.id === id);\n}\n\n/**\n * Get all tool IDs\n */\nexport function getAllToolIds(): string[] {\n  return TOOL_REGISTRY.map(t => t.id);\n}\n\n/**\n * Get tools that support a specific capability\n */\nexport function getToolsWithCapability(capability: keyof ToolCapabilities): ToolDefinition[] {\n  return TOOL_REGISTRY.filter(t => t.capabilities[capability]);\n}\n\n/**\n * Get tool ID from a file path\n */\nexport function getToolIdFromPath(filePath: string): string {\n  const normalizedPath = filePath.replace(/\\\\/g, '/');\n\n  if (normalizedPath.includes('.agents/') || normalizedPath.startsWith('.agents')) {\n    return 'antigravity';\n  }\n\n  // Check directory prefixes\n  for (const tool of TOOL_REGISTRY) {\n    if (normalizedPath.includes(`${tool.directoryPrefix}/`) ||\n        normalizedPath.startsWith(tool.directoryPrefix)) {\n      return tool.id;\n    }\n  }\n\n  // Check special files\n  for (const tool of TOOL_REGISTRY) {\n    if (tool.specialFiles) {\n      for (const specialFile of tool.specialFiles) {\n        if (normalizedPath.includes(specialFile)) {\n          return tool.id;\n        }\n      }\n    }\n  }\n\n  return 'unknown';\n}\n\n/**\n * Get display name for a tool ID\n */\nexport function getToolDisplayName(toolId: string): string {\n  const tool = getToolById(toolId);\n  return tool?.displayName || toolId;\n}\n\n/**\n * Get tool capabilities\n */\nexport function getToolCapabilities(toolId: string): ToolCapabilities {\n  const tool = getToolById(toolId);\n  return tool?.capabilities || { rules: false, agents: false, skills: false };\n}\n\n/**\n * Build directory prefix to tool ID mapping\n */\nexport function getDirectoryPrefixMap(): Record<string, string> {\n  const map: Record<string, string> = {};\n  for (const tool of TOOL_REGISTRY) {\n    map[tool.directoryPrefix] = tool.id;\n  }\n  return map;\n}\n\n/**\n * Build tool ID to display name mapping\n */\nexport function getDisplayNameMap(): Record<string, string> {\n  const map: Record<string, string> = {};\n  for (const tool of TOOL_REGISTRY) {\n    map[tool.id] = tool.displayName;\n  }\n  return map;\n}\n\n/**\n * Build tool capabilities mapping\n */\nexport function getCapabilitiesMap(): Record<string, ToolCapabilities> {\n  const map: Record<string, ToolCapabilities> = {};\n  for (const tool of TOOL_REGISTRY) {\n    map[tool.id] = tool.capabilities;\n  }\n  return map;\n}\n\n// ============================================================================\n// Derived Presets (for backward compatibility)\n// ============================================================================\n\n/**\n * Get rules export presets (for exportRulesService)\n */\nexport function getRulesExportPresets(): Record<string, Array<{\n  name: string;\n  path: string;\n  format: 'single' | 'directory';\n  fileExtension?: string;\n  description: string;\n}>> {\n  const presets: Record<string, Array<{ name: string; path: string; format: 'single' | 'directory'; fileExtension?: string; description: string }>> = {};\n\n  for (const tool of getToolsWithCapability('rules')) {\n    if (tool.paths.rulesExport) {\n      presets[tool.id] = [{\n        name: `${tool.id}-rules`,\n        path: tool.paths.rulesExport,\n        format: tool.paths.rulesFormat || 'single',\n        fileExtension: tool.paths.rulesFileExtension,\n        description: tool.description,\n      }];\n    }\n  }\n\n  return presets;\n}\n\n/**\n * Get agents sync presets (for sync/presets)\n */\nexport function getAgentsSyncPresets(): Record<string, {\n  name: string;\n  path: string;\n  filenameSuffix?: string;\n  description: string;\n}> {\n  const presets: Record<string, { name: string; path: string; filenameSuffix?: string; description: string }> = {};\n\n  for (const tool of getToolsWithCapability('agents')) {\n    if (tool.paths.agentsExport) {\n      presets[tool.id] = {\n        name: tool.id,\n        path: tool.paths.agentsExport,\n        filenameSuffix: tool.paths.agentsFilenameSuffix,\n        description: `${tool.displayName} agents directory`,\n      };\n    }\n  }\n\n  return presets;\n}\n\n/**\n * Get skills export presets (for skillExportService)\n */\nexport function getSkillsExportPresets(): Record<string, Array<{\n  name: string;\n  path: string;\n  description: string;\n}>> {\n  const presets: Record<string, Array<{ name: string; path: string; description: string }>> = {};\n\n  for (const tool of getToolsWithCapability('skills')) {\n    if (tool.paths.skillsExport) {\n      presets[tool.id] = [{\n        name: `${tool.id}-skills`,\n        path: tool.paths.skillsExport,\n        description: `${tool.displayName} skills directory`,\n      }];\n    }\n  }\n\n  return presets;\n}\n\n/**\n * Get rules import sources (for import/presets)\n */\nexport function getRulesImportSources(): Array<{\n  name: string;\n  paths: string[];\n  patterns: string[];\n  description: string;\n}> {\n  return getToolsWithCapability('rules')\n    .filter(t => t.paths.rulesImport)\n    .map(t => ({\n      name: t.id === 'cursor' ? 'cursorrules' :\n            t.id === 'windsurf' ? 'windsurfrules' :\n            t.id === 'cline' ? 'clinerules' :\n            t.id,\n      paths: t.paths.rulesImportPaths || [],\n      patterns: t.paths.rulesImport || [],\n      description: t.description,\n    }));\n}\n\n/**\n * Get agents import sources (for import/presets)\n */\nexport function getAgentsImportSources(): Array<{\n  name: string;\n  paths: string[];\n  patterns: string[];\n  description: string;\n}> {\n  return getToolsWithCapability('agents')\n    .filter(t => t.paths.agentsImport)\n    .map(t => ({\n      name: `${t.id}-agents`,\n      paths: [t.paths.agentsExport || ''],\n      patterns: t.paths.agentsImport || [],\n      description: `${t.displayName} agents directory`,\n    }));\n}\n\n/**\n * Get skills import sources (for reverseSync/presets)\n */\nexport function getSkillsImportSources(): Array<{\n  name: string;\n  paths: string[];\n  patterns: string[];\n  description: string;\n}> {\n  return getToolsWithCapability('skills')\n    .filter(t => t.paths.skillsImport)\n    .map(t => ({\n      name: `${t.id}-skills`,\n      paths: [t.paths.skillsExport || ''],\n      patterns: t.paths.skillsImport || [],\n      description: `${t.displayName} skills directory`,\n    }));\n}\n"
  },
  {
    "path": "src/services/shared/types.ts",
    "content": "/**\n * Shared Service Types\n *\n * Common interfaces and types used across all services.\n * Reduces duplication of dependency injection patterns and result types.\n */\n\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\n\n/**\n * Base dependencies for all services\n */\nexport interface BaseDependencies {\n  ui: CLIInterface;\n  t: TranslateFn;\n  version: string;\n}\n\n/**\n * Common result type for file operations (import, export, sync)\n */\nexport interface OperationResult {\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  errors: OperationError[];\n}\n\n/**\n * Error entry in operation results\n */\nexport interface OperationError {\n  file: string;\n  error: string;\n}\n\n/**\n * Base file information for detection services\n */\nexport interface FileInfo {\n  name: string;\n  path: string;\n  relativePath: string;\n}\n\n/**\n * Detection result for file discovery services\n */\nexport interface DetectionResult<T extends FileInfo = FileInfo> {\n  files: T[];\n  sources: string[];\n}\n\n/**\n * Options for dry-run operations\n */\nexport interface DryRunOptions {\n  dryRun?: boolean;\n  verbose?: boolean;\n  force?: boolean;\n}\n\n/**\n * Create an empty operation result\n */\nexport function createEmptyResult(): OperationResult {\n  return {\n    filesCreated: 0,\n    filesSkipped: 0,\n    filesFailed: 0,\n    errors: [],\n  };\n}\n\n/**\n * Merge multiple operation results\n */\nexport function mergeResults(...results: OperationResult[]): OperationResult {\n  return results.reduce(\n    (acc, result) => ({\n      filesCreated: acc.filesCreated + result.filesCreated,\n      filesSkipped: acc.filesSkipped + result.filesSkipped,\n      filesFailed: acc.filesFailed + result.filesFailed,\n      errors: [...acc.errors, ...result.errors],\n    }),\n    createEmptyResult()\n  );\n}\n\n/**\n * Add an error to operation result\n */\nexport function addError(\n  result: OperationResult,\n  file: string,\n  error: unknown\n): void {\n  result.filesFailed++;\n  result.errors.push({\n    file,\n    error: error instanceof Error ? error.message : String(error),\n  });\n}\n"
  },
  {
    "path": "src/services/shared/uiHelpers.ts",
    "content": "/**\n * Shared UI Helpers\n *\n * Common UI interaction patterns used across services.\n * Reduces duplication of spinner and summary display logic.\n */\n\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type { OperationResult } from './types';\nimport { typography } from '../../utils/theme';\n\n/**\n * Status types for spinner updates\n */\nexport type SpinnerStatus = 'success' | 'fail' | 'warn' | 'info';\n\n/**\n * Execute an async operation with spinner feedback\n */\nexport async function withSpinner<T>(\n  ui: CLIInterface,\n  message: string,\n  fn: () => Promise<T>,\n  options?: {\n    successMessage?: string;\n    failMessage?: string;\n    successStatus?: SpinnerStatus;\n    failStatus?: SpinnerStatus;\n  }\n): Promise<{ result: T | null; success: boolean; error?: Error }> {\n  const {\n    successMessage,\n    failMessage,\n    successStatus = 'success',\n    failStatus = 'fail',\n  } = options || {};\n\n  ui.startSpinner(message);\n\n  try {\n    const result = await fn();\n\n    if (successMessage) {\n      ui.updateSpinner(successMessage, successStatus);\n    }\n\n    ui.stopSpinner();\n    return { result, success: true };\n  } catch (error) {\n    if (failMessage) {\n      ui.updateSpinner(failMessage, failStatus);\n    }\n\n    ui.stopSpinner();\n    return {\n      result: null,\n      success: false,\n      error: error instanceof Error ? error : new Error(String(error)),\n    };\n  }\n}\n\n/**\n * Display operation summary with consistent formatting\n */\nexport function displayOperationSummary(\n  result: OperationResult,\n  options?: {\n    title?: string;\n    dryRun?: boolean;\n    showErrors?: boolean;\n    labels?: {\n      created?: string;\n      skipped?: string;\n      failed?: string;\n    };\n  }\n): void {\n  const {\n    title = 'Summary',\n    dryRun = false,\n    showErrors = true,\n    labels = {},\n  } = options || {};\n\n  const {\n    created = dryRun ? 'Would create' : 'Created',\n    skipped = 'Skipped',\n    failed = 'Failed',\n  } = labels;\n\n  console.log('');\n  console.log(typography.header(title));\n  console.log(typography.labeledValue(created, String(result.filesCreated)));\n  console.log(typography.labeledValue(skipped, String(result.filesSkipped)));\n  console.log(typography.labeledValue(failed, String(result.filesFailed)));\n\n  if (showErrors && result.errors.length > 0) {\n    console.log('');\n    console.log(typography.header('Errors'));\n    for (const { file, error } of result.errors) {\n      console.log(typography.labeledValue(file, error));\n    }\n  }\n}\n\n/**\n * Display a visual progress bar\n */\nexport function displayProgressBar(\n  current: number,\n  total: number,\n  options?: {\n    width?: number;\n    label?: string;\n    showPercentage?: boolean;\n  }\n): string {\n  const { width = 40, label, showPercentage = true } = options || {};\n\n  const percentage = total > 0 ? Math.round((current / total) * 100) : 0;\n  const filledWidth = Math.round((current / total) * width);\n  const emptyWidth = width - filledWidth;\n\n  const filled = '█'.repeat(filledWidth);\n  const empty = '░'.repeat(emptyWidth);\n  const bar = `${filled}${empty}`;\n\n  let output = bar;\n  if (showPercentage) {\n    output += ` ${percentage}%`;\n  }\n  if (label) {\n    output += ` (${label})`;\n  }\n\n  return output;\n}\n\n/**\n * Display a visual phase indicator\n */\nexport function displayPhaseIndicator(\n  phases: Array<{ id: string; status: 'completed' | 'in_progress' | 'pending' | 'skipped' }>,\n  options?: {\n    currentLabel?: string;\n    symbols?: {\n      completed?: string;\n      inProgress?: string;\n      pending?: string;\n      skipped?: string;\n    };\n  }\n): string {\n  const { currentLabel, symbols = {} } = options || {};\n\n  const {\n    completed = '[x]',\n    inProgress = '[>]',\n    pending = '[ ]',\n    skipped = '[-]',\n  } = symbols;\n\n  const symbolMap = {\n    completed,\n    in_progress: inProgress,\n    pending,\n    skipped,\n  };\n\n  const parts = phases.map((phase) => {\n    const symbol = symbolMap[phase.status] || pending;\n    return `${symbol} ${phase.id}`;\n  });\n\n  let output = parts.join(' → ');\n\n  if (currentLabel) {\n    output += `\\n${' '.repeat(10)}^ ${currentLabel}`;\n  }\n\n  return output;\n}\n\n/**\n * Create a box with content\n */\nexport function createBox(\n  content: string[],\n  options?: {\n    width?: number;\n    title?: string;\n    padding?: number;\n  }\n): string {\n  const { width = 50, title, padding = 1 } = options || {};\n\n  const innerWidth = width - 2;\n  const padStr = ' '.repeat(padding);\n\n  const lines: string[] = [];\n\n  // Top border\n  if (title) {\n    const titlePadded = ` ${title} `;\n    const remainingWidth = innerWidth - titlePadded.length;\n    const leftWidth = Math.floor(remainingWidth / 2);\n    const rightWidth = remainingWidth - leftWidth;\n    lines.push(`╭${'─'.repeat(leftWidth)}${titlePadded}${'─'.repeat(rightWidth)}╮`);\n  } else {\n    lines.push(`╭${'─'.repeat(innerWidth)}╮`);\n  }\n\n  // Content\n  for (const line of content) {\n    const contentWidth = innerWidth - padding * 2;\n    const paddedLine = line.slice(0, contentWidth).padEnd(contentWidth);\n    lines.push(`│${padStr}${paddedLine}${padStr}│`);\n  }\n\n  // Bottom border\n  lines.push(`╰${'─'.repeat(innerWidth)}╯`);\n\n  return lines.join('\\n');\n}\n"
  },
  {
    "path": "src/services/stack/index.ts",
    "content": "export { StackDetector } from './stackDetector';\nexport type { StackInfo } from './stackDetector';\n\nexport {\n  classifyProject,\n  detectCLILibraries,\n  isLibraryPackage,\n  PROJECT_TYPES,\n} from './projectTypeClassifier';\nexport type {\n  ProjectType,\n  ProjectClassification,\n  ExtendedStackInfo,\n} from './projectTypeClassifier';\n\nexport {\n  getAgentsForProjectType,\n  getDocsForProjectType,\n  getSkillsForProjectType,\n  getFilteredScaffolds,\n  shouldIncludeAgent,\n  shouldIncludeDoc,\n  shouldIncludeSkill,\n  CORE_AGENTS,\n  CORE_DOCS,\n  CORE_SKILLS,\n  AGENT_FILTER_MATRIX,\n  DOCS_FILTER_MATRIX,\n  SKILLS_FILTER_MATRIX,\n} from './scaffoldFilter';\n"
  },
  {
    "path": "src/services/stack/projectTypeClassifier.ts",
    "content": "/**\n * Project Type Classifier\n *\n * Classifies projects into categories based on detected stack information.\n * Used to filter which agents, skills, and documentation are relevant.\n */\n\nimport { StackInfo } from './stackDetector';\n\nexport const PROJECT_TYPES = [\n  'cli',\n  'web-frontend',\n  'web-backend',\n  'full-stack',\n  'mobile',\n  'library',\n  'monorepo',\n  'desktop',\n  'unknown',\n] as const;\n\nexport type ProjectType = (typeof PROJECT_TYPES)[number];\n\nexport interface ProjectClassification {\n  primaryType: ProjectType;\n  secondaryTypes: ProjectType[];\n  confidence: 'high' | 'medium' | 'low';\n  reasoning: string[];\n}\n\n/**\n * Framework categories for project type detection.\n * Exported for testing and extensibility.\n */\nexport const FRAMEWORK_CATEGORIES = {\n  frontend: [\n    'react',\n    'vue',\n    'angular',\n    'svelte',\n    'nextjs',\n    'nuxt',\n    'gatsby',\n    'remix',\n    'astro',\n    'solid',\n    'qwik',\n  ],\n  backend: [\n    'express',\n    'nestjs',\n    'fastify',\n    'koa',\n    'hapi',\n    'django',\n    'flask',\n    'fastapi',\n    'rails',\n    'laravel',\n    'spring',\n    'phoenix',\n    'gin',\n    'echo',\n    'fiber',\n  ],\n  mobile: ['react-native', 'flutter', 'ionic', 'capacitor', 'expo'],\n  desktop: ['electron', 'tauri', 'neutralino'],\n  cli: [\n    'commander',\n    'yargs',\n    'meow',\n    'inquirer',\n    'prompts',\n    'chalk',\n    'ora',\n    'listr',\n    'oclif',\n    'clipanion',\n    'cac',\n    'arg',\n  ],\n  monorepo: ['lerna', 'nx', 'turborepo', 'pnpm-workspace', 'rush'],\n} as const;\n\n/** Confidence thresholds for classification */\nconst CONFIDENCE_THRESHOLDS = {\n  high: 3, // 3+ reasons = high confidence\n  medium: 1, // 1-2 reasons = medium confidence\n} as const;\n\n/**\n * Extended stack info with additional fields for classification\n */\nexport interface ExtendedStackInfo extends StackInfo {\n  hasBinField?: boolean;\n  hasMainExport?: boolean;\n  hasTypesField?: boolean;\n  cliLibraries?: string[];\n}\n\n/**\n * Helper to check if any framework matches a category (case-insensitive)\n */\nfunction matchesCategory(frameworks: string[], category: readonly string[]): boolean {\n  return frameworks.some((f) =>\n    category.some((c) => f.toLowerCase().includes(c))\n  );\n}\n\n/**\n * Helper to get matching frameworks from a category\n */\nfunction getMatchingFrameworks(frameworks: string[], category: readonly string[]): string[] {\n  return frameworks.filter((f) =>\n    category.some((c) => f.toLowerCase().includes(c))\n  );\n}\n\n/**\n * Classify a project based on its detected stack\n */\nexport function classifyProject(stack: StackInfo | ExtendedStackInfo): ProjectClassification {\n  const reasoning: string[] = [];\n  const detectedTypes: ProjectType[] = [];\n  const extStack = stack as ExtendedStackInfo;\n\n  // Check for monorepo first (can contain other types)\n  const hasMonorepoTools = stack.isMonorepo ||\n    stack.buildTools.some((t) => FRAMEWORK_CATEGORIES.monorepo.includes(t as typeof FRAMEWORK_CATEGORIES.monorepo[number]));\n  if (hasMonorepoTools) {\n    detectedTypes.push('monorepo');\n    reasoning.push('Monorepo tools detected (lerna, nx, turborepo, or pnpm-workspace)');\n  }\n\n  // Check for mobile project\n  const hasMobileFramework = matchesCategory(stack.frameworks, FRAMEWORK_CATEGORIES.mobile);\n  if (hasMobileFramework) {\n    detectedTypes.push('mobile');\n    const matched = getMatchingFrameworks(stack.frameworks, FRAMEWORK_CATEGORIES.mobile);\n    reasoning.push(`Mobile framework detected: ${matched.join(', ')}`);\n  }\n\n  // Check for desktop project\n  const hasDesktopFramework = matchesCategory(stack.frameworks, FRAMEWORK_CATEGORIES.desktop);\n  if (hasDesktopFramework) {\n    detectedTypes.push('desktop');\n    const matched = getMatchingFrameworks(stack.frameworks, FRAMEWORK_CATEGORIES.desktop);\n    reasoning.push(`Desktop framework detected: ${matched.join(', ')}`);\n  }\n\n  // Check for frontend and backend frameworks\n  const hasFrontendFramework = matchesCategory(stack.frameworks, FRAMEWORK_CATEGORIES.frontend);\n  const hasBackendFramework = matchesCategory(stack.frameworks, FRAMEWORK_CATEGORIES.backend);\n\n  // Determine web project type\n  if (hasFrontendFramework && hasBackendFramework) {\n    detectedTypes.push('full-stack');\n    reasoning.push('Both frontend and backend frameworks detected');\n  } else if (hasFrontendFramework) {\n    detectedTypes.push('web-frontend');\n    const matched = getMatchingFrameworks(stack.frameworks, FRAMEWORK_CATEGORIES.frontend);\n    reasoning.push(`Frontend framework detected: ${matched.join(', ')}`);\n  } else if (hasBackendFramework) {\n    detectedTypes.push('web-backend');\n    const matched = getMatchingFrameworks(stack.frameworks, FRAMEWORK_CATEGORIES.backend);\n    reasoning.push(`Backend framework detected: ${matched.join(', ')}`);\n  }\n\n  // Check for CLI project\n  const hasCLIIndicators =\n    extStack.hasBinField ||\n    (extStack.cliLibraries && extStack.cliLibraries.length > 0) ||\n    stack.files.some((f) => f.match(/^(bin\\/|cli\\.(js|ts)$)/));\n\n  if (hasCLIIndicators && !hasFrontendFramework && !hasMobileFramework) {\n    detectedTypes.push('cli');\n    if (extStack.hasBinField) {\n      reasoning.push('package.json has bin field');\n    }\n    if (extStack.cliLibraries && extStack.cliLibraries.length > 0) {\n      reasoning.push(`CLI libraries detected: ${extStack.cliLibraries.join(', ')}`);\n    }\n    if (stack.files.some((f) => f.match(/^(bin\\/|cli\\.(js|ts)$)/))) {\n      reasoning.push('CLI entry files detected (bin/ or cli.js/ts)');\n    }\n  }\n\n  // Check for library/package\n  const isLibrary =\n    extStack.hasMainExport &&\n    !extStack.hasBinField &&\n    !hasFrontendFramework &&\n    !hasBackendFramework &&\n    !hasMobileFramework;\n\n  if (isLibrary) {\n    detectedTypes.push('library');\n    reasoning.push('Has main/exports field without bin, frontend, or backend indicators');\n    if (extStack.hasTypesField) {\n      reasoning.push('Has types/typings field (TypeScript library)');\n    }\n  }\n\n  // Determine primary and secondary types based on priority\n  const { primaryType, secondaryTypes } = prioritizeTypes(detectedTypes);\n\n  // Determine confidence based on reasoning count\n  const confidence = calculateConfidence(reasoning.length);\n\n  // Add fallback reasoning if nothing detected\n  if (primaryType === 'unknown') {\n    reasoning.push('Unable to determine project type from detected stack');\n    if (stack.primaryLanguage) {\n      reasoning.push(`Primary language: ${stack.primaryLanguage}`);\n    }\n  }\n\n  return { primaryType, secondaryTypes, confidence, reasoning };\n}\n\n/**\n * Type priority order for classification.\n * Higher priority types take precedence when multiple are detected.\n */\nconst TYPE_PRIORITY: readonly ProjectType[] = [\n  'monorepo',\n  'full-stack',\n  'mobile',\n  'desktop',\n  'web-frontend',\n  'web-backend',\n  'cli',\n  'library',\n];\n\n/**\n * Prioritize detected types and return primary + secondary\n */\nfunction prioritizeTypes(detectedTypes: ProjectType[]): {\n  primaryType: ProjectType;\n  secondaryTypes: ProjectType[];\n} {\n  let primaryType: ProjectType = 'unknown';\n  const secondaryTypes: ProjectType[] = [];\n\n  for (const type of TYPE_PRIORITY) {\n    if (detectedTypes.includes(type)) {\n      if (primaryType === 'unknown') {\n        primaryType = type;\n      } else {\n        secondaryTypes.push(type);\n      }\n    }\n  }\n\n  return { primaryType, secondaryTypes };\n}\n\n/**\n * Calculate confidence level based on reasoning count\n */\nfunction calculateConfidence(reasoningCount: number): 'high' | 'medium' | 'low' {\n  if (reasoningCount >= CONFIDENCE_THRESHOLDS.high) {\n    return 'high';\n  }\n  if (reasoningCount >= CONFIDENCE_THRESHOLDS.medium) {\n    return 'medium';\n  }\n  return 'low';\n}\n\n/**\n * Get CLI libraries from dependencies\n */\nexport function detectCLILibraries(dependencies: Record<string, string>): string[] {\n  return FRAMEWORK_CATEGORIES.cli.filter((lib) => lib in dependencies);\n}\n\n/**\n * Check if package.json indicates a library\n */\nexport function isLibraryPackage(packageJson: {\n  main?: string;\n  exports?: unknown;\n  bin?: unknown;\n  types?: string;\n  typings?: string;\n}): { hasMain: boolean; hasBin: boolean; hasTypes: boolean } {\n  return {\n    hasMain: !!(packageJson.main || packageJson.exports),\n    hasBin: !!packageJson.bin,\n    hasTypes: !!(packageJson.types || packageJson.typings),\n  };\n}\n"
  },
  {
    "path": "src/services/stack/scaffoldFilter.ts",
    "content": "/**\n * Scaffold Filter\n *\n * Provides filtering logic to determine which agents, docs, and skills\n * are relevant for each project type.\n *\n * Design: Uses a \"core + extras\" pattern for maintainability.\n * Core items are always included; extras are added per project type.\n */\n\nimport { ProjectType, PROJECT_TYPES } from './projectTypeClassifier';\nimport { AgentType, AGENT_TYPES } from '../../generators/agents/agentTypes';\nimport { DOCUMENT_GUIDE_KEYS } from '../../generators/documentation/guideRegistry';\nimport { BuiltInSkillType, BUILT_IN_SKILLS } from '../../workflow/skills/types';\n\n/**\n * Core agents that are always included regardless of project type\n */\nexport const CORE_AGENTS: readonly AgentType[] = [\n  'code-reviewer',\n  'bug-fixer',\n  'feature-developer',\n  'refactoring-specialist',\n  'test-writer',\n  'documentation-writer',\n  'performance-optimizer',\n] as const;\n\n/**\n * Core documentation that is always included\n */\nexport const CORE_DOCS: readonly string[] = [\n  'project-overview',\n  'development-workflow',\n  'testing-strategy',\n  'tooling',\n] as const;\n\n/**\n * Core skills that are always included\n */\nexport const CORE_SKILLS: readonly BuiltInSkillType[] = [\n  'commit-message',\n  'pr-review',\n  'code-review',\n  'test-generation',\n  'documentation',\n  'refactoring',\n  'bug-investigation',\n] as const;\n\n/**\n * Extra agents to include for specific project types (beyond core)\n */\nconst EXTRA_AGENTS_BY_TYPE: Record<ProjectType, readonly AgentType[]> = {\n  cli: [],\n  'web-frontend': ['security-auditor', 'frontend-specialist', 'architect-specialist', 'devops-specialist'],\n  'web-backend': ['security-auditor', 'backend-specialist', 'architect-specialist', 'devops-specialist', 'database-specialist'],\n  'full-stack': ['security-auditor', 'backend-specialist', 'frontend-specialist', 'architect-specialist', 'devops-specialist', 'database-specialist'],\n  mobile: ['security-auditor', 'architect-specialist', 'devops-specialist', 'mobile-specialist'],\n  library: [],\n  monorepo: ['security-auditor', 'backend-specialist', 'frontend-specialist', 'architect-specialist', 'devops-specialist', 'database-specialist', 'mobile-specialist'],\n  desktop: ['security-auditor', 'frontend-specialist', 'architect-specialist', 'devops-specialist'],\n  unknown: ['security-auditor', 'backend-specialist', 'frontend-specialist', 'architect-specialist', 'devops-specialist', 'database-specialist', 'mobile-specialist'],\n};\n\n/**\n * Extra docs to include for specific project types (beyond core)\n */\nconst EXTRA_DOCS_BY_TYPE: Record<ProjectType, readonly string[]> = {\n  cli: [],\n  'web-frontend': ['architecture', 'glossary', 'security'],\n  'web-backend': ['architecture', 'glossary', 'data-flow', 'security'],\n  'full-stack': ['architecture', 'glossary', 'data-flow', 'security'],\n  mobile: ['architecture', 'glossary', 'security'],\n  library: [],\n  monorepo: ['architecture', 'glossary', 'data-flow', 'security'],\n  desktop: ['architecture', 'glossary', 'security'],\n  unknown: ['architecture', 'glossary', 'data-flow', 'security'],\n};\n\n/**\n * Extra skills to include for specific project types (beyond core)\n */\nconst EXTRA_SKILLS_BY_TYPE: Record<ProjectType, readonly BuiltInSkillType[]> = {\n  cli: [],\n  'web-frontend': ['feature-breakdown', 'security-audit'],\n  'web-backend': ['feature-breakdown', 'api-design', 'security-audit'],\n  'full-stack': ['feature-breakdown', 'api-design', 'security-audit'],\n  mobile: ['feature-breakdown', 'security-audit'],\n  library: ['api-design'],\n  monorepo: ['feature-breakdown', 'api-design', 'security-audit'],\n  desktop: ['feature-breakdown', 'security-audit'],\n  unknown: ['feature-breakdown', 'api-design', 'security-audit'],\n};\n\n// Build filter matrices from core + extras (for backward compatibility with existing exports)\nfunction buildAgentMatrix(): Record<ProjectType, Record<AgentType, boolean>> {\n  const matrix = {} as Record<ProjectType, Record<AgentType, boolean>>;\n\n  for (const projectType of PROJECT_TYPES) {\n    const included = new Set([...CORE_AGENTS, ...EXTRA_AGENTS_BY_TYPE[projectType]]);\n    matrix[projectType] = {} as Record<AgentType, boolean>;\n\n    for (const agent of AGENT_TYPES) {\n      matrix[projectType][agent] = included.has(agent);\n    }\n  }\n\n  return matrix;\n}\n\nfunction buildDocsMatrix(): Record<ProjectType, Record<string, boolean>> {\n  const matrix = {} as Record<ProjectType, Record<string, boolean>>;\n\n  for (const projectType of PROJECT_TYPES) {\n    const included = new Set([...CORE_DOCS, ...EXTRA_DOCS_BY_TYPE[projectType]]);\n    matrix[projectType] = {} as Record<string, boolean>;\n\n    for (const doc of DOCUMENT_GUIDE_KEYS) {\n      matrix[projectType][doc] = included.has(doc);\n    }\n  }\n\n  return matrix;\n}\n\nfunction buildSkillsMatrix(): Record<ProjectType, Record<BuiltInSkillType, boolean>> {\n  const matrix = {} as Record<ProjectType, Record<BuiltInSkillType, boolean>>;\n\n  for (const projectType of PROJECT_TYPES) {\n    const included = new Set([...CORE_SKILLS, ...EXTRA_SKILLS_BY_TYPE[projectType]]);\n    matrix[projectType] = {} as Record<BuiltInSkillType, boolean>;\n\n    for (const skill of BUILT_IN_SKILLS) {\n      matrix[projectType][skill] = included.has(skill);\n    }\n  }\n\n  return matrix;\n}\n\n/**\n * Agent filtering matrix - which agents are relevant for each project type\n * Built from CORE_AGENTS + EXTRA_AGENTS_BY_TYPE for maintainability\n */\nexport const AGENT_FILTER_MATRIX = buildAgentMatrix();\n\n/**\n * Documentation filtering matrix - which docs are relevant for each project type\n * Built from CORE_DOCS + EXTRA_DOCS_BY_TYPE for maintainability\n */\nexport const DOCS_FILTER_MATRIX = buildDocsMatrix();\n\n/**\n * Skills filtering matrix - which skills are relevant for each project type\n * Built from CORE_SKILLS + EXTRA_SKILLS_BY_TYPE for maintainability\n */\nexport const SKILLS_FILTER_MATRIX = buildSkillsMatrix();\n\n/**\n * Get filtered list of agents for a project type\n * Uses core + extras pattern for efficiency\n */\nexport function getAgentsForProjectType(projectType: ProjectType): AgentType[] {\n  const extras = EXTRA_AGENTS_BY_TYPE[projectType] ?? [];\n  return [...CORE_AGENTS, ...extras] as AgentType[];\n}\n\n/**\n * Get filtered list of documentation keys for a project type\n * Uses core + extras pattern for efficiency\n */\nexport function getDocsForProjectType(projectType: ProjectType): string[] {\n  const extras = EXTRA_DOCS_BY_TYPE[projectType] ?? [];\n  return [...CORE_DOCS, ...extras];\n}\n\n/**\n * Get filtered list of skills for a project type\n * Uses core + extras pattern for efficiency\n */\nexport function getSkillsForProjectType(projectType: ProjectType): BuiltInSkillType[] {\n  const extras = EXTRA_SKILLS_BY_TYPE[projectType] ?? [];\n  return [...CORE_SKILLS, ...extras] as BuiltInSkillType[];\n}\n\n/**\n * Get all scaffolds filtered by project type\n */\nexport function getFilteredScaffolds(projectType: ProjectType): {\n  agents: AgentType[];\n  docs: string[];\n  skills: BuiltInSkillType[];\n} {\n  return {\n    agents: getAgentsForProjectType(projectType),\n    docs: getDocsForProjectType(projectType),\n    skills: getSkillsForProjectType(projectType),\n  };\n}\n\n/**\n * Check if a specific agent should be included for a project type\n */\nexport function shouldIncludeAgent(projectType: ProjectType, agent: AgentType): boolean {\n  const included = new Set(getAgentsForProjectType(projectType));\n  return included.has(agent);\n}\n\n/**\n * Check if a specific doc should be included for a project type\n */\nexport function shouldIncludeDoc(projectType: ProjectType, doc: string): boolean {\n  const included = new Set(getDocsForProjectType(projectType));\n  return included.has(doc);\n}\n\n/**\n * Check if a specific skill should be included for a project type\n */\nexport function shouldIncludeSkill(projectType: ProjectType, skill: BuiltInSkillType): boolean {\n  const included = new Set(getSkillsForProjectType(projectType));\n  return included.has(skill);\n}\n\n/**\n * Validation: Ensure all project types have entries in extras maps.\n * Called at module load time to catch configuration errors early.\n */\nfunction validateConfiguration(): void {\n  for (const projectType of PROJECT_TYPES) {\n    if (!(projectType in EXTRA_AGENTS_BY_TYPE)) {\n      throw new Error(`Missing EXTRA_AGENTS_BY_TYPE entry for project type: ${projectType}`);\n    }\n    if (!(projectType in EXTRA_DOCS_BY_TYPE)) {\n      throw new Error(`Missing EXTRA_DOCS_BY_TYPE entry for project type: ${projectType}`);\n    }\n    if (!(projectType in EXTRA_SKILLS_BY_TYPE)) {\n      throw new Error(`Missing EXTRA_SKILLS_BY_TYPE entry for project type: ${projectType}`);\n    }\n  }\n}\n\n// Validate configuration at module load time\nvalidateConfiguration();\n"
  },
  {
    "path": "src/services/stack/stackDetector.ts",
    "content": "/**\n * Stack Detector Service\n *\n * Detects the technology stack of a project for intelligent defaults.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\n\nexport interface StackInfo {\n  primaryLanguage: string | null;\n  languages: string[];\n  frameworks: string[];\n  buildTools: string[];\n  testFrameworks: string[];\n  files: string[];\n  packageManager: string | null;\n  isMonorepo: boolean;\n  hasDocker: boolean;\n  hasCI: boolean;\n  // Extended fields for project classification\n  hasBinField?: boolean;\n  hasMainExport?: boolean;\n  hasTypesField?: boolean;\n  cliLibraries?: string[];\n}\n\ninterface DetectionRule {\n  file: string | string[];\n  language?: string;\n  framework?: string;\n  buildTool?: string;\n  testFramework?: string;\n  packageManager?: string;\n}\n\nconst DETECTION_RULES: DetectionRule[] = [\n  // CLI indicators\n  { file: 'bin/**/*', framework: 'cli' },\n  { file: 'cli.js', framework: 'cli' },\n  { file: 'cli.ts', framework: 'cli' },\n\n  // JavaScript/TypeScript\n  { file: 'package.json', language: 'javascript' },\n  { file: 'tsconfig.json', language: 'typescript' },\n  { file: 'next.config.js', framework: 'nextjs' },\n  { file: 'next.config.mjs', framework: 'nextjs' },\n  { file: 'next.config.ts', framework: 'nextjs' },\n  { file: 'nuxt.config.js', framework: 'nuxt' },\n  { file: 'nuxt.config.ts', framework: 'nuxt' },\n  { file: 'vite.config.js', buildTool: 'vite' },\n  { file: 'vite.config.ts', buildTool: 'vite' },\n  { file: 'webpack.config.js', buildTool: 'webpack' },\n  { file: 'angular.json', framework: 'angular' },\n  { file: 'vue.config.js', framework: 'vue' },\n  { file: 'svelte.config.js', framework: 'svelte' },\n  { file: 'astro.config.mjs', framework: 'astro' },\n  { file: 'remix.config.js', framework: 'remix' },\n  { file: 'gatsby-config.js', framework: 'gatsby' },\n  { file: 'nest-cli.json', framework: 'nestjs' },\n  { file: 'jest.config.js', testFramework: 'jest' },\n  { file: 'jest.config.ts', testFramework: 'jest' },\n  { file: 'vitest.config.ts', testFramework: 'vitest' },\n  { file: 'playwright.config.ts', testFramework: 'playwright' },\n  { file: 'cypress.config.ts', testFramework: 'cypress' },\n  { file: 'yarn.lock', packageManager: 'yarn' },\n  { file: 'pnpm-lock.yaml', packageManager: 'pnpm' },\n  { file: 'bun.lockb', packageManager: 'bun' },\n  { file: 'package-lock.json', packageManager: 'npm' },\n\n  // Python\n  { file: 'pyproject.toml', language: 'python' },\n  { file: 'setup.py', language: 'python' },\n  { file: 'requirements.txt', language: 'python' },\n  { file: 'Pipfile', language: 'python', packageManager: 'pipenv' },\n  { file: 'poetry.lock', language: 'python', packageManager: 'poetry' },\n  { file: 'manage.py', framework: 'django' },\n  { file: 'app.py', framework: 'flask' },\n  { file: 'fastapi', framework: 'fastapi' },\n  { file: 'pytest.ini', testFramework: 'pytest' },\n  { file: 'conftest.py', testFramework: 'pytest' },\n\n  // Go\n  { file: 'go.mod', language: 'go' },\n  { file: 'go.sum', language: 'go' },\n\n  // Rust\n  { file: 'Cargo.toml', language: 'rust' },\n  { file: 'Cargo.lock', language: 'rust' },\n\n  // Java/Kotlin\n  { file: 'pom.xml', language: 'java', buildTool: 'maven' },\n  { file: 'build.gradle', language: 'java', buildTool: 'gradle' },\n  { file: 'build.gradle.kts', language: 'kotlin', buildTool: 'gradle' },\n  { file: 'settings.gradle', buildTool: 'gradle' },\n  { file: 'gradlew', buildTool: 'gradle' },\n\n  // Ruby\n  { file: 'Gemfile', language: 'ruby' },\n  { file: 'Gemfile.lock', language: 'ruby' },\n  { file: 'config/routes.rb', framework: 'rails' },\n\n  // PHP\n  { file: 'composer.json', language: 'php' },\n  { file: 'artisan', framework: 'laravel' },\n\n  // C#/.NET\n  { file: '*.csproj', language: 'csharp' },\n  { file: '*.sln', language: 'csharp' },\n\n  // Swift\n  { file: 'Package.swift', language: 'swift' },\n  { file: '*.xcodeproj', language: 'swift' },\n\n  // Mobile frameworks\n  { file: 'app.json', framework: 'react-native' },\n  { file: 'metro.config.js', framework: 'react-native' },\n  { file: 'metro.config.ts', framework: 'react-native' },\n  { file: 'react-native.config.js', framework: 'react-native' },\n  { file: 'pubspec.yaml', framework: 'flutter' },\n  { file: 'android/app/build.gradle', framework: 'android-native' },\n  { file: 'ios/*.xcworkspace', framework: 'ios-native' },\n  { file: 'capacitor.config.ts', framework: 'capacitor' },\n  { file: 'capacitor.config.json', framework: 'capacitor' },\n  { file: 'ionic.config.json', framework: 'ionic' },\n\n  // Desktop frameworks\n  { file: 'electron.config.js', framework: 'electron' },\n  { file: 'electron-builder.yml', framework: 'electron' },\n  { file: 'electron-builder.json', framework: 'electron' },\n  { file: 'forge.config.js', framework: 'electron' },\n  { file: 'tauri.conf.json', framework: 'tauri' },\n  { file: 'src-tauri/tauri.conf.json', framework: 'tauri' },\n  { file: 'neutralino.config.json', framework: 'neutralino' },\n\n  // Elixir\n  { file: 'mix.exs', language: 'elixir' },\n  { file: 'config/config.exs', framework: 'phoenix' },\n\n  // Infrastructure\n  { file: 'Dockerfile', buildTool: 'docker' },\n  { file: 'docker-compose.yml', buildTool: 'docker-compose' },\n  { file: 'docker-compose.yaml', buildTool: 'docker-compose' },\n  { file: 'terraform', buildTool: 'terraform' },\n  { file: 'kubernetes', buildTool: 'kubernetes' },\n  { file: 'k8s', buildTool: 'kubernetes' },\n  { file: 'helm', buildTool: 'helm' },\n\n  // Monorepo\n  { file: 'lerna.json', buildTool: 'lerna' },\n  { file: 'nx.json', buildTool: 'nx' },\n  { file: 'turbo.json', buildTool: 'turborepo' },\n  { file: 'pnpm-workspace.yaml', buildTool: 'pnpm-workspace' },\n];\n\nconst LANGUAGE_EXTENSIONS: Record<string, string> = {\n  '.ts': 'typescript',\n  '.tsx': 'typescript',\n  '.js': 'javascript',\n  '.jsx': 'javascript',\n  '.mjs': 'javascript',\n  '.py': 'python',\n  '.go': 'go',\n  '.rs': 'rust',\n  '.java': 'java',\n  '.kt': 'kotlin',\n  '.rb': 'ruby',\n  '.php': 'php',\n  '.cs': 'csharp',\n  '.swift': 'swift',\n  '.ex': 'elixir',\n  '.exs': 'elixir',\n  '.cpp': 'cpp',\n  '.c': 'c',\n  '.h': 'c',\n  '.hpp': 'cpp',\n};\n\nexport class StackDetector {\n  /**\n   * Detect the technology stack of a project\n   */\n  async detect(repoPath: string): Promise<StackInfo> {\n    const absolutePath = path.resolve(repoPath);\n\n    const result: StackInfo = {\n      primaryLanguage: null,\n      languages: [],\n      frameworks: [],\n      buildTools: [],\n      testFrameworks: [],\n      files: [],\n      packageManager: null,\n      isMonorepo: false,\n      hasDocker: false,\n      hasCI: false,\n      // Extended fields\n      hasBinField: false,\n      hasMainExport: false,\n      hasTypesField: false,\n      cliLibraries: [],\n    };\n\n    // Check detection rules\n    for (const rule of DETECTION_RULES) {\n      const files = Array.isArray(rule.file) ? rule.file : [rule.file];\n\n      for (const file of files) {\n        const matches = await this.checkFileExists(absolutePath, file);\n        if (matches.length > 0) {\n          result.files.push(...matches);\n\n          if (rule.language && !result.languages.includes(rule.language)) {\n            result.languages.push(rule.language);\n          }\n          if (rule.framework && !result.frameworks.includes(rule.framework)) {\n            result.frameworks.push(rule.framework);\n          }\n          if (rule.buildTool && !result.buildTools.includes(rule.buildTool)) {\n            result.buildTools.push(rule.buildTool);\n          }\n          if (rule.testFramework && !result.testFrameworks.includes(rule.testFramework)) {\n            result.testFrameworks.push(rule.testFramework);\n          }\n          if (rule.packageManager && !result.packageManager) {\n            result.packageManager = rule.packageManager;\n          }\n        }\n      }\n    }\n\n    // Detect languages by file extensions\n    const languageCounts = await this.countLanguagesByExtension(absolutePath);\n    for (const [lang, count] of Object.entries(languageCounts)) {\n      if (count > 0 && !result.languages.includes(lang)) {\n        result.languages.push(lang);\n      }\n    }\n\n    // Determine primary language (most files)\n    if (result.languages.length > 0) {\n      const langPriority = ['typescript', 'javascript', 'python', 'go', 'rust', 'java', 'kotlin', 'ruby', 'php', 'csharp', 'swift'];\n      result.primaryLanguage = langPriority.find(l => result.languages.includes(l)) || result.languages[0];\n    }\n\n    // Check for monorepo indicators\n    result.isMonorepo = result.buildTools.some(t =>\n      ['lerna', 'nx', 'turborepo', 'pnpm-workspace'].includes(t)\n    );\n\n    // Check for Docker\n    result.hasDocker = result.buildTools.includes('docker') || result.buildTools.includes('docker-compose');\n\n    // Check for CI\n    result.hasCI = await this.hasCI(absolutePath);\n\n    // Analyze package.json for additional indicators\n    const packageInfo = await this.analyzePackageJson(absolutePath);\n    if (packageInfo) {\n      result.hasBinField = packageInfo.hasBin;\n      result.hasMainExport = packageInfo.hasMain;\n      result.hasTypesField = packageInfo.hasTypes;\n      result.cliLibraries = packageInfo.cliLibraries;\n    }\n\n    return result;\n  }\n\n  /**\n   * Get recommended agents based on stack\n   */\n  getRecommendedAgents(stack: StackInfo): string[] {\n    const agents: string[] = ['feature-developer', 'bug-fixer', 'code-reviewer'];\n\n    if (stack.primaryLanguage) {\n      if (['typescript', 'javascript'].includes(stack.primaryLanguage)) {\n        if (stack.frameworks.some(f => ['nextjs', 'nuxt', 'vue', 'angular', 'svelte', 'react'].includes(f))) {\n          agents.push('frontend-specialist');\n        }\n        if (stack.frameworks.some(f => ['nestjs', 'express', 'fastify'].includes(f))) {\n          agents.push('backend-specialist');\n        }\n      }\n\n      if (['python', 'java', 'go', 'rust', 'csharp'].includes(stack.primaryLanguage)) {\n        agents.push('backend-specialist');\n      }\n\n      if (stack.frameworks.some(f => ['django', 'rails', 'laravel'].includes(f))) {\n        agents.push('database-specialist');\n      }\n    }\n\n    if (stack.testFrameworks.length > 0) {\n      agents.push('test-writer');\n    }\n\n    if (stack.hasDocker || stack.hasCI) {\n      agents.push('devops-specialist');\n    }\n\n    return [...new Set(agents)];\n  }\n\n  /**\n   * Get recommended rules based on stack\n   */\n  getRecommendedRules(stack: StackInfo): string[] {\n    const rules: string[] = [];\n\n    if (stack.primaryLanguage === 'typescript' || stack.primaryLanguage === 'javascript') {\n      rules.push('Use TypeScript strict mode', 'Prefer const over let', 'Use async/await over promises');\n    }\n\n    if (stack.primaryLanguage === 'python') {\n      rules.push('Follow PEP 8 style guide', 'Use type hints', 'Use virtual environments');\n    }\n\n    if (stack.testFrameworks.length > 0) {\n      rules.push(`Write tests using ${stack.testFrameworks[0]}`, 'Aim for >80% code coverage');\n    }\n\n    if (stack.frameworks.length > 0) {\n      rules.push(`Follow ${stack.frameworks[0]} best practices`);\n    }\n\n    return rules;\n  }\n\n  private async checkFileExists(basePath: string, pattern: string): Promise<string[]> {\n    try {\n      if (pattern.includes('*')) {\n        return await glob(pattern, {\n          cwd: basePath,\n          absolute: false,\n          ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],\n        });\n      }\n\n      const fullPath = path.join(basePath, pattern);\n      if (await fs.pathExists(fullPath)) {\n        return [pattern];\n      }\n      return [];\n    } catch {\n      return [];\n    }\n  }\n\n  private async countLanguagesByExtension(basePath: string): Promise<Record<string, number>> {\n    const counts: Record<string, number> = {};\n\n    try {\n      const files = await glob('**/*.*', {\n        cwd: basePath,\n        ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**', 'vendor/**', '__pycache__/**'],\n        nodir: true,\n      });\n\n      for (const file of files.slice(0, 1000)) {\n        const ext = path.extname(file).toLowerCase();\n        const lang = LANGUAGE_EXTENSIONS[ext];\n        if (lang) {\n          counts[lang] = (counts[lang] || 0) + 1;\n        }\n      }\n    } catch {\n      // Ignore errors\n    }\n\n    return counts;\n  }\n\n  private async hasCI(basePath: string): Promise<boolean> {\n    const ciFiles = [\n      '.github/workflows',\n      '.gitlab-ci.yml',\n      '.circleci/config.yml',\n      'Jenkinsfile',\n      '.travis.yml',\n      'azure-pipelines.yml',\n      'bitbucket-pipelines.yml',\n    ];\n\n    for (const ciFile of ciFiles) {\n      const fullPath = path.join(basePath, ciFile);\n      if (await fs.pathExists(fullPath)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  /**\n   * Analyze package.json for CLI, library, and other indicators\n   */\n  private async analyzePackageJson(basePath: string): Promise<{\n    hasBin: boolean;\n    hasMain: boolean;\n    hasTypes: boolean;\n    cliLibraries: string[];\n  } | null> {\n    const packageJsonPath = path.join(basePath, 'package.json');\n\n    try {\n      if (!await fs.pathExists(packageJsonPath)) {\n        return null;\n      }\n\n      const packageJson = await fs.readJson(packageJsonPath);\n\n      // Check for bin field (CLI indicator)\n      const hasBin = !!packageJson.bin;\n\n      // Check for main/exports (library indicator)\n      const hasMain = !!(packageJson.main || packageJson.exports || packageJson.module);\n\n      // Check for types field (TypeScript library indicator)\n      const hasTypes = !!(packageJson.types || packageJson.typings);\n\n      // Check for CLI libraries in dependencies\n      const allDeps = {\n        ...packageJson.dependencies,\n        ...packageJson.devDependencies,\n      };\n\n      const CLI_LIBRARIES = [\n        'commander',\n        'yargs',\n        'meow',\n        'inquirer',\n        'prompts',\n        'chalk',\n        'ora',\n        'listr',\n        'listr2',\n        'oclif',\n        'clipanion',\n        'cac',\n        'arg',\n        'minimist',\n        'caporal',\n        'vorpal',\n      ];\n\n      const cliLibraries = CLI_LIBRARIES.filter(lib => lib in allDeps);\n\n      return {\n        hasBin,\n        hasMain,\n        hasTypes,\n        cliLibraries,\n      };\n    } catch {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/services/state/index.ts",
    "content": "export { StateDetector, type ProjectState, type StateDetectionResult, type StateDetectorOptions } from './stateDetector';\n"
  },
  {
    "path": "src/services/state/stateDetector.test.ts",
    "content": "import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { StateDetector } from './stateDetector';\n\ndescribe('StateDetector', () => {\n  let tempDir: string;\n  let detector: StateDetector;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'state-detector-test-'));\n    detector = new StateDetector({ projectPath: tempDir });\n  });\n\n  afterEach(async () => {\n    await fs.rm(tempDir, { recursive: true, force: true });\n  });\n\n  describe('detect', () => {\n    it('should detect new state when no .context directory exists', async () => {\n      const result = await detector.detect();\n\n      expect(result.state).toBe('new');\n      expect(result.details.hasContextDir).toBe(false);\n    });\n\n    it('should detect unfilled state when docs have unfilled status', async () => {\n      const contextDir = path.join(tempDir, '.context');\n      const docsDir = path.join(contextDir, 'docs');\n\n      await fs.mkdir(docsDir, { recursive: true });\n      await fs.writeFile(\n        path.join(docsDir, 'README.md'),\n        '---\\nstatus: unfilled\\n---\\n\\n# Documentation'\n      );\n\n      const result = await detector.detect();\n\n      expect(result.state).toBe('unfilled');\n      expect(result.details.hasContextDir).toBe(true);\n      expect(result.details.unfilledFiles).toBeGreaterThan(0);\n    });\n\n    it('should detect ready state when all docs are filled', async () => {\n      const contextDir = path.join(tempDir, '.context');\n      const docsDir = path.join(contextDir, 'docs');\n      const srcDir = path.join(tempDir, 'src');\n\n      await fs.mkdir(docsDir, { recursive: true });\n      await fs.mkdir(srcDir, { recursive: true });\n\n      // Create source file first (older)\n      await fs.writeFile(path.join(srcDir, 'index.ts'), 'export const foo = 1;');\n\n      // Wait a moment\n      await new Promise(resolve => setTimeout(resolve, 100));\n\n      // Create doc after (newer)\n      await fs.writeFile(\n        path.join(docsDir, 'README.md'),\n        '# Documentation\\n\\nFilled content without front matter.'\n      );\n\n      const result = await detector.detect();\n\n      expect(result.state).toBe('ready');\n      expect(result.details.hasContextDir).toBe(true);\n      expect(result.details.unfilledFiles).toBe(0);\n    });\n\n    it('should detect outdated state when code is newer than docs', async () => {\n      const contextDir = path.join(tempDir, '.context');\n      const docsDir = path.join(contextDir, 'docs');\n      const srcDir = path.join(tempDir, 'src');\n\n      await fs.mkdir(docsDir, { recursive: true });\n      await fs.mkdir(srcDir, { recursive: true });\n\n      // Create old doc first\n      await fs.writeFile(\n        path.join(docsDir, 'README.md'),\n        '# Documentation\\n\\nOld content.'\n      );\n\n      // Wait a moment\n      await new Promise(resolve => setTimeout(resolve, 100));\n\n      // Create newer source file\n      await fs.writeFile(path.join(srcDir, 'index.ts'), 'export const bar = 2;');\n\n      const result = await detector.detect();\n\n      expect(result.state).toBe('outdated');\n      expect(result.details.hasContextDir).toBe(true);\n      expect(result.details.daysBehind).toBeGreaterThanOrEqual(0);\n    });\n  });\n\n  describe('with custom context directory', () => {\n    it('should use custom output directory', async () => {\n      const customOutput = path.join(tempDir, 'custom-context');\n      const customDetector = new StateDetector({\n        projectPath: tempDir,\n        contextDirName: 'custom-context'\n      });\n\n      await fs.mkdir(path.join(customOutput, 'docs'), { recursive: true });\n      await fs.writeFile(\n        path.join(customOutput, 'docs', 'README.md'),\n        '# Custom docs'\n      );\n\n      const result = await customDetector.detect();\n\n      expect(result.details.hasContextDir).toBe(true);\n    });\n  });\n\n  describe('describeState', () => {\n    it('should describe new state', async () => {\n      const result = await detector.detect();\n      const description = StateDetector.describeState(result);\n\n      expect(description).toBe('No context documentation found');\n    });\n\n    it('should describe unfilled state', async () => {\n      const contextDir = path.join(tempDir, '.context');\n      const docsDir = path.join(contextDir, 'docs');\n\n      await fs.mkdir(docsDir, { recursive: true });\n      await fs.writeFile(\n        path.join(docsDir, 'README.md'),\n        '---\\nstatus: unfilled\\n---\\n\\n# Doc'\n      );\n      await fs.writeFile(\n        path.join(docsDir, 'other.md'),\n        '# Filled doc'\n      );\n\n      const result = await detector.detect();\n      const description = StateDetector.describeState(result);\n\n      expect(description).toContain('files need to be filled');\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/state/stateDetector.ts",
    "content": "/**\n * @deprecated Import from ../cli/stateDetector instead.\n *\n * This shim preserves the previous path while the codebase is being split\n * into CLI-facing and harness-facing boundaries.\n */\n\nexport {\n  StateDetector,\n  default,\n  type ProjectState,\n  type StateDetectionResult,\n  type StateDetectorOptions,\n} from '../cli/stateDetector';\n"
  },
  {
    "path": "src/services/sync/index.ts",
    "content": "export { SyncService } from './syncService';\nexport type {\n  SyncMode,\n  PresetName,\n  TargetPreset,\n  SyncCommandFlags,\n  SyncServiceDependencies,\n  SyncOptions,\n  SyncResult,\n  SyncRunResult,\n  AgentFileInfo,\n  HandlerOptions,\n  HandlerResult\n} from './types';\nexport { TARGET_PRESETS, resolvePresets, getPresetByPath, getAllPresetNames } from './presets';\nexport { createSymlinks, checkSymlinkSupport } from './symlinkHandler';\nexport { createMarkdownReferences } from './markdownReferenceHandler';\n"
  },
  {
    "path": "src/services/sync/markdownReferenceHandler.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport type { AgentFileInfo, HandlerOptions, HandlerResult } from './types';\n\nfunction getTargetFilename(agent: AgentFileInfo, options: HandlerOptions): string {\n  const suffix = options.filenameSuffix || '';\n  return `${agent.name}${suffix}.md`;\n}\n\nexport async function createMarkdownReferences(\n  agentFiles: AgentFileInfo[],\n  targetPath: string,\n  sourcePath: string,\n  options: HandlerOptions\n): Promise<HandlerResult> {\n  const result: HandlerResult = {\n    filesCreated: 0,\n    filesSkipped: 0,\n    filesFailed: 0,\n    errors: []\n  };\n\n  for (const agent of agentFiles) {\n    const targetFilename = getTargetFilename(agent, options);\n    const refPath = path.join(targetPath, targetFilename);\n\n    try {\n      const exists = await fs.pathExists(refPath);\n\n      if (exists && !options.force) {\n        result.filesSkipped++;\n        if (options.verbose) {\n          console.log(`  Skipped (exists): ${refPath}`);\n        }\n        continue;\n      }\n\n      if (!options.dryRun) {\n        const relativePath = path.relative(targetPath, agent.sourcePath);\n        const content = generateReferenceMarkdown(agent, relativePath, sourcePath);\n        await fs.writeFile(refPath, content);\n      }\n\n      result.filesCreated++;\n\n      if (options.verbose) {\n        console.log(`  Created: ${refPath}`);\n      }\n    } catch (error) {\n      result.filesFailed++;\n      result.errors.push({\n        file: targetFilename,\n        error: error instanceof Error ? error.message : String(error)\n      });\n    }\n  }\n\n  return result;\n}\n\nfunction generateReferenceMarkdown(\n  agent: AgentFileInfo,\n  relativePath: string,\n  sourcePath: string\n): string {\n  const title = formatAgentTitle(agent.name);\n  const timestamp = new Date().toISOString();\n\n  return `# ${title} Agent\n\n> **Source**: [${agent.filename}](${relativePath})\n>\n> This is a reference file. The canonical agent definition is maintained in \\`.context/agents/\\`.\n\n---\n\n<!--\n  AUTO-GENERATED REFERENCE FILE\n\n  This file references the canonical agent playbook at:\n  ${sourcePath}/${agent.filename}\n\n  To update the agent, edit the source file directly.\n  Re-run 'dotcontext sync-agents' to regenerate this reference.\n\n  Generated: ${timestamp}\n-->\n\n## Quick Reference\n\n- **Agent Type**: ${agent.name}\n- **Source Location**: \\`${sourcePath}/${agent.filename}\\`\n- **Reference Path**: \\`${relativePath}\\`\n\n## Usage\n\nThis agent playbook can be accessed via the source file linked above.\nThe source file contains the full agent definition including:\n\n- Mission and responsibilities\n- Best practices\n- Key project resources\n- Repository starting points\n- Documentation touchpoints\n- Collaboration checklist\n\n## See Also\n\n- [Full Agent Playbook](${relativePath})\n\n---\n*Generated by Dotcontext*\n*Last synced: ${timestamp}*\n`;\n}\n\nfunction formatAgentTitle(name: string): string {\n  return name\n    .split('-')\n    .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n    .join(' ');\n}\n"
  },
  {
    "path": "src/services/sync/presets.ts",
    "content": "import path from 'node:path';\nimport type { TargetPreset, PresetName } from './types';\nimport { getAgentsSyncPresets } from '../shared';\n\n/**\n * Build agent sync presets from the unified tool registry\n */\nfunction buildTargetPresets(): Record<Exclude<PresetName, 'all'>, TargetPreset> {\n  const registryPresets = getAgentsSyncPresets();\n  const presets: Record<string, TargetPreset> = {};\n\n  for (const [toolId, preset] of Object.entries(registryPresets)) {\n    presets[toolId] = {\n      name: toolId as PresetName,\n      path: preset.path,\n      filenameSuffix: preset.filenameSuffix,\n      description: preset.description,\n    };\n  }\n\n  return presets as Record<Exclude<PresetName, 'all'>, TargetPreset>;\n}\n\n/**\n * Agent sync presets (derived from tool registry)\n */\nexport const TARGET_PRESETS: Record<Exclude<PresetName, 'all'>, TargetPreset> = buildTargetPresets();\n\nexport function resolvePresets(presetName: PresetName): TargetPreset[] {\n  if (presetName === 'all') {\n    return Object.values(TARGET_PRESETS);\n  }\n\n  const preset = TARGET_PRESETS[presetName];\n  return preset ? [preset] : [];\n}\n\nexport function getPresetByPath(targetPath: string): TargetPreset | undefined {\n  const normalizedTarget = path.normalize(path.resolve(targetPath));\n\n  for (const preset of Object.values(TARGET_PRESETS)) {\n    const normalizedPreset = path.normalize(path.resolve(preset.path));\n\n    if (normalizedTarget === normalizedPreset ||\n        normalizedTarget.startsWith(normalizedPreset + path.sep)) {\n      return preset;\n    }\n  }\n\n  return undefined;\n}\n\nexport function getAllPresetNames(): PresetName[] {\n  return [...Object.keys(TARGET_PRESETS), 'all'] as PresetName[];\n}\n"
  },
  {
    "path": "src/services/sync/symlinkHandler.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport type { AgentFileInfo, HandlerOptions, HandlerResult } from './types';\n\nfunction getTargetFilename(agent: AgentFileInfo, options: HandlerOptions): string {\n  const suffix = options.filenameSuffix || '';\n  return `${agent.name}${suffix}.md`;\n}\n\nexport async function createSymlinks(\n  agentFiles: AgentFileInfo[],\n  targetPath: string,\n  sourcePath: string,\n  options: HandlerOptions\n): Promise<HandlerResult> {\n  const result: HandlerResult = {\n    filesCreated: 0,\n    filesSkipped: 0,\n    filesFailed: 0,\n    errors: []\n  };\n\n  for (const agent of agentFiles) {\n    const targetFilename = getTargetFilename(agent, options);\n    const linkPath = path.join(targetPath, targetFilename);\n\n    try {\n      const exists = await fs.pathExists(linkPath);\n\n      if (exists && !options.force) {\n        result.filesSkipped++;\n        if (options.verbose) {\n          console.log(`  Skipped (exists): ${linkPath}`);\n        }\n        continue;\n      }\n\n      if (exists && options.force) {\n        if (!options.dryRun) {\n          await fs.remove(linkPath);\n        }\n      }\n\n      if (!options.dryRun) {\n        const relativePath = path.relative(targetPath, agent.sourcePath);\n        await createPlatformSymlink(relativePath, linkPath, agent.sourcePath);\n      }\n\n      result.filesCreated++;\n\n      if (options.verbose) {\n        console.log(`  Created: ${linkPath} -> ${agent.sourcePath}`);\n      }\n    } catch (error) {\n      result.filesFailed++;\n      result.errors.push({\n        file: targetFilename,\n        error: error instanceof Error ? error.message : String(error)\n      });\n    }\n  }\n\n  return result;\n}\n\nasync function createPlatformSymlink(\n  target: string,\n  linkPath: string,\n  absoluteTarget: string\n): Promise<void> {\n  const isWindows = process.platform === 'win32';\n\n  if (isWindows) {\n    try {\n      await fs.symlink(target, linkPath, 'file');\n    } catch (windowsError: unknown) {\n      const err = windowsError as NodeJS.ErrnoException;\n      if (err.code === 'EPERM' || err.code === 'ENOTSUP') {\n        await fs.copy(absoluteTarget, linkPath);\n      } else {\n        throw windowsError;\n      }\n    }\n  } else {\n    await fs.symlink(target, linkPath);\n  }\n}\n\nexport async function checkSymlinkSupport(): Promise<{\n  supported: boolean;\n  message?: string;\n}> {\n  const isWindows = process.platform === 'win32';\n\n  if (!isWindows) {\n    return { supported: true };\n  }\n\n  const testDir = path.join(process.cwd(), '.symlink-test');\n  const testLink = path.join(testDir, 'test-link');\n  const testTarget = path.join(testDir, 'test-target');\n\n  try {\n    await fs.ensureDir(testDir);\n    await fs.writeFile(testTarget, 'test');\n    await fs.symlink(testTarget, testLink, 'file');\n    await fs.remove(testDir);\n    return { supported: true };\n  } catch (error: unknown) {\n    await fs.remove(testDir).catch(() => {});\n\n    const err = error as NodeJS.ErrnoException;\n    if (err.code === 'EPERM') {\n      return {\n        supported: false,\n        message:\n          'Symlinks require Developer Mode or Administrator privileges on Windows. Consider using --mode markdown instead.'\n      };\n    }\n\n    return { supported: true };\n  }\n}\n"
  },
  {
    "path": "src/services/sync/syncService.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from '../../utils/theme';\nimport type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\nimport type {\n  SyncCommandFlags,\n  SyncServiceDependencies,\n  SyncOptions,\n  SyncResult,\n  SyncRunResult,\n  SyncMode,\n  AgentFileInfo\n} from './types';\nimport { getPresetByPath, resolvePresets } from './presets';\nimport { createSymlinks } from './symlinkHandler';\nimport { createMarkdownReferences } from './markdownReferenceHandler';\n\nexport class SyncService {\n  private readonly ui: CLIInterface;\n  private readonly t: TranslateFn;\n  private readonly version: string;\n\n  constructor(dependencies: SyncServiceDependencies) {\n    this.ui = dependencies.ui;\n    this.t = dependencies.t;\n    this.version = dependencies.version;\n  }\n\n  async run(rawOptions: SyncCommandFlags): Promise<SyncRunResult> {\n    const options = await this.resolveOptions(rawOptions);\n\n    await this.validateSource(options.sourcePath);\n\n    this.displayConfig(options);\n\n    const agentFiles = await this.discoverAgentFiles(options.sourcePath);\n\n    if (agentFiles.length === 0) {\n      this.ui.displayWarning(this.t('warnings.sync.noAgentsFound'));\n      return {\n        filesCreated: 0,\n        filesSkipped: 0,\n        filesFailed: 0,\n        targets: [],\n      };\n    }\n\n    this.ui.displayInfo(\n      this.t('info.sync.foundAgents'),\n      this.t('info.sync.foundAgentsDetail', { count: agentFiles.length })\n    );\n\n    const results: SyncResult[] = [];\n    let step = 1;\n    const totalSteps = options.targetPaths.length + 1;\n\n    for (const targetPath of options.targetPaths) {\n      this.ui.displayStep(\n        step,\n        totalSteps,\n        this.t('steps.sync.processingTarget', { path: targetPath })\n      );\n\n      const result = await this.syncToTarget(agentFiles, targetPath, options);\n\n      results.push(result);\n      step++;\n    }\n\n    this.ui.displayStep(totalSteps, totalSteps, this.t('steps.sync.summary'));\n    this.displaySummary(results, options.dryRun);\n\n    if (!options.dryRun) {\n      this.ui.displaySuccess(this.t('success.sync.completed'));\n    }\n\n    return {\n      filesCreated: results.reduce((sum, result) => sum + result.filesCreated, 0),\n      filesSkipped: results.reduce((sum, result) => sum + result.filesSkipped, 0),\n      filesFailed: results.reduce((sum, result) => sum + result.filesFailed, 0),\n      targets: results,\n    };\n  }\n\n  /**\n   * Resolve sync options from raw flags\n   *\n   * Supports three formats for targets:\n   * 1. Preset name via `preset` option (e.g., 'all', 'claude')\n   * 2. Preset names in `target` array (e.g., ['claude', 'github'])\n   * 3. Direct paths in `target` array (e.g., ['.custom/agents'])\n   */\n  private async resolveOptions(rawOptions: SyncCommandFlags): Promise<SyncOptions> {\n    const sourcePath = path.resolve(rawOptions.source || './.context/agents');\n\n    let targetPaths: string[] = [];\n\n    if (rawOptions.preset) {\n      const presets = resolvePresets(rawOptions.preset);\n      targetPaths = presets.map(p => path.resolve(p.path));\n    }\n\n    if (rawOptions.target && rawOptions.target.length > 0) {\n      for (const t of rawOptions.target) {\n        // First check if target is a preset name\n        const presets = resolvePresets(t as Parameters<typeof resolvePresets>[0]);\n        if (presets.length > 0) {\n          targetPaths.push(...presets.map(p => path.resolve(p.path)));\n        } else {\n          // Treat as a direct path\n          targetPaths.push(path.resolve(t));\n        }\n      }\n      // Deduplicate\n      targetPaths = [...new Set(targetPaths)];\n    }\n\n    if (targetPaths.length === 0) {\n      throw new Error(this.t('errors.sync.noTargetsSpecified'));\n    }\n\n    const mode: SyncMode = rawOptions.mode || 'symlink';\n\n    return {\n      sourcePath,\n      targetPaths,\n      mode,\n      force: Boolean(rawOptions.force),\n      dryRun: Boolean(rawOptions.dryRun),\n      verbose: Boolean(rawOptions.verbose)\n    };\n  }\n\n  private async validateSource(sourcePath: string): Promise<void> {\n    const exists = await fs.pathExists(sourcePath);\n    if (!exists) {\n      throw new Error(this.t('errors.sync.sourceMissing', { path: sourcePath }));\n    }\n\n    const stat = await fs.stat(sourcePath);\n    if (!stat.isDirectory()) {\n      throw new Error(this.t('errors.sync.sourceNotDirectory', { path: sourcePath }));\n    }\n  }\n\n  private async discoverAgentFiles(sourcePath: string): Promise<AgentFileInfo[]> {\n    const files = await fs.readdir(sourcePath);\n    const agentFiles: AgentFileInfo[] = [];\n\n    for (const filename of files) {\n      if (!filename.endsWith('.md')) continue;\n\n      const fullPath = path.join(sourcePath, filename);\n      const stat = await fs.stat(fullPath);\n\n      if (!stat.isFile()) continue;\n\n      const name = filename.replace('.md', '');\n\n      agentFiles.push({\n        name,\n        sourcePath: fullPath,\n        relativePath: filename,\n        filename\n      });\n    }\n\n    return agentFiles.sort((a, b) => a.name.localeCompare(b.name));\n  }\n\n  private async syncToTarget(\n    agentFiles: AgentFileInfo[],\n    targetPath: string,\n    options: SyncOptions\n  ): Promise<SyncResult> {\n    const result: SyncResult = {\n      targetPath,\n      filesCreated: 0,\n      filesSkipped: 0,\n      filesFailed: 0,\n      errors: []\n    };\n\n    if (!options.dryRun) {\n      await fs.ensureDir(targetPath);\n    }\n\n    this.ui.startSpinner(this.t('spinner.sync.processing', { path: targetPath }));\n\n    try {\n      const preset = getPresetByPath(targetPath);\n      const handlerResult =\n        options.mode === 'symlink'\n          ? await createSymlinks(agentFiles, targetPath, options.sourcePath, {\n              force: options.force,\n              dryRun: options.dryRun,\n              verbose: options.verbose,\n              filenameSuffix: preset?.filenameSuffix,\n            })\n          : await createMarkdownReferences(agentFiles, targetPath, options.sourcePath, {\n              force: options.force,\n              dryRun: options.dryRun,\n              verbose: options.verbose,\n              filenameSuffix: preset?.filenameSuffix,\n            });\n\n      result.filesCreated = handlerResult.filesCreated;\n      result.filesSkipped = handlerResult.filesSkipped;\n      result.filesFailed = handlerResult.filesFailed;\n      result.errors = handlerResult.errors;\n\n      this.ui.updateSpinner(\n        this.t('spinner.sync.complete', {\n          path: targetPath,\n          count: result.filesCreated\n        }),\n        'success'\n      );\n    } catch (error) {\n      this.ui.updateSpinner(this.t('spinner.sync.failed', { path: targetPath }), 'fail');\n      result.filesFailed = agentFiles.length;\n      result.errors.push({\n        file: targetPath,\n        error: error instanceof Error ? error.message : String(error)\n      });\n    } finally {\n      this.ui.stopSpinner();\n    }\n\n    return result;\n  }\n\n  private displayConfig(options: SyncOptions): void {\n    console.log('');\n    console.log(typography.header('Sync Configuration'));\n    console.log('');\n    console.log(typography.labeledValue('Source', options.sourcePath));\n    console.log(typography.labeledValue('Mode', options.mode));\n    console.log(`  ${colors.secondary('Targets')}`);\n    options.targetPaths.forEach(t => {\n      console.log(`    ${colors.secondaryDim(symbols.pointer)} ${colors.primary(t)}`);\n    });\n    if (options.dryRun) {\n      console.log('');\n      console.log(typography.warning('DRY RUN - No changes will be made'));\n    }\n    console.log('');\n  }\n\n  private displaySummary(results: SyncResult[], dryRun: boolean): void {\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header('Sync Summary'));\n    console.log('');\n\n    let totalCreated = 0;\n    let totalSkipped = 0;\n    let totalFailed = 0;\n\n    for (const result of results) {\n      totalCreated += result.filesCreated;\n      totalSkipped += result.filesSkipped;\n      totalFailed += result.filesFailed;\n\n      const status = result.filesFailed > 0\n        ? colors.error(symbols.error)\n        : colors.success(symbols.success);\n\n      console.log(`${status} ${colors.primary(result.targetPath)}`);\n      console.log(\n        `    ${colors.secondary(`Created: ${result.filesCreated}, Skipped: ${result.filesSkipped}, Failed: ${result.filesFailed}`)}`\n      );\n\n      if (result.errors.length > 0) {\n        result.errors.forEach(err => {\n          console.log(`    ${colors.error(symbols.error)} ${colors.secondaryDim(`${err.file} - ${err.error}`)}`);\n        });\n      }\n    }\n\n    console.log('');\n    console.log(typography.labeledValue('Created', `${totalCreated}${dryRun ? ' (dry run)' : ''}`));\n    console.log(typography.labeledValue('Skipped', totalSkipped.toString()));\n    console.log(typography.labeledValue('Failed', totalFailed.toString()));\n    console.log('');\n  }\n}\n"
  },
  {
    "path": "src/services/sync/types.ts",
    "content": "import type { CLIInterface } from '../../utils/cliUI';\nimport type { TranslateFn } from '../../utils/i18n';\n\nexport type SyncMode = 'symlink' | 'markdown';\n\nexport type PresetName = string;\n\nexport interface TargetPreset {\n  name: PresetName;\n  path: string;\n  filenameSuffix?: string;\n  description: string;\n}\n\nexport interface SyncCommandFlags {\n  source?: string;\n  target?: string[];\n  mode?: SyncMode;\n  preset?: PresetName;\n  force?: boolean;\n  dryRun?: boolean;\n  verbose?: boolean;\n}\n\nexport interface SyncServiceDependencies {\n  ui: CLIInterface;\n  t: TranslateFn;\n  version: string;\n}\n\nexport interface SyncOptions {\n  sourcePath: string;\n  targetPaths: string[];\n  mode: SyncMode;\n  force: boolean;\n  dryRun: boolean;\n  verbose: boolean;\n}\n\nexport interface SyncResult {\n  targetPath: string;\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  errors: Array<{ file: string; error: string }>;\n}\n\nexport interface AgentFileInfo {\n  name: string;\n  sourcePath: string;\n  relativePath: string;\n  filename: string;\n}\n\nexport interface HandlerOptions {\n  force: boolean;\n  dryRun: boolean;\n  verbose: boolean;\n  filenameSuffix?: string;\n}\n\nexport interface HandlerResult {\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  errors: Array<{ file: string; error: string }>;\n}\n\nexport interface SyncRunResult {\n  filesCreated: number;\n  filesSkipped: number;\n  filesFailed: number;\n  targets: SyncResult[];\n}\n"
  },
  {
    "path": "src/services/workflow/autoAdvance.ts",
    "content": "/**\n * Auto-Advance Service\n *\n * Automatically detects when phase outputs are complete and advances the workflow.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { glob } from 'glob';\nimport {\n  PrevcPhase,\n  PrevcStatus,\n  getOutputsForPhase,\n} from '../../workflow';\n\nexport interface AutoAdvanceResult {\n  shouldAdvance: boolean;\n  reason?: string;\n  detectedOutputs: string[];\n  missingOutputs: string[];\n}\n\n/**\n * Expected outputs for each phase (file patterns)\n */\nconst PHASE_OUTPUT_PATTERNS: Record<PrevcPhase, string[]> = {\n  P: [\n    '**/PRD.md',\n    '**/requirements*.md',\n    '**/specs*.md',\n    '**/user-stories*.md',\n    '**/*planning*.md',\n  ],\n  R: [\n    '**/architecture*.md',\n    '**/design*.md',\n    '**/ADR*.md',\n    '**/tech-spec*.md',\n    '**/*review*.md',\n  ],\n  E: [\n    '**/*.ts',\n    '**/*.tsx',\n    '**/*.js',\n    '**/*.jsx',\n    '**/*.py',\n    '**/*.go',\n    '**/*.rs',\n    '**/*.java',\n  ],\n  V: [\n    '**/*.test.*',\n    '**/*.spec.*',\n    '**/test_*.py',\n    '**/*_test.go',\n    '**/tests/**',\n    '**/__tests__/**',\n  ],\n  C: [\n    '**/CHANGELOG*.md',\n    '**/RELEASE*.md',\n    '**/deployment*.md',\n    '**/*confirmation*.md',\n  ],\n};\n\n/**\n * Minimum number of new files to consider phase complete\n */\nconst PHASE_MIN_FILES: Record<PrevcPhase, number> = {\n  P: 1,\n  R: 1,\n  E: 1,\n  V: 1,\n  C: 1,\n};\n\nexport class AutoAdvanceDetector {\n  private contextPath: string;\n  private repoPath: string;\n\n  constructor(repoPath: string) {\n    this.repoPath = path.resolve(repoPath);\n    this.contextPath = path.join(this.repoPath, '.context');\n  }\n\n  /**\n   * Check if the current phase should auto-advance\n   */\n  async checkAutoAdvance(\n    currentPhase: PrevcPhase,\n    status: PrevcStatus\n  ): Promise<AutoAdvanceResult> {\n    const result: AutoAdvanceResult = {\n      shouldAdvance: false,\n      detectedOutputs: [],\n      missingOutputs: [],\n    };\n\n    const patterns = PHASE_OUTPUT_PATTERNS[currentPhase];\n    const minFiles = PHASE_MIN_FILES[currentPhase];\n\n    // Get phase start time\n    const phaseStatus = status.phases[currentPhase];\n    const phaseStartTime = phaseStatus?.started_at\n      ? new Date(phaseStatus.started_at)\n      : new Date(0);\n\n    // Search for new files matching the patterns\n    for (const pattern of patterns) {\n      try {\n        const matches = await glob(pattern, {\n          cwd: this.repoPath,\n          ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],\n          absolute: true,\n        });\n\n        for (const match of matches) {\n          try {\n            const stats = await fs.stat(match);\n            // File was modified after phase started\n            if (stats.mtime > phaseStartTime) {\n              const relativePath = path.relative(this.repoPath, match);\n              if (!result.detectedOutputs.includes(relativePath)) {\n                result.detectedOutputs.push(relativePath);\n              }\n            }\n          } catch {\n            // File stat failed, skip\n          }\n        }\n      } catch {\n        // Glob failed, skip pattern\n      }\n    }\n\n    // Check if we have enough outputs\n    if (result.detectedOutputs.length >= minFiles) {\n      result.shouldAdvance = true;\n      result.reason = `Detected ${result.detectedOutputs.length} output(s) for phase ${currentPhase}`;\n    } else {\n      result.missingOutputs = this.getExpectedOutputTypes(currentPhase);\n      result.reason = `Waiting for outputs: ${result.missingOutputs.join(', ')}`;\n    }\n\n    return result;\n  }\n\n  /**\n   * Get human-readable expected output types for a phase\n   */\n  private getExpectedOutputTypes(phase: PrevcPhase): string[] {\n    const outputMap: Record<PrevcPhase, string[]> = {\n      P: ['PRD document', 'Requirements specification', 'User stories'],\n      R: ['Architecture document', 'Design decisions', 'Technical spec'],\n      E: ['Implementation code', 'Source files'],\n      V: ['Test files', 'Test results'],\n      C: ['Changelog', 'Release notes', 'Documentation updates'],\n    };\n\n    return outputMap[phase] || [];\n  }\n\n  /**\n   * Watch for file changes and trigger callback when outputs detected\n   */\n  async watchForOutputs(\n    currentPhase: PrevcPhase,\n    status: PrevcStatus,\n    onOutputsDetected: (outputs: string[]) => void,\n    intervalMs: number = 5000\n  ): Promise<() => void> {\n    let running = true;\n    let previousOutputs = new Set<string>();\n\n    const check = async () => {\n      if (!running) return;\n\n      const result = await this.checkAutoAdvance(currentPhase, status);\n\n      // Find new outputs\n      const newOutputs = result.detectedOutputs.filter(\n        (o) => !previousOutputs.has(o)\n      );\n\n      if (newOutputs.length > 0) {\n        newOutputs.forEach((o) => previousOutputs.add(o));\n        onOutputsDetected(newOutputs);\n      }\n\n      if (running) {\n        setTimeout(check, intervalMs);\n      }\n    };\n\n    // Start checking\n    check();\n\n    // Return stop function\n    return () => {\n      running = false;\n    };\n  }\n\n  /**\n   * Validate outputs for a phase\n   */\n  async validatePhaseOutputs(\n    phase: PrevcPhase,\n    outputs: string[]\n  ): Promise<{ valid: boolean; errors: string[] }> {\n    const errors: string[] = [];\n\n    for (const output of outputs) {\n      const fullPath = path.isAbsolute(output)\n        ? output\n        : path.join(this.repoPath, output);\n\n      if (!(await fs.pathExists(fullPath))) {\n        errors.push(`Output file not found: ${output}`);\n      }\n    }\n\n    return {\n      valid: errors.length === 0,\n      errors,\n    };\n  }\n}\n\nexport { AutoAdvanceDetector as default };\n"
  },
  {
    "path": "src/services/workflow/index.ts",
    "content": "/**\n * Workflow Service Exports\n */\n\nexport {\n  WorkflowService,\n  WorkflowServiceDependencies,\n  WorkflowInitOptions,\n  type WorkflowHarnessStatus,\n  HarnessWorkflowBlockedError,\n} from './workflowService';\nexport { AutoAdvanceDetector, AutoAdvanceResult } from './autoAdvance';\n"
  },
  {
    "path": "src/services/workflow/workflowService.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HarnessWorkflowStateService } from '../harness/workflowStateService';\nimport { HarnessWorkflowBlockedError, WorkflowService } from './workflowService';\n\ndescribe('WorkflowService harness integration', () => {\n  let tempDir: string;\n  let service: WorkflowService;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-workflow-harness-'));\n    await fs.writeJson(path.join(tempDir, 'package.json'), {\n      name: 'workflow-harness-test',\n      version: '1.0.0',\n      scripts: {\n        build: 'node -e \"process.exit(0)\"',\n      },\n    }, { spaces: 2 });\n    service = new WorkflowService(tempDir);\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('creates a harness session during workflow init', async () => {\n    await service.init({\n      name: 'alpha',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    const harness = await service.getHarnessStatus();\n    const workflowState = new HarnessWorkflowStateService({\n      contextPath: path.join(tempDir, '.context'),\n    });\n    const binding = await workflowState.getBinding();\n\n    expect(harness).not.toBeNull();\n    expect(harness?.session.name).toBe('alpha');\n    expect(harness?.session.status).toBe('active');\n    expect(binding?.sessionId).toBe(harness?.session.id);\n    expect(binding?.workflowName).toBe('alpha');\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'harness', 'workflows', 'prevc.json'))).toBe(true);\n    expect(await fs.pathExists(path.join(tempDir, '.context', 'workflow', 'status.yaml'))).toBe(false);\n  });\n\n  it('creates a fresh canonical binding when a workflow is reinitialized', async () => {\n    await service.init({\n      name: 'alpha',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    const firstTask = await service.defineHarnessTask({\n      title: 'Implement alpha',\n      requiredSensors: ['build'],\n    });\n    const firstHarness = await service.getHarnessStatus();\n\n    await service.init({\n      name: 'beta',\n      scale: 'SMALL',\n      autonomous: true,\n      archivePrevious: false,\n    });\n\n    const secondHarness = await service.getHarnessStatus();\n    const workflowState = new HarnessWorkflowStateService({\n      contextPath: path.join(tempDir, '.context'),\n    });\n    const binding = await workflowState.getBinding();\n\n    expect(firstHarness).not.toBeNull();\n    expect(secondHarness).not.toBeNull();\n    expect(secondHarness?.session.name).toBe('beta');\n    expect(secondHarness?.session.id).not.toBe(firstHarness?.session.id);\n    expect(secondHarness?.binding.activeTaskId).toBeUndefined();\n    expect(binding?.workflowName).toBe('beta');\n    expect(binding?.sessionId).toBe(secondHarness?.session.id);\n    expect(binding?.activeTaskId).toBeUndefined();\n    expect(binding?.sessionId).not.toBe(firstHarness?.session.id);\n    expect(firstTask.id).not.toBe(binding?.activeTaskId);\n  });\n\n  it('blocks workflow advance when required harness checks are missing', async () => {\n    await service.init({\n      name: 'beta',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    await service.defineHarnessTask({\n      title: 'Implement beta',\n      requiredSensors: ['build'],\n      requiredArtifacts: ['handoff-summary'],\n    });\n\n    await expect(service.advance()).rejects.toBeInstanceOf(HarnessWorkflowBlockedError);\n  });\n\n  it('allows workflow advance after required sensors and artifacts are satisfied', async () => {\n    await service.init({\n      name: 'gamma',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    await service.defineHarnessTask({\n      title: 'Implement gamma',\n      requiredSensors: ['build'],\n      requiredArtifacts: ['handoff-summary'],\n    });\n    await service.recordHarnessArtifact({\n      name: 'handoff-summary',\n      kind: 'text',\n      content: 'ready',\n    });\n\n    const sensorResult = await service.runHarnessSensors(['build']);\n    const nextPhase = await service.advance();\n    const harness = await service.getHarnessStatus();\n\n    expect(sensorResult.backpressure.blocked).toBe(false);\n    expect(nextPhase).toBeTruthy();\n    expect(harness?.completionCheck.blocked).toBe(false);\n    expect(harness?.sensorRuns).toHaveLength(1);\n  });\n\n  it('includes harness task details in formatted status output', async () => {\n    await service.init({\n      name: 'formatted-status',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    await service.defineHarnessTask({\n      title: 'Implement formatted status',\n      requiredSensors: ['build'],\n    });\n\n    const formatted = await service.getFormattedStatus();\n\n    expect(formatted).toContain('Harness:');\n    expect(formatted).toContain('Tasks: 1');\n    expect(formatted).toContain('Active Task: Implement formatted status (ready)');\n  });\n\n  it('registers sensors from project scripts instead of assuming a universal Node set', async () => {\n    const sensors = service.listAvailableSensors();\n\n    expect(sensors.map((sensor) => sensor.id)).toEqual(['build']);\n  });\n\n  it('detects Python sensors without assuming Node scripts', async () => {\n    const pythonDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dotcontext-python-workflow-'));\n\n    try {\n      await fs.writeFile(path.join(pythonDir, 'pyproject.toml'), '[project]\\nname = \"python-workflow-test\"\\nversion = \"0.1.0\"\\n');\n      await fs.writeFile(path.join(pythonDir, 'mypy.ini'), '[mypy]\\npython_version = 3.11\\n');\n\n      const pythonService = new WorkflowService(pythonDir);\n      const sensors = pythonService.listAvailableSensors();\n\n      expect(sensors.map((sensor) => sensor.id)).toEqual(['test', 'typecheck']);\n    } finally {\n      await fs.remove(pythonDir);\n    }\n  });\n\n  it('loads customizable sensors from .context/harness/sensors.json', async () => {\n    await fs.ensureDir(path.join(tempDir, '.context', 'harness'));\n    await fs.writeJson(\n      path.join(tempDir, '.context', 'harness', 'sensors.json'),\n      {\n        version: 1,\n        generatedAt: new Date().toISOString(),\n        source: 'manual',\n        sensors: [\n          {\n            id: 'quality',\n            name: 'Quality',\n            severity: 'warning',\n            command: 'echo quality',\n          },\n          {\n            id: 'build',\n            name: 'Build',\n            severity: 'critical',\n            command: 'npm run build',\n            script: 'build',\n            enabled: false,\n          },\n        ],\n      },\n      { spaces: 2 }\n    );\n\n    const customizedService = new WorkflowService(tempDir);\n    const sensors = customizedService.listAvailableSensors();\n\n    expect(sensors.map((sensor) => sensor.id)).toEqual(['quality']);\n  });\n\n  it('enforces artifact policy rules during workflow execution', async () => {\n    await service.init({\n      name: 'policy-run',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    await fs.ensureDir(path.join(tempDir, '.context', 'harness'));\n    await fs.writeJson(\n      path.join(tempDir, '.context', 'harness', 'policy.json'),\n      {\n        version: 1,\n        defaultEffect: 'allow',\n        rules: [\n          {\n            id: 'deny-secret-artifacts',\n            effect: 'deny',\n            when: {\n              tools: ['workflow'],\n              actions: ['recordArtifact'],\n              paths: ['src/secrets/*'],\n            },\n            reason: 'secret paths blocked',\n          },\n        ],\n      },\n      { spaces: 2 }\n    );\n\n    await expect(service.recordHarnessArtifact({\n      name: 'secret-output',\n      kind: 'file',\n      path: 'src/secrets/creds.txt',\n    })).rejects.toThrow('Policy blocked workflow.recordArtifact');\n  });\n\n  it('enforces checkpoint and handoff policy rules during workflow execution', async () => {\n    await service.init({\n      name: 'policy-checkpoint-handoff',\n      scale: 'SMALL',\n      autonomous: true,\n    });\n\n    await fs.ensureDir(path.join(tempDir, '.context', 'harness'));\n    await fs.writeJson(\n      path.join(tempDir, '.context', 'harness', 'policy.json'),\n      {\n        version: 1,\n        defaultEffect: 'allow',\n        rules: [\n          {\n            id: 'deny-checkpoint',\n            effect: 'deny',\n            when: {\n              tools: ['workflow'],\n              actions: ['checkpoint'],\n            },\n            reason: 'checkpoint denied',\n          },\n          {\n            id: 'deny-handoff',\n            effect: 'deny',\n            when: {\n              tools: ['workflow'],\n              actions: ['handoff'],\n            },\n            reason: 'handoff denied',\n          },\n        ],\n      },\n      { spaces: 2 }\n    );\n\n    await expect(service.checkpointHarnessSession('manual checkpoint')).rejects.toThrow('Policy blocked workflow.checkpoint');\n    await expect(service.handoff('planner', 'executor', ['artifact.txt'])).rejects.toThrow('Policy blocked workflow.handoff');\n  });\n});\n"
  },
  {
    "path": "src/services/workflow/workflowService.ts",
    "content": "/**\n * PREVC Workflow Service\n *\n * High-level service for managing PREVC workflows via CLI and MCP.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { exec as execCallback } from 'child_process';\nimport { promisify } from 'util';\nimport {\n  PrevcOrchestrator,\n  WorkflowSummary,\n  PrevcStatus,\n  PrevcPhase,\n  PrevcRole,\n  ProjectScale,\n  ProjectContext,\n  CollaborationSession,\n  CollaborationManager,\n  CollaborationSynthesis,\n  WorkflowSettings,\n  PlanApproval,\n  GateCheckResult,\n  PhaseOrchestration,\n  getScaleName,\n  getScaleFromName,\n  PHASE_NAMES_PT,\n  ROLE_DISPLAY_NAMES,\n} from '../../workflow';\nimport {\n  HarnessRuntimeStateService,\n  HarnessSensorCatalogService,\n  HarnessSensorsService,\n  HarnessTaskContractsService,\n  HarnessPolicyService,\n  HarnessWorkflowStateService,\n  type HarnessArtifactKind,\n  type HarnessTaskContract,\n  type HarnessHandoffContract,\n  type HarnessSensorRun,\n  type HarnessSessionRecord,\n  type HarnessSensorDefinition,\n  type WorkflowHarnessBinding,\n} from '../harness';\n\nconst exec = promisify(execCallback);\n\nexport interface WorkflowHarnessStatus {\n  binding: WorkflowHarnessBinding;\n  session: HarnessSessionRecord;\n  availableSensors: Array<Pick<HarnessSensorDefinition, 'id' | 'name' | 'description'>>;\n  sensorRuns: HarnessSensorRun[];\n  taskContracts: HarnessTaskContract[];\n  handoffs: HarnessHandoffContract[];\n  policyRules: number;\n  completionCheck: {\n    blocked: boolean;\n    reasons: string[];\n    taskCompletion: {\n      canComplete: boolean;\n      missingSensors: string[];\n      missingArtifacts: string[];\n      blockingFindings: string[];\n    } | null;\n  };\n}\n\nexport class HarnessWorkflowBlockedError extends Error {\n  constructor(\n    message: string,\n    public readonly reasons: string[],\n    public readonly harnessStatus: WorkflowHarnessStatus\n  ) {\n    super(message);\n    this.name = 'HarnessWorkflowBlockedError';\n  }\n}\n\n/**\n * Workflow service dependencies\n * Uses loose typing to be compatible with CLI and MCP contexts\n */\nexport interface WorkflowServiceDependencies {\n  ui?: {\n    displaySuccess: (message: string) => void;\n    displayError: (message: string, error?: Error) => void;\n    displayInfo: (message: string, detail?: string) => void;\n  };\n  t?: (key: string, params?: Record<string, unknown>) => string;\n}\n\n/**\n * Options for initializing a workflow\n */\nexport interface WorkflowInitOptions {\n  name: string;\n  description?: string;\n  scale?: string | ProjectScale;\n  files?: string[];\n  /** Enable autonomous mode (bypasses all gates) */\n  autonomous?: boolean;\n  /** Require a linked plan before advancing P → R */\n  requirePlan?: boolean;\n  /** Require plan approval before advancing R → E */\n  requireApproval?: boolean;\n  /** How to handle existing workflow: true = archive, false = delete, undefined = error if exists */\n  archivePrevious?: boolean;\n}\n\n/**\n * PREVC Workflow Service\n */\nexport class WorkflowService {\n  private readonly repoPath: string;\n  private contextPath: string;\n  private orchestrator: PrevcOrchestrator;\n  private collaborationManager: CollaborationManager;\n  private runtimeStateService: HarnessRuntimeStateService;\n  private sensorCatalogService: HarnessSensorCatalogService;\n  private sensorsService: HarnessSensorsService;\n  private taskContractsService: HarnessTaskContractsService;\n  private policyService: HarnessPolicyService;\n  private workflowStateService: HarnessWorkflowStateService;\n  private deps: WorkflowServiceDependencies;\n\n  constructor(\n    repoPath: string,\n    deps: WorkflowServiceDependencies = {}\n  ) {\n    const resolvedPath = path.resolve(repoPath);\n    this.repoPath = path.basename(resolvedPath) === '.context'\n      ? path.dirname(resolvedPath)\n      : resolvedPath;\n    this.contextPath = path.basename(resolvedPath) === '.context'\n      ? resolvedPath\n      : path.join(resolvedPath, '.context');\n    this.orchestrator = new PrevcOrchestrator(this.contextPath);\n    this.collaborationManager = new CollaborationManager();\n    this.runtimeStateService = new HarnessRuntimeStateService({ repoPath: this.repoPath });\n    this.sensorCatalogService = new HarnessSensorCatalogService({\n      repoPath: this.repoPath,\n      contextPath: this.contextPath,\n    });\n    this.sensorsService = new HarnessSensorsService({ stateService: this.runtimeStateService });\n    this.taskContractsService = new HarnessTaskContractsService({\n      repoPath: this.repoPath,\n      stateService: this.runtimeStateService,\n    });\n    this.policyService = new HarnessPolicyService({ repoPath: this.repoPath });\n    this.workflowStateService = new HarnessWorkflowStateService({ contextPath: this.contextPath });\n    this.deps = deps;\n    this.registerDefaultSensors();\n  }\n\n  /**\n   * Create a WorkflowService with the given repository path\n   */\n  static async create(\n    repoPath: string = process.cwd(),\n    deps: WorkflowServiceDependencies = {}\n  ): Promise<WorkflowService> {\n    return new WorkflowService(repoPath, deps);\n  }\n\n  /**\n   * Check if a workflow exists\n   */\n  async hasWorkflow(): Promise<boolean> {\n    return this.orchestrator.hasWorkflow();\n  }\n\n  /**\n   * Initialize a new workflow\n   */\n  async init(options: WorkflowInitOptions): Promise<PrevcStatus> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'init',\n      risk: options.archivePrevious ? 'high' : 'medium',\n      metadata: {\n        name: options.name,\n        scale: options.scale,\n      },\n    });\n\n    // Ensure .context directory exists\n    await fs.ensureDir(this.contextPath);\n    await fs.ensureDir(path.join(this.contextPath, 'workflow'));\n\n    // Determine scale\n    let scale: ProjectScale;\n    if (typeof options.scale === 'string') {\n      scale = getScaleFromName(options.scale) ?? ProjectScale.MEDIUM;\n    } else if (typeof options.scale === 'number') {\n      scale = options.scale;\n    } else {\n      // Auto-detect based on context\n      const context: ProjectContext = {\n        name: options.name,\n        description: options.description || options.name,\n        files: options.files,\n      };\n      const { detectProjectScale } = await import('../../workflow');\n      scale = detectProjectScale(context);\n    }\n\n    // Build settings overrides\n    const settings: Partial<WorkflowSettings> | undefined =\n      options.autonomous !== undefined ||\n      options.requirePlan !== undefined ||\n      options.requireApproval !== undefined\n        ? {\n            autonomous_mode: options.autonomous,\n            require_plan: options.requirePlan,\n            require_approval: options.requireApproval,\n          }\n        : undefined;\n\n    // Initialize workflow\n    const status = await this.orchestrator.initWorkflowWithScale(\n      options.name,\n      scale,\n      settings,\n      options.archivePrevious\n    );\n\n    await this.ensureHarnessSession(options.name, options.description);\n\n    this.deps.ui?.displaySuccess(\n      `Workflow PREVC initialized: ${options.name} (Scale: ${getScaleName(scale)})`\n    );\n\n    return status;\n  }\n\n  /**\n   * Get current workflow status\n   */\n  async getStatus(): Promise<PrevcStatus> {\n    return this.orchestrator.getStatus();\n  }\n\n  /**\n   * Get workflow summary for display\n   */\n  async getSummary(): Promise<WorkflowSummary> {\n    return this.orchestrator.getSummary();\n  }\n\n  /**\n   * Get formatted status for CLI display\n   */\n  async getFormattedStatus(): Promise<string> {\n    const summary = await this.getSummary();\n    const status = await this.getStatus();\n    const harness = await this.getHarnessStatus();\n\n    const lines: string[] = [];\n\n    lines.push(`📋 Workflow: ${summary.name}`);\n    lines.push(`📊 Scale: ${getScaleName(summary.scale as ProjectScale)}`);\n    lines.push(`📍 Current Phase: ${PHASE_NAMES_PT[summary.currentPhase]} (${summary.currentPhase})`);\n    lines.push(`📈 Progress: ${summary.progress.percentage}% (${summary.progress.completed}/${summary.progress.total} phases)`);\n    lines.push('');\n    lines.push('Phases:');\n\n    for (const [phase, phaseStatus] of Object.entries(status.phases)) {\n      const emoji = phaseStatus.status === 'completed' ? '✅' :\n                    phaseStatus.status === 'in_progress' ? '🔄' :\n                    phaseStatus.status === 'skipped' ? '⏭️' : '⏸️';\n      const phaseName = PHASE_NAMES_PT[phase as PrevcPhase];\n      lines.push(`  ${emoji} ${phase}: ${phaseName} - ${phaseStatus.status}`);\n    }\n\n    if (harness) {\n      const activeTask = harness.binding.activeTaskId\n        ? harness.taskContracts.find((task) => task.id === harness.binding.activeTaskId) || null\n        : null;\n\n      lines.push('');\n      lines.push('Harness:');\n      lines.push(`  🧵 Session: ${harness.session.name} (${harness.session.status})`);\n      lines.push(`  📌 Tasks: ${harness.taskContracts.length}`);\n      lines.push(`  🤝 Handoffs: ${harness.handoffs.length}`);\n      lines.push(`  🧪 Sensors: ${harness.sensorRuns.length}`);\n\n      if (activeTask) {\n        lines.push(`  🎯 Active Task: ${activeTask.title} (${activeTask.status})`);\n      } else if (harness.taskContracts.length > 0) {\n        lines.push('  🎯 Active Task: none');\n      }\n\n      if (harness.completionCheck.blocked) {\n        lines.push('  🚫 Completion checks blocked');\n        for (const reason of harness.completionCheck.reasons) {\n          lines.push(`    - ${reason}`);\n        }\n      }\n    }\n\n    if (summary.isComplete) {\n      lines.push('');\n      lines.push('✨ Workflow complete!');\n    }\n\n    return lines.join('\\n');\n  }\n\n  /**\n   * Advance to the next phase\n   */\n  async advance(outputs?: string[], options?: { force?: boolean }): Promise<PrevcPhase | null> {\n    const currentPhase = await this.orchestrator.getCurrentPhase();\n    if (!options?.force) {\n      const approval = await this.getApproval();\n      await this.policyService.authorize({\n        tool: 'workflow',\n        action: 'advance',\n        risk: 'high',\n        approval: approval?.plan_approved\n          ? { approvedBy: approval.approved_by, note: approval.approval_notes }\n          : undefined,\n      });\n\n      const harnessStatus = await this.getHarnessStatus();\n      if (harnessStatus?.completionCheck.blocked) {\n        throw new HarnessWorkflowBlockedError(\n          'Harness completion checks blocked workflow advance.',\n          harnessStatus.completionCheck.reasons,\n          harnessStatus\n        );\n      }\n    }\n\n    const nextPhase = await this.orchestrator.completePhase(outputs, options);\n    const binding = await this.requireHarnessBinding();\n\n    if (nextPhase) {\n      const session = await this.runtimeStateService.checkpointSession(binding.sessionId, {\n        note: `Advanced workflow phase ${currentPhase} -> ${nextPhase}`,\n        data: { from: currentPhase, to: nextPhase, outputs },\n      });\n      binding.updatedAt = session.updatedAt;\n      await this.saveHarnessBinding(binding);\n\n      this.deps.ui?.displaySuccess(\n        `Advanced from ${PHASE_NAMES_PT[currentPhase]} to ${PHASE_NAMES_PT[nextPhase]}`\n      );\n    } else {\n      const session = await this.runtimeStateService.completeSession(binding.sessionId, 'Workflow completed');\n      binding.updatedAt = session.updatedAt;\n      await this.saveHarnessBinding(binding);\n\n      this.deps.ui?.displaySuccess('Workflow completed!');\n    }\n\n    return nextPhase;\n  }\n\n  /**\n   * Check workflow gates for the current phase transition\n   */\n  async checkGates(): Promise<GateCheckResult> {\n    return this.orchestrator.checkGates();\n  }\n\n  /**\n   * Set workflow settings\n   */\n  async setSettings(settings: Partial<WorkflowSettings>): Promise<WorkflowSettings> {\n    const isHighRisk =\n      typeof settings.autonomous_mode === 'boolean' ||\n      typeof settings.require_approval === 'boolean';\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'setSettings',\n      risk: isHighRisk ? 'high' : 'medium',\n      metadata: settings as Record<string, unknown>,\n    });\n    return this.orchestrator.setSettings(settings);\n  }\n\n  /**\n   * Get workflow settings\n   */\n  async getSettings(): Promise<WorkflowSettings> {\n    return this.orchestrator.getSettings();\n  }\n\n  listAvailableSensors(): Array<Pick<HarnessSensorDefinition, 'id' | 'name' | 'description'>> {\n    return this.sensorsService.listSensors().map((sensor) => ({\n      id: sensor.id,\n      name: sensor.name,\n      description: sensor.description,\n    }));\n  }\n\n  /**\n   * Enable or disable autonomous mode\n   */\n  async setAutonomousMode(enabled: boolean): Promise<WorkflowSettings> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'setAutonomousMode',\n      risk: 'high',\n      metadata: { enabled },\n    });\n    return this.orchestrator.setSettings({ autonomous_mode: enabled });\n  }\n\n  /**\n   * Mark that a plan has been created/linked\n   */\n  async markPlanCreated(planSlug: string): Promise<void> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'markPlanCreated',\n      risk: 'medium',\n      metadata: { planSlug },\n    });\n    return this.orchestrator.markPlanCreated(planSlug);\n  }\n\n  /**\n   * Approve the plan\n   */\n  async approvePlan(approver: PrevcRole | string, notes?: string): Promise<PlanApproval> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'approvePlan',\n      risk: 'high',\n      approval: { approvedBy: String(approver), note: notes },\n      metadata: { approver, notes },\n    });\n    return this.orchestrator.approvePlan(approver, notes);\n  }\n\n  /**\n   * Get approval status\n   */\n  async getApproval(): Promise<PlanApproval | undefined> {\n    return this.orchestrator.getApproval();\n  }\n\n  /**\n   * Perform a handoff between agents\n   */\n  async handoff(\n    from: string,\n    to: string,\n    artifacts: string[]\n  ): Promise<void> {\n    const handoffRisk = artifacts.some((artifactPath) => /secret|token|key|password/i.test(artifactPath))\n      ? 'high'\n      : 'medium';\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'handoff',\n      paths: artifacts,\n      risk: handoffRisk,\n      metadata: {\n        from,\n        to,\n        artifactCount: artifacts.length,\n      },\n    });\n\n    await this.orchestrator.handoff(from, to, artifacts);\n    const binding = await this.requireHarnessBinding();\n\n    for (const artifactPath of artifacts) {\n      await this.runtimeStateService.addArtifact(binding.sessionId, {\n        name: artifactPath,\n        kind: 'file',\n        path: artifactPath,\n        metadata: { from, to, source: 'workflow.handoff' },\n      });\n    }\n\n    await this.taskContractsService.createHandoffContract({\n      from,\n      to,\n      sessionId: binding.sessionId,\n      taskId: binding.activeTaskId,\n      artifacts,\n      evidence: [],\n    });\n\n    binding.updatedAt = new Date().toISOString();\n    await this.saveHarnessBinding(binding);\n\n    this.deps.ui?.displaySuccess(\n      `Handoff: ${from} → ${to}`\n    );\n  }\n\n  /**\n   * Get orchestration guidance for a phase\n   */\n  async getPhaseOrchestration(phase: PrevcPhase): Promise<PhaseOrchestration> {\n    return this.orchestrator.getPhaseOrchestration(phase);\n  }\n\n  /**\n   * Get next agent suggestion after a handoff\n   */\n  getNextAgentSuggestion(currentAgent: string): { agent: string; reason: string } | null {\n    return this.orchestrator.getNextAgentSuggestion(currentAgent);\n  }\n\n  /**\n   * Start a collaboration session\n   */\n  async startCollaboration(\n    topic: string,\n    participants?: PrevcRole[]\n  ): Promise<CollaborationSession> {\n    const session = this.collaborationManager.createSession(topic, participants);\n    await session.start(topic, participants);\n\n    this.deps.ui?.displaySuccess(\n      `Collaboration started: ${topic}`\n    );\n    this.deps.ui?.displayInfo(\n      `Participants: ${session.getParticipantNames().join(', ')}`\n    );\n\n    return session;\n  }\n\n  /**\n   * Add a contribution to a collaboration session\n   */\n  contributeToCollaboration(\n    sessionId: string,\n    role: PrevcRole,\n    message: string\n  ): void {\n    const session = this.collaborationManager.getSession(sessionId);\n    if (!session) {\n      throw new Error(`Session not found: ${sessionId}`);\n    }\n\n    session.contribute(role, message);\n  }\n\n  /**\n   * End a collaboration session and get synthesis\n   */\n  async endCollaboration(sessionId: string): Promise<CollaborationSynthesis | null> {\n    return this.collaborationManager.endSession(sessionId);\n  }\n\n  /**\n   * Get recommended next actions\n   */\n  async getRecommendedActions(): Promise<string[]> {\n    return this.orchestrator.getRecommendedActions();\n  }\n\n  /**\n   * Check if workflow is complete\n   */\n  async isComplete(): Promise<boolean> {\n    return this.orchestrator.isComplete();\n  }\n\n  /**\n   * Update the current task\n   */\n  async updateTask(task: string): Promise<void> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'updateTask',\n      risk: 'medium',\n      metadata: { task },\n    });\n    await this.orchestrator.updateCurrentTask(task);\n  }\n\n  /**\n   * Start a role in the current phase\n   */\n  async startRole(role: PrevcRole): Promise<void> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'startRole',\n      risk: 'medium',\n      metadata: { role },\n    });\n    await this.orchestrator.startRole(role);\n    this.deps.ui?.displaySuccess(\n      `Started role: ${ROLE_DISPLAY_NAMES[role]}`\n    );\n  }\n\n  /**\n   * Complete a role's work\n   */\n  async completeRole(role: PrevcRole, outputs: string[]): Promise<void> {\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'completeRole',\n      paths: outputs,\n      risk: outputs.some((output) => /secret|token|key|password/i.test(output)) ? 'high' : 'medium',\n      metadata: {\n        role,\n        outputCount: outputs.length,\n      },\n    });\n    await this.orchestrator.completeRole(role, outputs);\n    this.deps.ui?.displaySuccess(\n      `Completed role: ${ROLE_DISPLAY_NAMES[role]}`\n    );\n  }\n\n  async getHarnessStatus(): Promise<WorkflowHarnessStatus | null> {\n    if (!(await this.hasWorkflow())) {\n      return null;\n    }\n\n    const summary = await this.getSummary();\n    const binding = await this.ensureHarnessSession(summary.name);\n\n    const session = await this.runtimeStateService.getSession(binding.sessionId);\n    const allSensorRuns = await this.sensorsService.getSessionSensorRuns(binding.sessionId);\n    const policyRules = await this.policyService.listRules();\n    const latestRuns = new Map<string, HarnessSensorRun>();\n    for (const run of allSensorRuns) {\n      const current = latestRuns.get(run.sensorId);\n      if (!current || current.createdAt < run.createdAt) {\n        latestRuns.set(run.sensorId, run);\n      }\n    }\n    const sensorRuns = Array.from(latestRuns.values()).sort((a, b) => a.sensorId.localeCompare(b.sensorId));\n    const taskContracts = (await this.taskContractsService.listTaskContracts())\n      .filter((contract) => contract.sessionId === binding.sessionId);\n    const handoffs = (await this.taskContractsService.listHandoffContracts())\n      .filter((handoff) => handoff.sessionId === binding.sessionId);\n    const activeTask = binding.activeTaskId\n      ? taskContracts.find((contract) => contract.id === binding.activeTaskId) ?? null\n      : null;\n    const taskCompletion = activeTask\n      ? await this.taskContractsService.evaluateTaskCompletion(activeTask.id, binding.sessionId)\n      : null;\n    const backpressure = this.sensorsService.evaluateBackpressure(sensorRuns, { requireEvidence: true });\n    const reasons = [\n      ...backpressure.reasons,\n      ...(taskCompletion?.blockingFindings || []),\n    ];\n\n    return {\n      binding,\n      session,\n      availableSensors: this.listAvailableSensors(),\n      sensorRuns,\n      taskContracts,\n      handoffs,\n      policyRules: policyRules.length,\n      completionCheck: {\n        blocked: reasons.length > 0,\n        reasons,\n        taskCompletion: taskCompletion ? {\n          canComplete: taskCompletion.canComplete,\n          missingSensors: taskCompletion.missingSensors,\n          missingArtifacts: taskCompletion.missingArtifacts,\n          blockingFindings: taskCompletion.blockingFindings,\n        } : null,\n      },\n    };\n  }\n\n  async checkpointHarnessSession(\n    note?: string,\n    data?: unknown,\n    artifactIds?: string[],\n    pause?: boolean\n  ): Promise<{ binding: WorkflowHarnessBinding; session: HarnessSessionRecord }> {\n    const binding = await this.requireHarnessBinding();\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'checkpoint',\n      risk: pause ? 'high' : 'medium',\n      metadata: {\n        note,\n        pause: Boolean(pause),\n        artifactCount: artifactIds?.length ?? 0,\n      },\n    });\n    const session = await this.runtimeStateService.checkpointSession(binding.sessionId, {\n      note,\n      data,\n      artifactIds,\n      pause,\n    });\n    binding.updatedAt = session.updatedAt;\n    await this.saveHarnessBinding(binding);\n    return { binding, session };\n  }\n\n  async recordHarnessArtifact(input: {\n    name: string;\n    kind?: HarnessArtifactKind;\n    content?: unknown;\n    path?: string;\n    metadata?: Record<string, unknown>;\n  }) {\n    const binding = await this.requireHarnessBinding();\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'recordArtifact',\n      paths: input.path ? [input.path] : [input.name],\n      risk: input.path?.includes('secret') ? 'critical' : 'medium',\n      metadata: input.metadata,\n    });\n    const artifact = await this.runtimeStateService.addArtifact(binding.sessionId, input);\n    binding.updatedAt = artifact.createdAt;\n    await this.saveHarnessBinding(binding);\n    return artifact;\n  }\n\n  async defineHarnessTask(input: {\n    title: string;\n    description?: string;\n    owner?: string;\n    inputs?: string[];\n    expectedOutputs?: string[];\n    acceptanceCriteria?: string[];\n    requiredSensors?: string[];\n    requiredArtifacts?: string[];\n    metadata?: Record<string, unknown>;\n  }): Promise<HarnessTaskContract> {\n    const binding = await this.requireHarnessBinding();\n    await this.policyService.authorize({\n      tool: 'workflow',\n      action: 'defineTask',\n      risk: input.requiredSensors?.includes('deploy') ? 'high' : 'medium',\n      metadata: {\n        ...input.metadata,\n        title: input.title,\n      },\n    });\n    const contract = await this.taskContractsService.createTaskContract({\n      ...input,\n      sessionId: binding.sessionId,\n      status: 'ready',\n    });\n\n    binding.activeTaskId = contract.id;\n    binding.updatedAt = contract.updatedAt;\n    await this.saveHarnessBinding(binding);\n    return contract;\n  }\n\n  async runHarnessSensors(sensorIds: string[], metadata?: Record<string, unknown>) {\n    const binding = await this.requireHarnessBinding();\n    const runs: HarnessSensorRun[] = [];\n\n    for (const sensorId of sensorIds) {\n      await this.policyService.authorize({\n        tool: 'workflow',\n        action: 'runSensors',\n        risk: sensorId === 'deploy' ? 'high' : 'medium',\n        metadata,\n      });\n      runs.push(await this.sensorsService.runSensor(sensorId, {\n        sessionId: binding.sessionId,\n        contractId: binding.activeTaskId,\n        metadata,\n      }));\n    }\n\n    return {\n      runs,\n      backpressure: this.sensorsService.evaluateBackpressure(runs, { requireEvidence: true }),\n    };\n  }\n\n  private async ensureHarnessSession(\n    workflowName: string,\n    description?: string\n  ): Promise<WorkflowHarnessBinding> {\n    const existing = await this.loadHarnessBinding();\n    if (existing && existing.workflowName === workflowName) {\n      try {\n        await this.runtimeStateService.getSession(existing.sessionId);\n        return existing;\n      } catch {\n        // Session was removed or became unreadable. Recreate below.\n      }\n    }\n\n    const session = await this.runtimeStateService.createSession({\n      name: workflowName,\n      metadata: {\n        workflow: true,\n        description,\n      },\n    });\n\n    const binding: WorkflowHarnessBinding = {\n      workflowName,\n      sessionId: session.id,\n      createdAt: new Date().toISOString(),\n      updatedAt: session.updatedAt,\n    };\n\n    await this.saveHarnessBinding(binding);\n    return binding;\n  }\n\n  private async requireHarnessBinding(): Promise<WorkflowHarnessBinding> {\n    const summary = await this.getSummary();\n    return this.ensureHarnessSession(summary.name);\n  }\n\n  private async loadHarnessBinding(): Promise<WorkflowHarnessBinding | null> {\n    return this.workflowStateService.getBinding();\n  }\n\n  private async saveHarnessBinding(binding: WorkflowHarnessBinding): Promise<void> {\n    await this.workflowStateService.saveBinding(binding);\n  }\n\n  private registerDefaultSensors(): void {\n    const definitions = this.sensorCatalogService.resolveEffectiveSensorsSync();\n\n    for (const definition of definitions) {\n      if (this.sensorsService.getSensor(definition.id)) {\n        continue;\n      }\n\n      this.sensorsService.registerSensor({\n        id: definition.id,\n        name: definition.name,\n        severity: definition.severity,\n        blocking: definition.severity === 'critical',\n        execute: async () => {\n          if (definition.script) {\n            const hasScript = await this.hasPackageScript(definition.script);\n            if (!hasScript) {\n              return {\n                status: definition.severity === 'warning' ? 'skipped' : 'blocked',\n                summary: `Script not available: ${definition.script}`,\n                evidence: [`Missing package.json script: ${definition.script}`],\n              };\n            }\n          }\n\n          return this.executeShellSensor(definition.command, definition.name);\n        },\n      });\n    }\n  }\n\n  private async hasPackageScript(scriptName: string): Promise<boolean> {\n    const packageJsonPath = path.join(this.repoPath, 'package.json');\n    if (!(await fs.pathExists(packageJsonPath))) {\n      return false;\n    }\n\n    const packageJson = await fs.readJson(packageJsonPath) as { scripts?: Record<string, string> };\n    return Boolean(packageJson.scripts?.[scriptName]);\n  }\n\n  private async executeShellSensor(command: string, name: string) {\n    try {\n      const { stdout, stderr } = await exec(command, {\n        cwd: this.repoPath,\n        timeout: 300000,\n      });\n\n      return {\n        status: 'passed' as const,\n        summary: `${name} passed`,\n        evidence: [this.trimEvidence(stdout), this.trimEvidence(stderr)].filter(Boolean) as string[],\n        output: {\n          command,\n        },\n      };\n    } catch (error: any) {\n      return {\n        status: 'failed' as const,\n        summary: `${name} failed`,\n        evidence: [\n          this.trimEvidence(error?.stdout),\n          this.trimEvidence(error?.stderr || error?.message),\n        ].filter(Boolean) as string[],\n        details: {\n          command,\n          exitCode: typeof error?.code === 'number' ? error.code : undefined,\n        },\n      };\n    }\n  }\n\n  private trimEvidence(value?: string, maxLength: number = 2000): string | null {\n    if (!value) {\n      return null;\n    }\n\n    return value.length > maxLength ? `${value.slice(0, maxLength)}\\n...[truncated]` : value;\n  }\n}\n"
  },
  {
    "path": "src/tests/integrity/postRefactoringIntegrity.test.ts",
    "content": "/**\n * Post-Refactoring Integrity Verification Tests\n *\n * Validates that all 5 contributions are correctly integrated\n * and no existing functionality was broken during refactoring.\n */\n\nimport * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs-extra';\n\n// ============================================================\n// Phase 4: Module Connectivity — verify all new imports resolve\n// ============================================================\n\ndescribe('Module Connectivity (Phase 4)', () => {\n    describe('Contribution 1: GitIgnoreManager → FileMapper integration', () => {\n        it('should import GitIgnoreManager from utils', () => {\n            const { GitIgnoreManager } = require('../../utils/gitignoreManager');\n            expect(GitIgnoreManager).toBeDefined();\n            expect(typeof GitIgnoreManager).toBe('function');\n        });\n\n        it('should create FileMapper with embedded GitIgnoreManager', () => {\n            const { FileMapper } = require('../../utils/fileMapper');\n            const mapper = new FileMapper();\n            expect(mapper).toBeDefined();\n            // Verify private gitIgnoreManager was instantiated\n            expect((mapper as any).gitIgnoreManager).toBeDefined();\n        });\n\n        it('should create FileMapper with custom excludes', () => {\n            const { FileMapper } = require('../../utils/fileMapper');\n            const mapper = new FileMapper(['*.custom']);\n            expect(mapper).toBeDefined();\n        });\n    });\n\n    describe('Contribution 2: FrontMatter exports', () => {\n        it('should export needsFill function', () => {\n            const { needsFill } = require('../../utils/frontMatter');\n            expect(typeof needsFill).toBe('function');\n        });\n\n        it('should export addFrontMatter function', () => {\n            const { addFrontMatter } = require('../../utils/frontMatter');\n            expect(typeof addFrontMatter).toBe('function');\n        });\n\n        it('should export removeFrontMatter function', () => {\n            const { removeFrontMatter } = require('../../utils/frontMatter');\n            expect(typeof removeFrontMatter).toBe('function');\n        });\n\n        it('should export parseFrontMatter function', () => {\n            const { parseFrontMatter } = require('../../utils/frontMatter');\n            expect(typeof parseFrontMatter).toBe('function');\n        });\n\n        it('should export getDocumentName function', () => {\n            const { getDocumentName } = require('../../utils/frontMatter');\n            expect(typeof getDocumentName).toBe('function');\n        });\n\n        it('should export isScaffoldContent function', () => {\n            const { isScaffoldContent } = require('../../utils/frontMatter');\n            expect(typeof isScaffoldContent).toBe('function');\n        });\n    });\n\n    describe('Contribution 3: PathValidator exports', () => {\n        it('should export PathValidator class', () => {\n            const { PathValidator } = require('../../utils/pathSecurity');\n            expect(PathValidator).toBeDefined();\n            expect(typeof PathValidator).toBe('function');\n        });\n\n        it('should export SecurityError class', () => {\n            const { SecurityError } = require('../../utils/pathSecurity');\n            expect(SecurityError).toBeDefined();\n            expect(typeof SecurityError).toBe('function');\n        });\n\n        it('should create PathValidator for a workspace root', () => {\n            const { PathValidator } = require('../../utils/pathSecurity');\n            const validator = new PathValidator('/workspace');\n            expect(validator).toBeDefined();\n            expect(typeof validator.validatePath).toBe('function');\n            expect(typeof validator.isWithinBoundary).toBe('function');\n            expect(typeof validator.safeResolve).toBe('function');\n        });\n    });\n\n    describe('Contribution 4: ContextCache exports', () => {\n        it('should export ContextCache class', () => {\n            const { ContextCache } = require('../../services/semantic/contextCache');\n            expect(ContextCache).toBeDefined();\n            expect(typeof ContextCache).toBe('function');\n        });\n\n        it('should create ContextCache with default options', () => {\n            const { ContextCache } = require('../../services/semantic/contextCache');\n            const cache = new ContextCache();\n            expect(cache).toBeDefined();\n            expect(cache.size).toBe(0);\n        });\n\n        it('should create ContextCache with custom TTL', () => {\n            const { ContextCache } = require('../../services/semantic/contextCache');\n            const cache = new ContextCache({ ttlMs: 1000, watchDirs: ['src'] });\n            expect(cache).toBeDefined();\n        });\n    });\n\n});\n\n// ============================================================\n// Phase 5: Contract Verification — backward compatibility\n// ============================================================\n\ndescribe('API Contract Verification (Phase 5)', () => {\n    describe('FrontMatter contract', () => {\n        it('addFrontMatter should produce valid YAML frontmatter', () => {\n            const { addFrontMatter } = require('../../utils/frontMatter');\n            const result = addFrontMatter('# Hello World', { status: 'filled', generated: '2026-01-01' });\n            expect(result).toContain('---');\n            expect(result).toContain('status: filled');\n            expect(result).toContain('generated:');\n            expect(result).toContain('# Hello World');\n        });\n\n        it('removeFrontMatter should strip YAML frontmatter', () => {\n            const { removeFrontMatter } = require('../../utils/frontMatter');\n            const input = '---\\nstatus: unfilled\\n---\\n# Hello';\n            const result = removeFrontMatter(input);\n            expect(result).not.toContain('---');\n            expect(result).toContain('# Hello');\n        });\n\n        it('needsFill should detect unfilled status in first 15 lines', async () => {\n            const { needsFill } = require('../../utils/frontMatter');\n            const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'needsfill-test-'));\n\n            // Create a v2 scaffold with status far down\n            const content = [\n                '---',\n                'type: documentation',\n                'name: architecture',\n                'description: System Architecture',\n                'generated: 2026-01-01',\n                'category: core',\n                'scaffoldVersion: v2',\n                'status: unfilled',\n                '---',\n                '# Architecture',\n                '',\n                'TODO'\n            ].join('\\n');\n\n            const filePath = path.join(tempDir, 'test.md');\n            await fs.writeFile(filePath, content);\n\n            const result = await needsFill(filePath);\n            expect(result).toBe(true);\n\n            await fs.remove(tempDir);\n        });\n\n        it('needsFill should return false for filled files', async () => {\n            const { needsFill } = require('../../utils/frontMatter');\n            const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'needsfill-test-'));\n\n            const content = '---\\nstatus: filled\\n---\\n# Done';\n            const filePath = path.join(tempDir, 'test.md');\n            await fs.writeFile(filePath, content);\n\n            const result = await needsFill(filePath);\n            expect(result).toBe(false);\n\n            await fs.remove(tempDir);\n        });\n    });\n\n    describe('GitIgnoreManager contract', () => {\n        it('should load .gitignore and filter paths correctly', async () => {\n            const { GitIgnoreManager } = require('../../utils/gitignoreManager');\n            const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitignore-contract-'));\n            await fs.writeFile(path.join(tempDir, '.gitignore'), 'dist/\\ncoverage/\\n');\n\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            const paths = ['src/index.ts', 'dist/bundle.js', 'coverage/lcov.info', 'README.md'];\n            const filtered = manager.filterPaths(paths);\n\n            expect(filtered).toContain('src/index.ts');\n            expect(filtered).toContain('README.md');\n            expect(filtered).not.toContain('dist/bundle.js');\n            expect(filtered).not.toContain('coverage/lcov.info');\n\n            await fs.remove(tempDir);\n        });\n    });\n\n    describe('PathValidator contract', () => {\n        it('should validate safe paths and reject traversals', () => {\n            const { PathValidator, SecurityError } = require('../../utils/pathSecurity');\n            const root = path.resolve('/workspace/project');\n            const validator = new PathValidator(root);\n\n            // Safe path\n            const resolved = validator.validatePath('src/index.ts');\n            expect(resolved).toBe(path.join(root, 'src', 'index.ts'));\n\n            // Traversal attack\n            expect(() => validator.validatePath('../../../etc/passwd')).toThrow(SecurityError);\n\n            // Null byte\n            expect(() => validator.validatePath('file\\0.ts')).toThrow(SecurityError);\n\n            // safeResolve returns null for bad paths\n            expect(validator.safeResolve('../../../etc/passwd')).toBeNull();\n            expect(validator.safeResolve('src/file.ts')).not.toBeNull();\n        });\n    });\n\n    describe('ContextCache contract', () => {\n        it('should store and retrieve data with TTL', async () => {\n            const { ContextCache } = require('../../services/semantic/contextCache');\n            const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cache-contract-'));\n            await fs.ensureDir(path.join(tempDir, 'src'));\n\n            const cache = new ContextCache({ ttlMs: 5000 });\n\n            // Cache miss\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n\n            // Store\n            await cache.set(tempDir, 'compact', 'cached-context');\n\n            // Cache hit\n            expect(await cache.get(tempDir, 'compact')).toBe('cached-context');\n\n            // Invalidation\n            cache.invalidateRepo(tempDir);\n            expect(await cache.get(tempDir, 'compact')).toBeNull();\n\n            await fs.remove(tempDir);\n        });\n    });\n\n});\n\n"
  },
  {
    "path": "src/types/scaffoldFrontmatter.ts",
    "content": "/**\n * Scaffold Frontmatter Types\n *\n * These types define the metadata structure for scaffolded files.\n * Scaffolding v2.0 generates frontmatter-only files where the AI\n * generates complete content based on scaffold structure definitions.\n */\n\nimport { PrevcPhase } from '../workflow/types';\n\n/**\n * File types that can be scaffolded\n */\nexport type ScaffoldFileType = 'doc' | 'agent' | 'skill' | 'plan';\n\n/**\n * Scaffold status\n */\nexport type ScaffoldStatus = 'unfilled' | 'filled';\n\n/**\n * Base frontmatter present in all scaffold files\n */\nexport interface BaseScaffoldFrontmatter {\n  /** File type identifier */\n  type: ScaffoldFileType;\n  /** Unique name/identifier (e.g., 'project-overview', 'code-reviewer') */\n  name: string;\n  /** Human-readable description */\n  description: string;\n  /** ISO date string when file was generated */\n  generated: string;\n  /** Fill status */\n  status: ScaffoldStatus;\n  /** Scaffold version for migration support */\n  scaffoldVersion: '2.0.0';\n}\n\n/**\n * Documentation-specific frontmatter\n */\nexport interface DocScaffoldFrontmatter extends BaseScaffoldFrontmatter {\n  type: 'doc';\n  /** Category of documentation */\n  category?: 'overview' | 'architecture' | 'workflow' | 'testing' | 'security' | 'tooling' | 'glossary' | 'data-flow' | 'api' | 'migration' | 'onboarding' | 'troubleshooting';\n}\n\n/**\n * Agent-specific frontmatter\n */\nexport interface AgentScaffoldFrontmatter extends BaseScaffoldFrontmatter {\n  type: 'agent';\n  /** Agent type identifier */\n  agentType: string;\n  /** PREVC phases where this agent is relevant */\n  phases?: PrevcPhase[];\n}\n\n/**\n * Skill-specific frontmatter\n */\nexport interface SkillScaffoldFrontmatter extends BaseScaffoldFrontmatter {\n  type: 'skill';\n  /** Skill slug/identifier */\n  skillSlug: string;\n  /** PREVC phases where this skill is relevant */\n  phases?: PrevcPhase[];\n  /** Whether this is a mode command */\n  mode?: boolean;\n  /** Disable auto-activation by AI */\n  disableModelInvocation?: boolean;\n}\n\n/**\n * Plan-specific frontmatter\n */\nexport interface PlanScaffoldFrontmatter extends BaseScaffoldFrontmatter {\n  type: 'plan';\n  /** Plan slug/identifier */\n  planSlug: string;\n  /** Plan summary/goal */\n  summary?: string;\n  /** Linked agents */\n  agents?: Array<{ type: string; role: string }>;\n  /** Linked documentation */\n  docs?: string[];\n  /** Plan phases */\n  phases?: Array<{ id: string; name: string; prevc: PrevcPhase }>;\n}\n\n/**\n * Union type for all scaffold frontmatter\n */\nexport type ScaffoldFrontmatter =\n  | DocScaffoldFrontmatter\n  | AgentScaffoldFrontmatter\n  | SkillScaffoldFrontmatter\n  | PlanScaffoldFrontmatter;\n\n/**\n * Type guard to check if frontmatter is for documentation\n */\nexport function isDocFrontmatter(fm: ScaffoldFrontmatter): fm is DocScaffoldFrontmatter {\n  return fm.type === 'doc';\n}\n\n/**\n * Type guard to check if frontmatter is for agent\n */\nexport function isAgentFrontmatter(fm: ScaffoldFrontmatter): fm is AgentScaffoldFrontmatter {\n  return fm.type === 'agent';\n}\n\n/**\n * Type guard to check if frontmatter is for skill\n */\nexport function isSkillFrontmatter(fm: ScaffoldFrontmatter): fm is SkillScaffoldFrontmatter {\n  return fm.type === 'skill';\n}\n\n/**\n * Type guard to check if frontmatter is for plan\n */\nexport function isPlanFrontmatter(fm: ScaffoldFrontmatter): fm is PlanScaffoldFrontmatter {\n  return fm.type === 'plan';\n}\n\n/**\n * Create unfilled frontmatter for a document\n */\nexport function createDocFrontmatter(\n  name: string,\n  description: string,\n  category?: DocScaffoldFrontmatter['category']\n): DocScaffoldFrontmatter {\n  return {\n    type: 'doc',\n    name,\n    description,\n    category,\n    generated: new Date().toISOString().split('T')[0],\n    status: 'unfilled',\n    scaffoldVersion: '2.0.0',\n  };\n}\n\n/**\n * Create unfilled frontmatter for an agent\n */\nexport function createAgentFrontmatter(\n  name: string,\n  description: string,\n  agentType: string,\n  phases?: PrevcPhase[]\n): AgentScaffoldFrontmatter {\n  return {\n    type: 'agent',\n    name,\n    description,\n    agentType,\n    phases,\n    generated: new Date().toISOString().split('T')[0],\n    status: 'unfilled',\n    scaffoldVersion: '2.0.0',\n  };\n}\n\n/**\n * Create unfilled frontmatter for a skill\n */\nexport function createSkillFrontmatter(\n  name: string,\n  description: string,\n  skillSlug: string,\n  options?: {\n    phases?: PrevcPhase[];\n    mode?: boolean;\n    disableModelInvocation?: boolean;\n  }\n): SkillScaffoldFrontmatter {\n  return {\n    type: 'skill',\n    name,\n    description,\n    skillSlug,\n    phases: options?.phases,\n    mode: options?.mode,\n    disableModelInvocation: options?.disableModelInvocation,\n    generated: new Date().toISOString().split('T')[0],\n    status: 'unfilled',\n    scaffoldVersion: '2.0.0',\n  };\n}\n\n/**\n * Create unfilled frontmatter for a plan\n */\nexport function createPlanFrontmatter(\n  name: string,\n  description: string,\n  planSlug: string,\n  options?: {\n    summary?: string;\n    agents?: Array<{ type: string; role: string }>;\n    docs?: string[];\n    phases?: Array<{ id: string; name: string; prevc: PrevcPhase }>;\n  }\n): PlanScaffoldFrontmatter {\n  return {\n    type: 'plan',\n    name,\n    description,\n    planSlug,\n    summary: options?.summary,\n    agents: options?.agents,\n    docs: options?.docs,\n    phases: options?.phases,\n    generated: new Date().toISOString().split('T')[0],\n    status: 'unfilled',\n    scaffoldVersion: '2.0.0',\n  };\n}\n\n/**\n * Serialize frontmatter to YAML string (for file output)\n */\nexport function serializeFrontmatter(fm: ScaffoldFrontmatter): string {\n  const lines: string[] = ['---'];\n\n  // Common fields first\n  lines.push(`type: ${fm.type}`);\n  lines.push(`name: ${fm.name}`);\n  lines.push(`description: ${fm.description}`);\n\n  // Type-specific fields\n  if (isDocFrontmatter(fm) && fm.category) {\n    lines.push(`category: ${fm.category}`);\n  }\n\n  if (isAgentFrontmatter(fm)) {\n    lines.push(`agentType: ${fm.agentType}`);\n    if (fm.phases && fm.phases.length > 0) {\n      lines.push(`phases: [${fm.phases.join(', ')}]`);\n    }\n  }\n\n  if (isSkillFrontmatter(fm)) {\n    lines.push(`skillSlug: ${fm.skillSlug}`);\n    if (fm.phases && fm.phases.length > 0) {\n      lines.push(`phases: [${fm.phases.join(', ')}]`);\n    }\n    if (fm.mode !== undefined) {\n      lines.push(`mode: ${fm.mode}`);\n    }\n    if (fm.disableModelInvocation !== undefined) {\n      lines.push(`disable-model-invocation: ${fm.disableModelInvocation}`);\n    }\n  }\n\n  if (isPlanFrontmatter(fm)) {\n    lines.push(`planSlug: ${fm.planSlug}`);\n    if (fm.summary) {\n      lines.push(`summary: ${fm.summary}`);\n    }\n    if (fm.agents && fm.agents.length > 0) {\n      lines.push('agents:');\n      for (const agent of fm.agents) {\n        lines.push(`  - type: \"${agent.type}\"`);\n        lines.push(`    role: \"${agent.role}\"`);\n      }\n    }\n    if (fm.docs && fm.docs.length > 0) {\n      lines.push('docs:');\n      for (const doc of fm.docs) {\n        lines.push(`  - \"${doc}\"`);\n      }\n    }\n    if (fm.phases && fm.phases.length > 0) {\n      lines.push('phases:');\n      for (const phase of fm.phases) {\n        lines.push(`  - id: \"${phase.id}\"`);\n        lines.push(`    name: \"${phase.name}\"`);\n        lines.push(`    prevc: \"${phase.prevc}\"`);\n      }\n    }\n  }\n\n  // Common trailing fields\n  lines.push(`generated: ${fm.generated}`);\n  lines.push(`status: ${fm.status}`);\n  lines.push(`scaffoldVersion: \"${fm.scaffoldVersion}\"`);\n\n  lines.push('---');\n\n  return lines.join('\\n');\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "export interface FileInfo {\n  path: string;\n  relativePath: string;\n  extension: string;\n  size: number;\n  content?: string;\n  type: 'file' | 'directory';\n}\n\nexport interface TopLevelDirectoryStats {\n  name: string;\n  fileCount: number;\n  totalSize: number;\n}\n\nexport interface RepoStructure {\n  rootPath: string;\n  files: FileInfo[];\n  directories: FileInfo[];\n  totalFiles: number;\n  totalSize: number;\n  topLevelDirectoryStats: TopLevelDirectoryStats[];\n}\n\nexport type AIProvider = 'openrouter' | 'openai' | 'anthropic' | 'google';\n\nexport interface LLMConfig {\n  apiKey: string;\n  model: string;\n  baseUrl?: string;\n  provider: AIProvider;\n}\n\nexport interface CLIOptions {\n  repoPath: string;\n  outputDir?: string;\n  model?: string;\n  apiKey?: string;\n  provider?: LLMConfig['provider'];\n  exclude?: string[];\n  include?: string[];\n  verbose?: boolean;\n  since?: string;\n  staged?: boolean;\n  force?: boolean;\n}\n\nexport interface AgentPrompt {\n  name: string;\n  description: string;\n  systemPrompt: string;\n  context: string;\n  examples?: string[];\n}\n\nexport interface TokenUsage {\n  promptTokens: number;\n  completionTokens: number;\n  totalTokens: number;\n}\n\nexport interface UsageStats {\n  totalCalls: number;\n  totalPromptTokens: number;\n  totalCompletionTokens: number;\n  totalTokens: number;\n  model: string;\n}\n"
  },
  {
    "path": "src/utils/cliUI.ts",
    "content": "import ora, { Ora } from 'ora';\nimport * as cliProgress from 'cli-progress';\n\nimport { TranslateFn, TranslationKey, TranslateParams } from './i18n';\nimport { colors, symbols, typography } from './theme';\n\nexport class CLIInterface {\n  private spinner: Ora | null = null;\n  private progressBar: cliProgress.SingleBar | null = null;\n  private startTime: number = Date.now();\n\n  constructor(private readonly translate: TranslateFn) {}\n\n  displayProjectInfo(repoPath: string, outputDir: string, mode: string): void {\n    console.log(typography.header(this.t('ui.projectConfiguration.title')));\n    console.log('');\n    console.log(typography.labeledValue(this.t('ui.projectConfiguration.repository'), repoPath, 14));\n    console.log(typography.labeledValue(this.t('ui.projectConfiguration.output'), outputDir, 14));\n    console.log(typography.labeledValue(this.t('ui.projectConfiguration.mode'), mode, 14));\n    console.log('');\n  }\n\n  startSpinner(text: string): void {\n    // Stop any existing spinner to prevent orphaned spinners\n    if (this.spinner) {\n      this.spinner.stop();\n    }\n    this.spinner = ora({\n      text: colors.secondary(text),\n      spinner: 'dots',\n      color: 'white'\n    }).start();\n  }\n\n  updateSpinner(text: string, type?: 'success' | 'fail' | 'warn' | 'info'): void {\n    if (!this.spinner) return;\n\n    switch (type) {\n      case 'success':\n        this.spinner.stopAndPersist({\n          symbol: colors.success(symbols.success),\n          text: colors.primary(text)\n        });\n        break;\n      case 'fail':\n        this.spinner.stopAndPersist({\n          symbol: colors.error(symbols.error),\n          text: colors.primary(text)\n        });\n        break;\n      case 'warn':\n        this.spinner.stopAndPersist({\n          symbol: colors.warning(symbols.warning),\n          text: colors.primary(text)\n        });\n        break;\n      case 'info':\n        this.spinner.stopAndPersist({\n          symbol: colors.accent(symbols.info),\n          text: colors.primary(text)\n        });\n        break;\n      default:\n        this.spinner.text = colors.secondary(text);\n    }\n  }\n\n  stopSpinner(success: boolean = true): void {\n    if (!this.spinner) return;\n\n    if (success) {\n      this.spinner.stop();\n    } else {\n      this.spinner.fail();\n    }\n    this.spinner = null;\n  }\n\n  createProgressBar(total: number, title: string): void {\n    this.progressBar = new cliProgress.SingleBar({\n      format: `${colors.secondary(title)} {bar} {percentage}% ${colors.secondaryDim('{value}/{total}')} ${colors.secondaryDim('{task}')}`,\n      barCompleteChar: '\\u2588',\n      barIncompleteChar: '\\u2591',\n      hideCursor: true,\n      stopOnComplete: true,\n      clearOnComplete: false\n    }, cliProgress.Presets.rect);\n\n    this.progressBar.start(total, 0, {\n      task: this.t('ui.progress.starting')\n    });\n  }\n\n  updateProgress(current: number, task: string): void {\n    if (!this.progressBar) return;\n    this.progressBar.update(current, { task });\n  }\n\n  completeProgress(): void {\n    if (!this.progressBar) return;\n    this.progressBar.stop();\n    this.progressBar = null;\n  }\n\n  displayAnalysisResults(totalFiles: number, totalDirs: number, totalSize: string): void {\n    console.log('');\n    console.log(typography.header(this.t('ui.analysis.complete.title')));\n    console.log('');\n    console.log(typography.labeledValue(this.t('ui.analysis.files'), totalFiles.toString()));\n    console.log(typography.labeledValue(this.t('ui.analysis.directories'), totalDirs.toString()));\n    console.log(typography.labeledValue(this.t('ui.analysis.totalSize'), totalSize));\n    console.log('');\n  }\n\n  displayFileTypeDistribution(distribution: Map<string, number>, totalFiles: number): void {\n    console.log(typography.header(this.t('ui.fileTypeDistribution.title')));\n    console.log('');\n\n    const sorted = Array.from(distribution.entries())\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, 10);\n\n    sorted.forEach(([ext, count]) => {\n      const percentage = ((count / totalFiles) * 100).toFixed(1);\n      const barLength = Math.round((count / totalFiles) * 30);\n      const bar = '\\u2588'.repeat(barLength) + '\\u2591'.repeat(30 - barLength);\n\n      console.log(\n        `  ${colors.primary(ext.padEnd(12))} ${colors.secondaryDim(bar)} ${colors.secondary(count.toString().padStart(4))} ${colors.secondaryDim(`(${percentage}%)`)}`\n      );\n    });\n    console.log('');\n  }\n\n  displayGenerationSummary(docsGenerated: number, agentsGenerated: number, skillsGenerated?: number): void {\n    const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);\n\n    console.log('');\n    console.log(typography.separator());\n    console.log(typography.header(this.t('ui.generationSummary.title')));\n    console.log('');\n    console.log(typography.labeledValue(this.t('ui.generationSummary.documentation'), `${docsGenerated} files`));\n    console.log(typography.labeledValue(this.t('ui.generationSummary.agents'), `${agentsGenerated} playbooks`));\n    if (skillsGenerated !== undefined && skillsGenerated > 0) {\n      console.log(typography.labeledValue(this.t('ui.generationSummary.skills'), `${skillsGenerated} skills`));\n    }\n    console.log(typography.labeledValue(this.t('ui.generationSummary.timeElapsed'), `${elapsed}s`));\n    console.log('');\n    console.log(colors.secondaryDim(this.t('ui.generationSummary.nextStep')));\n    console.log('');\n  }\n\n  displayError(message: string, error?: Error): void {\n    console.log('');\n    console.log(`${colors.error(symbols.error)} ${colors.primaryBold(this.t('ui.error.title'))}`);\n    console.log('');\n    console.log(`  ${colors.primary(message)}`);\n    if (error?.stack) {\n      console.log('');\n      console.log(colors.secondaryDim(error.stack));\n    }\n    console.log('');\n  }\n\n  displayInfo(title: string, message: string): void {\n    console.log('');\n    console.log(`${colors.accent(symbols.info)} ${typography.header(title)}`);\n    console.log('');\n    console.log(`  ${colors.primary(message)}`);\n    console.log('');\n  }\n\n  displaySuccess(message: string): void {\n    console.log(typography.success(message));\n  }\n\n  displayWarning(message: string): void {\n    console.log(typography.warning(message));\n  }\n\n  displayStep(step: number, total: number, description: string): void {\n    console.log(\n      colors.secondaryDim(`[${step}/${total}]`) + ' ' +\n      colors.primary(description)\n    );\n  }\n\n  formatBytes(bytes: number): string {\n    if (bytes === 0) return '0 Bytes';\n    const k = 1024;\n    const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n  }\n\n  private t(key: TranslationKey, params?: TranslateParams): string {\n    return this.translate(key, params);\n  }\n}\n"
  },
  {
    "path": "src/utils/fileMapper.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport { FileInfo, RepoStructure, TopLevelDirectoryStats } from '../types';\nimport { GitIgnoreManager } from './gitignoreManager';\n\nexport class FileMapper {\n  private excludePatterns: string[] = [\n    'node_modules',\n    '**/node_modules',\n    '**/node_modules/**',\n    '.git/**',\n    'dist/**',\n    'build/**',\n    '*.log',\n    '.env*',\n    '*.tmp',\n    '**/.DS_Store'\n  ];\n\n  private readonly gitIgnoreManager: GitIgnoreManager;\n\n  constructor(customExcludes: string[] = []) {\n    this.excludePatterns = [...this.excludePatterns, ...customExcludes];\n    this.gitIgnoreManager = new GitIgnoreManager({ extraPatterns: customExcludes });\n  }\n\n  private async loadGitignorePatterns(repoPath: string): Promise<string[]> {\n    const gitignorePath = path.join(repoPath, '.gitignore');\n    if (!await fs.pathExists(gitignorePath)) {\n      return [];\n    }\n    const content = await fs.readFile(gitignorePath, 'utf-8');\n    return content\n      .split('\\n')\n      .map(line => line.trim())\n      .filter(line => line.length > 0 && !line.startsWith('#'));\n  }\n\n  async mapRepository(repoPath: string, includePatterns?: string[]): Promise<RepoStructure> {\n    const absolutePath = path.resolve(repoPath);\n\n    if (!await fs.pathExists(absolutePath)) {\n      throw new Error(`Repository path does not exist: ${absolutePath}`);\n    }\n\n    const gitignorePatterns = await this.loadGitignorePatterns(absolutePath);\n    const ignorePatterns = [...this.excludePatterns, ...gitignorePatterns];\n\n    const patterns = includePatterns || ['**/*'];\n    const allFiles: string[] = [];\n\n    for (const pattern of patterns) {\n      const files = await glob(pattern, {\n        cwd: absolutePath,\n        ignore: ignorePatterns,\n        dot: false,\n        absolute: false\n      });\n      allFiles.push(...files);\n    }\n\n    // Apply .gitignore filtering on top of glob excludes\n    const filteredFiles = this.gitIgnoreManager.filterPaths(allFiles);\n\n    const uniqueFiles = [...new Set(filteredFiles)];\n    const fileInfos: FileInfo[] = [];\n    const directories: FileInfo[] = [];\n    let totalSize = 0;\n    const topLevelStats = new Map<string, { fileCount: number; totalSize: number }>();\n    const concurrency = 32;\n\n    for (let index = 0; index < uniqueFiles.length; index += concurrency) {\n      const slice = uniqueFiles.slice(index, index + concurrency);\n      await Promise.all(\n        slice.map(async relativePath => {\n          const fullPath = path.join(absolutePath, relativePath);\n          const stats = await fs.stat(fullPath);\n          const info: FileInfo = {\n            path: fullPath,\n            relativePath,\n            extension: path.extname(relativePath),\n            size: stats.size,\n            type: stats.isDirectory() ? 'directory' : 'file'\n          };\n\n          const topLevelSegment = this.extractTopLevelSegment(relativePath);\n          if (topLevelSegment) {\n            const current = topLevelStats.get(topLevelSegment) ?? { fileCount: 0, totalSize: 0 };\n            if (!stats.isDirectory()) {\n              current.fileCount += 1;\n              current.totalSize += stats.size;\n            }\n            topLevelStats.set(topLevelSegment, current);\n          }\n\n          if (stats.isDirectory()) {\n            directories.push(info);\n          } else {\n            fileInfos.push(info);\n            totalSize += stats.size;\n          }\n\n        })\n      );\n    }\n\n    const topLevelDirectoryStats: TopLevelDirectoryStats[] = Array.from(topLevelStats.entries())\n      .map(([name, stats]) => ({ name, fileCount: stats.fileCount, totalSize: stats.totalSize }))\n      .sort((a, b) => a.name.localeCompare(b.name));\n\n    return {\n      rootPath: absolutePath,\n      files: fileInfos,\n      directories,\n      totalFiles: fileInfos.length,\n      totalSize,\n      topLevelDirectoryStats\n    };\n  }\n\n  async readFileContent(filePath: string): Promise<string> {\n    try {\n      return await fs.readFile(filePath, 'utf-8');\n    } catch (error) {\n      return `Error reading file: ${error instanceof Error ? error.message : String(error)}`;\n    }\n  }\n\n  getFilesByExtension(files: FileInfo[], extension: string): FileInfo[] {\n    return files.filter(file => file.extension === extension);\n  }\n\n  isTextFile(filePath: string): boolean {\n    const textExtensions = [\n      '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c', '.h',\n      '.css', '.scss', '.sass', '.html', '.xml', '.json', '.yaml', '.yml',\n      '.md', '.txt', '.sql', '.sh', '.bat', '.ps1', '.php', '.rb', '.go',\n      '.rs', '.swift', '.kt', '.scala', '.r', '.m', '.pl', '.lua', '.vim',\n      '.dockerfile', '.gitignore', '.env'\n    ];\n\n    const ext = path.extname(filePath).toLowerCase();\n    return textExtensions.includes(ext) || !ext;\n  }\n\n  private extractTopLevelSegment(relativePath: string): string | null {\n    const parts = relativePath.split(/[/\\\\]/).filter(Boolean);\n    return parts.length > 0 ? parts[0] : null;\n  }\n}\n"
  },
  {
    "path": "src/utils/frontMatter.test.ts",
    "content": "import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport * as os from 'os';\nimport {\n  needsFill,\n  parseFrontMatter,\n  addFrontMatter,\n  removeFrontMatter,\n  hasFrontMatter,\n  createUnfilledFrontMatter,\n  getUnfilledFiles,\n  getFilledStats\n} from './frontMatter';\n\ndescribe('frontMatter', () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'frontmatter-test-'));\n  });\n\n  afterEach(async () => {\n    await fs.rm(tempDir, { recursive: true, force: true });\n  });\n\n  describe('needsFill', () => {\n    it('should return true for files with status: unfilled', async () => {\n      const filePath = path.join(tempDir, 'unfilled.md');\n      await fs.writeFile(filePath, '---\\nstatus: unfilled\\n---\\n\\n# Content');\n\n      expect(await needsFill(filePath)).toBe(true);\n    });\n\n    it('should return false for files without front matter', async () => {\n      const filePath = path.join(tempDir, 'no-frontmatter.md');\n      await fs.writeFile(filePath, '# Just content\\n\\nNo front matter here.');\n\n      expect(await needsFill(filePath)).toBe(false);\n    });\n\n    it('should return false for filled files', async () => {\n      const filePath = path.join(tempDir, 'filled.md');\n      await fs.writeFile(filePath, '---\\nstatus: filled\\n---\\n\\n# Filled content');\n\n      expect(await needsFill(filePath)).toBe(false);\n    });\n\n    it('should return false for files without status', async () => {\n      const filePath = path.join(tempDir, 'no-status.md');\n      await fs.writeFile(filePath, '---\\ngenerated: 2026-01-09\\n---\\n\\n# Content');\n\n      expect(await needsFill(filePath)).toBe(false);\n    });\n\n    it('should return false for non-existent files', async () => {\n      expect(await needsFill('/nonexistent/file.md')).toBe(false);\n    });\n  });\n\n  describe('parseFrontMatter', () => {\n    it('should parse front matter correctly', () => {\n      const content = '---\\nstatus: unfilled\\ngenerated: 2026-01-09\\n---\\n\\n# Title';\n      const { frontMatter, body } = parseFrontMatter(content);\n\n      expect(frontMatter).toEqual({\n        status: 'unfilled',\n        generated: '2026-01-09'\n      });\n      expect(body).toBe('# Title');\n    });\n\n    it('should return null frontMatter for content without it', () => {\n      const content = '# Just content\\n\\nNo front matter.';\n      const { frontMatter, body } = parseFrontMatter(content);\n\n      expect(frontMatter).toBeNull();\n      expect(body).toBe(content);\n    });\n\n    it('should handle incomplete front matter', () => {\n      const content = '---\\nstatus: unfilled\\n# Missing closing delimiter';\n      const { frontMatter, body } = parseFrontMatter(content);\n\n      expect(frontMatter).toBeNull();\n      expect(body).toBe(content);\n    });\n\n    it('should handle empty content between delimiters', () => {\n      const content = '---\\n---\\n\\n# Content';\n      const { frontMatter, body } = parseFrontMatter(content);\n\n      expect(frontMatter).toEqual({});\n      expect(body).toBe('# Content');\n    });\n  });\n\n  describe('addFrontMatter', () => {\n    it('should add front matter to content', () => {\n      const content = '# Title\\n\\nContent here.';\n      const result = addFrontMatter(content, { status: 'unfilled', generated: '2026-01-09' });\n\n      // Function adds single newline after closing ---\n      expect(result).toBe('---\\nstatus: unfilled\\ngenerated: 2026-01-09\\n---\\n# Title\\n\\nContent here.');\n    });\n\n    it('should skip undefined values', () => {\n      const content = '# Title';\n      const result = addFrontMatter(content, { status: 'unfilled', generated: undefined });\n\n      expect(result).toBe('---\\nstatus: unfilled\\n---\\n# Title');\n    });\n  });\n\n  describe('removeFrontMatter', () => {\n    it('should remove front matter from content', () => {\n      const content = '---\\nstatus: unfilled\\n---\\n\\n# Title\\n\\nContent';\n      const result = removeFrontMatter(content);\n\n      expect(result).toBe('# Title\\n\\nContent');\n    });\n\n    it('should return original content if no front matter', () => {\n      const content = '# Title\\n\\nContent';\n      const result = removeFrontMatter(content);\n\n      expect(result).toBe(content);\n    });\n  });\n\n  describe('hasFrontMatter', () => {\n    it('should return true for content with front matter', () => {\n      expect(hasFrontMatter('---\\nstatus: unfilled\\n---\\n\\n# Content')).toBe(true);\n    });\n\n    it('should return false for content without front matter', () => {\n      expect(hasFrontMatter('# Content')).toBe(false);\n    });\n\n    it('should handle leading whitespace', () => {\n      expect(hasFrontMatter('  ---\\nstatus: unfilled\\n---')).toBe(true);\n    });\n  });\n\n  describe('createUnfilledFrontMatter', () => {\n    it('should create front matter with status unfilled', () => {\n      const fm = createUnfilledFrontMatter();\n\n      expect(fm.status).toBe('unfilled');\n      expect(fm.generated).toMatch(/^\\d{4}-\\d{2}-\\d{2}$/);\n    });\n  });\n\n  describe('getUnfilledFiles', () => {\n    it('should return only unfilled files', async () => {\n      await fs.writeFile(path.join(tempDir, 'unfilled.md'), '---\\nstatus: unfilled\\n---\\n\\n# Unfilled');\n      await fs.writeFile(path.join(tempDir, 'filled.md'), '# Filled\\n\\nNo front matter.');\n\n      const unfilled = await getUnfilledFiles(tempDir);\n\n      expect(unfilled).toHaveLength(1);\n      expect(unfilled[0]).toContain('unfilled.md');\n    });\n\n    it('should handle nested directories', async () => {\n      await fs.mkdir(path.join(tempDir, 'nested'), { recursive: true });\n      await fs.writeFile(path.join(tempDir, 'nested', 'doc.md'), '---\\nstatus: unfilled\\n---\\n\\n# Doc');\n\n      const unfilled = await getUnfilledFiles(tempDir);\n\n      expect(unfilled).toHaveLength(1);\n      expect(unfilled[0]).toContain('nested');\n    });\n  });\n\n  describe('getFilledStats', () => {\n    it('should return correct statistics', async () => {\n      await fs.writeFile(path.join(tempDir, 'unfilled1.md'), '---\\nstatus: unfilled\\n---\\n');\n      await fs.writeFile(path.join(tempDir, 'unfilled2.md'), '---\\nstatus: unfilled\\n---\\n');\n      await fs.writeFile(path.join(tempDir, 'filled.md'), '# Filled content');\n\n      const stats = await getFilledStats(tempDir);\n\n      expect(stats.total).toBe(3);\n      expect(stats.filled).toBe(1);\n      expect(stats.unfilled).toBe(2);\n      expect(stats.files).toHaveLength(3);\n    });\n\n    it('should handle empty directory', async () => {\n      const stats = await getFilledStats(tempDir);\n\n      expect(stats.total).toBe(0);\n      expect(stats.filled).toBe(0);\n      expect(stats.unfilled).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/utils/frontMatter.ts",
    "content": "/**\n * YAML Front Matter utilities for status detection\n *\n * Allows instant detection of unfilled files by reading only the first line.\n * Supports both v1 (simple) and v2 (scaffold) frontmatter formats.\n */\n\nimport * as fs from 'fs/promises';\nimport { createReadStream } from 'fs';\nimport * as readline from 'readline';\nimport type { ScaffoldFrontmatter, ScaffoldFileType, ScaffoldStatus } from '../types/scaffoldFrontmatter';\n\n/**\n * Legacy v1 frontmatter (simple status tracking)\n */\nexport interface FrontMatter {\n  status?: 'unfilled' | 'filled';\n  generated?: string;\n  [key: string]: string | undefined;\n}\n\n/**\n * Parsed v2 scaffold frontmatter\n */\nexport interface ParsedScaffoldFrontmatter {\n  type: ScaffoldFileType;\n  name: string;\n  description: string;\n  generated: string;\n  status: ScaffoldStatus;\n  scaffoldVersion: '2.0.0';\n  // Type-specific fields\n  category?: string;\n  agentType?: string;\n  skillSlug?: string;\n  planSlug?: string;\n  phases?: string[];\n  mode?: boolean;\n  disableModelInvocation?: boolean;\n  summary?: string;\n  agents?: Array<{ type: string; role: string }>;\n  docs?: string[];\n}\n\n/**\n * Check if frontmatter is v2 scaffold format\n */\nexport function isScaffoldFrontmatter(fm: FrontMatter | ParsedScaffoldFrontmatter): fm is ParsedScaffoldFrontmatter {\n  return 'scaffoldVersion' in fm && fm.scaffoldVersion === '2.0.0';\n}\n\nconst FRONT_MATTER_DELIMITER = '---';\n\n/**\n * Check if a file needs to be filled by reading only the frontmatter block.\n * Searches for 'status: unfilled' only within the YAML frontmatter (between --- delimiters),\n * not in the document body where example code might contain the same string.\n */\nexport async function needsFill(filePath: string): Promise<boolean> {\n  try {\n    const { readFile } = await import('fs-extra');\n    const content = await readFile(filePath, 'utf-8');\n    const lines = content.split('\\n');\n\n    if (lines[0]?.trim() !== FRONT_MATTER_DELIMITER) {\n      return false;\n    }\n\n    for (let i = 1; i < lines.length; i++) {\n      const trimmed = lines[i].trim();\n      if (trimmed === FRONT_MATTER_DELIMITER) {\n        break; // End of frontmatter\n      }\n      if (trimmed.includes('status: unfilled')) {\n        return true;\n      }\n    }\n\n    return false;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Read only the first N lines of a file efficiently\n */\nasync function readFirstLines(filePath: string, n: number): Promise<string[]> {\n  return new Promise((resolve, reject) => {\n    const lines: string[] = [];\n    const stream = createReadStream(filePath);\n    const rl = readline.createInterface({\n      input: stream,\n      crlfDelay: Infinity\n    });\n\n    rl.on('line', (line) => {\n      lines.push(line);\n      if (lines.length >= n) {\n        rl.close();\n        stream.destroy();\n      }\n    });\n\n    rl.on('close', () => resolve(lines));\n    rl.on('error', reject);\n  });\n}\n\n/**\n * Parse YAML front matter from content string\n */\nexport function parseFrontMatter(content: string): { frontMatter: FrontMatter | null; body: string } {\n  const lines = content.split('\\n');\n\n  if (lines[0]?.trim() !== FRONT_MATTER_DELIMITER) {\n    return { frontMatter: null, body: content };\n  }\n\n  const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === FRONT_MATTER_DELIMITER);\n\n  if (endIndex === -1) {\n    return { frontMatter: null, body: content };\n  }\n\n  const frontMatterLines = lines.slice(1, endIndex);\n  const frontMatter: FrontMatter = {};\n\n  for (const line of frontMatterLines) {\n    const match = line.match(/^(\\w+):\\s*(.*)$/);\n    if (match) {\n      frontMatter[match[1]] = match[2].trim();\n    }\n  }\n\n  const body = lines.slice(endIndex + 1).join('\\n').replace(/^\\n+/, '');\n\n  return { frontMatter, body };\n}\n\n/**\n * Add front matter to content\n */\nexport function addFrontMatter(content: string, frontMatter: FrontMatter): string {\n  const lines = [FRONT_MATTER_DELIMITER];\n\n  for (const [key, value] of Object.entries(frontMatter)) {\n    if (value !== undefined) {\n      lines.push(`${key}: ${value}`);\n    }\n  }\n\n  lines.push(FRONT_MATTER_DELIMITER);\n  lines.push('');\n\n  return lines.join('\\n') + content;\n}\n\n/**\n * Remove front matter from content (used after filling)\n */\nexport function removeFrontMatter(content: string): string {\n  const { body } = parseFrontMatter(content);\n  return body;\n}\n\n/**\n * Check if content has front matter\n */\nexport function hasFrontMatter(content: string): boolean {\n  return content.trimStart().startsWith(FRONT_MATTER_DELIMITER);\n}\n\n/**\n * Create standard unfilled front matter\n */\nexport function createUnfilledFrontMatter(): FrontMatter {\n  return {\n    status: 'unfilled',\n    generated: new Date().toISOString().split('T')[0]\n  };\n}\n\n/**\n * Get all unfilled files in a directory\n */\nexport async function getUnfilledFiles(contextDir: string): Promise<string[]> {\n  const { glob } = await import('glob');\n  const files = await glob(`${contextDir}/**/*.md`);\n\n  const results = await Promise.all(\n    files.map(async (file) => ({\n      file,\n      unfilled: await needsFill(file)\n    }))\n  );\n\n  return results.filter(r => r.unfilled).map(r => r.file);\n}\n\n/**\n * Count filled vs unfilled files\n */\nexport async function getFilledStats(contextDir: string): Promise<{\n  total: number;\n  filled: number;\n  unfilled: number;\n  files: { path: string; filled: boolean }[];\n}> {\n  const { glob } = await import('glob');\n  const files = await glob(`${contextDir}/**/*.md`);\n\n  const results = await Promise.all(\n    files.map(async (file) => ({\n      path: file,\n      filled: !(await needsFill(file))\n    }))\n  );\n\n  return {\n    total: results.length,\n    filled: results.filter(r => r.filled).length,\n    unfilled: results.filter(r => !r.filled).length,\n    files: results\n  };\n}\n\n/**\n * Parse scaffold frontmatter with support for arrays and nested objects\n */\nexport function parseScaffoldFrontMatter(content: string): {\n  frontMatter: ParsedScaffoldFrontmatter | null;\n  body: string;\n} {\n  const lines = content.split('\\n');\n\n  if (lines[0]?.trim() !== FRONT_MATTER_DELIMITER) {\n    return { frontMatter: null, body: content };\n  }\n\n  const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === FRONT_MATTER_DELIMITER);\n\n  if (endIndex === -1) {\n    return { frontMatter: null, body: content };\n  }\n\n  const frontMatterLines = lines.slice(1, endIndex);\n  const frontMatter: Record<string, unknown> = {};\n\n  let i = 0;\n  while (i < frontMatterLines.length) {\n    const line = frontMatterLines[i];\n\n    // Skip empty lines\n    if (!line.trim()) {\n      i++;\n      continue;\n    }\n\n    // Check for key: value pattern\n    const simpleMatch = line.match(/^(\\w[\\w-]*):\\s*(.*)$/);\n    if (simpleMatch) {\n      const [, key, value] = simpleMatch;\n      const normalizedKey = key.replace(/-/g, '_'); // normalize kebab-case to snake_case\n\n      // Handle inline arrays [a, b, c]\n      if (value.startsWith('[') && value.endsWith(']')) {\n        const arrayContent = value.slice(1, -1);\n        frontMatter[normalizedKey] = arrayContent.split(',').map(s => s.trim().replace(/^[\"']|[\"']$/g, ''));\n      }\n      // Handle boolean\n      else if (value === 'true' || value === 'false') {\n        frontMatter[normalizedKey] = value === 'true';\n      }\n      // Handle quoted string\n      else if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n        frontMatter[normalizedKey] = value.slice(1, -1);\n      }\n      // Handle nested array (value is empty, next lines start with -)\n      else if (!value && i + 1 < frontMatterLines.length && frontMatterLines[i + 1]?.trim().startsWith('-')) {\n        const nestedArray: unknown[] = [];\n        i++;\n\n        while (i < frontMatterLines.length) {\n          const nestedLine = frontMatterLines[i];\n          if (!nestedLine.trim().startsWith('-') && !nestedLine.startsWith('  ')) {\n            break;\n          }\n\n          const itemMatch = nestedLine.match(/^\\s*-\\s*(.*)$/);\n          if (itemMatch) {\n            const itemValue = itemMatch[1].trim();\n\n            // Check if this is a nested object (key: value on same line)\n            const objectMatch = itemValue.match(/^(\\w+):\\s*[\"']?(.*)[\"']?$/);\n            if (objectMatch) {\n              // Start of nested object\n              const nestedObj: Record<string, string> = {};\n              nestedObj[objectMatch[1]] = objectMatch[2].replace(/^[\"']|[\"']$/g, '');\n              i++;\n\n              // Read additional properties of the nested object\n              while (i < frontMatterLines.length) {\n                const propLine = frontMatterLines[i];\n                const propMatch = propLine.match(/^\\s+(\\w+):\\s*[\"']?(.*)[\"']?$/);\n                if (propMatch && !propLine.trim().startsWith('-')) {\n                  nestedObj[propMatch[1]] = propMatch[2].replace(/^[\"']|[\"']$/g, '');\n                  i++;\n                } else {\n                  break;\n                }\n              }\n              nestedArray.push(nestedObj);\n              continue;\n            } else {\n              // Simple array item\n              nestedArray.push(itemValue.replace(/^[\"']|[\"']$/g, ''));\n            }\n          }\n          i++;\n        }\n\n        frontMatter[normalizedKey] = nestedArray;\n        continue;\n      }\n      // Handle simple string\n      else {\n        frontMatter[normalizedKey] = value;\n      }\n    }\n    i++;\n  }\n\n  const body = lines.slice(endIndex + 1).join('\\n').replace(/^\\n+/, '');\n\n  // Validate required scaffold fields\n  if (!frontMatter.scaffoldVersion || frontMatter.scaffoldVersion !== '2.0.0') {\n    return { frontMatter: null, body: content };\n  }\n\n  return {\n    frontMatter: frontMatter as unknown as ParsedScaffoldFrontmatter,\n    body,\n  };\n}\n\n/**\n * Get the document name from frontmatter (works for both v1 and v2)\n */\nexport function getDocumentName(content: string): string | null {\n  // Try v2 first\n  const { frontMatter: scaffoldFm } = parseScaffoldFrontMatter(content);\n  if (scaffoldFm && scaffoldFm.name) {\n    return scaffoldFm.name;\n  }\n\n  // Fall back to v1\n  const { frontMatter } = parseFrontMatter(content);\n  if (frontMatter && frontMatter.name) {\n    return frontMatter.name;\n  }\n\n  return null;\n}\n\n/**\n * Check if content is v2 scaffold format\n */\nexport function isScaffoldContent(content: string): boolean {\n  const { frontMatter } = parseScaffoldFrontMatter(content);\n  return frontMatter !== null;\n}\n"
  },
  {
    "path": "src/utils/gitService.ts",
    "content": "import { execSync } from 'child_process';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\nimport { colors, symbols, typography } from './theme';\n\nexport interface GitChanges {\n  added: string[];\n  modified: string[];\n  deleted: string[];\n  renamed: Array<{ from: string; to: string }>;\n}\n\nexport interface GitState {\n  lastCommit: string;\n}\n\nexport interface CommitResult {\n  hash: string;\n  shortHash: string;\n  filesCommitted: string[];\n}\n\nexport class GitService {\n  private repoPath: string;\n  private stateFile: string;\n\n  constructor(repoPath: string) {\n    this.repoPath = path.resolve(repoPath);\n    this.stateFile = path.join(this.repoPath, 'context-log.json');\n  }\n\n  isGitRepository(): boolean {\n    try {\n      execSync('git rev-parse --git-dir', { \n        cwd: this.repoPath, \n        stdio: 'pipe' \n      });\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  getCurrentCommit(): string {\n    try {\n      return execSync('git rev-parse HEAD', { \n        cwd: this.repoPath, \n        encoding: 'utf8' \n      }).trim();\n    } catch (error) {\n      throw new Error(`Failed to get current commit: ${error}`);\n    }\n  }\n\n  getChangedFiles(since?: string): GitChanges {\n    try {\n      let sinceRef = since || this.getLastProcessedCommit();\n      \n      // If no reference point, check if we have commits\n      if (!sinceRef) {\n        try {\n          // Try to get the previous commit\n          execSync('git rev-parse HEAD~1', { cwd: this.repoPath, stdio: 'pipe' });\n          sinceRef = 'HEAD~1';\n        } catch {\n          // No previous commit, get all tracked files as \"added\"\n          return this.getAllTrackedFilesAsAdded();\n        }\n      }\n\n      // Validate the reference commit exists\n      if (!this.isValidCommit(sinceRef)) {\n        console.warn(`Warning: Reference commit ${sinceRef} no longer exists, falling back to HEAD~1`);\n        try {\n          execSync('git rev-parse HEAD~1', { cwd: this.repoPath, stdio: 'pipe' });\n          sinceRef = 'HEAD~1';\n        } catch {\n          return this.getAllTrackedFilesAsAdded();\n        }\n      }\n      \n      // Get diff with name status\n      const diffOutput = execSync(\n        `git diff --name-status ${sinceRef}..HEAD`,\n        { \n          cwd: this.repoPath, \n          encoding: 'utf8' \n        }\n      ).trim();\n\n      const changes: GitChanges = {\n        added: [],\n        modified: [],\n        deleted: [],\n        renamed: []\n      };\n\n      if (!diffOutput) {\n        return changes;\n      }\n\n      const lines = diffOutput.split('\\n');\n      for (const line of lines) {\n        const parts = line.split('\\t');\n        const status = parts[0];\n        const filePath = parts[1];\n\n        switch (status.charAt(0)) {\n          case 'A':\n            changes.added.push(filePath);\n            break;\n          case 'M':\n            changes.modified.push(filePath);\n            break;\n          case 'D':\n            changes.deleted.push(filePath);\n            break;\n          case 'R':\n            const fromFile = parts[1];\n            const toFile = parts[2];\n            changes.renamed.push({ from: fromFile, to: toFile });\n            break;\n        }\n      }\n\n      return changes;\n    } catch (error) {\n      throw new Error(`Failed to get changed files: ${error}`);\n    }\n  }\n\n  getStagedFiles(): string[] {\n    try {\n      const output = execSync('git diff --cached --name-only', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      return output ? output.split('\\n') : [];\n    } catch (error) {\n      throw new Error(`Failed to get staged files: ${error}`);\n    }\n  }\n\n  getStagedChanges(): GitChanges {\n    try {\n      const output = execSync('git diff --cached --name-status', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      const changes: GitChanges = {\n        added: [],\n        modified: [],\n        deleted: [],\n        renamed: []\n      };\n\n      if (!output) {\n        return changes;\n      }\n\n      const lines = output.split('\\n');\n      for (const line of lines) {\n        const parts = line.split('\\t');\n        const status = parts[0];\n        const filePath = parts[1];\n\n        switch (status.charAt(0)) {\n          case 'A':\n            changes.added.push(filePath);\n            break;\n          case 'M':\n            changes.modified.push(filePath);\n            break;\n          case 'D':\n            changes.deleted.push(filePath);\n            break;\n          case 'R':\n            const fromFile = parts[1];\n            const toFile = parts[2];\n            changes.renamed.push({ from: fromFile, to: toFile });\n            break;\n        }\n      }\n\n      return changes;\n    } catch (error) {\n      throw new Error(`Failed to get staged changes: ${error}`);\n    }\n  }\n\n  getUncommittedChanges(): GitChanges {\n    try {\n      // Get both staged and unstaged changes\n      const diffOutput = execSync(\n        'git diff --name-status HEAD',\n        { \n          cwd: this.repoPath, \n          encoding: 'utf8' \n        }\n      ).trim();\n\n      const changes: GitChanges = {\n        added: [],\n        modified: [],\n        deleted: [],\n        renamed: []\n      };\n\n      if (!diffOutput) {\n        return changes;\n      }\n\n      const lines = diffOutput.split('\\n');\n      for (const line of lines) {\n        const parts = line.split('\\t');\n        const status = parts[0];\n        const filePath = parts[1];\n\n        switch (status.charAt(0)) {\n          case 'A':\n            changes.added.push(filePath);\n            break;\n          case 'M':\n            changes.modified.push(filePath);\n            break;\n          case 'D':\n            changes.deleted.push(filePath);\n            break;\n          case 'R':\n            const fromFile = parts[1];\n            const toFile = parts[2];\n            changes.renamed.push({ from: fromFile, to: toFile });\n            break;\n        }\n      }\n\n      return changes;\n    } catch (error) {\n      throw new Error(`Failed to get uncommitted changes: ${error}`);\n    }\n  }\n\n  getBranchName(): string {\n    try {\n      return execSync('git branch --show-current', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n    } catch (error) {\n      return 'unknown';\n    }\n  }\n\n  getLastProcessedCommit(): string | null {\n    try {\n      if (!fs.existsSync(this.stateFile)) {\n        return null;\n      }\n      const state: GitState = fs.readJsonSync(this.stateFile);\n      return state.lastCommit || null;\n    } catch {\n      return null;\n    }\n  }\n\n  saveState(commit: string): void {\n    try {\n      // Validate that the commit exists\n      if (!this.isValidCommit(commit)) {\n        throw new Error(`Invalid commit hash: ${commit}`);\n      }\n      \n      const state: GitState = {\n        lastCommit: commit\n      };\n      fs.writeJsonSync(this.stateFile, state, { spaces: 2 });\n    } catch (error) {\n      console.warn(`Warning: Could not save state: ${error}`);\n    }\n  }\n\n  getState(): GitState | null {\n    try {\n      if (!fs.existsSync(this.stateFile)) {\n        return null;\n      }\n      return fs.readJsonSync(this.stateFile);\n    } catch {\n      return null;\n    }\n  }\n\n  clearState(): void {\n    try {\n      if (fs.existsSync(this.stateFile)) {\n        fs.unlinkSync(this.stateFile);\n      }\n    } catch (error) {\n      console.warn(`Warning: Could not clear state: ${error}`);\n    }\n  }\n\n  filterRelevantFiles(files: string[], fileMapper: any): string[] {\n    return files.filter(file => {\n      const fullPath = path.join(this.repoPath, file);\n      return fs.existsSync(fullPath) && fileMapper.isTextFile(fullPath);\n    });\n  }\n\n  isFileTracked(filePath: string): boolean {\n    try {\n      execSync(`git ls-files --error-unmatch \"${filePath}\"`, {\n        cwd: this.repoPath,\n        stdio: 'pipe'\n      });\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  getTrackedFiles(): string[] {\n    try {\n      const output = execSync('git ls-files', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      return output ? output.split('\\n') : [];\n    } catch (error) {\n      throw new Error(`Failed to get tracked files: ${error}`);\n    }\n  }\n\n  filterTrackedFiles(files: string[]): string[] {\n    const trackedFiles = new Set(this.getTrackedFiles());\n    return files.filter(file => trackedFiles.has(file));\n  }\n\n  getCommitMessage(commit: string): string {\n    try {\n      return execSync(`git log --format=%B -n 1 ${commit}`, {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n    } catch {\n      return 'Unknown commit';\n    }\n  }\n\n  getFileHistory(filePath: string, limit: number = 10): Array<{ commit: string; date: string; message: string }> {\n    try {\n      const output = execSync(\n        `git log --format=\"%H|%ci|%s\" -n ${limit} -- \"${filePath}\"`,\n        {\n          cwd: this.repoPath,\n          encoding: 'utf8'\n        }\n      ).trim();\n\n      if (!output) return [];\n\n      return output.split('\\n').map(line => {\n        const [commit, date, message] = line.split('|');\n        return { commit, date, message };\n      });\n    } catch {\n      return [];\n    }\n  }\n\n  private getAllTrackedFilesAsAdded(): GitChanges {\n    try {\n      const output = execSync('git ls-files', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      const changes: GitChanges = {\n        added: output ? output.split('\\n') : [],\n        modified: [],\n        deleted: [],\n        renamed: []\n      };\n\n      return changes;\n    } catch (error) {\n      throw new Error(`Failed to get tracked files: ${error}`);\n    }\n  }\n\n  isValidCommit(commit: string): boolean {\n    try {\n      execSync(`git rev-parse --verify ${commit}`, {\n        cwd: this.repoPath,\n        stdio: 'pipe'\n      });\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  /**\n   * Get list of files with unstaged changes (modified but not staged)\n   */\n  getUnstagedFiles(): string[] {\n    try {\n      const output = execSync('git diff --name-only', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      return output ? output.split('\\n') : [];\n    } catch (error) {\n      throw new Error(`Failed to get unstaged files: ${error}`);\n    }\n  }\n\n  /**\n   * Stage files matching the given patterns\n   * @param patterns Array of file paths or glob patterns\n   * @returns Array of files that were staged\n   */\n  stageFiles(patterns: string[]): string[] {\n    if (patterns.length === 0) {\n      return [];\n    }\n\n    try {\n      // Stage files matching the patterns\n      for (const pattern of patterns) {\n        try {\n          execSync(`git add \"${pattern}\"`, {\n            cwd: this.repoPath,\n            stdio: 'pipe'\n          });\n        } catch {\n          // Pattern might not match any files, continue\n        }\n      }\n\n      // Return what was actually staged\n      return this.getStagedFiles();\n    } catch (error) {\n      throw new Error(`Failed to stage files: ${error}`);\n    }\n  }\n\n  /**\n   * Create a git commit with the staged changes\n   * @param message Commit message\n   * @param coAuthor Optional co-author for the commit (agent name)\n   * @returns Commit result with hash and files committed\n   */\n  commit(message: string, coAuthor?: string): CommitResult {\n    const stagedFiles = this.getStagedFiles();\n\n    if (stagedFiles.length === 0) {\n      throw new Error('Nothing to commit: no staged files');\n    }\n\n    // Build commit message with optional co-author\n    let fullMessage = message;\n    if (coAuthor) {\n      fullMessage += `\\n\\nCo-Authored-By: ${coAuthor} <noreply@anthropic.com>`;\n    }\n\n    try {\n      // Create commit using heredoc-style to handle multiline messages\n      execSync(`git commit -m \"${fullMessage.replace(/\"/g, '\\\\\"')}\"`, {\n        cwd: this.repoPath,\n        stdio: 'pipe'\n      });\n\n      // Get the commit info\n      const commitInfo = this.getCommitInfo('HEAD');\n      if (!commitInfo) {\n        throw new Error('Failed to get commit info after commit');\n      }\n\n      return {\n        hash: commitInfo.hash,\n        shortHash: commitInfo.shortHash,\n        filesCommitted: stagedFiles\n      };\n    } catch (error) {\n      throw new Error(`Failed to create commit: ${error}`);\n    }\n  }\n\n  /**\n   * Check if there are any uncommitted changes (staged or unstaged)\n   */\n  hasUncommittedChanges(): boolean {\n    try {\n      const output = execSync('git status --porcelain', {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      return output.length > 0;\n    } catch {\n      return false;\n    }\n  }\n\n  getCommitsBetween(fromCommit: string, toCommit: string = 'HEAD'): string[] {\n    try {\n      const output = execSync(`git rev-list --reverse ${fromCommit}..${toCommit}`, {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      return output ? output.split('\\n') : [];\n    } catch (error) {\n      throw new Error(`Failed to get commits between ${fromCommit} and ${toCommit}: ${error}`);\n    }\n  }\n\n  getCommitInfo(commit: string): { hash: string; shortHash: string; message: string; date: string } | null {\n    try {\n      const output = execSync(`git log --format=\"%H|%h|%s|%ci\" -n 1 ${commit}`, {\n        cwd: this.repoPath,\n        encoding: 'utf8'\n      }).trim();\n\n      const [hash, shortHash, message, date] = output.split('|');\n      return { hash, shortHash, message, date };\n    } catch {\n      return null;\n    }\n  }\n\n  displayCommitTrackingInfo(verbose: boolean = false): void {\n    const lastProcessed = this.getLastProcessedCommit();\n    const current = this.getCurrentCommit();\n\n    if (verbose) {\n      console.log('');\n      console.log(typography.header('Commit Tracking'));\n      console.log(typography.separator());\n\n      if (lastProcessed) {\n        const lastInfo = this.getCommitInfo(lastProcessed);\n        if (lastInfo) {\n          console.log(typography.labeledValue('Last documented', `${lastInfo.shortHash} - ${lastInfo.message}`));\n          console.log(`                  ${colors.secondaryDim(lastInfo.date)}`);\n        } else {\n          console.log(typography.labeledValue('Last documented', `${lastProcessed.substring(0, 8)} (commit no longer exists)`));\n        }\n      } else {\n        console.log(typography.labeledValue('Last documented', 'None (first run)'));\n      }\n\n      const currentInfo = this.getCommitInfo(current);\n      if (currentInfo) {\n        console.log(typography.labeledValue('Current commit', `${currentInfo.shortHash} - ${currentInfo.message}`));\n        console.log(`                  ${colors.secondaryDim(currentInfo.date)}`);\n      }\n\n      if (lastProcessed && lastProcessed !== current) {\n        const commitsBetween = this.getCommitsBetween(lastProcessed, current);\n        if (commitsBetween.length > 0) {\n          console.log(typography.labeledValue('Commits to process', commitsBetween.length.toString()));\n        }\n      }\n\n      console.log(typography.separator());\n    }\n  }\n\n  hasContextBeenInitialized(outputDir: string): boolean {\n    // Check if state file exists\n    const hasStateFile = fs.existsSync(this.stateFile);\n    \n    // Check if context folder exists and has content\n    const contextDir = path.join(outputDir);\n    const docsDir = path.join(contextDir, 'docs');\n    const hasContextFolder = fs.existsSync(contextDir) && fs.existsSync(docsDir);\n    \n    // Check if docs folder has any content\n    let hasDocumentation = false;\n    if (hasContextFolder) {\n      try {\n        const files = fs.readdirSync(docsDir);\n        hasDocumentation = files.length > 0;\n      } catch {\n        hasDocumentation = false;\n      }\n    }\n    \n    return hasStateFile || (hasContextFolder && hasDocumentation);\n  }\n}"
  },
  {
    "path": "src/utils/gitignoreManager.test.ts",
    "content": "import * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs-extra';\n\nimport { ensureGitignorePatterns, GitIgnoreManager } from './gitignoreManager';\n\ndescribe('GitIgnoreManager', () => {\n    let tempDir: string;\n\n    beforeEach(async () => {\n        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitignore-test-'));\n    });\n\n    afterEach(async () => {\n        await fs.remove(tempDir);\n    });\n\n    describe('default patterns', () => {\n        it('should ignore node_modules by default', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('node_modules')).toBe(true);\n            expect(manager.shouldIgnore('node_modules/package/index.js')).toBe(true);\n        });\n\n        it('should ignore .git by default', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('.git')).toBe(true);\n            expect(manager.shouldIgnore('.git/objects/abc')).toBe(true);\n        });\n\n        it('should ignore dist and build by default', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('dist')).toBe(true);\n            expect(manager.shouldIgnore('build')).toBe(true);\n        });\n\n        it('should not ignore src files by default', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('src/index.ts')).toBe(false);\n            expect(manager.shouldIgnore('README.md')).toBe(false);\n        });\n\n        it('should allow disabling defaults', async () => {\n            const manager = new GitIgnoreManager({ useDefaults: false });\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('node_modules')).toBe(false);\n        });\n    });\n\n    describe('loading .gitignore', () => {\n        it('should load patterns from .gitignore file', async () => {\n            await fs.writeFile(path.join(tempDir, '.gitignore'), 'coverage/\\n*.swp\\n');\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('coverage/lcov.info')).toBe(true);\n            expect(manager.shouldIgnore('file.swp')).toBe(true);\n            expect(manager.shouldIgnore('src/app.ts')).toBe(false);\n        });\n\n        it('should handle .gitignore with comments and empty lines', async () => {\n            await fs.writeFile(\n                path.join(tempDir, '.gitignore'),\n                '# This is a comment\\n\\n.cache/\\n\\n# Another comment\\n*.bak\\n'\n            );\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('.cache/data')).toBe(true);\n            expect(manager.shouldIgnore('file.bak')).toBe(true);\n        });\n\n        it('should respect negation patterns', async () => {\n            await fs.writeFile(\n                path.join(tempDir, '.gitignore'),\n                '*.log\\n!important.log\\n'\n            );\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('debug.log')).toBe(true);\n            expect(manager.shouldIgnore('important.log')).toBe(false);\n        });\n\n        it('should handle missing .gitignore gracefully', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            // Should still work with defaults\n            expect(manager.shouldIgnore('node_modules')).toBe(true);\n            expect(manager.shouldIgnore('src/index.ts')).toBe(false);\n        });\n\n        it('should handle .gitignore with Windows-style line endings', async () => {\n            await fs.writeFile(path.join(tempDir, '.gitignore'), '.next/\\r\\ncoverage/\\r\\n');\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('.next/static/bundle.js')).toBe(true);\n            expect(manager.shouldIgnore('coverage/lcov.info')).toBe(true);\n        });\n\n        it('should handle directory patterns', async () => {\n            await fs.writeFile(\n                path.join(tempDir, '.gitignore'),\n                '__pycache__/\\n.venv/\\n.idea/\\n'\n            );\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('__pycache__/module.pyc')).toBe(true);\n            expect(manager.shouldIgnore('.venv/lib/python3.10/site.py')).toBe(true);\n            expect(manager.shouldIgnore('.idea/workspace.xml')).toBe(true);\n        });\n    });\n\n    describe('extra patterns', () => {\n        it('should apply extra patterns from options', async () => {\n            const manager = new GitIgnoreManager({ extraPatterns: ['*.custom'] });\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('file.custom')).toBe(true);\n            expect(manager.shouldIgnore('file.ts')).toBe(false);\n        });\n    });\n\n    describe('caching', () => {\n        it('should return consistent results for repeated lookups', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            const result1 = manager.shouldIgnore('node_modules/express/index.js');\n            const result2 = manager.shouldIgnore('node_modules/express/index.js');\n\n            expect(result1).toBe(true);\n            expect(result2).toBe(true);\n        });\n\n        it('should handle cache clearing', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            manager.shouldIgnore('test.ts');\n            manager.clearCache();\n\n            // Should still work after cache clear\n            expect(manager.shouldIgnore('test.ts')).toBe(false);\n        });\n    });\n\n    describe('filterPaths', () => {\n        it('should filter out ignored paths', async () => {\n            await fs.writeFile(path.join(tempDir, '.gitignore'), 'coverage/\\n');\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            const input = [\n                'src/index.ts',\n                'coverage/lcov.info',\n                'node_modules/express/index.js',\n                'README.md'\n            ];\n\n            const filtered = manager.filterPaths(input);\n\n            expect(filtered).toEqual(['src/index.ts', 'README.md']);\n        });\n    });\n\n    describe('cross-platform path handling', () => {\n        it('should normalize backslashes to forward slashes', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('node_modules\\\\express\\\\index.js')).toBe(true);\n        });\n\n        it('should not ignore empty or root paths', async () => {\n            const manager = new GitIgnoreManager();\n            await manager.loadFromRepo(tempDir);\n\n            expect(manager.shouldIgnore('')).toBe(false);\n            expect(manager.shouldIgnore('.')).toBe(false);\n        });\n    });\n\n    describe('reload', () => {\n        it('should correctly reload when called multiple times', async () => {\n            const manager = new GitIgnoreManager();\n\n            // First load without .gitignore\n            await manager.loadFromRepo(tempDir);\n            expect(manager.shouldIgnore('coverage/lcov.info')).toBe(false);\n\n            // Create .gitignore and reload\n            await fs.writeFile(path.join(tempDir, '.gitignore'), 'coverage/\\n');\n            await manager.loadFromRepo(tempDir);\n            expect(manager.shouldIgnore('coverage/lcov.info')).toBe(true);\n        });\n    });\n\n    describe('ensureGitignorePatterns', () => {\n        it('should create .gitignore and append missing patterns once', async () => {\n            const result = await ensureGitignorePatterns(tempDir, [\n                '.context/plans/**',\n                '.context/cache/semantic/**',\n                '.context/workflow/**',\n                '.context/harness/sessions/**',\n            ]);\n\n            const content = await fs.readFile(path.join(tempDir, '.gitignore'), 'utf-8');\n\n            expect(result.updated).toBe(true);\n            expect(result.addedPatterns).toEqual([\n                '.context/plans/',\n                '.context/cache/semantic/',\n                '.context/workflow/',\n                '.context/harness/sessions/',\n            ]);\n            expect(content).toContain('# dotcontext runtime state');\n            expect(content).toContain('.context/plans/');\n            expect(content).toContain('.context/cache/semantic/');\n            expect(content).toContain('.context/workflow/');\n            expect(content).toContain('.context/harness/sessions/');\n        });\n\n        it('should preserve existing content and avoid duplicate entries', async () => {\n            await fs.writeFile(path.join(tempDir, '.gitignore'), 'node_modules/\\n.context/workflow/\\n');\n\n            const result = await ensureGitignorePatterns(tempDir, [\n                '.context/plans/**',\n                '.context/cache/semantic/**',\n                '.context/workflow/**',\n                '.context/harness/sessions/**',\n            ]);\n            const content = await fs.readFile(path.join(tempDir, '.gitignore'), 'utf-8');\n\n            expect(result.updated).toBe(true);\n            expect(result.addedPatterns).toEqual([\n                '.context/plans/',\n                '.context/cache/semantic/',\n                '.context/harness/sessions/',\n            ]);\n            expect((content.match(/\\.context\\/workflow\\//g) || []).length).toBe(1);\n        });\n    });\n});\n"
  },
  {
    "path": "src/utils/gitignoreManager.ts",
    "content": "/**\n * GitIgnoreManager - Loads and applies .gitignore rules for file filtering.\n *\n * Uses the `ignore` library for spec-compliant .gitignore pattern matching.\n * Caches lookup results via Map for O(1) repeat lookups.\n * Gracefully falls back to empty rules if no .gitignore exists.\n *\n * @see https://github.com/kaelzhang/node-ignore\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport ignore, { type Ignore } from 'ignore';\n\n/**\n * Default patterns that should always be excluded, regardless of .gitignore content.\n * These match the original hardcoded excludes from FileMapper.\n */\nconst DEFAULT_EXCLUDE_PATTERNS: readonly string[] = [\n  'node_modules',\n  '.git',\n  'dist',\n  'build',\n  '*.log',\n  '.env*',\n  '*.tmp',\n  '.DS_Store',\n] as const;\n\nexport interface GitIgnoreManagerOptions {\n  /** Extra patterns to add on top of .gitignore + defaults */\n  extraPatterns?: string[];\n  /** Whether to include default exclude patterns (default: true) */\n  useDefaults?: boolean;\n}\n\nexport class GitIgnoreManager {\n  private ignoreInstance: Ignore;\n  private readonly resultCache = new Map<string, boolean>();\n  private loaded = false;\n\n  constructor(private readonly options: GitIgnoreManagerOptions = {}) {\n    this.ignoreInstance = ignore();\n    this.applyDefaults();\n  }\n\n  /**\n   * Load .gitignore rules from the given repository root.\n   * Safe to call multiple times — resets and reloads each time.\n   *\n   * @param repoPath - Absolute path to the repository root\n   */\n  async loadFromRepo(repoPath: string): Promise<void> {\n    // Reset for fresh load\n    this.ignoreInstance = ignore();\n    this.resultCache.clear();\n    this.loaded = false;\n\n    // 1. Apply default patterns\n    this.applyDefaults();\n\n    // 2. Load .gitignore from repo root\n    const gitignorePath = path.join(repoPath, '.gitignore');\n    try {\n      const exists = await fs.pathExists(gitignorePath);\n      if (exists) {\n        const content = await fs.readFile(gitignorePath, 'utf-8');\n        const patterns = this.parseGitignore(content);\n        if (patterns.length > 0) {\n          this.ignoreInstance.add(patterns);\n        }\n      }\n    } catch {\n      // .gitignore not readable — continue with defaults only\n    }\n\n    // 3. Apply extra patterns from options\n    if (this.options.extraPatterns && this.options.extraPatterns.length > 0) {\n      this.ignoreInstance.add(this.options.extraPatterns);\n    }\n\n    this.loaded = true;\n  }\n\n  /**\n   * Check if a path should be ignored.\n   *\n   * @param relativePath - Path relative to the repository root (forward slashes)\n   * @returns true if the path should be ignored\n   */\n  shouldIgnore(relativePath: string): boolean {\n    // Normalize to forward slashes for cross-platform consistency\n    const normalized = relativePath.replace(/\\\\/g, '/');\n\n    // Empty or root path should not be ignored\n    if (!normalized || normalized === '.' || normalized === '/') {\n      return false;\n    }\n\n    // Check cache first\n    const cached = this.resultCache.get(normalized);\n    if (cached !== undefined) {\n      return cached;\n    }\n\n    const result = this.ignoreInstance.ignores(normalized);\n    this.resultCache.set(normalized, result);\n    return result;\n  }\n\n  /**\n   * Filter an array of relative paths, returning only non-ignored ones.\n   *\n   * @param relativePaths - Array of paths relative to repo root\n   * @returns Filtered array of non-ignored paths\n   */\n  filterPaths(relativePaths: string[]): string[] {\n    return relativePaths.filter(p => !this.shouldIgnore(p));\n  }\n\n  /**\n   * Get all patterns currently loaded (for debugging/logging).\n   */\n  get isLoaded(): boolean {\n    return this.loaded;\n  }\n\n  /**\n   * Clear the result cache. Useful after adding new patterns.\n   */\n  clearCache(): void {\n    this.resultCache.clear();\n  }\n\n  /**\n   * Parse .gitignore content into an array of patterns.\n   * Strips comments and empty lines.\n   */\n  private parseGitignore(content: string): string[] {\n    return content\n      .split(/\\r?\\n/)\n      .map(line => line.trim())\n      .filter(line => line.length > 0 && !line.startsWith('#'));\n  }\n\n  /**\n   * Apply default exclusion patterns.\n   */\n  private applyDefaults(): void {\n    if (this.options.useDefaults !== false) {\n      this.ignoreInstance.add([...DEFAULT_EXCLUDE_PATTERNS]);\n    }\n  }\n}\n\nexport interface EnsureGitignorePatternsOptions {\n  header?: string;\n}\n\nfunction normalizeGitignorePattern(pattern: string): string {\n  let normalized = pattern.replace(/\\\\/g, '/').trim();\n  if (!normalized) {\n    return normalized;\n  }\n\n  if (normalized.endsWith('/**')) {\n    normalized = `${normalized.slice(0, -3)}/`;\n  }\n\n  return normalized;\n}\n\nexport async function ensureGitignorePatterns(\n  repoPath: string,\n  patterns: string[],\n  options: EnsureGitignorePatternsOptions = {}\n): Promise<{ updated: boolean; addedPatterns: string[]; gitignorePath: string }> {\n  const gitignorePath = path.join(path.resolve(repoPath), '.gitignore');\n  const header = options.header ?? '# dotcontext runtime state';\n  const normalizedPatterns = patterns\n    .map(normalizeGitignorePattern)\n    .filter((pattern, index, list) => Boolean(pattern) && list.indexOf(pattern) === index);\n\n  const exists = await fs.pathExists(gitignorePath);\n  const original = exists ? await fs.readFile(gitignorePath, 'utf-8') : '';\n  const lines = original.split(/\\r?\\n/);\n  const existingPatterns = new Set(\n    lines\n      .map((line) => line.trim())\n      .filter((line) => line.length > 0 && !line.startsWith('#'))\n  );\n\n  const missingPatterns = normalizedPatterns.filter((pattern) => !existingPatterns.has(pattern));\n  if (missingPatterns.length === 0) {\n    return { updated: false, addedPatterns: [], gitignorePath };\n  }\n\n  const outputLines = exists && original.length > 0 ? [...lines] : [];\n  while (outputLines.length > 0 && outputLines[outputLines.length - 1].trim() === '') {\n    outputLines.pop();\n  }\n\n  if (outputLines.length > 0) {\n    outputLines.push('');\n  }\n  outputLines.push(header);\n  outputLines.push(...missingPatterns);\n  outputLines.push('');\n\n  await fs.writeFile(gitignorePath, `${outputLines.join('\\n')}\\n`, 'utf-8');\n  return { updated: true, addedPatterns: missingPatterns, gitignorePath };\n}\n"
  },
  {
    "path": "src/utils/i18n.test.ts",
    "content": "import { detectLocale, normalizeLocale } from './i18n';\n\ndescribe('i18n locale detection', () => {\n  it('normalizes system locale variants to supported locales', () => {\n    expect(normalizeLocale('pt_BR.UTF-8')).toBe('pt-BR');\n    expect(normalizeLocale('en_US.UTF-8')).toBe('en');\n  });\n\n  it('prefers explicit CLI flags over environment detection', () => {\n    expect(detectLocale(['--lang', 'en'], 'pt-BR', ['pt_BR.UTF-8'])).toBe('en');\n  });\n\n  it('falls back to DOTCONTEXT_LANG before OS locale', () => {\n    expect(detectLocale([], 'pt-BR', ['en_US.UTF-8'])).toBe('pt-BR');\n  });\n\n  it('detects supported locales from OS locale variables', () => {\n    expect(detectLocale([], null, ['pt_BR.UTF-8'])).toBe('pt-BR');\n    expect(detectLocale([], null, ['C', 'en_US.UTF-8'])).toBe('en');\n  });\n});\n"
  },
  {
    "path": "src/utils/i18n.ts",
    "content": "export type Locale = 'en' | 'pt-BR';\n\nexport const SUPPORTED_LOCALES: Locale[] = ['en', 'pt-BR'];\nexport const DEFAULT_LOCALE: Locale = 'en';\n\nconst englishMessages = {\n  'cli.description': 'Sync .context assets, reverse-sync tool state, and install MCP integrations for your repository',\n  'global.options.lang': 'Language for CLI output (en or pt-BR)',\n  'ui.version': 'Version {version}',\n  'ui.projectConfiguration.title': 'Project Configuration',\n  'ui.projectConfiguration.repository': 'Repository',\n  'ui.projectConfiguration.output': 'Output',\n  'ui.projectConfiguration.mode': 'Mode',\n  'ui.progress.starting': 'Starting...',\n  'ui.analysis.complete.title': 'Analysis Complete',\n  'ui.analysis.files': 'Files',\n  'ui.analysis.directories': 'Directories',\n  'ui.analysis.totalSize': 'Total Size',\n  'ui.fileTypeDistribution.title': 'File Type Distribution',\n  'ui.generationSummary.title': 'Scaffold Complete',\n  'ui.generationSummary.documentation': 'Documentation',\n  'ui.generationSummary.agents': 'Agents',\n  'ui.generationSummary.skills': 'Skills',\n  'ui.generationSummary.timeElapsed': 'Time',\n  'ui.generationSummary.nextStep': 'Next step: customize the generated templates to match your project.',\n  'ui.error.title': 'Error',\n  'ui.splash.directoryLabel': 'directory',\n  'info.prompt.title': 'System Prompt',\n  'info.prompt.usingCustom': 'Using prompt from {path}.',\n  'info.prompt.usingPackage': 'Using packaged prompt at {path}.',\n  'info.prompt.usingBundled': 'Using built-in prompt bundled with dotcontext.',\n  'info.update.available.title': 'Update available',\n  'info.update.available.detail': 'A newer version {latest} is available (current {current}). Update with {command}.',\n  'commands.init.description': 'Generate docs and agent scaffolding for a repository',\n  'commands.init.arguments.repoPath': 'Path to the repository to analyze',\n  'commands.init.arguments.type': 'Scaffold type: \"docs\", \"agents\", or \"both\" (default)',\n  'commands.init.options.output': 'Output directory for generated assets',\n  'commands.init.options.exclude': 'Glob patterns to exclude from analysis',\n  'commands.init.options.include': 'Glob patterns to include during analysis',\n  'commands.init.options.verbose': 'Enable verbose logging',\n  'commands.init.options.noSemantic': 'Disable semantic code analysis (enabled by default)',\n  'commands.init.options.noContentStubs': 'Disable content stubs (section headings and guidance) in scaffolds',\n  'commands.fill.description': 'Use an LLM to fill generated docs and agent playbooks with the latest repo context',\n  'commands.fill.arguments.repoPath': 'Path to the repository root used to build context',\n  'commands.fill.options.output': 'Scaffold directory containing docs/ and agents/',\n  'commands.fill.options.apiKey': 'API key for the LLM provider',\n  'commands.fill.options.model': 'LLM model to use',\n  'commands.fill.options.provider': 'LLM provider (openrouter, openai, anthropic, google)',\n  'commands.fill.options.baseUrl': 'Custom base URL for provider APIs',\n  'commands.fill.options.prompt': 'Path to an instruction prompt',\n  'commands.fill.options.limit': 'Maximum number of files to process',\n  'commands.fill.options.exclude': 'Glob patterns to exclude from repository analysis',\n  'commands.fill.options.include': 'Glob patterns to include during analysis',\n  'commands.fill.options.verbose': 'Enable verbose logging',\n  'commands.fill.options.noSemantic': 'Disable semantic context mode and use tool-based exploration instead',\n  'commands.fill.options.languages': 'Programming languages to analyze (e.g., typescript,python)',\n  'commands.fill.options.useLsp': 'Enable LSP for deeper semantic analysis (type info, references)',\n  'prompts.fill.semantic': 'Use semantic context mode? (faster, fewer tokens - recommended)',\n  'prompts.fill.languages': 'Select programming languages for analysis',\n  'prompts.fill.useLsp': 'Use LSP for deeper type analysis? (slower but more accurate)',\n  'commands.plan.description': 'Create a development plan that links documentation and agent playbooks',\n  'commands.plan.arguments.planName': 'Name of the plan (used to create the file slug)',\n  'commands.plan.options.output': 'Output directory for generated plan files',\n  'commands.plan.options.title': 'Custom title for the plan document',\n  'commands.plan.options.summary': 'Seed summary for the plan header',\n  'commands.plan.options.force': 'Overwrite the plan if it already exists (scaffold mode)',\n  'commands.plan.options.fill': 'Use an LLM to fill or update the plan instead of scaffolding',\n  'commands.plan.options.repo': 'Repository root for semantic context',\n  'commands.plan.options.apiKey': 'API key for the LLM provider',\n  'commands.plan.options.model': 'LLM model to use',\n  'commands.plan.options.provider': 'LLM provider (openrouter, openai, anthropic, google)',\n  'commands.plan.options.baseUrl': 'Custom base URL for provider APIs',\n  'commands.plan.options.prompt': 'Path to a plan update instruction prompt',\n  'commands.plan.options.dryRun': 'Preview updates without writing files',\n  'commands.plan.options.include': 'Glob patterns to include during repository analysis',\n  'commands.plan.options.exclude': 'Glob patterns to exclude from repository analysis',\n  'commands.plan.options.verbose': 'Enable verbose logging',\n  'commands.plan.options.noSemantic': 'Disable semantic code analysis (enabled by default)',\n  'commands.plan.options.noLsp': 'Disable LSP semantic analysis (enabled by default for plans)',\n  'errors.init.scaffoldFailed': 'Failed to scaffold repository assets',\n  'errors.fill.failed': 'Failed to fill documentation with LLM assistance',\n  'errors.plan.creationFailed': 'Failed to create plan template',\n  'errors.init.invalidType': 'Invalid scaffold type \"{value}\". Expected one of: {allowed}',\n  'errors.common.repoMissing': 'Repository path does not exist: {path}',\n  'warnings.scaffold.noneSelected': 'No documentation or agent playbooks selected. Nothing to scaffold.',\n  'warnings.agents.missingDirectory': 'No agents directory found. Initialize or import context through MCP first.',\n  'steps.init.analyze': 'Analyzing repository structure',\n  'steps.init.docs': 'Scaffolding documentation',\n  'steps.init.agents': 'Scaffolding agent playbooks',\n  'spinner.repo.scanning': 'Scanning repository...',\n  'spinner.repo.scanComplete': 'Found {fileCount} files across {directoryCount} directories',\n  'spinner.docs.creating': 'Creating docs directory and templates...',\n  'spinner.docs.creatingWithSemantic': 'Analyzing codebase and creating docs templates...',\n  'spinner.docs.created': 'Documentation scaffold created ({count} files)',\n  'spinner.agents.creating': 'Creating agent directory and templates...',\n  'spinner.agents.creatingWithSemantic': 'Analyzing codebase and creating agent playbooks...',\n  'spinner.agents.created': 'Agent scaffold created ({count} files)',\n  'spinner.plan.creating': 'Creating plan template...',\n  'spinner.plan.created': 'Plan template created',\n  'spinner.plan.creationFailed': 'Failed to create plan template',\n  'success.scaffold.ready': 'Scaffold ready in {path}',\n  'success.plan.createdAt': 'Plan created at {path}',\n  'errors.fill.missingDocsScaffold': 'Documentation scaffold not found. Run `dotcontext init` first.',\n  'errors.fill.missingAgentsScaffold': 'Agent scaffold not found. Run `dotcontext init` first.',\n  'errors.fill.missingScaffold': 'No scaffold found. Run `dotcontext init` first to create docs and/or agents.',\n  'steps.fill.analyze': 'Analyzing repository structure',\n  'warnings.fill.noTargets': 'No Markdown files found in .context/docs or .context/agents. Run \"dotcontext init\" before filling.',\n  'steps.fill.processFiles': 'Updating {count} files with {model}',\n  'spinner.fill.processing': 'Processing {path}...',\n  'spinner.fill.noContent': 'No content received for {path}',\n  'messages.fill.emptyResponse': 'Empty response from LLM',\n  'messages.fill.previewStart': '--- Preview Start ---',\n  'messages.fill.previewEnd': '--- Preview End ---',\n  'spinner.fill.updated': 'Updated {path}',\n  'spinner.fill.failed': 'Failed {path}',\n  'steps.fill.summary': 'Summarizing LLM usage',\n  'success.fill.completed': 'LLM-assisted update complete. Review the changes and commit when ready.',\n  'errors.plan.invalidName': 'Plan name must contain at least one alphanumeric character.',\n  'messages.plan.regenerated': 'Regenerated {path} before LLM fill.',\n  'messages.plan.created': 'Created {path} before LLM fill.',\n  'info.plan.scaffolded.title': 'Plan scaffolded',\n  'errors.plan.missingPlansDir': 'Plans directory not found. Run `dotcontext plan <name>` to create one.',\n  'errors.plan.notFound': 'Plan not found. Expected {expected}.',\n  'steps.plan.summary': 'Summarizing repository state',\n  'spinner.planFill.analyzingRepo': 'Analyzing repository...',\n  'spinner.planFill.summaryReady': 'Repository summary ready',\n  'steps.plan.update': 'Updating {path} with {model}',\n  'spinner.planFill.updating': 'Filling {path}...',\n  'spinner.planFill.noContent': 'No content received from LLM',\n  'spinner.planFill.dryRun': 'Dry run - preview follows',\n  'spinner.planFill.updated': 'Updated {path}',\n  'spinner.planFill.failed': 'Failed to fill plan',\n  'steps.plan.summaryResults': 'Summarizing LLM usage',\n  'success.plan.filled': 'Plan fill complete. Review the updates and commit when ready.',\n  'errors.commands.analyzeRemoved': 'The analyze command has been removed in the scaffolding-only version of dotcontext.',\n  'errors.commands.fillRemoved': 'The fill command is now available only through MCP-enabled AI tools.',\n  'errors.commands.updateRemoved': 'The update command is no longer supported. Re-run `dotcontext init` to refresh scaffolds.',\n  'errors.commands.previewRemoved': 'Preview mode has been retired. Use the generated docs and agent templates directly.',\n  'errors.commands.guidelinesRemoved': 'Guidelines generation relied on LLMs and is no longer available.',\n  'errors.cli.executionFailed': 'CLI execution failed',\n  'errors.init.overwriteDeclined': 'Scaffold aborted to avoid overwriting existing content.',\n  'prompts.main.action': 'What would you like to do?',\n  'prompts.main.choice.scaffold': 'Generate documentation/agent scaffolding',\n  'prompts.main.choice.fill': 'Fill docs and agents with an LLM',\n  'prompts.main.choice.plan': 'Create a development plan',\n  'prompts.main.choice.changeLanguage': 'Change language',\n  'prompts.main.choice.exit': 'Exit interactive mode',\n  'prompts.scaffold.repoPath': 'Repository path to analyze',\n  'prompts.scaffold.type': 'What should we scaffold?',\n  'prompts.scaffold.typeBoth': 'Docs and agents',\n  'prompts.scaffold.typeDocs': 'Docs only',\n  'prompts.scaffold.typeAgents': 'Agents only',\n  'prompts.scaffold.selectComponents': 'What should we scaffold? (Select all that apply)',\n  'prompts.scaffold.componentDocs': 'Documentation',\n  'prompts.scaffold.componentAgents': 'Agent playbooks',\n  'prompts.scaffold.componentSkills': 'Skills',\n  'spinner.skills.creating': 'Creating skills directory and templates...',\n  'spinner.skills.created': 'Skills scaffold created ({count} files)',\n  'steps.init.skills': 'Scaffolding skills',\n  'prompts.init.confirmOverwriteDocs': 'Documentation already exists at {path}. Overwrite existing files?',\n  'prompts.init.confirmOverwriteAgents': 'Agent playbooks already exist at {path}. Overwrite existing files?',\n  'prompts.init.confirmOverwriteSkills': 'Skills already exist at {path}. Overwrite existing files?',\n  'prompts.common.verbose': 'Enable verbose logging?',\n  'prompts.fill.repoPath': 'Repository path containing the scaffold',\n  'prompts.fill.promptPath': 'Custom prompt path (leave blank to use the bundled default)',\n  'prompts.fill.limit': 'Maximum number of files to update (leave blank for all)',\n  'prompts.fill.overrideModel': 'Override provider/model configuration?',\n  'prompts.fill.provider': 'Select LLM provider',\n  'prompts.fill.provider.openrouter': 'OpenRouter (access to multiple models)',\n  'prompts.fill.provider.openai': 'OpenAI (GPT-4, etc.)',\n  'prompts.fill.provider.anthropic': 'Anthropic (Claude)',\n  'prompts.fill.provider.google': 'Google (Gemini)',\n  'prompts.fill.model': 'Model identifier',\n  'prompts.fill.provideApiKey': 'Provide an API key for this run?',\n  'prompts.fill.apiKey': 'API key',\n  'prompts.plan.name': 'Plan name (used for the file slug)',\n  'prompts.plan.goal': 'What should be planned?',\n  'prompts.plan.mode': 'How would you like to work with this plan?',\n  'prompts.plan.modeScaffold': 'Scaffold a plan template',\n  'prompts.plan.modeFill': 'Create a plan with AI (outline or AI-filled)',\n  'prompts.plan.summary': 'Optional summary to seed the plan (leave blank to add later)',\n  'prompts.plan.repoPath': 'Repository root for context',\n  'prompts.plan.dryRun': 'Preview updates without writing files?',\n  'prompts.plan.useLsp': 'Use LSP for deeper type analysis? (recommended for plans)',\n  'prompts.language.select': 'Choose the CLI language / Escolha o idioma do CLI',\n  'prompts.language.option.en': 'English / Inglês',\n  'prompts.language.option.pt-BR': 'Portuguese (Brazil) / Português (Brasil)',\n  'errors.plan.fillFailed': 'Failed to fill plan with LLM assistance',\n  'errors.fill.promptMissing': 'Prompt file not found at {path}.',\n  'errors.fill.apiKeyMissing': '{provider} API key is required. Set one of {envVars} or use --api-key.',\n  'info.interactive.returning.title': 'Main menu',\n  'info.interactive.returning.detail': 'Returning to the interactive menu. Pick another action or choose Exit.',\n  'success.interactive.goodbye': 'Goodbye! Thanks for using dotcontext.',\n  'agent.starting': 'Starting',\n  'agent.complete': 'Complete',\n  'agent.steps': 'steps',\n  'agent.type.documentation': 'Documentation Agent',\n  'agent.type.playbook': 'Playbook Agent',\n  'agent.type.plan': 'Plan Agent',\n  'agent.type.fill': 'Fill Agent',\n  'agent.type.skill': 'Skill Agent',\n  'commands.sync.description': 'Sync agent playbooks to AI tool directories (.claude, .github, etc.)',\n  'commands.sync.options.source': 'Source agents directory',\n  'commands.sync.options.target': 'Target directories to sync to',\n  'commands.sync.options.mode': 'Reference type: symlink or markdown',\n  'commands.sync.options.preset': 'Built-in preset: claude, github, cursor, or all',\n  'commands.sync.options.force': 'Overwrite existing files/symlinks',\n  'commands.sync.options.dryRun': 'Preview changes without writing',\n  'commands.sync.options.verbose': 'Enable verbose logging',\n  'prompts.main.choice.syncAgents': 'Sync agents to AI tools',\n  'prompts.main.choice.create': 'Create documentation for this project',\n  'prompts.main.choice.moreOptions': 'More options...',\n  'prompts.main.choice.viewPending': 'View pending files',\n  'prompts.main.choice.updateDocs': 'Update documentation',\n  'prompts.main.choice.updateDocsBehind': 'Update documentation ({daysBehind} days behind)',\n  'prompts.main.choice.rescaffold': 'Re-scaffold (overwrite)',\n  'prompts.main.unfilledPrompt': '{count} files still need content. What would you like to do?',\n  'prompts.main.pendingFilesHeader': 'Files pending content:',\n  'prompts.setup.confirmContinue': 'This will create and fill documentation. Continue?',\n  'spinner.setup.creatingStructure': 'Creating documentation structure...',\n  'spinner.setup.fillingDocs': 'Filling documentation with AI...',\n  'info.setup.incomplete.title': 'Setup incomplete',\n  'info.setup.incomplete.detail': 'Use MCP in your AI tool to fill or refresh documentation later.',\n  'info.setup.enhanceWithAI': 'Use MCP in your AI tool to fill or refresh context files.',\n  'success.setup.docsCreated': 'Documentation created!',\n  'success.setup.scaffoldCreated': 'Scaffolds created with codebase context!',\n  'info.setup.reviewFiles': 'Review the files in .context/ and commit them.',\n  'prompts.sync.source': 'Source agents directory',\n  'prompts.sync.mode': 'How should references be created?',\n  'prompts.sync.modeSymlink': 'Symlinks (recommended)',\n  'prompts.sync.modeMarkdown': 'Markdown reference files',\n  'prompts.sync.targetType': 'Where to sync agents?',\n  'prompts.sync.targetPreset': 'Use a preset (Claude, GitHub, etc.)',\n  'prompts.sync.targetCustom': 'Custom path',\n  'prompts.sync.selectPreset': 'Select target preset',\n  'prompts.sync.preset.claude': 'Claude Code (.claude/agents)',\n  'prompts.sync.preset.github': 'GitHub Copilot (.github/agents)',\n  'prompts.sync.preset.cursor': 'Cursor (.cursor/agents)',\n  'prompts.sync.preset.all': 'All presets',\n  'prompts.sync.customPath': 'Enter custom target path',\n  'prompts.sync.dryRun': 'Preview changes first (dry-run)?',\n  'prompts.sync.force': 'Overwrite existing files?',\n  'errors.sync.failed': 'Failed to sync agents',\n  'errors.sync.noTargetsSpecified': 'No targets specified. Use --target or --preset.',\n  'errors.sync.sourceMissing': 'Source directory does not exist: {path}',\n  'errors.sync.sourceNotDirectory': 'Source path is not a directory: {path}',\n  'warnings.sync.noAgentsFound': 'No agent files found in source directory.',\n  'spinner.sync.processing': 'Syncing to {path}...',\n  'spinner.sync.complete': 'Synced {count} files to {path}',\n  'spinner.sync.failed': 'Failed to sync to {path}',\n  'success.sync.completed': 'Agent sync complete. Your AI tools are now configured.',\n  'steps.sync.processingTarget': 'Processing target: {path}',\n  'steps.sync.summary': 'Generating sync summary',\n  'info.sync.foundAgents': 'Agent files discovered',\n  'info.sync.foundAgentsDetail': 'Found {count} agent playbooks to sync',\n  // Import commands translations\n  'commands.importRules.description': 'Import cursor rules and AI assistant rules to .context/docs',\n  'commands.importRules.options.source': 'Source paths to scan for rules',\n  'commands.importRules.options.target': 'Target directory for imported rules',\n  'commands.importRules.options.format': 'Output format: markdown, formatted, or raw',\n  'commands.importRules.options.force': 'Overwrite existing files',\n  'commands.importRules.options.dryRun': 'Preview changes without writing',\n  'commands.importRules.options.verbose': 'Enable verbose logging',\n  'commands.importRules.options.autoDetect': 'Auto-detect rules in common locations',\n  'commands.importAgents.description': 'Import agents from AI tool directories to .context/agents',\n  'commands.importAgents.options.source': 'Source paths to scan for agents',\n  'commands.importAgents.options.target': 'Target directory for imported agents',\n  'commands.importAgents.options.force': 'Overwrite existing files',\n  'commands.importAgents.options.dryRun': 'Preview changes without writing',\n  'commands.importAgents.options.verbose': 'Enable verbose logging',\n  'commands.importAgents.options.autoDetect': 'Auto-detect agents in common locations',\n  'warnings.import.noRulesFound': 'No rules files found.',\n  'warnings.import.noAgentsFound': 'No agent files found.',\n  'spinner.import.detectingRules': 'Detecting rules files...',\n  'spinner.import.detectingAgents': 'Detecting agent files...',\n  'spinner.import.scanningPaths': 'Scanning source paths...',\n  'spinner.import.importing': 'Importing to {path}...',\n  'spinner.import.complete': 'Imported {count} files to {path}',\n  'info.import.foundRules': 'Rules files discovered',\n  'info.import.foundRulesDetail': 'Found {count} rules files to import',\n  'info.import.foundAgents': 'Agent files discovered',\n  'info.import.foundAgentsDetail': 'Found {count} agent files to import',\n  'success.import.completed': 'Import completed successfully.',\n  'errors.import.failed': 'Failed to import files',\n  'prompts.mode.select': 'How would you like to configure this operation?',\n  'prompts.mode.quick': 'Quick Start (use smart defaults)',\n  'prompts.mode.advanced': 'Advanced (customize all options)',\n  'prompts.summary.title': 'Configuration Summary',\n  'prompts.summary.proceed': 'Proceed with this configuration?',\n  'prompts.llm.autoDetected': 'Auto-detected from environment',\n  'prompts.quick.confirmRepo': 'Use this repository?',\n  'prompts.sync.quickTarget': 'Where should agents be synced?',\n  'prompts.sync.quickTarget.common': 'Claude Code + GitHub Copilot (recommended)',\n  'prompts.sync.quickTarget.claude': 'Claude Code only',\n  'prompts.sync.quickTarget.all': 'All AI tools',\n  'prompts.sync.quickTarget.custom': 'Custom path...',\n  // PREVC Workflow translations\n  'prompts.main.choice.workflow': 'PREVC Workflow',\n  'prompts.workflow.noWorkflowFound': 'No PREVC workflow found. Create a new one?',\n  'prompts.workflow.projectName': 'Project/feature name:',\n  'prompts.workflow.projectNameRequired': 'Name is required',\n  'prompts.workflow.description': 'Brief description (for scale detection):',\n  'prompts.workflow.scale': 'Project scale:',\n  'prompts.workflow.scale.auto': 'Auto-detect (based on description)',\n  'prompts.workflow.scale.quick': 'Quick - Bug fixes, tweaks (~5 min)',\n  'prompts.workflow.scale.small': 'Small - Simple features (~15 min)',\n  'prompts.workflow.scale.medium': 'Medium - Regular features (~30 min)',\n  'prompts.workflow.scale.large': 'Large - Complex systems, compliance (~1+ hours)',\n  'prompts.workflow.action': 'Workflow action:',\n  'prompts.workflow.action.advance': 'Advance to next phase',\n  'prompts.workflow.action.refresh': 'Refresh status',\n  'prompts.workflow.action.back': 'Back to main menu',\n  'success.workflow.initialized': 'Workflow PREVC initialized: {name}',\n  'success.workflow.advanced': 'Advanced to phase: {phase} - {phaseName}',\n  'success.workflow.completed': 'Workflow completed.',\n  'errors.workflow.initFailed': 'Failed to initialize workflow',\n  'errors.workflow.advanceFailed': 'Failed to advance workflow',\n  'errors.workflow.notFound': 'No workflow found. Run \"workflow init <name>\" first.',\n  'info.workflow.scale': 'Scale',\n  'info.workflow.currentPhase': 'Current Phase',\n  'prompts.workflow.workflowComplete': 'Current workflow is complete. What would you like to do?',\n  'prompts.workflow.action.newWorkflow': 'Start a new workflow',\n  'prompts.workflow.action.viewStatus': 'View completed workflow status',\n  'prompts.workflow.confirmNewWorkflow': 'This will replace the current workflow. Continue?',\n  // Start command translations\n  'commands.start.description': 'Smart start: initialize context and start workflow in one command',\n  'commands.start.arguments.featureName': 'Optional feature name to start a workflow',\n  'commands.start.options.template': 'Workflow template: hotfix, feature, mvp, or auto',\n  'commands.start.options.skipFill': 'Skip AI-assisted documentation filling',\n  'commands.start.options.skipWorkflow': 'Skip workflow initialization',\n  'commands.previewSplash.description': 'Render the interactive splash screen preview',\n  'commands.previewSplash.options.title': 'Override the splash title',\n  'commands.previewSplash.options.directory': 'Directory to display in the splash',\n  'spinner.start.detectingStack': 'Detecting technology stack...',\n  'spinner.start.stackDetected': 'Stack detected: {stack}',\n  'spinner.start.initializing': 'Initializing project context...',\n  'spinner.start.initialized': 'Project context initialized',\n  'spinner.start.initFailed': 'Failed to initialize context',\n  'spinner.start.filling': 'Filling documentation with AI...',\n  'spinner.start.filled': 'Documentation filled',\n  'spinner.start.fillFailed': 'Documentation refresh is now handled through MCP-enabled AI tools',\n  'errors.start.workflowFailed': 'Failed to start workflow',\n  'success.start.complete': 'Project ready! {details}',\n  // MCP Install command translations\n  'commands.mcpInstall.description': 'Install MCP server configuration for AI tools (Claude Code, Cursor, etc.)',\n  'commands.mcpInstall.options.global': 'Install globally in home directory (default)',\n  'commands.mcpInstall.options.local': 'Install locally in project directory',\n  'commands.mcpInstall.options.dryRun': 'Preview changes without writing',\n  'commands.mcpInstall.options.verbose': 'Enable verbose output',\n  'commands.mcpInstall.selectTool': 'Select the tool to configure MCP for:',\n  'commands.mcpInstall.allDetected': 'All detected tools',\n  'labels.detected': 'detected',\n  'info.mcp.wouldInstall': 'Would install MCP for {tool} at {path}',\n  'info.mcp.installed': 'MCP configured for {tool} at {path}',\n  'info.mcp.alreadyConfigured': '{tool} already has MCP configured',\n  'info.mcp.restartTools': 'Restart your AI tools to apply the MCP configuration.',\n  'success.mcp.installed': 'MCP installed for {count} tool(s)',\n  'warnings.mcp.noToolsDetected': 'No supported AI tools detected. Specify a tool manually.',\n  'errors.mcp.unsupportedTool': 'Unsupported tool: {tool}. Supported: {supported}',\n  'errors.mcp.installFailed': 'Failed to install MCP for {tool}',\n  // MCP tool display names\n  'commands.mcpInstall.selectTool.claudeDesktop': 'Claude Desktop',\n  'commands.mcpInstall.selectTool.vscode': 'VS Code (GitHub Copilot)',\n  'commands.mcpInstall.selectTool.roo': 'Roo Code',\n  'commands.mcpInstall.selectTool.warp': 'Warp Terminal',\n  'commands.mcpInstall.selectTool.amazonq': 'Amazon Q Developer CLI',\n  'commands.mcpInstall.selectTool.geminiCli': 'Gemini CLI',\n  'commands.mcpInstall.selectTool.kiro': 'Kiro',\n  'commands.mcpInstall.selectTool.zed': 'Zed Editor',\n  'commands.mcpInstall.selectTool.jetbrains': 'JetBrains IDEs',\n  // Export command translations\n  'commands.export.description': 'Export rules from .context to AI tool directories',\n  'commands.export.options.source': 'Source directory for rules',\n  'commands.export.options.targets': 'Target paths to export to',\n  'commands.export.options.preset': 'Export preset: cursor, claude, github, windsurf, cline, aider, codex, or all',\n  'commands.export.options.force': 'Overwrite existing files',\n  'commands.export.options.dryRun': 'Preview changes without writing',\n  'spinner.export.exporting': 'Exporting to {target}...',\n  'spinner.export.exported': 'Exported to {target}',\n  'spinner.export.skipped': 'Skipped {target} (already exists)',\n  'spinner.export.failed': 'Failed to export to {target}',\n  'spinner.export.dryRun': 'Would export to {target}',\n  'errors.export.noTargets': 'No export targets specified. Use --preset or --targets.',\n  'errors.export.noRules': 'No rules found in source directory.',\n  'success.export.completed': 'Exported rules to {count} target(s)',\n  // Report command translations\n  'commands.report.description': 'Generate workflow progress report',\n  'commands.report.options.format': 'Output format: console, markdown, or json',\n  'commands.report.options.output': 'Output file path (for markdown/json)',\n  'commands.report.options.includeStack': 'Include technology stack analysis',\n  'errors.report.noWorkflow': 'No workflow found. Run \"workflow init\" first.',\n  'success.report.saved': 'Report saved to {path}',\n  // Visual dashboard translations\n  'dashboard.title': 'Workflow Dashboard',\n  'dashboard.phase.completed': 'Completed',\n  'dashboard.phase.inProgress': 'In Progress',\n  'dashboard.phase.pending': 'Pending',\n  'dashboard.phase.skipped': 'Skipped',\n  // Auto-advance translations\n  'autoAdvance.detected': 'Detected {count} output(s) for phase {phase}',\n  'autoAdvance.suggest': 'Ready to advance to next phase?',\n  // Workflow template translations\n  'prompts.workflow.template': 'Select workflow template:',\n  'prompts.workflow.template.hotfix': 'Hotfix - Quick bug fix (E → V)',\n  'prompts.workflow.template.feature': 'Feature - Regular development (P → R → E → V)',\n  'prompts.workflow.template.mvp': 'MVP - Full product workflow (P → R → E → V → C)',\n  'prompts.workflow.template.auto': 'Auto-detect based on description',\n  // Skill command translations\n  'commands.skill.description': 'Manage skills (on-demand expertise for AI agents)',\n  'commands.skill.init.description': 'Initialize skills directory with built-in skills',\n  'commands.skill.fill.description': 'Fill skills with AI-generated project-specific content',\n  'commands.skill.list.description': 'List available skills',\n  'commands.skill.export.description': 'Export skills to AI tool directories (Claude, Gemini, Codex)',\n  'commands.skill.create.description': 'Create a new custom skill',\n  'prompts.main.choice.skills': 'Manage skills',\n  'prompts.skill.action': 'What would you like to do with skills?',\n  'prompts.skill.action.init': 'Initialize built-in skills',\n  'prompts.skill.action.fill': 'Fill skills with AI',\n  'prompts.skill.action.list': 'List available skills',\n  'prompts.skill.action.export': 'Export skills to AI tools',\n  'prompts.skill.action.create': 'Create a custom skill',\n  'prompts.skill.action.back': 'Back to main menu',\n  'prompts.skill.name': 'Skill name:',\n  'prompts.skill.description': 'Skill description:',\n  'prompts.skill.phases': 'PREVC phases (comma-separated: P,R,E,V,C):',\n  'prompts.skill.exportPreset': 'Export to which AI tools?',\n  'prompts.skill.exportPreset.all': 'All tools (Claude, Gemini, Codex)',\n  'prompts.skill.exportPreset.claude': 'Claude Code only',\n  'prompts.skill.exportPreset.gemini': 'Gemini CLI only',\n  'prompts.skill.exportPreset.codex': 'Codex CLI only',\n  'success.skill.initialized': 'Skills initialized in {path}',\n  'success.skill.created': 'Created skill: {name}',\n  'success.skill.exported': 'Exported {count} skills to {targets} targets',\n  'success.skill.filled': 'Filled {count} skills with project-specific content',\n  'errors.skill.initFailed': 'Failed to initialize skills',\n  'errors.skill.listFailed': 'Failed to list skills',\n  'errors.skill.exportFailed': 'Failed to export skills',\n  'errors.skill.createFailed': 'Failed to create skill',\n  'errors.skill.fillFailed': 'Failed to fill skills',\n  'errors.skill.missingScaffold': 'Skills directory not found. Run \"skill init\" first.',\n  'steps.skill.discover': 'Discovering skills',\n  'steps.skill.loadContext': 'Loading docs and agents context',\n  'steps.skill.fillSkills': 'Filling {count} skills with {model}',\n  'steps.skill.summary': 'Summary',\n  'spinner.skill.discovering': 'Discovering scaffolded skills...',\n  'spinner.skill.discovered': 'Found {count} skills to fill',\n  'spinner.skill.loadingContext': 'Loading docs and agents context...',\n  'spinner.skill.contextLoaded': 'Loaded docs ({docsSize} chars) and agents ({agentsSize} chars)',\n  'spinner.skill.noContent': 'No content generated for {skill}',\n  'spinner.skill.filled': 'Filled {skill}',\n  'spinner.skill.failed': 'Failed to fill {skill}',\n  'warnings.skill.noTargets': 'No skills found to fill. Run \"skill init\" first.',\n  'info.skill.generated': 'Generated',\n  'info.skill.skipped': 'Skipped (already exist)',\n  'info.skill.path': 'Path',\n  // Quick Sync translations\n  'prompts.main.choice.quickSync': 'Quick Sync',\n  'prompts.quickSync.selectComponents': 'Select components to sync:',\n  'prompts.quickSync.components.agents': 'Agents',\n  'prompts.quickSync.components.skills': 'Skills',\n  'prompts.quickSync.components.docs': 'Documentation',\n  'prompts.quickSync.noComponentsSelected': 'No components selected',\n  'prompts.quickSync.selectAgentTargets': 'Select agent sync targets:',\n  'prompts.quickSync.selectSkillTargets': 'Select skill export targets:',\n  'prompts.quickSync.selectDocTargets': 'Select doc export targets:',\n  'prompts.quickSync.syncing.agents': 'Syncing agents...',\n  'prompts.quickSync.syncing.skills': 'Exporting skills...',\n  'prompts.quickSync.syncing.rules': 'Exporting rules...',\n  'prompts.quickSync.syncing.docs': 'Checking docs...',\n  'prompts.quickSync.docsOutdated': '{days} days old',\n  'prompts.quickSync.updateDocs': 'Update docs too?',\n  'success.quickSync.complete': 'Sync complete',\n  // Reverse Sync translations\n  'prompts.main.choice.reverseSync': 'Reverse Sync (import from AI tools)',\n  'prompts.reverseSync.detecting': 'Detecting AI tool configurations...',\n  'prompts.reverseSync.detected': 'Detected AI Tools:',\n  'prompts.reverseSync.noFilesFound': 'No AI tool configuration files found',\n  'prompts.reverseSync.noComponentsSelected': 'No components selected for import',\n  'prompts.reverseSync.selectComponents': 'Select components to import:',\n  'prompts.reverseSync.mergeStrategy': 'How should conflicts be handled?',\n  'prompts.reverseSync.strategy.skip': 'Skip existing files',\n  'prompts.reverseSync.strategy.overwrite': 'Overwrite existing files',\n  'prompts.reverseSync.strategy.merge': 'Merge content (append)',\n  'prompts.reverseSync.strategy.rename': 'Rename new files',\n  'success.reverseSync.complete': 'Imported {count} files',\n  'errors.reverseSync.failed': 'Reverse sync failed',\n  // Manage submenus\n  'prompts.main.choice.manageSkills': 'Manage Skills',\n  'prompts.main.choice.manageAgents': 'Manage Agents',\n  'prompts.main.choice.settings': 'Settings (language)',\n  'prompts.main.choice.mcpInstall': 'Install/Configure MCP',\n  'prompts.main.choice.startWorkflow': 'Start Workflow',\n  'prompts.main.choice.createPlan': 'Create Plan',\n  'prompts.main.choice.quickSetup': 'Quick Setup (creates context from codebase)',\n  'prompts.main.choice.enhanceWithAI': 'Enhance context with AI',\n  // Agents submenu\n  'prompts.agents.action': 'Agents:',\n  'prompts.agents.choice.sync': 'Sync to targets',\n  'prompts.agents.choice.create': 'Create custom agent',\n  'prompts.agents.choice.list': 'List agents',\n  'prompts.agents.choice.back': 'Back',\n  // Create custom agent\n  'prompts.agent.name': 'Agent name:',\n  'prompts.agent.description': 'Agent description:',\n  'prompts.agent.role': 'Agent role/specialty:',\n  'success.agent.created': 'Created agent: {name}',\n  'spinner.agent.creating': 'Creating agent with AI...',\n  'spinner.agent.created': 'Agent created',\n  // Settings submenu\n  'prompts.settings.action': 'Settings:',\n  'prompts.settings.choice.language': 'Change language',\n  'prompts.settings.choice.back': 'Back',\n  // Mode selection (interactive entry)\n  'prompts.modeSelect.select': 'How would you like to use @dotcontext/cli?',\n  'prompts.modeSelect.choice.mcp': 'Set up MCP for my AI tool (recommended — no API key needed)',\n  'prompts.modeSelect.choice.cli': 'Use the interactive CLI for workflow, sync, imports, and MCP setup',\n  'prompts.modeSelect.choice.exit': 'Exit',\n  // More options submenu\n  'prompts.more.action': 'More options:',\n  'prompts.more.choice.back': 'Back',\n  // Quick Sync mode\n  'prompts.quickSync.mode': 'Sync all (agents, skills, docs) to common targets?',\n  'prompts.quickSync.mode.syncAll': 'Yes, sync all',\n  'prompts.quickSync.mode.customize': 'Customize targets...',\n  'prompts.quickSync.mode.cancel': 'Cancel',\n  // Compact status\n  'status.compact': '✓ Context ready ({docs} docs, {agents} agents, {skills} skills)',\n  'status.outdated': '⚠ Context outdated ({days} days) - refresh via MCP',\n  'status.new': 'No context found. Use MCP or reverse sync.',\n  'status.unfilled': 'Context found, {count} files still need content',\n  'status.detected.project': 'Detected: {languages} project',\n  // Hit Enter / Press Enter\n  'prompts.pressEnter': 'Press Enter to continue...',\n  // Environment variable loading\n  'prompts.env.loadEnv': 'Load environment variables from .env file?',\n  // Config summary labels\n  'configSummary.config': 'Config:',\n  'configSummary.options': 'Options:',\n  'configSummary.repo': 'repo',\n  'configSummary.provider': 'provider',\n  'configSummary.yes': 'Yes',\n  'configSummary.no': 'No',\n  // API key validation\n  'warnings.apiKey.empty': 'API key is empty. LLM calls will likely fail.',\n  'warnings.apiKey.formatMismatch': 'API key does not start with expected prefix \"{prefix}\" for {provider}. It may still work if correct.',\n  // Back/cancel options\n  'prompts.analysis.back': 'Back (cancel analysis options)',\n  'prompts.llm.back': 'Back (cancel LLM configuration)'\n} as const;\n\nexport type TranslationKey = keyof typeof englishMessages;\n\ntype TranslationDictionary = Record<TranslationKey, string>;\n\nconst portugueseMessages: TranslationDictionary = {\n  'cli.description': 'Sincronize assets de .context, faça reverse-sync do estado das ferramentas e instale integrações MCP para o seu repositório',\n  'global.options.lang': 'Idioma para a saída do CLI (en ou pt-BR)',\n  'ui.version': 'Versão {version}',\n  'ui.projectConfiguration.title': 'Configuração do Projeto',\n  'ui.projectConfiguration.repository': 'Repositório',\n  'ui.projectConfiguration.output': 'Saída',\n  'ui.projectConfiguration.mode': 'Modo',\n  'ui.progress.starting': 'Iniciando...',\n  'ui.analysis.complete.title': 'Análise Concluída',\n  'ui.analysis.files': 'Arquivos',\n  'ui.analysis.directories': 'Diretórios',\n  'ui.analysis.totalSize': 'Tamanho total',\n  'ui.fileTypeDistribution.title': 'Distribuição de Tipos',\n  'ui.generationSummary.title': 'Scaffold Concluído',\n  'ui.generationSummary.documentation': 'Documentação',\n  'ui.generationSummary.agents': 'Agentes',\n  'ui.generationSummary.skills': 'Skills',\n  'ui.generationSummary.timeElapsed': 'Tempo',\n  'ui.generationSummary.nextStep': 'Próximo passo: personalize os templates gerados para o seu projeto.',\n  'ui.error.title': 'Erro',\n  'ui.splash.directoryLabel': 'diretorio',\n  'info.prompt.title': 'Prompt do sistema',\n  'info.prompt.usingCustom': 'Usando prompt em {path}.',\n  'info.prompt.usingPackage': 'Usando prompt empacotado em {path}.',\n  'info.prompt.usingBundled': 'Usando prompt padrão incluído no dotcontext.',\n  'info.update.available.title': 'Atualização disponível',\n  'info.update.available.detail': 'Uma nova versão {latest} está disponível (atual {current}). Atualize com {command}.',\n  'commands.init.description': 'Gerar bases de documentação e agentes para um repositório',\n  'commands.init.arguments.repoPath': 'Caminho do repositório a ser analisado',\n  'commands.init.arguments.type': 'Tipo de base: \"docs\", \"agents\" ou \"both\" (padrão)',\n  'commands.init.options.output': 'Diretório de saída para os artefatos gerados',\n  'commands.init.options.exclude': 'Padrões glob para excluir da análise',\n  'commands.init.options.include': 'Padrões glob para incluir durante a análise',\n  'commands.init.options.verbose': 'Ativa logs detalhados',\n  'commands.init.options.noSemantic': 'Desativa análise semântica de código (ativada por padrão)',\n  'commands.init.options.noContentStubs': 'Desativa stubs de conteúdo (cabeçalhos e orientações) nos scaffolds',\n  'commands.fill.description': 'Usar um assistente de IA para preencher docs e agents com o contexto mais recente do repositório',\n  'commands.fill.arguments.repoPath': 'Caminho da raiz do repositório usado para montar o contexto',\n  'commands.fill.options.output': 'Diretório com docs/ e agents/ gerados pela base',\n  'commands.fill.options.apiKey': 'Chave de API para o provedor de LLM',\n  'commands.fill.options.model': 'Modelo de LLM a ser utilizado',\n  'commands.fill.options.provider': 'Provedor de LLM (openrouter, openai, anthropic, google)',\n  'commands.fill.options.baseUrl': 'URL base personalizada para as APIs do provedor',\n  'commands.fill.options.prompt': 'Caminho para o prompt de instrução',\n  'commands.fill.options.limit': 'Número máximo de arquivos a processar',\n  'commands.fill.options.exclude': 'Padrões glob para excluir da análise do repositório',\n  'commands.fill.options.include': 'Padrões glob para incluir durante a análise',\n  'commands.fill.options.verbose': 'Ativa logs detalhados',\n  'commands.fill.options.noSemantic': 'Desativa modo de contexto semântico e usa exploração baseada em ferramentas',\n  'commands.fill.options.languages': 'Linguagens de programação a analisar (ex: typescript,python)',\n  'commands.fill.options.useLsp': 'Habilita LSP para análise semântica mais profunda (tipos, referências)',\n  'prompts.fill.semantic': 'Usar modo de contexto semântico? (mais rápido, menos tokens - recomendado)',\n  'prompts.fill.languages': 'Selecione as linguagens de programação para análise',\n  'prompts.fill.useLsp': 'Usar LSP para análise de tipos mais profunda? (mais lento mas mais preciso)',\n  'commands.plan.description': 'Criar um plano de desenvolvimento que conecta docs e playbooks de agentes',\n  'commands.plan.arguments.planName': 'Nome do plano (usado como slug do arquivo)',\n  'commands.plan.options.output': 'Diretório de saída para os arquivos de plano gerados',\n  'commands.plan.options.title': 'Título personalizado para o plano',\n  'commands.plan.options.summary': 'Resumo inicial para o cabeçalho do plano',\n  'commands.plan.options.force': 'Sobrescreve o plano se ele já existir (modo base)',\n  'commands.plan.options.fill': 'Usa um assistente de IA para preencher ou atualizar o plano em vez de apenas gerar',\n  'commands.plan.options.repo': 'Raiz do repositório para contexto semântico',\n  'commands.plan.options.apiKey': 'Chave de API para o provedor de LLM',\n  'commands.plan.options.model': 'Modelo de LLM a ser utilizado',\n  'commands.plan.options.provider': 'Provedor de LLM (openrouter, openai, anthropic, google)',\n  'commands.plan.options.baseUrl': 'URL base personalizada para as APIs do provedor',\n  'commands.plan.options.prompt': 'Caminho para o prompt de atualização do plano',\n  'commands.plan.options.dryRun': 'Pré-visualiza as atualizações sem escrever arquivos',\n  'commands.plan.options.include': 'Padrões glob para incluir na análise do repositório',\n  'commands.plan.options.exclude': 'Padrões glob para excluir da análise do repositório',\n  'commands.plan.options.verbose': 'Ativa logs detalhados',\n  'commands.plan.options.noSemantic': 'Desativa análise semântica de código (ativada por padrão)',\n  'commands.plan.options.noLsp': 'Desativa análise semântica LSP (ativada por padrão para planos)',\n  'errors.init.scaffoldFailed': 'Falha ao gerar os artefatos base',\n  'errors.fill.failed': 'Falha ao preencher documentação e agentes com ajuda de um assistente de IA',\n  'errors.plan.creationFailed': 'Falha ao criar o template do plano',\n  'errors.init.invalidType': 'Tipo de base \"{value}\" inválido. Use um dos seguintes: {allowed}',\n  'errors.common.repoMissing': 'O caminho do repositório não existe: {path}',\n  'warnings.scaffold.noneSelected': 'Nenhuma documentação ou playbook selecionado. Nada a gerar.',\n  'warnings.agents.missingDirectory': 'Nenhum diretório de agents encontrado. Inicialize ou importe o contexto via MCP primeiro.',\n  'steps.init.analyze': 'Analisando a estrutura do repositório',\n  'steps.init.docs': 'Gerando bases de documentação',\n  'steps.init.agents': 'Gerando playbooks de agentes',\n  'spinner.repo.scanning': 'Escaneando o repositório...',\n  'spinner.repo.scanComplete': 'Encontrados {fileCount} arquivos em {directoryCount} diretórios',\n  'spinner.docs.creating': 'Criando diretório e templates de docs...',\n  'spinner.docs.creatingWithSemantic': 'Analisando codebase e criando templates de docs...',\n  'spinner.docs.created': 'Scaffold de documentação criado ({count} arquivos)',\n  'spinner.agents.creating': 'Criando diretório e templates de agentes...',\n  'spinner.agents.creatingWithSemantic': 'Analisando codebase e criando playbooks de agentes...',\n  'spinner.agents.created': 'Scaffold de agentes criado ({count} arquivos)',\n  'spinner.plan.creating': 'Criando template de plano...',\n  'spinner.plan.created': 'Template de plano criado',\n  'spinner.plan.creationFailed': 'Falha ao criar o template do plano',\n  'success.scaffold.ready': 'Base disponível em {path}',\n  'success.plan.createdAt': 'Plano criado em {path}',\n  'errors.fill.missingDocsScaffold': 'Scaffold de documentação não encontrado. Execute `dotcontext init` primeiro.',\n  'errors.fill.missingAgentsScaffold': 'Scaffold de agentes não encontrado. Execute `dotcontext init` primeiro.',\n  'errors.fill.missingScaffold': 'Nenhum scaffold encontrado. Execute `dotcontext init` primeiro para criar docs e/ou agents.',\n  'steps.fill.analyze': 'Analisando a estrutura do repositório',\n  'warnings.fill.noTargets': 'Nenhum arquivo Markdown foi encontrado em .context/docs ou .context/agents. Execute \"dotcontext init\" antes de preencher.',\n  'steps.fill.processFiles': 'Atualizando {count} arquivos com {model}',\n  'spinner.fill.processing': 'Processando {path}...',\n  'spinner.fill.noContent': 'Nenhum conteúdo recebido para {path}',\n  'messages.fill.emptyResponse': 'Resposta vazia do assistente de IA',\n  'messages.fill.previewStart': '--- Início da prévia ---',\n  'messages.fill.previewEnd': '--- Fim da prévia ---',\n  'spinner.fill.updated': 'Atualizado {path}',\n  'spinner.fill.failed': 'Falha em {path}',\n  'steps.fill.summary': 'Resumindo o uso do assistente de IA',\n  'success.fill.completed': 'Preenchimento com assistente de IA concluído. Revise as mudanças e faça o commit quando estiver pronto.',\n  'errors.plan.invalidName': 'O nome do plano deve conter ao menos um caractere alfanumérico.',\n  'messages.plan.regenerated': 'Regerado {path} antes do preenchimento com assistente de IA.',\n  'messages.plan.created': 'Criado {path} antes do preenchimento com assistente de IA.',\n  'info.plan.scaffolded.title': 'Plano criado',\n  'errors.plan.missingPlansDir': 'Diretório de planos não encontrado. Execute `dotcontext plan <nome>` para criar um.',\n  'errors.plan.notFound': 'Plano não encontrado. Esperado {expected}.',\n  'steps.plan.summary': 'Resumindo estado do repositório',\n  'spinner.planFill.analyzingRepo': 'Analisando repositório...',\n  'spinner.planFill.summaryReady': 'Resumo do repositório pronto',\n  'steps.plan.update': 'Atualizando {path} com {model}',\n  'spinner.planFill.updating': 'Preenchendo {path}...',\n  'spinner.planFill.noContent': 'Nenhum conteúdo recebido do assistente de IA',\n  'spinner.planFill.dryRun': 'Execução simulada - prévia a seguir',\n  'spinner.planFill.updated': 'Atualizado {path}',\n  'spinner.planFill.failed': 'Falha ao preencher o plano',\n  'steps.plan.summaryResults': 'Resumindo o uso do assistente de IA',\n  'success.plan.filled': 'Preenchimento do plano concluído. Revise as atualizações e faça o commit quando estiver pronto.',\n  'errors.commands.analyzeRemoved': 'O comando analyze foi removido na versão focada em bases do dotcontext.',\n  'errors.commands.fillRemoved': 'O comando fill agora está disponível apenas por ferramentas de IA com MCP.',\n  'errors.commands.updateRemoved': 'O comando update não é mais suportado. Execute `dotcontext init` novamente para atualizar as bases.',\n  'errors.commands.previewRemoved': 'O modo de pré-visualização foi descontinuado. Use diretamente os docs e playbooks gerados.',\n  'errors.commands.guidelinesRemoved': 'A geração de guidelines dependia de LLMs e não está mais disponível.',\n  'errors.cli.executionFailed': 'Falha na execução do CLI',\n  'errors.init.overwriteDeclined': 'Scaffold cancelado para evitar sobrescrever conteúdo existente.',\n  'prompts.main.action': 'O que você gostaria de fazer?',\n  'prompts.main.choice.scaffold': 'Gerar bases de documentação/agentes',\n  'prompts.main.choice.fill': 'Preencher docs e agentes com um LLM',\n  'prompts.main.choice.plan': 'Criar um plano de desenvolvimento',\n  'prompts.main.choice.changeLanguage': 'Mudar idioma',\n  'prompts.main.choice.exit': 'Sair do modo interativo',\n  'prompts.scaffold.repoPath': 'Caminho do repositório para analisar',\n  'prompts.scaffold.type': 'O que devemos gerar?',\n  'prompts.scaffold.typeBoth': 'Docs e playbooks',\n  'prompts.scaffold.typeDocs': 'Somente docs',\n  'prompts.scaffold.typeAgents': 'Somente playbooks',\n  'prompts.scaffold.selectComponents': 'O que devemos gerar? (Selecione todos que desejar)',\n  'prompts.scaffold.componentDocs': 'Documentacao',\n  'prompts.scaffold.componentAgents': 'Playbooks de agentes',\n  'prompts.scaffold.componentSkills': 'Skills',\n  'spinner.skills.creating': 'Criando diretorio e templates de skills...',\n  'spinner.skills.created': 'Scaffold de skills criado ({count} arquivos)',\n  'steps.init.skills': 'Gerando skills',\n  'prompts.init.confirmOverwriteDocs': 'A documentação já existe em {path}. Deseja sobrescrever os arquivos atuais?',\n  'prompts.init.confirmOverwriteAgents': 'Os playbooks de agentes já existem em {path}. Deseja sobrescrever os arquivos atuais?',\n  'prompts.init.confirmOverwriteSkills': 'Os skills já existem em {path}. Deseja sobrescrever os arquivos atuais?',\n  'prompts.common.verbose': 'Ativar logs detalhados?',\n  'prompts.fill.repoPath': 'Caminho do repositório que contém a base',\n  'prompts.fill.promptPath': 'Caminho do prompt personalizado (deixe em branco para usar o padrão incluso)',\n  'prompts.fill.limit': 'Número máximo de arquivos a atualizar (deixe em branco para todos)',\n  'prompts.fill.overrideModel': 'Sobrescrever configuração de provedor/modelo?',\n  'prompts.fill.provider': 'Selecione o provedor de LLM',\n  'prompts.fill.provider.openrouter': 'OpenRouter (acesso a múltiplos modelos)',\n  'prompts.fill.provider.openai': 'OpenAI (GPT-4, etc.)',\n  'prompts.fill.provider.anthropic': 'Anthropic (Claude)',\n  'prompts.fill.provider.google': 'Google (Gemini)',\n  'prompts.fill.model': 'Identificador do modelo',\n  'prompts.fill.provideApiKey': 'Informar uma chave de API para esta execução?',\n  'prompts.fill.apiKey': 'Chave de API',\n  'prompts.plan.name': 'Nome do plano (usado no slug do arquivo)',\n  'prompts.plan.goal': 'O que deve ser planejado?',\n  'prompts.plan.mode': 'Como você quer trabalhar com este plano?',\n  'prompts.plan.modeScaffold': 'Gerar um template de plano',\n  'prompts.plan.modeFill': 'Criar um plano com I.A. (cria um plano com o contexto atual)',\n  'prompts.plan.summary': 'Resumo opcional para iniciar o plano (deixe em branco para adicionar depois)',\n  'prompts.plan.repoPath': 'Raiz do repositório para contexto',\n  'prompts.plan.dryRun': 'Pré-visualizar atualizações sem escrever arquivos?',\n  'prompts.plan.useLsp': 'Usar LSP para análise de tipos mais profunda? (recomendado para planos)',\n  'prompts.language.select': 'Escolha o idioma do CLI / Choose the CLI language',\n  'prompts.language.option.en': 'Inglês / English',\n  'prompts.language.option.pt-BR': 'Português (Brasil) / Portuguese (Brazil)',\n  'errors.plan.fillFailed': 'Falha ao preencher o plano com ajuda de um assistente de IA',\n  'errors.fill.promptMissing': 'Arquivo de prompt não encontrado em {path}.',\n  'errors.fill.apiKeyMissing': 'É necessária uma chave de API {provider}. Defina uma das variáveis {envVars} ou use --api-key.',\n  'info.interactive.returning.title': 'Menu principal',\n  'info.interactive.returning.detail': 'Voltando ao menu interativo. Escolha outra ação ou selecione Sair.',\n  'success.interactive.goodbye': 'Até logo! Obrigado por usar o dotcontext.',\n  'agent.starting': 'Iniciando',\n  'agent.complete': 'Concluído',\n  'agent.steps': 'passos',\n  'agent.type.documentation': 'Agente de Documentação',\n  'agent.type.playbook': 'Agente de Playbook',\n  'agent.type.plan': 'Agente de Plano',\n  'agent.type.fill': 'Agente de Preenchimento',\n  'agent.type.skill': 'Agente de Skill',\n  'commands.sync.description': 'Sincronizar playbooks de agentes com diretórios de ferramentas IA (.claude, .github, etc.)',\n  'commands.sync.options.source': 'Diretório de origem dos agentes',\n  'commands.sync.options.target': 'Diretórios de destino para sincronizar',\n  'commands.sync.options.mode': 'Tipo de referência: symlink ou markdown',\n  'commands.sync.options.preset': 'Preset padrão: claude, github, cursor ou all',\n  'commands.sync.options.force': 'Sobrescrever arquivos/symlinks existentes',\n  'commands.sync.options.dryRun': 'Pré-visualizar mudanças sem escrever',\n  'commands.sync.options.verbose': 'Ativa logs detalhados',\n  'prompts.main.choice.syncAgents': 'Sincronizar agentes com ferramentas IA',\n  'prompts.main.choice.create': 'Criar documentação para este projeto',\n  'prompts.main.choice.moreOptions': 'Mais opções...',\n  'prompts.main.choice.viewPending': 'Ver arquivos pendentes',\n  'prompts.main.choice.updateDocs': 'Atualizar documentação',\n  'prompts.main.choice.updateDocsBehind': 'Atualizar documentação ({daysBehind} dias atrás)',\n  'prompts.main.choice.rescaffold': 'Refazer scaffold (sobrescrever)',\n  'prompts.main.unfilledPrompt': '{count} arquivos ainda precisam de conteudo. O que você gostaria de fazer?',\n  'prompts.main.pendingFilesHeader': 'Arquivos pendentes de conteúdo:',\n  'prompts.setup.confirmContinue': 'Isso criará e preencherá a documentação. Continuar?',\n  'spinner.setup.creatingStructure': 'Criando estrutura de documentação...',\n  'spinner.setup.fillingDocs': 'Preenchendo documentação com IA...',\n  'info.setup.incomplete.title': 'Configuração incompleta',\n  'info.setup.incomplete.detail': 'Use MCP na sua ferramenta de IA para preencher ou atualizar a documentação depois.',\n  'info.setup.enhanceWithAI': 'Use MCP na sua ferramenta de IA para preencher ou atualizar os arquivos de contexto.',\n  'success.setup.docsCreated': 'Documentação criada!',\n  'success.setup.scaffoldCreated': 'Scaffolds criados com contexto do codebase!',\n  'info.setup.reviewFiles': 'Revise os arquivos em .context/ e faça commit.',\n  'prompts.sync.source': 'Diretório de origem dos agentes',\n  'prompts.sync.mode': 'Como as referências devem ser criadas?',\n  'prompts.sync.modeSymlink': 'Symlinks (recomendado)',\n  'prompts.sync.modeMarkdown': 'Arquivos de referência Markdown',\n  'prompts.sync.targetType': 'Para onde sincronizar os agentes?',\n  'prompts.sync.targetPreset': 'Usar um preset (Claude, GitHub, etc.)',\n  'prompts.sync.targetCustom': 'Caminho personalizado',\n  'prompts.sync.selectPreset': 'Selecione o preset de destino',\n  'prompts.sync.preset.claude': 'Claude Code (.claude/agents)',\n  'prompts.sync.preset.github': 'GitHub Copilot (.github/agents)',\n  'prompts.sync.preset.cursor': 'Cursor (.cursor/agents)',\n  'prompts.sync.preset.all': 'Todos os presets',\n  'prompts.sync.customPath': 'Digite o caminho personalizado de destino',\n  'prompts.sync.dryRun': 'Pré-visualizar mudanças primeiro (dry-run)?',\n  'prompts.sync.force': 'Sobrescrever arquivos existentes?',\n  'errors.sync.failed': 'Falha ao sincronizar agentes',\n  'errors.sync.noTargetsSpecified': 'Nenhum destino especificado. Use --target ou --preset.',\n  'errors.sync.sourceMissing': 'Diretório de origem não existe: {path}',\n  'errors.sync.sourceNotDirectory': 'Caminho de origem não é um diretório: {path}',\n  'warnings.sync.noAgentsFound': 'Nenhum arquivo de agente encontrado no diretório de origem.',\n  'spinner.sync.processing': 'Sincronizando para {path}...',\n  'spinner.sync.complete': 'Sincronizados {count} arquivos para {path}',\n  'spinner.sync.failed': 'Falha ao sincronizar para {path}',\n  'success.sync.completed': 'Sincronização de agentes concluída. Suas ferramentas IA estão configuradas.',\n  'steps.sync.processingTarget': 'Processando destino: {path}',\n  'steps.sync.summary': 'Gerando resumo da sincronização',\n  'info.sync.foundAgents': 'Arquivos de agentes descobertos',\n  'info.sync.foundAgentsDetail': 'Encontrados {count} playbooks de agentes para sincronizar',\n  // Traduções dos comandos de importação\n  'commands.importRules.description': 'Importar regras do Cursor e de assistentes IA para .context/docs',\n  'commands.importRules.options.source': 'Caminhos de origem para procurar regras',\n  'commands.importRules.options.target': 'Diretório de destino para regras importadas',\n  'commands.importRules.options.format': 'Formato de saída: markdown, formatted ou raw',\n  'commands.importRules.options.force': 'Sobrescrever arquivos existentes',\n  'commands.importRules.options.dryRun': 'Pré-visualizar mudanças sem escrever',\n  'commands.importRules.options.verbose': 'Ativar logs detalhados',\n  'commands.importRules.options.autoDetect': 'Auto-detectar regras em locais comuns',\n  'commands.importAgents.description': 'Importar agentes de diretórios de ferramentas IA para .context/agents',\n  'commands.importAgents.options.source': 'Caminhos de origem para procurar agentes',\n  'commands.importAgents.options.target': 'Diretório de destino para agentes importados',\n  'commands.importAgents.options.force': 'Sobrescrever arquivos existentes',\n  'commands.importAgents.options.dryRun': 'Pré-visualizar mudanças sem escrever',\n  'commands.importAgents.options.verbose': 'Ativar logs detalhados',\n  'commands.importAgents.options.autoDetect': 'Auto-detectar agentes em locais comuns',\n  'warnings.import.noRulesFound': 'Nenhum arquivo de regras encontrado.',\n  'warnings.import.noAgentsFound': 'Nenhum arquivo de agente encontrado.',\n  'spinner.import.detectingRules': 'Detectando arquivos de regras...',\n  'spinner.import.detectingAgents': 'Detectando arquivos de agentes...',\n  'spinner.import.scanningPaths': 'Escaneando caminhos de origem...',\n  'spinner.import.importing': 'Importando para {path}...',\n  'spinner.import.complete': 'Importados {count} arquivos para {path}',\n  'info.import.foundRules': 'Arquivos de regras descobertos',\n  'info.import.foundRulesDetail': 'Encontrados {count} arquivos de regras para importar',\n  'info.import.foundAgents': 'Arquivos de agentes descobertos',\n  'info.import.foundAgentsDetail': 'Encontrados {count} arquivos de agentes para importar',\n  'success.import.completed': 'Importação concluída com sucesso.',\n  'errors.import.failed': 'Falha ao importar arquivos',\n  'prompts.mode.select': 'Como você quer configurar esta operação?',\n  'prompts.mode.quick': 'Início Rápido (usar configurações automáticas)',\n  'prompts.mode.advanced': 'Avançado (personalizar todas as opções)',\n  'prompts.summary.title': 'Resumo da Configuração',\n  'prompts.summary.proceed': 'Prosseguir com esta configuração?',\n  'prompts.llm.autoDetected': 'Detectado automaticamente do ambiente',\n  'prompts.quick.confirmRepo': 'Usar este repositório?',\n  'prompts.sync.quickTarget': 'Para onde sincronizar os agentes?',\n  'prompts.sync.quickTarget.common': 'Claude Code + GitHub Copilot (recomendado)',\n  'prompts.sync.quickTarget.claude': 'Somente Claude Code',\n  'prompts.sync.quickTarget.all': 'Todas as ferramentas IA',\n  'prompts.sync.quickTarget.custom': 'Caminho personalizado...',\n  // PREVC Workflow translations\n  'prompts.main.choice.workflow': 'Workflow PREVC',\n  'prompts.workflow.noWorkflowFound': 'Nenhum workflow PREVC encontrado. Criar um novo?',\n  'prompts.workflow.projectName': 'Nome do projeto/feature:',\n  'prompts.workflow.projectNameRequired': 'Nome é obrigatório',\n  'prompts.workflow.description': 'Descrição breve (para detecção de escala):',\n  'prompts.workflow.scale': 'Escala do projeto:',\n  'prompts.workflow.scale.auto': 'Auto-detectar (baseado na descrição)',\n  'prompts.workflow.scale.quick': 'Rápido - Bug fixes, ajustes (~5 min)',\n  'prompts.workflow.scale.small': 'Pequeno - Features simples (~15 min)',\n  'prompts.workflow.scale.medium': 'Médio - Features regulares (~30 min)',\n  'prompts.workflow.scale.large': 'Grande - Sistemas complexos, compliance (~1+ hora)',\n  'prompts.workflow.action': 'Ação do workflow:',\n  'prompts.workflow.action.advance': 'Avançar para próxima fase',\n  'prompts.workflow.action.refresh': 'Atualizar status',\n  'prompts.workflow.action.back': 'Voltar ao menu principal',\n  'success.workflow.initialized': 'Workflow PREVC inicializado: {name}',\n  'success.workflow.advanced': 'Avançou para fase: {phase} - {phaseName}',\n  'success.workflow.completed': 'Workflow concluído.',\n  'errors.workflow.initFailed': 'Falha ao inicializar workflow',\n  'errors.workflow.advanceFailed': 'Falha ao avançar workflow',\n  'errors.workflow.notFound': 'Nenhum workflow encontrado. Execute \"workflow init <nome>\" primeiro.',\n  'info.workflow.scale': 'Escala',\n  'info.workflow.currentPhase': 'Fase Atual',\n  'prompts.workflow.workflowComplete': 'O workflow atual está concluído. O que você gostaria de fazer?',\n  'prompts.workflow.action.newWorkflow': 'Iniciar um novo workflow',\n  'prompts.workflow.action.viewStatus': 'Ver status do workflow concluído',\n  'prompts.workflow.confirmNewWorkflow': 'Isso substituirá o workflow atual. Continuar?',\n  // Traduções do comando start\n  'commands.start.description': 'Inicio inteligente: inicializa o contexto e inicia o workflow em um comando',\n  'commands.start.arguments.featureName': 'Nome opcional da feature para iniciar um workflow',\n  'commands.start.options.template': 'Template de workflow: hotfix, feature, mvp ou auto',\n  'commands.start.options.skipFill': 'Pular preenchimento de documentação com IA',\n  'commands.start.options.skipWorkflow': 'Pular inicialização do workflow',\n  'commands.previewSplash.description': 'Renderizar uma previa da splash screen interativa',\n  'commands.previewSplash.options.title': 'Sobrescrever o titulo da splash',\n  'commands.previewSplash.options.directory': 'Diretorio exibido na splash',\n  'spinner.start.detectingStack': 'Detectando stack de tecnologia...',\n  'spinner.start.stackDetected': 'Stack detectada: {stack}',\n  'spinner.start.initializing': 'Inicializando contexto do projeto...',\n  'spinner.start.initialized': 'Contexto do projeto inicializado',\n  'spinner.start.initFailed': 'Falha ao inicializar contexto',\n  'spinner.start.filling': 'Preenchendo documentação com IA...',\n  'spinner.start.filled': 'Documentação preenchida',\n  'spinner.start.fillFailed': 'A atualização da documentação agora é feita por ferramentas de IA com MCP',\n  'errors.start.workflowFailed': 'Falha ao iniciar workflow',\n  'success.start.complete': 'Projeto pronto! {details}',\n  // Traduções do comando mcp:install\n  'commands.mcpInstall.description': 'Instalar configuração do servidor MCP para ferramentas de IA (Claude Code, Cursor, etc.)',\n  'commands.mcpInstall.options.global': 'Instalar globalmente no diretório home (padrão)',\n  'commands.mcpInstall.options.local': 'Instalar localmente no diretório do projeto',\n  'commands.mcpInstall.options.dryRun': 'Visualizar mudanças sem escrever',\n  'commands.mcpInstall.options.verbose': 'Habilitar saída detalhada',\n  'commands.mcpInstall.selectTool': 'Selecione a ferramenta para configurar o MCP:',\n  'commands.mcpInstall.allDetected': 'Todas as ferramentas detectadas',\n  'labels.detected': 'detectado',\n  'info.mcp.wouldInstall': 'Instalaria MCP para {tool} em {path}',\n  'info.mcp.installed': 'MCP configurado para {tool} em {path}',\n  'info.mcp.alreadyConfigured': '{tool} já tem MCP configurado',\n  'info.mcp.restartTools': 'Reinicie suas ferramentas de IA para aplicar a configuração do MCP.',\n  'success.mcp.installed': 'MCP instalado para {count} ferramenta(s)',\n  'warnings.mcp.noToolsDetected': 'Nenhuma ferramenta de IA suportada detectada. Especifique uma ferramenta manualmente.',\n  'errors.mcp.unsupportedTool': 'Ferramenta não suportada: {tool}. Suportadas: {supported}',\n  'errors.mcp.installFailed': 'Falha ao instalar MCP para {tool}',\n  // Nomes de exibição das ferramentas MCP\n  'commands.mcpInstall.selectTool.claudeDesktop': 'Claude Desktop',\n  'commands.mcpInstall.selectTool.vscode': 'VS Code (GitHub Copilot)',\n  'commands.mcpInstall.selectTool.roo': 'Roo Code',\n  'commands.mcpInstall.selectTool.warp': 'Warp Terminal',\n  'commands.mcpInstall.selectTool.amazonq': 'Amazon Q Developer CLI',\n  'commands.mcpInstall.selectTool.geminiCli': 'Gemini CLI',\n  'commands.mcpInstall.selectTool.kiro': 'Kiro',\n  'commands.mcpInstall.selectTool.zed': 'Zed Editor',\n  'commands.mcpInstall.selectTool.jetbrains': 'JetBrains IDEs',\n  // Traduções do comando export\n  'commands.export.description': 'Exportar regras de .context para diretórios de ferramentas IA',\n  'commands.export.options.source': 'Diretório fonte das regras',\n  'commands.export.options.targets': 'Caminhos de destino para exportar',\n  'commands.export.options.preset': 'Preset de exportação: cursor, claude, github, windsurf, cline, aider, codex ou all',\n  'commands.export.options.force': 'Sobrescrever arquivos existentes',\n  'commands.export.options.dryRun': 'Pré-visualizar mudanças sem escrever',\n  'spinner.export.exporting': 'Exportando para {target}...',\n  'spinner.export.exported': 'Exportado para {target}',\n  'spinner.export.skipped': 'Pulado {target} (já existe)',\n  'spinner.export.failed': 'Falha ao exportar para {target}',\n  'spinner.export.dryRun': 'Exportaria para {target}',\n  'errors.export.noTargets': 'Nenhum destino de exportação especificado. Use --preset ou --targets.',\n  'errors.export.noRules': 'Nenhuma regra encontrada no diretório fonte.',\n  'success.export.completed': 'Regras exportadas para {count} destino(s)',\n  // Traduções do comando report\n  'commands.report.description': 'Gerar relatório de progresso do workflow',\n  'commands.report.options.format': 'Formato de saída: console, markdown ou json',\n  'commands.report.options.output': 'Caminho do arquivo de saída (para markdown/json)',\n  'commands.report.options.includeStack': 'Incluir análise da stack de tecnologia',\n  'errors.report.noWorkflow': 'Nenhum workflow encontrado. Execute \"workflow init\" primeiro.',\n  'success.report.saved': 'Relatório salvo em {path}',\n  // Traduções do dashboard visual\n  'dashboard.title': 'Dashboard do Workflow',\n  'dashboard.phase.completed': 'Concluído',\n  'dashboard.phase.inProgress': 'Em Andamento',\n  'dashboard.phase.pending': 'Pendente',\n  'dashboard.phase.skipped': 'Pulado',\n  // Traduções do auto-advance\n  'autoAdvance.detected': 'Detectados {count} output(s) para fase {phase}',\n  'autoAdvance.suggest': 'Pronto para avançar para próxima fase?',\n  // Traduções de templates de workflow\n  'prompts.workflow.template': 'Selecione o template de workflow:',\n  'prompts.workflow.template.hotfix': 'Hotfix - Correção rápida de bug (E → V)',\n  'prompts.workflow.template.feature': 'Feature - Desenvolvimento regular (P → R → E → V)',\n  'prompts.workflow.template.mvp': 'MVP - Workflow completo de produto (P → R → E → V → C)',\n  'prompts.workflow.template.auto': 'Auto-detectar baseado na descrição',\n  // Traduções do comando skill\n  'commands.skill.description': 'Gerenciar skills (expertise sob demanda para agentes IA)',\n  'commands.skill.init.description': 'Inicializar diretório de skills com skills built-in',\n  'commands.skill.fill.description': 'Preencher skills com conteúdo específico do projeto gerado por IA',\n  'commands.skill.list.description': 'Listar skills disponíveis',\n  'commands.skill.export.description': 'Exportar skills para diretórios de ferramentas IA (Claude, Gemini, Codex)',\n  'commands.skill.create.description': 'Criar uma nova skill personalizada',\n  'prompts.main.choice.skills': 'Gerenciar skills',\n  'prompts.skill.action': 'O que você gostaria de fazer com skills?',\n  'prompts.skill.action.init': 'Inicializar skills built-in',\n  'prompts.skill.action.fill': 'Preencher skills com IA',\n  'prompts.skill.action.list': 'Listar skills disponíveis',\n  'prompts.skill.action.export': 'Exportar skills para ferramentas IA',\n  'prompts.skill.action.create': 'Criar uma skill personalizada',\n  'prompts.skill.action.back': 'Voltar ao menu principal',\n  'prompts.skill.name': 'Nome da skill:',\n  'prompts.skill.description': 'Descrição da skill:',\n  'prompts.skill.phases': 'Fases PREVC (separadas por vírgula: P,R,E,V,C):',\n  'prompts.skill.exportPreset': 'Exportar para quais ferramentas IA?',\n  'prompts.skill.exportPreset.all': 'Todas as ferramentas (Claude, Gemini, Codex)',\n  'prompts.skill.exportPreset.claude': 'Somente Claude Code',\n  'prompts.skill.exportPreset.gemini': 'Somente Gemini CLI',\n  'prompts.skill.exportPreset.codex': 'Somente Codex CLI',\n  'success.skill.initialized': 'Skills inicializadas em {path}',\n  'success.skill.created': 'Skill criada: {name}',\n  'success.skill.exported': 'Exportadas {count} skills para {targets} destinos',\n  'success.skill.filled': '{count} skills preenchidas com conteúdo específico do projeto',\n  'errors.skill.initFailed': 'Falha ao inicializar skills',\n  'errors.skill.listFailed': 'Falha ao listar skills',\n  'errors.skill.exportFailed': 'Falha ao exportar skills',\n  'errors.skill.createFailed': 'Falha ao criar skill',\n  'errors.skill.fillFailed': 'Falha ao preencher skills',\n  'errors.skill.missingScaffold': 'Diretório de skills não encontrado. Execute \"skill init\" primeiro.',\n  'steps.skill.discover': 'Descobrindo skills',\n  'steps.skill.loadContext': 'Carregando contexto de docs e agents',\n  'steps.skill.fillSkills': 'Preenchendo {count} skills com {model}',\n  'steps.skill.summary': 'Resumo',\n  'spinner.skill.discovering': 'Descobrindo skills scaffolded...',\n  'spinner.skill.discovered': 'Encontradas {count} skills para preencher',\n  'spinner.skill.loadingContext': 'Carregando contexto de docs e agents...',\n  'spinner.skill.contextLoaded': 'Carregado docs ({docsSize} chars) e agents ({agentsSize} chars)',\n  'spinner.skill.noContent': 'Nenhum conteúdo gerado para {skill}',\n  'spinner.skill.filled': '{skill} preenchida',\n  'spinner.skill.failed': 'Falha ao preencher {skill}',\n  'warnings.skill.noTargets': 'Nenhuma skill encontrada para preencher. Execute \"skill init\" primeiro.',\n  'info.skill.generated': 'Geradas',\n  'info.skill.skipped': 'Puladas (já existem)',\n  'info.skill.path': 'Caminho',\n  // Quick Sync translations\n  'prompts.main.choice.quickSync': 'Sincronização Rápida',\n  'prompts.quickSync.selectComponents': 'Selecione os componentes para sincronizar:',\n  'prompts.quickSync.components.agents': 'Agents',\n  'prompts.quickSync.components.skills': 'Skills',\n  'prompts.quickSync.components.docs': 'Documentação',\n  'prompts.quickSync.noComponentsSelected': 'Nenhum componente selecionado',\n  'prompts.quickSync.selectAgentTargets': 'Selecione os destinos para agents:',\n  'prompts.quickSync.selectSkillTargets': 'Selecione os destinos para skills:',\n  'prompts.quickSync.selectDocTargets': 'Selecione os destinos para docs:',\n  'prompts.quickSync.syncing.agents': 'Sincronizando agents...',\n  'prompts.quickSync.syncing.skills': 'Exportando skills...',\n  'prompts.quickSync.syncing.rules': 'Exportando regras...',\n  'prompts.quickSync.syncing.docs': 'Verificando docs...',\n  'prompts.quickSync.docsOutdated': '{days} dias atrás',\n  'prompts.quickSync.updateDocs': 'Atualizar docs também?',\n  'success.quickSync.complete': 'Sincronização completa',\n  // Reverse Sync translations\n  'prompts.main.choice.reverseSync': 'Sincronização Reversa (importar de ferramentas de IA)',\n  'prompts.reverseSync.detecting': 'Detectando configurações de ferramentas IA...',\n  'prompts.reverseSync.detected': 'Ferramentas IA Detectadas:',\n  'prompts.reverseSync.noFilesFound': 'Nenhum arquivo de configuração de ferramentas IA encontrado',\n  'prompts.reverseSync.noComponentsSelected': 'Nenhum componente selecionado para importação',\n  'prompts.reverseSync.selectComponents': 'Selecione os componentes para importar:',\n  'prompts.reverseSync.mergeStrategy': 'Como devemos lidar com conflitos?',\n  'prompts.reverseSync.strategy.skip': 'Pular arquivos existentes',\n  'prompts.reverseSync.strategy.overwrite': 'Sobrescrever arquivos existentes',\n  'prompts.reverseSync.strategy.merge': 'Mesclar conteúdo (anexar)',\n  'prompts.reverseSync.strategy.rename': 'Renomear novos arquivos',\n  'success.reverseSync.complete': '{count} arquivos importados',\n  'errors.reverseSync.failed': 'Reverse sync falhou',\n  // Manage submenus\n  'prompts.main.choice.manageSkills': 'Gerenciar Skills',\n  'prompts.main.choice.manageAgents': 'Gerenciar Agents',\n  'prompts.main.choice.settings': 'Configurações (idioma)',\n  'prompts.main.choice.mcpInstall': 'Instalar/Configurar MCP',\n  'prompts.main.choice.startWorkflow': 'Iniciar Workflow',\n  'prompts.main.choice.createPlan': 'Criar Plano',\n  'prompts.main.choice.quickSetup': 'Setup Rápido (cria contexto do codebase)',\n  'prompts.main.choice.enhanceWithAI': 'Aprimorar contexto com IA',\n  // Agents submenu\n  'prompts.agents.action': 'Agents:',\n  'prompts.agents.choice.sync': 'Sincronizar para targets',\n  'prompts.agents.choice.create': 'Criar agent personalizado',\n  'prompts.agents.choice.list': 'Listar agents',\n  'prompts.agents.choice.back': 'Voltar',\n  // Create custom agent\n  'prompts.agent.name': 'Nome do agent:',\n  'prompts.agent.description': 'Descrição do agent:',\n  'prompts.agent.role': 'Role/especialidade do agent:',\n  'success.agent.created': 'Agent criado: {name}',\n  'spinner.agent.creating': 'Criando agent com IA...',\n  'spinner.agent.created': 'Agent criado',\n  // Settings submenu\n  'prompts.settings.action': 'Configurações:',\n  'prompts.settings.choice.language': 'Mudar idioma',\n  'prompts.settings.choice.back': 'Voltar',\n  // Mode selection (interactive entry)\n  'prompts.modeSelect.select': 'Como você gostaria de usar o @dotcontext/cli?',\n  'prompts.modeSelect.choice.mcp': 'Configurar MCP para minha ferramenta de IA (recomendado — sem API key)',\n  'prompts.modeSelect.choice.cli': 'Usar a CLI interativa para workflow, sincronização, importações e configuração MCP',\n  'prompts.modeSelect.choice.exit': 'Sair',\n  // More options submenu\n  'prompts.more.action': 'Mais opções:',\n  'prompts.more.choice.back': 'Voltar',\n  // Quick Sync mode\n  'prompts.quickSync.mode': 'Sincronizar tudo (agents, skills, docs) para destinos comuns?',\n  'prompts.quickSync.mode.syncAll': 'Sim, sincronizar tudo',\n  'prompts.quickSync.mode.customize': 'Personalizar destinos...',\n  'prompts.quickSync.mode.cancel': 'Cancelar',\n  // Compact status\n  'status.compact': '✓ Contexto pronto ({docs} docs, {agents} agents, {skills} skills)',\n  'status.outdated': '⚠ Contexto desatualizado ({days} dias) - atualize via MCP',\n  'status.new': 'Nenhum contexto encontrado. Use MCP ou reverse sync.',\n  'status.unfilled': 'Contexto encontrado, {count} arquivos ainda precisam de conteúdo',\n  'status.detected.project': 'Detectado: projeto {languages}',\n  // Hit Enter / Press Enter\n  'prompts.pressEnter': 'Pressione Enter para continuar...',\n  // Environment variable loading\n  'prompts.env.loadEnv': 'Carregar variáveis de ambiente do arquivo .env?',\n  // Config summary labels\n  'configSummary.config': 'Configuração:',\n  'configSummary.options': 'Opções:',\n  'configSummary.repo': 'repo',\n  'configSummary.provider': 'provedor',\n  'configSummary.yes': 'Sim',\n  'configSummary.no': 'Não',\n  // API key validation\n  'warnings.apiKey.empty': 'A chave de API está vazia. Chamadas ao LLM provavelmente falharão.',\n  'warnings.apiKey.formatMismatch': 'A chave de API não começa com o prefixo esperado \"{prefix}\" para {provider}. Pode funcionar se estiver correta.',\n  // Back/cancel options\n  'prompts.analysis.back': 'Voltar (cancelar opções de análise)',\n  'prompts.llm.back': 'Voltar (cancelar configuração do LLM)'\n};\n\nconst dictionaries: Record<Locale, TranslationDictionary> = {\n  en: englishMessages,\n  'pt-BR': portugueseMessages\n};\n\nexport type TranslateParams = Record<string, string | number | undefined>;\n\nexport type TranslateFn = (key: TranslationKey, params?: TranslateParams) => string;\n\nexport function createTranslator(locale: Locale): TranslateFn {\n  const normalized = normalizeLocale(locale);\n  return (key: TranslationKey, params?: TranslateParams) => {\n    const dictionary = dictionaries[normalized] || dictionaries[DEFAULT_LOCALE];\n    const fallback = dictionaries[DEFAULT_LOCALE];\n    const template = dictionary[key] ?? fallback[key];\n    return fillTemplate(template, params);\n  };\n}\n\nexport function normalizeLocale(locale: string): Locale {\n  return resolveLocaleCandidate(locale) || DEFAULT_LOCALE;\n}\n\nexport function detectLocale(\n  argv: string[],\n  envLocale?: string | null,\n  systemLocaleCandidates: Array<string | null | undefined> = []\n): Locale {\n  const candidates = [\n    extractLocaleFromArgs(argv),\n    envLocale,\n    ...systemLocaleCandidates\n  ];\n\n  for (const candidate of candidates) {\n    const resolved = resolveLocaleCandidate(candidate);\n    if (resolved) {\n      return resolved;\n    }\n  }\n\n  return DEFAULT_LOCALE;\n}\n\nfunction extractLocaleFromArgs(argv: string[]): string | undefined {\n  for (let index = 0; index < argv.length; index += 1) {\n    const segment = argv[index];\n    if (segment === '--lang' || segment === '--language' || segment === '-l') {\n      return argv[index + 1];\n    }\n    if (segment.startsWith('--lang=')) {\n      return segment.split('=')[1];\n    }\n    if (segment.startsWith('--language=')) {\n      return segment.split('=')[1];\n    }\n  }\n  return undefined;\n}\n\nfunction fillTemplate(template: string, params?: TranslateParams): string {\n  if (!params) {\n    return template;\n  }\n  return template.replace(/\\{(\\w+)\\}/g, (_match, key) => {\n    const value = params[key];\n    return value === undefined ? '' : String(value);\n  });\n}\n\nexport function isSupportedLocale(locale: string): boolean {\n  return SUPPORTED_LOCALES.some(candidate => candidate.toLowerCase() === locale.toLowerCase());\n}\n\nfunction resolveLocaleCandidate(locale?: string | null): Locale | undefined {\n  if (!locale) {\n    return undefined;\n  }\n\n  const trimmed = locale.trim();\n  if (!trimmed) {\n    return undefined;\n  }\n\n  const directMatch = SUPPORTED_LOCALES.find(candidate => candidate.toLowerCase() === trimmed.toLowerCase());\n  if (directMatch) {\n    return directMatch;\n  }\n\n  const normalized = trimmed.replace(/_/g, '-').split('.')[0].toLowerCase();\n  if (normalized === 'c' || normalized === 'posix') {\n    return undefined;\n  }\n\n  if (normalized === 'pt' || normalized.startsWith('pt-')) {\n    return 'pt-BR';\n  }\n\n  if (normalized === 'en' || normalized.startsWith('en-')) {\n    return 'en';\n  }\n\n  return undefined;\n}\n"
  },
  {
    "path": "src/utils/pathSecurity.test.ts",
    "content": "import * as path from 'path';\nimport { PathValidator, SecurityError } from './pathSecurity';\n\ndescribe('PathValidator', () => {\n    // Use a normalized test root that works cross-platform\n    const workspaceRoot = path.resolve('/workspace/project');\n    let validator: PathValidator;\n\n    beforeEach(() => {\n        validator = new PathValidator(workspaceRoot);\n    });\n\n    describe('validatePath', () => {\n        it('should allow paths within workspace', () => {\n            const result = validator.validatePath('src/index.ts');\n            expect(result).toBe(path.join(workspaceRoot, 'src', 'index.ts'));\n        });\n\n        it('should allow nested paths within workspace', () => {\n            const result = validator.validatePath('src/services/fill/fillService.ts');\n            expect(result).toBe(path.join(workspaceRoot, 'src', 'services', 'fill', 'fillService.ts'));\n        });\n\n        it('should allow workspace root itself', () => {\n            const result = validator.validatePath('.');\n            expect(result).toBe(workspaceRoot);\n        });\n\n        it('should block ../ directory traversal', () => {\n            expect(() => validator.validatePath('../../../etc/passwd'))\n                .toThrow(SecurityError);\n        });\n\n        it('should block ../ traversal to parent directories', () => {\n            expect(() => validator.validatePath('../sibling-project/secrets.env'))\n                .toThrow(SecurityError);\n        });\n\n        it('should block paths that escape via intermediate ../', () => {\n            expect(() => validator.validatePath('src/../../outside/file.txt'))\n                .toThrow(SecurityError);\n        });\n\n        it('should block null byte injection', () => {\n            expect(() => validator.validatePath('src/file.ts\\0.jpg'))\n                .toThrow(SecurityError);\n            expect(() => validator.validatePath('src/file.ts\\0.jpg'))\n                .toThrow('null bytes');\n        });\n\n        it('should block URL-encoded traversal (%2e%2e%2f)', () => {\n            expect(() => validator.validatePath('%2e%2e%2fetc/passwd'))\n                .toThrow(SecurityError);\n            expect(() => validator.validatePath('%2e%2e%2fetc/passwd'))\n                .toThrow('URL-encoded');\n        });\n\n        it('should block double URL-encoded traversal (%252e)', () => {\n            expect(() => validator.validatePath('%252e%252e/etc/passwd'))\n                .toThrow(SecurityError);\n        });\n\n        it('should block URL-encoded backslash traversal (%2e%2e%5c)', () => {\n            expect(() => validator.validatePath('%2e%2e%5cwindows%5csystem32'))\n                .toThrow(SecurityError);\n        });\n    });\n\n    describe('isWithinBoundary', () => {\n        it('should return true for path within boundary', () => {\n            const filePath = path.join(workspaceRoot, 'src', 'file.ts');\n            expect(validator.isWithinBoundary(filePath)).toBe(true);\n        });\n\n        it('should return true for workspace root itself', () => {\n            expect(validator.isWithinBoundary(workspaceRoot)).toBe(true);\n        });\n\n        it('should return false for path outside boundary', () => {\n            const outsidePath = path.resolve('/other/directory/file.ts');\n            expect(validator.isWithinBoundary(outsidePath)).toBe(false);\n        });\n\n        it('should return false for parent directory', () => {\n            const parentPath = path.resolve(workspaceRoot, '..');\n            expect(validator.isWithinBoundary(parentPath)).toBe(false);\n        });\n    });\n\n    describe('safeResolve', () => {\n        it('should return resolved path for valid paths', () => {\n            const result = validator.safeResolve('src/index.ts');\n            expect(result).toBe(path.join(workspaceRoot, 'src', 'index.ts'));\n        });\n\n        it('should return null for traversal attempts', () => {\n            expect(validator.safeResolve('../../../etc/passwd')).toBeNull();\n        });\n\n        it('should return null for null byte injection', () => {\n            expect(validator.safeResolve('file\\0.ts')).toBeNull();\n        });\n    });\n\n    describe('SecurityError', () => {\n        it('should include attempted path and workspace root', () => {\n            try {\n                validator.validatePath('../../../etc/passwd');\n                fail('Should have thrown SecurityError');\n            } catch (error) {\n                expect(error).toBeInstanceOf(SecurityError);\n                const securityError = error as SecurityError;\n                expect(securityError.attemptedPath).toBe('../../../etc/passwd');\n                expect(securityError.workspaceRoot).toBe(workspaceRoot);\n                expect(securityError.name).toBe('SecurityError');\n            }\n        });\n    });\n});\n"
  },
  {
    "path": "src/utils/pathSecurity.ts",
    "content": "/**\n * Path Security Utilities\n *\n * Validates and sanitizes file paths to prevent path traversal attacks\n * in the MCP server. All file access through MCP tools should be validated\n * against the workspace root to prevent unauthorized access.\n *\n * Security patterns detected:\n * - `../` directory traversal\n * - URL-encoded traversal (%2e%2e%2f)\n * - Null byte injection (\\0)\n * - Absolute paths outside workspace\n * - Windows drive letter paths outside workspace\n */\n\nimport * as path from 'path';\n\n/**\n * Error thrown when a path traversal or unauthorized access attempt is detected.\n */\nexport class SecurityError extends Error {\n    public readonly attemptedPath: string;\n    public readonly workspaceRoot: string;\n\n    constructor(message: string, attemptedPath: string, workspaceRoot: string) {\n        super(message);\n        this.name = 'SecurityError';\n        this.attemptedPath = attemptedPath;\n        this.workspaceRoot = workspaceRoot;\n    }\n}\n\n/**\n * Validates that file paths remain within a trusted workspace boundary.\n *\n * Used by the MCP server to ensure AI agents cannot access files\n * outside the project directory via path traversal or other techniques.\n */\nexport class PathValidator {\n    private readonly normalizedRoot: string;\n\n    constructor(private readonly workspaceRoot: string) {\n        this.normalizedRoot = path.resolve(workspaceRoot);\n    }\n\n    /**\n     * Validate that a path resolves within the workspace boundary.\n     *\n     * @param inputPath - The path to validate (may be relative or absolute)\n     * @returns The resolved, validated absolute path\n     * @throws SecurityError if the path resolves outside the workspace\n     */\n    validatePath(inputPath: string): string {\n        // 1. Check for null bytes (common injection technique)\n        if (inputPath.includes('\\0')) {\n            throw new SecurityError(\n                'Path contains null bytes',\n                inputPath,\n                this.normalizedRoot\n            );\n        }\n\n        // 2. Check for URL-encoded traversal sequences\n        if (this.hasUrlEncodedTraversal(inputPath)) {\n            throw new SecurityError(\n                'Path contains URL-encoded traversal sequences',\n                inputPath,\n                this.normalizedRoot\n            );\n        }\n\n        // 3. Resolve the path against workspace root\n        const resolved = path.resolve(this.normalizedRoot, inputPath);\n\n        // 4. Verify the resolved path is within workspace\n        if (!this.isWithinBoundary(resolved)) {\n            throw new SecurityError(\n                `Access denied: path resolves outside workspace boundary`,\n                inputPath,\n                this.normalizedRoot\n            );\n        }\n\n        return resolved;\n    }\n\n    /**\n     * Check if a path is within the workspace boundary.\n     * Does not throw — returns a boolean.\n     */\n    isWithinBoundary(resolvedPath: string): boolean {\n        const normalizedTarget = path.resolve(resolvedPath);\n        return (\n            normalizedTarget === this.normalizedRoot ||\n            normalizedTarget.startsWith(this.normalizedRoot + path.sep)\n        );\n    }\n\n    /**\n     * Safely resolve a path, returning null instead of throwing on violations.\n     */\n    safeResolve(inputPath: string): string | null {\n        try {\n            return this.validatePath(inputPath);\n        } catch {\n            return null;\n        }\n    }\n\n    /**\n     * Check for URL-encoded directory traversal patterns.\n     */\n    private hasUrlEncodedTraversal(input: string): boolean {\n        const patterns = [\n            /%2e%2e[%2f%5c]/i, // ../  URL-encoded\n            /%2e%2e$/i,         // .. at end URL-encoded\n            /%252e/i,           // double-encoded dot\n        ];\n        return patterns.some(pattern => pattern.test(input));\n    }\n}\n"
  },
  {
    "path": "src/utils/processShutdown.ts",
    "content": "export interface ShutdownTarget {\n  stop(): Promise<void>;\n}\n\nexport interface ShutdownController {\n  shutdown: (signal?: NodeJS.Signals) => Promise<void>;\n  dispose: () => void;\n}\n\nexport interface ShutdownControllerOptions {\n  label?: string;\n  onError?: (error: unknown) => void;\n  exit?: (code: number) => never;\n}\n\n/**\n * Register idempotent shutdown handlers for SIGINT/SIGTERM.\n *\n * The returned controller removes the listeners if `dispose()` is called\n * before a signal arrives.\n */\nexport function registerProcessShutdown(\n  target: ShutdownTarget,\n  options: ShutdownControllerOptions = {}\n): ShutdownController {\n  let shuttingDown = false;\n  const signalListeners = new Map<NodeJS.Signals, () => void>();\n  const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];\n\n  const dispose = (): void => {\n    for (const [signal, listener] of signalListeners.entries()) {\n      process.off(signal, listener);\n    }\n    signalListeners.clear();\n  };\n\n  const shutdown = async (signal?: NodeJS.Signals): Promise<void> => {\n    if (shuttingDown) {\n      return;\n    }\n\n    shuttingDown = true;\n    let exitCode = 0;\n\n    try {\n      await target.stop();\n    } catch (error) {\n      exitCode = 1;\n      options.onError?.(error);\n    } finally {\n      dispose();\n      options.exit?.(exitCode);\n    }\n  };\n\n  for (const signal of signals) {\n    const listener = () => {\n      void shutdown(signal);\n    };\n    signalListeners.set(signal, listener);\n    process.once(signal, listener);\n  }\n\n  return { shutdown, dispose };\n}\n"
  },
  {
    "path": "src/utils/prompts/configSummary.ts",
    "content": "import { colors } from '../theme';\nimport type { TranslateFn } from '../i18n';\nimport type { ConfigSummary } from './types';\n\n/**\n * Displays a compact configuration summary before execution\n */\nexport function displayConfigSummary(summary: ConfigSummary, _t: TranslateFn): void {\n  // Line 1: operation | repo | provider (model)\n  const parts: string[] = [];\n  parts.push(summary.operation);\n  if (summary.repoPath) parts.push(`${_t('configSummary.repo')}: ${summary.repoPath}`);\n  if (summary.provider) {\n    const providerInfo = summary.model\n      ? `${summary.provider} (${summary.model})`\n      : summary.provider;\n    parts.push(`${_t('configSummary.provider')}: ${providerInfo}`);\n  }\n  console.log(`  ${colors.secondary(_t('configSummary.config'))} ${parts.join(' | ')}`);\n\n  // Line 2: options (if any)\n  if (summary.options && Object.keys(summary.options).length > 0) {\n    const optParts: string[] = [];\n    for (const [key, value] of Object.entries(summary.options)) {\n      const displayValue =\n        typeof value === 'boolean'\n          ? value ? _t('configSummary.yes') : _t('configSummary.no')\n          : Array.isArray(value)\n            ? value.join(', ')\n            : String(value);\n      optParts.push(`${key}=${displayValue}`);\n    }\n    console.log(`  ${colors.secondary(_t('configSummary.options'))} ${optParts.join(', ')}`);\n  }\n}\n"
  },
  {
    "path": "src/utils/prompts/index.ts",
    "content": "import type { TranslateFn } from '../i18n';\nimport type { InteractiveMode, AnalysisOptions } from './types';\nimport { themedSelect, themedConfirm, themedCheckbox, Separator } from '../themedPrompt';\nimport { colors } from '../theme';\n\n// Re-export types\nexport * from './types';\n\n// Re-export modules\nexport { detectSmartDefaults } from './smartDefaults';\nexport { displayConfigSummary } from './configSummary';\n\n/**\n * Prompts user to choose between quick and advanced mode\n */\nexport async function promptInteractiveMode(t: TranslateFn): Promise<InteractiveMode> {\n  return themedSelect<InteractiveMode>({\n    message: t('prompts.mode.select'),\n    choices: [\n      { name: t('prompts.mode.quick'), value: 'quick' },\n      { name: t('prompts.mode.advanced'), value: 'advanced' }\n    ],\n    default: 'quick'\n  });\n}\n\n/**\n * Prompts for analysis options (semantic, languages, LSP)\n *\n * Returns null if the user chooses to go back/cancel.\n */\nexport async function promptAnalysisOptions(\n  t: TranslateFn,\n  defaults: { languages?: string[]; useLsp?: boolean } = {}\n): Promise<AnalysisOptions | null> {\n  // Use a select instead of confirm so we can offer a back option\n  const semanticChoice = await themedSelect<'yes' | 'no' | '__back__'>({\n    message: t('prompts.fill.semantic'),\n    choices: [\n      { name: 'Yes', value: 'yes' },\n      { name: 'No', value: 'no' },\n      new Separator(),\n      { name: colors.secondary(t('prompts.analysis.back')), value: '__back__' }\n    ],\n    default: 'yes'\n  });\n\n  if (semanticChoice === '__back__') {\n    return null;\n  }\n\n  const useSemantic = semanticChoice === 'yes';\n  let languages: string[] | undefined;\n  let useLsp = false;\n\n  if (useSemantic) {\n    const defaultLanguages = defaults.languages || ['typescript', 'javascript', 'python'];\n    const selectedLanguages = await themedCheckbox<string>({\n      message: t('prompts.fill.languages'),\n      choices: [\n        { name: 'TypeScript', value: 'typescript', checked: defaultLanguages.includes('typescript') },\n        { name: 'JavaScript', value: 'javascript', checked: defaultLanguages.includes('javascript') },\n        { name: 'Python', value: 'python', checked: defaultLanguages.includes('python') }\n      ]\n    });\n    languages = selectedLanguages.length > 0 ? selectedLanguages : undefined;\n\n    useLsp = await themedConfirm({\n      message: t('prompts.fill.useLsp'),\n      default: defaults.useLsp ?? false\n    });\n  }\n\n  const verbose = await themedConfirm({\n    message: t('prompts.common.verbose'),\n    default: false\n  });\n\n  return {\n    semantic: useSemantic,\n    languages,\n    useLsp,\n    verbose\n  };\n}\n\n/**\n * Prompts for confirmation before proceeding\n */\nexport async function promptConfirmProceed(t: TranslateFn): Promise<boolean> {\n  return themedConfirm({\n    message: t('prompts.summary.proceed'),\n    default: true\n  });\n}\n\n/**\n * Prompts user to load environment variables from .env file\n */\nexport async function promptLoadEnv(t: TranslateFn): Promise<boolean> {\n  return themedConfirm({\n    message: t('prompts.env.loadEnv'),\n    default: false\n  });\n}\n"
  },
  {
    "path": "src/utils/prompts/smartDefaults.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\nimport type { SmartDefaults } from './types';\n\n/**\n * Detects smart defaults from environment and project structure\n */\nexport async function detectSmartDefaults(basePath?: string): Promise<SmartDefaults> {\n  const repoPath = basePath || process.cwd();\n  const outputDir = path.resolve(repoPath, '.context');\n\n  // Check if scaffold exists\n  const scaffoldExists = fs.existsSync(outputDir);\n\n  // Detect languages from project files\n  const detectedLanguages = await detectProjectLanguages(repoPath);\n\n  return {\n    repoPath,\n    outputDir,\n    scaffoldExists,\n    detectedLanguages\n  };\n}\n\n/**\n * Detects programming languages used in the project\n */\nasync function detectProjectLanguages(repoPath: string): Promise<string[]> {\n  const languages: string[] = [];\n\n  // Check for TypeScript\n  if (\n    fs.existsSync(path.join(repoPath, 'tsconfig.json')) ||\n    fs.existsSync(path.join(repoPath, 'tsconfig.base.json'))\n  ) {\n    languages.push('typescript');\n  }\n\n  // Check for JavaScript (package.json without TypeScript)\n  if (fs.existsSync(path.join(repoPath, 'package.json')) && !languages.includes('typescript')) {\n    languages.push('javascript');\n  }\n\n  // Check for Python\n  if (\n    fs.existsSync(path.join(repoPath, 'pyproject.toml')) ||\n    fs.existsSync(path.join(repoPath, 'setup.py')) ||\n    fs.existsSync(path.join(repoPath, 'requirements.txt'))\n  ) {\n    languages.push('python');\n  }\n\n  // Default to common languages if none detected\n  if (languages.length === 0) {\n    return ['typescript', 'javascript'];\n  }\n\n  return languages;\n}\n"
  },
  {
    "path": "src/utils/prompts/types.ts",
    "content": "export type InteractiveMode = 'quick' | 'advanced';\n\nexport interface PathsPromptResult {\n  repoPath: string;\n  outputDir: string;\n}\n\nexport interface SmartDefaults {\n  repoPath: string;\n  outputDir: string;\n  scaffoldExists: boolean;\n  detectedLanguages: string[];\n}\n\nexport interface ConfigSummary {\n  operation: 'fill' | 'plan' | 'sync';\n  repoPath?: string;\n  outputDir?: string;\n  provider?: string;\n  model?: string;\n  apiKeySource?: 'env' | 'provided' | 'none';\n  options?: Record<string, string | boolean | string[]>;\n}\n\nexport interface AnalysisOptions {\n  semantic: boolean;\n  languages?: string[];\n  useLsp: boolean;\n  verbose: boolean;\n}\n"
  },
  {
    "path": "src/utils/splashScreen.test.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\n\nimport {\n  formatSplashDirectory,\n  packageNameToDisplayName,\n  renderSplashScreen,\n} from './splashScreen';\n\nfunction stripAnsi(value: string): string {\n  return value.replace(/\\u001B\\[[0-9;]*m/g, '');\n}\n\ndescribe('splashScreen', () => {\n  it('humanizes scoped package names for the splash title', () => {\n    expect(packageNameToDisplayName('@dotcontext/cli')).toBe('Dotcontext');\n  });\n\n  it('shortens home-directory paths with a tilde', () => {\n    const directory = path.join(os.homedir(), 'workspace', 'ai-coders-context');\n    expect(formatSplashDirectory(directory)).toBe('~/workspace/ai-coders-context');\n  });\n\n  it('renders a codex-style splash box with aligned rows', () => {\n    const output = stripAnsi(renderSplashScreen({\n      title: 'AI Coders CLI',\n      version: '0.8.0',\n      lines: [\n        { label: 'directory', value: '~/workspace/ai-coders-context' },\n      ],\n    }));\n\n    expect(output).toContain('AI Coders CLI');\n    expect(output).toContain('directory:');\n    expect(output).toContain('~/workspace/ai-coders-context');\n    expect(output).toContain('╭');\n    expect(output).toContain('╰');\n  });\n});\n"
  },
  {
    "path": "src/utils/splashScreen.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport boxen from 'boxen';\n\nimport { colors } from './theme';\n\nexport interface SplashScreenLine {\n  label: string;\n  value: string;\n  note?: string;\n}\n\nexport interface SplashScreenOptions {\n  title: string;\n  version: string;\n  lines: SplashScreenLine[];\n}\n\nexport function renderSplashScreen(options: SplashScreenOptions): string {\n  const title = `${colors.accent('>_')} ${colors.primaryBold(options.title)} ${colors.secondary(`(v${options.version})`)}`;\n  const labelWidth = options.lines.reduce((max, line) => Math.max(max, `${line.label}:`.length), 0);\n\n  const content = [\n    title,\n    '',\n    ...options.lines.map(line => formatLine(line, labelWidth)),\n  ].join('\\n');\n\n  return boxen(content, {\n    borderStyle: 'round',\n    borderColor: 'gray',\n    padding: {\n      top: 0,\n      bottom: 0,\n      left: 1,\n      right: 1,\n    },\n  });\n}\n\nexport function packageNameToDisplayName(packageName: string): string {\n  if (packageName === '@dotcontext/cli') {\n    return 'Dotcontext';\n  }\n\n  return packageName\n    .replace(/^@/, '')\n    .replace(/\\//g, ' ')\n    .split(/[-_\\s]+/)\n    .filter(Boolean)\n    .map(part => part.toLowerCase() === 'ai'\n      ? 'AI'\n      : part.charAt(0).toUpperCase() + part.slice(1))\n    .join(' ');\n}\n\nexport function formatSplashDirectory(directory: string, maxLength = 48): string {\n  const homeDirectory = os.homedir();\n  const resolved = path.resolve(directory);\n  const withTilde = resolved === homeDirectory\n    ? '~'\n    : resolved.startsWith(`${homeDirectory}${path.sep}`)\n      ? `~${path.sep}${path.relative(homeDirectory, resolved)}`\n      : resolved;\n\n  return truncateMiddle(withTilde, maxLength);\n}\n\nfunction formatLine(line: SplashScreenLine, labelWidth: number): string {\n  const label = colors.secondary(`${line.label}:`.padEnd(labelWidth + 2));\n  const value = colors.primary(line.value);\n\n  if (!line.note) {\n    return `${label}${value}`;\n  }\n\n  return `${label}${value}  ${colors.secondaryDim(line.note)}`;\n}\n\nfunction truncateMiddle(value: string, maxLength: number): string {\n  if (value.length <= maxLength || maxLength < 8) {\n    return value;\n  }\n\n  const visibleChars = maxLength - 3;\n  const prefixLength = Math.ceil(visibleChars / 2);\n  const suffixLength = Math.floor(visibleChars / 2);\n\n  return `${value.slice(0, prefixLength)}...${value.slice(value.length - suffixLength)}`;\n}\n"
  },
  {
    "path": "src/utils/theme.ts",
    "content": "import chalk from 'chalk';\n\n/**\n * Professional CLI theme with two-tone color palette\n * Inspired by Vercel CLI and Stripe CLI\n */\n\n// Color palette - subtle two-tone system\nexport const colors = {\n  // Primary: White/Bright for emphasis\n  primary: chalk.white,\n  primaryBold: chalk.bold.white,\n\n  // Secondary: Gray/Dim for supporting text\n  secondary: chalk.gray,\n  secondaryDim: chalk.dim,\n\n  // Accent: Used sparingly for key highlights\n  accent: chalk.cyan,\n\n  // Status colors\n  success: chalk.green,\n  error: chalk.red,\n  warning: chalk.yellow,\n} as const;\n\n// Unicode symbols replacing emojis\nexport const symbols = {\n  // Status indicators\n  success: '\\u2713',      // ✓ checkmark\n  error: '\\u2717',        // ✗ X mark\n  warning: '!',           // exclamation\n  info: '\\u2022',         // • bullet\n\n  // Navigation/Structure\n  pointer: '\\u25B8',      // ▸ right-pointing triangle\n  bullet: '\\u2022',       // • bullet\n  dash: '\\u2500',         // ─ horizontal line\n  pipe: '\\u2502',         // │ vertical line\n  corner: '\\u2514',       // └ corner\n\n  // Progress/Activity\n  pending: '\\u25CB',      // ○ empty circle\n  active: '\\u25CF',       // ● filled circle\n\n  // Agent types\n  documentation: '\\u25A0', // ■ filled square\n  playbook: '\\u25A1',      // □ empty square\n  plan: '\\u25C6',          // ◆ diamond\n  fill: '\\u2726',          // ✦ four-pointed star\n  skill: '\\u2605',         // ★ star\n  tool: '\\u25B6',          // ▶ play triangle\n} as const;\n\n// Typography helper functions\nexport const typography = {\n  // Headers\n  header: (text: string): string => colors.primaryBold(text),\n  subheader: (text: string): string => colors.secondary(text),\n\n  // Labels and values (with consistent width support)\n  label: (text: string): string => colors.secondary(`${text}:`),\n  value: (text: string): string => colors.primary(text),\n\n  // Labeled line with padding\n  labeledValue: (label: string, value: string, labelWidth = 12): string => {\n    const paddedLabel = label.padEnd(labelWidth);\n    return `  ${colors.secondary(paddedLabel)} ${colors.primary(value)}`;\n  },\n\n  // Status messages\n  success: (text: string): string =>\n    `${colors.success(symbols.success)} ${colors.primary(text)}`,\n\n  error: (text: string): string =>\n    `${colors.error(symbols.error)} ${colors.primary(text)}`,\n\n  warning: (text: string): string =>\n    `${colors.warning(symbols.warning)} ${colors.primary(text)}`,\n\n  info: (text: string): string =>\n    `${colors.accent(symbols.info)} ${colors.primary(text)}`,\n\n  // Separator line\n  separator: (width = 50): string => colors.secondaryDim(symbols.dash.repeat(width)),\n\n  // Tree structure\n  treePipe: (): string => colors.secondary(`  ${symbols.pipe} `),\n  treeCorner: (): string => colors.secondary(`  ${symbols.corner} `),\n  treeItem: (text: string): string => `${typography.treePipe()}${colors.secondary(text)}`,\n  treeLastItem: (text: string): string => `${typography.treeCorner()}${text}`,\n} as const;\n\n// Inquirer prompt theme configuration\n// Compatible with @inquirer/core Theme interface\nexport const promptTheme = {\n  prefix: {\n    idle: colors.accent(symbols.pointer),\n    done: colors.success(symbols.success),\n  },\n  style: {\n    answer: (text: string) => colors.primary(text),\n    message: (text: string, _status: string) => colors.primary(text),\n    error: (text: string) => colors.error(text),\n    defaultAnswer: (text: string) => colors.secondaryDim(text),\n    help: (text: string) => colors.secondaryDim(text),\n    highlight: (text: string) => colors.accent(text),\n    key: (text: string) => colors.accent(text),\n  },\n  icon: {\n    cursor: colors.accent(symbols.pointer),\n  },\n};\n"
  },
  {
    "path": "src/utils/themedPrompt.ts",
    "content": "/**\n * Themed wrappers around @inquirer/prompts that apply the project's promptTheme.\n * The legacy inquirer.prompt() API does not support themes, so these wrappers\n * use @inquirer/prompts directly with the theme pre-applied.\n */\nimport { select, confirm, input, password, checkbox, Separator } from '@inquirer/prompts';\nimport { promptTheme } from './theme';\n\ntype Choice<V> = {\n  value: V;\n  name?: string;\n  description?: string;\n  short?: string;\n  disabled?: boolean | string;\n  type?: never;\n};\n\nexport async function themedSelect<V>(config: {\n  message: string;\n  choices: ReadonlyArray<Separator | Choice<V>>;\n  default?: unknown;\n  pageSize?: number;\n  loop?: boolean;\n}): Promise<V> {\n  return select<V>({\n    ...config,\n    theme: promptTheme,\n  });\n}\n\nexport async function themedConfirm(config: {\n  message: string;\n  default?: boolean;\n}): Promise<boolean> {\n  return confirm({\n    ...config,\n    theme: promptTheme,\n  });\n}\n\nexport async function themedInput(config: {\n  message: string;\n  default?: string;\n  validate?: (value: string) => boolean | string | Promise<boolean | string>;\n}): Promise<string> {\n  return input({\n    ...config,\n    theme: promptTheme,\n  });\n}\n\nexport async function themedPassword(config: {\n  message: string;\n  mask?: string;\n}): Promise<string> {\n  return password({\n    ...config,\n    theme: promptTheme,\n  });\n}\n\nexport async function themedCheckbox<V>(config: {\n  message: string;\n  choices: ReadonlyArray<{ name?: string; value: V; checked?: boolean; disabled?: boolean | string } | Separator>;\n  pageSize?: number;\n}): Promise<V[]> {\n  return checkbox<V>({\n    ...config,\n    theme: promptTheme,\n  });\n}\n\nexport { Separator };\n"
  },
  {
    "path": "src/utils/versionChecker.test.ts",
    "content": "import { checkForUpdates } from './versionChecker';\nimport type { TranslateFn, TranslateParams } from './i18n';\n\ndescribe('versionChecker', () => {\n  const ui = {\n    displayInfo: jest.fn<void, [string, string]>()\n  };\n\n  const t: TranslateFn = (key: string, params?: TranslateParams) => {\n    const context = (params ?? {}) as Record<string, string | number | undefined>;\n    const latest = context.latest ?? '';\n    const current = context.current ?? '';\n    const command = context.command ?? '';\n    switch (key) {\n      case 'info.update.available.title':\n        return 'Update available';\n      case 'info.update.available.detail':\n        return `Latest: ${latest}, current: ${current}, update with: ${command}`;\n      default:\n        return key;\n    }\n  };\n\n  beforeEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it('informs the user when a newer version exists', async () => {\n    await checkForUpdates({\n      packageName: '@scope/test',\n      currentVersion: '0.1.0',\n      ui,\n      t,\n      fetcher: async (pkgName: string, timeoutMs: number) => {\n        expect(pkgName).toBe('@scope/test');\n        expect(timeoutMs).toBeGreaterThan(0);\n        return '0.2.0';\n      },\n      updateCommand: 'npm i -g @scope/test',\n      force: true\n    });\n\n    expect(ui.displayInfo).toHaveBeenCalledWith(\n      'Update available',\n      'Latest: 0.2.0, current: 0.1.0, update with: npm i -g @scope/test'\n    );\n  });\n\n  it('does not notify when already on latest version', async () => {\n    await checkForUpdates({\n      packageName: 'pkg',\n      currentVersion: '1.0.0',\n      ui,\n      t,\n      fetcher: async (_packageName, _timeout) => '1.0.0',\n      force: true\n    });\n\n    expect(ui.displayInfo).not.toHaveBeenCalled();\n  });\n\n  it('silently ignores registry failures', async () => {\n    await expect(\n      checkForUpdates({\n        packageName: 'pkg',\n        currentVersion: '1.0.0',\n        ui,\n        t,\n        fetcher: async (_packageName, _timeout) => {\n          throw new Error('network');\n        },\n        force: true\n      })\n    ).resolves.toBeUndefined();\n\n    expect(ui.displayInfo).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/utils/versionChecker.ts",
    "content": "import { gt } from 'semver';\n\nimport type { CLIInterface } from './cliUI';\nimport type { TranslateFn } from './i18n';\n\ninterface VersionCheckOptions {\n  packageName: string;\n  currentVersion: string;\n  ui: Pick<CLIInterface, 'displayInfo'>;\n  t: TranslateFn;\n  registryTimeoutMs?: number;\n  fetcher?: (packageName: string, timeoutMs: number) => Promise<string>;\n  updateCommand?: string;\n  force?: boolean;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nconst DISABLE_ENV_FLAGS = ['DOTCONTEXT_DISABLE_UPDATE_CHECK', 'NO_UPDATE_NOTIFIER'];\n\nasync function fetchLatestVersion(packageName: string, timeoutMs: number): Promise<string> {\n  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;\n  const controller = new AbortController();\n  const timeout = setTimeout(() => {\n    controller.abort();\n  }, timeoutMs);\n\n  try {\n    const response = await fetch(url, { signal: controller.signal });\n    if (!response.ok) {\n      throw new Error(`HTTP ${response.status}`);\n    }\n\n    const data = (await response.json()) as { version?: string };\n    if (!data?.version) {\n      throw new Error('missing-version');\n    }\n\n    return data.version;\n  } finally {\n    clearTimeout(timeout);\n  }\n}\n\nexport async function checkForUpdates(options: VersionCheckOptions): Promise<void> {\n  const {\n    packageName,\n    currentVersion,\n    ui,\n    t,\n    registryTimeoutMs = DEFAULT_TIMEOUT_MS,\n    fetcher,\n    updateCommand,\n    force = false\n  } = options;\n\n  if (!force) {\n    if (process.env.NODE_ENV === 'test' || process.env.CI === 'true') {\n      return;\n    }\n\n    for (const envFlag of DISABLE_ENV_FLAGS) {\n      const value = process.env[envFlag];\n      if (typeof value === 'string' && value.toLowerCase() !== 'false') {\n        return;\n      }\n    }\n  }\n\n  try {\n    const latestVersion = await (fetcher\n      ? fetcher(packageName, registryTimeoutMs)\n      : fetchLatestVersion(packageName, registryTimeoutMs));\n\n    if (gt(latestVersion, currentVersion)) {\n      const command = updateCommand ?? `npm install -g ${packageName}`;\n      ui.displayInfo(\n        t('info.update.available.title'),\n        t('info.update.available.detail', {\n          latest: latestVersion,\n          current: currentVersion,\n          command\n        })\n      );\n    }\n  } catch (error) {\n    // Swallow errors silently; update hints should never block the CLI.\n  }\n}\n"
  },
  {
    "path": "src/version.ts",
    "content": "/**\n * Centralized version and package constants\n *\n * Single source of truth for version information.\n * Uses fs.readFileSync to avoid JSON import compatibility issues\n * across different Node.js versions and module formats.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst packageJsonPath = path.resolve(__dirname, '..', 'package.json');\nconst pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n\nexport const VERSION: string = pkg.version;\nexport const PACKAGE_NAME: string = pkg.name;\n"
  },
  {
    "path": "src/workflow/agents/agentRegistry.ts",
    "content": "/**\n * Agent Registry\n *\n * Centralized registry for built-in and custom agents.\n * Single source of truth for agent types and discovery.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\n\n/**\n * Built-in agent types available in the system\n */\nexport const BUILT_IN_AGENTS = [\n  'code-reviewer',\n  'bug-fixer',\n  'feature-developer',\n  'refactoring-specialist',\n  'test-writer',\n  'documentation-writer',\n  'performance-optimizer',\n  'security-auditor',\n  'backend-specialist',\n  'frontend-specialist',\n  'architect-specialist',\n  'devops-specialist',\n  'database-specialist',\n  'mobile-specialist',\n] as const;\n\nexport type BuiltInAgentType = (typeof BUILT_IN_AGENTS)[number];\n\n/**\n * Agent metadata extracted from playbook\n */\nexport interface AgentMetadata {\n  type: string;\n  path: string;\n  isCustom: boolean;\n  exists: boolean;\n  title?: string;\n  description?: string;\n}\n\n/**\n * Agent discovery result\n */\nexport interface DiscoveredAgents {\n  all: AgentMetadata[];\n  builtIn: AgentMetadata[];\n  custom: AgentMetadata[];\n}\n\n/**\n * Check if an agent type is built-in\n */\nexport function isBuiltInAgent(agentType: string): agentType is BuiltInAgentType {\n  return BUILT_IN_AGENTS.includes(agentType as BuiltInAgentType);\n}\n\n/**\n * Agent Registry class\n *\n * Handles discovery and metadata extraction for both\n * built-in and custom agents.\n */\nexport class AgentRegistry {\n  private agentsPath: string;\n  private cache: Map<string, AgentMetadata> = new Map();\n\n  constructor(repoPath: string) {\n    this.agentsPath = path.join(repoPath, '.context', 'agents');\n  }\n\n  /**\n   * Discover all available agents (built-in + custom)\n   */\n  async discoverAll(): Promise<DiscoveredAgents> {\n    const builtIn = await this.discoverBuiltIn();\n    const custom = await this.discoverCustom();\n\n    return {\n      all: [...builtIn, ...custom],\n      builtIn,\n      custom,\n    };\n  }\n\n  /**\n   * Discover built-in agents\n   */\n  async discoverBuiltIn(): Promise<AgentMetadata[]> {\n    const agents: AgentMetadata[] = [];\n\n    for (const agentType of BUILT_IN_AGENTS) {\n      const metadata = await this.getAgentMetadata(agentType);\n      agents.push(metadata);\n    }\n\n    return agents;\n  }\n\n  /**\n   * Discover custom agents from .context/agents/\n   */\n  async discoverCustom(): Promise<AgentMetadata[]> {\n    const agents: AgentMetadata[] = [];\n\n    if (!await fs.pathExists(this.agentsPath)) {\n      return agents;\n    }\n\n    const files = await fs.readdir(this.agentsPath);\n\n    for (const file of files) {\n      if (!file.endsWith('.md') || file === 'README.md') {\n        continue;\n      }\n\n      const agentType = file.replace('.md', '');\n\n      // Skip if it's a built-in agent\n      if (isBuiltInAgent(agentType)) {\n        continue;\n      }\n\n      const metadata = await this.getAgentMetadata(agentType);\n      agents.push(metadata);\n    }\n\n    return agents;\n  }\n\n  /**\n   * Get metadata for a specific agent\n   */\n  async getAgentMetadata(agentType: string): Promise<AgentMetadata> {\n    // Check cache first\n    const cached = this.cache.get(agentType);\n    if (cached) {\n      return cached;\n    }\n\n    const agentPath = path.join(this.agentsPath, `${agentType}.md`);\n    const exists = await fs.pathExists(agentPath);\n    const isCustom = !isBuiltInAgent(agentType);\n\n    const metadata: AgentMetadata = {\n      type: agentType,\n      path: `agents/${agentType}.md`,\n      isCustom,\n      exists,\n    };\n\n    // Extract title and description if file exists\n    if (exists) {\n      const extracted = await this.extractMetadataFromFile(agentPath);\n      metadata.title = extracted.title;\n      metadata.description = extracted.description;\n    }\n\n    // Cache the result\n    this.cache.set(agentType, metadata);\n\n    return metadata;\n  }\n\n  /**\n   * Check if an agent exists\n   */\n  async agentExists(agentType: string): Promise<boolean> {\n    const agentPath = path.join(this.agentsPath, `${agentType}.md`);\n    return fs.pathExists(agentPath);\n  }\n\n  /**\n   * Get agent playbook content\n   */\n  async getPlaybookContent(agentType: string): Promise<string | null> {\n    const agentPath = path.join(this.agentsPath, `${agentType}.md`);\n\n    if (!await fs.pathExists(agentPath)) {\n      return null;\n    }\n\n    return fs.readFile(agentPath, 'utf-8');\n  }\n\n  /**\n   * Clear the metadata cache\n   */\n  clearCache(): void {\n    this.cache.clear();\n  }\n\n  /**\n   * Extract metadata from agent playbook file\n   */\n  private async extractMetadataFromFile(\n    filePath: string\n  ): Promise<{ title?: string; description?: string }> {\n    try {\n      const content = await fs.readFile(filePath, 'utf-8');\n      const titleMatch = content.match(/^#\\s+(.+)$/m);\n      const descMatch = content.match(/^>\\s*(.+)$/m);\n\n      return {\n        title: titleMatch?.[1],\n        description: descMatch?.[1],\n      };\n    } catch {\n      return {};\n    }\n  }\n}\n\n/**\n * Create an AgentRegistry instance\n */\nexport function createAgentRegistry(repoPath: string): AgentRegistry {\n  return new AgentRegistry(repoPath);\n}\n"
  },
  {
    "path": "src/workflow/agents/index.ts",
    "content": "/**\n * Agent Registry Module\n *\n * Centralized management of built-in and custom agents.\n */\n\nexport {\n  BUILT_IN_AGENTS,\n  BuiltInAgentType,\n  AgentMetadata,\n  DiscoveredAgents,\n  isBuiltInAgent,\n  AgentRegistry,\n  createAgentRegistry,\n} from './agentRegistry';\n"
  },
  {
    "path": "src/workflow/collaboration.ts",
    "content": "/**\n * PREVC Collaboration Mode\n *\n * Enables multi-role collaboration for complex decisions,\n * brainstorming, or cross-functional analysis.\n */\n\nimport {\n  PrevcRole,\n  Contribution,\n  CollaborationStatus,\n  CollaborationSynthesis,\n} from './types';\nimport { ROLE_DISPLAY_NAMES } from './roles';\nimport { getRoleConfig } from './prevcConfig';\n\n/**\n * Generate a unique session ID\n */\nfunction generateSessionId(): string {\n  return `collab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Collaboration Session\n *\n * Manages a multi-role collaboration session.\n */\nexport class CollaborationSession {\n  private id: string;\n  private topic: string;\n  private activeRoles: PrevcRole[];\n  private contributions: Contribution[];\n  private status: 'active' | 'synthesizing' | 'concluded';\n  private startedAt: Date;\n\n  constructor(topic: string, participants?: PrevcRole[]) {\n    this.id = generateSessionId();\n    this.topic = topic;\n    this.activeRoles = participants || [];\n    this.contributions = [];\n    this.status = 'active';\n    this.startedAt = new Date();\n  }\n\n  /**\n   * Get the session ID\n   */\n  getId(): string {\n    return this.id;\n  }\n\n  /**\n   * Start a new collaboration session\n   */\n  async start(\n    topic: string,\n    participants?: PrevcRole[]\n  ): Promise<CollaborationStatus> {\n    this.topic = topic;\n    this.activeRoles = participants || (await this.selectRelevantRoles(topic));\n    this.status = 'active';\n    this.startedAt = new Date();\n    this.contributions = [];\n\n    return this.getStatus();\n  }\n\n  /**\n   * Get the current session status\n   */\n  getStatus(): CollaborationStatus {\n    return {\n      id: this.id,\n      topic: this.topic,\n      participants: this.activeRoles,\n      started: this.startedAt,\n      status: this.status,\n    };\n  }\n\n  /**\n   * Add a contribution from a role\n   */\n  contribute(role: PrevcRole, message: string): void {\n    if (!this.activeRoles.includes(role)) {\n      throw new Error(\n        `Role ${role} is not a participant in this session`\n      );\n    }\n\n    if (this.status !== 'active') {\n      throw new Error('Cannot contribute to a session that is not active');\n    }\n\n    this.contributions.push({\n      role,\n      message,\n      timestamp: new Date(),\n    });\n  }\n\n  /**\n   * Get all contributions\n   */\n  getContributions(): Contribution[] {\n    return [...this.contributions];\n  }\n\n  /**\n   * Get contributions by role\n   */\n  getContributionsByRole(role: PrevcRole): Contribution[] {\n    return this.contributions.filter((c) => c.role === role);\n  }\n\n  /**\n   * Synthesize the discussion into decisions and recommendations\n   */\n  async synthesize(): Promise<CollaborationSynthesis> {\n    this.status = 'synthesizing';\n\n    const decisions = await this.extractDecisions();\n    const recommendations = await this.generateRecommendations();\n\n    this.status = 'concluded';\n\n    return {\n      topic: this.topic,\n      participants: this.activeRoles,\n      contributions: this.contributions,\n      decisions,\n      recommendations,\n    };\n  }\n\n  /**\n   * Extract decisions from contributions\n   */\n  private async extractDecisions(): Promise<string[]> {\n    const decisions: string[] = [];\n\n    // Look for decision keywords in contributions\n    const decisionKeywords = [\n      'decidimos',\n      'decided',\n      'conclusão',\n      'conclusion',\n      'definimos',\n      'defined',\n      'escolhemos',\n      'chose',\n      'optamos',\n      'opted',\n    ];\n\n    for (const contribution of this.contributions) {\n      const lowerMessage = contribution.message.toLowerCase();\n      for (const keyword of decisionKeywords) {\n        if (lowerMessage.includes(keyword)) {\n          decisions.push(\n            `[${ROLE_DISPLAY_NAMES[contribution.role]}]: ${contribution.message}`\n          );\n          break;\n        }\n      }\n    }\n\n    return decisions;\n  }\n\n  /**\n   * Generate recommendations based on contributions\n   */\n  private async generateRecommendations(): Promise<string[]> {\n    const recommendations: string[] = [];\n\n    // Look for recommendation keywords\n    const recommendationKeywords = [\n      'recomendo',\n      'recommend',\n      'sugiro',\n      'suggest',\n      'devemos',\n      'should',\n      'melhor',\n      'better',\n      'ideal',\n    ];\n\n    for (const contribution of this.contributions) {\n      const lowerMessage = contribution.message.toLowerCase();\n      for (const keyword of recommendationKeywords) {\n        if (lowerMessage.includes(keyword)) {\n          recommendations.push(\n            `[${ROLE_DISPLAY_NAMES[contribution.role]}]: ${contribution.message}`\n          );\n          break;\n        }\n      }\n    }\n\n    // Add role-specific recommendations based on their expertise\n    for (const role of this.activeRoles) {\n      const config = getRoleConfig(role);\n      if (config && config.responsibilities.length > 0) {\n        recommendations.push(\n          `Consider ${ROLE_DISPLAY_NAMES[role]}'s expertise in: ${config.responsibilities[0]}`\n        );\n      }\n    }\n\n    return recommendations;\n  }\n\n  /**\n   * Select relevant roles based on the topic\n   */\n  private async selectRelevantRoles(topic: string): Promise<PrevcRole[]> {\n    const lowerTopic = topic.toLowerCase();\n\n    // Architecture/Design topics\n    if (\n      lowerTopic.includes('arquitetura') ||\n      lowerTopic.includes('architecture') ||\n      lowerTopic.includes('design')\n    ) {\n      return ['architect', 'developer', 'designer'];\n    }\n\n    // Testing/Quality topics\n    if (\n      lowerTopic.includes('teste') ||\n      lowerTopic.includes('test') ||\n      lowerTopic.includes('qualidade') ||\n      lowerTopic.includes('quality')\n    ) {\n      return ['qa', 'reviewer', 'developer'];\n    }\n\n    // Planning/Requirements topics\n    if (\n      lowerTopic.includes('requisito') ||\n      lowerTopic.includes('requirement') ||\n      lowerTopic.includes('planejamento') ||\n      lowerTopic.includes('planning')\n    ) {\n      return ['planner', 'architect', 'designer'];\n    }\n\n    // Documentation topics\n    if (\n      lowerTopic.includes('documentação') ||\n      lowerTopic.includes('documentation') ||\n      lowerTopic.includes('docs')\n    ) {\n      return ['documenter', 'developer', 'planner'];\n    }\n\n    // Security topics\n    if (\n      lowerTopic.includes('segurança') ||\n      lowerTopic.includes('security')\n    ) {\n      return ['qa', 'architect', 'reviewer'];\n    }\n\n    // Performance topics\n    if (\n      lowerTopic.includes('performance') ||\n      lowerTopic.includes('desempenho')\n    ) {\n      return ['qa', 'developer', 'architect'];\n    }\n\n    // Default: core team\n    return ['planner', 'architect', 'developer'];\n  }\n\n  /**\n   * Get session duration in minutes\n   */\n  getDurationMinutes(): number {\n    const now = new Date();\n    const diffMs = now.getTime() - this.startedAt.getTime();\n    return Math.round(diffMs / 60000);\n  }\n\n  /**\n   * Get participant names for display\n   */\n  getParticipantNames(): string[] {\n    return this.activeRoles.map((role) => ROLE_DISPLAY_NAMES[role]);\n  }\n\n  /**\n   * Check if a role is a participant\n   */\n  isParticipant(role: PrevcRole): boolean {\n    return this.activeRoles.includes(role);\n  }\n\n  /**\n   * Add a participant to the session\n   */\n  addParticipant(role: PrevcRole): void {\n    if (!this.activeRoles.includes(role)) {\n      this.activeRoles.push(role);\n    }\n  }\n\n  /**\n   * Remove a participant from the session\n   */\n  removeParticipant(role: PrevcRole): void {\n    this.activeRoles = this.activeRoles.filter((r) => r !== role);\n  }\n}\n\n/**\n * Collaboration Manager\n *\n * Manages multiple collaboration sessions.\n */\nexport class CollaborationManager {\n  private sessions: Map<string, CollaborationSession> = new Map();\n\n  /**\n   * Create a new collaboration session\n   */\n  createSession(topic: string, participants?: PrevcRole[]): CollaborationSession {\n    const session = new CollaborationSession(topic, participants);\n    this.sessions.set(session.getStatus().id, session);\n    return session;\n  }\n\n  /**\n   * Get a session by ID\n   */\n  getSession(id: string): CollaborationSession | undefined {\n    return this.sessions.get(id);\n  }\n\n  /**\n   * Get all active sessions\n   */\n  getActiveSessions(): CollaborationSession[] {\n    return Array.from(this.sessions.values()).filter(\n      (s) => s.getStatus().status === 'active'\n    );\n  }\n\n  /**\n   * End a session\n   */\n  async endSession(id: string): Promise<CollaborationSynthesis | null> {\n    const session = this.sessions.get(id);\n    if (!session) {\n      return null;\n    }\n\n    const synthesis = await session.synthesize();\n    return synthesis;\n  }\n\n  /**\n   * Clear all concluded sessions\n   */\n  clearConcludedSessions(): void {\n    for (const [id, session] of this.sessions) {\n      if (session.getStatus().status === 'concluded') {\n        this.sessions.delete(id);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/workflow/errors.ts",
    "content": "/**\n * PREVC Workflow Errors\n *\n * Custom error types for workflow gate violations and other workflow errors.\n */\n\nimport { PrevcPhase, GateType } from './types';\n\n/**\n * Base workflow error\n */\nexport class WorkflowError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'WorkflowError';\n  }\n}\n\n/**\n * Error thrown when a workflow gate blocks phase advancement\n */\nexport class WorkflowGateError extends WorkflowError {\n  /** The phase transition being blocked */\n  readonly transition: { from: PrevcPhase; to: PrevcPhase };\n  /** The gate that blocked the transition */\n  readonly gate: GateType;\n  /** Hint for resolving the error */\n  readonly hint: string;\n\n  constructor(options: {\n    message: string;\n    transition: { from: PrevcPhase; to: PrevcPhase };\n    gate: GateType;\n    hint: string;\n  }) {\n    super(options.message);\n    this.name = 'WorkflowGateError';\n    this.transition = options.transition;\n    this.gate = options.gate;\n    this.hint = options.hint;\n  }\n}\n\n/**\n * Error thrown when trying to approve a plan that doesn't exist\n */\nexport class NoPlanToApproveError extends WorkflowError {\n  constructor(message = 'No plan is linked to approve. Link a plan first using linkPlan.') {\n    super(message);\n    this.name = 'NoPlanToApproveError';\n  }\n}\n\n/**\n * Error thrown when no workflow exists\n */\nexport class NoWorkflowError extends WorkflowError {\n  constructor(message = 'No workflow found. Run workflowInit first.') {\n    super(message);\n    this.name = 'NoWorkflowError';\n  }\n}\n"
  },
  {
    "path": "src/workflow/gates/gateChecker.test.ts",
    "content": "import {\n  WorkflowGateChecker,\n  getDefaultSettings,\n  GateCheckResult,\n} from './gateChecker';\nimport { PrevcStatus, ProjectScale, WorkflowSettings } from '../types';\n\n// Helper to create a minimal valid PrevcStatus\nfunction createMockStatus(overrides: Partial<PrevcStatus> = {}): PrevcStatus {\n  const defaultStatus: PrevcStatus = {\n    project: {\n      name: 'test-project',\n      scale: ProjectScale.MEDIUM,\n      started: new Date().toISOString(),\n      current_phase: 'P',\n    },\n    phases: {\n      P: { status: 'in_progress' },\n      R: { status: 'pending' },\n      E: { status: 'pending' },\n      V: { status: 'pending' },\n      C: { status: 'pending' },\n    },\n    agents: {},\n    roles: {},\n  };\n\n  return {\n    ...defaultStatus,\n    ...overrides,\n    project: { ...defaultStatus.project, ...overrides.project },\n    phases: { ...defaultStatus.phases, ...overrides.phases },\n    agents: { ...defaultStatus.agents, ...overrides.agents },\n    roles: { ...defaultStatus.roles, ...overrides.roles },\n  } as PrevcStatus;\n}\n\ndescribe('WorkflowGateChecker', () => {\n  let checker: WorkflowGateChecker;\n\n  beforeEach(() => {\n    checker = new WorkflowGateChecker();\n  });\n\n  describe('getDefaultSettings', () => {\n    it('should return autonomous mode for QUICK scale', () => {\n      const settings = getDefaultSettings(ProjectScale.QUICK);\n      expect(settings.autonomous_mode).toBe(true);\n      expect(settings.require_plan).toBe(false);\n      expect(settings.require_approval).toBe(false);\n    });\n\n    it('should require plan but not approval for SMALL scale', () => {\n      const settings = getDefaultSettings(ProjectScale.SMALL);\n      expect(settings.autonomous_mode).toBe(false);\n      expect(settings.require_plan).toBe(true);\n      expect(settings.require_approval).toBe(false);\n    });\n\n    it('should require both plan and approval for MEDIUM scale', () => {\n      const settings = getDefaultSettings(ProjectScale.MEDIUM);\n      expect(settings.autonomous_mode).toBe(false);\n      expect(settings.require_plan).toBe(true);\n      expect(settings.require_approval).toBe(true);\n    });\n\n    it('should require both plan and approval for LARGE scale', () => {\n      const settings = getDefaultSettings(ProjectScale.LARGE);\n      expect(settings.autonomous_mode).toBe(false);\n      expect(settings.require_plan).toBe(true);\n      expect(settings.require_approval).toBe(true);\n    });\n\n    it('should require both plan and approval for LARGE scale', () => {\n      const settings = getDefaultSettings(ProjectScale.LARGE);\n      expect(settings.autonomous_mode).toBe(false);\n      expect(settings.require_plan).toBe(true);\n      expect(settings.require_approval).toBe(true);\n    });\n\n    it('should map legacy ENTERPRISE string to LARGE scale', () => {\n      const settings = getDefaultSettings('ENTERPRISE');\n      expect(settings.autonomous_mode).toBe(false);\n      expect(settings.require_plan).toBe(true);\n      expect(settings.require_approval).toBe(true);\n    });\n  });\n\n  describe('checkGates', () => {\n    describe('autonomous mode', () => {\n      it('should pass all gates when autonomous mode is enabled', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'P',\n            settings: {\n              autonomous_mode: true,\n              require_plan: true,\n              require_approval: true,\n            },\n          },\n        });\n\n        const result = checker.checkGates(status);\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.plan_required.required).toBe(false);\n        expect(result.gates.approval_required.required).toBe(false);\n      });\n    });\n\n    describe('P → R transition', () => {\n      it('should block P → R when no plan is linked', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'P',\n          },\n        });\n\n        const result = checker.checkGates(status, 'R');\n        expect(result.canAdvance).toBe(false);\n        expect(result.gates.plan_required.required).toBe(true);\n        expect(result.gates.plan_required.passed).toBe(false);\n        expect(result.blockingGate).toBe('plan_required');\n        expect(result.hint).toContain('linkPlan');\n      });\n\n      it('should allow P → R when plan is linked via project.plan', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'P',\n            plan: 'my-plan',\n          },\n        });\n\n        const result = checker.checkGates(status, 'R');\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.plan_required.passed).toBe(true);\n      });\n\n      it('should allow P → R when approval.plan_created is true', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'P',\n          },\n          approval: {\n            plan_created: true,\n            plan_approved: false,\n          },\n        });\n\n        const result = checker.checkGates(status, 'R');\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.plan_required.passed).toBe(true);\n      });\n\n      it('should skip plan gate for QUICK scale', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.QUICK,\n            started: new Date().toISOString(),\n            current_phase: 'P',\n          },\n        });\n\n        const result = checker.checkGates(status, 'R');\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.plan_required.required).toBe(false);\n      });\n    });\n\n    describe('R → E transition', () => {\n      it('should block R → E when plan is not approved', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'R',\n          },\n          approval: {\n            plan_created: true,\n            plan_approved: false,\n          },\n        });\n\n        const result = checker.checkGates(status, 'E');\n        expect(result.canAdvance).toBe(false);\n        expect(result.gates.approval_required.required).toBe(true);\n        expect(result.gates.approval_required.passed).toBe(false);\n        expect(result.blockingGate).toBe('approval_required');\n        expect(result.hint).toContain('workflowApprovePlan');\n      });\n\n      it('should allow R → E when plan is approved', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'R',\n          },\n          approval: {\n            plan_created: true,\n            plan_approved: true,\n          },\n        });\n\n        const result = checker.checkGates(status, 'E');\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.approval_required.passed).toBe(true);\n      });\n\n      it('should skip approval gate for SMALL scale', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.SMALL,\n            started: new Date().toISOString(),\n            current_phase: 'R',\n          },\n        });\n\n        const result = checker.checkGates(status, 'E');\n        expect(result.canAdvance).toBe(true);\n        expect(result.gates.approval_required.required).toBe(false);\n      });\n    });\n\n    describe('other transitions', () => {\n      it('should allow E → V without any gates', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'E',\n          },\n        });\n\n        const result = checker.checkGates(status, 'V');\n        expect(result.canAdvance).toBe(true);\n      });\n\n      it('should allow V → C without any gates', () => {\n        const status = createMockStatus({\n          project: {\n            name: 'test',\n            scale: ProjectScale.MEDIUM,\n            started: new Date().toISOString(),\n            current_phase: 'V',\n          },\n        });\n\n        const result = checker.checkGates(status, 'C');\n        expect(result.canAdvance).toBe(true);\n      });\n    });\n  });\n\n  describe('enforceGates', () => {\n    it('should not throw when gates pass', () => {\n      const status = createMockStatus({\n        project: {\n          name: 'test',\n          scale: ProjectScale.MEDIUM,\n          started: new Date().toISOString(),\n          current_phase: 'P',\n          plan: 'my-plan',\n        },\n      });\n\n      expect(() => {\n        checker.enforceGates(status, { nextPhase: 'R' });\n      }).not.toThrow();\n    });\n\n    it('should throw WorkflowGateError when gates fail', () => {\n      const status = createMockStatus({\n        project: {\n          name: 'test',\n          scale: ProjectScale.MEDIUM,\n          started: new Date().toISOString(),\n          current_phase: 'P',\n        },\n      });\n\n      expect(() => {\n        checker.enforceGates(status, { nextPhase: 'R' });\n      }).toThrow();\n    });\n\n    it('should not throw when force is true', () => {\n      const status = createMockStatus({\n        project: {\n          name: 'test',\n          scale: ProjectScale.MEDIUM,\n          started: new Date().toISOString(),\n          current_phase: 'P',\n        },\n      });\n\n      expect(() => {\n        checker.enforceGates(status, { force: true, nextPhase: 'R' });\n      }).not.toThrow();\n    });\n  });\n});\n"
  },
  {
    "path": "src/workflow/gates/gateChecker.ts",
    "content": "/**\n * PREVC Workflow Gate Checker\n *\n * Enforces plan creation and approval gates during phase transitions.\n */\n\nimport {\n  PrevcStatus,\n  PrevcPhase,\n  WorkflowSettings,\n  ProjectScale,\n  GateType,\n} from '../types';\nimport { WorkflowGateError } from '../errors';\n\n/**\n * Status of an individual gate\n */\nexport interface GateStatus {\n  /** Whether the gate check passed */\n  passed: boolean;\n  /** Whether the gate is required for this transition */\n  required: boolean;\n}\n\n/**\n * Result of a gate check\n */\nexport interface GateCheckResult {\n  /** Whether the workflow can advance */\n  canAdvance: boolean;\n  /** Gate statuses */\n  gates: Record<GateType, GateStatus>;\n  /** Reason if advancement is blocked */\n  blockingReason?: string;\n  /** Hint for resolving the blocking gate */\n  hint?: string;\n  /** The blocking gate type */\n  blockingGate?: GateType;\n}\n\n/**\n * Get default workflow settings based on project scale\n */\nexport function getDefaultSettings(scale: ProjectScale | string): WorkflowSettings {\n  const scaleNum = typeof scale === 'number' ? scale : ProjectScale[scale as keyof typeof ProjectScale];\n\n  switch (scaleNum) {\n    case ProjectScale.QUICK:\n      return {\n        autonomous_mode: true,\n        require_plan: false,\n        require_approval: false,\n      };\n    case ProjectScale.SMALL:\n      return {\n        autonomous_mode: false,\n        require_plan: true,\n        require_approval: false,\n      };\n    case ProjectScale.MEDIUM:\n    case ProjectScale.LARGE:\n    default:\n      return {\n        autonomous_mode: false,\n        require_plan: true,\n        require_approval: true,\n      };\n  }\n}\n\n/**\n * PREVC Workflow Gate Checker\n *\n * Validates workflow gates before phase transitions.\n */\nexport class WorkflowGateChecker {\n  /**\n   * Check all gates for the current phase transition\n   */\n  checkGates(\n    status: PrevcStatus,\n    nextPhase?: PrevcPhase\n  ): GateCheckResult {\n    const currentPhase = status.project.current_phase;\n    const targetPhase = nextPhase || this.getNextPhase(currentPhase);\n\n    // Get effective settings (use defaults if not set)\n    const settings = this.getEffectiveSettings(status);\n\n    // If autonomous mode is enabled, all gates pass\n    if (settings.autonomous_mode) {\n      return {\n        canAdvance: true,\n        gates: {\n          plan_required: { passed: true, required: false },\n          approval_required: { passed: true, required: false },\n        },\n      };\n    }\n\n    const result: GateCheckResult = {\n      canAdvance: true,\n      gates: {\n        plan_required: { passed: true, required: false },\n        approval_required: { passed: true, required: false },\n      },\n    };\n\n    // Check P → R transition: requires linked plan\n    if (currentPhase === 'P' && targetPhase === 'R') {\n      if (settings.require_plan) {\n        result.gates.plan_required.required = true;\n        const hasPlan = this.hasLinkedPlan(status);\n        result.gates.plan_required.passed = hasPlan;\n\n        if (!hasPlan) {\n          result.canAdvance = false;\n          result.blockingGate = 'plan_required';\n          result.blockingReason = 'A plan must be linked before advancing from Planning to Review phase.';\n          result.hint = 'Use scaffoldPlan and linkPlan to create and link a plan, or enable autonomous mode.';\n        }\n      }\n    }\n\n    // Check R → E transition: requires plan approval\n    if (currentPhase === 'R' && targetPhase === 'E') {\n      if (settings.require_approval) {\n        result.gates.approval_required.required = true;\n        const isApproved = this.isPlanApproved(status);\n        result.gates.approval_required.passed = isApproved;\n\n        if (!isApproved) {\n          result.canAdvance = false;\n          result.blockingGate = 'approval_required';\n          result.blockingReason = 'The plan must be approved before advancing from Review to Execution phase.';\n          result.hint = 'Use workflowApprovePlan to approve the plan, or enable autonomous mode.';\n        }\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Enforce gates for the current phase transition.\n   *\n   * Validates that all required gates pass before allowing phase advancement.\n   * If a gate check fails and `force` is not set, throws a `WorkflowGateError`.\n   *\n   * @param status - Current workflow status\n   * @param options - Enforcement options\n   * @param options.force - If true, skips gate enforcement entirely\n   * @param options.nextPhase - Override the target phase (defaults to next in sequence)\n   *\n   * @throws {WorkflowGateError} When a gate blocks the transition and force is false.\n   *   The error includes:\n   *   - `gate`: Which gate blocked ('plan_required' | 'approval_required')\n   *   - `transition`: The blocked phase transition { from, to }\n   *   - `hint`: Actionable suggestion for resolving the block\n   *\n   * @example\n   * ```ts\n   * // Normal enforcement - may throw\n   * gateChecker.enforceGates(status);\n   *\n   * // Force bypass - never throws\n   * gateChecker.enforceGates(status, { force: true });\n   * ```\n   */\n  enforceGates(\n    status: PrevcStatus,\n    options: { force?: boolean; nextPhase?: PrevcPhase } = {}\n  ): void {\n    if (options.force) {\n      return;\n    }\n\n    const result = this.checkGates(status, options.nextPhase);\n\n    if (!result.canAdvance && result.blockingGate) {\n      const currentPhase = status.project.current_phase;\n      const targetPhase = options.nextPhase || this.getNextPhase(currentPhase);\n\n      throw new WorkflowGateError({\n        message: result.blockingReason!,\n        transition: { from: currentPhase, to: targetPhase! },\n        gate: result.blockingGate,\n        hint: result.hint!,\n      });\n    }\n  }\n\n  /**\n   * Get effective settings (merge with scale defaults)\n   */\n  private getEffectiveSettings(status: PrevcStatus): WorkflowSettings {\n    const defaults = getDefaultSettings(status.project.scale);\n    const overrides = status.project.settings;\n\n    if (!overrides) {\n      return defaults;\n    }\n\n    return {\n      autonomous_mode: overrides.autonomous_mode ?? defaults.autonomous_mode,\n      require_plan: overrides.require_plan ?? defaults.require_plan,\n      require_approval: overrides.require_approval ?? defaults.require_approval,\n    };\n  }\n\n  /**\n   * Check if a plan is linked to the workflow\n   */\n  private hasLinkedPlan(status: PrevcStatus): boolean {\n    // Check approval tracking\n    if (status.approval?.plan_created) {\n      return true;\n    }\n\n    // Check project metadata\n    if (status.project.plan) {\n      return true;\n    }\n\n    // Check linked plans array\n    if (status.project.plans && status.project.plans.length > 0) {\n      return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Check if the plan is approved\n   */\n  private isPlanApproved(status: PrevcStatus): boolean {\n    return status.approval?.plan_approved === true;\n  }\n\n  /**\n   * Get the next phase in the workflow sequence\n   */\n  private getNextPhase(current: PrevcPhase): PrevcPhase | null {\n    const order: PrevcPhase[] = ['P', 'R', 'E', 'V', 'C'];\n    const idx = order.indexOf(current);\n    return idx >= 0 && idx < order.length - 1 ? order[idx + 1] : null;\n  }\n}\n\n// Export singleton factory\nexport function createGateChecker(): WorkflowGateChecker {\n  return new WorkflowGateChecker();\n}\n"
  },
  {
    "path": "src/workflow/gates/index.ts",
    "content": "/**\n * PREVC Workflow Gates\n *\n * Gate checking for workflow phase transitions.\n */\n\nexport {\n  WorkflowGateChecker,\n  createGateChecker,\n  getDefaultSettings,\n  GateCheckResult,\n  GateStatus,\n} from './gateChecker';\n"
  },
  {
    "path": "src/workflow/index.ts",
    "content": "/**\n * PREVC Workflow System\n *\n * A structured workflow for software development with 5 phases:\n * P - Planejamento (Planning)\n * R - Revisão (Review)\n * E - Execução (Execution)\n * V - Validação (Validation)\n * C - Confirmação (Confirmation)\n */\n\n// Types\nexport * from './types';\n\n// Errors\nexport {\n  WorkflowError,\n  WorkflowGateError,\n  NoPlanToApproveError,\n  NoWorkflowError,\n} from './errors';\n\n// Gates\nexport {\n  WorkflowGateChecker,\n  createGateChecker,\n  getDefaultSettings,\n  GateCheckResult,\n  GateStatus,\n} from './gates';\n\n// Roles\nexport {\n  PREVC_ROLES,\n  ROLE_TO_SPECIALISTS,\n  SPECIALIST_TO_ROLE,\n  ROLE_DISPLAY_NAMES,\n  ROLE_DISPLAY_NAMES_EN,\n  isValidRole,\n  getRoleForSpecialist,\n  getSpecialistsForRole,\n} from './roles';\n\n// Phases\nexport {\n  PREVC_PHASE_ORDER,\n  PREVC_PHASES,\n  PHASE_NAMES_EN,\n  PHASE_NAMES_PT,\n  getPhaseDefinition,\n  getNextPhase,\n  getPreviousPhase,\n  isPhaseOptional,\n  getRolesForPhase,\n  getOutputsForPhase,\n  isValidPhase,\n  getPhaseOrder,\n} from './phases';\n\n// Configuration\nexport {\n  ROLE_CONFIG,\n  getRoleConfig,\n  getRolesForPhase as getRolesForPhaseFromConfig,\n  getOutputsForRole,\n  getResponsibilitiesForRole,\n} from './prevcConfig';\n\n// Scaling\nexport { ProjectScale } from './types';\nexport {\n  SCALE_ROUTES,\n  detectProjectScale,\n  getScaleRoute,\n  getPhasesForScale,\n  getRolesForScale,\n  isPhaseRequiredForScale,\n  getScaleName,\n  getScaleFromName,\n  getEstimatedTime,\n} from './scaling';\n\n// Status Management\nexport { PrevcStatusManager } from './status/statusManager';\nexport {\n  createInitialStatus,\n  createQuickFlowStatus,\n  createSmallProjectStatus,\n  createMediumProjectStatus,\n  createLargeProjectStatus,\n  generateResumeContext,\n} from './status/templates';\n\n// Orchestrator\nexport {\n  PrevcOrchestrator,\n  WorkflowSummary,\n  CompletePhaseOptions,\n  InitWorkflowOptions,\n} from './orchestrator';\n\n// Collaboration\nexport {\n  CollaborationSession,\n  CollaborationManager,\n} from './collaboration';\n\n// Orchestration\nexport {\n  AgentOrchestrator,\n  agentOrchestrator,\n  AgentType,\n  AGENT_TYPES,\n  PHASE_TO_AGENTS,\n  ROLE_TO_AGENTS,\n  DocumentLinker,\n  documentLinker,\n  DocType,\n  DocGuide,\n  DOCUMENT_GUIDES,\n  AGENT_TO_DOCS,\n  PHASE_TO_DOCS,\n  ROLE_TO_DOCS,\n} from './orchestration';\n\n// Plan Integration\nexport {\n  PlanLinker,\n  createPlanLinker,\n  PlanReference,\n  LinkedPlan,\n  PlanPhase,\n  PlanStep,\n  PlanDecision,\n  PlanRisk,\n  WorkflowPlans,\n  AgentLineupEntry,\n  PLAN_PHASE_TO_PREVC,\n} from './plans';\n\n// Agent Registry\nexport {\n  BUILT_IN_AGENTS,\n  BuiltInAgentType,\n  AgentMetadata,\n  DiscoveredAgents,\n  isBuiltInAgent,\n  AgentRegistry,\n  createAgentRegistry,\n} from './agents';\n\n// Skills\nexport {\n  Skill,\n  SkillMetadata,\n  SkillReference,\n  DiscoveredSkills,\n  BUILT_IN_SKILLS,\n  BuiltInSkillType,\n  isBuiltInSkill,\n  SKILL_TO_PHASES,\n  SkillRegistry,\n  createSkillRegistry,\n  getBuiltInSkillTemplates,\n  generateFrontmatter,\n  generatePortableFrontmatter,\n  wrapWithFrontmatter,\n  wrapWithPortableFrontmatter,\n  parseFrontmatter,\n} from './skills';\n"
  },
  {
    "path": "src/workflow/orchestration/agentOrchestrator.ts",
    "content": "/**\n * Agent Orchestrator\n *\n * Maps PREVC workflow roles and phases to specialized agents,\n * providing intelligent agent selection and sequencing.\n */\n\nimport { PrevcPhase, PrevcRole } from '../types';\nimport { ROLE_TO_SPECIALISTS } from '../roles';\n\n/**\n * Available agent types from the existing system\n */\nexport const AGENT_TYPES = [\n  'code-reviewer',\n  'bug-fixer',\n  'feature-developer',\n  'refactoring-specialist',\n  'test-writer',\n  'documentation-writer',\n  'performance-optimizer',\n  'security-auditor',\n  'backend-specialist',\n  'frontend-specialist',\n  'architect-specialist',\n  'devops-specialist',\n  'database-specialist',\n  'mobile-specialist',\n] as const;\n\nexport type AgentType = (typeof AGENT_TYPES)[number];\n\n/**\n * Mapping from PREVC phases to relevant agent types\n */\nexport const PHASE_TO_AGENTS: Record<PrevcPhase, AgentType[]> = {\n  P: ['architect-specialist', 'documentation-writer', 'frontend-specialist'],\n  R: ['architect-specialist', 'code-reviewer', 'security-auditor'],\n  E: [\n    'feature-developer',\n    'backend-specialist',\n    'frontend-specialist',\n    'database-specialist',\n    'mobile-specialist',\n    'bug-fixer',\n  ],\n  V: [\n    'test-writer',\n    'code-reviewer',\n    'security-auditor',\n    'performance-optimizer',\n  ],\n  C: ['documentation-writer', 'devops-specialist'],\n};\n\n/**\n * Mapping from PREVC roles to agent types\n */\nexport const ROLE_TO_AGENTS: Record<PrevcRole, AgentType[]> = {\n  planner: ['architect-specialist', 'documentation-writer'],\n  designer: ['frontend-specialist'],\n  architect: ['architect-specialist', 'backend-specialist', 'database-specialist'],\n  developer: [\n    'feature-developer',\n    'bug-fixer',\n    'backend-specialist',\n    'frontend-specialist',\n    'mobile-specialist',\n    'database-specialist',\n  ],\n  qa: ['test-writer', 'security-auditor', 'performance-optimizer'],\n  reviewer: ['code-reviewer', 'security-auditor'],\n  documenter: ['documentation-writer'],\n  'solo-dev': [\n    'refactoring-specialist',\n    'bug-fixer',\n    'feature-developer',\n    'test-writer',\n    'documentation-writer',\n  ],\n};\n\n/**\n * Task keywords to agent mapping for intelligent selection\n */\nconst TASK_KEYWORDS: Record<string, AgentType[]> = {\n  // Architecture keywords\n  architecture: ['architect-specialist'],\n  design: ['architect-specialist', 'frontend-specialist'],\n  system: ['architect-specialist', 'backend-specialist'],\n  scalability: ['architect-specialist', 'performance-optimizer'],\n\n  // Development keywords\n  feature: ['feature-developer'],\n  implement: ['feature-developer', 'backend-specialist'],\n  build: ['feature-developer'],\n  create: ['feature-developer'],\n\n  // Bug/Fix keywords\n  bug: ['bug-fixer'],\n  fix: ['bug-fixer'],\n  error: ['bug-fixer'],\n  issue: ['bug-fixer'],\n\n  // Testing keywords\n  test: ['test-writer'],\n  coverage: ['test-writer'],\n  unit: ['test-writer'],\n  integration: ['test-writer'],\n\n  // Code quality keywords\n  review: ['code-reviewer'],\n  refactor: ['refactoring-specialist', 'code-reviewer'],\n  clean: ['refactoring-specialist'],\n  optimize: ['performance-optimizer', 'refactoring-specialist'],\n\n  // Security keywords\n  security: ['security-auditor'],\n  vulnerability: ['security-auditor'],\n  auth: ['security-auditor', 'backend-specialist'],\n  permission: ['security-auditor'],\n\n  // Performance keywords\n  performance: ['performance-optimizer'],\n  speed: ['performance-optimizer'],\n  memory: ['performance-optimizer'],\n  cache: ['performance-optimizer', 'backend-specialist'],\n\n  // Documentation keywords\n  document: ['documentation-writer'],\n  readme: ['documentation-writer'],\n  docs: ['documentation-writer'],\n\n  // Backend keywords\n  api: ['backend-specialist', 'documentation-writer'],\n  server: ['backend-specialist'],\n  endpoint: ['backend-specialist'],\n  microservice: ['backend-specialist'],\n\n  // Frontend keywords\n  ui: ['frontend-specialist'],\n  component: ['frontend-specialist'],\n  style: ['frontend-specialist'],\n  responsive: ['frontend-specialist'],\n\n  // Database keywords\n  database: ['database-specialist'],\n  query: ['database-specialist'],\n  migration: ['database-specialist'],\n  schema: ['database-specialist'],\n\n  // DevOps keywords\n  deploy: ['devops-specialist'],\n  ci: ['devops-specialist'],\n  pipeline: ['devops-specialist'],\n  docker: ['devops-specialist'],\n\n  // Mobile keywords\n  mobile: ['mobile-specialist'],\n  ios: ['mobile-specialist'],\n  android: ['mobile-specialist'],\n  app: ['mobile-specialist', 'frontend-specialist'],\n};\n\n/**\n * Agent Orchestrator class\n */\nexport class AgentOrchestrator {\n  /**\n   * Get recommended agents for a PREVC phase\n   */\n  getAgentsForPhase(phase: PrevcPhase): AgentType[] {\n    return PHASE_TO_AGENTS[phase] || [];\n  }\n\n  /**\n   * Get recommended agents for a PREVC role\n   */\n  getAgentsForRole(role: PrevcRole): AgentType[] {\n    return ROLE_TO_AGENTS[role] || [];\n  }\n\n  /**\n   * Get primary agent for a role (first in the list)\n   */\n  getPrimaryAgentForRole(role: PrevcRole): AgentType | null {\n    const agents = this.getAgentsForRole(role);\n    return agents.length > 0 ? agents[0] : null;\n  }\n\n  /**\n   * Select agents based on task description\n   */\n  selectAgentsByTask(taskDescription: string): AgentType[] {\n    const lowerTask = taskDescription.toLowerCase();\n    const matchedAgents = new Set<AgentType>();\n\n    for (const [keyword, agents] of Object.entries(TASK_KEYWORDS)) {\n      if (lowerTask.includes(keyword)) {\n        agents.forEach((agent) => matchedAgents.add(agent));\n      }\n    }\n\n    // If no matches, return a default set\n    if (matchedAgents.size === 0) {\n      return ['feature-developer', 'code-reviewer'];\n    }\n\n    return Array.from(matchedAgents);\n  }\n\n  /**\n   * Get agent handoff sequence for a workflow\n   * Returns ordered list of agents based on phase progression\n   */\n  getAgentHandoffSequence(phases: PrevcPhase[]): AgentType[] {\n    const sequence: AgentType[] = [];\n    const seen = new Set<AgentType>();\n\n    for (const phase of phases) {\n      const agents = this.getAgentsForPhase(phase);\n      for (const agent of agents) {\n        if (!seen.has(agent)) {\n          sequence.push(agent);\n          seen.add(agent);\n        }\n      }\n    }\n\n    return sequence;\n  }\n\n  /**\n   * Get recommended agent sequence for a specific task\n   */\n  getTaskAgentSequence(\n    taskDescription: string,\n    includeReview: boolean = true\n  ): AgentType[] {\n    const primaryAgents = this.selectAgentsByTask(taskDescription);\n    const sequence = [...primaryAgents];\n\n    // Add test-writer if not already included\n    if (!sequence.includes('test-writer')) {\n      sequence.push('test-writer');\n    }\n\n    // Add code-reviewer for review if requested\n    if (includeReview && !sequence.includes('code-reviewer')) {\n      sequence.push('code-reviewer');\n    }\n\n    // Add documentation-writer at the end\n    if (!sequence.includes('documentation-writer')) {\n      sequence.push('documentation-writer');\n    }\n\n    return sequence;\n  }\n\n  /**\n   * Map a specialist name to agent type\n   */\n  specialistToAgent(specialist: string): AgentType | null {\n    // Direct mapping if it's already an agent type\n    if (AGENT_TYPES.includes(specialist as AgentType)) {\n      return specialist as AgentType;\n    }\n\n    // Check role mappings\n    for (const [role, specialists] of Object.entries(ROLE_TO_SPECIALISTS)) {\n      if (specialists.includes(specialist)) {\n        const agents = ROLE_TO_AGENTS[role as PrevcRole];\n        return agents.length > 0 ? agents[0] : null;\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * Get all available agent types\n   */\n  getAllAgentTypes(): AgentType[] {\n    return [...AGENT_TYPES];\n  }\n\n  /**\n   * Check if agent type is valid\n   */\n  isValidAgentType(type: string): type is AgentType {\n    return AGENT_TYPES.includes(type as AgentType);\n  }\n\n  /**\n   * Get agent description\n   */\n  getAgentDescription(agent: AgentType): string {\n    const descriptions: Record<AgentType, string> = {\n      'code-reviewer': 'Reviews code for quality, style, and best practices',\n      'bug-fixer': 'Identifies and fixes bugs with targeted solutions',\n      'feature-developer': 'Implements new features following architecture',\n      'refactoring-specialist': 'Improves code structure and eliminates code smells',\n      'test-writer': 'Creates comprehensive test suites',\n      'documentation-writer': 'Writes and maintains documentation',\n      'performance-optimizer': 'Identifies and resolves performance bottlenecks',\n      'security-auditor': 'Audits code for security vulnerabilities',\n      'backend-specialist': 'Develops server-side logic and APIs',\n      'frontend-specialist': 'Builds user interfaces and interactions',\n      'architect-specialist': 'Designs system architecture and patterns',\n      'devops-specialist': 'Manages deployment and CI/CD pipelines',\n      'database-specialist': 'Designs and optimizes database solutions',\n      'mobile-specialist': 'Develops mobile applications',\n    };\n\n    return descriptions[agent] || 'Specialized development agent';\n  }\n}\n\n// Export singleton instance\nexport const agentOrchestrator = new AgentOrchestrator();\n"
  },
  {
    "path": "src/workflow/orchestration/documentLinker.ts",
    "content": "/**\n * Document Linker\n *\n * Links agents and workflow phases to relevant documentation sections,\n * enabling context-aware navigation and knowledge sharing.\n */\n\nimport { PrevcPhase, PrevcRole } from '../types';\nimport { AgentType } from './agentOrchestrator';\n\n/**\n * Documentation types available in .context/docs/\n */\nexport type DocType =\n  | 'architecture'\n  | 'data-flow'\n  | 'glossary'\n  | 'api'\n  | 'getting-started'\n  | 'deployment'\n  | 'security'\n  | 'testing'\n  | 'contributing'\n  | 'readme';\n\n/**\n * Document guide information\n */\nexport interface DocGuide {\n  type: DocType;\n  path: string;\n  title: string;\n  description: string;\n}\n\n/**\n * Standard documentation guides\n */\nexport const DOCUMENT_GUIDES: DocGuide[] = [\n  {\n    type: 'architecture',\n    path: '.context/docs/architecture.md',\n    title: 'Architecture',\n    description: 'System architecture, patterns, and design decisions',\n  },\n  {\n    type: 'data-flow',\n    path: '.context/docs/data-flow.md',\n    title: 'Data Flow',\n    description: 'How data moves through the system',\n  },\n  {\n    type: 'glossary',\n    path: '.context/docs/glossary.md',\n    title: 'Glossary',\n    description: 'Domain terms and definitions',\n  },\n  {\n    type: 'api',\n    path: '.context/docs/api.md',\n    title: 'API Reference',\n    description: 'API endpoints and usage',\n  },\n  {\n    type: 'getting-started',\n    path: '.context/docs/getting-started.md',\n    title: 'Getting Started',\n    description: 'Setup and onboarding guide',\n  },\n  {\n    type: 'deployment',\n    path: '.context/docs/deployment.md',\n    title: 'Deployment',\n    description: 'Deployment procedures and environments',\n  },\n  {\n    type: 'security',\n    path: '.context/docs/security.md',\n    title: 'Security',\n    description: 'Security guidelines and practices',\n  },\n  {\n    type: 'testing',\n    path: '.context/docs/testing.md',\n    title: 'Testing',\n    description: 'Testing strategies and coverage',\n  },\n  {\n    type: 'contributing',\n    path: '.context/docs/contributing.md',\n    title: 'Contributing',\n    description: 'Contribution guidelines',\n  },\n  {\n    type: 'readme',\n    path: '.context/docs/README.md',\n    title: 'Documentation Index',\n    description: 'Overview of all documentation',\n  },\n];\n\n/**\n * Mapping from agent types to relevant documentation\n */\nexport const AGENT_TO_DOCS: Record<AgentType, DocType[]> = {\n  'code-reviewer': ['architecture', 'contributing', 'glossary'],\n  'bug-fixer': ['architecture', 'data-flow', 'testing'],\n  'feature-developer': ['architecture', 'data-flow', 'api', 'getting-started'],\n  'refactoring-specialist': ['architecture', 'glossary', 'contributing'],\n  'test-writer': ['testing', 'architecture', 'api'],\n  'documentation-writer': ['readme', 'glossary', 'architecture', 'api'],\n  'performance-optimizer': ['architecture', 'data-flow', 'deployment'],\n  'security-auditor': ['security', 'architecture', 'api'],\n  'backend-specialist': ['architecture', 'api', 'data-flow', 'deployment'],\n  'frontend-specialist': ['architecture', 'data-flow', 'getting-started'],\n  'architect-specialist': ['architecture', 'data-flow', 'security', 'deployment'],\n  'devops-specialist': ['deployment', 'security', 'testing'],\n  'database-specialist': ['architecture', 'data-flow', 'glossary'],\n  'mobile-specialist': ['architecture', 'api', 'getting-started'],\n};\n\n/**\n * Mapping from PREVC phases to relevant documentation\n */\nexport const PHASE_TO_DOCS: Record<PrevcPhase, DocType[]> = {\n  P: ['architecture', 'glossary', 'readme'], // Planning\n  R: ['architecture', 'security', 'data-flow'], // Review\n  E: ['architecture', 'api', 'data-flow', 'getting-started'], // Execution\n  V: ['testing', 'security', 'api'], // Validation\n  C: ['deployment', 'readme', 'contributing'], // Confirmation\n};\n\n/**\n * Mapping from PREVC roles to relevant documentation\n */\nexport const ROLE_TO_DOCS: Record<PrevcRole, DocType[]> = {\n  planner: ['architecture', 'glossary', 'readme'],\n  designer: ['architecture', 'getting-started'],\n  architect: ['architecture', 'data-flow', 'security', 'deployment'],\n  developer: ['architecture', 'api', 'data-flow', 'getting-started'],\n  qa: ['testing', 'security', 'api'],\n  reviewer: ['architecture', 'contributing', 'glossary'],\n  documenter: ['readme', 'glossary', 'architecture', 'api', 'contributing'],\n  'solo-dev': ['architecture', 'api', 'testing', 'readme'],\n};\n\n/**\n * Document Linker class\n */\nexport class DocumentLinker {\n  /**\n   * Get documentation guides for an agent type\n   */\n  getDocsForAgent(agent: AgentType): DocGuide[] {\n    const docTypes = AGENT_TO_DOCS[agent] || [];\n    return this.getGuidesByTypes(docTypes);\n  }\n\n  /**\n   * Get documentation guides for a PREVC phase\n   */\n  getDocsForPhase(phase: PrevcPhase): DocGuide[] {\n    const docTypes = PHASE_TO_DOCS[phase] || [];\n    return this.getGuidesByTypes(docTypes);\n  }\n\n  /**\n   * Get documentation guides for a PREVC role\n   */\n  getDocsForRole(role: PrevcRole): DocGuide[] {\n    const docTypes = ROLE_TO_DOCS[role] || [];\n    return this.getGuidesByTypes(docTypes);\n  }\n\n  /**\n   * Get primary documentation for an agent (first in list)\n   */\n  getPrimaryDocForAgent(agent: AgentType): DocGuide | null {\n    const docs = this.getDocsForAgent(agent);\n    return docs.length > 0 ? docs[0] : null;\n  }\n\n  /**\n   * Get all documentation guides\n   */\n  getAllDocs(): DocGuide[] {\n    return [...DOCUMENT_GUIDES];\n  }\n\n  /**\n   * Get a specific document guide by type\n   */\n  getDocByType(type: DocType): DocGuide | null {\n    return DOCUMENT_GUIDES.find((doc) => doc.type === type) || null;\n  }\n\n  /**\n   * Get documentation paths for an agent\n   */\n  getDocPathsForAgent(agent: AgentType): string[] {\n    return this.getDocsForAgent(agent).map((doc) => doc.path);\n  }\n\n  /**\n   * Get documentation paths for a phase\n   */\n  getDocPathsForPhase(phase: PrevcPhase): string[] {\n    return this.getDocsForPhase(phase).map((doc) => doc.path);\n  }\n\n  /**\n   * Get documentation paths for a role\n   */\n  getDocPathsForRole(role: PrevcRole): string[] {\n    return this.getDocsForRole(role).map((doc) => doc.path);\n  }\n\n  /**\n   * Get combined documentation for multiple agents\n   */\n  getDocsForAgents(agents: AgentType[]): DocGuide[] {\n    const docTypes = new Set<DocType>();\n\n    for (const agent of agents) {\n      const types = AGENT_TO_DOCS[agent] || [];\n      types.forEach((type) => docTypes.add(type));\n    }\n\n    return this.getGuidesByTypes(Array.from(docTypes));\n  }\n\n  /**\n   * Get documentation relevant to a task description\n   */\n  getDocsForTask(taskDescription: string): DocGuide[] {\n    const lowerTask = taskDescription.toLowerCase();\n    const relevantDocs = new Set<DocType>();\n\n    // Keyword matching\n    if (lowerTask.includes('architect') || lowerTask.includes('design') || lowerTask.includes('structure')) {\n      relevantDocs.add('architecture');\n    }\n    if (lowerTask.includes('api') || lowerTask.includes('endpoint')) {\n      relevantDocs.add('api');\n    }\n    if (lowerTask.includes('test') || lowerTask.includes('coverage')) {\n      relevantDocs.add('testing');\n    }\n    if (lowerTask.includes('security') || lowerTask.includes('auth')) {\n      relevantDocs.add('security');\n    }\n    if (lowerTask.includes('deploy') || lowerTask.includes('release')) {\n      relevantDocs.add('deployment');\n    }\n    if (lowerTask.includes('data') || lowerTask.includes('flow')) {\n      relevantDocs.add('data-flow');\n    }\n    if (lowerTask.includes('document') || lowerTask.includes('readme')) {\n      relevantDocs.add('readme');\n    }\n    if (lowerTask.includes('setup') || lowerTask.includes('start')) {\n      relevantDocs.add('getting-started');\n    }\n\n    // Default to architecture if no matches\n    if (relevantDocs.size === 0) {\n      relevantDocs.add('architecture');\n      relevantDocs.add('readme');\n    }\n\n    return this.getGuidesByTypes(Array.from(relevantDocs));\n  }\n\n  /**\n   * Generate markdown links for agent documentation\n   */\n  generateAgentDocLinks(agent: AgentType): string {\n    const docs = this.getDocsForAgent(agent);\n    if (docs.length === 0) return '';\n\n    const lines = ['## Relevant Documentation', ''];\n    for (const doc of docs) {\n      lines.push(`- [${doc.title}](${doc.path}) - ${doc.description}`);\n    }\n    return lines.join('\\n');\n  }\n\n  /**\n   * Generate markdown links for phase documentation\n   */\n  generatePhaseDocLinks(phase: PrevcPhase): string {\n    const docs = this.getDocsForPhase(phase);\n    if (docs.length === 0) return '';\n\n    const lines = ['## Phase Documentation', ''];\n    for (const doc of docs) {\n      lines.push(`- [${doc.title}](${doc.path}) - ${doc.description}`);\n    }\n    return lines.join('\\n');\n  }\n\n  /**\n   * Helper to get guides by doc types\n   */\n  private getGuidesByTypes(types: DocType[]): DocGuide[] {\n    return types\n      .map((type) => DOCUMENT_GUIDES.find((doc) => doc.type === type))\n      .filter((doc): doc is DocGuide => doc !== undefined);\n  }\n}\n\n// Export singleton instance\nexport const documentLinker = new DocumentLinker();\n"
  },
  {
    "path": "src/workflow/orchestration/index.ts",
    "content": "/**\n * Orchestration Module Exports\n *\n * Provides agent orchestration and document linking capabilities\n * for the PREVC workflow system.\n */\n\n// Agent Orchestrator\nexport {\n  AgentOrchestrator,\n  agentOrchestrator,\n  AgentType,\n  AGENT_TYPES,\n  PHASE_TO_AGENTS,\n  ROLE_TO_AGENTS,\n} from './agentOrchestrator';\n\n// Document Linker\nexport {\n  DocumentLinker,\n  documentLinker,\n  DocType,\n  DocGuide,\n  DOCUMENT_GUIDES,\n  AGENT_TO_DOCS,\n  PHASE_TO_DOCS,\n  ROLE_TO_DOCS,\n} from './documentLinker';\n"
  },
  {
    "path": "src/workflow/orchestrator.ts",
    "content": "/**\n * PREVC Workflow Orchestrator\n *\n * Manages workflow progression, phase transitions, and role handoffs.\n */\n\nimport * as path from 'path';\nimport {\n  PrevcStatus,\n  PrevcPhase,\n  PrevcRole,\n  ProjectContext,\n  ProjectScale,\n  WorkflowSettings,\n  PlanApproval,\n  PhaseOrchestration,\n  AgentSequenceStep,\n  ToolGuidance,\n} from './types';\nimport { PrevcStatusManager } from './status/statusManager';\nimport { detectProjectScale, getScaleRoute } from './scaling';\nimport { PREVC_PHASE_ORDER, getPhaseDefinition, PHASE_NAMES_EN } from './phases';\nimport { getRoleConfig } from './prevcConfig';\nimport { WorkflowGateChecker, GateCheckResult, getDefaultSettings } from './gates';\nimport { PlanLinker } from './plans/planLinker';\nimport { AgentOrchestrator, PHASE_TO_AGENTS } from './orchestration/agentOrchestrator';\nimport { createSkillRegistry } from './skills';\n\n/**\n * Options for completing a phase\n */\nexport interface CompletePhaseOptions {\n  /** Force advancement even if gates would block */\n  force?: boolean;\n}\n\n/**\n * Options for initializing a workflow with settings\n */\nexport interface InitWorkflowOptions {\n  name: string;\n  scale: ProjectScale;\n  /** Override default settings */\n  settings?: Partial<WorkflowSettings>;\n}\n\n/**\n * PREVC Workflow Orchestrator\n *\n * Coordinates the execution of the PREVC workflow.\n */\nexport class PrevcOrchestrator {\n  private repoPath: string;\n  private contextPath: string;\n  private statusManager: PrevcStatusManager;\n  private gateChecker: WorkflowGateChecker;\n  private planLinker: PlanLinker;\n\n  constructor(contextPath: string) {\n    this.repoPath = path.dirname(contextPath);\n    this.contextPath = contextPath;\n    this.statusManager = new PrevcStatusManager(contextPath);\n    this.gateChecker = new WorkflowGateChecker();\n    // Pass statusManager to PlanLinker for breadcrumb trail logging\n    this.planLinker = new PlanLinker(path.dirname(contextPath), this.statusManager);\n  }\n\n  /**\n   * Check if a workflow exists\n   */\n  async hasWorkflow(): Promise<boolean> {\n    return this.statusManager.exists();\n  }\n\n  /**\n   * Initialize a new workflow\n   */\n  async initWorkflow(context: ProjectContext): Promise<PrevcStatus> {\n    const scale = detectProjectScale(context);\n    const route = getScaleRoute(scale);\n\n    const status = await this.statusManager.create({\n      name: context.name,\n      scale,\n      phases: route.phases,\n      roles: route.roles,\n    });\n\n    await this.planLinker.ensureWorkflowPlanIndex();\n\n    return status;\n  }\n\n  /**\n   * Initialize a workflow with explicit scale\n   * @param archivePrevious - If undefined and workflow exists, throws error. If true, archives. If false, deletes.\n   */\n  async initWorkflowWithScale(\n    name: string,\n    scale: ProjectScale,\n    settings?: Partial<WorkflowSettings>,\n    archivePrevious?: boolean\n  ): Promise<PrevcStatus> {\n    // Check for existing workflow\n    if (await this.hasWorkflow()) {\n      if (archivePrevious === undefined) {\n        throw new Error(\n          'A workflow already exists. Use archivePrevious=true to archive or archivePrevious=false to delete the existing workflow.'\n        );\n      }\n      await this.resetWorkflow(archivePrevious);\n    }\n\n    const route = getScaleRoute(scale);\n\n    const status = await this.statusManager.create({\n      name,\n      scale,\n      phases: route.phases,\n      roles: route.roles,\n    });\n\n    await this.planLinker.ensureWorkflowPlanIndex();\n\n    // Apply custom settings if provided\n    if (settings) {\n      await this.statusManager.setSettings(settings);\n      return this.statusManager.load();\n    }\n\n    return status;\n  }\n\n  /**\n   * Reset the current workflow\n   * @param archive - If true, archives the current workflow. If false, deletes it.\n   */\n  async resetWorkflow(archive: boolean): Promise<void> {\n    if (archive) {\n      await this.archiveCurrentWorkflow();\n      await this.planLinker.archivePlans();\n    } else {\n      await this.planLinker.clearAllPlans();\n      await this.statusManager.remove();\n    }\n  }\n\n  /**\n   * Archive the current workflow to .context/workflow/archive/\n   */\n  private async archiveCurrentWorkflow(): Promise<void> {\n    if (!(await this.statusManager.exists())) {\n      return;\n    }\n\n    // Get current workflow name for archive folder\n    let archiveName = 'workflow';\n    try {\n      const status = await this.statusManager.load();\n      archiveName = status.project.name.replace(/[^a-zA-Z0-9-_]/g, '-');\n    } catch {\n      // Use default name if can't load status\n    }\n    await this.statusManager.archive(archiveName);\n  }\n\n  /**\n   * Initialize a workflow with full options\n   */\n  async initWorkflowWithOptions(options: InitWorkflowOptions): Promise<PrevcStatus> {\n    return this.initWorkflowWithScale(options.name, options.scale, options.settings);\n  }\n\n  /**\n   * Get the current workflow status\n   */\n  async getStatus(): Promise<PrevcStatus> {\n    return this.statusManager.load();\n  }\n\n  /**\n   * Get the current phase\n   */\n  async getCurrentPhase(): Promise<PrevcPhase> {\n    return this.statusManager.getCurrentPhase();\n  }\n\n  /**\n   * Get the current active role\n   */\n  async getCurrentRole(): Promise<PrevcRole | null> {\n    return this.statusManager.getActiveRole();\n  }\n\n  /**\n   * Get the phase definition for the current phase\n   */\n  async getCurrentPhaseDefinition() {\n    const phase = await this.getCurrentPhase();\n    return getPhaseDefinition(phase);\n  }\n\n  /**\n   * Perform a handoff from one agent to another\n   * @param from - Agent name handing off (e.g., 'feature-developer')\n   * @param to - Agent name receiving (e.g., 'test-writer')\n   * @param artifacts - Array of output file paths\n   */\n  async handoff(\n    from: string,\n    to: string,\n    artifacts: string[]\n  ): Promise<void> {\n    // Update the outgoing agent\n    await this.statusManager.updateAgent(from, {\n      status: 'completed',\n      outputs: artifacts,\n    });\n\n    // Update the incoming agent\n    await this.statusManager.updateAgent(to, {\n      status: 'in_progress',\n    });\n  }\n\n  /**\n   * Get the next agent suggestion after a handoff\n   */\n  getNextAgentSuggestion(currentAgent: string): { agent: string; reason: string } | null {\n    const orchestrator = new AgentOrchestrator();\n\n    // Common handoff sequences\n    const handoffSequences: Record<string, { agent: string; reason: string }> = {\n      'feature-developer': { agent: 'test-writer', reason: 'Write tests for the new code' },\n      'bug-fixer': { agent: 'test-writer', reason: 'Write regression tests' },\n      'test-writer': { agent: 'code-reviewer', reason: 'Review implementation and tests' },\n      'code-reviewer': { agent: 'documentation-writer', reason: 'Document the changes' },\n      'backend-specialist': { agent: 'test-writer', reason: 'Write API tests' },\n      'frontend-specialist': { agent: 'test-writer', reason: 'Write UI tests' },\n      'refactoring-specialist': { agent: 'test-writer', reason: 'Verify refactored code' },\n    };\n\n    return handoffSequences[currentAgent] || null;\n  }\n\n  /**\n   * Get orchestration guidance for a phase\n   */\n  async getPhaseOrchestration(phase: PrevcPhase): Promise<PhaseOrchestration> {\n    const orchestrator = new AgentOrchestrator();\n    const agents = PHASE_TO_AGENTS[phase] || [];\n    const sequence = orchestrator.getAgentHandoffSequence([phase]);\n\n    const suggestedSequence: AgentSequenceStep[] = sequence.map((agent) => ({\n      agent,\n      task: this.getAgentDefaultTask(agent),\n    }));\n\n    const startWith = agents.length > 0 ? agents[0] : 'feature-developer';\n    const instruction = this.buildOrchestrationInstruction(phase, startWith);\n    const skillRegistry = createSkillRegistry(this.repoPath);\n    const skills = await skillRegistry.getSkillsForPhase(phase);\n    const recommendedSkills = skills.map((skill) => ({\n      slug: skill.slug,\n      name: skill.metadata.name,\n      description: skill.metadata.description,\n      path: skill.path,\n      isBuiltIn: skill.isBuiltIn,\n    }));\n\n    // Build tool guidance for explicit orchestration\n    const toolGuidance = this.buildToolGuidance(phase, startWith);\n\n    // Build step-by-step orchestration instructions\n    const orchestrationSteps = this.buildOrchestrationSteps(phase, sequence);\n\n    return {\n      recommendedAgents: agents,\n      suggestedSequence,\n      startWith,\n      instruction,\n      recommendedSkills,\n      toolGuidance,\n      orchestrationSteps,\n    };\n  }\n\n  /**\n   * Get default task description for an agent\n   */\n  private getAgentDefaultTask(agent: string): string {\n    const taskMap: Record<string, string> = {\n      'feature-developer': 'Implement core functionality',\n      'bug-fixer': 'Fix identified issues',\n      'test-writer': 'Write tests for new code',\n      'code-reviewer': 'Review implementation',\n      'documentation-writer': 'Document the changes',\n      'backend-specialist': 'Implement server-side logic',\n      'frontend-specialist': 'Build user interface',\n      'database-specialist': 'Design and optimize database',\n      'architect-specialist': 'Design system architecture',\n      'security-auditor': 'Audit for security vulnerabilities',\n      'performance-optimizer': 'Optimize performance',\n      'refactoring-specialist': 'Improve code structure',\n      'devops-specialist': 'Configure deployment pipeline',\n      'mobile-specialist': 'Develop mobile features',\n    };\n\n    return taskMap[agent] || 'Execute assigned tasks';\n  }\n\n  /**\n   * Build tool guidance with concrete MCP tool call examples\n   */\n  private buildToolGuidance(phase: PrevcPhase, startAgent: string): ToolGuidance {\n    return {\n      discoverExample: `agent({ action: \"orchestrate\", phase: \"${phase}\" })`,\n      sequenceExample: `agent({ action: \"getSequence\", phases: [\"${phase}\"] })`,\n      handoffExample: `workflow-manage({ action: \"handoff\", from: \"${startAgent}\", to: \"<next-agent>\", artifacts: [\"output.md\"] })`,\n    };\n  }\n\n  /**\n   * Build step-by-step orchestration instructions with tool calls\n   */\n  private buildOrchestrationSteps(phase: PrevcPhase, agents: string[]): string[] {\n    const phaseName = PHASE_NAMES_EN[phase];\n    const startAgent = agents[0] || 'feature-developer';\n    const nextAgent = agents[1] || '<next-agent>';\n\n    return [\n      `1. Discover agents for ${phaseName} phase: agent({ action: \"orchestrate\", phase: \"${phase}\" })`,\n      `2. Review recommended sequence: agent({ action: \"getSequence\", phases: [\"${phase}\"] })`,\n      `3. Begin with ${startAgent} agent - follow playbook at .context/agents/${startAgent}.md`,\n      `4. Execute handoffs: workflow-manage({ action: \"handoff\", from: \"${startAgent}\", to: \"${nextAgent}\", artifacts: [\"output.md\"] })`,\n      `5. Leverage skills: skill({ action: \"getForPhase\", phase: \"${phase}\" })`,\n    ];\n  }\n\n  /**\n   * Build orchestration instruction for a phase\n   */\n  private buildOrchestrationInstruction(phase: PrevcPhase, startAgent: string): string {\n    const phaseName = PHASE_NAMES_EN[phase];\n\n    return `ORCHESTRATION GUIDE for ${phaseName} phase:\\n\\n` +\n      `1. START: Activate ${startAgent} agent\\n` +\n      `   - Review agent playbook: .context/agents/${startAgent}.md\\n` +\n      `   - Understand responsibilities and outputs\\n\\n` +\n      `2. DISCOVER: Find all agents for this phase\\n` +\n      `   - Call: agent({ action: \"orchestrate\", phase: \"${phase}\" })\\n` +\n      `   - Review: Recommended agents and their roles\\n\\n` +\n      `3. SEQUENCE: Plan agent handoff order\\n` +\n      `   - Call: agent({ action: \"getSequence\", phases: [\"${phase}\"] })\\n` +\n      `   - Follow: Suggested sequence for optimal workflow\\n\\n` +\n      `4. EXECUTE: Perform work and handoffs\\n` +\n      `   - Work: Complete tasks as ${startAgent}\\n` +\n      `   - Handoff: workflow-manage({ action: \"handoff\", from: \"${startAgent}\", to: \"<next-agent>\", artifacts: [...] })\\n` +\n      `   - Repeat: Continue through sequence until phase complete\\n\\n` +\n      `5. ADVANCE: Move to next phase\\n` +\n      `   - Call: workflow-advance({ outputs: [...] })\\n` +\n      `   - Review: Next phase orchestration guidance`;\n  }\n\n  /**\n   * Complete the current phase and advance to the next\n   */\n  async completePhase(\n    outputs?: string[],\n    options: CompletePhaseOptions = {}\n  ): Promise<PrevcPhase | null> {\n    const status = await this.getStatus();\n    const currentPhase = status.project.current_phase;\n    const nextPhase = await this.getNextPhase();\n\n    // Check gates before advancing (unless force is true)\n    if (nextPhase) {\n      this.gateChecker.enforceGates(status, {\n        force: options.force,\n        nextPhase,\n      });\n    }\n\n    // Mark current phase as complete\n    await this.statusManager.markPhaseComplete(currentPhase, outputs);\n\n    // Auto-sync linked plan markdown with execution progress\n    if (status.project.plan) {\n      try {\n        await this.planLinker.syncPlanMarkdown(status.project.plan);\n      } catch {\n        // Silent fail - plan sync is non-critical\n      }\n    }\n\n    // Get and transition to next phase\n    if (nextPhase) {\n      await this.statusManager.transitionToPhase(nextPhase);\n    }\n\n    return nextPhase;\n  }\n\n  /**\n   * Check gates for the current phase transition\n   */\n  async checkGates(): Promise<GateCheckResult> {\n    const status = await this.getStatus();\n    return this.gateChecker.checkGates(status);\n  }\n\n  /**\n   * Set workflow settings\n   */\n  async setSettings(settings: Partial<WorkflowSettings>): Promise<WorkflowSettings> {\n    return this.statusManager.setSettings(settings);\n  }\n\n  /**\n   * Get workflow settings\n   */\n  async getSettings(): Promise<WorkflowSettings> {\n    return this.statusManager.getSettings();\n  }\n\n  /**\n   * Mark that a plan has been created/linked\n   */\n  async markPlanCreated(planSlug: string): Promise<void> {\n    return this.statusManager.markPlanCreated(planSlug);\n  }\n\n  /**\n   * Approve the plan\n   */\n  async approvePlan(approver: PrevcRole | string, notes?: string): Promise<PlanApproval> {\n    return this.statusManager.approvePlan(approver, notes);\n  }\n\n  /**\n   * Get approval status\n   */\n  async getApproval(): Promise<PlanApproval | undefined> {\n    return this.statusManager.getApproval();\n  }\n\n  /**\n   * Advance to the next phase\n   */\n  async advanceToNextPhase(): Promise<PrevcPhase | null> {\n    const nextPhase = await this.getNextPhase();\n    if (nextPhase) {\n      await this.statusManager.transitionToPhase(nextPhase);\n    }\n    return nextPhase;\n  }\n\n  /**\n   * Get the next phase that should be executed\n   */\n  async getNextPhase(): Promise<PrevcPhase | null> {\n    return this.statusManager.getNextPhase();\n  }\n\n  /**\n   * Check if the workflow is complete\n   */\n  async isComplete(): Promise<boolean> {\n    return this.statusManager.isComplete();\n  }\n\n  /**\n   * Get recommended next actions for the current state\n   */\n  async getRecommendedActions(): Promise<string[]> {\n    const status = await this.getStatus();\n    const currentPhase = status.project.current_phase;\n    const phaseDefinition = getPhaseDefinition(currentPhase);\n    const actions: string[] = [];\n\n    // Suggest phase-specific actions\n    actions.push(\n      `Complete ${phaseDefinition.name} phase tasks`\n    );\n\n    // Suggest role-specific actions\n    for (const role of phaseDefinition.roles) {\n      const roleConfig = getRoleConfig(role);\n      if (roleConfig) {\n        actions.push(...roleConfig.responsibilities.slice(0, 2));\n      }\n    }\n\n    // Suggest output creation\n    if (phaseDefinition.outputs.length > 0) {\n      actions.push(\n        `Create outputs: ${phaseDefinition.outputs.join(', ')}`\n      );\n    }\n\n    return actions;\n  }\n\n  /**\n   * Get a summary of the current workflow state\n   */\n  async getSummary(): Promise<WorkflowSummary> {\n    const status = await this.getStatus();\n    const isComplete = await this.isComplete();\n\n    // Count completed phases\n    let completedPhases = 0;\n    let totalPhases = 0;\n\n    for (const phase of PREVC_PHASE_ORDER) {\n      if (status.phases[phase].status !== 'skipped') {\n        totalPhases++;\n        if (status.phases[phase].status === 'completed') {\n          completedPhases++;\n        }\n      }\n    }\n\n    return {\n      name: status.project.name,\n      scale: status.project.scale,\n      currentPhase: status.project.current_phase,\n      progress: {\n        completed: completedPhases,\n        total: totalPhases,\n        percentage: Math.round((completedPhases / totalPhases) * 100),\n      },\n      isComplete,\n      startedAt: status.project.started,\n    };\n  }\n\n  /**\n   * Update the current task description\n   */\n  async updateCurrentTask(task: string): Promise<void> {\n    const currentPhase = await this.getCurrentPhase();\n    const activeRole = await this.getCurrentRole();\n\n    await this.statusManager.updatePhase(currentPhase, {\n      current_task: task,\n      role: activeRole || undefined,\n    });\n  }\n\n  /**\n   * Start a specific role in the current phase\n   */\n  async startRole(role: PrevcRole): Promise<void> {\n    const currentPhase = await this.getCurrentPhase();\n\n    await this.statusManager.updateRole(role, {\n      status: 'in_progress',\n      phase: currentPhase,\n    });\n\n    await this.statusManager.updatePhase(currentPhase, {\n      role,\n    });\n  }\n\n  /**\n   * Complete a role's work in the current phase\n   */\n  async completeRole(role: PrevcRole, outputs: string[]): Promise<void> {\n    await this.statusManager.updateRole(role, {\n      status: 'completed',\n      outputs,\n      last_active: new Date().toISOString(),\n    });\n  }\n}\n\n/**\n * Workflow summary for display\n */\nexport interface WorkflowSummary {\n  name: string;\n  scale: ProjectScale | keyof typeof ProjectScale;\n  currentPhase: PrevcPhase;\n  progress: {\n    completed: number;\n    total: number;\n    percentage: number;\n  };\n  isComplete: boolean;\n  startedAt: string;\n}\n"
  },
  {
    "path": "src/workflow/phases.ts",
    "content": "/**\n * PREVC Workflow Phases\n *\n * Defines the five phases of the PREVC workflow:\n * P - Planning\n * R - Review\n * E - Execution\n * V - Validation\n * C - Confirmation\n */\n\nimport { PrevcPhase, PhaseDefinition } from './types';\n\n/**\n * All PREVC phases in order\n */\nexport const PREVC_PHASE_ORDER: PrevcPhase[] = ['P', 'R', 'E', 'V', 'C'];\n\n/**\n * Complete definition of all PREVC phases\n */\nexport const PREVC_PHASES: Record<PrevcPhase, PhaseDefinition> = {\n  P: {\n    name: 'Planning',\n    description: 'Discovery, requirements and specifications',\n    roles: ['planner', 'designer'],\n    outputs: ['prd', 'tech-spec', 'requirements', 'wireframes'],\n    optional: false,\n    order: 1,\n  },\n  R: {\n    name: 'Review',\n    description: 'Architecture, technical decisions and design review',\n    roles: ['architect', 'designer'],\n    outputs: ['architecture', 'adr', 'design-spec'],\n    optional: true, // Depends on scale\n    order: 2,\n  },\n  E: {\n    name: 'Execution',\n    description: 'Implementation and development',\n    roles: ['developer'],\n    outputs: ['code', 'unit-tests'],\n    optional: false,\n    order: 3,\n  },\n  V: {\n    name: 'Validation',\n    description: 'Tests, QA and code review',\n    roles: ['qa', 'reviewer'],\n    outputs: ['test-report', 'review-comments', 'approval'],\n    optional: false,\n    order: 4,\n  },\n  C: {\n    name: 'Confirmation',\n    description: 'Documentation, deploy and handoff',\n    roles: ['documenter'],\n    outputs: ['documentation', 'changelog', 'deploy'],\n    optional: true, // Depends on scale\n    order: 5,\n  },\n};\n\n/**\n * Phase display names (English - default)\n */\nexport const PHASE_NAMES: Record<PrevcPhase, string> = {\n  P: 'Planning',\n  R: 'Review',\n  E: 'Execution',\n  V: 'Validation',\n  C: 'Confirmation',\n};\n\n/**\n * Phase display names in English (alias for consistency)\n */\nexport const PHASE_NAMES_EN = PHASE_NAMES;\n\n/**\n * Phase display names in Portuguese (for i18n)\n */\nexport const PHASE_NAMES_PT: Record<PrevcPhase, string> = {\n  P: 'Planejamento',\n  R: 'Revisão',\n  E: 'Execução',\n  V: 'Validação',\n  C: 'Confirmação',\n};\n\n/**\n * Get the definition for a specific phase\n */\nexport function getPhaseDefinition(phase: PrevcPhase): PhaseDefinition {\n  return PREVC_PHASES[phase];\n}\n\n/**\n * Get the next phase in the workflow\n */\nexport function getNextPhase(currentPhase: PrevcPhase): PrevcPhase | null {\n  const currentIndex = PREVC_PHASE_ORDER.indexOf(currentPhase);\n  if (currentIndex === -1 || currentIndex >= PREVC_PHASE_ORDER.length - 1) {\n    return null;\n  }\n  return PREVC_PHASE_ORDER[currentIndex + 1];\n}\n\n/**\n * Get the previous phase in the workflow\n */\nexport function getPreviousPhase(currentPhase: PrevcPhase): PrevcPhase | null {\n  const currentIndex = PREVC_PHASE_ORDER.indexOf(currentPhase);\n  if (currentIndex <= 0) {\n    return null;\n  }\n  return PREVC_PHASE_ORDER[currentIndex - 1];\n}\n\n/**\n * Check if a phase is optional\n */\nexport function isPhaseOptional(phase: PrevcPhase): boolean {\n  return PREVC_PHASES[phase]?.optional ?? false;\n}\n\n/**\n * Get all roles for a specific phase\n */\nexport function getRolesForPhase(phase: PrevcPhase): string[] {\n  return PREVC_PHASES[phase]?.roles || [];\n}\n\n/**\n * Get all outputs for a specific phase\n */\nexport function getOutputsForPhase(phase: PrevcPhase): string[] {\n  return PREVC_PHASES[phase]?.outputs || [];\n}\n\n/**\n * Check if a string is a valid PREVC phase\n */\nexport function isValidPhase(phase: string): phase is PrevcPhase {\n  return PREVC_PHASE_ORDER.includes(phase as PrevcPhase);\n}\n\n/**\n * Get the phase order number (1-5)\n */\nexport function getPhaseOrder(phase: PrevcPhase): number {\n  return PREVC_PHASES[phase]?.order ?? 0;\n}\n"
  },
  {
    "path": "src/workflow/plans/index.ts",
    "content": "/**\n * Plan-Workflow Integration Module\n *\n * Exports for linking implementation plans to the PREVC workflow system.\n */\n\nexport * from './types';\nexport { PlanLinker, createPlanLinker } from './planLinker';\n"
  },
  {
    "path": "src/workflow/plans/planLinker.ts",
    "content": "/**\n * Plan Linker\n *\n * Links implementation plans to the PREVC workflow system.\n * Provides bidirectional sync between plan progress and workflow phases.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport {\n  PlanReference,\n  LinkedPlan,\n  PlanPhase,\n  PlanDecision,\n  WorkflowPlans,\n  PLAN_PHASE_TO_PREVC,\n  StepExecution,\n  PlanPhaseTracking,\n  PlanExecutionTracking,\n} from './types';\nimport { PrevcPhase, StatusType } from '../types';\nimport { AgentRegistry, AgentMetadata, createAgentRegistry } from '../agents';\nimport { PrevcStatusManager } from '../status/statusManager';\nimport { GitService } from '../../utils/gitService';\n\n/**\n * Plan Linker class\n *\n * Responsible for:\n * - Linking plans to PREVC workflow\n * - Parsing plan frontmatter and content\n * - Tracking plan progress\n * - Recording decisions\n *\n * Agent discovery is delegated to AgentRegistry (SRP).\n */\nexport class PlanLinker {\n  private readonly repoPath: string;\n  private readonly contextPath: string;\n  private readonly plansPath: string;\n  private readonly workflowPath: string;\n  private readonly agentRegistry: AgentRegistry;\n  private readonly statusManager?: PrevcStatusManager;\n  private readonly autoCommitOnPhaseComplete: boolean;\n\n  constructor(repoPath: string, statusManager?: PrevcStatusManager, autoCommitOnPhaseComplete: boolean = true) {\n    this.repoPath = repoPath;\n    this.contextPath = path.join(repoPath, '.context');\n    this.plansPath = path.join(this.contextPath, 'plans');\n    this.workflowPath = path.join(this.contextPath, 'workflow');\n    this.agentRegistry = createAgentRegistry(repoPath);\n    this.statusManager = statusManager;\n    this.autoCommitOnPhaseComplete = autoCommitOnPhaseComplete;\n  }\n\n  /**\n   * Create a PlanLinker with the given repository path\n   */\n  static async create(\n    repoPath: string = process.cwd(),\n    statusManager?: PrevcStatusManager,\n    autoCommitOnPhaseComplete: boolean = true\n  ): Promise<PlanLinker> {\n    return new PlanLinker(repoPath, statusManager, autoCommitOnPhaseComplete);\n  }\n\n  /**\n   * Ensure workflow plan index exists\n   */\n  async ensureWorkflowPlanIndex(): Promise<void> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n\n    if (await fs.pathExists(plansFile)) {\n      return;\n    }\n\n    await fs.ensureDir(this.workflowPath);\n    const initialPlans: WorkflowPlans = { active: [], completed: [] };\n    await fs.writeFile(plansFile, JSON.stringify(initialPlans, null, 2), 'utf-8');\n  }\n\n  /**\n   * Discover all available agents (built-in + custom)\n   * Delegates to AgentRegistry\n   */\n  async discoverAgents(): Promise<Array<{ type: string; path: string; isCustom: boolean }>> {\n    const discovered = await this.agentRegistry.discoverAll();\n    return discovered.all.map(a => ({\n      type: a.type,\n      path: a.path,\n      isCustom: a.isCustom,\n    }));\n  }\n\n  /**\n   * Get agent info including custom agents\n   * Delegates to AgentRegistry\n   */\n  async getAgentInfo(agentType: string): Promise<AgentMetadata> {\n    return this.agentRegistry.getAgentMetadata(agentType);\n  }\n\n  /**\n   * Link a plan to the current workflow\n   */\n  async linkPlan(planSlug: string): Promise<PlanReference | null> {\n    const planPath = path.join(this.plansPath, `${planSlug}.md`);\n\n    if (!await fs.pathExists(planPath)) {\n      return null;\n    }\n\n    const content = await fs.readFile(planPath, 'utf-8');\n    const planInfo = this.parsePlanFile(content, planSlug);\n\n    const ref: PlanReference = {\n      slug: planSlug,\n      path: `plans/${planSlug}.md`,\n      title: planInfo.title,\n      summary: planInfo.summary,\n      linkedAt: new Date().toISOString(),\n      status: 'active',\n    };\n\n    // Update workflow plans tracking\n    await this.addPlanToWorkflow(ref);\n\n    return ref;\n  }\n\n  /**\n   * Get all linked plans for the current workflow\n   */\n  async getLinkedPlans(): Promise<WorkflowPlans> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n\n    if (!await fs.pathExists(plansFile)) {\n      return { active: [], completed: [] };\n    }\n\n    const content = await fs.readFile(plansFile, 'utf-8');\n    try {\n      return JSON.parse(content) || { active: [], completed: [] };\n    } catch {\n      return { active: [], completed: [] };\n    }\n  }\n\n  /**\n   * Update a linked plan reference in workflow tracking.\n   */\n  async updatePlanReference(\n    planSlug: string,\n    updater: (ref: PlanReference) => PlanReference\n  ): Promise<PlanReference | null> {\n    const plans = await this.getLinkedPlans();\n\n    const updateBucket = (bucket: PlanReference[]) => {\n      const index = bucket.findIndex((ref) => ref.slug === planSlug);\n      if (index === -1) {\n        return null;\n      }\n\n      const updated = updater({ ...bucket[index] });\n      bucket[index] = updated;\n      return updated;\n    };\n\n    const updatedRef = updateBucket(plans.active) ?? updateBucket(plans.completed);\n    if (!updatedRef) {\n      return null;\n    }\n\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n    await fs.ensureDir(path.dirname(plansFile));\n    await fs.writeJson(plansFile, plans, { spaces: 2 });\n    return updatedRef;\n  }\n\n  /**\n   * Get detailed plan with workflow mapping\n   */\n  async getLinkedPlan(planSlug: string): Promise<LinkedPlan | null> {\n    const plans = await this.getLinkedPlans();\n    const ref = [...plans.active, ...plans.completed].find(p => p.slug === planSlug);\n\n    if (!ref) {\n      return null;\n    }\n\n    const planPath = path.join(this.contextPath, ref.path);\n    if (!await fs.pathExists(planPath)) {\n      return null;\n    }\n\n    const content = await fs.readFile(planPath, 'utf-8');\n    return this.parsePlanToLinked(content, ref);\n  }\n\n  /**\n   * Get plans for a specific PREVC phase\n   */\n  async getPlansForPhase(phase: PrevcPhase): Promise<LinkedPlan[]> {\n    const plans = await this.getLinkedPlans();\n    const linkedPlans: LinkedPlan[] = [];\n\n    for (const ref of plans.active) {\n      const plan = await this.getLinkedPlan(ref.slug);\n      if (plan) {\n        const hasPhase = plan.phases.some(p => p.prevcPhase === phase);\n        if (hasPhase) {\n          linkedPlans.push(plan);\n        }\n      }\n    }\n\n    return linkedPlans;\n  }\n\n  /**\n   * Update plan phase status and sync with workflow\n   */\n  async updatePlanPhase(\n    planSlug: string,\n    phaseId: string,\n    status: StatusType\n  ): Promise<boolean> {\n    const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n\n    let tracking: Record<string, unknown> = {};\n    if (await fs.pathExists(trackingFile)) {\n      const content = await fs.readFile(trackingFile, 'utf-8');\n      try {\n        tracking = JSON.parse(content) || {};\n      } catch {\n        tracking = {};\n      }\n    }\n\n    // Update phase tracking\n    if (!tracking.phases) {\n      tracking.phases = {};\n    }\n    (tracking.phases as Record<string, unknown>)[phaseId] = {\n      status,\n      updatedAt: new Date().toISOString(),\n    };\n\n    // Calculate progress\n    const plan = await this.getLinkedPlan(planSlug);\n    if (plan) {\n      const totalPhases = plan.phases.length;\n      const completedPhases = plan.phases.filter(p =>\n        (tracking.phases as Record<string, { status: string }>)?.[p.id]?.status === 'completed'\n      ).length;\n      tracking.progress = totalPhases > 0 ? Math.round((completedPhases / totalPhases) * 100) : 0;\n    }\n\n    // Save tracking\n    await fs.ensureDir(path.dirname(trackingFile));\n    await fs.writeFile(trackingFile, JSON.stringify(tracking, null, 2), 'utf-8');\n\n    // Log phase update to workflow execution history\n    if (this.statusManager) {\n      const currentPhase = await this.statusManager.getCurrentPhase();\n      await this.statusManager.addHistoryEntry({\n        phase: currentPhase,\n        action: 'plan_phase_updated',\n        plan: planSlug,\n        description: `Plan phase ${phaseId} updated to ${status}`,\n      });\n    }\n\n    return true;\n  }\n\n  /**\n   * Record a decision in the plan\n   */\n  async recordDecision(\n    planSlug: string,\n    decision: Omit<PlanDecision, 'id' | 'decidedAt'>\n  ): Promise<PlanDecision> {\n    const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n\n    let tracking: Record<string, unknown> = {};\n    if (await fs.pathExists(trackingFile)) {\n      const content = await fs.readFile(trackingFile, 'utf-8');\n      try {\n        tracking = JSON.parse(content) || {};\n      } catch {\n        tracking = {};\n      }\n    }\n\n    if (!tracking.decisions) {\n      tracking.decisions = [];\n    }\n\n    const fullDecision: PlanDecision = {\n      ...decision,\n      id: `dec-${Date.now()}`,\n      decidedAt: new Date().toISOString(),\n    };\n\n    (tracking.decisions as PlanDecision[]).push(fullDecision);\n\n    await fs.ensureDir(path.dirname(trackingFile));\n    await fs.writeFile(trackingFile, JSON.stringify(tracking, null, 2), 'utf-8');\n\n    // Log decision to workflow execution history\n    if (this.statusManager) {\n      const currentPhase = await this.statusManager.getCurrentPhase();\n      await this.statusManager.addHistoryEntry({\n        phase: decision.phase || currentPhase,\n        action: 'decision_recorded',\n        plan: planSlug,\n        description: `Decision recorded: ${decision.title}`,\n      });\n    }\n\n    return fullDecision;\n  }\n\n  /**\n   * Get current phase mapping for workflow\n   */\n  getPhaseMappingForWorkflow(plan: LinkedPlan, currentPrevcPhase: PrevcPhase): PlanPhase[] {\n    return plan.phases.filter(p => p.prevcPhase === currentPrevcPhase);\n  }\n\n  /**\n   * Check if plan has pending work for a PREVC phase\n   */\n  hasPendingWorkForPhase(plan: LinkedPlan, phase: PrevcPhase): boolean {\n    const phasesInPrevc = plan.phases.filter(p => p.prevcPhase === phase);\n    return phasesInPrevc.some(p => p.status === 'pending' || p.status === 'in_progress');\n  }\n\n  /**\n   * Get plan progress summary\n   */\n  async getPlanProgress(planSlug: string): Promise<{\n    overall: number;\n    byPhase: Record<PrevcPhase, { total: number; completed: number; percentage: number }>;\n  }> {\n    const plan = await this.getLinkedPlan(planSlug);\n    if (!plan) {\n      return { overall: 0, byPhase: {} as Record<PrevcPhase, { total: number; completed: number; percentage: number }> };\n    }\n\n    const byPhase: Record<PrevcPhase, { total: number; completed: number; percentage: number }> = {\n      P: { total: 0, completed: 0, percentage: 0 },\n      R: { total: 0, completed: 0, percentage: 0 },\n      E: { total: 0, completed: 0, percentage: 0 },\n      V: { total: 0, completed: 0, percentage: 0 },\n      C: { total: 0, completed: 0, percentage: 0 },\n    };\n\n    for (const phase of plan.phases) {\n      byPhase[phase.prevcPhase].total++;\n      if (phase.status === 'completed') {\n        byPhase[phase.prevcPhase].completed++;\n      }\n    }\n\n    // Calculate percentages\n    for (const key of Object.keys(byPhase) as PrevcPhase[]) {\n      const { total, completed } = byPhase[key];\n      byPhase[key].percentage = total > 0 ? Math.round((completed / total) * 100) : 0;\n    }\n\n    return {\n      overall: plan.progress,\n      byPhase,\n    };\n  }\n\n  /**\n   * Update individual step status within a plan phase\n   */\n  async updatePlanStep(\n    planSlug: string,\n    phaseId: string,\n    stepIndex: number,\n    status: StatusType,\n    options?: {\n      output?: string;\n      notes?: string;\n    }\n  ): Promise<boolean> {\n    const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n    const now = new Date().toISOString();\n\n    // Load existing tracking or create new\n    let tracking = await this.loadPlanTracking(planSlug);\n    if (!tracking) {\n      tracking = {\n        planSlug,\n        progress: 0,\n        phases: {},\n        decisions: [],\n        lastUpdated: now,\n      };\n    }\n\n    // Ensure phase exists in tracking\n    if (!tracking.phases[phaseId]) {\n      tracking.phases[phaseId] = {\n        phaseId,\n        status: 'in_progress',\n        startedAt: now,\n        steps: [],\n      };\n    }\n\n    // Find or create step entry\n    let step = tracking.phases[phaseId].steps.find(s => s.stepIndex === stepIndex);\n    if (!step) {\n      // Get step description from the plan if possible\n      const plan = await this.getLinkedPlan(planSlug);\n      const planPhase = plan?.phases.find(p => p.id === phaseId);\n      const planStep = planPhase?.steps.find(s => s.order === stepIndex);\n\n      step = {\n        stepIndex,\n        description: planStep?.description || `Step ${stepIndex}`,\n        status: 'pending',\n      };\n      tracking.phases[phaseId].steps.push(step);\n    }\n\n    // Update step status and timestamps\n    step.status = status;\n    if (status === 'in_progress' && !step.startedAt) {\n      step.startedAt = now;\n    }\n    if (status === 'completed') {\n      step.completedAt = now;\n    }\n    if (options?.output) {\n      step.output = options.output;\n    }\n    if (options?.notes) {\n      step.notes = options.notes;\n    }\n\n    // Update phase status based on steps\n    const phaseSteps = tracking.phases[phaseId].steps;\n    const allStepsCompleted = phaseSteps.length > 0 && phaseSteps.every(s => s.status === 'completed');\n    const anyStepInProgress = phaseSteps.some(s => s.status === 'in_progress');\n\n    if (allStepsCompleted) {\n      tracking.phases[phaseId].status = 'completed';\n      tracking.phases[phaseId].completedAt = now;\n\n      // Auto-commit on phase completion\n      if (this.autoCommitOnPhaseComplete) {\n        await this.autoCommitPhase(planSlug, phaseId);\n      }\n    } else if (anyStepInProgress || phaseSteps.some(s => s.status === 'completed')) {\n      tracking.phases[phaseId].status = 'in_progress';\n    }\n\n    // Recalculate overall progress\n    tracking.progress = this.calculateStepProgress(tracking);\n    tracking.lastUpdated = now;\n\n    // Save tracking\n    await fs.ensureDir(path.dirname(trackingFile));\n    await fs.writeFile(trackingFile, JSON.stringify(tracking, null, 2), 'utf-8');\n\n    // Log step to workflow execution history (breadcrumb trail)\n    if (this.statusManager) {\n      const action = status === 'completed' ? 'step_completed' :\n                     status === 'in_progress' ? 'step_started' :\n                     status === 'skipped' ? 'step_skipped' : null;\n      if (action) {\n        await this.statusManager.addStepHistoryEntry({\n          action,\n          plan: planSlug,\n          planPhase: phaseId,\n          stepIndex,\n          stepDescription: step.description,\n          output: options?.output,\n          notes: options?.notes,\n        });\n      }\n    }\n\n    // Auto-sync to markdown\n    await this.syncPlanMarkdown(planSlug);\n\n    return true;\n  }\n\n  /**\n   * Update approval metadata for a linked plan.\n   * Keeps the workflow plan index and plan tracking projection in sync.\n   */\n  async updatePlanApproval(\n    planSlug: string,\n    approval: {\n      approvalStatus: 'pending' | 'approved' | 'rejected';\n      approvedAt?: string;\n      approvedBy?: string;\n    }\n  ): Promise<PlanReference | null> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n\n    if (!await fs.pathExists(plansFile)) {\n      return null;\n    }\n\n    const plans = await this.getLinkedPlans();\n    let updatedRef: PlanReference | null = null;\n    let found = false;\n\n    const applyApproval = (ref: PlanReference): PlanReference => {\n      found = true;\n      const nextRef: PlanReference = {\n        ...ref,\n        approval_status: approval.approvalStatus,\n        approved_at: approval.approvedAt,\n        approved_by: approval.approvedBy,\n      };\n      updatedRef = nextRef;\n      return nextRef;\n    };\n\n    plans.active = plans.active.map((ref) => (ref.slug === planSlug ? applyApproval(ref) : ref));\n    plans.completed = plans.completed.map((ref) => (ref.slug === planSlug ? applyApproval(ref) : ref));\n\n    if (!found || !updatedRef) {\n      return null;\n    }\n\n    await fs.ensureDir(this.workflowPath);\n    await fs.writeFile(plansFile, JSON.stringify(plans, null, 2), 'utf-8');\n\n    const tracking = await this.loadPlanTracking(planSlug);\n    if (tracking) {\n      tracking.approvalStatus = approval.approvalStatus;\n      tracking.approvedAt = approval.approvedAt;\n      tracking.approvedBy = approval.approvedBy;\n      tracking.lastUpdated = approval.approvedAt || new Date().toISOString();\n      const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n      await fs.ensureDir(path.dirname(trackingFile));\n      await fs.writeFile(trackingFile, JSON.stringify(tracking, null, 2), 'utf-8');\n    }\n\n    return updatedRef;\n  }\n\n  /**\n   * Get detailed execution status for a plan including all steps\n   */\n  async getPlanExecutionStatus(planSlug: string): Promise<PlanExecutionTracking | null> {\n    return this.loadPlanTracking(planSlug);\n  }\n\n  /**\n   * Sync tracking data back to the plan markdown file\n   * Updates checkboxes, timestamps, and adds execution history section\n   */\n  async syncPlanMarkdown(planSlug: string): Promise<boolean> {\n    const planPath = path.join(this.plansPath, `${planSlug}.md`);\n    const tracking = await this.loadPlanTracking(planSlug);\n\n    if (!tracking || !await fs.pathExists(planPath)) {\n      return false;\n    }\n\n    let content = await fs.readFile(planPath, 'utf-8');\n\n    // Update frontmatter with progress and phase statuses\n    content = this.updateFrontmatterProgress(content, tracking);\n\n    // Update step checkboxes in markdown body\n    content = this.updateStepCheckboxes(content, tracking);\n\n    // Add/update execution history section\n    content = this.updateExecutionHistorySection(content, tracking);\n\n    await fs.writeFile(planPath, content, 'utf-8');\n    return true;\n  }\n\n  /**\n   * Record commit information for a completed phase\n   */\n  async recordPhaseCommit(\n    planSlug: string,\n    phaseId: string,\n    commitInfo: {\n      hash: string;\n      shortHash: string;\n      committedBy?: string;\n    }\n  ): Promise<boolean> {\n    const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n    const now = new Date().toISOString();\n\n    // Load existing tracking\n    let tracking = await this.loadPlanTracking(planSlug);\n    if (!tracking) {\n      return false;\n    }\n\n    // Ensure phase exists in tracking\n    if (!tracking.phases[phaseId]) {\n      return false;\n    }\n\n    // Record commit info\n    tracking.phases[phaseId].commitHash = commitInfo.hash;\n    tracking.phases[phaseId].commitShortHash = commitInfo.shortHash;\n    tracking.phases[phaseId].committedAt = now;\n    if (commitInfo.committedBy) {\n      tracking.phases[phaseId].committedBy = commitInfo.committedBy;\n    }\n\n    tracking.lastUpdated = now;\n\n    // Save tracking\n    await fs.ensureDir(path.dirname(trackingFile));\n    await fs.writeFile(trackingFile, JSON.stringify(tracking, null, 2), 'utf-8');\n\n    // Sync to markdown\n    await this.syncPlanMarkdown(planSlug);\n\n    return true;\n  }\n\n  /**\n   * Load plan tracking from JSON file\n   */\n  private async loadPlanTracking(planSlug: string): Promise<PlanExecutionTracking | null> {\n    const trackingFile = path.join(this.workflowPath, 'plan-tracking', `${planSlug}.json`);\n\n    if (!await fs.pathExists(trackingFile)) {\n      return null;\n    }\n\n    try {\n      const content = await fs.readFile(trackingFile, 'utf-8');\n      const data = JSON.parse(content);\n\n      // Migrate old format to new format if needed\n      if (!data.phases || typeof data.phases !== 'object') {\n        // Old format had phases as simple status objects\n        const migratedPhases: Record<string, PlanPhaseTracking> = {};\n        if (data.phases) {\n          for (const [phaseId, phaseData] of Object.entries(data.phases as Record<string, { status: string; updatedAt?: string }>)) {\n            migratedPhases[phaseId] = {\n              phaseId,\n              status: phaseData.status as StatusType,\n              startedAt: phaseData.updatedAt,\n              completedAt: phaseData.status === 'completed' ? phaseData.updatedAt : undefined,\n              steps: [],\n            };\n          }\n        }\n        return {\n          planSlug,\n          progress: data.progress || 0,\n          phases: migratedPhases,\n          decisions: data.decisions || [],\n          lastUpdated: data.lastUpdated || new Date().toISOString(),\n        };\n      }\n\n      return data as PlanExecutionTracking;\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Calculate progress based on completed steps across all phases\n   */\n  private calculateStepProgress(tracking: PlanExecutionTracking): number {\n    let totalSteps = 0;\n    let completedSteps = 0;\n\n    for (const phase of Object.values(tracking.phases)) {\n      totalSteps += phase.steps.length;\n      completedSteps += phase.steps.filter(s => s.status === 'completed').length;\n    }\n\n    return totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0;\n  }\n\n  /**\n   * Update frontmatter with progress percentage\n   */\n  private updateFrontmatterProgress(content: string, tracking: PlanExecutionTracking): string {\n    // Check if frontmatter exists\n    if (!content.startsWith('---')) {\n      return content;\n    }\n\n    const endIndex = content.indexOf('---', 3);\n    if (endIndex === -1) {\n      return content;\n    }\n\n    let frontmatter = content.slice(0, endIndex);\n    const body = content.slice(endIndex);\n\n    // Update or add progress field\n    if (frontmatter.includes('progress:')) {\n      frontmatter = frontmatter.replace(/progress:\\s*\\d+/, `progress: ${tracking.progress}`);\n    } else {\n      // Add progress after status line or at end of frontmatter\n      if (frontmatter.includes('status:')) {\n        frontmatter = frontmatter.replace(/(status:\\s*\\w+)/, `$1\\nprogress: ${tracking.progress}`);\n      } else {\n        frontmatter = frontmatter.trimEnd() + `\\nprogress: ${tracking.progress}\\n`;\n      }\n    }\n\n    // Update or add lastUpdated field\n    if (frontmatter.includes('lastUpdated:')) {\n      frontmatter = frontmatter.replace(/lastUpdated:\\s*\"[^\"]*\"/, `lastUpdated: \"${tracking.lastUpdated}\"`);\n    } else {\n      frontmatter = frontmatter.trimEnd() + `\\nlastUpdated: \"${tracking.lastUpdated}\"\\n`;\n    }\n\n    return frontmatter + body;\n  }\n\n  /**\n   * Update step checkboxes in markdown body\n   */\n  private updateStepCheckboxes(content: string, tracking: PlanExecutionTracking): string {\n    // Match numbered steps with optional existing checkboxes\n    // Patterns like: \"1. **Step text**\" or \"1. [ ] **Step text**\" or \"1. [x] **Step text**\"\n    const stepPattern = /^(\\d+)\\.\\s*(?:\\[[ x]\\]\\s*)?(.+?)(?:\\s*\\*\\([^)]*\\)\\*)?$/gm;\n\n    // Track which phase we're currently in by finding phase headers\n    let currentPhaseId: string | null = null;\n\n    // Split content into lines for processing\n    const lines = content.split('\\n');\n    const updatedLines: string[] = [];\n\n    for (const line of lines) {\n      // Check for phase header\n      const phaseMatch = line.match(/^###\\s+Phase\\s+(\\d+)/);\n      if (phaseMatch) {\n        currentPhaseId = `phase-${phaseMatch[1]}`;\n        updatedLines.push(line);\n        continue;\n      }\n\n      // Check for numbered step\n      const stepMatch = line.match(/^(\\d+)\\.\\s*(?:\\[[ x]\\]\\s*)?(.+?)(?:\\s*\\*\\([^)]*\\)\\*)?$/);\n      if (stepMatch && currentPhaseId) {\n        const stepNum = parseInt(stepMatch[1], 10);\n        const stepText = stepMatch[2].trim();\n\n        // Find step in tracking\n        const phaseTracking = tracking.phases[currentPhaseId];\n        const stepTracking = phaseTracking?.steps.find(s => s.stepIndex === stepNum);\n\n        if (stepTracking) {\n          const checkMark = stepTracking.status === 'completed' ? '[x]' : '[ ]';\n          let timestamp = '';\n          if (stepTracking.completedAt) {\n            timestamp = ` *(completed: ${stepTracking.completedAt})*`;\n          } else if (stepTracking.startedAt && stepTracking.status === 'in_progress') {\n            timestamp = ` *(in progress since: ${stepTracking.startedAt})*`;\n          }\n          updatedLines.push(`${stepNum}. ${checkMark} ${stepText}${timestamp}`);\n          continue;\n        }\n      }\n\n      updatedLines.push(line);\n    }\n\n    return updatedLines.join('\\n');\n  }\n\n  /**\n   * Add or update execution history section in markdown\n   */\n  private updateExecutionHistorySection(content: string, tracking: PlanExecutionTracking): string {\n    const historySection = this.generateExecutionHistoryMarkdown(tracking);\n\n    // Check if section exists\n    const historyMarker = '## Execution History';\n    const existingIndex = content.indexOf(historyMarker);\n\n    if (existingIndex > -1) {\n      // Find the end of the section (next ## header or end of file)\n      const afterHistory = content.slice(existingIndex);\n      const nextSectionMatch = afterHistory.match(/\\n## [^E]/);\n      if (nextSectionMatch && nextSectionMatch.index) {\n        const endIndex = existingIndex + nextSectionMatch.index;\n        content = content.slice(0, existingIndex) + historySection + content.slice(endIndex);\n      } else {\n        // History is the last section\n        content = content.slice(0, existingIndex) + historySection;\n      }\n    } else {\n      // Add before \"## Evidence\" or \"## Rollback\" or at end\n      const insertPoints = ['## Evidence', '## Rollback'];\n      let insertIndex = -1;\n\n      for (const marker of insertPoints) {\n        const idx = content.indexOf(marker);\n        if (idx > -1) {\n          insertIndex = idx;\n          break;\n        }\n      }\n\n      if (insertIndex > -1) {\n        content = content.slice(0, insertIndex) + historySection + '\\n\\n' + content.slice(insertIndex);\n      } else {\n        content = content.trimEnd() + '\\n\\n' + historySection;\n      }\n    }\n\n    return content;\n  }\n\n  /**\n   * Generate execution history markdown section\n   */\n  private generateExecutionHistoryMarkdown(tracking: PlanExecutionTracking): string {\n    const lines = [\n      '## Execution History',\n      '',\n      `> Last updated: ${tracking.lastUpdated} | Progress: ${tracking.progress}%`,\n      '',\n    ];\n\n    // Sort phases by ID\n    const sortedPhases = Object.entries(tracking.phases).sort(([a], [b]) => a.localeCompare(b));\n\n    for (const [phaseId, phase] of sortedPhases) {\n      const statusIcon = phase.status === 'completed' ? '[DONE]' :\n                         phase.status === 'in_progress' ? '[IN PROGRESS]' :\n                         phase.status === 'skipped' ? '[SKIPPED]' :\n                         '[PENDING]';\n\n      lines.push(`### ${phaseId} ${statusIcon}`);\n\n      if (phase.startedAt) {\n        lines.push(`- Started: ${phase.startedAt}`);\n      }\n      if (phase.completedAt) {\n        lines.push(`- Completed: ${phase.completedAt}`);\n      }\n\n      if (phase.steps.length > 0) {\n        lines.push('');\n        const sortedSteps = [...phase.steps].sort((a, b) => a.stepIndex - b.stepIndex);\n        for (const step of sortedSteps) {\n          const check = step.status === 'completed' ? 'x' : ' ';\n          let line = `- [${check}] Step ${step.stepIndex}: ${step.description}`;\n\n          if (step.completedAt) {\n            line += ` *(${step.completedAt})*`;\n          } else if (step.startedAt && step.status === 'in_progress') {\n            line += ` *(in progress)*`;\n          }\n\n          lines.push(line);\n\n          if (step.output) {\n            lines.push(`  - Output: ${step.output}`);\n          }\n          if (step.notes) {\n            lines.push(`  - Notes: ${step.notes}`);\n          }\n        }\n      }\n\n      lines.push('');\n    }\n\n    return lines.join('\\n');\n  }\n\n  /**\n   * Parse plan markdown file to extract info\n   */\n  private parsePlanFile(content: string, slug: string): { title: string; summary?: string } {\n    const titleMatch = content.match(/^#\\s+(.+?)(?:\\s+Plan)?$/m);\n    const summaryMatch = content.match(/^>\\s*(.+)$/m);\n\n    return {\n      title: titleMatch?.[1] || slug,\n      summary: summaryMatch?.[1],\n    };\n  }\n\n  /**\n   * Parse frontmatter from plan content\n   */\n  private parseFrontMatter(content: string): {\n    agents: Array<{ type: string; role?: string }>;\n    docs: string[];\n    phases: Array<{ id: string; name: string; prevc: string }>;\n  } | null {\n    // Check if content starts with frontmatter\n    if (!content.startsWith('---')) {\n      return null;\n    }\n\n    const endIndex = content.indexOf('---', 3);\n    if (endIndex === -1) {\n      return null;\n    }\n\n    const frontMatterContent = content.slice(3, endIndex).trim();\n    const result: {\n      agents: Array<{ type: string; role?: string }>;\n      docs: string[];\n      phases: Array<{ id: string; name: string; prevc: string }>;\n    } = {\n      agents: [],\n      docs: [],\n      phases: [],\n    };\n\n    // Parse agents section\n    const agentsMatch = frontMatterContent.match(/agents:\\s*\\n((?:\\s+-[^\\n]+\\n?)+)/);\n    if (agentsMatch) {\n      const agentLines = agentsMatch[1].split('\\n').filter(l => l.trim());\n      let currentAgent: { type: string; role?: string } | null = null;\n\n      for (const line of agentLines) {\n        const typeMatch = line.match(/type:\\s*\"([^\"]+)\"/);\n        const roleMatch = line.match(/role:\\s*\"([^\"]+)\"/);\n\n        if (typeMatch) {\n          if (currentAgent) {\n            result.agents.push(currentAgent);\n          }\n          currentAgent = { type: typeMatch[1] };\n        }\n        if (roleMatch && currentAgent) {\n          currentAgent.role = roleMatch[1];\n        }\n      }\n      if (currentAgent) {\n        result.agents.push(currentAgent);\n      }\n    }\n\n    // Parse docs section\n    const docsMatch = frontMatterContent.match(/docs:\\s*\\n((?:\\s+-[^\\n]+\\n?)+)/);\n    if (docsMatch) {\n      const docLines = docsMatch[1].split('\\n').filter(l => l.trim());\n      for (const line of docLines) {\n        const docMatch = line.match(/-\\s*\"([^\"]+)\"/);\n        if (docMatch) {\n          result.docs.push(docMatch[1]);\n        }\n      }\n    }\n\n    // Parse phases section\n    const phasesMatch = frontMatterContent.match(/phases:\\s*\\n((?:\\s+-[^\\n]+\\n?)+)/);\n    if (phasesMatch) {\n      const phaseLines = phasesMatch[1].split('\\n').filter(l => l.trim());\n      let currentPhase: { id: string; name: string; prevc: string } | null = null;\n\n      for (const line of phaseLines) {\n        const idMatch = line.match(/id:\\s*\"([^\"]+)\"/);\n        const nameMatch = line.match(/name:\\s*\"([^\"]+)\"/);\n        const prevcMatch = line.match(/prevc:\\s*\"([^\"]+)\"/);\n\n        if (idMatch) {\n          if (currentPhase && currentPhase.id && currentPhase.name && currentPhase.prevc) {\n            result.phases.push(currentPhase);\n          }\n          currentPhase = { id: idMatch[1], name: '', prevc: '' };\n        }\n        if (nameMatch && currentPhase) {\n          currentPhase.name = nameMatch[1];\n        }\n        if (prevcMatch && currentPhase) {\n          currentPhase.prevc = prevcMatch[1];\n        }\n      }\n      if (currentPhase && currentPhase.id && currentPhase.name && currentPhase.prevc) {\n        result.phases.push(currentPhase);\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Parse plan file into LinkedPlan structure\n   */\n  private parsePlanToLinked(content: string, ref: PlanReference): LinkedPlan {\n    // Try frontmatter first, then fallback to body parsing\n    const frontMatter = this.parseFrontMatter(content);\n\n    const phases = frontMatter?.phases.length\n      ? frontMatter.phases.map(p => ({\n          id: p.id,\n          name: p.name,\n          prevcPhase: p.prevc as PrevcPhase,\n          steps: [],\n          status: 'pending' as const,\n        }))\n      : this.extractPhasesFromBody(content);\n\n    const agents = frontMatter?.agents.length\n      ? frontMatter.agents.map(a => a.type)\n      : this.extractAgentsFromBody(content);\n\n    const docs = frontMatter?.docs.length\n      ? frontMatter.docs\n      : this.extractDocsFromBody(content);\n\n    const decisions = this.extractDecisions();\n\n    const completedPhases = phases.filter(p => p.status === 'completed').length;\n    const progress = phases.length > 0 ? Math.round((completedPhases / phases.length) * 100) : 0;\n\n    const currentPhase = phases.find(p => p.status === 'in_progress')?.id;\n\n    return {\n      ref,\n      phases,\n      decisions,\n      risks: [],\n      agents,\n      docs,\n      progress,\n      currentPhase,\n      // Include full agent lineup with roles from frontmatter\n      agentLineup: frontMatter?.agents || agents.map(a => ({ type: a })),\n    };\n  }\n\n  /**\n   * Extract phases from plan markdown body (fallback)\n   */\n  private extractPhasesFromBody(content: string): PlanPhase[] {\n    const phases: PlanPhase[] = [];\n\n    // Match \"### Phase N — Name\" or \"### Phase N - Name\"\n    const phaseRegex = /###\\s+Phase\\s+(\\d+)\\s*[—-]\\s*(.+)/g;\n    let match;\n\n    while ((match = phaseRegex.exec(content)) !== null) {\n      const phaseNum = match[1];\n      const phaseName = match[2].trim();\n      const phaseId = `phase-${phaseNum}`;\n\n      // Determine PREVC mapping based on phase name\n      const lowerName = phaseName.toLowerCase();\n      let prevcPhase: PrevcPhase = 'E'; // Default to Execution\n\n      for (const [keyword, phase] of Object.entries(PLAN_PHASE_TO_PREVC)) {\n        if (lowerName.includes(keyword)) {\n          prevcPhase = phase;\n          break;\n        }\n      }\n\n      phases.push({\n        id: phaseId,\n        name: phaseName,\n        prevcPhase,\n        steps: [],\n        status: 'pending',\n      });\n    }\n\n    // If no phases found, create default structure\n    if (phases.length === 0) {\n      phases.push(\n        { id: 'phase-1', name: 'Discovery & Alignment', prevcPhase: 'P', steps: [], status: 'pending' },\n        { id: 'phase-2', name: 'Implementation', prevcPhase: 'E', steps: [], status: 'pending' },\n        { id: 'phase-3', name: 'Validation & Handoff', prevcPhase: 'V', steps: [], status: 'pending' }\n      );\n    }\n\n    return phases;\n  }\n\n  /**\n   * Extract decisions from plan content\n   */\n  private extractDecisions(): PlanDecision[] {\n    // Decisions are typically recorded during execution, start empty\n    return [];\n  }\n\n  /**\n   * Extract agents from plan body (fallback)\n   */\n  private extractAgentsFromBody(content: string): string[] {\n    const agents: string[] = [];\n\n    // Match agent references in table rows\n    const agentRegex = /\\[([^\\]]+)\\]\\(\\.\\.\\/agents\\/([^)]+)\\.md\\)/g;\n    let match;\n\n    while ((match = agentRegex.exec(content)) !== null) {\n      const agentType = match[2];\n      if (!agents.includes(agentType)) {\n        agents.push(agentType);\n      }\n    }\n\n    return agents;\n  }\n\n  /**\n   * Extract documentation references from plan body (fallback)\n   */\n  private extractDocsFromBody(content: string): string[] {\n    const docs: string[] = [];\n\n    // Match doc references\n    const docRegex = /\\[([^\\]]+)\\]\\(\\.\\.\\/docs\\/([^)]+)\\)/g;\n    let match;\n\n    while ((match = docRegex.exec(content)) !== null) {\n      const docPath = match[2];\n      if (!docs.includes(docPath)) {\n        docs.push(docPath);\n      }\n    }\n\n    return docs;\n  }\n\n  /**\n   * Add plan reference to workflow tracking\n   */\n  private async addPlanToWorkflow(ref: PlanReference): Promise<void> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n\n    let plans: WorkflowPlans = { active: [], completed: [] };\n    if (await fs.pathExists(plansFile)) {\n      const content = await fs.readFile(plansFile, 'utf-8');\n      try {\n        plans = JSON.parse(content) || { active: [], completed: [] };\n      } catch {\n        plans = { active: [], completed: [] };\n      }\n    }\n\n    // Preserve approval metadata if the plan was already linked in another workflow\n    const existingRef = [...plans.active, ...plans.completed].find((p) => p.slug === ref.slug);\n    const nextRef: PlanReference = existingRef\n      ? {\n          ...ref,\n          approval_status: existingRef.approval_status,\n          approved_at: existingRef.approved_at,\n          approved_by: existingRef.approved_by,\n        }\n      : ref;\n\n    // Remove if already exists\n    plans.active = plans.active.filter(p => p.slug !== ref.slug);\n    plans.completed = plans.completed.filter(p => p.slug !== ref.slug);\n\n    // Add to active\n    plans.active.push(nextRef);\n\n    // Set as primary if first plan\n    if (!plans.primary) {\n      plans.primary = nextRef.slug;\n    }\n\n    await fs.ensureDir(this.workflowPath);\n    await fs.writeFile(plansFile, JSON.stringify(plans, null, 2), 'utf-8');\n  }\n\n  /**\n   * Clear all plans and tracking data\n   * Used when deleting/resetting a workflow\n   */\n  async clearAllPlans(): Promise<void> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n    const trackingDir = path.join(this.workflowPath, 'plan-tracking');\n\n    // Remove plans.json\n    if (await fs.pathExists(plansFile)) {\n      await fs.remove(plansFile);\n    }\n\n    // Remove plan-tracking directory\n    if (await fs.pathExists(trackingDir)) {\n      await fs.remove(trackingDir);\n    }\n  }\n\n  /**\n   * Archive all plans and tracking data\n   * Moves files to .context/workflow/archive/{timestamp}/\n   */\n  async archivePlans(): Promise<void> {\n    const plansFile = path.join(this.workflowPath, 'plans.json');\n    const trackingDir = path.join(this.workflowPath, 'plan-tracking');\n\n    // Check if there's anything to archive\n    const hasPlans = await fs.pathExists(plansFile);\n    const hasTracking = await fs.pathExists(trackingDir);\n\n    if (!hasPlans && !hasTracking) {\n      return;\n    }\n\n    // Create archive directory with timestamp\n    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n    const archiveDir = path.join(this.workflowPath, 'archive', `plans-${timestamp}`);\n\n    await fs.ensureDir(archiveDir);\n\n    // Move plans.json to archive\n    if (hasPlans) {\n      await fs.move(plansFile, path.join(archiveDir, 'plans.json'));\n    }\n\n    // Move plan-tracking directory to archive\n    if (hasTracking) {\n      await fs.move(trackingDir, path.join(archiveDir, 'plan-tracking'));\n    }\n  }\n\n  /**\n   * Automatically create a git commit when a phase completes\n   * @param planSlug Plan identifier\n   * @param phaseId Phase identifier\n   * @returns true if commit was created, false if skipped or failed\n   */\n  private async autoCommitPhase(planSlug: string, phaseId: string): Promise<boolean> {\n    try {\n      // Load plan to get phase details\n      const plan = await this.getLinkedPlan(planSlug);\n      if (!plan) {\n        console.warn(`[AutoCommit] Plan not found: ${planSlug}`);\n        return false;\n      }\n\n      const phase = plan.phases.find(p => p.id === phaseId);\n      if (!phase) {\n        console.warn(`[AutoCommit] Phase not found: ${phaseId} in plan ${planSlug}`);\n        return false;\n      }\n\n      // Initialize git service\n      const gitService = new GitService(this.repoPath);\n\n      // Check if this is a git repository\n      if (!gitService.isGitRepository()) {\n        console.warn('[AutoCommit] Not a git repository - skipping auto-commit');\n        return false;\n      }\n\n      // Default commit message from phase's commitCheckpoint or generate one\n      const commitMessage = phase.commitCheckpoint ||\n        `chore(plan): complete ${phase.name} for ${planSlug}`;\n\n      // Stage .context/** files (plan tracking and markdown updates)\n      const stagePatterns = ['.context/**'];\n\n      try {\n        const stagedFiles = gitService.stageFiles(stagePatterns);\n\n        if (stagedFiles.length === 0) {\n          console.info('[AutoCommit] No files to commit - skipping');\n          return false;\n        }\n\n        // Create the commit with AI Context Agent as co-author\n        const coAuthor = 'AI Context Agent';\n        const commitResult = gitService.commit(commitMessage, coAuthor);\n\n        // Record the commit in plan tracking\n        await this.recordPhaseCommit(planSlug, phaseId, {\n          hash: commitResult.hash,\n          shortHash: commitResult.shortHash,\n          committedBy: coAuthor,\n        });\n\n        console.info(`[AutoCommit] Created commit ${commitResult.shortHash} for phase ${phaseId}`);\n        return true;\n      } catch (error) {\n        // Non-critical failure - log but don't throw\n        console.warn(`[AutoCommit] Failed to create commit for phase ${phaseId}:`, error);\n        return false;\n      }\n    } catch (error) {\n      // Catch-all to prevent breaking the main updatePlanStep flow\n      console.error('[AutoCommit] Unexpected error in autoCommitPhase:', error);\n      return false;\n    }\n  }\n}\n\n// Export singleton factory\nexport function createPlanLinker(repoPath: string, statusManager?: PrevcStatusManager, autoCommitOnPhaseComplete: boolean = true): PlanLinker {\n  return new PlanLinker(repoPath, statusManager, autoCommitOnPhaseComplete);\n}\n"
  },
  {
    "path": "src/workflow/plans/types.ts",
    "content": "/**\n * Plan-Workflow Integration Types\n *\n * Types for linking implementation plans to the PREVC workflow system.\n * Plans provide the \"what\" and \"how\", workflow provides the \"when\" and tracking.\n */\n\nimport { PrevcPhase, PrevcRole, StatusType } from '../types';\n\n/**\n * Plan phase mapping to PREVC phases\n */\nexport const PLAN_PHASE_TO_PREVC: Record<string, PrevcPhase> = {\n  'discovery': 'P',      // Discovery & Alignment → Planning\n  'alignment': 'P',\n  'review': 'R',         // Architecture Review → Review\n  'architecture': 'R',\n  'implementation': 'E', // Implementation → Execution\n  'build': 'E',\n  'validation': 'V',     // Validation → Validation\n  'testing': 'V',\n  'handoff': 'C',        // Handoff → Confirmation\n  'deployment': 'C',\n};\n\n/**\n * Reference to a plan file\n */\nexport interface PlanReference {\n  /** Slug/identifier of the plan */\n  slug: string;\n  /** Path to the plan file relative to .context */\n  path: string;\n  /** Display title */\n  title: string;\n  /** Brief summary */\n  summary?: string;\n  /** When the plan was linked */\n  linkedAt: string;\n  /** Plan status */\n  status: 'active' | 'completed' | 'paused' | 'cancelled';\n  /** Approval status for workflow gates */\n  approval_status?: 'pending' | 'approved' | 'rejected';\n  /** When the plan was approved */\n  approved_at?: string;\n  /** Who approved the plan */\n  approved_by?: string;\n}\n\n/**\n * Step within a plan phase\n */\nexport interface PlanStep {\n  /** Step number within the phase */\n  order: number;\n  /** Step description */\n  description: string;\n  /** Assigned role/owner */\n  assignee?: PrevcRole | string;\n  /** Step status */\n  status: StatusType;\n  /** Output artifacts produced */\n  outputs?: string[];\n  /** Completion timestamp */\n  completedAt?: string;\n}\n\n/**\n * Phase within a plan (maps to PREVC phases)\n */\nexport interface PlanPhase {\n  /** Phase identifier (e.g., \"phase-1\", \"discovery\") */\n  id: string;\n  /** Display name */\n  name: string;\n  /** Mapped PREVC phase */\n  prevcPhase: PrevcPhase;\n  /** Steps in this phase */\n  steps: PlanStep[];\n  /** Phase status */\n  status: StatusType;\n  /** Commit checkpoint message */\n  commitCheckpoint?: string;\n  /** Start timestamp */\n  startedAt?: string;\n  /** Completion timestamp */\n  completedAt?: string;\n}\n\n/**\n * Decision record within a plan\n */\nexport interface PlanDecision {\n  /** Decision identifier */\n  id: string;\n  /** Decision title */\n  title: string;\n  /** Decision description/rationale */\n  description: string;\n  /** Who made the decision */\n  decidedBy?: PrevcRole | string;\n  /** When the decision was made */\n  decidedAt?: string;\n  /** Related PREVC phase */\n  phase?: PrevcPhase;\n  /** Status: proposed, accepted, rejected, superseded */\n  status: 'proposed' | 'accepted' | 'rejected' | 'superseded';\n  /** Alternatives considered */\n  alternatives?: string[];\n  /** Consequences of this decision */\n  consequences?: string[];\n}\n\n/**\n * Risk entry within a plan\n */\nexport interface PlanRisk {\n  /** Risk identifier */\n  id: string;\n  /** Risk description */\n  description: string;\n  /** Probability: low, medium, high */\n  probability: 'low' | 'medium' | 'high';\n  /** Impact: low, medium, high */\n  impact: 'low' | 'medium' | 'high';\n  /** Mitigation strategy */\n  mitigation?: string;\n  /** Owner responsible for mitigation */\n  owner?: PrevcRole | string;\n  /** Current status */\n  status: 'identified' | 'mitigated' | 'occurred' | 'closed';\n}\n\n/**\n * Agent lineup entry with role description\n */\nexport interface AgentLineupEntry {\n  /** Agent type identifier */\n  type: string;\n  /** Role/responsibility in this plan */\n  role?: string;\n}\n\n/**\n * Complete plan structure linked to workflow\n */\nexport interface LinkedPlan {\n  /** Plan reference info */\n  ref: PlanReference;\n  /** Plan phases with PREVC mapping */\n  phases: PlanPhase[];\n  /** Key decisions made during planning */\n  decisions: PlanDecision[];\n  /** Identified risks */\n  risks: PlanRisk[];\n  /** Agents involved in this plan (simple list) */\n  agents: string[];\n  /** Full agent lineup with roles (from frontmatter) */\n  agentLineup: AgentLineupEntry[];\n  /** Documentation touchpoints */\n  docs: string[];\n  /** Overall progress percentage */\n  progress: number;\n  /** Current active phase */\n  currentPhase?: string;\n}\n\n/**\n * Plan tracking in workflow status\n */\nexport interface WorkflowPlans {\n  /** Currently active plans */\n  active: PlanReference[];\n  /** Completed plans */\n  completed: PlanReference[];\n  /** Current primary plan (if any) */\n  primary?: string;\n}\n\n/**\n * Plan-workflow sync event\n */\nexport interface PlanSyncEvent {\n  /** Event type */\n  type: 'plan_linked' | 'plan_updated' | 'phase_completed' | 'decision_made' | 'risk_updated';\n  /** Plan slug */\n  planSlug: string;\n  /** Affected PREVC phase */\n  phase?: PrevcPhase;\n  /** Event timestamp */\n  timestamp: string;\n  /** Event details */\n  details?: Record<string, unknown>;\n}\n\n/**\n * Individual step execution tracking\n */\nexport interface StepExecution {\n  /** Step index (1-based) within the phase */\n  stepIndex: number;\n  /** Step description */\n  description: string;\n  /** Current status */\n  status: StatusType;\n  /** When step was started */\n  startedAt?: string;\n  /** When step was completed */\n  completedAt?: string;\n  /** Output artifact produced */\n  output?: string;\n  /** Execution notes */\n  notes?: string;\n}\n\n/**\n * Enhanced phase tracking with step-level detail\n */\nexport interface PlanPhaseTracking {\n  /** Phase ID */\n  phaseId: string;\n  /** Phase status */\n  status: StatusType;\n  /** When phase was started */\n  startedAt?: string;\n  /** When phase was completed */\n  completedAt?: string;\n  /** Individual step execution records */\n  steps: StepExecution[];\n  /** Full commit hash when phase was committed */\n  commitHash?: string;\n  /** Short commit hash for display */\n  commitShortHash?: string;\n  /** When the commit was made */\n  committedAt?: string;\n  /** Agent or role that triggered the commit */\n  committedBy?: string;\n}\n\n/**\n * Complete plan execution tracking\n * Stored in .context/workflow/plan-tracking/{slug}.json\n */\nexport interface PlanExecutionTracking {\n  /** Plan slug */\n  planSlug: string;\n  /** Overall progress (0-100) */\n  progress: number;\n  /** Approval status mirrored from workflow state */\n  approvalStatus?: 'pending' | 'approved' | 'rejected';\n  /** When the linked plan was approved */\n  approvedAt?: string;\n  /** Who approved the linked plan */\n  approvedBy?: string;\n  /** Phase tracking with steps */\n  phases: Record<string, PlanPhaseTracking>;\n  /** Decisions recorded during execution */\n  decisions: PlanDecision[];\n  /** Last update timestamp */\n  lastUpdated: string;\n}\n"
  },
  {
    "path": "src/workflow/prevcConfig.ts",
    "content": "/**\n * PREVC Role Configuration\n *\n * Defines responsibilities, outputs, and specialists for each PREVC role.\n */\n\nimport { PrevcRole, RoleDefinition } from './types';\n\n/**\n * Complete configuration for all PREVC roles\n */\nexport const ROLE_CONFIG: Record<PrevcRole, RoleDefinition> = {\n  planner: {\n    phase: 'P',\n    responsibilities: [\n      'Conduct discovery and requirements gathering',\n      'Create specifications and project scope',\n      'Define acceptance criteria',\n      'Generate PRD or Tech Spec',\n      'Identify risks and dependencies',\n    ],\n    outputs: ['prd', 'tech-spec', 'requirements'],\n    specialists: [],\n  },\n\n  designer: {\n    phase: ['P', 'R'],\n    responsibilities: [\n      'Create wireframes and prototypes',\n      'Define design system and components',\n      'Ensure accessibility and usability',\n      'Document UI/UX patterns',\n      'Validate user flows',\n    ],\n    outputs: ['wireframes', 'design-spec', 'ui-components'],\n    specialists: ['frontend-specialist'],\n  },\n\n  architect: {\n    phase: 'R',\n    responsibilities: [\n      'Define system architecture',\n      'Create ADRs (Architecture Decision Records)',\n      'Choose technologies and patterns',\n      'Ensure scalability and maintainability',\n      'Review technical impact of decisions',\n    ],\n    outputs: ['architecture', 'adr', 'tech-decisions'],\n    specialists: ['architect-specialist'],\n  },\n\n  developer: {\n    phase: 'E',\n    responsibilities: [\n      'Implement code according to specifications',\n      'Follow defined patterns and architecture',\n      'Create basic unit tests',\n      'Document code when necessary',\n      'Solve technical problems',\n    ],\n    outputs: ['code', 'unit-tests'],\n    specialists: [\n      'feature-developer',\n      'bug-fixer',\n      'backend-specialist',\n      'frontend-specialist',\n      'mobile-specialist',\n      'database-specialist',\n      'devops-specialist',\n    ],\n  },\n\n  qa: {\n    phase: 'V',\n    responsibilities: [\n      'Create and execute integration tests',\n      'Validate security and performance',\n      'Ensure quality gates',\n      'Report and track bugs',\n      'Validate acceptance criteria',\n    ],\n    outputs: ['test-report', 'qa-approval', 'bug-report'],\n    specialists: ['test-writer', 'security-auditor', 'performance-optimizer'],\n  },\n\n  reviewer: {\n    phase: 'V',\n    responsibilities: [\n      'Review code and architecture',\n      'Ensure compliance with standards',\n      'Suggest improvements and optimizations',\n      'Validate best practices',\n      'Approve or request changes',\n    ],\n    outputs: ['review-comments', 'approval'],\n    specialists: ['code-reviewer'],\n  },\n\n  documenter: {\n    phase: 'C',\n    responsibilities: [\n      'Create technical documentation',\n      'Update README and APIs',\n      'Prepare handoff to production',\n      'Generate changelog and release notes',\n      'Document important decisions',\n    ],\n    outputs: ['documentation', 'changelog', 'readme'],\n    specialists: ['documentation-writer'],\n  },\n\n  'solo-dev': {\n    phase: ['P', 'R', 'E', 'V', 'C'],\n    responsibilities: [\n      'Execute complete flow for small tasks',\n      'Bug fixes and quick refactorings',\n      'Low complexity features',\n      'Maintenance of existing code',\n      'Adjustments and specific tweaks',\n    ],\n    outputs: ['code', 'tests', 'docs'],\n    specialists: ['refactoring-specialist', 'bug-fixer'],\n  },\n};\n\n/**\n * Get the configuration for a specific role\n */\nexport function getRoleConfig(role: PrevcRole): RoleDefinition {\n  return ROLE_CONFIG[role];\n}\n\n/**\n * Get all roles that participate in a specific phase\n */\nexport function getRolesForPhase(phase: string): PrevcRole[] {\n  return (Object.entries(ROLE_CONFIG) as [PrevcRole, RoleDefinition][])\n    .filter(([, config]) => {\n      if (Array.isArray(config.phase)) {\n        return config.phase.includes(phase as 'P' | 'R' | 'E' | 'V' | 'C');\n      }\n      return config.phase === phase;\n    })\n    .map(([role]) => role);\n}\n\n/**\n * Get all outputs for a specific role\n */\nexport function getOutputsForRole(role: PrevcRole): string[] {\n  return ROLE_CONFIG[role]?.outputs || [];\n}\n\n/**\n * Get all responsibilities for a specific role\n */\nexport function getResponsibilitiesForRole(role: PrevcRole): string[] {\n  return ROLE_CONFIG[role]?.responsibilities || [];\n}\n"
  },
  {
    "path": "src/workflow/roles.ts",
    "content": "/**\n * PREVC Workflow Roles\n *\n * Defines the available roles in the PREVC workflow system\n * and their mapping to existing agent types.\n */\n\nimport { PrevcRole } from './types';\n\n/**\n * All available PREVC roles\n */\nexport const PREVC_ROLES = [\n  'planner', // P: Discovery, requirements, specifications\n  'designer', // P/R: UX, design systems, wireframes\n  'architect', // R: ADRs, technical decisions, blueprints\n  'developer', // E: Implementation, coding\n  'qa', // V: Tests, quality gates\n  'reviewer', // V: Code review, standards\n  'documenter', // C: Documentation, handoff\n  'solo-dev', // P→C: Full quick flow\n] as const;\n\n/**\n * Mapping from PREVC roles to existing agent types (specialists)\n * @deprecated Use ROLE_TO_AGENTS from orchestration/agentOrchestrator instead.\n * This mapping is kept for backward compatibility.\n */\nexport const ROLE_TO_SPECIALISTS: Record<PrevcRole, string[]> = {\n  planner: [], // New role, no existing mapping\n  designer: ['frontend-specialist'],\n  architect: ['architect-specialist'],\n  developer: [\n    'feature-developer',\n    'bug-fixer',\n    'backend-specialist',\n    'frontend-specialist',\n    'mobile-specialist',\n  ],\n  qa: ['test-writer', 'security-auditor', 'performance-optimizer'],\n  reviewer: ['code-reviewer'],\n  documenter: ['documentation-writer'],\n  'solo-dev': ['refactoring-specialist', 'bug-fixer'],\n};\n\n/**\n * Mapping from existing agent types to PREVC roles\n * @deprecated Use agent-based tracking instead of role-based.\n * This mapping is kept for backward compatibility.\n */\nexport const SPECIALIST_TO_ROLE: Record<string, PrevcRole> = {\n  'frontend-specialist': 'designer',\n  'architect-specialist': 'architect',\n  'feature-developer': 'developer',\n  'bug-fixer': 'developer',\n  'backend-specialist': 'developer',\n  'mobile-specialist': 'developer',\n  'test-writer': 'qa',\n  'security-auditor': 'qa',\n  'performance-optimizer': 'qa',\n  'code-reviewer': 'reviewer',\n  'documentation-writer': 'documenter',\n  'refactoring-specialist': 'solo-dev',\n  'database-specialist': 'developer',\n  'devops-specialist': 'developer',\n};\n\n/**\n * Role display names (English)\n */\nexport const ROLE_DISPLAY_NAMES: Record<PrevcRole, string> = {\n  planner: 'Planner',\n  designer: 'Designer',\n  architect: 'Architect',\n  developer: 'Developer',\n  qa: 'QA Engineer',\n  reviewer: 'Reviewer',\n  documenter: 'Documenter',\n  'solo-dev': 'Solo Dev',\n};\n\n/**\n * Role display names in English (alias for consistency)\n */\nexport const ROLE_DISPLAY_NAMES_EN = ROLE_DISPLAY_NAMES;\n\n/**\n * Role display names in Portuguese (for i18n)\n */\nexport const ROLE_DISPLAY_NAMES_PT: Record<PrevcRole, string> = {\n  planner: 'Planejador',\n  designer: 'Designer',\n  architect: 'Arquiteto',\n  developer: 'Desenvolvedor',\n  qa: 'QA',\n  reviewer: 'Revisor',\n  documenter: 'Documentador',\n  'solo-dev': 'Solo Dev',\n};\n\n/**\n * Check if a string is a valid PREVC role\n */\nexport function isValidRole(role: string): role is PrevcRole {\n  return PREVC_ROLES.includes(role as PrevcRole);\n}\n\n/**\n * Get the PREVC role for an existing agent type\n * @deprecated Use agent-based tracking instead of role-based.\n */\nexport function getRoleForSpecialist(specialist: string): PrevcRole | null {\n  return SPECIALIST_TO_ROLE[specialist] || null;\n}\n\n/**\n * Get all specialists for a PREVC role\n * @deprecated Use ROLE_TO_AGENTS from orchestration/agentOrchestrator instead.\n */\nexport function getSpecialistsForRole(role: PrevcRole): string[] {\n  return ROLE_TO_SPECIALISTS[role] || [];\n}\n"
  },
  {
    "path": "src/workflow/scaling.ts",
    "content": "/**\n * PREVC Scale-Adaptive Routing\n *\n * Determines which phases and roles are needed based on project scale.\n */\n\nimport { ProjectScale, PrevcPhase, PrevcRole, ScaleRoute, ProjectContext } from './types';\n\n/**\n * Scale route configurations\n * Defines which phases, roles, and documents are required for each scale level.\n */\nexport const SCALE_ROUTES: Record<ProjectScale, ScaleRoute> = {\n  [ProjectScale.QUICK]: {\n    phases: ['E', 'V'],\n    roles: ['solo-dev'],\n    documents: ['code'],\n    skipReview: true,\n  },\n  [ProjectScale.SMALL]: {\n    phases: ['P', 'E', 'V'],\n    roles: ['planner', 'developer', 'qa'],\n    documents: ['tech-spec', 'code', 'test-report'],\n  },\n  [ProjectScale.MEDIUM]: {\n    phases: ['P', 'R', 'E', 'V'],\n    roles: ['planner', 'architect', 'developer', 'qa', 'reviewer'],\n    documents: ['prd', 'architecture', 'code', 'test-report', 'review'],\n  },\n  [ProjectScale.LARGE]: {\n    phases: ['P', 'R', 'E', 'V', 'C'],\n    roles: 'all',\n    documents: ['prd', 'architecture', 'code', 'test-report', 'documentation', 'adr'],\n  },\n};\n\n/**\n * Keywords that indicate a bug fix\n */\nconst BUG_FIX_KEYWORDS = [\n  'fix',\n  'bug',\n  'hotfix',\n  'patch',\n  'correção',\n  'corrigir',\n  'erro',\n  'issue',\n  'problema',\n];\n\n/**\n * Keywords that indicate a simple feature\n */\nconst SIMPLE_FEATURE_KEYWORDS = [\n  'add',\n  'adicionar',\n  'simple',\n  'simples',\n  'pequeno',\n  'small',\n  'minor',\n  'tweak',\n  'ajuste',\n];\n\n/**\n * Keywords that indicate security/compliance requirements\n */\nconst SECURITY_KEYWORDS = [\n  'security',\n  'segurança',\n  'compliance',\n  'audit',\n  'auditoria',\n  'gdpr',\n  'lgpd',\n  'pci',\n  'hipaa',\n  'soc2',\n];\n\n/**\n * Keywords that indicate documentation is needed\n */\nconst DOCUMENTATION_KEYWORDS = [\n  'document',\n  'documentar',\n  'docs',\n  'readme',\n  'api',\n  'public',\n  'externa',\n  'external',\n];\n\n/**\n * Check if description indicates a bug fix\n */\nfunction isBugFix(description: string): boolean {\n  const lowerDesc = description.toLowerCase();\n  return BUG_FIX_KEYWORDS.some((keyword) => lowerDesc.includes(keyword));\n}\n\n/**\n * Check if description indicates a simple feature\n */\nfunction isSimpleFeature(description: string): boolean {\n  const lowerDesc = description.toLowerCase();\n  return SIMPLE_FEATURE_KEYWORDS.some((keyword) => lowerDesc.includes(keyword));\n}\n\n/**\n * Check if description indicates security/compliance requirements\n */\nfunction requiresSecurityAudit(description: string): boolean {\n  const lowerDesc = description.toLowerCase();\n  return SECURITY_KEYWORDS.some((keyword) => lowerDesc.includes(keyword));\n}\n\n/**\n * Check if description indicates documentation is needed\n */\nfunction requiresDocumentation(description: string): boolean {\n  const lowerDesc = description.toLowerCase();\n  return DOCUMENTATION_KEYWORDS.some((keyword) => lowerDesc.includes(keyword));\n}\n\n/**\n * Detect the appropriate project scale based on context\n */\nexport function detectProjectScale(context: ProjectContext): ProjectScale {\n  const { description, files = [], complexity, hasCompliance } = context;\n\n  // QUICK: Bug fixes, small refactorings\n  if (isBugFix(description) || (files.length <= 3 && !hasCompliance)) {\n    return ProjectScale.QUICK;\n  }\n\n  // SMALL: Simple features, no new architecture\n  if (isSimpleFeature(description) && files.length <= 10) {\n    return ProjectScale.SMALL;\n  }\n\n  // LARGE: Multiple modules, documentation needed, compliance/security\n  if (\n    files.length > 30 ||\n    requiresDocumentation(description) ||\n    complexity === 'high' ||\n    hasCompliance ||\n    requiresSecurityAudit(description)\n  ) {\n    return ProjectScale.LARGE;\n  }\n\n  // MEDIUM: Default for regular features\n  return ProjectScale.MEDIUM;\n}\n\n/**\n * Get the scale route for a specific scale level\n */\nexport function getScaleRoute(scale: ProjectScale): ScaleRoute {\n  return SCALE_ROUTES[scale];\n}\n\n/**\n * Get the phases required for a specific scale\n */\nexport function getPhasesForScale(scale: ProjectScale): PrevcPhase[] {\n  return SCALE_ROUTES[scale].phases;\n}\n\n/**\n * Get the roles required for a specific scale\n */\nexport function getRolesForScale(scale: ProjectScale): PrevcRole[] | 'all' {\n  return SCALE_ROUTES[scale].roles;\n}\n\n/**\n * Check if a phase is required for a specific scale\n */\nexport function isPhaseRequiredForScale(\n  phase: PrevcPhase,\n  scale: ProjectScale\n): boolean {\n  return SCALE_ROUTES[scale].phases.includes(phase);\n}\n\n/**\n * Get scale name for display\n */\nexport function getScaleName(scale: ProjectScale): string {\n  const names: Record<ProjectScale, string> = {\n    [ProjectScale.QUICK]: 'Quick',\n    [ProjectScale.SMALL]: 'Small',\n    [ProjectScale.MEDIUM]: 'Medium',\n    [ProjectScale.LARGE]: 'Large',\n  };\n  return names[scale];\n}\n\n/**\n * Get scale from string name\n * Maps 'enterprise' to LARGE for backward compatibility\n */\nexport function getScaleFromName(name: string): ProjectScale | null {\n  const nameMap: Record<string, ProjectScale> = {\n    quick: ProjectScale.QUICK,\n    small: ProjectScale.SMALL,\n    medium: ProjectScale.MEDIUM,\n    large: ProjectScale.LARGE,\n    enterprise: ProjectScale.LARGE, // Legacy migration\n  };\n  return nameMap[name.toLowerCase()] ?? null;\n}\n\n/**\n * Get estimated time for a scale level\n */\nexport function getEstimatedTime(scale: ProjectScale): string {\n  const times: Record<ProjectScale, string> = {\n    [ProjectScale.QUICK]: '~5 min',\n    [ProjectScale.SMALL]: '~15 min',\n    [ProjectScale.MEDIUM]: '~30 min',\n    [ProjectScale.LARGE]: '~1+ hours',\n  };\n  return times[scale];\n}\n"
  },
  {
    "path": "src/workflow/skills/frontmatter.ts",
    "content": "/**\n * Skill Frontmatter Utilities\n *\n * Shared utilities for generating and parsing SKILL.md frontmatter.\n * Follows DRY principle by centralizing frontmatter logic.\n */\n\nimport { SkillMetadata } from './types';\nimport { PrevcPhase } from '../types';\n\n/**\n * Generate YAML frontmatter string from metadata\n */\nexport function generateFrontmatter(metadata: SkillMetadata, slug?: string): string {\n  const lines: string[] = ['---'];\n\n  lines.push(`name: ${slug ?? metadata.name}`);\n  lines.push(`description: ${metadata.description}`);\n\n  if (metadata.phases && metadata.phases.length > 0) {\n    lines.push(`phases: [${metadata.phases.join(', ')}]`);\n  }\n\n  if (metadata.mode !== undefined) {\n    lines.push(`mode: ${metadata.mode}`);\n  }\n\n  if (metadata.disableModelInvocation !== undefined) {\n    lines.push(`disable-model-invocation: ${metadata.disableModelInvocation}`);\n  }\n\n  lines.push('---');\n\n  return lines.join('\\n');\n}\n\n/**\n * Generate portable skill frontmatter for exported AI-tool skills.\n * Keep only the fields understood by external skill runtimes.\n */\nexport function generatePortableFrontmatter(name: string, description: string): string {\n  return ['---', `name: ${name}`, `description: ${description}`, '---'].join('\\n');\n}\n\n/**\n * Wrap content with frontmatter\n */\nexport function wrapWithFrontmatter(metadata: SkillMetadata, content: string, slug?: string): string {\n  const result = `${generateFrontmatter(metadata, slug)}\\n\\n${content}`;\n  return result.endsWith('\\n') ? result : `${result}\\n`;\n}\n\n/**\n * Wrap content with portable frontmatter for exported AI-tool skills.\n */\nexport function wrapWithPortableFrontmatter(name: string, description: string, content: string): string {\n  const result = `${generatePortableFrontmatter(name, description)}\\n\\n${content}`;\n  return result.endsWith('\\n') ? result : `${result}\\n`;\n}\n\n/**\n * Parse YAML frontmatter from SKILL.md content\n */\nexport function parseFrontmatter(content: string): { metadata: SkillMetadata; body: string } {\n  const defaultMetadata: SkillMetadata = {\n    name: '',\n    description: '',\n  };\n\n  if (!content.startsWith('---')) {\n    return { metadata: defaultMetadata, body: content };\n  }\n\n  const endIndex = content.indexOf('---', 3);\n  if (endIndex === -1) {\n    return { metadata: defaultMetadata, body: content };\n  }\n\n  const frontmatterRaw = content.substring(3, endIndex).trim();\n  const body = content.substring(endIndex + 3).trim();\n\n  const metadata: SkillMetadata = { ...defaultMetadata };\n\n  for (const line of frontmatterRaw.split('\\n')) {\n    const colonIndex = line.indexOf(':');\n    if (colonIndex === -1) continue;\n\n    const key = line.substring(0, colonIndex).trim();\n    const value = line.substring(colonIndex + 1).trim();\n\n    switch (key) {\n      case 'name':\n        metadata.name = stripQuotes(value);\n        break;\n      case 'description':\n        metadata.description = stripQuotes(value);\n        break;\n      case 'mode':\n        metadata.mode = value === 'true';\n        break;\n      case 'disable-model-invocation':\n      case 'disableModelInvocation':\n        metadata.disableModelInvocation = value === 'true';\n        break;\n      case 'phases':\n        metadata.phases = parsePhaseArray(value);\n        break;\n    }\n  }\n\n  return { metadata, body };\n}\n\n/**\n * Strip surrounding quotes from a string\n */\nfunction stripQuotes(value: string): string {\n  return value.replace(/^[\"']|[\"']$/g, '');\n}\n\n/**\n * Parse phases array from frontmatter value\n */\nfunction parsePhaseArray(value: string): PrevcPhase[] {\n  const match = value.match(/\\[(.*)\\]/);\n  if (!match) return [];\n\n  return match[1]\n    .split(',')\n    .map((p) => p.trim().replace(/[\"']/g, ''))\n    .filter((p): p is PrevcPhase => ['P', 'R', 'E', 'V', 'C'].includes(p));\n}\n"
  },
  {
    "path": "src/workflow/skills/index.ts",
    "content": "/**\n * Skills Module\n *\n * On-demand expertise for AI agents.\n */\n\nexport {\n  Skill,\n  SkillMetadata,\n  SkillReference,\n  DiscoveredSkills,\n  BUILT_IN_SKILLS,\n  BuiltInSkillType,\n  isBuiltInSkill,\n  SKILL_TO_PHASES,\n} from './types';\n\nexport {\n  SkillRegistry,\n  createSkillRegistry,\n} from './skillRegistry';\n\nexport {\n  getBuiltInSkillTemplates,\n  SkillTemplate,\n} from './skillTemplates';\n\nexport {\n  generateFrontmatter,\n  generatePortableFrontmatter,\n  wrapWithFrontmatter,\n  wrapWithPortableFrontmatter,\n  parseFrontmatter,\n} from './frontmatter';\n"
  },
  {
    "path": "src/workflow/skills/skillRegistry.ts",
    "content": "/**\n * Skill Registry\n *\n * Centralized management for skill discovery and metadata retrieval.\n * Supports both built-in and custom skills.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n  Skill,\n  SkillMetadata,\n  DiscoveredSkills,\n  BUILT_IN_SKILLS,\n  BuiltInSkillType,\n  isBuiltInSkill,\n  SKILL_TO_PHASES,\n} from './types';\nimport { getBuiltInSkillTemplates } from './skillTemplates';\nimport { parseFrontmatter, wrapWithFrontmatter } from './frontmatter';\nimport { PrevcPhase } from '../types';\n\n/** Default skills directory path */\nconst SKILLS_DIR = '.context/skills';\n\n/** Secondary skills directory (cross-client interoperability) */\nconst AGENTS_SKILLS_DIR = '.agents/skills';\n\nexport class SkillRegistry {\n  private readonly repoPath: string;\n  private readonly contextPath: string;\n  private cache: DiscoveredSkills | null = null;\n\n  constructor(repoPath: string) {\n    this.repoPath = repoPath;\n    this.contextPath = path.join(repoPath, SKILLS_DIR);\n  }\n\n  /**\n   * Discover all available skills (built-in + custom)\n   */\n  async discoverAll(): Promise<DiscoveredSkills> {\n    if (this.cache) {\n      return this.cache;\n    }\n\n    const builtIn = this.discoverBuiltInSkills();\n    const custom = this.discoverCustomSkills();\n\n    this.cache = {\n      builtIn,\n      custom,\n      all: [...builtIn, ...custom],\n    };\n\n    return this.cache;\n  }\n\n  /**\n   * Get skill metadata by slug\n   */\n  async getSkillMetadata(slug: string): Promise<Skill | null> {\n    const discovered = await this.discoverAll();\n    return discovered.all.find((s) => s.slug === slug) || null;\n  }\n\n  /**\n   * Get skill content (full SKILL.md)\n   */\n  async getSkillContent(slug: string): Promise<string | null> {\n    const skill = await this.getSkillMetadata(slug);\n    if (!skill) return null;\n\n    try {\n      if (fs.existsSync(skill.path)) {\n        return fs.readFileSync(skill.path, 'utf-8');\n      }\n\n      return wrapWithFrontmatter(skill.metadata, skill.content, skill.slug);\n    } catch (error) {\n      console.error(`Failed to read skill file: ${skill.path}`, error);\n      return null;\n    }\n  }\n\n  /**\n   * Get skills for a PREVC phase\n   */\n  async getSkillsForPhase(phase: PrevcPhase): Promise<Skill[]> {\n    const discovered = await this.discoverAll();\n    return discovered.all.filter((skill) => skill.metadata.phases?.includes(phase));\n  }\n\n  /**\n   * Clear cache\n   */\n  clearCache(): void {\n    this.cache = null;\n  }\n\n  /**\n   * Discover built-in skills from templates\n   */\n  private discoverBuiltInSkills(): Skill[] {\n    const skills: Skill[] = [];\n    const templates = getBuiltInSkillTemplates();\n\n    for (const skillType of BUILT_IN_SKILLS) {\n      const skillPath = path.join(this.contextPath, skillType, 'SKILL.md');\n\n      if (fs.existsSync(skillPath)) {\n        const skill = this.parseSkillFile(skillPath, skillType, true);\n        if (skill) {\n          skills.push(skill);\n        }\n      } else {\n        // Create virtual skill from built-in template\n        const skill = this.createBuiltInSkill(skillType, templates);\n        skills.push(skill);\n      }\n    }\n\n    return skills;\n  }\n\n  /**\n   * Discover custom skills from .context/skills/ and .agents/skills/\n   *\n   * Skills from .context/skills/ take precedence over .agents/skills/\n   * on name collisions (project-level skills override cross-client ones).\n   */\n  private discoverCustomSkills(): Skill[] {\n    const skills: Skill[] = [];\n    const discoveredSlugs = new Set<string>();\n\n    // 1. Discover from .context/skills/ (highest priority)\n    this.discoverSkillsFromDirectory(this.contextPath, skills, discoveredSlugs);\n\n    // 2. Discover from .agents/skills/ (lower priority, skip collisions)\n    const agentsSkillsPath = path.join(this.repoPath, AGENTS_SKILLS_DIR);\n    this.discoverSkillsFromDirectory(agentsSkillsPath, skills, discoveredSlugs);\n\n    return skills;\n  }\n\n  /**\n   * Discover custom skills from a specific directory\n   */\n  private discoverSkillsFromDirectory(\n    dirPath: string,\n    skills: Skill[],\n    discoveredSlugs: Set<string>\n  ): void {\n    if (!fs.existsSync(dirPath)) {\n      return;\n    }\n\n    try {\n      const entries = fs.readdirSync(dirPath, { withFileTypes: true });\n\n      for (const entry of entries) {\n        if (!entry.isDirectory()) continue;\n\n        const slug = entry.name;\n\n        // Skip built-in skills (already handled) and already-discovered slugs\n        if (isBuiltInSkill(slug)) continue;\n        if (discoveredSlugs.has(slug)) continue;\n\n        const skillPath = path.join(dirPath, slug, 'SKILL.md');\n\n        if (fs.existsSync(skillPath)) {\n          const skill = this.parseSkillFile(skillPath, slug, false);\n          if (skill) {\n            skills.push(skill);\n            discoveredSlugs.add(slug);\n          }\n        }\n      }\n    } catch (error) {\n      console.error(`Failed to discover custom skills from ${dirPath}:`, error);\n    }\n  }\n\n  /**\n   * Parse a SKILL.md file\n   */\n  private parseSkillFile(skillPath: string, slug: string, isBuiltIn: boolean): Skill | null {\n    try {\n      const content = fs.readFileSync(skillPath, 'utf-8');\n      const { metadata, body } = parseFrontmatter(content);\n\n      if (!metadata.name || !metadata.description) {\n        console.warn(`Skill ${slug} missing required name or description`);\n        return null;\n      }\n\n      const skillDir = path.dirname(skillPath);\n      const resources = this.discoverResources(skillDir);\n\n      return {\n        slug,\n        path: skillPath,\n        metadata,\n        content: body,\n        resources,\n        isBuiltIn,\n      };\n    } catch (error) {\n      console.error(`Failed to parse skill file: ${skillPath}`, error);\n      return null;\n    }\n  }\n\n  /**\n   * Discover resource files in skill directory\n   */\n  private discoverResources(skillDir: string): string[] {\n    const resources: string[] = [];\n\n    try {\n      const entries = fs.readdirSync(skillDir, { withFileTypes: true });\n\n      for (const entry of entries) {\n        if (entry.name === 'SKILL.md') continue;\n\n        const fullPath = path.join(skillDir, entry.name);\n\n        if (entry.isFile()) {\n          resources.push(fullPath);\n        } else if (entry.isDirectory()) {\n          resources.push(...this.discoverResourcesRecursive(fullPath));\n        }\n      }\n    } catch (error) {\n      // Directory might not exist or be readable\n    }\n\n    return resources;\n  }\n\n  private discoverResourcesRecursive(dir: string): string[] {\n    const resources: string[] = [];\n\n    try {\n      const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n      for (const entry of entries) {\n        const fullPath = path.join(dir, entry.name);\n        if (entry.isFile()) {\n          resources.push(fullPath);\n        } else if (entry.isDirectory()) {\n          resources.push(...this.discoverResourcesRecursive(fullPath));\n        }\n      }\n    } catch (error) {\n      // Directory might not exist or be readable\n    }\n\n    return resources;\n  }\n\n  /**\n   * Create a virtual built-in skill (not yet scaffolded)\n   */\n  private createBuiltInSkill(\n    skillType: BuiltInSkillType,\n    templates: ReturnType<typeof getBuiltInSkillTemplates>\n  ): Skill {\n    const template = templates[skillType];\n\n    return {\n      slug: skillType,\n      path: path.join(this.contextPath, skillType, 'SKILL.md'),\n      metadata: {\n        name: skillType,\n        description: template.description,\n        phases: SKILL_TO_PHASES[skillType],\n      },\n      content: template.content,\n      resources: [],\n      isBuiltIn: true,\n    };\n  }\n}\n\n/**\n * Factory function\n */\nexport function createSkillRegistry(repoPath: string): SkillRegistry {\n  return new SkillRegistry(repoPath);\n}\n"
  },
  {
    "path": "src/workflow/skills/skillTemplates.ts",
    "content": "/**\n * Built-in Skill Templates\n *\n * Separated from SkillRegistry to follow Single Responsibility Principle.\n * Each template contains description and markdown content for a built-in skill.\n *\n * Content is derived from the canonical skill definitions in\n * src/generators/shared/structures/skills/definitions.ts to maintain\n * a single source of truth.\n */\n\nimport { BuiltInSkillType } from './types';\nimport {\n  SkillDefaultContent,\n} from '../../generators/shared/structures/skills/factory';\nimport {\n  commitMessageContent,\n  prReviewContent,\n  codeReviewContent,\n  testGenerationContent,\n  documentationContent,\n  refactoringContent,\n  bugInvestigationContent,\n  featureBreakdownContent,\n  apiDesignContent,\n  securityAuditContent,\n} from '../../generators/shared/structures/skills/definitions';\n\nexport interface SkillTemplate {\n  description: string;\n  content: string;\n}\n\nfunction contentToMarkdown(title: string, overview: string, content: SkillDefaultContent): string {\n  let md = `# ${title}\\n\\n${overview}.\\n\\n`;\n  if (content.instructions) {\n    md += `## Workflow\\n${content.instructions}\\n\\n`;\n  }\n  if (content.examples) {\n    md += `## Examples\\n${content.examples}\\n\\n`;\n  }\n  if (content.guidelines) {\n    md += `## Quality Bar\\n${content.guidelines}\\n\\n`;\n  }\n  md += `## Resource Strategy\n- Add \\`scripts/\\` only when the task is fragile, repetitive, or benefits from deterministic execution.\n- Add \\`references/\\` only when details are too large or too variant-specific to keep in \\`SKILL.md\\`.\n- Add \\`assets/\\` only for files that will be consumed in the final output.\n- Keep extra docs out of the skill folder; prefer \\`SKILL.md\\` plus only the resources that materially help.`;\n  return md;\n}\n\nfunction extractTriggerBullets(whenToUse?: string): string[] {\n  if (!whenToUse) {\n    return [];\n  }\n\n  return whenToUse\n    .split('\\n')\n    .map(line => line.trim())\n    .filter(line => line.startsWith('-'))\n    .map(line => line.replace(/^-+\\s*/, '').replace(/\\.$/, '').trim())\n    .filter(Boolean);\n}\n\nfunction joinNaturalLanguage(items: string[]): string {\n  if (items.length === 0) {\n    return '';\n  }\n  if (items.length === 1) {\n    return items[0];\n  }\n  if (items.length === 2) {\n    return `${items[0]} or ${items[1]}`;\n  }\n  return `${items.slice(0, -1).join(', ')}, or ${items[items.length - 1]}`;\n}\n\nfunction buildTemplateDescription(baseDescription: string, content: SkillDefaultContent): string {\n  const triggers = extractTriggerBullets(content.whenToUse);\n  if (triggers.length === 0) {\n    return baseDescription;\n  }\n\n  return `${baseDescription}. Use when ${joinNaturalLanguage(triggers)}`;\n}\n\n/**\n * Get all built-in skill templates\n */\nexport function getBuiltInSkillTemplates(): Record<BuiltInSkillType, SkillTemplate> {\n  return {\n    'commit-message': createCommitMessageSkill(),\n    'pr-review': createPrReviewSkill(),\n    'code-review': createCodeReviewSkill(),\n    'test-generation': createTestGenerationSkill(),\n    'documentation': createDocumentationSkill(),\n    'refactoring': createRefactoringSkill(),\n    'bug-investigation': createBugInvestigationSkill(),\n    'feature-breakdown': createFeatureBreakdownSkill(),\n    'api-design': createApiDesignSkill(),\n    'security-audit': createSecurityAuditSkill(),\n  };\n}\n\nfunction createCommitMessageSkill(): SkillTemplate {\n  const overview = 'Use this skill to turn a concrete set of staged changes into a clean conventional commit message';\n  return {\n    description: buildTemplateDescription('Generate commit messages that follow conventional commits and repository scope conventions', commitMessageContent),\n    content: contentToMarkdown('Commit Message', overview, commitMessageContent),\n  };\n}\n\nfunction createPrReviewSkill(): SkillTemplate {\n  const overview = 'Use this skill to review a pull request against the repository quality bar and leave actionable feedback';\n  return {\n    description: buildTemplateDescription('Review pull requests against team standards and best practices', prReviewContent),\n    content: contentToMarkdown('Pull Request Review', overview, prReviewContent),\n  };\n}\n\nfunction createCodeReviewSkill(): SkillTemplate {\n  const overview = 'Use this skill to inspect code for correctness, maintainability, and higher-order risks before changes move forward';\n  return {\n    description: buildTemplateDescription('Review code quality, patterns, and best practices', codeReviewContent),\n    content: contentToMarkdown('Code Review', overview, codeReviewContent),\n  };\n}\n\nfunction createTestGenerationSkill(): SkillTemplate {\n  const overview = 'Use this skill to produce tests that cover behavior, regressions, and the repository testing conventions';\n  return {\n    description: buildTemplateDescription('Generate comprehensive test cases for code', testGenerationContent),\n    content: contentToMarkdown('Test Generation', overview, testGenerationContent),\n  };\n}\n\nfunction createDocumentationSkill(): SkillTemplate {\n  const overview = 'Use this skill to create or update technical documentation that matches the current code and the target audience';\n  return {\n    description: buildTemplateDescription('Generate and update technical documentation', documentationContent),\n    content: contentToMarkdown('Documentation', overview, documentationContent),\n  };\n}\n\nfunction createRefactoringSkill(): SkillTemplate {\n  const overview = 'Use this skill to improve structure without changing behavior, keeping the work incremental and test-backed';\n  return {\n    description: buildTemplateDescription('Refactor code safely with a step-by-step approach', refactoringContent),\n    content: contentToMarkdown('Refactoring', overview, refactoringContent),\n  };\n}\n\nfunction createBugInvestigationSkill(): SkillTemplate {\n  const overview = 'Use this skill to reproduce a bug, narrow the failure surface, and document a defensible root cause';\n  return {\n    description: buildTemplateDescription('Investigate bugs systematically and perform root cause analysis', bugInvestigationContent),\n    content: contentToMarkdown('Bug Investigation', overview, bugInvestigationContent),\n  };\n}\n\nfunction createFeatureBreakdownSkill(): SkillTemplate {\n  const overview = 'Use this skill to decompose a feature into small, testable tasks with explicit dependencies and acceptance criteria';\n  return {\n    description: buildTemplateDescription('Break down features into implementable tasks', featureBreakdownContent),\n    content: contentToMarkdown('Feature Breakdown', overview, featureBreakdownContent),\n  };\n}\n\nfunction createApiDesignSkill(): SkillTemplate {\n  const overview = 'Use this skill to design API resources, payloads, and failure modes with consistent conventions';\n  return {\n    description: buildTemplateDescription('Design RESTful APIs following best practices', apiDesignContent),\n    content: contentToMarkdown('API Design', overview, apiDesignContent),\n  };\n}\n\nfunction createSecurityAuditSkill(): SkillTemplate {\n  const overview = 'Use this skill to review code and integrations for security weaknesses and rank the findings by severity';\n  return {\n    description: buildTemplateDescription('Review code and infrastructure for security weaknesses', securityAuditContent),\n    content: contentToMarkdown('Security Audit', overview, securityAuditContent),\n  };\n}\n"
  },
  {
    "path": "src/workflow/skills/types.ts",
    "content": "/**\n * Skill Types\n *\n * Skills are on-demand expertise that AI agents can activate when needed.\n * Unlike agents (persistent behavioral playbooks), skills are task-specific\n * procedures that get loaded only when relevant.\n */\n\nimport { PrevcPhase } from '../types';\n\n/**\n * SKILL.md frontmatter metadata\n */\nexport interface SkillMetadata {\n  /** Unique identifier (lowercase, hyphens) */\n  name: string;\n  /** Description of when to use this skill */\n  description: string;\n  /** Categorize as mode command (modifies behavior) */\n  mode?: boolean;\n  /** Prevent auto-activation by AI */\n  disableModelInvocation?: boolean;\n  /** PREVC phases where this skill is relevant */\n  phases?: PrevcPhase[];\n}\n\n/**\n * Full skill representation\n */\nexport interface Skill {\n  /** Directory name */\n  slug: string;\n  /** Full path to SKILL.md */\n  path: string;\n  /** Parsed frontmatter */\n  metadata: SkillMetadata;\n  /** Markdown instructions */\n  content: string;\n  /** Optional helper files in skill directory */\n  resources: string[];\n  /** Is this a built-in skill? */\n  isBuiltIn: boolean;\n}\n\n/**\n * Skill reference for linking\n */\nexport interface SkillReference {\n  slug: string;\n  path: string;\n  name: string;\n  description: string;\n  linkedAt: string;\n}\n\n/**\n * Discovered skills summary\n */\nexport interface DiscoveredSkills {\n  builtIn: Skill[];\n  custom: Skill[];\n  all: Skill[];\n}\n\n/**\n * Built-in skills we provide\n */\nexport const BUILT_IN_SKILLS = [\n  'commit-message',\n  'pr-review',\n  'code-review',\n  'test-generation',\n  'documentation',\n  'refactoring',\n  'bug-investigation',\n  'feature-breakdown',\n  'api-design',\n  'security-audit',\n] as const;\n\nexport type BuiltInSkillType = (typeof BUILT_IN_SKILLS)[number];\n\n/**\n * Check if a skill is built-in\n */\nexport function isBuiltInSkill(skillType: string): skillType is BuiltInSkillType {\n  return BUILT_IN_SKILLS.includes(skillType as BuiltInSkillType);\n}\n\n/**\n * Skill to PREVC phase mapping\n */\nexport const SKILL_TO_PHASES: Record<BuiltInSkillType, PrevcPhase[]> = {\n  'commit-message': ['E', 'C'],\n  'pr-review': ['R', 'V'],\n  'code-review': ['R', 'V'],\n  'test-generation': ['E', 'V'],\n  'documentation': ['P', 'C'],\n  'refactoring': ['E'],\n  'bug-investigation': ['E', 'V'],\n  'feature-breakdown': ['P'],\n  'api-design': ['P', 'R'],\n  'security-audit': ['R', 'V'],\n};\n"
  },
  {
    "path": "src/workflow/status/statusManager.test.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { ProjectScale } from '../types';\nimport { PrevcStatusManager } from './statusManager';\n\ndescribe('PrevcStatusManager canonical persistence', () => {\n  let tempDir: string;\n  let contextPath: string;\n  let manager: PrevcStatusManager;\n\n  beforeEach(async () => {\n    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prevc-status-'));\n    contextPath = path.join(tempDir, '.context');\n    manager = new PrevcStatusManager(contextPath);\n  });\n\n  afterEach(async () => {\n    await fs.remove(tempDir);\n  });\n\n  it('stores canonical PREVC state under .context/harness/workflows without creating status.yaml', async () => {\n    const created = await manager.create({\n      name: 'canonical-alpha',\n      scale: ProjectScale.SMALL,\n    });\n\n    const canonicalPath = path.join(contextPath, 'harness', 'workflows', 'prevc.json');\n    const projectionPath = path.join(contextPath, 'workflow', 'status.yaml');\n\n    expect(created.project.name).toBe('canonical-alpha');\n    expect(await fs.pathExists(canonicalPath)).toBe(true);\n    expect(await fs.pathExists(projectionPath)).toBe(false);\n\n    const canonical = await fs.readJson(canonicalPath);\n    expect(canonical.workflowType).toBe('prevc');\n    expect(canonical.status.project.name).toBe('canonical-alpha');\n  });\n\n  it('migrates legacy status.yaml into canonical harness workflow state on load', async () => {\n    const projectionPath = path.join(contextPath, 'workflow', 'status.yaml');\n    await fs.ensureDir(path.dirname(projectionPath));\n    await fs.writeFile(projectionPath, [\n      'project:',\n      '  name: \"legacy-alpha\"',\n      '  scale: SMALL',\n      '  started: \"2026-04-11T00:00:00.000Z\"',\n      '  current_phase: P',\n      '',\n      'phases:',\n      '  P:',\n      '    status: in_progress',\n      '    started_at: \"2026-04-11T00:00:00.000Z\"',\n      '  R:',\n      '    status: pending',\n      '  E:',\n      '    status: pending',\n      '  V:',\n      '    status: pending',\n      '  C:',\n      '    status: pending',\n      '',\n    ].join('\\n'), 'utf-8');\n\n    const loaded = await manager.load();\n    const canonicalPath = path.join(contextPath, 'harness', 'workflows', 'prevc.json');\n\n    expect(loaded.project.name).toBe('legacy-alpha');\n    expect(await fs.pathExists(canonicalPath)).toBe(true);\n    expect(await fs.pathExists(projectionPath)).toBe(false);\n\n    const canonical = await fs.readJson(canonicalPath);\n    expect(canonical.status.project.name).toBe('legacy-alpha');\n    expect(canonical.status.project.current_phase).toBe('P');\n  });\n});\n"
  },
  {
    "path": "src/workflow/status/statusManager.ts",
    "content": "/**\n * PREVC Status Manager\n *\n * Manages canonical PREVC workflow state stored in harness runtime state.\n * Legacy status.yaml files are migrated on read but no longer written.\n */\n\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { HarnessWorkflowStateService } from '../../services/harness/workflowStateService';\nimport {\n  PrevcStatus,\n  PrevcPhase,\n  PrevcRole,\n  PhaseUpdate,\n  RoleUpdate,\n  AgentUpdate,\n  ProjectScale,\n  PhaseStatus,\n  RoleStatus,\n  AgentStatus,\n  WorkflowSettings,\n  PlanApproval,\n  ExecutionHistory,\n  ExecutionHistoryEntry,\n  ExecutionAction,\n} from '../types';\nimport { PREVC_PHASE_ORDER } from '../phases';\nimport { getScaleRoute } from '../scaling';\nimport { createInitialStatus, generateResumeContext } from './templates';\nimport { getDefaultSettings } from '../gates';\n\n/**\n * PREVC Status Manager\n *\n * Handles reading and writing canonical workflow state, with optional legacy migration.\n */\nexport class PrevcStatusManager {\n  private contextPath: string;\n  private workflowState: HarnessWorkflowStateService;\n  private cachedStatus: PrevcStatus | null = null;\n\n  constructor(contextPath: string) {\n    this.contextPath = contextPath;\n    this.workflowState = new HarnessWorkflowStateService({ contextPath });\n  }\n\n  private get legacyStatusPath(): string {\n    return path.join(this.contextPath, 'workflow', 'status.yaml');\n  }\n\n  /**\n   * Check if a workflow status file exists\n   */\n  async exists(): Promise<boolean> {\n    return (await this.workflowState.exists()) || fs.pathExists(this.legacyStatusPath);\n  }\n\n  /**\n   * Load the workflow status from disk\n   */\n  async load(): Promise<PrevcStatus> {\n    const hasCanonicalState = await this.workflowState.exists();\n    const hasLegacyProjection = await fs.pathExists(this.legacyStatusPath);\n\n    if (!hasCanonicalState && !hasLegacyProjection) {\n      throw new Error('Workflow status not found. Run \"workflow init\" first.');\n    }\n\n    let status: PrevcStatus;\n    if (hasCanonicalState) {\n      status = await this.workflowState.load();\n    } else {\n      const content = await fs.readFile(this.legacyStatusPath, 'utf-8');\n      status = this.parseYaml(content);\n    }\n\n    status = this.migrateStatus(status);\n    await this.persistCanonical(status);\n    if (hasLegacyProjection) {\n      await fs.remove(this.legacyStatusPath);\n    }\n    this.cachedStatus = status;\n    return this.cachedStatus;\n  }\n\n  /**\n   * Load status synchronously (for use in orchestrator)\n   */\n  loadSync(): PrevcStatus {\n    if (this.cachedStatus) {\n      return this.cachedStatus;\n    }\n\n    const hasCanonicalState = fs.existsSync(path.join(this.contextPath, 'harness', 'workflows', 'prevc.json'));\n    const hasLegacyProjection = fs.existsSync(this.legacyStatusPath);\n\n    if (!hasCanonicalState && !hasLegacyProjection) {\n      throw new Error('Workflow status not found. Run \"workflow init\" first.');\n    }\n\n    let status: PrevcStatus;\n    if (hasCanonicalState) {\n      status = this.workflowState.loadSync();\n    } else {\n      const content = fs.readFileSync(this.legacyStatusPath, 'utf-8');\n      status = this.parseYaml(content);\n    }\n\n    status = this.migrateStatus(status);\n    this.cachedStatus = status;\n    return this.cachedStatus;\n  }\n\n  /**\n   * Save the workflow status to disk\n   */\n  async save(status: PrevcStatus): Promise<void> {\n    await this.persistCanonical(status);\n    this.cachedStatus = status;\n  }\n\n  async remove(): Promise<void> {\n    await this.workflowState.remove();\n    if (await fs.pathExists(this.legacyStatusPath)) {\n      await fs.remove(this.legacyStatusPath);\n    }\n    this.cachedStatus = null;\n  }\n\n  async archive(name: string): Promise<void> {\n    await this.workflowState.archive(name);\n    if (await fs.pathExists(this.legacyStatusPath)) {\n      const archiveDir = path.join(this.contextPath, 'workflow', 'archive');\n      const safeName = name.replace(/[^a-zA-Z0-9-_]/g, '-');\n      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n      await fs.ensureDir(archiveDir);\n      await fs.move(this.legacyStatusPath, path.join(archiveDir, `${safeName}-${timestamp}.legacy-status.yaml`));\n    }\n    this.cachedStatus = null;\n  }\n\n  private async persistCanonical(status: PrevcStatus): Promise<void> {\n    await this.workflowState.save(status);\n    if (await fs.pathExists(this.legacyStatusPath)) {\n      await fs.remove(this.legacyStatusPath);\n    }\n  }\n\n  /**\n   * Create a new workflow status\n   */\n  async create(options: {\n    name: string;\n    scale: ProjectScale;\n    phases?: PrevcPhase[];\n    roles?: PrevcRole[] | 'all';\n  }): Promise<PrevcStatus> {\n    const route = getScaleRoute(options.scale);\n    const phases = options.phases || route.phases;\n    const roles = options.roles || route.roles;\n\n    const status = createInitialStatus({\n      name: options.name,\n      scale: options.scale,\n      phases,\n      roles,\n    });\n\n    await this.save(status);\n    return status;\n  }\n\n  /**\n   * Update a phase's status\n   */\n  async updatePhase(phase: PrevcPhase, update: PhaseUpdate): Promise<void> {\n    const status = await this.load();\n\n    status.phases[phase] = {\n      ...status.phases[phase],\n      ...update,\n    };\n\n    await this.save(status);\n  }\n\n  /**\n   * Update a role's status\n   * @deprecated Use updateAgent instead\n   */\n  async updateRole(role: PrevcRole, update: RoleUpdate): Promise<void> {\n    const status = await this.load();\n\n    if (!status.roles[role]) {\n      status.roles[role] = {};\n    }\n\n    status.roles[role] = {\n      ...status.roles[role],\n      ...update,\n    };\n\n    await this.save(status);\n  }\n\n  /**\n   * Update an agent's status (replaces updateRole)\n   */\n  async updateAgent(agentName: string, update: AgentUpdate): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    // Initialize agents if not present\n    if (!status.agents) {\n      status.agents = {};\n    }\n\n    const existingAgent = status.agents[agentName];\n\n    // Build agent status\n    const agentStatus: AgentStatus = {\n      status: update.status ?? existingAgent?.status ?? 'pending',\n      started_at: existingAgent?.started_at,\n      completed_at: existingAgent?.completed_at,\n      outputs: update.outputs ?? existingAgent?.outputs,\n    };\n\n    // Track timestamps based on status changes\n    if (update.status === 'in_progress' && !existingAgent?.started_at) {\n      agentStatus.started_at = now;\n    }\n    if (update.status === 'completed' && !existingAgent?.completed_at) {\n      agentStatus.completed_at = now;\n    }\n\n    status.agents[agentName] = agentStatus;\n\n    await this.save(status);\n  }\n\n  /**\n   * Transition to a new phase\n   */\n  async transitionToPhase(phase: PrevcPhase): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    // Update current phase\n    status.project.current_phase = phase;\n\n    // Mark new phase as in_progress\n    status.phases[phase] = {\n      ...status.phases[phase],\n      status: 'in_progress',\n      started_at: now,\n    };\n\n    // Add history entry\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n    status.execution.history.push({\n      timestamp: now,\n      phase,\n      action: 'started',\n    });\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(phase, 'started');\n\n    await this.save(status);\n  }\n\n  /**\n   * Mark a phase as complete\n   */\n  async markPhaseComplete(\n    phase: PrevcPhase,\n    outputs?: string[]\n  ): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    const phaseStatus: PhaseStatus = {\n      ...status.phases[phase],\n      status: 'completed',\n      completed_at: now,\n    };\n\n    if (outputs) {\n      phaseStatus.outputs = outputs.map((p) => ({ path: p, status: 'filled' }));\n    }\n\n    status.phases[phase] = phaseStatus;\n\n    // Add history entry\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n    status.execution.history.push({\n      timestamp: now,\n      phase,\n      action: 'completed',\n    });\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(phase, 'completed');\n\n    await this.save(status);\n  }\n\n  /**\n   * Get the current phase\n   */\n  async getCurrentPhase(): Promise<PrevcPhase> {\n    const status = await this.load();\n    return status.project.current_phase;\n  }\n\n  /**\n   * Get the active role (if any)\n   */\n  async getActiveRole(): Promise<PrevcRole | null> {\n    const status = await this.load();\n\n    for (const [role, roleStatus] of Object.entries(status.roles)) {\n      if ((roleStatus as RoleStatus).status === 'in_progress') {\n        return role as PrevcRole;\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * Get the next phase that should be executed\n   */\n  async getNextPhase(): Promise<PrevcPhase | null> {\n    const status = await this.load();\n    const currentIndex = PREVC_PHASE_ORDER.indexOf(\n      status.project.current_phase\n    );\n\n    for (let i = currentIndex + 1; i < PREVC_PHASE_ORDER.length; i++) {\n      const phase = PREVC_PHASE_ORDER[i];\n      if (status.phases[phase].status !== 'skipped') {\n        return phase;\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * Check if the workflow is complete\n   */\n  async isComplete(): Promise<boolean> {\n    const status = await this.load();\n\n    for (const phase of PREVC_PHASE_ORDER) {\n      const phaseStatus = status.phases[phase];\n      if (\n        phaseStatus.status !== 'completed' &&\n        phaseStatus.status !== 'skipped'\n      ) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  /**\n   * Set workflow settings\n   */\n  async setSettings(settings: Partial<WorkflowSettings>): Promise<WorkflowSettings> {\n    const status = await this.load();\n    const defaults = getDefaultSettings(status.project.scale);\n\n    const currentSettings = status.project.settings || defaults;\n    const newSettings: WorkflowSettings = {\n      autonomous_mode: settings.autonomous_mode ?? currentSettings.autonomous_mode,\n      require_plan: settings.require_plan ?? currentSettings.require_plan,\n      require_approval: settings.require_approval ?? currentSettings.require_approval,\n    };\n\n    status.project.settings = newSettings;\n    await this.save(status);\n    return newSettings;\n  }\n\n  /**\n   * Get workflow settings (with defaults applied)\n   */\n  async getSettings(): Promise<WorkflowSettings> {\n    const status = await this.load();\n    const defaults = getDefaultSettings(status.project.scale);\n    return status.project.settings || defaults;\n  }\n\n  /**\n   * Mark that a plan has been created/linked\n   */\n  async markPlanCreated(planSlug: string): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    if (!status.approval) {\n      status.approval = {\n        plan_created: false,\n        plan_approved: false,\n      };\n    }\n\n    status.approval.plan_created = true;\n    status.project.plan = planSlug;\n\n    // Add history entry\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n    status.execution.history.push({\n      timestamp: now,\n      phase: status.project.current_phase,\n      action: 'plan_linked',\n      plan: planSlug,\n    });\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(status.project.current_phase, 'plan_linked');\n\n    await this.save(status);\n  }\n\n  /**\n   * Approve the plan\n   */\n  async approvePlan(approver: PrevcRole | string, notes?: string): Promise<PlanApproval> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    if (!status.approval) {\n      status.approval = {\n        plan_created: false,\n        plan_approved: false,\n      };\n    }\n\n    status.approval.plan_approved = true;\n    status.approval.approved_by = approver;\n    status.approval.approved_at = now;\n    if (notes) {\n      status.approval.approval_notes = notes;\n    }\n\n    // Add history entry\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n    status.execution.history.push({\n      timestamp: now,\n      phase: status.project.current_phase,\n      action: 'plan_approved',\n      approved_by: String(approver),\n    });\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(status.project.current_phase, 'plan_approved');\n\n    await this.save(status);\n    return status.approval;\n  }\n\n  /**\n   * Get approval status\n   */\n  async getApproval(): Promise<PlanApproval | undefined> {\n    const status = await this.load();\n    return status.approval;\n  }\n\n  /**\n   * Add an entry to the execution history\n   */\n  async addHistoryEntry(entry: Omit<ExecutionHistoryEntry, 'timestamp'>): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    // Ensure execution exists\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n\n    // Add new entry\n    const fullEntry: ExecutionHistoryEntry = {\n      ...entry,\n      timestamp: now,\n    };\n    status.execution.history.push(fullEntry);\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(entry.phase, entry.action);\n\n    await this.save(status);\n  }\n\n  /**\n   * Add a step-level entry to the execution history (breadcrumb trail)\n   */\n  async addStepHistoryEntry(entry: {\n    action: 'step_started' | 'step_completed' | 'step_skipped';\n    plan: string;\n    planPhase: string;\n    stepIndex: number;\n    stepDescription?: string;\n    output?: string;\n    notes?: string;\n  }): Promise<void> {\n    const status = await this.load();\n    const now = new Date().toISOString();\n\n    // Ensure execution exists\n    if (!status.execution) {\n      status.execution = {\n        history: [],\n        last_activity: now,\n        resume_context: '',\n      };\n    }\n\n    // Add new step entry\n    const fullEntry: ExecutionHistoryEntry = {\n      timestamp: now,\n      phase: status.project.current_phase,\n      action: entry.action,\n      plan: entry.plan,\n      planPhase: entry.planPhase,\n      stepIndex: entry.stepIndex,\n      stepDescription: entry.stepDescription,\n      output: entry.output,\n      notes: entry.notes,\n    };\n    status.execution.history.push(fullEntry);\n    status.execution.last_activity = now;\n    status.execution.resume_context = generateResumeContext(\n      status.project.current_phase,\n      entry.action,\n      { planPhase: entry.planPhase, stepIndex: entry.stepIndex, stepDescription: entry.stepDescription }\n    );\n\n    await this.save(status);\n  }\n\n  /**\n   * Get execution history\n   */\n  async getExecutionHistory(): Promise<ExecutionHistory | undefined> {\n    const status = await this.load();\n    return status.execution;\n  }\n\n  /**\n   * Apply migration logic for existing workflows\n   * - Add default settings based on scale\n   * - Initialize approval tracking based on current state\n   * - Initialize execution history for old workflows\n   * - Initialize agents object for old workflows\n   */\n  private migrateStatus(status: PrevcStatus): PrevcStatus {\n    // Add default settings if missing\n    if (!status.project.settings) {\n      status.project.settings = getDefaultSettings(status.project.scale);\n    }\n\n    // Initialize agents if missing (migration from old workflows)\n    if (!status.agents) {\n      status.agents = {};\n    }\n\n    // Initialize approval tracking if missing\n    if (!status.approval) {\n      const hasPlan = Boolean(status.project.plan || (status.project.plans && status.project.plans.length > 0));\n      const isPastReview = ['E', 'V', 'C'].includes(status.project.current_phase) ||\n        status.phases['R'].status === 'completed';\n\n      status.approval = {\n        plan_created: hasPlan,\n        // Auto-approve if already past R phase (grandfather clause)\n        plan_approved: isPastReview,\n        approved_by: isPastReview ? 'system-migration' : undefined,\n        approved_at: isPastReview ? new Date().toISOString() : undefined,\n      };\n    }\n\n    // Initialize execution history if missing (migration from old workflows)\n    if (!status.execution) {\n      const now = new Date().toISOString();\n      const currentPhase = status.project.current_phase;\n      const history: ExecutionHistoryEntry[] = [];\n\n      // Reconstruct history from phase data\n      for (const phase of PREVC_PHASE_ORDER) {\n        const phaseStatus = status.phases[phase];\n        if (phaseStatus.started_at) {\n          history.push({\n            timestamp: phaseStatus.started_at,\n            phase,\n            action: 'started',\n          });\n        }\n        if (phaseStatus.completed_at) {\n          history.push({\n            timestamp: phaseStatus.completed_at,\n            phase,\n            action: 'completed',\n          });\n        }\n        if (phaseStatus.status === 'skipped') {\n          history.push({\n            timestamp: now,\n            phase,\n            action: 'phase_skipped',\n          });\n        }\n      }\n\n      // Sort by timestamp\n      history.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n      // Determine resume context based on current state\n      let resumeContext = generateResumeContext(currentPhase, 'started');\n      if (status.approval?.plan_approved) {\n        resumeContext = generateResumeContext(currentPhase, 'plan_approved');\n      } else if (status.approval?.plan_created) {\n        resumeContext = generateResumeContext(currentPhase, 'plan_linked');\n      }\n\n      status.execution = {\n        history: history.length > 0 ? history : [{\n          timestamp: status.project.started || now,\n          phase: currentPhase,\n          action: 'started',\n          description: 'Migrated from legacy workflow',\n        }],\n        last_activity: history.length > 0 ? history[history.length - 1].timestamp : now,\n        resume_context: resumeContext,\n      };\n    }\n\n    return status;\n  }\n\n  /**\n   * Parse YAML content to PrevcStatus object\n   * Simple implementation for the specific format\n   */\n  private parseYaml(content: string): PrevcStatus {\n    // Basic YAML parsing - for production, use a proper YAML library\n    const lines = content.split('\\n');\n    const result: PrevcStatus = {\n      project: {\n        name: '',\n        scale: ProjectScale.MEDIUM,\n        started: new Date().toISOString(),\n        current_phase: 'P',\n      },\n      phases: {\n        P: { status: 'pending' },\n        R: { status: 'pending' },\n        E: { status: 'pending' },\n        V: { status: 'pending' },\n        C: { status: 'pending' },\n      },\n      agents: {},\n      roles: {},\n    };\n\n    let currentSection = '';\n    let currentPhase = '';\n    let currentRole = '';\n    let currentAgent = '';\n\n    for (const line of lines) {\n      const trimmed = line.trim();\n      if (!trimmed || trimmed.startsWith('#')) continue;\n\n      if (line.startsWith('project:')) {\n        currentSection = 'project';\n      } else if (line.startsWith('phases:')) {\n        currentSection = 'phases';\n      } else if (line.startsWith('agents:')) {\n        currentSection = 'agents';\n      } else if (line.startsWith('roles:')) {\n        currentSection = 'roles';\n      } else if (line.startsWith('settings:')) {\n        currentSection = 'settings';\n      } else if (line.startsWith('approval:')) {\n        currentSection = 'approval';\n      } else if (line.startsWith('execution:')) {\n        currentSection = 'execution';\n      } else if (currentSection === 'project' && line.startsWith('  ')) {\n        const [key, ...valueParts] = trimmed.split(':');\n        const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n        if (key === 'name') result.project.name = value;\n        if (key === 'scale') {\n          const scaleMap: Record<string, ProjectScale> = {\n            QUICK: ProjectScale.QUICK,\n            SMALL: ProjectScale.SMALL,\n            MEDIUM: ProjectScale.MEDIUM,\n            LARGE: ProjectScale.LARGE,\n            ENTERPRISE: ProjectScale.LARGE, // Legacy migration - map to LARGE\n          };\n          result.project.scale = scaleMap[value] ?? ProjectScale.MEDIUM;\n        }\n        if (key === 'started') result.project.started = value;\n        if (key === 'current_phase')\n          result.project.current_phase = value as PrevcPhase;\n      } else if (currentSection === 'phases') {\n        if (line.match(/^  [PREVC]:/)) {\n          currentPhase = trimmed.replace(':', '') as PrevcPhase;\n        } else if (currentPhase && line.startsWith('    ')) {\n          const [key, ...valueParts] = trimmed.split(':');\n          const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n          if (key === 'status') {\n            result.phases[currentPhase as PrevcPhase].status = value as\n              | 'pending'\n              | 'in_progress'\n              | 'completed'\n              | 'skipped';\n          }\n          if (key === 'started_at') {\n            result.phases[currentPhase as PrevcPhase].started_at = value;\n          }\n          if (key === 'completed_at') {\n            result.phases[currentPhase as PrevcPhase].completed_at = value;\n          }\n          if (key === 'reason') {\n            result.phases[currentPhase as PrevcPhase].reason = value;\n          }\n        }\n      } else if (currentSection === 'agents') {\n        if (line.match(/^  [a-z-]+:/)) {\n          currentAgent = trimmed.replace(':', '');\n          result.agents[currentAgent] = { status: 'pending' };\n        } else if (currentAgent && line.startsWith('    ')) {\n          const [key, ...valueParts] = trimmed.split(':');\n          const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n          const agentStatus = result.agents[currentAgent] || { status: 'pending' };\n          if (key === 'status') {\n            agentStatus.status = value as\n              | 'pending'\n              | 'in_progress'\n              | 'completed'\n              | 'skipped';\n          }\n          if (key === 'started_at') {\n            agentStatus.started_at = value;\n          }\n          if (key === 'completed_at') {\n            agentStatus.completed_at = value;\n          }\n          // Parse outputs array (simplified - single line format: [a, b, c])\n          if (key === 'outputs') {\n            const outputsMatch = value.match(/^\\[(.*)\\]$/);\n            if (outputsMatch) {\n              agentStatus.outputs = outputsMatch[1]\n                .split(',')\n                .map(s => s.trim().replace(/^[\"']|[\"']$/g, ''))\n                .filter(s => s);\n            }\n          }\n          result.agents[currentAgent] = agentStatus;\n        }\n      } else if (currentSection === 'roles') {\n        if (line.match(/^  [a-z-]+:/)) {\n          currentRole = trimmed.replace(':', '') as PrevcRole;\n          result.roles[currentRole as PrevcRole] = {};\n        } else if (currentRole && line.startsWith('    ')) {\n          const [key, ...valueParts] = trimmed.split(':');\n          const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n          const roleStatus = result.roles[currentRole as PrevcRole] || {};\n          if (key === 'status') {\n            roleStatus.status = value as\n              | 'pending'\n              | 'in_progress'\n              | 'completed'\n              | 'skipped';\n          }\n          if (key === 'phase') {\n            roleStatus.phase = value as PrevcPhase;\n          }\n          if (key === 'last_active') {\n            roleStatus.last_active = value;\n          }\n          if (key === 'current_task') {\n            roleStatus.current_task = value;\n          }\n          result.roles[currentRole as PrevcRole] = roleStatus;\n        }\n      } else if (currentSection === 'settings' && line.startsWith('  ')) {\n        if (!result.project.settings) {\n          result.project.settings = {\n            autonomous_mode: false,\n            require_plan: true,\n            require_approval: true,\n          };\n        }\n        const [key, ...valueParts] = trimmed.split(':');\n        const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n        if (key === 'autonomous_mode') {\n          result.project.settings.autonomous_mode = value === 'true';\n        }\n        if (key === 'require_plan') {\n          result.project.settings.require_plan = value === 'true';\n        }\n        if (key === 'require_approval') {\n          result.project.settings.require_approval = value === 'true';\n        }\n      } else if (currentSection === 'approval' && line.startsWith('  ')) {\n        if (!result.approval) {\n          result.approval = {\n            plan_created: false,\n            plan_approved: false,\n          };\n        }\n        const [key, ...valueParts] = trimmed.split(':');\n        const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n        if (key === 'plan_created') {\n          result.approval.plan_created = value === 'true';\n        }\n        if (key === 'plan_approved') {\n          result.approval.plan_approved = value === 'true';\n        }\n        if (key === 'approved_by') {\n          result.approval.approved_by = value || undefined;\n        }\n        if (key === 'approved_at') {\n          result.approval.approved_at = value || undefined;\n        }\n        if (key === 'approval_notes') {\n          result.approval.approval_notes = value || undefined;\n        }\n      } else if (currentSection === 'execution' && line.startsWith('  ')) {\n        if (!result.execution) {\n          result.execution = {\n            history: [],\n            last_activity: '',\n            resume_context: '',\n          };\n        }\n        const [key, ...valueParts] = trimmed.split(':');\n        const value = valueParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n        if (key === 'last_activity') {\n          result.execution.last_activity = value;\n        }\n        if (key === 'resume_context') {\n          result.execution.resume_context = value;\n        }\n        // Note: history parsing is simplified - for complex arrays, use a YAML library\n        // History entries are primarily written by the system, not manually edited\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Serialize PrevcStatus object to YAML string\n   */\n  private serializeYaml(status: PrevcStatus): string {\n    const lines: string[] = [];\n\n    // Project section\n    lines.push('project:');\n    lines.push(`  name: \"${status.project.name}\"`);\n    const scaleName =\n      typeof status.project.scale === 'number'\n        ? ProjectScale[status.project.scale]\n        : status.project.scale;\n    lines.push(`  scale: ${scaleName}`);\n    lines.push(`  started: \"${status.project.started}\"`);\n    lines.push(`  current_phase: ${status.project.current_phase}`);\n    lines.push('');\n\n    // Phases section\n    lines.push('phases:');\n    for (const phase of PREVC_PHASE_ORDER) {\n      const phaseStatus = status.phases[phase];\n      lines.push(`  ${phase}:`);\n      lines.push(`    status: ${phaseStatus.status}`);\n      if (phaseStatus.started_at) {\n        lines.push(`    started_at: \"${phaseStatus.started_at}\"`);\n      }\n      if (phaseStatus.completed_at) {\n        lines.push(`    completed_at: \"${phaseStatus.completed_at}\"`);\n      }\n      if (phaseStatus.role) {\n        lines.push(`    role: ${phaseStatus.role}`);\n      }\n      if (phaseStatus.current_task) {\n        lines.push(`    current_task: \"${phaseStatus.current_task}\"`);\n      }\n      if (phaseStatus.reason) {\n        lines.push(`    reason: \"${phaseStatus.reason}\"`);\n      }\n      if (phaseStatus.outputs && phaseStatus.outputs.length > 0) {\n        lines.push('    outputs:');\n        for (const output of phaseStatus.outputs) {\n          lines.push(`      - path: \"${output.path}\"`);\n          lines.push(`        status: ${output.status}`);\n        }\n      }\n    }\n    lines.push('');\n\n    // Agents section (replaces roles as primary tracking)\n    const agentsWithData: Array<[string, AgentStatus]> = [];\n    if (status.agents) {\n      for (const [agentName, agentStatus] of Object.entries(status.agents)) {\n        if (agentStatus) {\n          agentsWithData.push([agentName, agentStatus]);\n        }\n      }\n    }\n    if (agentsWithData.length > 0) {\n      lines.push('agents:');\n      for (const [agentName, agentStatus] of agentsWithData) {\n        lines.push(`  ${agentName}:`);\n        lines.push(`    status: ${agentStatus.status}`);\n        if (agentStatus.started_at) {\n          lines.push(`    started_at: \"${agentStatus.started_at}\"`);\n        }\n        if (agentStatus.completed_at) {\n          lines.push(`    completed_at: \"${agentStatus.completed_at}\"`);\n        }\n        if (agentStatus.outputs && agentStatus.outputs.length > 0) {\n          lines.push(`    outputs: [${agentStatus.outputs.map((o) => `\"${o}\"`).join(', ')}]`);\n        }\n      }\n      lines.push('');\n    }\n\n    // Roles section (legacy - kept for backward compatibility)\n    const rolesWithData: Array<[string, RoleStatus]> = [];\n    if (status.roles) {\n      for (const [role, roleStatus] of Object.entries(status.roles)) {\n        if (roleStatus && Object.keys(roleStatus).length > 0) {\n          rolesWithData.push([role, roleStatus]);\n        }\n      }\n    }\n    if (rolesWithData.length > 0) {\n      lines.push('roles:');\n      for (const [role, roleStatus] of rolesWithData) {\n        lines.push(`  ${role}:`);\n        if (roleStatus.status) {\n          lines.push(`    status: ${roleStatus.status}`);\n        }\n        if (roleStatus.phase) {\n          lines.push(`    phase: ${roleStatus.phase}`);\n        }\n        if (roleStatus.last_active) {\n          lines.push(`    last_active: \"${roleStatus.last_active}\"`);\n        }\n        if (roleStatus.current_task) {\n          lines.push(`    current_task: \"${roleStatus.current_task}\"`);\n        }\n        if (roleStatus.outputs && roleStatus.outputs.length > 0) {\n          lines.push(`    outputs: [${roleStatus.outputs.map((o) => `\"${o}\"`).join(', ')}]`);\n        }\n      }\n      lines.push('');\n    }\n\n    // Execution section (new - replaces roles as primary tracking)\n    if (status.execution && status.execution.history.length > 0) {\n      lines.push('execution:');\n      lines.push('  history:');\n      for (const entry of status.execution.history) {\n        lines.push(`    - timestamp: \"${entry.timestamp}\"`);\n        lines.push(`      phase: ${entry.phase}`);\n        lines.push(`      action: ${entry.action}`);\n        if (entry.plan) {\n          lines.push(`      plan: \"${entry.plan}\"`);\n        }\n        if (entry.approved_by) {\n          lines.push(`      approved_by: \"${entry.approved_by}\"`);\n        }\n        if (entry.description) {\n          lines.push(`      description: \"${entry.description}\"`);\n        }\n        // Step-level breadcrumb fields\n        if (entry.planPhase) {\n          lines.push(`      planPhase: \"${entry.planPhase}\"`);\n        }\n        if (entry.stepIndex !== undefined) {\n          lines.push(`      stepIndex: ${entry.stepIndex}`);\n        }\n        if (entry.stepDescription) {\n          lines.push(`      stepDescription: \"${entry.stepDescription}\"`);\n        }\n        if (entry.output) {\n          lines.push(`      output: \"${entry.output}\"`);\n        }\n        if (entry.notes) {\n          lines.push(`      notes: \"${entry.notes}\"`);\n        }\n      }\n      lines.push(`  last_activity: \"${status.execution.last_activity}\"`);\n      lines.push(`  resume_context: \"${status.execution.resume_context}\"`);\n      lines.push('');\n    }\n\n    // Settings section\n    const defaultSettings = getDefaultSettings(status.project.scale);\n    const settings = status.project.settings;\n    const shouldWriteSettings = settings\n      && (\n        settings.autonomous_mode !== defaultSettings.autonomous_mode ||\n        settings.require_plan !== defaultSettings.require_plan ||\n        settings.require_approval !== defaultSettings.require_approval\n      );\n    if (shouldWriteSettings && settings) {\n      lines.push('settings:');\n      lines.push(`  autonomous_mode: ${settings.autonomous_mode}`);\n      lines.push(`  require_plan: ${settings.require_plan}`);\n      lines.push(`  require_approval: ${settings.require_approval}`);\n      lines.push('');\n    }\n\n    // Approval section\n    const approval = status.approval;\n    const shouldWriteApproval = approval && (\n      approval.plan_created ||\n      approval.plan_approved ||\n      Boolean(approval.approved_by) ||\n      Boolean(approval.approved_at) ||\n      Boolean(approval.approval_notes)\n    );\n    if (shouldWriteApproval && approval) {\n      lines.push('approval:');\n      lines.push(`  plan_created: ${approval.plan_created}`);\n      lines.push(`  plan_approved: ${approval.plan_approved}`);\n      if (approval.approved_by) {\n        lines.push(`  approved_by: \"${approval.approved_by}\"`);\n      }\n      if (approval.approved_at) {\n        lines.push(`  approved_at: \"${approval.approved_at}\"`);\n      }\n      if (approval.approval_notes) {\n        lines.push(`  approval_notes: \"${approval.approval_notes}\"`);\n      }\n      lines.push('');\n    }\n\n    return lines.join('\\n') + '\\n';\n  }\n}\n"
  },
  {
    "path": "src/workflow/status/templates.ts",
    "content": "/**\n * PREVC Status Templates\n *\n * Templates for creating initial workflow status structures.\n */\n\nimport {\n  PrevcStatus,\n  PrevcPhase,\n  PrevcRole,\n  ProjectScale,\n  PhaseStatus,\n  ExecutionAction,\n} from '../types';\nimport { PREVC_PHASE_ORDER } from '../phases';\nimport { getScaleRoute } from '../scaling';\n\n/**\n * Phase names for resume context\n */\nconst PHASE_NAMES: Record<PrevcPhase, string> = {\n  P: 'Planejamento',\n  R: 'Revisão',\n  E: 'Execução',\n  V: 'Validação',\n  C: 'Confirmação',\n};\n\n/**\n * Step context for resume context generation\n */\ninterface StepContext {\n  planPhase?: string;\n  stepIndex?: number;\n  stepDescription?: string;\n}\n\n/**\n * Generate resume context based on current phase and action\n */\nexport function generateResumeContext(\n  phase: PrevcPhase,\n  action: ExecutionAction,\n  stepContext?: StepContext\n): string {\n  const phaseName = PHASE_NAMES[phase];\n\n  switch (action) {\n    case 'started':\n      return `Fase ${phase} (${phaseName}) em progresso`;\n    case 'completed':\n      return `Fase ${phase} (${phaseName}) concluída`;\n    case 'plan_linked':\n      return `Plano vinculado - aguardando revisão`;\n    case 'plan_approved':\n      return `Plano aprovado - pronto para execução`;\n    case 'phase_skipped':\n      return `Fase ${phase} ignorada - avançando`;\n    case 'settings_changed':\n      return `Configurações atualizadas`;\n    // Plan-level tracking actions\n    case 'plan_phase_updated':\n      return `Fase do plano atualizada`;\n    case 'decision_recorded':\n      return `Decisão registrada`;\n    // Step-level breadcrumb actions\n    case 'step_started':\n      if (stepContext?.stepDescription) {\n        return `Trabalhando em: ${stepContext.stepDescription}`;\n      }\n      return `Trabalhando no passo ${stepContext?.stepIndex || '?'} de ${stepContext?.planPhase || 'fase'}`;\n    case 'step_completed':\n      if (stepContext?.stepDescription) {\n        return `Concluído: ${stepContext.stepDescription}`;\n      }\n      return `Passo ${stepContext?.stepIndex || '?'} de ${stepContext?.planPhase || 'fase'} concluído`;\n    case 'step_skipped':\n      return `Passo ${stepContext?.stepIndex || '?'} de ${stepContext?.planPhase || 'fase'} ignorado`;\n    default:\n      return `Fase ${phase} (${phaseName})`;\n  }\n}\n\n/**\n * Options for creating initial status\n */\nexport interface CreateStatusOptions {\n  name: string;\n  scale: ProjectScale;\n  phases?: PrevcPhase[];\n  roles?: PrevcRole[] | 'all';\n}\n\n/**\n * Create initial workflow status\n */\nexport function createInitialStatus(options: CreateStatusOptions): PrevcStatus {\n  const { name, scale, phases } = options;\n\n  const route = getScaleRoute(scale);\n  const activePhasesSet = new Set(phases || route.phases);\n\n  // Determine first active phase\n  let firstPhase: PrevcPhase = 'P';\n  for (const phase of PREVC_PHASE_ORDER) {\n    if (activePhasesSet.has(phase)) {\n      firstPhase = phase;\n      break;\n    }\n  }\n\n  // Create phase statuses\n  const phaseStatuses: Record<PrevcPhase, PhaseStatus> = {\n    P: { status: 'pending' },\n    R: { status: 'pending' },\n    E: { status: 'pending' },\n    V: { status: 'pending' },\n    C: { status: 'pending' },\n  };\n\n  // Mark skipped phases\n  for (const phase of PREVC_PHASE_ORDER) {\n    if (!activePhasesSet.has(phase)) {\n      phaseStatuses[phase] = {\n        status: 'skipped',\n        reason: `Not required for scale ${ProjectScale[scale]}`,\n      };\n    }\n  }\n\n  // Mark first phase as in_progress\n  phaseStatuses[firstPhase] = {\n    status: 'in_progress',\n    started_at: new Date().toISOString(),\n  };\n\n  // Create initial status\n  const now = new Date().toISOString();\n\n  return {\n    project: {\n      name,\n      scale,\n      started: now,\n      current_phase: firstPhase,\n    },\n    phases: phaseStatuses,\n    agents: {},\n    roles: {},\n  };\n}\n\n/**\n * Create a minimal status for quick flow\n */\nexport function createQuickFlowStatus(name: string): PrevcStatus {\n  return createInitialStatus({\n    name,\n    scale: ProjectScale.QUICK,\n    phases: ['E', 'V'],\n    roles: ['solo-dev'],\n  });\n}\n\n/**\n * Create a status for small projects\n */\nexport function createSmallProjectStatus(name: string): PrevcStatus {\n  return createInitialStatus({\n    name,\n    scale: ProjectScale.SMALL,\n    phases: ['P', 'E', 'V'],\n    roles: ['planner', 'developer', 'qa'],\n  });\n}\n\n/**\n * Create a status for medium projects\n */\nexport function createMediumProjectStatus(name: string): PrevcStatus {\n  return createInitialStatus({\n    name,\n    scale: ProjectScale.MEDIUM,\n    phases: ['P', 'R', 'E', 'V'],\n    roles: ['planner', 'architect', 'developer', 'qa', 'reviewer'],\n  });\n}\n\n/**\n * Create a status for large projects\n */\nexport function createLargeProjectStatus(name: string): PrevcStatus {\n  return createInitialStatus({\n    name,\n    scale: ProjectScale.LARGE,\n    phases: ['P', 'R', 'E', 'V', 'C'],\n    roles: 'all',\n  });\n}\n\n"
  },
  {
    "path": "src/workflow/types.ts",
    "content": "/**\n * PREVC Workflow Types\n *\n * Core type definitions for the PREVC workflow system:\n * P - Planning\n * R - Review\n * E - Execution\n * V - Validation\n * C - Confirmation\n */\n\n/**\n * The five phases of the PREVC workflow\n */\nexport type PrevcPhase = 'P' | 'R' | 'E' | 'V' | 'C';\n\n/**\n * Available roles in the PREVC workflow\n */\nexport type PrevcRole =\n  | 'planner'\n  | 'designer'\n  | 'architect'\n  | 'developer'\n  | 'qa'\n  | 'reviewer'\n  | 'documenter'\n  | 'solo-dev';\n\n/**\n * Project scale levels for adaptive routing\n */\nexport enum ProjectScale {\n  QUICK = 0, // Bug fixes, tweaks (~5 min)\n  SMALL = 1, // Simple features (~15 min)\n  MEDIUM = 2, // Medium features (~30 min)\n  LARGE = 3, // Products, complex systems, compliance (~1+ hour)\n}\n\n/**\n * Status of a phase or role\n */\nexport type StatusType = 'pending' | 'in_progress' | 'completed' | 'skipped';\n\n/**\n * Definition of a PREVC phase\n */\nexport interface PhaseDefinition {\n  name: string;\n  description: string;\n  roles: PrevcRole[];\n  outputs: string[];\n  optional: boolean;\n  order: number;\n}\n\n/**\n * Definition of a PREVC role\n */\nexport interface RoleDefinition {\n  phase: PrevcPhase | PrevcPhase[];\n  responsibilities: string[];\n  outputs: string[];\n  specialists: string[]; // Mapped agent types from existing system\n}\n\n/**\n * Scale route configuration\n */\nexport interface ScaleRoute {\n  phases: PrevcPhase[];\n  roles: PrevcRole[] | 'all';\n  documents: string[] | 'all';\n  skipReview?: boolean;\n  extras?: string[];\n}\n\n/**\n * Phase status in the workflow\n */\nexport interface PhaseStatus {\n  status: StatusType;\n  started_at?: string;\n  completed_at?: string;\n  role?: PrevcRole;\n  current_task?: string;\n  reason?: string;\n  outputs?: OutputStatus[];\n}\n\n/**\n * Output file status\n */\nexport interface OutputStatus {\n  path: string;\n  status: 'unfilled' | 'filled';\n}\n\n/**\n * Role status in the workflow\n * @deprecated Use AgentStatus instead\n */\nexport interface RoleStatus {\n  status?: StatusType;\n  last_active?: string;\n  phase?: PrevcPhase;\n  current_task?: string;\n  outputs?: string[];\n}\n\n/**\n * Agent status in the workflow (replaces RoleStatus)\n */\nexport interface AgentStatus {\n  status: StatusType;\n  started_at?: string;\n  completed_at?: string;\n  outputs?: string[];\n}\n\n/**\n * Plan reference in workflow (lightweight)\n */\nexport interface WorkflowPlanRef {\n  slug: string;\n  path: string;\n  status: 'active' | 'completed' | 'paused';\n}\n\n/**\n * Workflow settings for gate control\n */\nexport interface WorkflowSettings {\n  /** Skip all gates and plan requirements */\n  autonomous_mode: boolean;\n  /** Require a linked plan before advancing P → R */\n  require_plan: boolean;\n  /** Require plan approval before advancing R → E */\n  require_approval: boolean;\n}\n\n/**\n * Gate types for workflow phase transitions\n */\nexport type GateType = 'plan_required' | 'approval_required';\n\n/**\n * Plan approval status tracking\n */\nexport interface PlanApproval {\n  /** Whether a plan has been created/linked */\n  plan_created: boolean;\n  /** Whether the plan has been approved */\n  plan_approved: boolean;\n  /** Who approved the plan */\n  approved_by?: PrevcRole | string;\n  /** When the plan was approved */\n  approved_at?: string;\n  /** Optional notes from approver */\n  approval_notes?: string;\n}\n\n/**\n * Execution history action types\n */\nexport type ExecutionAction =\n  | 'started'\n  | 'completed'\n  | 'plan_linked'\n  | 'plan_approved'\n  | 'phase_skipped'\n  | 'settings_changed'\n  // Plan-level tracking actions\n  | 'plan_phase_updated'\n  | 'decision_recorded'\n  // Step-level actions for breadcrumb tracking\n  | 'step_started'\n  | 'step_completed'\n  | 'step_skipped';\n\n/**\n * Entry in the execution history\n */\nexport interface ExecutionHistoryEntry {\n  timestamp: string;\n  phase: PrevcPhase;\n  action: ExecutionAction;\n  plan?: string;\n  approved_by?: string;\n  description?: string;\n  // Step-level fields for breadcrumb tracking\n  /** Plan phase ID (e.g., \"phase-1\") */\n  planPhase?: string;\n  /** 1-based step index within the plan phase */\n  stepIndex?: number;\n  /** Human-readable description of the step */\n  stepDescription?: string;\n  /** Output artifact from step completion */\n  output?: string;\n  /** Execution notes */\n  notes?: string;\n}\n\n/**\n * Execution history tracking for workflow\n */\nexport interface ExecutionHistory {\n  history: ExecutionHistoryEntry[];\n  last_activity: string;\n  resume_context: string;\n}\n\n/**\n * Project metadata in the workflow status\n */\nexport interface ProjectMetadata {\n  name: string;\n  scale: ProjectScale | keyof typeof ProjectScale;\n  started: string;\n  current_phase: PrevcPhase;\n  /** Primary plan being executed */\n  plan?: string;\n  /** All linked plans */\n  plans?: WorkflowPlanRef[];\n  /** Workflow settings for gate control */\n  settings?: WorkflowSettings;\n}\n\n/**\n * Complete workflow status structure (stored canonically in harness workflow state)\n */\nexport interface PrevcStatus {\n  project: ProjectMetadata;\n  phases: Record<PrevcPhase, PhaseStatus>;\n  /** Execution history (replaces roles) */\n  execution?: ExecutionHistory;\n  /** Agent status tracking (replaces roles) */\n  agents: Record<string, AgentStatus>;\n  /** Legacy roles - kept for backward compatibility */\n  roles: Partial<Record<PrevcRole, RoleStatus>>;\n  /** Plan approval tracking */\n  approval?: PlanApproval;\n}\n\n/**\n * Context for project analysis and scale detection\n */\nexport interface ProjectContext {\n  name: string;\n  description: string;\n  files?: string[];\n  complexity?: 'low' | 'medium' | 'high';\n  hasCompliance?: boolean;\n}\n\n/**\n * Update payload for phase status\n */\nexport interface PhaseUpdate {\n  status?: StatusType;\n  role?: PrevcRole;\n  current_task?: string;\n  outputs?: OutputStatus[];\n}\n\n/**\n * Update payload for role status\n * @deprecated Use AgentUpdate instead\n */\nexport interface RoleUpdate {\n  status?: StatusType;\n  phase?: PrevcPhase;\n  current_task?: string;\n  outputs?: string[];\n  last_active?: string;\n}\n\n/**\n * Update payload for agent status (replaces RoleUpdate)\n */\nexport interface AgentUpdate {\n  status?: StatusType;\n  outputs?: string[];\n}\n\n/**\n * Contribution in a collaboration session\n */\nexport interface Contribution {\n  role: PrevcRole;\n  message: string;\n  timestamp: Date;\n}\n\n/**\n * Status of a collaboration session\n */\nexport interface CollaborationStatus {\n  id: string;\n  topic: string;\n  participants: PrevcRole[];\n  started: Date;\n  status: 'active' | 'synthesizing' | 'concluded';\n}\n\n/**\n * Synthesis result from a collaboration session\n */\nexport interface CollaborationSynthesis {\n  topic: string;\n  participants: PrevcRole[];\n  contributions: Contribution[];\n  decisions: string[];\n  recommendations: string[];\n}\n\n/**\n * Suggested agent step in orchestration sequence\n */\nexport interface AgentSequenceStep {\n  agent: string;\n  task: string;\n}\n\n/**\n * Skill suggestion for orchestration\n */\nexport interface SkillSuggestion {\n  slug: string;\n  name: string;\n  description: string;\n  path: string;\n  isBuiltIn: boolean;\n}\n\n/**\n * Tool usage guidance for explicit agent orchestration\n */\nexport interface ToolGuidance {\n  /** Example agent discovery tool call */\n  discoverExample: string;\n  /** Example agent sequence tool call */\n  sequenceExample: string;\n  /** Example handoff tool call */\n  handoffExample: string;\n}\n\n/**\n * Phase orchestration guidance for agent-based workflows\n */\nexport interface PhaseOrchestration {\n  /** Recommended agents for this phase */\n  recommendedAgents: string[];\n  /** Suggested sequence of agents with tasks */\n  suggestedSequence: AgentSequenceStep[];\n  /** Agent to start with */\n  startWith: string;\n  /** Instruction for starting the phase */\n  instruction: string;\n  /** Recommended skills for this phase */\n  recommendedSkills?: SkillSuggestion[];\n  /** Explicit tool usage guidance for agent orchestration */\n  toolGuidance?: ToolGuidance;\n  /** Step-by-step orchestration instructions with tool calls */\n  orchestrationSteps?: string[];\n}\n"
  },
  {
    "path": "templates/packages/cli.README.md",
    "content": "# @dotcontext/cli\n\nOperator-facing package for dotcontext.\n\nThis package owns:\n\n- the `dotcontext` binary\n- local operator workflows\n- MCP installation into supported tools\n- sync, reverse-sync, import/export, and workflow UX\n\nIt depends on the harness and MCP boundaries but is the user-facing entrypoint.\n"
  },
  {
    "path": "templates/packages/harness.README.md",
    "content": "# @dotcontext/harness\n\nReusable harness runtime for dotcontext.\n\nThis package owns:\n\n- harness domain services\n- workflow runtime\n- plans, agents, context, and skills orchestration\n- transport-agnostic APIs intended to be consumed by adapters such as MCP\n\nIt does not own CLI UX or protocol-specific startup concerns.\n"
  },
  {
    "path": "templates/packages/mcp.README.md",
    "content": "# @dotcontext/mcp\n\nModel Context Protocol adapter for dotcontext.\n\nThis package owns:\n\n- the MCP server binary\n- the MCP installer command surface\n- MCP tool registration\n- request/response translation for the harness runtime\n\nIt exposes the harness over MCP and should remain thinner than the runtime it serves.\n\n## Usage\n\n```bash\n# Install MCP configuration into supported AI tools\nnpx @dotcontext/mcp install\n\n# Start the MCP server directly\nnpx -y @dotcontext/mcp@latest\n```\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"baseUrl\": \"./src\",\n    \"paths\": {\n      \"@generators/agents/*\": [\"generators/agents/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"**/*.test.ts\"]\n}"
  }
]