Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion architecture/features/agent-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ Without this feature, users would need to manually create and maintain agent-spe
2. - `p1` - **IF** `--agent` flag provided, filter to single agent - `inst-if-filter`
3. - `p1` - **RETURN** list of agents to generate for - `inst-return-agents`
4. [x] - `p1` - Resolve config/kits/ directory and registered kit dirs from core.toml for workflow/skill discovery - `inst-resolve-kits`
5. [x] - `p1` - Parse CLI arguments, resolve project root, studio root, load agent config (shared context for agents commands) - `inst-resolve-context`
5. [x] - `p1` - Registered kit path resolution honors register-mode entries persisted as `..` when the resolved path stays inside the project root, so same-project canonical kits remain discoverable to `generate-agents` - `inst-resolve-register-project-root-kit`
6. [x] - `p1` - Parse CLI arguments, resolve project root, studio root, load agent config (shared context for agents commands) - `inst-resolve-context`

**Supporting**:
- [x] - `p1` - Module-level constant for all recognized agent names - `inst-define-registry-const`
Expand Down
8 changes: 6 additions & 2 deletions skills/studio/scripts/studio/commands/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,7 @@ def _compute_workflow_skill_id(wf_name: str, kit_slug: Optional[str], prefix: st
# @cpt-end:cpt-studio-algo-agent-integration-list-workflows:p1:inst-scan-core-workflows


# @cpt-begin:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-register-project-root-kit
# @cpt-begin:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-kits
def _resolve_registered_project_relative_path(
project_root: Path,
Expand All @@ -2039,7 +2040,6 @@ def _resolve_registered_project_relative_path(
not normalized
or path_obj.is_absolute()
or PureWindowsPath(normalized).is_absolute()
or ".." in path_obj.parts
):
return None
resolved = (project_root / Path(normalized)).resolve()
Expand All @@ -2049,8 +2049,10 @@ def _resolve_registered_project_relative_path(
return None
return resolved
# @cpt-end:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-kits
# @cpt-end:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-register-project-root-kit


# @cpt-begin:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-register-project-root-kit
# @cpt-begin:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-kits
def _resolve_registered_legacy_studio_path(
studio_root: Path,
Expand All @@ -2059,11 +2061,12 @@ def _resolve_registered_legacy_studio_path(
) -> Optional[Path]:
normalized = PurePosixPath(raw_path.strip().replace("\\", "/")).as_posix()
path_obj = PurePosixPath(normalized)
has_parent_traversal = ".." in path_obj.parts
if (
not normalized
or path_obj.is_absolute()
or PureWindowsPath(normalized).is_absolute()
or ".." in path_obj.parts
or (has_parent_traversal and normalized != "..")
):
return None
resolved = (studio_root / Path(normalized)).resolve()
Expand All @@ -2072,6 +2075,7 @@ def _resolve_registered_legacy_studio_path(
except ValueError:
return None
return resolved
# @cpt-end:cpt-studio-algo-agent-integration-discover-agents:p1:inst-resolve-register-project-root-kit


def _registered_kit_dirs(project_root: Optional[Path]) -> Set[str]:
Expand Down
134 changes: 134 additions & 0 deletions tests/test_agents_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,140 @@ def test_generate_agents_uses_kitmodel_public_skill_and_agent_but_not_rule(self)
self.assertFalse((root / ".cursor" / "agents" / "cf-pubkit-codexonly.mdc").exists())
self.assertFalse((root / ".cursor" / "agents" / "cf-pubkit-codex-auditor.mdc").exists())

def test_generate_agents_supports_register_mode_project_root_kit_path(self):
from studio.commands.agents import _default_agents_config, _process_single_agent

with TemporaryDirectory() as td:
root = Path(td) / "project"
root.mkdir()
(root / ".git").mkdir()
(root / "AGENTS.md").write_text(
'<!-- @cf:root-agents -->\n```toml\ncf-studio-path = ".cf-studio"\n```\n<!-- /@cf:root-agents -->\n',
encoding="utf-8",
)
studio_root = root / ".cf-studio"
(studio_root / ".core" / "skills" / "studio").mkdir(parents=True)
(studio_root / ".core" / "skills" / "studio" / "SKILL.md").write_text(
"---\nname: cf\ndescription: Constructor Studio\n---\nCore skill\n",
encoding="utf-8",
)
(studio_root / ".core" / "workflows").mkdir(parents=True)
(studio_root / "config").mkdir(parents=True)
(studio_root / "config" / "core.toml").write_text(
"\n".join([
'version = "1.0"',
'project_root = ".."',
"",
"[kits.gears]",
'format = "CFS"',
'path = ".."',
'version = "0.1.0"',
'install_mode = "register"',
]) + "\n",
encoding="utf-8",
)
(root / ".cf-studio-kit.toml").write_text(
"\n".join([
'manifest_version = "1.0"',
"",
"[[kits]]",
'slug = "gears"',
'name = "Gears"',
'version = "0.1.0"',
"",
"[[kits.resources]]",
'id = "workflow_doc_prd"',
'kind = "skill"',
'source = "studio-kit-gears/workflows/doc-prd.md"',
'type = "file"',
"public = true",
]) + "\n",
encoding="utf-8",
)
workflow_source = root / "studio-kit-gears" / "workflows" / "doc-prd.md"
workflow_source.parent.mkdir(parents=True)
workflow_source.write_text(
"---\nname: doc-prd\ndescription: Draft PRDs\n---\n# Doc PRD\n",
encoding="utf-8",
)

result = _process_single_agent(
"cursor",
root,
studio_root,
_default_agents_config(),
None,
dry_run=False,
)

self.assertEqual(result["status"], "PASS")
generated_skill = root / ".agents" / "skills" / "cf-gears-doc-prd" / "SKILL.md"
self.assertTrue(generated_skill.is_file())
generated_content = generated_skill.read_text(encoding="utf-8")
self.assertIn("cf-gears-doc-prd", generated_content)
self.assertIn("studio-kit-gears/workflows/doc-prd.md", generated_content)

def test_list_public_components_supports_register_mode_project_root_kit_path(self):
from studio.commands.agents import _list_public_components

with TemporaryDirectory() as td:
root = Path(td) / "project"
root.mkdir()
(root / ".git").mkdir()
(root / "AGENTS.md").write_text(
'<!-- @cf:root-agents -->\n```toml\ncf-studio-path = ".cf-studio"\n```\n<!-- /@cf:root-agents -->\n',
encoding="utf-8",
)
studio_root = root / ".cf-studio"
(studio_root / "config").mkdir(parents=True)
(studio_root / "config" / "core.toml").write_text(
"\n".join([
'version = "1.0"',
'project_root = ".."',
"",
"[kits.gears]",
'format = "CFS"',
'path = ".."',
'version = "0.1.0"',
'install_mode = "register"',
]) + "\n",
encoding="utf-8",
)
(root / ".cf-studio-kit.toml").write_text(
"\n".join([
'manifest_version = "1.0"',
"",
"[[kits]]",
'slug = "gears"',
'name = "Gears"',
'version = "0.1.0"',
"",
"[[kits.resources]]",
'id = "workflow_doc_prd"',
'kind = "skill"',
'source = "studio-kit-gears/workflows/doc-prd.md"',
'type = "file"',
"public = true",
]) + "\n",
encoding="utf-8",
)
workflow_source = root / "studio-kit-gears" / "workflows" / "doc-prd.md"
workflow_source.parent.mkdir(parents=True)
workflow_source.write_text(
"---\nname: doc-prd\ndescription: Draft PRDs\n---\n# Doc PRD\n",
encoding="utf-8",
)

components, manifest_backed = _list_public_components(studio_root, root, "cursor")

self.assertEqual(manifest_backed, {"gears"})
self.assertEqual(len(components), 1)
kit_slug, component, source_path, kit_root, _kit_entry = components[0]
self.assertEqual(kit_slug, "gears")
self.assertEqual(getattr(component, "generated_name", ""), "cf-gears-doc-prd")
self.assertEqual(source_path, workflow_source.resolve())
self.assertEqual(kit_root, root.resolve())

def test_nested_public_subagent_uses_registered_resource_binding(self):
from studio.commands.agents import _default_agents_config, _process_single_agent

Expand Down
Loading