Skip to content

Release optimization followups#13

Merged
xhwSkhizein merged 12 commits intomainfrom
release-optimization-followups
Apr 14, 2026
Merged

Release optimization followups#13
xhwSkhizein merged 12 commits intomainfrom
release-optimization-followups

Conversation

@xhwSkhizein
Copy link
Copy Markdown
Owner

@xhwSkhizein xhwSkhizein commented Apr 14, 2026

Summary

  • package and align the new Browser CLI delivery skill stack for install-skills
  • normalize daemon numeric parameter validation so malformed input returns INVALID_INPUT instead of INTERNAL_ERROR
  • unify automation manifest round-trip semantics across publish/import/export/inspect/persistence, including runtime.log_level persistence
  • add uninstall and cleanup documentation, and link it from the primary install docs

Validation

  • ./scripts/check.sh
  • uv run pytest tests/unit/test_daemon_app_validation.py -v
  • uv run pytest tests/unit/test_automation_projections.py tests/unit/test_task_runtime_automation.py tests/unit/test_automation_publish.py tests/unit/test_automation_api.py tests/unit/test_automation_commands.py -v
  • uv run pytest tests/unit/test_repo_text_contracts.py::test_uninstall_doc_is_linked_from_primary_install_docs -v

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --target flag to install-skills command for custom skill installation directories
    • Browser CLI skills are now packaged with the distribution for reliable installation
    • Added comprehensive uninstall guide (docs/uninstall.md) with cleanup instructions
  • Documentation

    • New uninstall documentation linked from README and installation guides
    • Updated skill documentation structure and references
    • CI improvements for release artifact verification
  • Improvements

    • Enhanced error handling and validation messages in daemon operations
    • Better semantic consistency across automation configuration workflows

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive Browser CLI packaging and skill system, adding three-skill delivery orchestration, automation manifest projection/roundtrip semantics, packaged skill distribution via wheels, updated CI/release validation, daemon parameter normalization, and uninstall documentation with supporting tests and design specs.

Changes

Cohort / File(s) Summary
CI/Release Workflows
.github/workflows/ci.yml, .github/workflows/release.yml
Added packaging-smoke and release artifact validation jobs that build wheels, verify packaged skills exist, and run browser-cli install-skills --dry-run smoke tests before publishing.
Automation Projections Architecture
src/browser_cli/automation/projections.py, src/browser_cli/automation/models.py, src/browser_cli/automation/publisher.py, src/browser_cli/commands/automation.py, src/browser_cli/automation/api/server.py
Introduced new centralized projections.py module with six public functions for semantic round-trip conversion between AutomationManifest, PersistedAutomationDefinition, TOML/config payloads, and snapshot manifests. Updated dependent modules to delegate to projections instead of local helpers. Added log_level field to persisted definitions.
Automation Persistence
src/browser_cli/automation/persistence/store.py
Added log_level column to automations SQLite table with schema migration and row mapping logic.
Install-Skills Refactoring
src/browser_cli/commands/install_skills.py, src/browser_cli/cli/main.py, scripts/guards/architecture.py
Replaced filesystem/git-heuristic skill discovery with importlib.resources-based packaging lookup against fixed whitelist. Added --target CLI flag, new PackagedSkill model, and transactional per-skill installation. Updated architecture guard to recognize packaged_skills boundary.
Packaged Skills Assets
src/browser_cli/packaged_skills/..., pyproject.toml
Created browser_cli.packaged_skills package containing three bundled skill SKILL.md files (browser-cli-delivery, browser-cli-explore, browser-cli-converge). Updated setuptools configuration to include */SKILL.md in wheel distributions.
Skill Documentation (Repository)
skills/browser-cli-delivery/SKILL.md, skills/browser-cli-explore/SKILL.md, skills/browser-cli-converge/SKILL.md, skills/browser-cli-explore-delivery/*
Replaced legacy single browser-cli-explore-delivery skill with three-skill system: delivery (orchestrator), explore (metadata capture), converge (execution logic). Removed legacy skill docs and references.
Daemon Parameter Validation
src/browser_cli/daemon/app.py
Added centralized numeric parsing helpers (_require_int, _optional_int, _require_float, _optional_float) and migrated all handler argument parsing to use them, ensuring malformed numeric inputs consistently raise InvalidInputError instead of leaking raw exceptions.
Uninstall Documentation
docs/uninstall.md, docs/installed-with-uv.md, README.md, AGENTS.md
Added comprehensive uninstall guide covering inspection, backup, shutdown, cleanup, and removal workflows. Updated primary docs to link to uninstall guide. Updated AGENTS.md to reflect new skill topology and packaged_skills module ownership.
Design & Implementation Plans
docs/superpowers/specs/*, docs/superpowers/plans/*
Added four new design specs and four implementation plans covering automation manifest semantics, browser CLI delivery skill system, install-skills, daemon validation, and uninstall documentation.
Automation Projection Tests
tests/unit/test_automation_projections.py, tests/unit/test_automation_publish.py, tests/unit/test_automation_api.py, tests/unit/test_automation_commands.py, tests/unit/test_task_runtime_automation.py
Added comprehensive round-trip tests for manifest↔persisted definition conversions, snapshot manifest rendering, config payload generation, and inspect snapshot/live config alignment validation.
Install-Skills Tests
tests/unit/test_install_skills_command.py, tests/unit/test_release_artifacts.py, tests/unit/test_repo_metadata.py, tests/unit/test_cli.py
Added discovery, installation, update, and wheel-contents validation tests. Verify packaged skills are present in distributions and CLI help reflects new --target flag.
Skill Documentation Tests
tests/unit/test_repo_skill_docs.py
Added repository text-contract tests validating expected skill topology, AGENTS.md references, and required contract phrases in each skill SKILL.md file.
Additional Tests
tests/unit/test_daemon_app_validation.py, tests/unit/test_repo_text_contracts.py
Added daemon request validation tests for numeric parsing error handling and uninstall documentation linkage validation across primary docs.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as API Server
    participant Projections as Projections Module
    participant Persistence as Persistence Store
    participant Publisher

    Client->>Server: POST /automations (payload)
    Server->>Projections: payload_to_persisted_definition(payload)
    Projections->>Persistence: create_automation(definition)
    Persistence-->>Projections: automation_id
    Projections-->>Server: PersistedAutomationDefinition
    
    Server->>Server: validate and enable

    Client->>Server: GET /automations/{id}/export
    Server->>Persistence: load_automation(id)
    Persistence-->>Server: PersistedAutomationDefinition
    Server->>Projections: persisted_definition_to_manifest_toml(definition)
    Projections-->>Server: TOML string
    Server-->>Client: TOML manifest

    Client->>Publisher: publish_task_dir(directory)
    Publisher->>Persistence: load_automation(manifest_id)
    Persistence-->>Publisher: PersistedAutomationDefinition
    Publisher->>Projections: manifest_to_snapshot_manifest_toml(manifest)
    Projections-->>Publisher: snapshot TOML
    Publisher->>Publisher: write snapshot manifest
Loading
sequenceDiagram
    participant User
    participant CLI as install-skills CLI
    participant Discovery as Packaged Skills Discovery
    participant Resources as importlib.resources
    participant Installation as Installation Handler
    participant Filesystem as Filesystem

    User->>CLI: install-skills --target /custom/path
    CLI->>CLI: get_skills_target_path('/custom/path')
    CLI->>Discovery: discover_packaged_skills()
    Discovery->>Resources: files('browser_cli.packaged_skills')
    Resources-->>Discovery: packaged skills root
    Discovery->>Discovery: iterate PUBLIC_SKILL_NAMES
    Discovery->>Filesystem: verify SKILL.md exists for each
    Filesystem-->>Discovery: skill validity check
    Discovery-->>CLI: list[PackagedSkill]
    
    CLI->>Installation: install_skills_from_paths(skills, target, dry_run=False)
    
    loop for each skill
        Installation->>Filesystem: create temp directory
        Installation->>Resources: extract skill files as_file()
        Resources-->>Filesystem: staged skill content
        Installation->>Filesystem: remove old skill directory (if exists)
        Installation->>Filesystem: move staged content to target
    end
    
    Installation-->>CLI: list[tuple(skill_name, action)]
    CLI-->>User: "Installed 3 skills"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Three skills now dance in perfect sync,
Explore, converge, and orchestrate the link,
Packaged and shipped in every wheel,
Projections ensure the round-trip's real,
From manifest to persisted, back again—
Browser CLI automation, now pristine! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title accurately summarizes the main changes: packaging the Browser CLI delivery skill stack, validating daemon parameters, unifying automation manifest semantics, and adding uninstall documentation.
Description check ✅ Passed The description provides a clear summary of all major changes and includes validation steps showing the author ran relevant tests, but does not follow the repository's template structure with sections like 'Type of Change', 'Checklist', or 'Architectural Boundaries'.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release-optimization-followups

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.

Copy link
Copy Markdown

@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: 8

🧹 Nitpick comments (6)
tests/unit/test_daemon_app_validation.py (1)

145-162: Add zero-value regression cases for defaulted numeric fields.

Given the new helper migration, it’s worth locking behavior for explicit 0 values (dy, timeout, maybe count) so defaults only apply when fields are missing/blank.

Suggested tests
+def test_scroll_preserves_explicit_zero_dy() -> None:
+    payload = _execute(
+        DaemonRequest(
+            action="scroll",
+            args={"dy": 0},
+            agent_id="agent-a",
+            request_id="req-6",
+        )
+    )
+    assert payload["ok"] is True
+    assert payload["data"]["dy"] == 0
+
+
+def test_wait_network_preserves_explicit_zero_timeout() -> None:
+    payload = _execute(
+        DaemonRequest(
+            action="wait-network",
+            args={"timeout": 0},
+            agent_id="agent-a",
+            request_id="req-7",
+        )
+    )
+    assert payload["ok"] is True
+    assert payload["data"]["timeout_seconds"] == 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_daemon_app_validation.py` around lines 145 - 162, Add
regression tests that verify explicit zero values are preserved rather than
replaced by defaults: extend or add tests around
test_mouse_click_successfully_parses_integer_fields (and similar request
handlers that use the helper) to call _execute with DaemonRequest args providing
"dy": "0", "timeout": "0" and "count": "0" (both as strings and as ints) and
assert payload["data"] contains numeric 0 for those fields; also add cases where
fields are missing/blank to confirm defaults still apply when absent. Ensure you
reference the same helper invocation (_execute) and DaemonRequest construction
so the tests exercise the migrated helper logic.
skills/browser-cli-converge/SKILL.md (1)

1-62: Consider a single source of truth for duplicated SKILL docs.

This file and src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md are currently identical; that’s easy to drift over time. Consider generating the packaged copy from this canonical source during packaging.

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

In `@skills/browser-cli-converge/SKILL.md` around lines 1 - 62, The SKILL.md
content for the browser-cli-converge skill is duplicated (browser-cli-converge
SKILL.md and src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md);
make the first file the canonical source and remove manual duplication by
updating the packaging pipeline to copy or generate
src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md from the canonical
SKILL.md during build/packaging (or replace the packaged file with a
symlink/redirect), and update packaging scripts to assert equality so future
edits only need to change the canonical SKILL.md named "browser-cli-converge".
README.md (1)

262-264: Update the skill docs link to the split skill stack.

The documentation list still references the legacy browser-cli-explore-delivery path. Consider linking the current delivery/explore/converge skill docs for consistency.

Suggested docs update
-- Explore-to-task skill: [`skills/browser-cli-explore-delivery/SKILL.md`](skills/browser-cli-explore-delivery/SKILL.md)
+- Browser CLI delivery skills:
+  - [`skills/browser-cli-delivery/SKILL.md`](skills/browser-cli-delivery/SKILL.md)
+  - [`skills/browser-cli-explore/SKILL.md`](skills/browser-cli-explore/SKILL.md)
+  - [`skills/browser-cli-converge/SKILL.md`](skills/browser-cli-converge/SKILL.md)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 262 - 264, Update the obsolete skill docs link by
replacing the legacy "skills/browser-cli-explore-delivery/SKILL.md" entry with
links to the split skill stack (delivery, explore, converge); specifically edit
the README entry that currently lists "Explore-to-task skill:
[`skills/browser-cli-explore-delivery/SKILL.md`]" and change it to three links
pointing to the current skill docs for delivery, explore, and converge (use the
canonical SKILL.md in each respective skill directory and keep the same list
formatting).
skills/browser-cli-delivery/SKILL.md (1)

26-26: Minor: Consider hyphenating "Browser CLI-based".

Per standard English grammar, compound modifiers before a noun typically use a hyphen.

📝 Optional fix
-- the task is not Browser CLI based
+- the task is not Browser CLI-based
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/browser-cli-delivery/SKILL.md` at line 26, The phrase "the task is not
Browser CLI based" should use a hyphenated compound modifier; update the text in
SKILL.md where that phrase occurs (search for "the task is not Browser CLI
based") to read "the task is not Browser CLI-based" so the compound adjective
"Browser CLI-based" is grammatically correct.
docs/superpowers/plans/2026-04-14-automation-manifest-roundtrip-implementation-plan.md (1)

789-802: Consider parameterized identifiers for defense-in-depth.

The _ensure_column helper uses f-string interpolation for SQL identifiers:

conn.execute(f"PRAGMA table_info({table})")
conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {ddl}")

While these values are hardcoded internal strings (not user input), using string formatting for SQL identifiers is generally discouraged. SQLite doesn't support parameterized identifiers, so this pattern is sometimes unavoidable, but consider adding a comment noting that table, column, and ddl must be trusted internal values.

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

In
`@docs/superpowers/plans/2026-04-14-automation-manifest-roundtrip-implementation-plan.md`
around lines 789 - 802, The _ensure_column function interpolates SQL identifiers
(table, column, ddl) directly into SQL strings which is acceptable only because
these values are internal; add a concise defense-in-depth fix: update the
_ensure_column implementation to validate or sanitize identifiers (e.g., assert
they match a safe regex like r'^[A-Za-z_][A-Za-z0-9_]*$' for table/column)
and/or add a clear comment above _ensure_column stating that table, column, and
ddl must be trusted internal values and must never come from user input;
reference the _ensure_column function and the two conn.execute calls to locate
where to add the validation/comment.
src/browser_cli/commands/install_skills.py (1)

46-49: Consider calling resolve() on the default path for consistency.

get_skills_target_path calls resolve() when an explicit target is provided (line 48) but not for the default ~/.agents/skills path (line 49). For consistency and to handle edge cases with relative symlinks in $HOME, consider resolving the default path as well.

♻️ Optional consistency fix
 def get_skills_target_path(target: str | None) -> Path:
     if target:
         return Path(target).expanduser().resolve()
-    return Path.home() / ".agents" / "skills"
+    return (Path.home() / ".agents" / "skills").resolve()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/browser_cli/commands/install_skills.py` around lines 46 - 49,
get_skills_target_path currently calls resolve() only for explicit targets but
returns an unresolved Path.home() / ".agents" / "skills" for the default; change
the default return in get_skills_target_path to also expanduser() and resolve()
(e.g., resolve Path.home().expanduser()/".agents"/"skills") so both branches
return fully resolved absolute paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/installed-with-uv.md`:
- Around line 112-115: The markdown link in the "Remove Browser CLI" section
currently points to docs/uninstall.md which resolves incorrectly; update the
link target to the same-directory relative path "uninstall.md" (i.e., change the
href string `docs/uninstall.md` to `uninstall.md`) in the installed-with-uv.md
file so the link correctly resolves to the local uninstall document.

In
`@docs/superpowers/plans/2026-04-14-browser-cli-install-skills-implementation-plan.md`:
- Around line 219-222: The plan uses PackagedSkill(...) with a path= keyword but
the actual dataclass PackagedSkill expects source: Traversable | Path; update
the document snippet to use source= instead of path= for entries like
PackagedSkill(name="browser-cli-delivery", source=source_root /
"browser-cli-delivery") so the docs match the implementation in the
PackagedSkill dataclass.

In `@docs/superpowers/specs/2026-04-14-browser-cli-delivery-skills-design.md`:
- Line 5: Replace the hardcoded developer workspace path string
`/home/hongv/workspace/browser-cli` in the design spec with a portable reference
such as `Repo: browser-cli` or remove the path entirely; update the single-line
"Repo: ..." entry in
docs/superpowers/specs/2026-04-14-browser-cli-delivery-skills-design.md so it no
longer contains the developer-specific path.

In `@docs/superpowers/specs/2026-04-14-browser-cli-uninstall-doc-design.md`:
- Line 5: Remove the developer-specific absolute path that appears in the spec
header (the string "/home/hongv/workspace/browser-cli") and replace it with a
neutral, repo-relative identifier or nothing; update the top-of-file header in
the browser-cli uninstall doc (the spec header line) so it contains no
machine/user-specific paths and use a generic repository name like "browser-cli"
or a relative path if context is needed.

In `@src/browser_cli/automation/projections.py`:
- Around line 268-279: The _remap_result_json_path function currently returns
target_artifact_dir / relative even when the computed relative path contains
parent-traversal segments (e.g., '..'), allowing escape from the target artifact
dir; change the logic to detect any '..' segments in the computed relative
(check relative.parts for '..') and if found fall back to returning
target_artifact_dir / source_result_json_path.name instead of joining the unsafe
relative, otherwise continue to return target_artifact_dir / relative; keep the
early None guard and the ValueError fallback behavior unchanged.
- Around line 46-81: In payload_to_persisted_definition, the enabled field
currently uses bool(payload.get("enabled")) which treats non-empty strings like
"false" as True; change the logic to explicitly handle booleans and known string
values: if payload["enabled"] is a bool use it, if it's a str accept
case-insensitive "true"/"false" and convert accordingly, and for other types (or
unknown strings) raise a ValueError so malformed input is rejected rather than
silently enabling automations; update the assignment for enabled in
PersistedAutomationDefinition accordingly.

In `@src/browser_cli/commands/install_skills.py`:
- Around line 84-97: In _install_one_skill change the order so we don't delete
the existing destination before a successful move: keep staging into
TemporaryDirectory as staged (Path tmp_dir / skill.name), then move staged to a
temporary target (e.g., destination.with_suffix or destination + ".tmp") using
shutil.move; only after that move succeeds, remove the old destination with
shutil.rmtree and finally rename/replace the temp target to destination (or use
os.replace for atomic swap). Ensure exceptions still raise OperationFailedError
and that TemporaryDirectory cleanup and any leftover temp targets are handled so
the old directory remains intact if the move fails.

In `@src/browser_cli/daemon/app.py`:
- Around line 476-477: The current use of "or" to apply defaults (e.g., dx =
self._optional_int(request.args, "dx") or 0) will replace valid zeros with the
fallback; instead, change these assignments (dx, dy and the other spots using
self._optional_int/self._optional_float) to check for None explicitly and only
substitute the default when the helper returned None (e.g., value =
self._optional_int(...); dx = value if value is not None else 0). Update the
occurrences referenced (dx, dy and the other similar lines) to follow this
pattern so explicit 0 or 0.0 values are preserved.

---

Nitpick comments:
In
`@docs/superpowers/plans/2026-04-14-automation-manifest-roundtrip-implementation-plan.md`:
- Around line 789-802: The _ensure_column function interpolates SQL identifiers
(table, column, ddl) directly into SQL strings which is acceptable only because
these values are internal; add a concise defense-in-depth fix: update the
_ensure_column implementation to validate or sanitize identifiers (e.g., assert
they match a safe regex like r'^[A-Za-z_][A-Za-z0-9_]*$' for table/column)
and/or add a clear comment above _ensure_column stating that table, column, and
ddl must be trusted internal values and must never come from user input;
reference the _ensure_column function and the two conn.execute calls to locate
where to add the validation/comment.

In `@README.md`:
- Around line 262-264: Update the obsolete skill docs link by replacing the
legacy "skills/browser-cli-explore-delivery/SKILL.md" entry with links to the
split skill stack (delivery, explore, converge); specifically edit the README
entry that currently lists "Explore-to-task skill:
[`skills/browser-cli-explore-delivery/SKILL.md`]" and change it to three links
pointing to the current skill docs for delivery, explore, and converge (use the
canonical SKILL.md in each respective skill directory and keep the same list
formatting).

In `@skills/browser-cli-converge/SKILL.md`:
- Around line 1-62: The SKILL.md content for the browser-cli-converge skill is
duplicated (browser-cli-converge SKILL.md and
src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md); make the first
file the canonical source and remove manual duplication by updating the
packaging pipeline to copy or generate
src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md from the canonical
SKILL.md during build/packaging (or replace the packaged file with a
symlink/redirect), and update packaging scripts to assert equality so future
edits only need to change the canonical SKILL.md named "browser-cli-converge".

In `@skills/browser-cli-delivery/SKILL.md`:
- Line 26: The phrase "the task is not Browser CLI based" should use a
hyphenated compound modifier; update the text in SKILL.md where that phrase
occurs (search for "the task is not Browser CLI based") to read "the task is not
Browser CLI-based" so the compound adjective "Browser CLI-based" is
grammatically correct.

In `@src/browser_cli/commands/install_skills.py`:
- Around line 46-49: get_skills_target_path currently calls resolve() only for
explicit targets but returns an unresolved Path.home() / ".agents" / "skills"
for the default; change the default return in get_skills_target_path to also
expanduser() and resolve() (e.g., resolve
Path.home().expanduser()/".agents"/"skills") so both branches return fully
resolved absolute paths.

In `@tests/unit/test_daemon_app_validation.py`:
- Around line 145-162: Add regression tests that verify explicit zero values are
preserved rather than replaced by defaults: extend or add tests around
test_mouse_click_successfully_parses_integer_fields (and similar request
handlers that use the helper) to call _execute with DaemonRequest args providing
"dy": "0", "timeout": "0" and "count": "0" (both as strings and as ints) and
assert payload["data"] contains numeric 0 for those fields; also add cases where
fields are missing/blank to confirm defaults still apply when absent. Ensure you
reference the same helper invocation (_execute) and DaemonRequest construction
so the tests exercise the migrated helper logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a274e67-e510-4625-aea6-f32e298ca184

📥 Commits

Reviewing files that changed from the base of the PR and between 50f575b and bc81d1f.

📒 Files selected for processing (54)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • AGENTS.md
  • README.md
  • docs/installed-with-uv.md
  • docs/superpowers/plans/2026-04-11-browser-cli-network-response-body-implementation-plan.md
  • docs/superpowers/plans/2026-04-13-browser-cli-task-automation-implementation-plan.md
  • docs/superpowers/plans/2026-04-14-automation-manifest-roundtrip-implementation-plan.md
  • docs/superpowers/plans/2026-04-14-browser-cli-delivery-skills-implementation-plan.md
  • docs/superpowers/plans/2026-04-14-browser-cli-install-skills-implementation-plan.md
  • docs/superpowers/plans/2026-04-14-browser-cli-uninstall-doc-implementation-plan.md
  • docs/superpowers/plans/2026-04-14-daemon-parameter-validation-implementation-plan.md
  • docs/superpowers/specs/2026-04-13-browser-cli-task-automation-design.md
  • docs/superpowers/specs/2026-04-14-automation-manifest-roundtrip-design.md
  • docs/superpowers/specs/2026-04-14-browser-cli-delivery-skills-design.md
  • docs/superpowers/specs/2026-04-14-browser-cli-uninstall-doc-design.md
  • docs/superpowers/specs/2026-04-14-daemon-parameter-validation-design.md
  • docs/superpowers/specs/2026-04-14-install-skills-design.md
  • docs/uninstall.md
  • pyproject.toml
  • scripts/guards/architecture.py
  • skills/browser-cli-converge/SKILL.md
  • skills/browser-cli-delivery/SKILL.md
  • skills/browser-cli-explore-delivery/SKILL.md
  • skills/browser-cli-explore-delivery/agents/openai.yaml
  • skills/browser-cli-explore-delivery/references/preflight-and-runtime.md
  • skills/browser-cli-explore-delivery/references/task-input-design.md
  • skills/browser-cli-explore-delivery/references/task-modes.md
  • skills/browser-cli-explore/SKILL.md
  • src/browser_cli/automation/api/server.py
  • src/browser_cli/automation/models.py
  • src/browser_cli/automation/persistence/store.py
  • src/browser_cli/automation/projections.py
  • src/browser_cli/automation/publisher.py
  • src/browser_cli/cli/main.py
  • src/browser_cli/commands/automation.py
  • src/browser_cli/commands/install_skills.py
  • src/browser_cli/daemon/app.py
  • src/browser_cli/packaged_skills/__init__.py
  • src/browser_cli/packaged_skills/browser-cli-converge/SKILL.md
  • src/browser_cli/packaged_skills/browser-cli-delivery/SKILL.md
  • src/browser_cli/packaged_skills/browser-cli-explore/SKILL.md
  • tests/unit/test_automation_api.py
  • tests/unit/test_automation_commands.py
  • tests/unit/test_automation_projections.py
  • tests/unit/test_automation_publish.py
  • tests/unit/test_cli.py
  • tests/unit/test_daemon_app_validation.py
  • tests/unit/test_install_skills_command.py
  • tests/unit/test_release_artifacts.py
  • tests/unit/test_repo_metadata.py
  • tests/unit/test_repo_skill_docs.py
  • tests/unit/test_repo_text_contracts.py
  • tests/unit/test_task_runtime_automation.py
💤 Files with no reviewable changes (5)
  • skills/browser-cli-explore-delivery/agents/openai.yaml
  • skills/browser-cli-explore-delivery/references/task-modes.md
  • skills/browser-cli-explore-delivery/references/task-input-design.md
  • skills/browser-cli-explore-delivery/references/preflight-and-runtime.md
  • skills/browser-cli-explore-delivery/SKILL.md

Comment thread docs/installed-with-uv.md
Comment on lines +112 to +115
## Remove Browser CLI

To remove Browser CLI later, including Browser CLI home data and local cleanup
steps for maintainers, see [`docs/uninstall.md`](docs/uninstall.md).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the relative uninstall link target.

Line 115 currently links to docs/uninstall.md from inside docs/installed-with-uv.md, which resolves as docs/docs/uninstall.md. Use a same-directory relative link.

Suggested fix
-To remove Browser CLI later, including Browser CLI home data and local cleanup
-steps for maintainers, see [`docs/uninstall.md`](docs/uninstall.md).
+To remove Browser CLI later, including Browser CLI home data and local cleanup
+steps for maintainers, see [`docs/uninstall.md`](uninstall.md).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Remove Browser CLI
To remove Browser CLI later, including Browser CLI home data and local cleanup
steps for maintainers, see [`docs/uninstall.md`](docs/uninstall.md).
## Remove Browser CLI
To remove Browser CLI later, including Browser CLI home data and local cleanup
steps for maintainers, see [`docs/uninstall.md`](uninstall.md).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/installed-with-uv.md` around lines 112 - 115, The markdown link in the
"Remove Browser CLI" section currently points to docs/uninstall.md which
resolves incorrectly; update the link target to the same-directory relative path
"uninstall.md" (i.e., change the href string `docs/uninstall.md` to
`uninstall.md`) in the installed-with-uv.md file so the link correctly resolves
to the local uninstall document.

Comment on lines +219 to +222
install_skills_module.PackagedSkill(name="browser-cli-delivery", path=source_root / "browser-cli-delivery"),
install_skills_module.PackagedSkill(name="browser-cli-explore", path=source_root / "browser-cli-explore"),
install_skills_module.PackagedSkill(name="browser-cli-converge", path=source_root / "browser-cli-converge"),
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Documentation drift: PackagedSkill attribute name mismatch.

The plan's test code uses path= attribute:

install_skills_module.PackagedSkill(name="browser-cli-delivery", path=source_root / "browser-cli-delivery"),

However, the actual implementation (per src/browser_cli/commands/install_skills.py lines 22-25) uses source=:

`@dataclass`(frozen=True, slots=True)
class PackagedSkill:
    name: str
    source: Traversable | Path

Since this plan document serves as a reference, consider updating the attribute name for consistency with the shipped code.

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

In
`@docs/superpowers/plans/2026-04-14-browser-cli-install-skills-implementation-plan.md`
around lines 219 - 222, The plan uses PackagedSkill(...) with a path= keyword
but the actual dataclass PackagedSkill expects source: Traversable | Path;
update the document snippet to use source= instead of path= for entries like
PackagedSkill(name="browser-cli-delivery", source=source_root /
"browser-cli-delivery") so the docs match the implementation in the
PackagedSkill dataclass.

Comment thread docs/superpowers/specs/2026-04-14-browser-cli-delivery-skills-design.md Outdated

Date: 2026-04-14
Status: Drafted for review
Repo: `/home/hongv/workspace/browser-cli`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove developer-specific absolute path from the spec header.

/home/hongv/workspace/browser-cli is machine/user-specific and shouldn’t be committed in shared docs.

Proposed fix
-Repo: `/home/hongv/workspace/browser-cli`
+Repo: `browser-cli`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Repo: `/home/hongv/workspace/browser-cli`
Repo: `browser-cli`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-04-14-browser-cli-uninstall-doc-design.md` at
line 5, Remove the developer-specific absolute path that appears in the spec
header (the string "/home/hongv/workspace/browser-cli") and replace it with a
neutral, repo-relative identifier or nothing; update the top-of-file header in
the browser-cli uninstall doc (the spec header line) so it contains no
machine/user-specific paths and use a generic repository name like "browser-cli"
or a relative path if context is needed.

Comment on lines +46 to +81
def payload_to_persisted_definition(payload: dict[str, Any]) -> PersistedAutomationDefinition:
automation_id = str(payload.get("id") or "").strip()
if not automation_id:
raise ValueError("Automation id is required.")
output_dir_raw = str(payload.get("output_dir") or "").strip()
result_json_raw = str(payload.get("result_json_path") or "").strip()
return PersistedAutomationDefinition(
id=automation_id,
name=str(payload.get("name") or automation_id),
description=str(payload.get("description") or ""),
version=str(payload.get("version") or "0.1.0"),
task_path=Path(str(payload.get("task_path") or "")),
task_meta_path=Path(str(payload.get("task_meta_path") or "")),
entrypoint=str(payload.get("entrypoint") or "run"),
enabled=bool(payload.get("enabled")),
definition_status=str(payload.get("definition_status") or "valid"),
definition_error=str(payload.get("definition_error"))
if payload.get("definition_error")
else None,
schedule_kind=str(payload.get("schedule_kind") or "manual"),
schedule_payload=dict(payload.get("schedule_payload") or {}),
timezone=str(payload.get("timezone") or "UTC"),
output_dir=Path(output_dir_raw) if output_dir_raw else Path(),
result_json_path=Path(result_json_raw) if result_json_raw else None,
stdout_mode=str(payload.get("stdout_mode") or "json"),
input_overrides=dict(payload.get("input_overrides") or {}),
before_run_hooks=tuple(payload.get("before_run_hooks") or []),
after_success_hooks=tuple(payload.get("after_success_hooks") or []),
after_failure_hooks=tuple(payload.get("after_failure_hooks") or []),
retry_attempts=int(payload.get("retry_attempts") or 0),
retry_backoff_seconds=int(payload.get("retry_backoff_seconds") or 0),
timeout_seconds=float(payload["timeout_seconds"])
if payload.get("timeout_seconds") is not None
else None,
log_level=str(payload.get("log_level") or "info"),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix boolean coercion for enabled to avoid silent misconfiguration.

Line 60 currently uses bool(payload.get("enabled")); non-empty strings like "false" evaluate to True, which can silently enable automations instead of rejecting malformed input.

Proposed fix
 def payload_to_persisted_definition(payload: dict[str, Any]) -> PersistedAutomationDefinition:
     automation_id = str(payload.get("id") or "").strip()
     if not automation_id:
         raise ValueError("Automation id is required.")
+    enabled_raw = payload.get("enabled")
+    if isinstance(enabled_raw, bool):
+        enabled = enabled_raw
+    elif enabled_raw is None:
+        enabled = False
+    elif isinstance(enabled_raw, str):
+        normalized = enabled_raw.strip().lower()
+        if normalized in {"true", "1", "yes", "on"}:
+            enabled = True
+        elif normalized in {"false", "0", "no", "off", ""}:
+            enabled = False
+        else:
+            raise ValueError("enabled must be a boolean.")
+    else:
+        raise ValueError("enabled must be a boolean.")
     output_dir_raw = str(payload.get("output_dir") or "").strip()
     result_json_raw = str(payload.get("result_json_path") or "").strip()
     return PersistedAutomationDefinition(
         id=automation_id,
@@
-        enabled=bool(payload.get("enabled")),
+        enabled=enabled,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/browser_cli/automation/projections.py` around lines 46 - 81, In
payload_to_persisted_definition, the enabled field currently uses
bool(payload.get("enabled")) which treats non-empty strings like "false" as
True; change the logic to explicitly handle booleans and known string values: if
payload["enabled"] is a bool use it, if it's a str accept case-insensitive
"true"/"false" and convert accordingly, and for other types (or unknown strings)
raise a ValueError so malformed input is rejected rather than silently enabling
automations; update the assignment for enabled in PersistedAutomationDefinition
accordingly.

Comment on lines +268 to +279
def _remap_result_json_path(
source_artifact_dir: Path,
source_result_json_path: Path | None,
target_artifact_dir: Path,
) -> Path | None:
if source_result_json_path is None:
return None
try:
relative = source_result_json_path.relative_to(source_artifact_dir)
except ValueError:
return target_artifact_dir / source_result_json_path.name
return target_artifact_dir / relative
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against .. path segments when remapping snapshot result_json_path.

Lines 276-279 can carry parent traversal into the target path if the source is lexically under the artifact dir (e.g., artifacts/../out.json), which can escape snapshot artifact boundaries.

Proposed fix
 def _remap_result_json_path(
     source_artifact_dir: Path,
     source_result_json_path: Path | None,
     target_artifact_dir: Path,
 ) -> Path | None:
@@
     try:
         relative = source_result_json_path.relative_to(source_artifact_dir)
     except ValueError:
         return target_artifact_dir / source_result_json_path.name
+    if ".." in relative.parts:
+        return target_artifact_dir / source_result_json_path.name
     return target_artifact_dir / relative
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/browser_cli/automation/projections.py` around lines 268 - 279, The
_remap_result_json_path function currently returns target_artifact_dir /
relative even when the computed relative path contains parent-traversal segments
(e.g., '..'), allowing escape from the target artifact dir; change the logic to
detect any '..' segments in the computed relative (check relative.parts for
'..') and if found fall back to returning target_artifact_dir /
source_result_json_path.name instead of joining the unsafe relative, otherwise
continue to return target_artifact_dir / relative; keep the early None guard and
the ValueError fallback behavior unchanged.

Comment thread src/browser_cli/commands/install_skills.py
Comment on lines +476 to +477
dx = self._optional_int(request.args, "dx") or 0
dy = self._optional_int(request.args, "dy") or 700
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

or fallbacks are overriding explicit numeric 0 values.

Using self._optional_* (...) or <default> turns valid 0 into the default. Example: dy=0 becomes 700, timeout=0 becomes 30.0.

Proposed fix (preserve explicit zero via is None)
-        dx = self._optional_int(request.args, "dx") or 0
-        dy = self._optional_int(request.args, "dy") or 700
+        dx = self._optional_int(request.args, "dx")
+        if dx is None:
+            dx = 0
+        dy = self._optional_int(request.args, "dy")
+        if dy is None:
+            dy = 700
@@
-        count = self._optional_int(request.args, "count") or 1
+        count = self._optional_int(request.args, "count")
+        if count is None:
+            count = 1
@@
-        timeout_seconds = self._optional_float(request.args, "timeout") or 30.0
+        timeout_seconds = self._optional_float(request.args, "timeout")
+        if timeout_seconds is None:
+            timeout_seconds = 30.0
@@
-        timeout_seconds = self._optional_float(request.args, "timeout_seconds") or 30.0
+        timeout_seconds = self._optional_float(request.args, "timeout_seconds")
+        if timeout_seconds is None:
+            timeout_seconds = 30.0
@@
-        timeout = self._optional_float(request.args, "timeout") or 5.0
+        timeout = self._optional_float(request.args, "timeout")
+        if timeout is None:
+            timeout = 5.0
@@
-        timeout = self._optional_float(request.args, "timeout") or 5.0
+        timeout = self._optional_float(request.args, "timeout")
+        if timeout is None:
+            timeout = 5.0

Also applies to: 485-485, 573-573, 624-624, 735-735, 746-746

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

In `@src/browser_cli/daemon/app.py` around lines 476 - 477, The current use of
"or" to apply defaults (e.g., dx = self._optional_int(request.args, "dx") or 0)
will replace valid zeros with the fallback; instead, change these assignments
(dx, dy and the other spots using self._optional_int/self._optional_float) to
check for None explicitly and only substitute the default when the helper
returned None (e.g., value = self._optional_int(...); dx = value if value is not
None else 0). Update the occurrences referenced (dx, dy and the other similar
lines) to follow this pattern so explicit 0 or 0.0 values are preserved.

@xhwSkhizein xhwSkhizein merged commit fd40993 into main Apr 14, 2026
8 checks passed
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