Skip to content

feat(v3.13.1-p1): resolve_workspace_dir normalizer + load_workspace_json fallback#178

Merged
Halildeu merged 2 commits intomainfrom
feat/v3.13.1-p1-path-compat
Apr 20, 2026
Merged

feat(v3.13.1-p1): resolve_workspace_dir normalizer + load_workspace_json fallback#178
Halildeu merged 2 commits intomainfrom
feat/v3.13.1-p1-path-compat

Conversation

@Halildeu
Copy link
Copy Markdown
Owner

Summary

  • New public helper ao_kernel.config.resolve_workspace_dir(path) — tolerant normalization between project root and .ao/ workspace dir
  • load_workspace_json() now uses the helper first, so doctor + migrate + workspace.load_config + MCP ao_workspace_status benefit transparently
  • +10 pins in tests/test_resolve_workspace_dir_v3131_p1.py
  • Coverage 85.8% → 86.10%

Why (external AI + Codex REVISE absorb)

Before P1:

  • workspace_root(override=X) returned X as-is (assumed X was .ao/)
  • load_workspace_json(X) expected X/workspace.json
  • doctor --workspace-root . FAILED because ./workspace.json is absent (.ao/workspace.json is where it really lives)
  • doctor --workspace-root .ao passed

After P1:

  • Both doctor --workspace-root . and doctor --workspace-root .ao work (8/8 OK verified manually)
  • Back-compat preserved — operators pointing at .ao/ directly keep working
  • No change to workspace_root() or init_cmd.run() contracts

Deferred (follow-up notes)

  • init_cmd.run(override) WRITE-side same asymmetry (writes under override, not override/.ao/). Fixing would break operators who intentionally pass .ao as override; needs Codex consultation + migration note. Deferred to v3.14+.
  • system-status CLI alias — Codex rejected (name collision with tool_registry kavramı). Docs-only fix already shipped in PR-D1 (docs(v3.13.1-d1): README + CLAUDE.md + examples drift fix #177).

Test plan

  • 10 new pins pass
  • Full suite: 2760 passed, 1 skipped
  • Coverage 86.10% (gate ≥85%)
  • Manual verification: doctor --workspace-root . → 8/8 OK
  • CI 9/9 GREEN
  • Codex post-impl review → MERGE

🤖 Generated with Claude Code

…son fallback

External AI UX review + Codex plan-time REVISE absorb. The CLI
`--workspace-root X` option was asymmetric:

- `workspace_root(override=X)` returned X as-is (expected X to be
  the `.ao/` dir)
- `load_workspace_json(X)` expected workspace.json directly under X
- Result: `doctor --workspace-root .` FAILed (`./workspace.json`
  absent) while `doctor --workspace-root .ao` passed

v3.13.1 P1 (non-breaking Option B): new public helper
`ao_kernel.config.resolve_workspace_dir(path)` that tolerates both
conventions.

- Accepts **project root** (walks into `.ao/`).
- Accepts **workspace dir directly** (`.ao/` itself; legacy).
- Returns input unchanged if neither — downstream caller fails
  closed with the user-supplied path in the error message.

`load_workspace_json()` now calls the helper first, so doctor +
migrate + workspace.load_config + MCP ao_workspace_status all
benefit without explicit rewiring.

+10 pins in tests/test_resolve_workspace_dir_v3131_p1.py covering:
- Project root with `.ao/` resolves to `.ao/`
- `.ao/` dir passthrough
- Neither-shape fail-closed
- String input accepted
- Edge case: direct workspace.json takes precedence over `.ao/`
- Integration: load_workspace_json(project_root) works post-fix
- Back-compat: load_workspace_json(`.ao/`) still works
- Malformed JSON still fails closed
- End-to-end: doctor_cmd._check_workspace_json(project_root) OK

Verified manually: `doctor --workspace-root .` returns 8/8 OK after
`init` (previously 7/1 FAIL on workspace.json valid). Coverage gate
holds at 86.10% (was 85.8% pre-P1).

Follow-up note for v3.14+: `init_cmd.run(override)` has the WRITE
side of the same asymmetry (writes directly under override, not in
`override/.ao/`). Not fixed here because P1 is read-side non-breaking;
the write-side change would be a contract break for operators who
intentionally pass `.ao` dir as override. Deferred with the
`system-status` alias question and the demo_bugfix.py cwd workaround.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

📊 Benchmark Scorecard

Scenario Status Duration Cost (USD) Review Score
governed_bugfix ✅ pass 42ms (▲2.4%) $0.0000 —
governed_review ✅ pass 41ms (▼2.4%) $0.0000 — 0.85 (−)

Baseline: f1798c472a70870b6bc694fc1b21cce39f717192 · HEAD: a843333f16c2d1b8cfbe0d3b020c2649d2e1ac9f · PR: #178 · No regressions.

Codex post-impl BLOCKER absorb: after P1 load_workspace_json was
path-tolerant but migrate's mutation/backup/report paths still used
the un-normalized `ws`, so:

- `migrate --workspace-root <project_root>` reads .ao/workspace.json
  (via the P1 helper) but plans/writes a stray workspace.json at the
  project root and stashes .backup/ there too.

Fix:
- migrate_cmd.py: resolve ws via resolve_workspace_dir() once, then
  use `resolved_ws` for mutation file path, report.workspace_path,
  backup_dir, and legacy comparison.
- New resolve_workspace_dir import in migrate_cmd.

+2 pins in tests/test_resolve_workspace_dir_v3131_p1.py:
- TestMigrateIntegration.test_dry_run_mutation_file_targets_resolved_ao_dir:
  dry-run report shows mutation file under .ao/, workspace_path ends
  with /.ao
- TestMigrateIntegration.test_non_dry_run_writes_to_ao_workspace_json_and_backup_under_ao:
  actual mutation updates .ao/workspace.json, project root stays clean,
  backup path lives under .ao/.backup/

Also hardened the doctor integration pin (Codex iter-1 feedback) to
call the real `doctor_cmd._check_workspace_json()` rather than
re-testing load_workspace_json.

Local: 2762 passed, coverage 86.10%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Halildeu Halildeu merged commit 7a11985 into main Apr 20, 2026
9 checks passed
@Halildeu Halildeu deleted the feat/v3.13.1-p1-path-compat branch April 20, 2026 00:20
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.

1 participant