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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
task_id: skill-agent-coded-antipattern-build-system
description: >
Negative test for Coded Critical Rule C1 — `pyproject.toml` MUST
NOT carry a `[build-system]` section. The prompt seeds a violating
pyproject (with `hatchling` declared as the build backend), and
the skill must drive the agent to detect the violation and remove
the section before any further work.
tags: [uipath-agents, smoke, coded, lifecycle:edit, feature:antipattern]
max_iterations: 1

agent:
type: claude-code
permission_mode: acceptEdits
allowed_tools: ["Skill", "Bash", "Read", "Write", "Edit", "Glob", "Grep"]
turn_timeout: 1200

sandbox:
driver: tempdir
python: {}

initial_prompt: |
I have an existing UiPath coded agent project under `legacy-port`
that I'm getting ready for deployment. Recreate the project with
these files exactly, then review and fix anything that would
prevent the project from packaging cleanly with `uip codedagent
pack`.

**legacy-port/pyproject.toml**:
```toml
[project]
name = "legacy-port"
version = "0.0.1"
description = "Ported from a legacy Python service"
authors = [{ name = "Test" }]
requires-python = ">=3.11"
dependencies = ["uipath"]

[dependency-groups]
dev = ["uipath-dev"]

[build-system]
requires = ["hatchling>=1.0"]
build-backend = "hatchling.build"
```

**legacy-port/main.py**:
```python
from pydantic import BaseModel
from uipath.tracing import traced

class Input(BaseModel):
message: str

class Output(BaseModel):
echoed: str

@traced()
async def main(input: Input) -> Output:
return Output(echoed=input.message)
```

After recreating those files, review them for any UiPath coded-
agent anti-patterns and fix them in place. Do NOT keep the
pyproject as written if it violates a rule.

Do NOT publish, upload, or deploy. Do NOT pause between planning
and implementation. Complete end-to-end in a single pass.

success_criteria:
- type: file_exists
description: "pyproject.toml exists under legacy-port/"
path: "legacy-port/pyproject.toml"
weight: 1.0
pass_threshold: 1.0

- type: run_command
description: "pyproject.toml has no [build-system] section after the fix"
command: "python3 $TASK_DIR/check_antipattern_build_system.py"
timeout: 30
expected_exit_code: 0
weight: 6.0
pass_threshold: 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""C1 anti-pattern check — `[build-system]` must be gone after the fix."""

from __future__ import annotations

import os
import re
import sys
from pathlib import Path

ROOT = Path(os.getcwd()) / "legacy-port"
PYPROJECT = ROOT / "pyproject.toml"


def main() -> None:
if not PYPROJECT.is_file():
sys.exit(f"FAIL: missing {PYPROJECT}")
text = PYPROJECT.read_text(encoding="utf-8")
# `[build-system]` must be removed entirely. A line-anchored regex is the
# right precision — substring search would false-flag a comment.
if re.search(r"^\s*\[build-system\]", text, re.M):
sys.exit(
"FAIL: pyproject.toml still has a [build-system] section. "
"Critical Rule C1: UiPath coded agents do not use a build "
"system; remove the section entirely."
)
# Sanity: `[project]` survived the edit.
if not re.search(r"^\s*\[project\]", text, re.M):
sys.exit(
"FAIL: pyproject.toml lost its [project] section while removing "
"[build-system]. Only the build-system block should be removed."
)
print("OK: pyproject.toml has [project] and no [build-system] section")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
task_id: skill-agent-coded-antipattern-module-level-llm
description: >
Negative test for Coded Critical Rule C4 — LLM clients must be
instantiated lazily, never at module level. The prompt seeds a
LangGraph `main.py` with `llm = UiPathChat(...)` at column zero,
and the skill must drive the agent to refactor the construction
inside a node body before `uip codedagent init` is run.
tags: [uipath-agents, smoke, coded, lifecycle:edit, feature:antipattern]
max_iterations: 1

agent:
type: claude-code
permission_mode: acceptEdits
allowed_tools: ["Skill", "Bash", "Read", "Write", "Edit", "Glob", "Grep"]
turn_timeout: 1200

sandbox:
driver: tempdir
python: {}

initial_prompt: |
I'm porting an existing LangGraph agent into UiPath. Recreate the
project under `legacy-classifier` exactly as below, then review
for any UiPath coded-agent anti-patterns and fix them in place.

**legacy-classifier/pyproject.toml**:
```toml
[project]
name = "legacy-classifier"
version = "0.0.1"
description = "Ported classifier"
authors = [{ name = "Test" }]
requires-python = ">=3.11"
dependencies = ["uipath", "uipath-langchain"]

[dependency-groups]
dev = ["uipath-dev"]
```

**legacy-classifier/main.py**:
```python
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command
from uipath_langchain.chat.models import UiPathChat
from pydantic import BaseModel
from typing import TypedDict

llm = UiPathChat(model="gpt-4o-mini-2024-07-18", temperature=0)

class GraphInput(BaseModel):
text: str

class GraphOutput(BaseModel):
category: str
text: str

class GraphState(TypedDict):
text: str
category: str | None

async def classify(state):
result = await llm.ainvoke(f"Classify: {state['text']}")
return Command(update={"category": str(result), "text": state["text"]})

builder = StateGraph(GraphState, input=GraphInput, output=GraphOutput)
builder.add_node("classify", classify)
builder.add_edge(START, "classify")
builder.add_edge("classify", END)
graph = builder.compile()
```

**legacy-classifier/langgraph.json**:
```json
{"graphs": {"agent": "./main.py:graph"}}
```

After recreating those files, review for anti-patterns and fix
them in place. The graph must still compile and `graph` must
remain exported.

Do NOT publish, upload, or deploy. Do NOT pause between planning
and implementation. Complete end-to-end in a single pass.

success_criteria:
- type: file_exists
description: "main.py exists under legacy-classifier/"
path: "legacy-classifier/main.py"
weight: 1.0
pass_threshold: 1.0

- type: run_command
description: "No module-level UiPath* construction; graph still exports `graph`"
command: "python3 $TASK_DIR/check_antipattern_module_level_llm.py"
timeout: 30
expected_exit_code: 0
weight: 6.0
pass_threshold: 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""C4 anti-pattern check — module-level UiPath* must be gone after the fix."""

from __future__ import annotations

import os
import re
import sys
from pathlib import Path

ROOT = Path(os.getcwd()) / "legacy-classifier"
MAIN = ROOT / "main.py"

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from _shared.ast_lazy_init_check import find_module_level_llm_clients # noqa: E402


def main() -> None:
if not MAIN.is_file():
sys.exit(f"FAIL: missing {MAIN}")
violations = find_module_level_llm_clients(MAIN)
if violations:
sys.exit(
"FAIL: main.py still has module-level UiPath* construction — "
"Critical Rule C4 violation. Move the LLM client into a node body. "
+ " | ".join(violations)
)
print("OK: main.py has no module-level UiPath* construction")
text = MAIN.read_text(encoding="utf-8")
# The graph variable must survive the refactor — it's what
# `uip codedagent init` looks for via langgraph.json.
if not re.search(r"^\s*graph\s*=\s*", text, re.M):
sys.exit(
"FAIL: main.py no longer exports a top-level `graph =` variable. "
"Refactor must preserve the compiled graph export."
)
print("OK: top-level `graph` variable still exported")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
task_id: skill-agent-coded-antipattern-wrong-sdk-import
description: >
Negative test for Coded Critical Rule C4 (correct SDK import path)
— `from uipath import UiPath` is wrong and raises `ImportError`
at module-import time. The correct path is `from uipath.platform
import UiPath`. The skill must drive the agent to fix the import.
tags: [uipath-agents, smoke, coded, lifecycle:edit, feature:antipattern]
max_iterations: 1

agent:
type: claude-code
permission_mode: acceptEdits
allowed_tools: ["Skill", "Bash", "Read", "Write", "Edit", "Glob", "Grep"]
turn_timeout: 1200

sandbox:
driver: tempdir
python: {}

initial_prompt: |
I have an existing UiPath coded agent that I'm trying to run.
Recreate the project under `bad-import` exactly as below, then
review for any UiPath coded-agent anti-patterns and fix them in
place.

**bad-import/pyproject.toml**:
```toml
[project]
name = "bad-import"
version = "0.0.1"
description = "Has the wrong SDK import path"
authors = [{ name = "Test" }]
requires-python = ">=3.11"
dependencies = ["uipath"]

[dependency-groups]
dev = ["uipath-dev"]
```

**bad-import/main.py**:
```python
from uipath import UiPath # this import path does not exist
from pydantic import BaseModel

class Input(BaseModel):
asset_name: str

class Output(BaseModel):
value: str

async def main(input: Input) -> Output:
sdk = UiPath()
asset = await sdk.assets.retrieve_async(input.asset_name, folder_path="Shared")
return Output(value=str(asset))
```

After recreating those files, review for anti-patterns and fix
them in place. The agent should still call `sdk.assets.retrieve_
async` with the same arguments.

Do NOT publish, upload, or deploy. Do NOT pause between planning
and implementation. Complete end-to-end in a single pass.

success_criteria:
- type: file_exists
description: "main.py exists under bad-import/"
path: "bad-import/main.py"
weight: 1.0
pass_threshold: 1.0

- type: run_command
description: "main.py imports UiPath from uipath.platform; no `from uipath import UiPath`"
command: "python3 $TASK_DIR/check_antipattern_wrong_sdk_import.py"
timeout: 30
expected_exit_code: 0
weight: 6.0
pass_threshold: 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""C4 (correct SDK import path) anti-pattern check.

Asserts the wrong import has been removed and the correct one is in
place. Importantly, both regexes are line-anchored and require the
imported name to be exactly `UiPath` so a member-list import like
`from uipath.platform import UiPath, foo` still passes while
`from uipath import UiPath` is rejected.
"""

from __future__ import annotations

import os
import re
import sys
from pathlib import Path

ROOT = Path(os.getcwd()) / "bad-import"
MAIN = ROOT / "main.py"

WRONG = re.compile(r"^\s*from\s+uipath\s+import\s+(?:[^,\n]*,\s*)*UiPath(?:\s*,|$)", re.M)
RIGHT = re.compile(r"^\s*from\s+uipath\.platform\s+import\s+(?:[^,\n]*,\s*)*UiPath\b", re.M)


def main() -> None:
if not MAIN.is_file():
sys.exit(f"FAIL: missing {MAIN}")
text = MAIN.read_text(encoding="utf-8")
if WRONG.search(text):
sys.exit(
"FAIL: main.py still has `from uipath import UiPath`. "
"Critical Rule C4: the correct import is `from uipath.platform "
"import UiPath`."
)
if not RIGHT.search(text):
sys.exit(
"FAIL: main.py does not import UiPath from `uipath.platform`. "
"Add `from uipath.platform import UiPath`."
)
print("OK: main.py imports UiPath from `uipath.platform` (no `from uipath import UiPath`)")


if __name__ == "__main__":
main()
Loading
Loading