Skip to content

Feature/project workspace#355

Open
perasperaactual wants to merge 7 commits into
mpfaffenberger:mainfrom
perasperaactual:upstream-pr
Open

Feature/project workspace#355
perasperaactual wants to merge 7 commits into
mpfaffenberger:mainfrom
perasperaactual:upstream-pr

Conversation

@perasperaactual
Copy link
Copy Markdown

This PR adds a unified project-local workspace directory (.code-puppy/) that lets projects define their own MCP servers, JSON agents, and plugins without touching the global ~/.code_puppy/ config. An optional projectOnly flag switches from additive merge to full isolation.

This is the .vscode/ pattern applied to code-puppy; reproducible project-scoped AI agent environments that work out of the box with no manual global config surgery.

Walk-up discovery: on startup, code-puppy walks from CWD to the git root looking for .code-puppy/. The first directory found is the project workspace. Config never crosses a git root boundary.

my-project/
└── .code-puppy/
├── config.json # { "projectOnly": false } ← optional
├── mcp_servers.json # project MCP servers
├── agents/ # JSON agent definitions
└── plugins/ # project-scoped plugins

config.json supports one flag: projectOnly (boolean, default false).

projectOnly: false (default) — additive merge, local wins on name collision. Zero behavior change for users without a .code-puppy/ directory.

projectOnly: true — global ~/.code_puppy/ is skipped entirely:

┌────────────────────────────────────────────────────────┬─────────────────┐
│ Resource │ Effect │
├────────────────────────────────────────────────────────┼─────────────────┤
│ Global mcp_servers.json │ Skipped │
│ ~/.code_puppy/agents/ user JSON agents │ Skipped │
│ Builtin Python agents (security-auditor, qa-kitten, │ Hidden │
│ etc.) │ │
│ ~/.code_puppy/plugins/ user plugins │ Skipped │
│ .code-puppy/ workspace agents/plugins │ Loaded │
│ Builtin code-puppy plugins │ Always loaded │
│ Base code-puppy agent │ Always │
│ │ available │
└────────────────────────────────────────────────────────┴─────────────────┘

…ppy.json config

MCP servers now auto-enable on startup when config.enabled=True (the default).
Previously _enabled was hardcoded False, requiring an explicit /mcp start
before tools were available.

load_local_mcp_config() walks up from CWD to find a project-local
.code-puppy.json. sync_from_local_config() registers those servers after
global config (local wins on collision, never persists to mcp_registry.json).
Supports mcpServers array + mcp_servers object formats.
…eValidator

LLMs sometimes pass the questions array as a JSON string. pydantic_ai
rejects 'expected list, received str'. BeforeValidator(_coerce_questions_json_string)
json.loads() when input is a string, passes through otherwise. Transparent
to JSON Schema generation.
Add unified workspace discovery that walks up from CWD to git root
looking for a .code-puppy/ directory. Reads config.json for settings
including the projectOnly flag (default: false).

- ProjectWorkspace frozen dataclass with root_path, workspace_path,
  project_only, and config fields
- get_project_workspace() with per-process caching, CWD invalidation
- is_project_only() convenience wrapper
- get_project_agents_directory() now prefers workspace agents/ dir
  with backward compat for legacy .code_puppy/agents/ in CWD
- PROJECT_WORKSPACE_DIR_NAME constant for easy naming changes
- 22 tests covering discovery, git-root boundary, caching, config
  parsing, legacy fallback, and nearest-wins semantics

Part 1 of 5 for project workspace support.
Bead: code_puppy-9id
load_local_mcp_config() now checks the workspace first:
  1. .code-puppy/mcp_servers.json (dedicated file in workspace)
  2. mcpServers/mcp_servers keys in .code-puppy/config.json
  3. Legacy .code-puppy.json walk-up (backward compat)

MCPManager.sync_from_config() skips global ~/.code_puppy/mcp_servers.json
entirely when projectOnly mode is active.

Extract _parse_mcp_data() helper to DRY the format-parsing logic between
workspace and legacy loading paths.

8 new tests covering workspace MCP loading, priority, variable expansion,
legacy fallback, and projectOnly blocking of global config.

Part 2 of 5 for project workspace support.
Bead: code_puppy-9id
discover_json_agents() skips user-level agents (~/.code_puppy/agents/)
when projectOnly is active — only workspace JSON agents are loaded.

_discover_agents() in projectOnly mode:
- Skips all builtin Python agents (security-auditor, qa-kitten, etc.)
- Registers only the base code-puppy agent as fallback
- Skips plugin-registered agents (section 3)
- JSON agents still load (via discover_json_agents which gates internally)

This is the core isolation mechanism: in projectOnly mode, list_agents()
returns only what the workspace defines plus the base code-puppy agent.

6 new tests covering user-agent skip, additive mode, name collision,
base agent always available, builtin agent hiding, and normal mode.

Part 3 of 5 for project workspace support.
Bead: code_puppy-9id
Add _load_project_plugins() for loading plugins from workspace
.code-puppy/plugins/ directory, same mechanism as user plugins.

load_plugin_callbacks() now:
- Always loads builtin plugins (code-puppy internals)
- Skips user plugins (~/.code_puppy/plugins/) in projectOnly mode
- Loads project plugins from workspace when available
- Returns 3-key dict: builtin, user, project

6 new tests covering project plugin discovery, user plugin skip in
projectOnly, user plugin inclusion without projectOnly, builtin always
loading, result structure, and no-workspace fallback.

Part 4 of 5 for project workspace support.
Bead: code_puppy-9id
PROJECT_WORKSPACE_DIR_NAME = ".code_puppy" (underscore) — consistent
with upstream's .code_puppy/ convention (see 1298654), the existing
AGENTS_DIR path, and the general code_puppy naming style.

Legacy .code-puppy.json flat file is unchanged — that's a separate
pre-existing convention for the walk-up MCP config file.

Update test_workspace_preferred_over_legacy to mock get_project_workspace()
directly, since workspace and legacy paths now share the same directory
name and can no longer be distinguished by filesystem layout alone.

Bead: code_puppy-9id

# Conflicts:
#	CHANGES_FROM_UPSTREAM.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant