[spark-compete] fix(cli): name installed + registry modules in 'Unknown module target' install error#737
Conversation
…wn module target' install error
QA write-up — for reviewer eyesTL;DR. Beforecandidate = Path(target)
if candidate.exists():
manifest_path = candidate / "spark.toml"
if not manifest_path.exists():
raise SystemExit(f"{candidate} does not contain spark.toml")
return load_module(candidate)
raise SystemExit(f"Unknown module target: {target}")Aftercandidate = Path(target)
if candidate.exists():
manifest_path = candidate / "spark.toml"
if not manifest_path.exists():
raise SystemExit(f"{candidate} does not contain spark.toml")
return load_module(candidate)
raise SystemExit(_unknown_install_target_message(target, modules, registry))
def _unknown_install_target_message(
target: str, modules: dict[str, Module], registry: dict[str, Any]
) -> str:
installed_names = sorted(modules.keys()) if modules else []
registry_modules = registry.get("modules") if isinstance(registry.get("modules"), dict) else {}
registry_names = sorted(name for name in registry_modules.keys() if name not in modules)
parts = [f"Unknown module target: {target}."]
if installed_names:
parts.append("Installed modules: " + ", ".join(installed_names) + ".")
else:
parts.append("No modules are installed yet.")
if registry_names:
parts.append("Registry-known modules: " + ", ".join(registry_names) + ".")
parts.append(
"Or pass a git URL (https://, git@, .git suffix, or owner/repo shorthand) "
"or a local directory containing a spark.toml manifest."
)
return " ".join(parts)SmokeThe test covers two cases: (Case A) the message names installed and registry-known modules separately without double-listing when one name appears in both; (Case B) the empty-registry edge case prints Scope
Disjoint with sibling PRs
|
|
R24 cleanup: this source PR has received R24 leaderboard credit and is represented in the shipped/adopted release path by spark-cli#754. Thanks for the contribution; closing this PR to keep the review queue clean. |
{
"schema": "spark-compete-hotfix-v1",
"event": "spark-compete-first-event",
"submission_mode": "public_repo_pr",
"submission_target_url": "#737",
"team": {
"name": "SparkThisUp",
"members": ["ValHallaBuilder", "Baz707", "DanFireDash"],
"github_accounts": ["4gjnbzb4zf-sudo"],
"llm_device_holder": "ValHallaBuilder",
"device_holder_github": "4gjnbzb4zf-sudo"
},
"target_repo": {
"id": "vibeforge1111/spark-cli",
"source": "https://github.com/vibeforge1111/spark-cli",
"owner_surface": "spark-cli"
},
"issue": {
"type": "usage_friction",
"severity": "low",
"title": "resolve_install_target's 'Unknown module target' error omits installed/registry modules and the acceptable target shapes",
"actual_behavior": "src/spark_cli/cli.py:4537 raises SystemExit(f"Unknown module target: {target}") when
spark install <name>falls through every branch of resolve_install_target (installed dict, registry definition, git source, local path). The message names only the typed-but-unresolved argument and says nothing about (1) which modules are currently installed, (2) which modules the local registry definition knows about, or (3) the two alternative target shapes the function supports (git URL or local directory containing spark.toml). An operator who typed a near-miss has to leave the shell to runspark listand read the registry file before they can recover.","expected_behavior": "The same SystemExit names the installed modules, the registry-known modules (deduplicated against the installed set), and the two alternative target shapes (git URL with documented prefix list + .git suffix + owner/repo shorthand; local directory with spark.toml manifest). Pure-correct lookup paths (where the typed name DOES resolve via modules/registry/git/local) stay byte-identical -- the new message text only fires when SystemExit was already going to fire. The empty-registry edge case (no installed modules, no registry entries) prints a clear 'No modules are installed yet.' line plus the alternative shapes, so a fresh operator running
spark install typobefore any setup also gets a recovery path.","repro_steps": [
"gh pr checkout ",
"PYTHONPATH=src python -m unittest tests.test_cli.SparkCliTests.test_unknown_install_target_message_lists_installed_and_registry -> ok. Case A asserts the message names installed and registry-known modules separately (no double-listing); Case B asserts the empty-registry edge case prints 'No modules are installed yet.' and does NOT print the 'Registry-known modules:' line.",
"Manual: PYTHONPATH=src python -c "from spark_cli.cli import _unknown_install_target_message; print(_unknown_install_target_message('typo', {'spark-cli': 1}, {'modules': {'spark-character': {}}}))" -> 'Unknown module target: typo. Installed modules: spark-cli. Registry-known modules: spark-character. Or pass a git URL (https://, git@, .git suffix, or owner/repo shorthand) or a local directory containing a spark.toml manifest.'",
"Pure-hit paths unchanged: resolve_install_target still returns the correct Module when the target resolves via modules dict (L4508), registry definition (L4511-L4521), git source (L4527-L4530), or local manifest directory (L4531-L4536). The new message text only fires when the function was already going to SystemExit."
],
"affected_workflow": "spark install -- when the operator's typed target falls through every branch, the actionable-error fallback now tells them which modules they can install by short name (installed + registry) and which two alternative shapes the function accepts (git URL or local manifest path) without leaving the shell."
},
"evidence": {
"safe_links_only": true,
"before_after_proof": "One SystemExit site at src/spark_cli/cli.py:4537. Before: raise SystemExit(f"Unknown module target: {target}"). After: raise SystemExit(_unknown_install_target_message(target, modules, registry)) where the helper assembles the message from in-scope data: installed module names (sorted), registry-known names (deduplicated against installed, sorted), an empty-state line, and a one-line description of the two alternative target shapes. Diff: 1 file modified for the fix (22 changed lines around resolve_install_target and a new _unknown_install_target_message helper), 1 test file (37 insertions in tests/test_cli.py for two regression cases). The pure-hit return paths at L4508/L4511-L4521/L4527-L4530/L4531-L4536 are byte-identical.",
"links": ["https://github.com//pull/737"],
"forbidden": ["pdf","zip","exe","unknown downloads","shortened links","archives","binaries","tokens","browser cookies","wallet material","raw logs","raw conversations","raw memory","raw patches","private repo maps","private scoring details"]
},
"proposed_fix": {
"approach": "Extract the SystemExit message into a small _unknown_install_target_message(target, modules, registry) helper next to resolve_install_target. The helper assembles the message from in-scope data: the installed module names (sorted, from the
modulesdict already in scope), the registry-known names (sorted, fromregistry.get('modules')already in scope at L4510, deduplicated against the installed set), an empty-state line when the registry is empty, and a one-line description of the two alternative target shapes (git URL with the documented prefix list + .git suffix + owner/repo shorthand; local directory containing a spark.toml manifest). The fix is additive, narrowly scoped to the message text, and changes nothing on the four pure-hit return paths.","files_expected": ["src/spark_cli/cli.py", "tests/test_cli.py"],
"tests_or_smoke": "PYTHONPATH=src python -m unittest tests.test_cli.SparkCliTests.test_unknown_install_target_message_lists_installed_and_registry -> ok. The new test asserts (Case A) the message names installed and registry-known modules separately without double-listing when one name appears in both, and (Case B) the empty-registry edge case prints 'No modules are installed yet.' and does NOT print the 'Registry-known modules:' line. Pre-existing tests that exercise resolve_install_target's pure-hit paths are unaffected -- the helper only changes the SystemExit message text."
},
"pr": {
"branch": "sentinel/actionable-error/unknown-install-target-listing",
"title_prefix": "[spark-compete]",
"author_github": "4gjnbzb4zf-sudo",
"body_must_include": ["packet","team","pr_author","repo","actual_behavior","expected_behavior","repro_steps","before_after_proof","tests_or_smoke","duplicate_notes","risk_notes","review_claim"],
"url": "#737"
},
"review_claim": {
"impact_claim": "low",
"evidence_types": ["redacted_terminal_excerpt"],
"duplicate_notes": "Pre-flight
gh pr list --repo vibeforge1111/spark-cli --search 'resolve_install_target OR Unknown module target' --state allreturned a cluster of sibling-named PRs that are scope-disjoint from this one. PR #511 modifies resolve_installed_target_modules + resolve_start_modules (theUnknown installed modulemessage on thespark logs/spark startpath -- different function name, different surface). PR #516 names known bundles in theUnknown bundlemessage (different surface). PR #518 lists valid choices in setup-wizard 'Unknown X' prompts (different surface). PR #347 validates registry module names against path traversal (registry write path, not the install resolve path). PR #277 catches load_module crashes on missing/malformed spark.toml (different concern). None of those PRs touch resolve_install_target's SystemExit message at L4537. This entry extends the sibling actionable-error pattern to the install resolve-target fallback that no other PR touches.","risk_notes": "Local scope: one file changed for the fix, plus one regression test with two cases. The four pure-hit return paths in resolve_install_target are byte-identical. The new helper is a pure-text assembler with no I/O and no side effects -- it reads only the in-scope
modulesdict and theregistrydefinition that the caller already loaded at L4510. No new module-level constant, no new import.","review_state_requested": "pr_review"
}
}