Skip to content

feat(artifact): implement Phase 2 Python Script Artifact (#526)#547

Merged
phodal merged 2 commits intomasterfrom
feature/python-artifact-phase2
Mar 8, 2026
Merged

feat(artifact): implement Phase 2 Python Script Artifact (#526)#547
phodal merged 2 commits intomasterfrom
feature/python-artifact-phase2

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Mar 8, 2026

Summary

Implements Phase 2 of AutoDev Unit (Issue #526): Python Script Artifact support.

New Files

  • PEP723Parser.kt (commonMain) - PEP 723 parser/generator
  • PythonArtifactAgent.kt (commonMain) - SubAgent for Python script generation
  • PythonArtifactPackager.kt (jvmMain) - uv/pip packaging with AutoDev context
  • PEP723ParserTest.kt (jvmTest) - 10 unit tests

Modified

  • PythonArtifactExecutor.kt - Refactored to use shared PEP723Parser

All tests passing.

Summary by CodeRabbit

  • New Features

    • PEP 723 inline script metadata parsing/generation and injection/stripping for Python artifacts
    • LLM-driven Python artifact generator that produces self-contained scripts with metadata
    • Self-contained packaging with UV-first dependency installation and pip fallback, plus requirements output
  • Refactor

    • Centralized dependency parsing used across components
  • Tests

    • Comprehensive tests covering parsing, generation, injection, and stripping of metadata

- Add PEP723Parser (commonMain): cross-platform PEP 723 inline script
  metadata parser/generator with parse, generate, injectMetadata, and
  stripMetadata operations.
- Add PythonArtifactAgent (commonMain): SubAgent that uses LLM to
  generate PEP 723-compliant Python scripts.
- Add PythonArtifactPackager (jvmMain): packages Python scripts with
  dependency resolution via uv (preferred) or pip fallback, and embeds
  AutoDev Unit context in the PEP 723 header.
- Refactor PythonArtifactExecutor: replace 40-line local PEP 723
  parsing logic with delegation to shared PEP723Parser.
- Add PEP723ParserTest: 10 unit tests covering parse, generate, inject,
  and strip operations.
Copilot AI review requested due to automatic review settings March 8, 2026 01:17
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 565d7112-157e-4508-8ea9-33192c319faa

📥 Commits

Reviewing files that changed from the base of the PR and between 529f81d and 3474db5.

📒 Files selected for processing (4)
  • gradle.properties
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/artifact/PEP723Parser.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt

📝 Walkthrough

Walkthrough

Adds centralized PEP 723 metadata parsing/generation, an LLM-driven Python artifact sub-agent that produces scripts with embedded PEP 723 blocks, and a JVM packager that enriches scripts, resolves dependencies (UV then pip), and writes artifacts. Executor now delegates dependency parsing to the new parser.

Changes

Cohort / File(s) Summary
PEP 723 Parsing Infrastructure
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/artifact/PEP723Parser.kt, mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt
New PEP723Parser singleton and PEP723Metadata data class; parsing (requires-python, dependencies, autodev-unit), generation, inject/strip utilities; comprehensive tests.
Python Artifact Generation (LLM sub-agent)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt
New PythonArtifactAgent and PythonArtifactInput to drive LLM-based Python script generation, extract code from responses, inject PEP 723 metadata, and produce ToolResult outputs.
Python Artifact Packaging
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt
New PythonArtifactPackager with dual dependency resolution strategies (UV preferred, pip fallback), writes enriched index.py and requirements.txt, streams install output, returns PackageResult with chosen strategy.
Executor Refactor
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/executor/PythonArtifactExecutor.kt
Replaced inline regex parsing with delegation to PEP723Parser.parseDependencies(); updated docs.
Build Metadata
gradle.properties
mppVersion changed from 3.0.0-alpha5 to 1.0.2.

Sequence Diagram

sequenceDiagram
    participant User
    participant Agent as PythonArtifactAgent
    participant LLM as LLMService
    participant Parser as PEP723Parser
    participant Packager as PythonArtifactPackager
    participant Resolver as UV/PIP Resolver

    User->>Agent: submit prompt + desired deps
    Agent->>LLM: SYSTEM_PROMPT + user prompt
    LLM-->>Agent: streamed response with code block
    Agent->>Agent: extractPythonCode()
    Agent->>Parser: injectMetadata(script, deps, requiresPython, autodevContext)
    Parser-->>Agent: enriched script (with PEP 723 block)
    Agent-->>User: ToolResult with enriched script

    User->>Packager: packageScript(enriched script, context, outputDir)
    Packager->>Parser: parse(script)
    Parser-->>Packager: PEP723Metadata
    Packager->>Parser: generate(updated deps, requiresPython, autodevContext)
    Parser-->>Packager: PEP 723 block
    Packager->>Packager: write index.py, requirements.txt
    Packager->>Resolver: try UV install
    Resolver-->>Packager: UV success / UV fail -> pip
    Packager-->>User: PackageResult.Success / Error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nibble lines of PEP and weave a tiny script,
Metadata snug, dependencies tightly gripped.
From LLM whispers to UV's quick prance,
Packaged and ready—give my paws a chance! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change as implementing Phase 2 Python Script Artifact support, which aligns with the comprehensive changes adding PEP723Parser, PythonArtifactAgent, PythonArtifactPackager, and related test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/python-artifact-phase2

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@augmentcode
Copy link

augmentcode bot commented Mar 8, 2026

🤖 Augment PR Summary

Summary: Adds Phase 2 support for generating and packaging Python “script artifacts” using PEP 723 inline metadata.

Changes:

  • Introduces PEP723Parser to parse/generate/inject/strip PEP 723 metadata blocks (including an AutoDev Unit [tool.autodev-unit] section).
  • Adds PythonArtifactAgent sub-agent to stream LLM output and extract Python scripts from either artifact XML tags or fenced code blocks.
  • Adds PythonArtifactPackager (JVM) to enrich scripts with AutoDev context and install dependencies via uv (preferred) with a pip fallback.
  • Refactors PythonArtifactExecutor to delegate dependency parsing to the shared PEP723Parser.
  • Adds PEP723ParserTest with unit coverage for parsing, generation, injection, and stripping behaviors.

Technical Notes: Dependency resolution is driven by PEP 723 metadata, enabling “single-file” scripts to be executed in a reproducible way while carrying AutoDev context for load-back/reversibility.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

): CommandResult = withContext(Dispatchers.IO) {
try {
val processBuilder = ProcessBuilder()
.command("sh", "-c", command)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ProcessBuilder("sh", "-c", command) can enable shell injection if command incorporates untrusted values (e.g., dependency specs parsed from the script/LLM output). Consider executing via an argument list (no shell) or strictly validating/escaping any interpolated values before invocation.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


private suspend fun isCommandAvailable(cmd: String): Boolean = withContext(Dispatchers.IO) {
try {
val process = ProcessBuilder("which", cmd)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessBuilder("which", cmd) (and later hardcoding sh) makes this packager Unix-specific; on Windows it will fail to detect/run uv/pip. If Windows is a supported IDE target, consider routing command execution through existing cross-platform shell helpers in the codebase.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


// Validate PEP 723 metadata is present; inject if missing
val meta = PEP723Parser.parse(scriptContent)
val finalScript = if (meta.rawBlock == null && input.dependencies.isNotEmpty()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only injects PEP 723 metadata when input.dependencies is non-empty, so an empty-deps script could be returned without any header if the LLM omits it (despite the system rule that every script must start with metadata). Consider injecting a minimal block (at least requires-python) even when deps are empty, or aligning the rule/behavior.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

// ---- Parsing ----

private val PEP723_BLOCK_PATTERN = Regex(
"""#\s*///\s*script\s*\n(.*?)#\s*///""",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These regexes hardcode \n, so scripts with CRLF (\r\n) may fail to match/strip/inject the PEP 723 block (and related sections). Consider allowing optional \r in newline parts to be robust across platforms.

Severity: medium

Other Locations
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/artifact/PEP723Parser.kt:56

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Phase 2 “Python Script Artifact” support by introducing a shared PEP 723 metadata parser/generator, a Python artifact generation sub-agent, and a JVM-side packaging flow, plus refactoring the Python executor to reuse the shared parser.

Changes:

  • Added PEP723Parser (common) with parse/generate/inject/strip helpers for PEP 723 inline metadata.
  • Added PythonArtifactAgent (common) to generate Python scripts with inline metadata via LLM output extraction.
  • Added PythonArtifactPackager (JVM) to enrich scripts with AutoDev context and install dependencies via uv/pip; refactored PythonArtifactExecutor to use the shared parser; added unit tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/artifact/PEP723Parser.kt New shared PEP 723 parsing/generation utilities used by agent/executor/packager.
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt New LLM-powered sub-agent to produce Python artifact scripts and metadata.
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt New JVM packager to inject context and resolve dependencies with uv/pip.
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/executor/PythonArtifactExecutor.kt Refactor to delegate dependency parsing to PEP723Parser.
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt New unit tests for PEP 723 parsing/generation/injection/stripping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +39 to +42
private val PEP723_BLOCK_PATTERN = Regex(
"""#\s*///\s*script\s*\n(.*?)#\s*///""",
RegexOption.DOT_MATCHES_ALL
)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP723_BLOCK_PATTERN hard-codes \n line endings, so scripts with CRLF (\r\n) may fail to match and metadata won't be parsed/replaced/stripped correctly. Updating the regex to tolerate \r?\n (and ideally using multiline anchors) will make parsing more robust across platforms/editors.

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +126
if (isCommandAvailable("uv")) {
onOutput?.invoke("📦 Installing dependencies via uv...\n")
val result = executeCommand("uv pip install ${dependencies.joinToString(" ")}", workDir.absolutePath, onOutput)
if (result.exitCode == 0) {
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds a shell command by interpolating dependencies directly into a string that is executed via sh -c. Because dependency values come from script metadata, a crafted dependency like "requests"; rm -rf ... could lead to command injection. Avoid passing untrusted content through the shell: execute uv/pip with argument lists (ProcessBuilder args), or install via -r requirements.txt (which you already write) instead of inlining dependencies into the command string.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
* 1. **UV (preferred)** – uses `uv run` for zero-config execution with PEP 723 metadata.
* 2. **pip fallback** – generates a `requirements.txt` and calls `pip install`.
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KDoc says the UV strategy uses uv run with PEP 723 metadata, but the implementation actually runs uv pip install ... (and never calls uv run). Either update the documentation to match what this class does, or adjust the implementation to use the documented UV workflow so future readers/operators aren’t misled.

Suggested change
* 1. **UV (preferred)** – uses `uv run` for zero-config execution with PEP 723 metadata.
* 2. **pip fallback** – generates a `requirements.txt` and calls `pip install`.
* 1. **UV (preferred)** – installs dependencies via `uv pip install` using PEP 723 metadata
* (or a generated requirements file) to resolve the environment.
* 2. **pip fallback** – generates a `requirements.txt` and calls `pip install` directly.

Copilot uses AI. Check for mistakes.

// Try fenced python code block
val fencedPattern = Regex(
"""```python\s*\n(.*?)```""",
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractPythonCode() only matches fenced code blocks that use a \n newline after ```python. Responses that use CRLF (\r\n) or put code immediately after the language tag can fail extraction. Consider making the regex more flexible (e.g., allow `\r?\n` and optional whitespace) so code extraction is reliable across platforms/LLM formatting.

Suggested change
"""```python\s*\n(.*?)```""",
// Matches ```python, optional trailing text, optional CRLF/newline, then captures everything up to the next ``` or end of string
"""```python[^\n\r]*\r?\n?(.*?)(```|$)""",

Copilot uses AI. Check for mistakes.
val deps = PEP723Parser.parseDependencies(script)
assertTrue(deps.isEmpty())
}

Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current tests cover the happy path, but there are no tests for edge cases implied by the implementation changes: (1) parseDependencies() should not pick up a dependencies = [...] list that appears in normal Python code outside the PEP 723 header, and (2) CRLF line endings / single-quoted TOML strings should still be parsed if you intend full PEP 723 compliance. Adding targeted tests here would prevent regressions when tightening the parser.

Suggested change
@Test
fun `parseDependencies should ignore dependencies assignment in normal code`() {
val script = """
import os
dependencies = [
"should-not-be-picked",
]
print("done")
""".trimIndent()
val deps = PEP723Parser.parseDependencies(script)
assertTrue(deps.isEmpty())
}
@Test
fun `parseDependencies should handle CRLF and single-quoted strings`() {
val script =
"# /// script\r\n" +
"# requires-python = '>=3.11'\r\n" +
"# dependencies = [\r\n" +
"# 'requests>=2.28.0',\r\n" +
"# 'pandas>=2.0.0',\r\n" +
"# ]\r\n" +
"# ///\r\n" +
"\r\n" +
"print('ok')\r\n"
val deps = PEP723Parser.parseDependencies(script)
assertEquals(2, deps.size)
assertEquals("requests>=2.28.0", deps[0])
assertEquals("pandas>=2.0.0", deps[1])
}

Copilot uses AI. Check for mistakes.
)

private val DEPENDENCIES_PATTERN = Regex(
"""dependencies\s*=\s*\[(.*?)\]""",
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEPENDENCIES_PATTERN uses \[(.*?)\] to capture the array contents, which will terminate at the first ] character. Valid dependency specifiers can contain ] (extras), e.g. requests[socks]>=2.0, which would cause truncated parsing. Consider parsing the dependencies section line-by-line until the closing # ] line (or use a minimal TOML array parser) instead of a raw bracket regex.

Suggested change
"""dependencies\s*=\s*\[(.*?)\]""",
"""dependencies\s*=\s*\[(.*)]""",

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +104
/**
* Extract only the dependency list from a Python script (convenience method).
*/
fun parseDependencies(pythonContent: String): List<String> {
val depsMatch = DEPENDENCIES_PATTERN.find(pythonContent) ?: return emptyList()
val depsContent = depsMatch.groupValues[1]

return DEP_ITEM_PATTERN.findAll(depsContent)
.map { it.groupValues[1] }
.toList()
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseDependencies() scans the entire input for dependencies = [...], so it can accidentally pick up a Python variable/string named dependencies outside the PEP 723 header. Since callers pass full script text (e.g., executor/agent), this can produce incorrect dependency installs. Consider first extracting the PEP 723 block with PEP723_BLOCK_PATTERN (or reusing parse()) and only searching within that block.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +62
private val REQUIRES_PYTHON_PATTERN = Regex(
"""requires-python\s*=\s*"([^"]*)""""
)

private val DEPENDENCIES_PATTERN = Regex(
"""dependencies\s*=\s*\[(.*?)\]""",
RegexOption.DOT_MATCHES_ALL
)

private val DEP_ITEM_PATTERN = Regex("""["']([^"']+)["']""")

private val AUTODEV_SECTION_PATTERN = Regex(
"""\[tool\.autodev-unit\]\s*\n(.*?)(?=#\s*///|\[tool\.|$)""",
RegexOption.DOT_MATCHES_ALL
)

private val AUTODEV_KV_PATTERN = Regex(
"""#\s*(\S+)\s*=\s*"([^"]*)""""
)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parser currently only recognizes double-quoted TOML strings (e.g., requires-python = "...", # key = "..."). TOML also allows single-quoted strings, so valid PEP 723 metadata may be silently ignored. Consider extending REQUIRES_PYTHON_PATTERN / AUTODEV_KV_PATTERN to support both quote styles (or using a small TOML parser on the de-commented block).

Copilot uses AI. Check for mistakes.
* Strip the PEP 723 metadata block from a Python script, returning only the code body.
*/
fun stripMetadata(pythonContent: String): String {
return PEP723_BLOCK_PATTERN.replace(pythonContent, "").trimStart('\n')
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stripMetadata() only trims leading \n after removing the block; if the script uses CRLF the result can start with a stray \r. Consider trimming both \r and \n (or calling trimStart() without args if that's acceptable here).

Suggested change
return PEP723_BLOCK_PATTERN.replace(pythonContent, "").trimStart('\n')
return PEP723_BLOCK_PATTERN.replace(pythonContent, "").trimStart('\r', '\n')

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +105
// Validate PEP 723 metadata is present; inject if missing
val meta = PEP723Parser.parse(scriptContent)
val finalScript = if (meta.rawBlock == null && input.dependencies.isNotEmpty()) {
PEP723Parser.injectMetadata(
pythonContent = scriptContent,
dependencies = input.dependencies,
requiresPython = input.requiresPython
)
} else {
scriptContent
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says “inject if missing”, but the code only injects a PEP 723 header when the input dependency list is non-empty. If the LLM omits the header and dependencies is empty, the resulting artifact can violate the agent’s own SYSTEM_PROMPT rule (“Every script MUST begin with an inline metadata block”). Consider injecting the header whenever meta.rawBlock == null (even with an empty dependency list) so requires-python is always present.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt (2)

109-117: Avoid double parsing of the final script.

The script is parsed twice: once for dependencies (line 114) and once for requiresPython (line 115). Consider reusing the parsed metadata.

♻️ Proposed optimization
+            val finalMeta = PEP723Parser.parse(finalScript)
+
             ToolResult.AgentResult(
                 success = true,
                 content = finalScript,
                 metadata = mapOf(
                     "type" to "python",
-                    "dependencies" to PEP723Parser.parseDependencies(finalScript).joinToString(","),
-                    "requiresPython" to (PEP723Parser.parse(finalScript).requiresPython ?: ">=3.11")
+                    "dependencies" to finalMeta.dependencies.joinToString(","),
+                    "requiresPython" to (finalMeta.requiresPython ?: ">=3.11")
                 )
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt`
around lines 109 - 117, In PythonArtifactAgent where you build the
ToolResult.AgentResult from finalScript, avoid calling
PEP723Parser.parse(finalScript) twice; instead call
PEP723Parser.parse(finalScript) once into a local val (e.g., parsed =
PEP723Parser.parse(finalScript)) and reuse parsed for both dependencies (or use
PEP723Parser.parseDependencies(finalScript) once if needed) and for
requiresPython (use parsed.requiresPython ?: ">=3.11") before constructing
ToolResult.AgentResult so both metadata entries reuse the same parsed result.

3-5: Remove unused imports.

ArtifactContext, ConversationMessage, and ModelInfo are imported but not used in this file.

♻️ Proposed fix
 package cc.unitmesh.agent.subagent
 
-import cc.unitmesh.agent.artifact.ArtifactContext
-import cc.unitmesh.agent.artifact.ConversationMessage
-import cc.unitmesh.agent.artifact.ModelInfo
 import cc.unitmesh.agent.artifact.PEP723Parser
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt`
around lines 3 - 5, Remove the unused imports ArtifactContext,
ConversationMessage, and ModelInfo from the top of PythonArtifactAgent.kt;
locate the import block that currently imports
cc.unitmesh.agent.artifact.ArtifactContext, ConversationMessage, and ModelInfo
and delete those three import entries so only actually used imports remain, then
run a quick compile or linter to ensure no other references to ArtifactContext,
ConversationMessage, or ModelInfo exist in the file.
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt (1)

155-187: Consider extracting shared command execution utilities.

The executeCommand and CommandResult implementations are nearly identical to those in PythonArtifactExecutor. Consider extracting these into a shared utility class to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt`
around lines 155 - 187, The executeCommand function and CommandResult data class
in PythonArtifactPackager duplicate logic already present in
PythonArtifactExecutor; extract them into a shared utility (e.g., CommandUtils
or ProcessRunner) and replace the local definitions by calling the shared
function; specifically move the executeCommand implementation and the
CommandResult type out of PythonArtifactPackager/PythonArtifactExecutor into the
new shared utility, update both classes to use the shared executeCommand(...)
and CommandResult, and ensure the onOutput callback and error handling behavior
remain identical.
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt (1)

189-191: Consider using assertFalse for negated assertions.

Using assertFalse(result.contains("old-package")) is more idiomatic and provides clearer error messages than assertTrue(!result.contains(...)).

♻️ Suggested improvement
-        // old package should be gone
-        assertTrue(!result.contains("old-package"))
+        // old package should be gone
+        assertFalse(result.contains("old-package"))

Same applies to lines 211-212.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt`
around lines 189 - 191, Replace the negated assertions that use
assertTrue(!result.contains("old-package")) with the idiomatic
assertFalse(result.contains("old-package")) to improve clarity and error
messages; update the occurrence in PEP723ParserTest (the assertion referencing
result.contains("old-package")) and the similar pair at the later occurrence
referenced in lines 211-212 to use assertFalse instead of assertTrue(!...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt`:
- Line 67: Remove emoji/UTF-8 characters from progress messages to satisfy WASM
compatibility: update the onProgress calls that currently use "🐍 Generating
Python script..." and the later call using "✅ ..." to plain ASCII text (e.g.,
"Generating Python script..." and "Generation complete" or similar). Locate the
onProgress invocations inside PythonArtifactAgent.kt (the calls to onProgress
that include emoji) and replace the message strings with equivalent emoji-free
messages, preserving punctuation and intent.

In
`@mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt`:
- Line 125: The command-injection risk comes from building a single shell string
with "uv pip install ${dependencies.joinToString(" ")}" inside
PythonArtifactPackager.kt and passing it to executeCommand; change to an
array/vararg-style invocation of executeCommand so each token is passed as a
separate argument (keep the "uv" and "pip" tokens and append dependencies as
individual args rather than interpolating a single shell string), or if array
execution is not available validate each entry in dependencies against a strict
whitelist regex (e.g., only allow letters, digits, dot, dash, underscore and
version operators) before joining; update the call site referencing
executeCommand and the dependencies.joinToString usage accordingly.
- Around line 144-152: The isCommandAvailable function currently calls the
Unix-only "which" binary; replace it with a cross-platform check: detect Windows
via System.getProperty("os.name") and either call "where" on Windows or better
yet implement a PATH scan by splitting System.getenv("PATH") with
File.pathSeparator and checking for an executable file matching the command (on
Windows also consider PATHEXT extensions) to determine availability; update the
isCommandAvailable suspend function to perform this filesystem-based check on
Dispatchers.IO instead of invoking "which".

---

Nitpick comments:
In
`@mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt`:
- Around line 109-117: In PythonArtifactAgent where you build the
ToolResult.AgentResult from finalScript, avoid calling
PEP723Parser.parse(finalScript) twice; instead call
PEP723Parser.parse(finalScript) once into a local val (e.g., parsed =
PEP723Parser.parse(finalScript)) and reuse parsed for both dependencies (or use
PEP723Parser.parseDependencies(finalScript) once if needed) and for
requiresPython (use parsed.requiresPython ?: ">=3.11") before constructing
ToolResult.AgentResult so both metadata entries reuse the same parsed result.
- Around line 3-5: Remove the unused imports ArtifactContext,
ConversationMessage, and ModelInfo from the top of PythonArtifactAgent.kt;
locate the import block that currently imports
cc.unitmesh.agent.artifact.ArtifactContext, ConversationMessage, and ModelInfo
and delete those three import entries so only actually used imports remain, then
run a quick compile or linter to ensure no other references to ArtifactContext,
ConversationMessage, or ModelInfo exist in the file.

In
`@mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt`:
- Around line 155-187: The executeCommand function and CommandResult data class
in PythonArtifactPackager duplicate logic already present in
PythonArtifactExecutor; extract them into a shared utility (e.g., CommandUtils
or ProcessRunner) and replace the local definitions by calling the shared
function; specifically move the executeCommand implementation and the
CommandResult type out of PythonArtifactPackager/PythonArtifactExecutor into the
new shared utility, update both classes to use the shared executeCommand(...)
and CommandResult, and ensure the onOutput callback and error handling behavior
remain identical.

In `@mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt`:
- Around line 189-191: Replace the negated assertions that use
assertTrue(!result.contains("old-package")) with the idiomatic
assertFalse(result.contains("old-package")) to improve clarity and error
messages; update the occurrence in PEP723ParserTest (the assertion referencing
result.contains("old-package")) and the similar pair at the later occurrence
referenced in lines 211-212 to use assertFalse instead of assertTrue(!...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09102b6f-b29b-4471-8ea9-b5e9bdafff4b

📥 Commits

Reviewing files that changed from the base of the PR and between e986bfd and 529f81d.

📒 Files selected for processing (5)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/artifact/PEP723Parser.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/PythonArtifactAgent.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/PythonArtifactPackager.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/artifact/executor/PythonArtifactExecutor.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/artifact/PEP723ParserTest.kt

@phodal phodal merged commit 381935c into master Mar 8, 2026
1 of 2 checks passed
@phodal phodal deleted the feature/python-artifact-phase2 branch March 8, 2026 01:31
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.

2 participants