feat: implement company template system with 7 built-in presets#85
feat: implement company template system with 7 built-in presets#85
Conversation
Add a two-pass template rendering pipeline that bootstraps company configurations from pre-built YAML templates with Jinja2 variable substitution. Pass 1 extracts metadata and variables from plain YAML, Pass 2 renders the full template through a SandboxedEnvironment then validates as RootConfig. - Extract deep_merge to config/utils.py as shared utility - Add template error hierarchy (TemplateError, NotFound, Render, Validation) - Add Pydantic schema models (CompanyTemplate, TemplateVariable, etc.) - Add 4 minimal personality presets and auto-name generation - Add template loader with user dir (~/.ai-company/templates/) override - Add Jinja2 renderer pipeline with variable collection and agent expansion - Ship 7 built-in templates: solo_founder, startup, dev_shop, product_team, agency, full_company, research_lab - Add jinja2==3.1.6 explicit dependency - Exclude Jinja2 template files from check-yaml pre-commit hook - 88 new unit tests, 97% overall coverage Closes #60
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a template subsystem: Pydantic schema, two‑pass loader, sandboxed Jinja2 renderer, personality presets/auto-naming, seven built-in YAML templates, template exceptions, a shared config deep_merge utility, Jinja2 dependency, tests, and pre-commit exclusion for builtins. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant Loader as Loader (pass1)
participant Source as YAML Source
participant Renderer as Renderer (pass2)
participant Validator as RootConfig Validator
User->>Loader: list_templates() / load_template(name)
Loader->>Source: fetch raw YAML (builtin or user)
Source-->>Loader: raw YAML text
Loader->>Loader: strip Jinja2, parse metadata (pass1)
Loader-->>User: LoadedTemplate (raw_yaml, metadata)
User->>Renderer: render_template(LoadedTemplate, variables)
Renderer->>Renderer: collect variables (defaults + user)
Renderer->>Renderer: create sandboxed Jinja2 env
Renderer->>Renderer: render raw_yaml -> rendered YAML
Renderer->>Loader: parse rendered YAML (pass2)
Loader-->>Renderer: parsed config dict
Renderer->>Renderer: expand agents, build departments, to_float, deep_merge defaults
Renderer->>Validator: validate merged dict -> RootConfig
Validator-->>Renderer: validated RootConfig
Renderer-->>User: RootConfig
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust and flexible template system for defining AI company configurations. It establishes a two-stage process for loading and rendering YAML-based templates, enabling dynamic variable substitution and ensuring structural integrity through Pydantic validation. The system comes with a set of predefined company structures and allows for user-defined overrides, significantly enhancing the configurability and reusability of company setups. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive company template system, featuring a two-pass Jinja2 rendering pipeline, Pydantic schema validation, and extensive testing. However, it contains several critical vulnerabilities related to file handling, including path traversal and arbitrary file reads in the template loading logic. A Python 2-style exception clause also causes a Denial of Service in Python 3 environments. Additionally, a critical syntax error prevents the code from running, and there are opportunities to make exception handling more specific and robust, and to fix a potential bug when scanning for user templates.
src/ai_company/templates/loader.py
Outdated
|
|
||
| # Try user directory first. | ||
| if _USER_TEMPLATES_DIR.is_dir(): | ||
| user_path = _USER_TEMPLATES_DIR / f"{name_clean}.yaml" |
There was a problem hiding this comment.
The load_template function is vulnerable to path traversal. It constructs a file path by joining a base directory with a user-supplied name without sanitization. An attacker could use absolute paths or parent directory references (e.g., ../../) to escape the intended directory and read arbitrary .yaml files on the system.
Recommendation: Sanitize the name input using Path(name).name to ensure only the filename is used, or verify that the resolved path is contained within _USER_TEMPLATES_DIR.
| user_path = _USER_TEMPLATES_DIR / f"{name_clean}.yaml" | |
| user_path = _USER_TEMPLATES_DIR / f"{Path(name_clean).name}.yaml" |
| path = Path(path) | ||
| if not path.is_file(): | ||
| msg = f"Template file not found: {path}" | ||
| raise TemplateNotFoundError( | ||
| msg, | ||
| locations=(ConfigLocation(file_path=str(path)),), | ||
| ) | ||
| return _load_from_file(path) |
There was a problem hiding this comment.
The load_template_file function allows reading arbitrary files from the filesystem. If the path argument is controlled by an untrusted user, it can be used to read sensitive system files that the application has access to.
Recommendation: Implement strict validation to ensure the provided path is within an allowed directory or matches a whitelist of permitted files.
src/ai_company/templates/loader.py
Outdated
|
|
||
| # User templates (higher priority). | ||
| if _USER_TEMPLATES_DIR.is_dir(): | ||
| for path in sorted(_USER_TEMPLATES_DIR.glob("*.yaml")): |
There was a problem hiding this comment.
The glob("*.yaml") pattern can match directories if their names end with .yaml. The code doesn't check if path is a file before attempting to read it in _load_from_file, which will cause an IsADirectoryError. You should add a check to ensure you only process files.
| for path in sorted(_USER_TEMPLATES_DIR.glob("*.yaml")): | |
| for path in sorted(p for p in _USER_TEMPLATES_DIR.glob("*.yaml") if p.is_file()): |
src/ai_company/templates/loader.py
Outdated
| except Exception: | ||
| logger.warning("Skipping invalid user template: %s", path) |
There was a problem hiding this comment.
Using a broad except Exception: can mask underlying issues and makes debugging harder. It's better to catch only the specific exceptions you expect from template loading, such as TemplateRenderError and TemplateValidationError. Also, including the exception message in the log will provide more context about why a user template was skipped.
| except Exception: | |
| logger.warning("Skipping invalid user template: %s", path) | |
| except (TemplateRenderError, TemplateValidationError) as e: | |
| logger.warning("Skipping invalid user template %s: %s", path, e) |
src/ai_company/templates/loader.py
Outdated
| except Exception: | ||
| logger.warning("Skipping invalid builtin template: %s", name) |
There was a problem hiding this comment.
Using a broad except Exception: can hide unexpected errors. For built-in templates, an error should be more visible than a warning, as it indicates a bug in the application itself. I suggest catching specific expected exceptions and logging them as errors to make them more prominent. This will improve debuggability without crashing the application if list_templates is called in a context where a crash is undesirable.
| except Exception: | |
| logger.warning("Skipping invalid builtin template: %s", name) | |
| except (TemplateRenderError, TemplateValidationError) as e: | |
| logger.error("Skipping invalid builtin template %r: %s", name, e) |
src/ai_company/templates/renderer.py
Outdated
| """ | ||
| try: | ||
| return float(value) | ||
| except TypeError, ValueError: |
There was a problem hiding this comment.
This line uses Python 2-style exception handling syntax (except TypeError, ValueError:), which is a SyntaxError in Python 3 and will cause a Denial of Service. To fix this, group the exceptions in a tuple for Python 3 compatibility.
| except TypeError, ValueError: | |
| except (TypeError, ValueError): |
src/ai_company/templates/loader.py
Outdated
| except Exception as exc: | ||
| msg = f"Template validation failed for {source_name}: {exc}" | ||
| raise TemplateValidationError( | ||
| msg, | ||
| locations=(ConfigLocation(file_path=source_name),), | ||
| ) from exc |
There was a problem hiding this comment.
Catching a broad Exception can hide unexpected errors during Pydantic model instantiation. It's better to catch the specific ValidationError that Pydantic raises. This makes the error handling more precise and robust.
You will need to add from pydantic import ValidationError to the imports at the top of the file.
| except Exception as exc: | |
| msg = f"Template validation failed for {source_name}: {exc}" | |
| raise TemplateValidationError( | |
| msg, | |
| locations=(ConfigLocation(file_path=source_name),), | |
| ) from exc | |
| except ValidationError as exc: | |
| msg = f"Template validation failed for {source_name}: {exc}" | |
| raise TemplateValidationError( | |
| msg, | |
| locations=(ConfigLocation(file_path=source_name),), | |
| ) from exc |
src/ai_company/templates/renderer.py
Outdated
| except Exception as exc: | ||
| msg = f"Jinja2 rendering failed for {source_name}: {exc}" | ||
| raise TemplateRenderError( | ||
| msg, | ||
| locations=(ConfigLocation(file_path=source_name),), | ||
| ) from exc |
There was a problem hiding this comment.
Catching a broad Exception here can hide non-Jinja2 related bugs. It's better to be more specific and catch jinja2.TemplateError, which is the base exception for errors during Jinja2 processing. This makes the error handling more robust.
To avoid name collisions with the local TemplateError, you can import it with an alias, like from jinja2.exceptions import TemplateError as JinjaTemplateError.
| except Exception as exc: | |
| msg = f"Jinja2 rendering failed for {source_name}: {exc}" | |
| raise TemplateRenderError( | |
| msg, | |
| locations=(ConfigLocation(file_path=source_name),), | |
| ) from exc | |
| except jinja2.TemplateError as exc: | |
| msg = f"Jinja2 rendering failed for {source_name}: {exc}" | |
| raise TemplateRenderError( | |
| msg, | |
| locations=(ConfigLocation(file_path=source_name),), | |
| ) from exc |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/ai_company/templates/builtins/dev_shop.yaml`:
- Line 21: The YAML lint error is caused by spacing inside the Jinja braces for
the budget_monthly value; update the Jinja expression associated with the
budget_monthly key (the template "{{ budget | default(75.0) }}") to remove the
extra space immediately after "{{" and before "}}" so it conforms to the braces
rule (e.g., use the same expression without those inner-leading/trailing
spaces), then re-run the YAML linter to confirm the file passes.
In `@src/ai_company/templates/builtins/full_company.yaml`:
- Line 21: The Jinja expression used for budget_monthly violates the YAML lint
"braces" rule; update the template expression for budget_monthly (the Jinja
token containing budget | default(200.0)) to remove the extra spaces directly
inside the curly braces so the braces are tight (adjust the Jinja tag around
budget | default(200.0) accordingly) to satisfy yaml linting.
In `@src/ai_company/templates/builtins/product_team.yaml`:
- Line 21: The Jinja expression on the budget_monthly line has extra spaces
inside the braces which triggers YAML linting; update the template expression in
product_team.yaml (the budget_monthly: line containing the {{ ... }} expression)
to remove the spaces immediately after '{{' and before '}}' so the expression
reads with no inner brace padding (e.g., change the existing {{ budget |
default(80.0) }} form to use no spaces adjacent to the braces).
In `@src/ai_company/templates/builtins/solo_founder.yaml`:
- Line 21: The Jinja expression for the budget in the template key
budget_monthly uses spacing that triggers YAML linting; update the Jinja braces
to remove inner spaces so the line reads with tightly wrapped braces (e.g.,
change the Jinja expression used in budget_monthly to {{budget |
default(25.0)}}) and commit the change so check-yaml passes.
In `@src/ai_company/templates/builtins/startup.yaml`:
- Line 21: The YAML lint error is caused by spacing inside the Jinja braces for
the budget_monthly key; update the Jinja expression from "{{ budget |
default(50.0) }}" to remove the inner brace spacing so it reads "{{budget |
default(50.0)}}" (i.e., edit the budget_monthly line replacing the existing
Jinja tag with {{budget | default(50.0)}} to satisfy the 'braces' rule).
In `@src/ai_company/templates/loader.py`:
- Around line 289-293: Move the call to _normalize_template_data and the shape
check for template_data into the validation try/except so malformed payloads
raise a wrapped TemplateValidationError instead of raw AttributeError/TypeError:
inside the block that constructs CompanyTemplate(**...), first assert
template_data is a mapping (e.g. isinstance(template_data, Mapping) or dict)
and/or check required keys, then call _normalize_template_data(template_data)
and pass the result to CompanyTemplate; catch exceptions and re-raise as
TemplateValidationError with the original exception info so errors from
normalization or instantiation are consistently wrapped.
- Around line 335-349: The helper _to_float currently swallows malformed numeric
strings by returning 0.0; change it to surface errors instead: in the except
ValueError branch raise a ValueError (including the original value and the
original exception message) so invalid YAML numeric input fails validation, keep
the final return float(value) for non-strings, and ensure any callers of
_to_float (where it’s used to coerce template fields) either validate input
beforehand or handle the propagated ValueError.
In `@src/ai_company/templates/renderer.py`:
- Around line 199-207: The code assumes data["template"] is a dict and returns
it directly in renderer.py; add a type check after extracting result (e.g.,
result = data["template"]) to verify it's a mapping/dict and, if not, raise
TemplateRenderError with a clear message (include source_name and the same
locations tuple using ConfigLocation) so non-mapping render outputs
(scalars/lists) produce a TemplateRenderError instead of later AttributeError;
reference the existing TemplateRenderError, ConfigLocation, data, source_name,
and result symbols when locating where to add this validation.
- Around line 380-392: The _safe_float function uses invalid exception syntax
and masks conversion errors; update it to catch exceptions with Python 3 syntax
(e.g., except (TypeError, ValueError) as e:) and stop silently returning
0.0—instead either re-raise a ValueError with a clear message including the
offending value (e.g., raise ValueError(f"Invalid numeric value for config:
{value!r}") from e) or let the original exception propagate so callers can
handle validation at system boundaries; modify the _safe_float function to
implement one of these behaviors and keep the float(value) attempt intact.
In `@tests/unit/config/test_utils.py`:
- Around line 8-68: There are duplicate TestDeepMerge tests for deep_merge;
consolidate by keeping this canonical suite (class TestDeepMerge with its test
methods) in tests/unit/config/test_utils.py and remove the overlapping tests in
tests/unit/config/test_loader.py; ensure any required import of deep_merge is
present in test_utils.py, that pytest markers (pytest.mark.unit) remain, and
that you delete or disable the duplicate TestDeepMerge class/methods in
test_loader.py so there’s a single authoritative test location.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (26)
.pre-commit-config.yamlpyproject.tomlsrc/ai_company/config/loader.pysrc/ai_company/config/utils.pysrc/ai_company/templates/__init__.pysrc/ai_company/templates/builtins/__init__.pysrc/ai_company/templates/builtins/agency.yamlsrc/ai_company/templates/builtins/dev_shop.yamlsrc/ai_company/templates/builtins/full_company.yamlsrc/ai_company/templates/builtins/product_team.yamlsrc/ai_company/templates/builtins/research_lab.yamlsrc/ai_company/templates/builtins/solo_founder.yamlsrc/ai_company/templates/builtins/startup.yamlsrc/ai_company/templates/errors.pysrc/ai_company/templates/loader.pysrc/ai_company/templates/presets.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pytests/unit/config/test_loader.pytests/unit/config/test_utils.pytests/unit/templates/__init__.pytests/unit/templates/conftest.pytests/unit/templates/test_loader.pytests/unit/templates/test_presets.pytests/unit/templates/test_renderer.pytests/unit/templates/test_schema.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (5)
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
Pin all dependency versions using '==' in pyproject.toml; organize into 'test' and 'dev' dependency groups
Files:
pyproject.toml
**/*.{yaml,yml,json,toml}
📄 CodeRabbit inference engine (CLAUDE.md)
Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files
Files:
pyproject.tomlsrc/ai_company/templates/builtins/dev_shop.yamlsrc/ai_company/templates/builtins/full_company.yamlsrc/ai_company/templates/builtins/solo_founder.yamlsrc/ai_company/templates/builtins/product_team.yamlsrc/ai_company/templates/builtins/agency.yamlsrc/ai_company/templates/builtins/startup.yamlsrc/ai_company/templates/builtins/research_lab.yaml
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Use PEP 649 native lazy annotations instead offrom __future__ import annotations(Python 3.14+)
All public functions and classes must include type hints; enforce via mypy strict mode
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Keep functions under 50 lines and files under 800 lines
Create new objects instead of mutating existing ones; enforce immutability patterns
Use Pydantic v2 models (BaseModel, model_validator, ConfigDict) for data validation
Validate at system boundaries (user input, external APIs, config files); never silently swallow errors
Enforce 88-character line length (via ruff configuration)
Files:
tests/unit/templates/test_schema.pysrc/ai_company/config/utils.pytests/unit/config/test_loader.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pytests/unit/templates/test_renderer.pysrc/ai_company/templates/errors.pytests/unit/config/test_utils.pysrc/ai_company/templates/presets.pytests/unit/templates/test_loader.pysrc/ai_company/config/loader.pytests/unit/templates/test_presets.pysrc/ai_company/templates/__init__.pytests/unit/templates/conftest.pysrc/ai_company/templates/loader.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Mark tests with@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e, or@pytest.mark.slow
Maintain minimum 80% code coverage, enforced in CI via pytest-cov
Set test timeout to 30 seconds per test; use asyncio_mode = 'auto' for async tests without manual@pytest.mark.asyncio
Files:
tests/unit/templates/test_schema.pytests/unit/config/test_loader.pytests/unit/templates/test_renderer.pytests/unit/config/test_utils.pytests/unit/templates/test_loader.pytests/unit/templates/test_presets.pytests/unit/templates/conftest.py
{src/**/*.py,tests/**/*.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Run ruff check and ruff format as pre-commit hooks on all Python files; enforce no commits to main branch
Files:
tests/unit/templates/test_schema.pysrc/ai_company/config/utils.pytests/unit/config/test_loader.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pytests/unit/templates/test_renderer.pysrc/ai_company/templates/errors.pytests/unit/config/test_utils.pysrc/ai_company/templates/presets.pytests/unit/templates/test_loader.pysrc/ai_company/config/loader.pytests/unit/templates/test_presets.pysrc/ai_company/templates/__init__.pytests/unit/templates/conftest.pysrc/ai_company/templates/loader.py
🧠 Learnings (11)
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to pyproject.toml : Minimize external dependencies. When adding new dependencies, add them to `pyproject.toml` with exact version pins.
Applied to files:
pyproject.toml
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Framework for orchestrating autonomous AI agents within a virtual company structure; use FastAPI for REST/WebSocket APIs
Applied to files:
src/ai_company/templates/builtins/full_company.yamlsrc/ai_company/templates/builtins/startup.yaml
📚 Learning: 2026-01-24T09:54:45.426Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/agents.instructions.md:0-0
Timestamp: 2026-01-24T09:54:45.426Z
Learning: Applies to agents/test*.py : Agent tests should cover: successful generation with valid output, handling malformed LLM responses, error conditions (network errors, timeouts), output format validation, and integration with story state
Applied to files:
tests/unit/templates/test_schema.py
📚 Learning: 2026-01-24T09:54:45.426Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/agents.instructions.md:0-0
Timestamp: 2026-01-24T09:54:45.426Z
Learning: Applies to agents/*.py : Validate all LLM outputs by checking required fields, verifying data types, handling malformed responses, and using Pydantic models for structured data
Applied to files:
tests/unit/templates/test_schema.py
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Applies to **/*.{yaml,yml,json,toml} : Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-02-26T17:43:50.902Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to .pre-commit-config.yaml : For pre-commit tools that should check entire directories, use `pass_filenames: false` instead of relying on staged file filtering.
Applied to files:
.pre-commit-config.yaml
📚 Learning: 2026-01-24T09:54:56.100Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/test-files.instructions.md:0-0
Timestamp: 2026-01-24T09:54:56.100Z
Learning: Each test should be independent and not rely on other tests; use pytest fixtures for test setup (shared fixtures in `tests/conftest.py`); clean up resources in teardown/fixtures
Applied to files:
tests/unit/config/test_utils.pytests/unit/templates/conftest.py
📚 Learning: 2026-01-24T16:33:29.354Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-24T16:33:29.354Z
Learning: Applies to tests/unit/test_*.py : Write unit tests for new functionality using pytest in `tests/unit/` with `test_*.py` naming convention
Applied to files:
tests/unit/templates/test_presets.pytests/unit/templates/conftest.py
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to tests/unit/test_*.py : Write unit tests for new functionality using pytest. Place test files in `tests/unit/` with `test_*.py` naming convention.
Applied to files:
tests/unit/templates/conftest.py
📚 Learning: 2026-01-24T09:54:56.100Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/test-files.instructions.md:0-0
Timestamp: 2026-01-24T09:54:56.100Z
Learning: Applies to **/tests/conftest.py : Place shared pytest fixtures in `tests/conftest.py`
Applied to files:
tests/unit/templates/conftest.py
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to tests/**/*.py : Use pytest fixtures for test setup. Shared fixtures should be in `tests/conftest.py`
Applied to files:
tests/unit/templates/conftest.py
🧬 Code graph analysis (10)
tests/unit/templates/test_schema.py (2)
src/ai_company/core/enums.py (2)
CompanyType(72-82)SeniorityLevel(6-21)src/ai_company/templates/schema.py (5)
CompanyTemplate(144-237)TemplateAgentConfig(47-79)TemplateDepartmentConfig(82-107)TemplateMetadata(110-141)TemplateVariable(12-44)
tests/unit/config/test_loader.py (1)
tests/unit/config/test_utils.py (1)
TestDeepMerge(9-68)
src/ai_company/templates/schema.py (1)
src/ai_company/core/enums.py (2)
CompanyType(72-82)SeniorityLevel(6-21)
tests/unit/templates/test_renderer.py (4)
src/ai_company/config/schema.py (1)
RootConfig(199-323)src/ai_company/templates/errors.py (1)
TemplateRenderError(14-15)src/ai_company/templates/loader.py (2)
load_template(133-162)load_template_file(165-185)src/ai_company/templates/renderer.py (1)
render_template(38-84)
src/ai_company/templates/errors.py (1)
src/ai_company/config/errors.py (2)
ConfigError(24-59)ConfigLocation(7-21)
tests/unit/config/test_utils.py (1)
tests/unit/config/test_loader.py (7)
TestDeepMerge(41-99)test_empty_base(93-95)test_empty_override(97-99)test_simple_override(42-46)test_nested_merge(48-52)test_deeply_nested(81-85)test_deeply_nested(485-489)
tests/unit/templates/test_loader.py (2)
src/ai_company/templates/errors.py (3)
TemplateNotFoundError(10-11)TemplateRenderError(14-15)TemplateValidationError(18-33)src/ai_company/templates/loader.py (6)
LoadedTemplate(65-76)TemplateInfo(48-61)list_builtin_templates(124-130)list_templates(79-121)load_template(133-162)load_template_file(165-185)
tests/unit/templates/test_presets.py (1)
src/ai_company/templates/presets.py (2)
generate_auto_name(87-103)get_personality_preset(67-84)
src/ai_company/templates/__init__.py (4)
src/ai_company/templates/errors.py (4)
TemplateError(6-7)TemplateNotFoundError(10-11)TemplateRenderError(14-15)TemplateValidationError(18-33)src/ai_company/templates/loader.py (6)
LoadedTemplate(65-76)TemplateInfo(48-61)list_builtin_templates(124-130)list_templates(79-121)load_template(133-162)load_template_file(165-185)src/ai_company/templates/renderer.py (1)
render_template(38-84)src/ai_company/templates/schema.py (5)
CompanyTemplate(144-237)TemplateAgentConfig(47-79)TemplateDepartmentConfig(82-107)TemplateMetadata(110-141)TemplateVariable(12-44)
src/ai_company/templates/loader.py (3)
src/ai_company/config/errors.py (1)
ConfigLocation(7-21)src/ai_company/templates/errors.py (3)
TemplateNotFoundError(10-11)TemplateRenderError(14-15)TemplateValidationError(18-33)src/ai_company/templates/schema.py (1)
CompanyTemplate(144-237)
🪛 YAMLlint (1.38.0)
src/ai_company/templates/builtins/dev_shop.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/full_company.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/solo_founder.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/product_team.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/agency.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/startup.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
src/ai_company/templates/builtins/research_lab.yaml
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🔇 Additional comments (16)
.pre-commit-config.yaml (1)
17-17: No changes needed. The exclusion of built-in templates fromcheck-yamlis justified because these files contain Jinja2 template syntax (e.g.,{{ budget | default(100.0) }}), which would cause false positives with static YAML validation. Template validation is properly handled at runtime through the template loader and renderer system (seesrc/ai_company/templates/loader.pyandrenderer.py), which validates templates against the RootConfig schema.Likely an incorrect or invalid review comment.
src/ai_company/templates/builtins/agency.yaml (1)
1-52: LGTM!The template structure is well-defined with proper metadata, variables, departments, and agents. The budget percentages correctly sum to 100%. The YAMLlint warnings about brace spacing on line 21 are false positives—
{{ budget | default(100.0) }}follows standard Jinja2 formatting conventions with spaces inside braces and around the pipe operator.src/ai_company/config/utils.py (1)
7-31: LGTM!The implementation correctly ensures immutability by deep-copying both inputs. The recursive merge logic properly handles nested dicts while replacing other types. The docstring accurately describes the behavior.
tests/unit/templates/conftest.py (1)
1-131: LGTM!Well-structured test fixtures with clear YAML template constants covering minimal, variable-based, required-variable, invalid syntax, and missing key scenarios. The factory fixtures follow pytest best practices and enable isolated test setup.
src/ai_company/templates/presets.py (1)
1-103: LGTM!The implementation is well-designed with proper error messages, case-insensitive lookups, and deterministic name generation via optional seeding. The shallow copy in
get_personality_presetis sufficient since all nested values (tuples, strings) are immutable.src/ai_company/templates/errors.py (1)
1-33: LGTM!Clean exception hierarchy that properly extends the existing
ConfigErrorframework.TemplateValidationErrorappropriately captures per-field errors as an immutable tuple structure for detailed validation feedback.tests/unit/templates/test_presets.py (1)
1-74: LGTM!Comprehensive test coverage for both
get_personality_presetandgenerate_auto_namefunctions. Tests properly verify case insensitivity, whitespace handling, copy semantics, error conditions, and deterministic behavior with seeds. All tests are correctly marked with@pytest.mark.unit.pyproject.toml (1)
16-16: jinja2==3.1.6 is valid and recommended. This is the latest version and includes patches for CVE-2025-27516 (sandbox breakout via |attr filter), CVE-2024-56201, CVE-2024-34064, and CVE-2024-22195. The version pin correctly follows the coding guidelines with exact pinning using '=='.src/ai_company/config/loader.py (1)
20-20: Shareddeep_mergeintegration looks correct.The refactor keeps merge order and validation flow intact while centralizing merge behavior in one utility.
Also applies to: 422-427, 467-467
tests/unit/templates/test_schema.py (1)
18-255: Strong schema validation coverage in this test suite.The added cases exercise core constraints (bounds, duplicates, enum validation, and frozen-model behavior) well.
tests/unit/config/test_loader.py (1)
24-24:deep_mergetest migration is consistent and complete.All merge-behavior assertions were correctly updated to the shared utility import without changing intended coverage.
Also applies to: 37-99
src/ai_company/templates/builtins/research_lab.yaml (1)
1-50: Template content is consistent with the new template pipeline.Metadata, variables, departments, and agent definitions are coherent and align with expected built-in template shape.
src/ai_company/templates/__init__.py (1)
24-64: Public re-export surface looks clean and complete.The package API exposes the expected loader/renderer/schema/error symbols consistently.
tests/unit/templates/test_loader.py (1)
37-201: Loader test coverage is robust for both success and failure paths.The suite exercises precedence, path handling, and error types well.
tests/unit/templates/test_renderer.py (1)
22-191: Renderer tests cover key pipeline invariants effectively.Good coverage across defaults, overrides, required-variable failures, and invalid Jinja2 behavior.
src/ai_company/templates/schema.py (1)
12-237: Schema model design is strong and consistent with Pydantic v2 patterns.Immutability, field constraints, and post-validators for range/uniqueness checks are well implemented.
|
|
||
| company: | ||
| type: "dev_shop" | ||
| budget_monthly: {{ budget | default(75.0) }} |
There was a problem hiding this comment.
Fix Jinja braces spacing on Line 21 to satisfy YAML linting.
budget_monthly: {{ budget | default(75.0) }} is flagged by YAMLlint (braces).
Suggested fix
- budget_monthly: {{ budget | default(75.0) }}
+ budget_monthly: {{budget | default(75.0)}}As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| budget_monthly: {{ budget | default(75.0) }} | |
| budget_monthly: {{budget | default(75.0)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/builtins/dev_shop.yaml` at line 21, The YAML lint
error is caused by spacing inside the Jinja braces for the budget_monthly value;
update the Jinja expression associated with the budget_monthly key (the template
"{{ budget | default(75.0) }}") to remove the extra space immediately after "{{"
and before "}}" so it conforms to the braces rule (e.g., use the same expression
without those inner-leading/trailing spaces), then re-run the YAML linter to
confirm the file passes.
|
|
||
| company: | ||
| type: "full_company" | ||
| budget_monthly: {{ budget | default(200.0) }} |
There was a problem hiding this comment.
Fix Jinja braces spacing on Line 21 to satisfy YAML linting.
budget_monthly: {{ budget | default(200.0) }} is flagged by YAMLlint (braces).
Suggested fix
- budget_monthly: {{ budget | default(200.0) }}
+ budget_monthly: {{budget | default(200.0)}}As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| budget_monthly: {{ budget | default(200.0) }} | |
| budget_monthly: {{budget | default(200.0)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/builtins/full_company.yaml` at line 21, The Jinja
expression used for budget_monthly violates the YAML lint "braces" rule; update
the template expression for budget_monthly (the Jinja token containing budget |
default(200.0)) to remove the extra spaces directly inside the curly braces so
the braces are tight (adjust the Jinja tag around budget | default(200.0)
accordingly) to satisfy yaml linting.
|
|
||
| company: | ||
| type: "product_team" | ||
| budget_monthly: {{ budget | default(80.0) }} |
There was a problem hiding this comment.
Fix Jinja braces spacing on Line 21 to satisfy YAML linting.
budget_monthly: {{ budget | default(80.0) }} is flagged by YAMLlint (braces).
Suggested fix
- budget_monthly: {{ budget | default(80.0) }}
+ budget_monthly: {{budget | default(80.0)}}As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| budget_monthly: {{ budget | default(80.0) }} | |
| budget_monthly: {{budget | default(80.0)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/builtins/product_team.yaml` at line 21, The Jinja
expression on the budget_monthly line has extra spaces inside the braces which
triggers YAML linting; update the template expression in product_team.yaml (the
budget_monthly: line containing the {{ ... }} expression) to remove the spaces
immediately after '{{' and before '}}' so the expression reads with no inner
brace padding (e.g., change the existing {{ budget | default(80.0) }} form to
use no spaces adjacent to the braces).
|
|
||
| company: | ||
| type: "solo_founder" | ||
| budget_monthly: {{ budget | default(25.0) }} |
There was a problem hiding this comment.
Fix Jinja braces spacing on Line 21 to satisfy YAML linting.
budget_monthly: {{ budget | default(25.0) }} is flagged by YAMLlint (braces).
Suggested fix
- budget_monthly: {{ budget | default(25.0) }}
+ budget_monthly: {{budget | default(25.0)}}As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| budget_monthly: {{ budget | default(25.0) }} | |
| budget_monthly: {{budget | default(25.0)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/builtins/solo_founder.yaml` at line 21, The Jinja
expression for the budget in the template key budget_monthly uses spacing that
triggers YAML linting; update the Jinja braces to remove inner spaces so the
line reads with tightly wrapped braces (e.g., change the Jinja expression used
in budget_monthly to {{budget | default(25.0)}}) and commit the change so
check-yaml passes.
|
|
||
| company: | ||
| type: "startup" | ||
| budget_monthly: {{ budget | default(50.0) }} |
There was a problem hiding this comment.
Fix Jinja braces spacing on Line 21 to satisfy YAML linting.
budget_monthly: {{ budget | default(50.0) }} is flagged by YAMLlint (braces).
Suggested fix
- budget_monthly: {{ budget | default(50.0) }}
+ budget_monthly: {{budget | default(50.0)}}As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| budget_monthly: {{ budget | default(50.0) }} | |
| budget_monthly: {{budget | default(50.0)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 21-21: too many spaces inside braces
(braces)
[error] 21-21: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/builtins/startup.yaml` at line 21, The YAML lint
error is caused by spacing inside the Jinja braces for the budget_monthly key;
update the Jinja expression from "{{ budget | default(50.0) }}" to remove the
inner brace spacing so it reads "{{budget | default(50.0)}}" (i.e., edit the
budget_monthly line replacing the existing Jinja tag with {{budget |
default(50.0)}} to satisfy the 'braces' rule).
src/ai_company/templates/loader.py
Outdated
| def _to_float(value: Any) -> float: | ||
| """Coerce a value to float, handling string numerics. | ||
|
|
||
| Args: | ||
| value: Raw value from YAML (may be str, int, float). | ||
|
|
||
| Returns: | ||
| Float value. | ||
| """ | ||
| if isinstance(value, str): | ||
| try: | ||
| return float(value) | ||
| except ValueError: | ||
| return 0.0 | ||
| return float(value) |
There was a problem hiding this comment.
Avoid silent 0.0 fallback in _to_float.
Line 348 converts invalid numeric input to 0.0, which hides malformed template data instead of failing validation.
Proposed fix
def _to_float(value: Any) -> float:
@@
- if isinstance(value, str):
- try:
- return float(value)
- except ValueError:
- return 0.0
- return float(value)
+ try:
+ return float(value)
+ except (TypeError, ValueError) as exc:
+ raise ValueError(f"Invalid numeric value: {value!r}") from excAs per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/loader.py` around lines 335 - 349, The helper
_to_float currently swallows malformed numeric strings by returning 0.0; change
it to surface errors instead: in the except ValueError branch raise a ValueError
(including the original value and the original exception message) so invalid
YAML numeric input fails validation, keep the final return float(value) for
non-strings, and ensure any callers of _to_float (where it’s used to coerce
template fields) either validate input beforehand or handle the propagated
ValueError.
There was a problem hiding this comment.
Pull request overview
Implements a new “company templates” subsystem that can bootstrap a RootConfig from built-in (and user-provided) YAML templates via a two-pass YAML parse + Jinja2 render pipeline.
Changes:
- Adds template loading/rendering pipeline (
ai_company.templates.*) plus 7 built-in YAML presets. - Extracts
deep_merge()into a sharedai_company.config.utilsmodule and updates config loader/tests accordingly. - Adds unit test coverage for template schema/loader/renderer and personality preset + auto-naming helpers.
Reviewed changes
Copilot reviewed 24 out of 27 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds Jinja2 dependency to the lockfile. |
| pyproject.toml | Adds jinja2==3.1.6 as a runtime dependency. |
| .pre-commit-config.yaml | Excludes built-in template YAML directory from check-yaml hook. |
| src/ai_company/config/loader.py | Switches from private _deep_merge to shared deep_merge. |
| src/ai_company/config/utils.py | Introduces shared deep_merge() utility. |
| src/ai_company/templates/init.py | Exposes templates public API surface. |
| src/ai_company/templates/errors.py | Adds template-specific error hierarchy. |
| src/ai_company/templates/loader.py | Implements template discovery (builtin/user/file) + pass-1 parsing. |
| src/ai_company/templates/presets.py | Adds minimal personality presets + deterministic auto-name generation. |
| src/ai_company/templates/renderer.py | Implements pass-2 Jinja2 render → YAML parse → RootConfig build/validate. |
| src/ai_company/templates/schema.py | Adds Pydantic v2 models for template schema validation. |
| src/ai_company/templates/builtins/init.py | Marks built-in templates directory as a Python package for resources loading. |
| src/ai_company/templates/builtins/solo_founder.yaml | Adds built-in solo founder template. |
| src/ai_company/templates/builtins/startup.yaml | Adds built-in startup template. |
| src/ai_company/templates/builtins/dev_shop.yaml | Adds built-in dev shop template. |
| src/ai_company/templates/builtins/product_team.yaml | Adds built-in product team template. |
| src/ai_company/templates/builtins/agency.yaml | Adds built-in agency template. |
| src/ai_company/templates/builtins/full_company.yaml | Adds built-in full company template. |
| src/ai_company/templates/builtins/research_lab.yaml | Adds built-in research lab template. |
| tests/unit/config/test_loader.py | Updates tests to use shared deep_merge. |
| tests/unit/config/test_utils.py | Adds direct unit tests for deep_merge. |
| tests/unit/templates/init.py | Adds templates test package marker. |
| tests/unit/templates/conftest.py | Adds fixtures and template YAML test strings. |
| tests/unit/templates/test_loader.py | Adds unit tests for template discovery/loading and errors. |
| tests/unit/templates/test_renderer.py | Adds unit tests for rendering pipeline, variables, and error handling. |
| tests/unit/templates/test_schema.py | Adds unit tests for Pydantic template schema models. |
| tests/unit/templates/test_presets.py | Adds unit tests for personality presets + auto-name generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/ai_company/templates/renderer.py
Outdated
| Float value, or 0.0 on conversion failure. | ||
| """ | ||
| try: | ||
| return float(value) | ||
| except TypeError, ValueError: | ||
| return 0.0 |
There was a problem hiding this comment.
_safe_float() returns 0.0 on conversion failure. That makes invalid template values silently succeed (e.g., autonomy/budget typos become 0.0), which differs from normal YAML config loading where Pydantic would error on non-numeric input. Consider raising TemplateRenderError (or letting RootConfig validation fail) instead of coercing failures to 0.0.
| Float value, or 0.0 on conversion failure. | |
| """ | |
| try: | |
| return float(value) | |
| except TypeError, ValueError: | |
| return 0.0 | |
| Float value. | |
| Raises: | |
| TemplateRenderError: If the value cannot be converted to float. | |
| """ | |
| try: | |
| return float(value) | |
| except (TypeError, ValueError) as exc: | |
| raise TemplateRenderError( | |
| f"Invalid numeric value in rendered template: {value!r}", | |
| ) from exc |
src/ai_company/templates/schema.py
Outdated
| level: Seniority level override. | ||
| model: Model tier alias (e.g. ``"opus"``, ``"sonnet"``, ``"haiku"``). | ||
| personality_preset: Named personality preset from the presets registry. | ||
| department: Department override (``None`` uses the role's default). |
There was a problem hiding this comment.
The department field docstring says None will use the role’s default department, but the current renderer implementation defaults missing departments to the literal string "engineering". Either update this docstring to match the implemented behavior, or implement role-default department resolution in the renderer (using the built-in role catalog).
| department: Department override (``None`` uses the role's default). | |
| department: Department override (``None`` uses the renderer's default, | |
| currently ``"engineering"``). |
src/ai_company/templates/loader.py
Outdated
| if not isinstance(data, dict) or "template" not in data: | ||
| msg = f"Template YAML must have a top-level 'template' key in {source_name}" | ||
| raise TemplateValidationError( | ||
| msg, | ||
| locations=(ConfigLocation(file_path=source_name),), | ||
| ) | ||
|
|
||
| template_data = data["template"] | ||
| normalized = _normalize_template_data(template_data) | ||
| try: | ||
| return CompanyTemplate(**normalized) | ||
| except Exception as exc: |
There was a problem hiding this comment.
In _parse_template_yaml, template_data = data["template"] is assumed to be a dict. If the YAML contains template: null or template: [], _normalize_template_data(template_data) will raise an AttributeError that bypasses the Template* error wrappers. Consider validating that data["template"] is a mapping and raising TemplateValidationError with a clear message when it isn’t.
| except Exception: | ||
| logger.warning("Skipping invalid builtin template: %s", name) | ||
|
|
||
| return tuple(info for _, info in sorted(seen.items())) |
There was a problem hiding this comment.
list_templates() docstring says “User templates are listed first”, but the returned tuple is sorted purely by template name (sorted(seen.items())). Either adjust the ordering to actually prioritize source="user" (e.g., stable sort by (source_priority, name)) or update the docstring so callers don’t rely on an ordering that isn’t guaranteed.
| return tuple(info for _, info in sorted(seen.items())) | |
| return tuple( | |
| sorted( | |
| seen.values(), | |
| key=lambda info: (0 if info.source == "user" else 1, info.name), | |
| ) | |
| ) |
src/ai_company/templates/loader.py
Outdated
| except ValueError: | ||
| return 0.0 |
There was a problem hiding this comment.
_to_float() returns 0.0 for non-numeric strings. That can silently turn invalid template values (e.g., budget_monthly: "abc") into a valid-looking config, masking user mistakes. Consider raising TemplateValidationError/TemplateRenderError (with a location) when coercion fails instead of defaulting to 0.0.
| except ValueError: | |
| return 0.0 | |
| except ValueError as exc: | |
| raise ValueError(f"Invalid numeric value for template field: {value!r}") from exc |
| agent_dict: dict[str, Any] = { | ||
| "name": name, | ||
| "role": role, | ||
| "department": agent.get("department", "engineering"), | ||
| "level": agent.get("level", "mid"), | ||
| } |
There was a problem hiding this comment.
department defaults to "engineering" whenever it’s omitted. The schema docstring says None should use the role’s default department, and the repo has a built-in role catalog (ai_company.core.role_catalog.get_builtin_role) that already encodes default departments. Consider deriving the department from the built-in role when not provided, and only falling back to a hard-coded default if the role is unknown.
src/ai_company/templates/loader.py
Outdated
| metadata = { | ||
| "name": data.get("name", ""), | ||
| "description": data.get("description", ""), | ||
| "version": data.get("version", "1.0.0"), | ||
| "company_type": company.get("type", "custom"), | ||
| "tags": tuple(data.get("tags", ())), | ||
| } |
There was a problem hiding this comment.
TemplateMetadata supports min_agents / max_agents, but _normalize_template_data() never reads these keys from the YAML template, so users cannot set them in templates and the agent-range validators won’t reflect template intent. Consider mapping min_agents and max_agents (and validating they’re ints) from the template: YAML into metadata.
src/ai_company/templates/renderer.py
Outdated
| locations=(ConfigLocation(file_path=source_name),), | ||
| ) | ||
|
|
||
| result: dict[str, Any] = data["template"] |
There was a problem hiding this comment.
_parse_rendered_yaml() returns data["template"] without verifying it’s a mapping. If the rendered YAML produces template: null or a non-dict value, later code will crash with an AttributeError instead of raising a TemplateRenderError. Consider validating the type of data["template"] here and raising a TemplateRenderError with a clear message.
| result: dict[str, Any] = data["template"] | |
| result = data["template"] | |
| if not isinstance(result, dict): | |
| msg = ( | |
| "Rendered template 'template' section must be a mapping " | |
| f"(dict) for {source_name}" | |
| ) | |
| raise TemplateRenderError( | |
| msg, | |
| locations=(ConfigLocation(file_path=source_name),), | |
| ) |
| return { | ||
| "company_name": company_name, | ||
| "company_type": company.get("type", template.metadata.company_type.value), | ||
| "agents": agents, | ||
| "departments": departments, | ||
| "config": { | ||
| "autonomy": _safe_float(company.get("autonomy", template.autonomy)), | ||
| "budget_monthly": _safe_float( | ||
| company.get("budget_monthly", template.budget_monthly), | ||
| ), | ||
| "communication_pattern": rendered_data.get( | ||
| "communication", | ||
| template.communication, | ||
| ), | ||
| }, | ||
| } |
There was a problem hiding this comment.
CompanyTemplate.workflow is parsed/validated, and templates set workflow: ..., but _build_config_dict() never carries this into the generated RootConfig (there’s no corresponding field in default_config_dict() / RootConfig). As-is, changing workflow in a template has no effect. Either plumb workflow into an existing config field (if one exists) or drop it from the template schema/templates to avoid a misleading no-op setting.
…, and Copilot - Narrow exception handling: replace broad `except Exception` with specific types - Add path traversal protection in template loader - Add filesystem error handling for template file reads (OSError, UnicodeDecodeError) - Extract `to_float()` utility to config/utils.py for strict numeric validation - Add type-match validation for TemplateVariable defaults - Strengthen type hints: use NotBlankStr | None for optional identifiers - Add __str__ method to TemplateValidationError with per-field details - Remove duplicate TestDeepMerge class from test_loader.py - Fix docstrings: module-level, class-level, and field descriptions - Include exception details in warning logs - Add template data type guard before Pydantic validation
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/ai_company/config/loader.py (1)
371-443: 🛠️ Refactor suggestion | 🟠 MajorRefactor
load_configinto smaller helpers to satisfy function-size rule.
load_configis currently too long; split the phases (resolve path, parse/merge layers, env substitution, validate) into helper functions to keep this public function under the limit.♻️ Suggested refactor shape
def load_config( config_path: Path | str | None = None, *, override_paths: tuple[Path | str, ...] = (), ) -> RootConfig: - if config_path is None: - config_path = discover_config() - config_path = Path(config_path) - - merged = default_config_dict() - yaml_text = _read_config_text(config_path) - primary = _parse_yaml_string(yaml_text, str(config_path)) - merged = deep_merge(merged, primary) - - for override_path in override_paths: - override = _parse_yaml_file(Path(override_path)) - merged = deep_merge(merged, override) - - merged = _substitute_env_vars(merged, source_file="<merged config>") - line_map = _build_line_map(yaml_text) - return _validate_config_dict( - merged, - source_file=str(config_path), - line_map=line_map, - ) + resolved_path = _resolve_config_path(config_path) + merged, yaml_text = _load_merged_layers(resolved_path, override_paths) + merged = _substitute_env_vars(merged, source_file="<merged config>") + return _validate_with_primary_line_map(merged, yaml_text, resolved_path)As per coding guidelines "Keep functions under 50 lines and files under 800 lines".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/config/loader.py` around lines 371 - 443, The load_config function should be split into small helpers: extract path resolution into a helper (resolve_config_path using discover_config), extract primary file reading/parsing and line-map construction into read_and_parse_primary (calls _read_config_text, _parse_yaml_string and _build_line_map), extract override merging into apply_overrides (iterates override_paths and calls _parse_yaml_file + deep_merge), extract env substitution into substitute_env (calls _substitute_env_vars with source_file="<merged config>"), and keep validation in validate_config (wraps _validate_config_dict). Refactor load_config to call these helpers in order (default_config_dict -> read_and_parse_primary -> apply_overrides -> substitute_env -> validate_config) so load_config stays short while reusing the existing functions: load_config, discover_config, default_config_dict, _read_config_text, _parse_yaml_string, deep_merge, _parse_yaml_file, _substitute_env_vars, _build_line_map, and _validate_config_dict.
♻️ Duplicate comments (1)
src/ai_company/templates/loader.py (1)
382-407:⚠️ Potential issue | 🟠 MajorAvoid broad
0.0fallback in_to_float; only allow placeholder fallback.Current behavior masks invalid numeric values that should fail validation.
💡 Proposed fix
def _to_float(value: Any) -> float: @@ - if value is None: + if value is None or value == "__JINJA2__": return 0.0 try: return float(value) - except (TypeError, ValueError): - logger.debug( - "Cannot convert %r to float in Pass 1 " - "(may be a Jinja2 placeholder), using 0.0", - value, - ) - return 0.0 + except (TypeError, ValueError) as exc: + msg = f"Invalid numeric value in template pass 1: {value!r}" + raise ValueError(msg) from excAs per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/templates/loader.py` around lines 382 - 407, The helper _to_float currently swallows all TypeError/ValueError and returns 0.0, masking real bad input; change it so that it only returns 0.0 for known placeholder patterns (e.g. Jinja2 placeholders like "__JINJA2__" or a regex match) and otherwise re-raise the original exception so upstream validation fails; update the except block in _to_float to detect placeholder strings (log a debug message and return 0.0) but for any other conversion error re-raise the ValueError/TypeError so invalid numeric values are not silently accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/ai_company/templates/loader.py`:
- Around line 79-132: The list_templates function (and similarly
_parse_template_yaml) is too large and mixes responsibilities; split
list_templates into smaller helpers such as _collect_user_templates(path: Path)
-> dict[str, TemplateInfo], _collect_builtin_templates(seen: dict[str,
TemplateInfo]) -> None, and _make_template_info(name, loaded, source) ->
TemplateInfo to isolate loading, metadata extraction, error handling and
sorting; call existing helpers _load_from_file and _load_builtin inside these
new functions and move the try/except/logging into each helper (use
logger.warning for user template errors and logger.exception for builtin
errors), then have list_templates simply call the two collectors, sort seen and
return the tuple. Apply the same decomposition to _parse_template_yaml by
extracting YAML parsing, validation, and metadata extraction into focused helper
functions and reusing them from the caller.
- Around line 359-365: When building metadata, avoid calling company.get on
non-mappings: read company_raw = data.get("company", None), validate it with
isinstance(company_raw, Mapping) (or dict) and if it's None set company = {} but
if it's not a mapping raise a clear validation error (e.g., ValueError or custom
ValidationError) before proceeding to construct metadata; update subsequent uses
(the metadata dict creation that references company.get and the surrounding
normalization code) to use the validated company variable.
- Line 125: Replace the Python 2 style exception clause that uses commas with a
parenthesized tuple: change "except TemplateRenderError,
TemplateValidationError, OSError:" to "except (TemplateRenderError,
TemplateValidationError, OSError):" (and if you need the exception instance, use
"except (TemplateRenderError, TemplateValidationError, OSError) as e:"). Update
both occurrences that reference TemplateRenderError, TemplateValidationError,
and OSError (the except block around the template rendering/validation logic and
the other similar except at the later occurrence) so the syntax is valid in
Python 3.
In `@src/ai_company/templates/renderer.py`:
- Around line 219-275: _build_config_dict and _expand_agents are doing multiple
tasks (parsing company fields, numeric validation, agent expansion and
department construction) and exceed the size limit; split them into smaller
focused helpers so each function is under ~50 lines. Specifically, extract: a
parse_company_metadata or _extract_company_fields(rendered_data, template,
variables) to compute company_name/type and source_name, a
_parse_config_values(rendered_data, template) that handles
autonomy/budget_monthly parsing and raises TemplateRenderError on ValueError,
and leave _build_config_dict to orchestrate calling _expand_agents(raw_agents)
and _build_departments(raw_depts) and assembling the final dict; also move any
numeric conversion logic from _expand_agents into the new _parse_config_values
to keep responsibilities separated. Ensure new helper names
(_extract_company_fields, _parse_config_values) are referenced by
_build_config_dict and update imports/uses accordingly so tests still pass.
- Around line 320-331: The code currently catches KeyError from
get_personality_preset(preset_name) and only logs a warning, silently using a
default; instead, make invalid personality presets fail rendering by removing
the silent fallback and raising a clear error: when
get_personality_preset(preset_name) raises KeyError (or when preset_name not in
PERSONALITY_PRESETS), raise an explicit exception (e.g., ValueError or re-raise
KeyError) that includes preset_name, agent name, and the sorted
PERSONALITY_PRESETS so the renderer fails loudly; update the logic around
agent_dict["personality"], get_personality_preset, and any logger.warning usage
to ensure the error propagates rather than being swallowed.
- Around line 234-247: Rendered values from rendered_data (company, agents,
departments) must be validated and converted to proper shapes before use; update
the block that reads rendered_data in renderer.py to check that company is a
dict (and that company_name is a str or derive from template.metadata.name),
that raw_agents is a list before calling _expand_agents, and that raw_depts is a
list before calling _build_departments, and raise TemplateRenderError with a
clear message when any of those validations fail; add these checks around the
existing uses of rendered_data, company, _expand_agents(raw_agents) and
_build_departments(raw_depts) so invalid YAML shapes produce TemplateRenderError
with context instead of raw attribute/type errors.
In `@src/ai_company/templates/schema.py`:
- Line 29: The models that parse external YAML should reject unknown fields:
update each model's model_config (for TemplateVariable, TemplateAgentConfig,
TemplateDepartmentConfig, TemplateMetadata, and CompanyTemplate) to include
extra="forbid" (e.g., extend the existing ConfigDict(frozen=True) to also set
extra="forbid") so Pydantic will raise on unknown fields instead of silently
ignoring them. Ensure you modify the model_config in each named class
(TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig,
TemplateMetadata, CompanyTemplate).
- Around line 53-60: The validation currently uses type_map and
isinstance(self.default, expected) which accepts bool for var_type "int" and
"float" because bool subclasses int; modify the check in the schema validation
(symbols: type_map, expected, self.var_type, self.default, isinstance) to
explicitly reject booleans for numeric types—either by adding a guard like "if
isinstance(self.default, bool) and self.var_type in ('int','float'): raise
TypeError" before the isinstance check, or by comparing types directly
(type(self.default) is int or float) so bools are not accepted; implement one of
these approaches and ensure the error message mentions the variable
name/var_type for clarity.
---
Outside diff comments:
In `@src/ai_company/config/loader.py`:
- Around line 371-443: The load_config function should be split into small
helpers: extract path resolution into a helper (resolve_config_path using
discover_config), extract primary file reading/parsing and line-map construction
into read_and_parse_primary (calls _read_config_text, _parse_yaml_string and
_build_line_map), extract override merging into apply_overrides (iterates
override_paths and calls _parse_yaml_file + deep_merge), extract env
substitution into substitute_env (calls _substitute_env_vars with
source_file="<merged config>"), and keep validation in validate_config (wraps
_validate_config_dict). Refactor load_config to call these helpers in order
(default_config_dict -> read_and_parse_primary -> apply_overrides ->
substitute_env -> validate_config) so load_config stays short while reusing the
existing functions: load_config, discover_config, default_config_dict,
_read_config_text, _parse_yaml_string, deep_merge, _parse_yaml_file,
_substitute_env_vars, _build_line_map, and _validate_config_dict.
---
Duplicate comments:
In `@src/ai_company/templates/loader.py`:
- Around line 382-407: The helper _to_float currently swallows all
TypeError/ValueError and returns 0.0, masking real bad input; change it so that
it only returns 0.0 for known placeholder patterns (e.g. Jinja2 placeholders
like "__JINJA2__" or a regex match) and otherwise re-raise the original
exception so upstream validation fails; update the except block in _to_float to
detect placeholder strings (log a debug message and return 0.0) but for any
other conversion error re-raise the ValueError/TypeError so invalid numeric
values are not silently accepted.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
src/ai_company/config/loader.pysrc/ai_company/config/utils.pysrc/ai_company/templates/errors.pysrc/ai_company/templates/loader.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pytests/unit/config/test_loader.py
💤 Files with no reviewable changes (1)
- tests/unit/config/test_loader.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (actions)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Use PEP 649 native lazy annotations instead offrom __future__ import annotations(Python 3.14+)
All public functions and classes must include type hints; enforce via mypy strict mode
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Keep functions under 50 lines and files under 800 lines
Create new objects instead of mutating existing ones; enforce immutability patterns
Use Pydantic v2 models (BaseModel, model_validator, ConfigDict) for data validation
Validate at system boundaries (user input, external APIs, config files); never silently swallow errors
Enforce 88-character line length (via ruff configuration)
Files:
src/ai_company/config/utils.pysrc/ai_company/templates/loader.pysrc/ai_company/templates/errors.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pysrc/ai_company/config/loader.py
{src/**/*.py,tests/**/*.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Run ruff check and ruff format as pre-commit hooks on all Python files; enforce no commits to main branch
Files:
src/ai_company/config/utils.pysrc/ai_company/templates/loader.pysrc/ai_company/templates/errors.pysrc/ai_company/templates/renderer.pysrc/ai_company/templates/schema.pysrc/ai_company/config/loader.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to src/prompts/**/*.py : YAML templates must be loaded once at startup under lock protection for thread safety.
📚 Learning: 2026-02-26T17:43:50.902Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to src/prompts/**/*.py : YAML templates must be loaded once at startup under lock protection for thread safety.
Applied to files:
src/ai_company/templates/loader.py
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files); never silently swallow errors
Applied to files:
src/ai_company/templates/loader.pysrc/ai_company/templates/renderer.py
🧬 Code graph analysis (5)
src/ai_company/templates/loader.py (3)
src/ai_company/config/errors.py (1)
ConfigLocation(7-21)src/ai_company/templates/errors.py (3)
TemplateNotFoundError(10-11)TemplateRenderError(14-20)TemplateValidationError(23-59)src/ai_company/templates/schema.py (1)
CompanyTemplate(167-260)
src/ai_company/templates/errors.py (1)
src/ai_company/config/errors.py (2)
ConfigError(24-59)ConfigLocation(7-21)
src/ai_company/templates/renderer.py (8)
src/ai_company/templates/errors.py (3)
TemplateError(6-7)TemplateRenderError(14-20)TemplateValidationError(23-59)src/ai_company/config/defaults.py (1)
default_config_dict(6-28)src/ai_company/config/errors.py (1)
ConfigLocation(7-21)src/ai_company/config/schema.py (1)
RootConfig(199-323)src/ai_company/config/utils.py (2)
deep_merge(30-54)to_float(7-27)src/ai_company/templates/presets.py (2)
generate_auto_name(87-103)get_personality_preset(67-84)src/ai_company/templates/loader.py (1)
LoadedTemplate(65-76)src/ai_company/templates/schema.py (1)
CompanyTemplate(167-260)
src/ai_company/templates/schema.py (1)
src/ai_company/core/enums.py (2)
CompanyType(72-82)SeniorityLevel(6-21)
src/ai_company/config/loader.py (2)
src/ai_company/config/utils.py (1)
deep_merge(30-54)src/ai_company/config/defaults.py (1)
default_config_dict(6-28)
🔇 Additional comments (7)
src/ai_company/config/loader.py (2)
423-429: Good move centralizing merge behavior viadeep_merge.Using one shared merge utility on Lines [423], [429], and [468] improves consistency and preserves non-mutating semantics across load paths.
Also applies to: 468-468
190-195: Logging improvement is solid.Including the caught exception in the warning on Lines [190-195] materially improves diagnosability when AST line mapping fails.
src/ai_company/config/utils.py (2)
7-27:to_floatvalidation and error reporting look correct.The explicit
Noneguard and wrapped conversion errors provide clear boundary validation behavior for callers.
30-54:deep_mergeimplementation is clean and non-mutating.Recursive dict merge with deep copies is consistent with immutable layering semantics and suitable as a shared config utility.
src/ai_company/templates/errors.py (1)
6-59: Exception hierarchy and validation-error formatting look solid.The layering and
TemplateValidationError.__str__behavior are clear and robust.src/ai_company/templates/renderer.py (1)
202-216: Good defensive validation of rendered YAML structure.The explicit
'template'mapping checks are correct and prevent opaque downstream crashes.src/ai_company/templates/loader.py (1)
157-165: Path traversal guard is a good defensive control.The normalized-name check correctly blocks path separator input before user-directory lookup.
| def list_templates() -> tuple[TemplateInfo, ...]: | ||
| """Return all available templates (user directory + built-in). | ||
|
|
||
| User templates override built-in templates of the same name. | ||
| The result is sorted alphabetically by template name. Templates | ||
| that fail to load are silently skipped with a warning log. | ||
|
|
||
| Returns: | ||
| Sorted tuple of :class:`TemplateInfo` objects. | ||
| """ | ||
| seen: dict[str, TemplateInfo] = {} | ||
|
|
||
| # User templates (higher priority). | ||
| if _USER_TEMPLATES_DIR.is_dir(): | ||
| for path in sorted( | ||
| p for p in _USER_TEMPLATES_DIR.glob("*.yaml") if p.is_file() | ||
| ): | ||
| name = path.stem | ||
| try: | ||
| loaded = _load_from_file(path) | ||
| meta = loaded.template.metadata | ||
| seen[name] = TemplateInfo( | ||
| name=name, | ||
| display_name=meta.name, | ||
| description=meta.description, | ||
| source="user", | ||
| ) | ||
| except (TemplateRenderError, TemplateValidationError, OSError) as exc: | ||
| logger.warning( | ||
| "Skipping invalid user template %s: %s", | ||
| path, | ||
| exc, | ||
| ) | ||
|
|
||
| # Built-in templates (lower priority). | ||
| for name in sorted(BUILTIN_TEMPLATES): | ||
| if name not in seen: | ||
| try: | ||
| loaded = _load_builtin(name) | ||
| meta = loaded.template.metadata | ||
| seen[name] = TemplateInfo( | ||
| name=name, | ||
| display_name=meta.name, | ||
| description=meta.description, | ||
| source="builtin", | ||
| ) | ||
| except TemplateRenderError, TemplateValidationError, OSError: | ||
| logger.exception( | ||
| "Built-in template %r is invalid (packaging defect)", | ||
| name, | ||
| ) | ||
|
|
||
| return tuple(info for _, info in sorted(seen.items())) | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Refactor oversized functions into smaller helpers.
list_templates and _parse_template_yaml exceed the function-size guideline and combine multiple responsibilities.
As per coding guidelines, "Keep functions under 50 lines and files under 800 lines".
Also applies to: 286-345
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/loader.py` around lines 79 - 132, The list_templates
function (and similarly _parse_template_yaml) is too large and mixes
responsibilities; split list_templates into smaller helpers such as
_collect_user_templates(path: Path) -> dict[str, TemplateInfo],
_collect_builtin_templates(seen: dict[str, TemplateInfo]) -> None, and
_make_template_info(name, loaded, source) -> TemplateInfo to isolate loading,
metadata extraction, error handling and sorting; call existing helpers
_load_from_file and _load_builtin inside these new functions and move the
try/except/logging into each helper (use logger.warning for user template errors
and logger.exception for builtin errors), then have list_templates simply call
the two collectors, sort seen and return the tuple. Apply the same decomposition
to _parse_template_yaml by extracting YAML parsing, validation, and metadata
extraction into focused helper functions and reusing them from the caller.
| description=meta.description, | ||
| source="builtin", | ||
| ) | ||
| except TemplateRenderError, TemplateValidationError, OSError: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if file exists and get line count
wc -l src/ai_company/templates/loader.py
# View the specific lines mentioned (125 and 401)
sed -n '120,130p' src/ai_company/templates/loader.py
sed -n '396,406p' src/ai_company/templates/loader.pyRepository: Aureliolo/ai-company
Length of output: 789
🏁 Script executed:
# Verify the exact Python version constraint and check if there are syntax checks in place
head -20 src/ai_company/templates/loader.py
# Check if file can be parsed as Python
python3 -m py_compile src/ai_company/templates/loader.py 2>&1 || echo "Compilation failed"Repository: Aureliolo/ai-company
Length of output: 917
Fix invalid Python exception syntax.
Lines 125 and 401 use Python 2 style comma-separated exception handling which causes SyntaxError: multiple exception types must be parenthesized and prevents module import.
Proposed fix
- except TemplateRenderError, TemplateValidationError, OSError:
+ except (TemplateRenderError, TemplateValidationError, OSError):
@@
- except TypeError, ValueError:
+ except (TypeError, ValueError):🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/loader.py` at line 125, Replace the Python 2 style
exception clause that uses commas with a parenthesized tuple: change "except
TemplateRenderError, TemplateValidationError, OSError:" to "except
(TemplateRenderError, TemplateValidationError, OSError):" (and if you need the
exception instance, use "except (TemplateRenderError, TemplateValidationError,
OSError) as e:"). Update both occurrences that reference TemplateRenderError,
TemplateValidationError, and OSError (the except block around the template
rendering/validation logic and the other similar except at the later occurrence)
so the syntax is valid in Python 3.
| def _build_config_dict( | ||
| rendered_data: dict[str, Any], | ||
| template: CompanyTemplate, | ||
| variables: dict[str, Any], | ||
| ) -> dict[str, Any]: | ||
| """Build a RootConfig-compatible dict from rendered template data. | ||
|
|
||
| Args: | ||
| rendered_data: Parsed dict from the rendered YAML. | ||
| template: Original template metadata (for fallback values). | ||
| variables: Collected variables. | ||
|
|
||
| Returns: | ||
| Dict suitable for ``RootConfig(**deep_merge(defaults, result))``. | ||
| """ | ||
| company = rendered_data.get("company", {}) | ||
| company_name = variables.get( | ||
| "company_name", | ||
| template.metadata.name, | ||
| ) | ||
|
|
||
| # Expand agents. | ||
| raw_agents = rendered_data.get("agents", []) | ||
| agents = _expand_agents(raw_agents) | ||
|
|
||
| # Build departments for RootConfig. | ||
| raw_depts = rendered_data.get("departments", []) | ||
| departments = _build_departments(raw_depts) | ||
|
|
||
| source_name = template.metadata.name | ||
| try: | ||
| autonomy = to_float( | ||
| company.get("autonomy", template.autonomy), | ||
| field_name="autonomy", | ||
| ) | ||
| budget_monthly = to_float( | ||
| company.get("budget_monthly", template.budget_monthly), | ||
| field_name="budget_monthly", | ||
| ) | ||
| except ValueError as exc: | ||
| msg = f"Invalid numeric value in rendered template {source_name!r}: {exc}" | ||
| raise TemplateRenderError(msg) from exc | ||
|
|
||
| return { | ||
| "company_name": company_name, | ||
| "company_type": company.get("type", template.metadata.company_type.value), | ||
| "agents": agents, | ||
| "departments": departments, | ||
| "config": { | ||
| "autonomy": autonomy, | ||
| "budget_monthly": budget_monthly, | ||
| "communication_pattern": rendered_data.get( | ||
| "communication", | ||
| template.communication, | ||
| ), | ||
| }, | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Split oversized helper functions into smaller units.
_build_config_dict and _expand_agents currently mix multiple responsibilities and exceed the size limit.
As per coding guidelines, "Keep functions under 50 lines and files under 800 lines".
Also applies to: 278-339
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/renderer.py` around lines 219 - 275,
_build_config_dict and _expand_agents are doing multiple tasks (parsing company
fields, numeric validation, agent expansion and department construction) and
exceed the size limit; split them into smaller focused helpers so each function
is under ~50 lines. Specifically, extract: a parse_company_metadata or
_extract_company_fields(rendered_data, template, variables) to compute
company_name/type and source_name, a _parse_config_values(rendered_data,
template) that handles autonomy/budget_monthly parsing and raises
TemplateRenderError on ValueError, and leave _build_config_dict to orchestrate
calling _expand_agents(raw_agents) and _build_departments(raw_depts) and
assembling the final dict; also move any numeric conversion logic from
_expand_agents into the new _parse_config_values to keep responsibilities
separated. Ensure new helper names (_extract_company_fields,
_parse_config_values) are referenced by _build_config_dict and update
imports/uses accordingly so tests still pass.
| try: | ||
| agent_dict["personality"] = get_personality_preset(preset_name) | ||
| except KeyError: | ||
| available = sorted(PERSONALITY_PRESETS) | ||
| logger.warning( | ||
| "Unknown personality preset %r for agent %r; " | ||
| "using default personality. Available presets: %s", | ||
| preset_name, | ||
| name, | ||
| available, | ||
| ) | ||
|
|
There was a problem hiding this comment.
Do not silently downgrade invalid personality_preset to a warning.
An unknown preset should fail rendering so template errors are explicit and actionable.
💡 Proposed fix
if preset_name:
try:
agent_dict["personality"] = get_personality_preset(preset_name)
- except KeyError:
+ except KeyError as exc:
available = sorted(PERSONALITY_PRESETS)
- logger.warning(
- "Unknown personality preset %r for agent %r; "
- "using default personality. Available presets: %s",
- preset_name,
- name,
- available,
- )
+ msg = (
+ f"Unknown personality preset {preset_name!r} for agent {name!r}. "
+ f"Available presets: {available}"
+ )
+ raise TemplateRenderError(msg) from excAs per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try: | |
| agent_dict["personality"] = get_personality_preset(preset_name) | |
| except KeyError: | |
| available = sorted(PERSONALITY_PRESETS) | |
| logger.warning( | |
| "Unknown personality preset %r for agent %r; " | |
| "using default personality. Available presets: %s", | |
| preset_name, | |
| name, | |
| available, | |
| ) | |
| try: | |
| agent_dict["personality"] = get_personality_preset(preset_name) | |
| except KeyError as exc: | |
| available = sorted(PERSONALITY_PRESETS) | |
| msg = ( | |
| f"Unknown personality preset {preset_name!r} for agent {name!r}. " | |
| f"Available presets: {available}" | |
| ) | |
| raise TemplateRenderError(msg) from exc |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/templates/renderer.py` around lines 320 - 331, The code
currently catches KeyError from get_personality_preset(preset_name) and only
logs a warning, silently using a default; instead, make invalid personality
presets fail rendering by removing the silent fallback and raising a clear
error: when get_personality_preset(preset_name) raises KeyError (or when
preset_name not in PERSONALITY_PRESETS), raise an explicit exception (e.g.,
ValueError or re-raise KeyError) that includes preset_name, agent name, and the
sorted PERSONALITY_PRESETS so the renderer fails loudly; update the logic around
agent_dict["personality"], get_personality_preset, and any logger.warning usage
to ensure the error propagates rather than being swallowed.
- Guard template.company type before calling .get() in normalization - Add extra="forbid" to all 5 template schema models (catches YAML typos) - Reject bool defaults for int/float var_type (bool subclasses int) - Validate company/agents/departments shapes in renderer before use
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 27 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| agent_dict: dict[str, Any] = { | ||
| "name": name, | ||
| "role": role, | ||
| "department": agent.get("department", "engineering"), |
There was a problem hiding this comment.
Renderer behavior doesn't match the schema/docstring promise that department=None defaults to "engineering" during rendering. agent.get("department", "engineering") returns None when the key exists with a YAML null value, which then fails RootConfig validation. Treat missing/None/blank departments as the default (or raise a clear TemplateRenderError).
| agent_dict: dict[str, Any] = { | |
| "name": name, | |
| "role": role, | |
| "department": agent.get("department", "engineering"), | |
| # Normalize department: treat missing/None/blank as the default "engineering". | |
| raw_department = agent.get("department") | |
| if raw_department is None: | |
| department = "engineering" | |
| elif isinstance(raw_department, str) and not raw_department.strip(): | |
| department = "engineering" | |
| else: | |
| department = raw_department | |
| agent_dict: dict[str, Any] = { | |
| "name": name, | |
| "role": role, | |
| "department": department, |
| except TemplateRenderError, TemplateValidationError, OSError: | ||
| logger.exception( | ||
| "Built-in template %r is invalid (packaging defect)", | ||
| name, | ||
| ) |
There was a problem hiding this comment.
except TemplateRenderError, TemplateValidationError, OSError: is invalid Python syntax (multiple exception types must be parenthesized). This will prevent the module from importing and break template listing entirely. Use except (TemplateRenderError, TemplateValidationError, OSError) as exc: (or drop as exc if unused).
| for idx, agent in enumerate(raw_agents): | ||
| role = agent.get("role", "Agent") | ||
| name = str(agent.get("name", "")).strip() | ||
|
|
||
| # Auto-generate name if empty or still a Jinja2 expression. | ||
| if not name or name.startswith("{{"): | ||
| name = generate_auto_name(role, seed=idx) |
There was a problem hiding this comment.
role = agent.get("role", "Agent") can be None if the rendered YAML explicitly sets role: null, and generate_auto_name(role, ...) will then crash because generate_auto_name calls .strip() on the role. Coerce role to a non-empty string (and raise a TemplateRenderError if it's missing/blank) before calling generate_auto_name.
| role = agent.get("role", "Agent") | ||
| name = str(agent.get("name", "")).strip() | ||
|
|
||
| # Auto-generate name if empty or still a Jinja2 expression. | ||
| if not name or name.startswith("{{"): | ||
| name = generate_auto_name(role, seed=idx) |
There was a problem hiding this comment.
name = str(agent.get("name", "")).strip() turns a YAML null into the literal string "None", which will bypass the auto-name logic and then fail RootConfig validation expectations (or silently create an agent actually named "None"). Treat None (and other non-string scalars) as empty for naming purposes, e.g. name_value = agent.get("name"); name = "" if name_value is None else str(name_value).strip().
| expanded: list[dict[str, Any]] = [] | ||
| used_names: set[str] = set() | ||
|
|
||
| for idx, agent in enumerate(raw_agents): |
There was a problem hiding this comment.
_expand_agents assumes every item in raw_agents is a mapping; if the YAML contains a non-mapping entry (e.g. - "CEO"), this will raise an AttributeError at agent.get(...) rather than a TemplateRenderError with useful context. Validate isinstance(agent, dict) and raise TemplateRenderError when invalid entries are encountered.
| for idx, agent in enumerate(raw_agents): | |
| for idx, agent in enumerate(raw_agents): | |
| if not isinstance(agent, dict): | |
| raise TemplateRenderError( | |
| f"Invalid agent entry at index {idx}: expected a mapping/dict, " | |
| f"got {type(agent).__name__!r} with value {agent!r}." | |
| ) |
| for dept in raw_depts: | ||
| try: | ||
| budget_pct = to_float( | ||
| dept.get("budget_percent", 0.0), | ||
| field_name=f"departments[{dept.get('name', '')}].budget_percent", | ||
| ) |
There was a problem hiding this comment.
_build_departments assumes every item in raw_depts is a mapping; invalid YAML like departments: ["engineering"] will raise AttributeError on dept.get(...) instead of a structured TemplateRenderError. Add an isinstance(dept, dict) check and raise TemplateRenderError with the offending value (and ideally the index) when invalid entries are present.
| source_name = template.metadata.name | ||
| try: | ||
| autonomy = to_float( | ||
| company.get("autonomy", template.autonomy), | ||
| field_name="autonomy", | ||
| ) | ||
| budget_monthly = to_float( | ||
| company.get("budget_monthly", template.budget_monthly), | ||
| field_name="budget_monthly", | ||
| ) | ||
| except ValueError as exc: | ||
| msg = f"Invalid numeric value in rendered template {source_name!r}: {exc}" | ||
| raise TemplateRenderError(msg) from exc |
There was a problem hiding this comment.
In _build_config_dict, source_name is set to template.metadata.name and then used in error messages for numeric coercion failures. This loses the actual template source (e.g. <builtin:startup> or a file path) which is available via loaded.source_name in the caller, making errors harder to trace. Prefer using the real loaded.source_name (or pass it into _build_config_dict) for all render/parse error messages.
🤖 I have created a release *beep* *boop* --- ## [0.1.1](ai-company-v0.1.0...ai-company-v0.1.1) (2026-03-10) ### Features * add autonomy levels and approval timeout policies ([#42](#42), [#126](#126)) ([#197](#197)) ([eecc25a](eecc25a)) * add CFO cost optimization service with anomaly detection, reports, and approval decisions ([#186](#186)) ([a7fa00b](a7fa00b)) * add code quality toolchain (ruff, mypy, pre-commit, dependabot) ([#63](#63)) ([36681a8](36681a8)) * add configurable cost tiers and subscription/quota-aware tracking ([#67](#67)) ([#185](#185)) ([9baedfa](9baedfa)) * add container packaging, Docker Compose, and CI pipeline ([#269](#269)) ([435bdfe](435bdfe)), closes [#267](#267) * add coordination error taxonomy classification pipeline ([#146](#146)) ([#181](#181)) ([70c7480](70c7480)) * add cost-optimized, hierarchical, and auction assignment strategies ([#175](#175)) ([ce924fa](ce924fa)), closes [#173](#173) * add design specification, license, and project setup ([8669a09](8669a09)) * add env var substitution and config file auto-discovery ([#77](#77)) ([7f53832](7f53832)) * add FastestStrategy routing + vendor-agnostic cleanup ([#140](#140)) ([09619cb](09619cb)), closes [#139](#139) * add HR engine and performance tracking ([#45](#45), [#47](#47)) ([#193](#193)) ([2d091ea](2d091ea)) * add issue auto-search and resolution verification to PR review skill ([#119](#119)) ([deecc39](deecc39)) * add memory retrieval, ranking, and context injection pipeline ([#41](#41)) ([873b0aa](873b0aa)) * add pluggable MemoryBackend protocol with models, config, and events ([#180](#180)) ([46cfdd4](46cfdd4)) * add pluggable MemoryBackend protocol with models, config, and events ([#32](#32)) ([46cfdd4](46cfdd4)) * add pluggable PersistenceBackend protocol with SQLite implementation ([#36](#36)) ([f753779](f753779)) * add progressive trust and promotion/demotion subsystems ([#43](#43), [#49](#49)) ([3a87c08](3a87c08)) * add retry handler, rate limiter, and provider resilience ([#100](#100)) ([b890545](b890545)) * add SecOps security agent with rule engine, audit log, and ToolInvoker integration ([#40](#40)) ([83b7b6c](83b7b6c)) * add shared org memory and memory consolidation/archival ([#125](#125), [#48](#48)) ([4a0832b](4a0832b)) * design unified provider interface ([#86](#86)) ([3e23d64](3e23d64)) * expand template presets, rosters, and add inheritance ([#80](#80), [#81](#81), [#84](#84)) ([15a9134](15a9134)) * implement agent runtime state vs immutable config split ([#115](#115)) ([4cb1ca5](4cb1ca5)) * implement AgentEngine core orchestrator ([#11](#11)) ([#143](#143)) ([f2eb73a](f2eb73a)) * implement basic tool system (registry, invocation, results) ([#15](#15)) ([c51068b](c51068b)) * implement built-in file system tools ([#18](#18)) ([325ef98](325ef98)) * implement communication foundation — message bus, dispatcher, and messenger ([#157](#157)) ([8e71bfd](8e71bfd)) * implement company template system with 7 built-in presets ([#85](#85)) ([cbf1496](cbf1496)) * implement conflict resolution protocol ([#122](#122)) ([#166](#166)) ([e03f9f2](e03f9f2)) * implement core entity and role system models ([#69](#69)) ([acf9801](acf9801)) * implement crash recovery with fail-and-reassign strategy ([#149](#149)) ([e6e91ed](e6e91ed)) * implement engine extensions — Plan-and-Execute loop and call categorization ([#134](#134), [#135](#135)) ([#159](#159)) ([9b2699f](9b2699f)) * implement enterprise logging system with structlog ([#73](#73)) ([2f787e5](2f787e5)) * implement graceful shutdown with cooperative timeout strategy ([#130](#130)) ([6592515](6592515)) * implement hierarchical delegation and loop prevention ([#12](#12), [#17](#17)) ([6be60b6](6be60b6)) * implement LiteLLM driver and provider registry ([#88](#88)) ([ae3f18b](ae3f18b)), closes [#4](#4) * implement LLM decomposition strategy and workspace isolation ([#174](#174)) ([aa0eefe](aa0eefe)) * implement meeting protocol system ([#123](#123)) ([ee7caca](ee7caca)) * implement message and communication domain models ([#74](#74)) ([560a5d2](560a5d2)) * implement model routing engine ([#99](#99)) ([d3c250b](d3c250b)) * implement parallel agent execution ([#22](#22)) ([#161](#161)) ([65940b3](65940b3)) * implement per-call cost tracking service ([#7](#7)) ([#102](#102)) ([c4f1f1c](c4f1f1c)) * implement personality injection and system prompt construction ([#105](#105)) ([934dd85](934dd85)) * implement single-task execution lifecycle ([#21](#21)) ([#144](#144)) ([c7e64e4](c7e64e4)) * implement subprocess sandbox for tool execution isolation ([#131](#131)) ([#153](#153)) ([3c8394e](3c8394e)) * implement task assignment subsystem with pluggable strategies ([#172](#172)) ([c7f1b26](c7f1b26)), closes [#26](#26) [#30](#30) * implement task decomposition and routing engine ([#14](#14)) ([9c7fb52](9c7fb52)) * implement Task, Project, Artifact, Budget, and Cost domain models ([#71](#71)) ([81eabf1](81eabf1)) * implement tool permission checking ([#16](#16)) ([833c190](833c190)) * implement YAML config loader with Pydantic validation ([#59](#59)) ([ff3a2ba](ff3a2ba)) * implement YAML config loader with Pydantic validation ([#75](#75)) ([ff3a2ba](ff3a2ba)) * initialize project with uv, hatchling, and src layout ([39005f9](39005f9)) * initialize project with uv, hatchling, and src layout ([#62](#62)) ([39005f9](39005f9)) * Litestar REST API, WebSocket feed, and approval queue (M6) ([#189](#189)) ([29fcd08](29fcd08)) * make TokenUsage.total_tokens a computed field ([#118](#118)) ([c0bab18](c0bab18)), closes [#109](#109) * parallel tool execution in ToolInvoker.invoke_all ([#137](#137)) ([58517ee](58517ee)) * testing framework, CI pipeline, and M0 gap fixes ([#64](#64)) ([f581749](f581749)) * wire all modules into observability system ([#97](#97)) ([f7a0617](f7a0617)) ### Bug Fixes * address Greptile post-merge review findings from PRs [#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175) ([#176](#176)) ([c5ca929](c5ca929)) * address post-merge review feedback from PRs [#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167) ([#170](#170)) ([3bf897a](3bf897a)), closes [#169](#169) * enforce strict mypy on test files ([#89](#89)) ([aeeff8c](aeeff8c)) * harden Docker sandbox, MCP bridge, and code runner ([#50](#50), [#53](#53)) ([d5e1b6e](d5e1b6e)) * harden git tools security + code quality improvements ([#150](#150)) ([000a325](000a325)) * harden subprocess cleanup, env filtering, and shutdown resilience ([#155](#155)) ([d1fe1fb](d1fe1fb)) * incorporate post-merge feedback + pre-PR review fixes ([#164](#164)) ([c02832a](c02832a)) * pre-PR review fixes for post-merge findings ([#183](#183)) ([26b3108](26b3108)) * strengthen immutability for BaseTool schema and ToolInvoker boundaries ([#117](#117)) ([7e5e861](7e5e861)) ### Performance * harden non-inferable principle implementation ([#195](#195)) ([02b5f4e](02b5f4e)), closes [#188](#188) ### Refactoring * adopt NotBlankStr across all models ([#108](#108)) ([#120](#120)) ([ef89b90](ef89b90)) * extract _SpendingTotals base class from spending summary models ([#111](#111)) ([2f39c1b](2f39c1b)) * harden BudgetEnforcer with error handling, validation extraction, and review fixes ([#182](#182)) ([c107bf9](c107bf9)) * harden personality profiles, department validation, and template rendering ([#158](#158)) ([10b2299](10b2299)) * pre-PR review improvements for ExecutionLoop + ReAct loop ([#124](#124)) ([8dfb3c0](8dfb3c0)) * split events.py into per-domain event modules ([#136](#136)) ([e9cba89](e9cba89)) ### Documentation * add ADR-001 memory layer evaluation and selection ([#178](#178)) ([db3026f](db3026f)), closes [#39](#39) * add agent scaling research findings to DESIGN_SPEC ([#145](#145)) ([57e487b](57e487b)) * add CLAUDE.md, contributing guide, and dev documentation ([#65](#65)) ([55c1025](55c1025)), closes [#54](#54) * add crash recovery, sandboxing, analytics, and testing decisions ([#127](#127)) ([5c11595](5c11595)) * address external review feedback with MVP scope and new protocols ([#128](#128)) ([3b30b9a](3b30b9a)) * expand design spec with pluggable strategy protocols ([#121](#121)) ([6832db6](6832db6)) * finalize 23 design decisions (ADR-002) ([#190](#190)) ([8c39742](8c39742)) * update project docs for M2.5 conventions and add docs-consistency review agent ([#114](#114)) ([99766ee](99766ee)) ### Tests * add e2e single agent integration tests ([#24](#24)) ([#156](#156)) ([f566fb4](f566fb4)) * add provider adapter integration tests ([#90](#90)) ([40a61f4](40a61f4)) ### CI/CD * add Release Please for automated versioning and GitHub Releases ([#278](#278)) ([a488758](a488758)) * bump actions/checkout from 4 to 6 ([#95](#95)) ([1897247](1897247)) * bump actions/upload-artifact from 4 to 7 ([#94](#94)) ([27b1517](27b1517)) * harden CI/CD pipeline ([#92](#92)) ([ce4693c](ce4693c)) * split vulnerability scans into critical-fail and high-warn tiers ([#277](#277)) ([aba48af](aba48af)) ### Maintenance * add /worktree skill for parallel worktree management ([#171](#171)) ([951e337](951e337)) * add design spec context loading to research-link skill ([8ef9685](8ef9685)) * add post-merge-cleanup skill ([#70](#70)) ([f913705](f913705)) * add pre-pr-review skill and update CLAUDE.md ([#103](#103)) ([92e9023](92e9023)) * add research-link skill and rename skill files to SKILL.md ([#101](#101)) ([651c577](651c577)) * bump aiosqlite from 0.21.0 to 0.22.1 ([#191](#191)) ([3274a86](3274a86)) * bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group ([#96](#96)) ([0338d0c](0338d0c)) * bump ruff from 0.15.4 to 0.15.5 ([a49ee46](a49ee46)) * fix M0 audit items ([#66](#66)) ([c7724b5](c7724b5)) * pin setup-uv action to full SHA ([#281](#281)) ([4448002](4448002)) * post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests, hookify rules ([#148](#148)) ([c57a6a9](c57a6a9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
🤖 I have created a release *beep* *boop* --- ## [0.1.0](v0.0.0...v0.1.0) (2026-03-11) ### Features * add autonomy levels and approval timeout policies ([#42](#42), [#126](#126)) ([#197](#197)) ([eecc25a](eecc25a)) * add CFO cost optimization service with anomaly detection, reports, and approval decisions ([#186](#186)) ([a7fa00b](a7fa00b)) * add code quality toolchain (ruff, mypy, pre-commit, dependabot) ([#63](#63)) ([36681a8](36681a8)) * add configurable cost tiers and subscription/quota-aware tracking ([#67](#67)) ([#185](#185)) ([9baedfa](9baedfa)) * add container packaging, Docker Compose, and CI pipeline ([#269](#269)) ([435bdfe](435bdfe)), closes [#267](#267) * add coordination error taxonomy classification pipeline ([#146](#146)) ([#181](#181)) ([70c7480](70c7480)) * add cost-optimized, hierarchical, and auction assignment strategies ([#175](#175)) ([ce924fa](ce924fa)), closes [#173](#173) * add design specification, license, and project setup ([8669a09](8669a09)) * add env var substitution and config file auto-discovery ([#77](#77)) ([7f53832](7f53832)) * add FastestStrategy routing + vendor-agnostic cleanup ([#140](#140)) ([09619cb](09619cb)), closes [#139](#139) * add HR engine and performance tracking ([#45](#45), [#47](#47)) ([#193](#193)) ([2d091ea](2d091ea)) * add issue auto-search and resolution verification to PR review skill ([#119](#119)) ([deecc39](deecc39)) * add mandatory JWT + API key authentication ([#256](#256)) ([c279cfe](c279cfe)) * add memory retrieval, ranking, and context injection pipeline ([#41](#41)) ([873b0aa](873b0aa)) * add pluggable MemoryBackend protocol with models, config, and events ([#180](#180)) ([46cfdd4](46cfdd4)) * add pluggable MemoryBackend protocol with models, config, and events ([#32](#32)) ([46cfdd4](46cfdd4)) * add pluggable output scan response policies ([#263](#263)) ([b9907e8](b9907e8)) * add pluggable PersistenceBackend protocol with SQLite implementation ([#36](#36)) ([f753779](f753779)) * add progressive trust and promotion/demotion subsystems ([#43](#43), [#49](#49)) ([3a87c08](3a87c08)) * add retry handler, rate limiter, and provider resilience ([#100](#100)) ([b890545](b890545)) * add SecOps security agent with rule engine, audit log, and ToolInvoker integration ([#40](#40)) ([83b7b6c](83b7b6c)) * add shared org memory and memory consolidation/archival ([#125](#125), [#48](#48)) ([4a0832b](4a0832b)) * design unified provider interface ([#86](#86)) ([3e23d64](3e23d64)) * expand template presets, rosters, and add inheritance ([#80](#80), [#81](#81), [#84](#84)) ([15a9134](15a9134)) * implement agent runtime state vs immutable config split ([#115](#115)) ([4cb1ca5](4cb1ca5)) * implement AgentEngine core orchestrator ([#11](#11)) ([#143](#143)) ([f2eb73a](f2eb73a)) * implement AuditRepository for security audit log persistence ([#279](#279)) ([94bc29f](94bc29f)) * implement basic tool system (registry, invocation, results) ([#15](#15)) ([c51068b](c51068b)) * implement built-in file system tools ([#18](#18)) ([325ef98](325ef98)) * implement communication foundation — message bus, dispatcher, and messenger ([#157](#157)) ([8e71bfd](8e71bfd)) * implement company template system with 7 built-in presets ([#85](#85)) ([cbf1496](cbf1496)) * implement conflict resolution protocol ([#122](#122)) ([#166](#166)) ([e03f9f2](e03f9f2)) * implement core entity and role system models ([#69](#69)) ([acf9801](acf9801)) * implement crash recovery with fail-and-reassign strategy ([#149](#149)) ([e6e91ed](e6e91ed)) * implement engine extensions — Plan-and-Execute loop and call categorization ([#134](#134), [#135](#135)) ([#159](#159)) ([9b2699f](9b2699f)) * implement enterprise logging system with structlog ([#73](#73)) ([2f787e5](2f787e5)) * implement graceful shutdown with cooperative timeout strategy ([#130](#130)) ([6592515](6592515)) * implement hierarchical delegation and loop prevention ([#12](#12), [#17](#17)) ([6be60b6](6be60b6)) * implement LiteLLM driver and provider registry ([#88](#88)) ([ae3f18b](ae3f18b)), closes [#4](#4) * implement LLM decomposition strategy and workspace isolation ([#174](#174)) ([aa0eefe](aa0eefe)) * implement meeting protocol system ([#123](#123)) ([ee7caca](ee7caca)) * implement message and communication domain models ([#74](#74)) ([560a5d2](560a5d2)) * implement model routing engine ([#99](#99)) ([d3c250b](d3c250b)) * implement parallel agent execution ([#22](#22)) ([#161](#161)) ([65940b3](65940b3)) * implement per-call cost tracking service ([#7](#7)) ([#102](#102)) ([c4f1f1c](c4f1f1c)) * implement personality injection and system prompt construction ([#105](#105)) ([934dd85](934dd85)) * implement single-task execution lifecycle ([#21](#21)) ([#144](#144)) ([c7e64e4](c7e64e4)) * implement subprocess sandbox for tool execution isolation ([#131](#131)) ([#153](#153)) ([3c8394e](3c8394e)) * implement task assignment subsystem with pluggable strategies ([#172](#172)) ([c7f1b26](c7f1b26)), closes [#26](#26) [#30](#30) * implement task decomposition and routing engine ([#14](#14)) ([9c7fb52](9c7fb52)) * implement Task, Project, Artifact, Budget, and Cost domain models ([#71](#71)) ([81eabf1](81eabf1)) * implement tool permission checking ([#16](#16)) ([833c190](833c190)) * implement YAML config loader with Pydantic validation ([#59](#59)) ([ff3a2ba](ff3a2ba)) * implement YAML config loader with Pydantic validation ([#75](#75)) ([ff3a2ba](ff3a2ba)) * initialize project with uv, hatchling, and src layout ([39005f9](39005f9)) * initialize project with uv, hatchling, and src layout ([#62](#62)) ([39005f9](39005f9)) * Litestar REST API, WebSocket feed, and approval queue (M6) ([#189](#189)) ([29fcd08](29fcd08)) * make TokenUsage.total_tokens a computed field ([#118](#118)) ([c0bab18](c0bab18)), closes [#109](#109) * parallel tool execution in ToolInvoker.invoke_all ([#137](#137)) ([58517ee](58517ee)) * testing framework, CI pipeline, and M0 gap fixes ([#64](#64)) ([f581749](f581749)) * wire all modules into observability system ([#97](#97)) ([f7a0617](f7a0617)) ### Bug Fixes * address Greptile post-merge review findings from PRs [#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175) ([#176](#176)) ([c5ca929](c5ca929)) * address post-merge review feedback from PRs [#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167) ([#170](#170)) ([3bf897a](3bf897a)), closes [#169](#169) * enforce strict mypy on test files ([#89](#89)) ([aeeff8c](aeeff8c)) * harden Docker sandbox, MCP bridge, and code runner ([#50](#50), [#53](#53)) ([d5e1b6e](d5e1b6e)) * harden git tools security + code quality improvements ([#150](#150)) ([000a325](000a325)) * harden subprocess cleanup, env filtering, and shutdown resilience ([#155](#155)) ([d1fe1fb](d1fe1fb)) * incorporate post-merge feedback + pre-PR review fixes ([#164](#164)) ([c02832a](c02832a)) * pre-PR review fixes for post-merge findings ([#183](#183)) ([26b3108](26b3108)) * resolve circular imports, bump litellm, fix release tag format ([#286](#286)) ([a6659b5](a6659b5)) * strengthen immutability for BaseTool schema and ToolInvoker boundaries ([#117](#117)) ([7e5e861](7e5e861)) ### Performance * harden non-inferable principle implementation ([#195](#195)) ([02b5f4e](02b5f4e)), closes [#188](#188) ### Refactoring * adopt NotBlankStr across all models ([#108](#108)) ([#120](#120)) ([ef89b90](ef89b90)) * extract _SpendingTotals base class from spending summary models ([#111](#111)) ([2f39c1b](2f39c1b)) * harden BudgetEnforcer with error handling, validation extraction, and review fixes ([#182](#182)) ([c107bf9](c107bf9)) * harden personality profiles, department validation, and template rendering ([#158](#158)) ([10b2299](10b2299)) * pre-PR review improvements for ExecutionLoop + ReAct loop ([#124](#124)) ([8dfb3c0](8dfb3c0)) * split events.py into per-domain event modules ([#136](#136)) ([e9cba89](e9cba89)) ### Documentation * add ADR-001 memory layer evaluation and selection ([#178](#178)) ([db3026f](db3026f)), closes [#39](#39) * add agent scaling research findings to DESIGN_SPEC ([#145](#145)) ([57e487b](57e487b)) * add CLAUDE.md, contributing guide, and dev documentation ([#65](#65)) ([55c1025](55c1025)), closes [#54](#54) * add crash recovery, sandboxing, analytics, and testing decisions ([#127](#127)) ([5c11595](5c11595)) * address external review feedback with MVP scope and new protocols ([#128](#128)) ([3b30b9a](3b30b9a)) * expand design spec with pluggable strategy protocols ([#121](#121)) ([6832db6](6832db6)) * finalize 23 design decisions (ADR-002) ([#190](#190)) ([8c39742](8c39742)) * update project docs for M2.5 conventions and add docs-consistency review agent ([#114](#114)) ([99766ee](99766ee)) ### Tests * add e2e single agent integration tests ([#24](#24)) ([#156](#156)) ([f566fb4](f566fb4)) * add provider adapter integration tests ([#90](#90)) ([40a61f4](40a61f4)) ### CI/CD * add Release Please for automated versioning and GitHub Releases ([#278](#278)) ([a488758](a488758)) * bump actions/checkout from 4 to 6 ([#95](#95)) ([1897247](1897247)) * bump actions/upload-artifact from 4 to 7 ([#94](#94)) ([27b1517](27b1517)) * bump anchore/scan-action from 6.5.1 to 7.3.2 ([#271](#271)) ([80a1c15](80a1c15)) * bump docker/build-push-action from 6.19.2 to 7.0.0 ([#273](#273)) ([dd0219e](dd0219e)) * bump docker/login-action from 3.7.0 to 4.0.0 ([#272](#272)) ([33d6238](33d6238)) * bump docker/metadata-action from 5.10.0 to 6.0.0 ([#270](#270)) ([baee04e](baee04e)) * bump docker/setup-buildx-action from 3.12.0 to 4.0.0 ([#274](#274)) ([5fc06f7](5fc06f7)) * bump sigstore/cosign-installer from 3.9.1 to 4.1.0 ([#275](#275)) ([29dd16c](29dd16c)) * harden CI/CD pipeline ([#92](#92)) ([ce4693c](ce4693c)) * split vulnerability scans into critical-fail and high-warn tiers ([#277](#277)) ([aba48af](aba48af)) ### Maintenance * add /worktree skill for parallel worktree management ([#171](#171)) ([951e337](951e337)) * add design spec context loading to research-link skill ([8ef9685](8ef9685)) * add post-merge-cleanup skill ([#70](#70)) ([f913705](f913705)) * add pre-pr-review skill and update CLAUDE.md ([#103](#103)) ([92e9023](92e9023)) * add research-link skill and rename skill files to SKILL.md ([#101](#101)) ([651c577](651c577)) * bump aiosqlite from 0.21.0 to 0.22.1 ([#191](#191)) ([3274a86](3274a86)) * bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group ([#96](#96)) ([0338d0c](0338d0c)) * bump ruff from 0.15.4 to 0.15.5 ([a49ee46](a49ee46)) * fix M0 audit items ([#66](#66)) ([c7724b5](c7724b5)) * **main:** release ai-company 0.1.1 ([#282](#282)) ([2f4703d](2f4703d)) * pin setup-uv action to full SHA ([#281](#281)) ([4448002](4448002)) * post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests, hookify rules ([#148](#148)) ([c57a6a9](c57a6a9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.com>
Summary
CompanyTemplate,TemplateVariable,TemplateAgentConfig,TemplateDepartmentConfig,TemplateMetadata) with full Pydantic v2 validation~/.ai-company/templates/) with override priority over builtinsdeep_merge()toconfig/utils.pyas a shared public utilitycheck-yamlpre-commit hookTwo-Pass Pipeline
RootConfigFollow-Up Issues Created
Test plan
RootConfigruff checkclean,mypystrict clean, 97% coverage (1048 tests total)Closes #60
🤖 Generated with Claude Code