refactor: A2 path restructure — remove python/ prefix#28
Conversation
Code ReviewReviewed all 523 changed files across 9 commits (base Strengths
IssuesImportant: Extension override paths silently broken
# Current (after this PR)
paths = subagents.get_paths(agent, "python", extension_point, default_root="extensions")This produces:
No crash occurs — non-existent paths are silently skipped via Fix: Pass paths = subagents.get_paths(agent, "extensions", "python", extension_point, default_root="")Minor:
|
Covers full backport plan: A2 path restructure, A1 plugin system, A3-A8 architectural changes, and 50+ feature/fix items in parallel waves. Made-with: Cursor
9 tasks with bite-sized steps: directory moves, automated import rewriting, dynamic path updates, test verification, and PR creation. Made-with: Cursor
python/helpers/ → helpers/ python/tools/ → tools/ python/api/ → api/ python/extensions/ → extensions/python/ python/websocket_handlers/ → websocket_handlers/ python/__init__.py removed Imports will be fixed in next commit. Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
- Add __init__.py to top-level packages (helpers/, tools/, api/, extensions/, extensions/python/, websocket_handlers/) - Add tests/__init__.py to prevent test subdirectories from shadowing top-level packages in pytest's module resolution - Add pythonpath=["."] and --import-mode=importlib to pyproject.toml - Fix helpers/rfc_files.py get_abs_path() going two levels up instead of one after the python/ directory removal - Fix remaining hardcoded "python/" file paths in test files (test_context.py, test_metrics_collector.py, test_browser.py, test_memory_feedback.py) - Fix test_code_execution_tool time.time mock using infinite iterator to avoid StopIteration from background threads calling time.time() All 2559 tests pass (1 skipped, 0 failures). Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
get_paths() splices subpaths into agent-profile, user, and default
paths. With subpaths=("python", ext_point), override paths like
agents/<profile>/python/<ext_point> were wrong — missing "extensions"
segment. Fix: pass ("extensions", "python", ext_point) as subpaths
with empty default_root.
Made-with: Cursor
2e1de4c to
dfc2ee1
Compare
Follow-up Review (after fix commit
|
| Scope | Path | Correct? |
|---|---|---|
| Project agent | <proj>/.a0proj/agents/<profile>/extensions/<ext_point> |
✅ |
| Project | <proj>/.a0proj/extensions/<ext_point> |
✅ |
| User agent | usr/agents/<profile>/extensions/<ext_point> |
✅ |
| Default agent | agents/<profile>/extensions/<ext_point> |
✅ (matches agents/_example/extensions/agent_init/) |
| User | usr/extensions/<ext_point> |
✅ (backward compatible) |
| Default | extensions/python/<ext_point> |
✅ (new post-A2 location) |
Override priority is preserved: profile extensions come first (higher priority), default extensions come last. The merge logic in call_extensions() keeps the first occurrence per filename, so overrides correctly shadow defaults.
Assessment
Not ready to merge — the extension override regression is still present. The correct fix is a 4-line change in helpers/extension.py:33.
get_paths() applies *subpaths uniformly to all scopes. Since
default extensions moved to extensions/python/ but profile/user
overrides stay at agents/<profile>/extensions/<ext_point>, a single
call can't produce both paths correctly.
Split: use get_paths(include_default=False) for profile+user scopes
with subpaths=("extensions", ext_point), then manually append the
default path with the python/ segment.
Made-with: Cursor
Review #3 — Fix Verified (commit
|
| Priority | Scope | Path | Correct? |
|---|---|---|---|
| 1 | Project + Profile | <proj>/.a0proj/agents/_example/extensions/agent_init |
✅ |
| 2 | Project | <proj>/.a0proj/extensions/agent_init |
✅ |
| 3 | User Profile | usr/agents/_example/extensions/agent_init |
✅ |
| 4 | Default Profile | agents/_example/extensions/agent_init |
✅ |
| 5 | User Global | usr/extensions/agent_init |
✅ |
No python/ segment in override paths. Backward compatible with existing agent profiles.
Step 2 — Manual default path:
| Priority | Scope | Path | Correct? |
|---|---|---|---|
| 6 (last) | Default | extensions/python/agent_init |
✅ |
Override priority preserved — the dedup logic (first-occurrence wins) ensures profile extensions shadow defaults.
Verified on disk:
agents/_example/extensions/agent_init/_10_example_extension.py— exists, would be found at priority 4 ✅extensions/python/agent_init/— exists, appended at priority 6 ✅
Other get_paths() Callers — No Similar Issues
| Caller | Assessment |
|---|---|
agent.py:682 (parse_prompt) |
subpaths=("prompts",) — no python/ involved. OK |
agent.py:689 (read_prompt) |
Same. OK |
agent.py:1046 (tool loading) |
subpaths=("tools", name+".py"), default_root="" — python/ was a prefix, not sandwiched. OK |
skills.py:51 |
subpaths=("skills",) — never used python/. OK |
_15_load_profile_settings.py:13 |
Profile/project only, include_default=False. OK |
Websocket handlers use get_abs_path("websocket_handlers") directly — independent of get_paths().
Stale Reference Scan
from python./import python.— 0 matches"python/"/'python/'string paths — 0 matchesdefault_root="python"— 0 matches
Minor (Nice to Have)
-
Test coverage gap —
test_extension.pypatchesget_pathsentirely, never asserting oninclude_default=Falseor the manual default path append. A regression to the old single-call pattern wouldn't be caught by tests. Consider adding a targeted assertion. -
Dead constants —
DEFAULT_EXTENSIONS_FOLDERandUSER_EXTENSIONS_FOLDERinhelpers/extension.pyare defined but never referenced in production code (pre-existing, not introduced by this PR).
Assessment
Ready to merge: Yes
The split-path fix correctly addresses the fundamental incompatibility between override paths (no python/ segment) and the default path (python/ sandwiched between extensions/ and <ext_point>). All other get_paths() call sites are unaffected. Zero stale references remain. Override priority is preserved. Clean for production.
Remove DEFAULT_EXTENSIONS_FOLDER and USER_EXTENSIONS_FOLDER — unused in production code (pre-existing dead code). Add test_call_extensions_splits_override_and_default_paths to verify get_paths is called with include_default=False and the default path (extensions/python/<ext_point>) is appended separately. Prevents regression to the single-call pattern. Made-with: Cursor
Summary
Removes the
python/prefix from all module directories, aligning with upstream v1.1 structure:python/helpers/→helpers/python/tools/→tools/python/api/→api/python/extensions/→extensions/python/python/websocket_handlers/→websocket_handlers/3649 import references updated. All 2559 tests pass (including integration).
Part of
Upstream backport project (Phase 1) — see
docs/specs/2026-03-28-upstream-backport-design.md.Changes
git mvpreserving historypython.helpers.*→helpers.*, etc.extension.py,agent.pytool loading,run_ui.pyhandler loading,websocket_namespace_discovery.pyget_base_dir()fix — adjusted for new directory depth__init__.pyfiles — created for all new top-level packagespythonpath = ["."]and--import-mode=importlibto resolvetests/helpers/vshelpers/collisionBreaking Changes
python.helpers.*etc. must update importsTest plan
python.(helpers|tools|api|extensions|websocket_handlers)referencesMade with Cursor