Skip to content

feat: add bugfix spec workflow and viewer enhancements#85

Merged
maxritter merged 4 commits intomainfrom
dev
Feb 24, 2026
Merged

feat: add bugfix spec workflow and viewer enhancements#85
maxritter merged 4 commits intomainfrom
dev

Conversation

@maxritter
Copy link
Owner

@maxritter maxritter commented Feb 24, 2026

Summary

  • Add /spec-bugfix-plan command for structured bug fix planning with root cause analysis, behavior contracts, and fixed task structure
  • Spec dispatcher (/spec) auto-detects task type (feature vs bugfix) and routes to the appropriate planning flow
  • Console viewer: parse Type: field from plan files, show bugfix badges in dashboard and spec views, extract SpecHeaderCard component
  • Installer: add property-based testing tools (Hypothesis, Schemathesis) to dependency step
  • Update workflow rules (task-and-workflow.md) to document bugfix spec dispatch and verification agent behavior
  • Documentation and README updated to reflect dual-mode spec system

Test plan

  • CI passes (Python unit tests, installer tests, console tests, typecheck)
  • CodeRabbit review passes
  • Bugfix spec type detected correctly in plan parser
  • Viewer displays bugfix badge on plan cards

Summary by CodeRabbit

  • New Features

    • Auto-detection of feature vs. bug-fix classification for plans
    • Visual spec type indicators (Feature/Bugfix badges) in dashboard and plan views
    • New spec header card with progress, tasks, and metadata display
    • Property-based testing tools offered in installer
  • Documentation

    • Updated workflow descriptions for Quick Mode and Spec-Driven Mode
    • Expanded guidance and examples for bug-fix handling and spec taxonomy
  • Tests

    • New tests verifying spec type parsing and installer PBT behavior

…cements

Introduce /spec-bugfix-plan command for structured bug fix planning with
root cause analysis. Spec dispatcher now auto-detects task type (feature vs
bugfix) and routes accordingly. Console viewer shows spec type badges,
extracted SpecHeaderCard component, and updated plan reader to parse Type
field. Installer adds property-based testing tools. Documentation and
workflow rules updated to reflect the dual-mode spec system.
@vercel
Copy link

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
claude-pilot Ignored Ignored Preview Feb 24, 2026 10:05am
pilot-shell Ignored Ignored Preview Feb 24, 2026 10:05am

Request Review

@claude
Copy link

claude bot commented Feb 24, 2026

Claude finished @maxritter's task in 3m 19s —— View job


Code Review: feat: add bugfix spec workflow and viewer enhancements

  • Gather context (CLAUDE.md, repo structure)
  • Review pilot commands (spec.md, spec-bugfix-plan.md, spec-plan.md)
  • Review console changes (planFileReader, SpecHeaderCard, Spec/index.tsx)
  • Review installer changes (dependencies.py)
  • Review rules and documentation changes
  • Post final review

Overview

This PR adds a bugfix-specific spec planning flow alongside the existing feature flow, wires Type: detection through the viewer, extracts a SpecHeaderCard component, and adds property-based testing tools to the installer. The architecture is coherent and the implementation is consistent across all touch points. A few issues worth addressing:


Issues

Bug: install_pbt_tools silently swallows failures

installer/steps/dependencies.py:559-571

def install_pbt_tools() -> bool:
    if not command_exists("hypothesis"):
        _run_bash_with_retry("uv tool install hypothesis")  # return value ignored

    if not _is_fast_check_installed():
        _run_bash_with_retry(npm_global_cmd("npm install -g fast-check"))  # return value ignored

    return True  # always True, regardless of success

Both _run_bash_with_retry return values are discarded. The function always returns True, so a failed install is reported as success to the UI spinner. This differs from every other installer in the file (e.g., install_python_tools, install_typescript_lsp, etc., which all return False on failure). The docstring says "best-effort: failure does not block installation," which is fine — but the established pattern for best-effort is to show a warning, not silently succeed. Compare to _install_playwright_cli_with_ui which returns False and shows a warning.

Fix this →


Bug: command_exists("hypothesis") skip-check is unreliable

installer/steps/dependencies.py:565

hypothesis is a Python testing library. When installed via uv tool install hypothesis, it may or may not register a hypothesis CLI depending on the installed version and entry points. Unlike ruff or basedpyright (which are CLI tools with known entry points), hypothesis exposes a CLI only in some versions (the hypothesis write / hypothesis fuzz subcommands exist but aren't universally available). If hypothesis doesn't register a CLI command, command_exists("hypothesis") will always return False, causing a reinstall attempt on every install run.

A more reliable check (consistent with _is_fast_check_installed) would be to check the uv tool list:

def _is_hypothesis_installed() -> bool:
    try:
        result = subprocess.run(
            ["uv", "tool", "list"],
            capture_output=True, text=True, timeout=10,
        )
        return result.returncode == 0 and "hypothesis" in result.stdout
    except Exception:
        return False

Fix this →


Minor: ruff check template uses wrong invocation form

pilot/commands/spec-bugfix-plan.md:404

- `ruff check . --fix` (Python) or equivalent for the project

Everywhere else in the codebase (including spec-plan.md and task templates), Python tooling is invoked via uv run (e.g., uv run pytest -q, uv run basedpyright). The direct ruff check invocation will fail in environments where ruff is only available inside the uv-managed environment. Should be uv run ruff check . --fix.

Fix this →


Observation: spec-bugfix-plan.md references spec-plan.md for worktree creation

pilot/commands/spec-bugfix-plan.md:49

Follow same pattern as spec-plan.md Step 1.1.2.

The worktree creation logic is cross-referenced rather than inlined. This creates a fragile doc dependency: if spec-plan.md Step 1.1.2 changes, spec-bugfix-plan.md silently drifts. Given these are prompts consumed at runtime by a model that may not fetch both files in sequence, inlining the worktree creation steps (or extracting them to a shared reference) would be safer.


Observation: PlanInfo interface duplicated in five places

PlanInfo (or a structural equivalent) is now defined locally in:

  • planFileReader.ts (exported)
  • Spec/index.tsx
  • useStats.ts
  • Dashboard/PlanStatus.tsx
  • SpecHeaderCard.tsx (as SpecMeta)

This PR consistently adds specType to all five, which is good. But the duplication means future additions require the same five-point update. Pre-existing issue, but the pattern is now established with one more field.


Observation: plan-stats-endpoint.test.ts tests source strings, not behavior

console/tests/worker/plan-stats-endpoint.test.ts:57-61

it("should parse specType from plan files", () => {
  expect(readerSource).toContain("specType");
  expect(readerSource).toContain('"Bugfix"');
  expect(readerSource).toContain(/^Type:\s*(\w+)/m.source);
});

This checks that certain strings appear in the source file, not that parsing actually works. No test exercises parsePlanContent with a plan containing Type: Bugfix and asserts specType === "Bugfix", or a plan without a Type: field and asserts specType === undefined. The existing test infrastructure for parsePlanContent in the file could be extended to cover the new branch. Low priority given the parser logic is straightforward, but worth noting.


What's Working Well

  • parsePlanContent in planFileReader.ts handles the new Type: field cleanly (lines 55-57). Only sets specType for "Bugfix" (intentional — undefined for Feature means no badge shown, which is correct). Backward-compatible with existing plans missing the Type: header.
  • SpecHeaderCard extraction is clean. The component is self-contained, props are clearly typed, and it reduces Spec/index.tsx from 325 lines to 190. The metadata row in SpecHeaderCard (lines 112-159) correctly renders the bugfix badge only when specType === "Bugfix".
  • Dispatcher in spec.md correctly implements thin routing — parses → asks → detects type → invokes Skill. The dispatch table (Section 0.2) is complete and backward-compatible (Type: absent → Feature).
  • spec-bugfix-plan.md methodology is sound. The Behavior Contract (C ⟹ P / ¬C ⟹ unchanged) and the fixed 4-task TDD structure (RED → GREEN → fix → verify) are well-suited for bug fixes and appropriately tighter than the feature planning flow.
  • task-and-workflow.md update correctly documents that spec-bugfix-plan skips the plan-verifier/plan-challenger agents, with the rationale (fixed task structure + user approval gate is sufficient).

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb3d655 and 0b07e3f.

⛔ Files ignored due to path filters (1)
  • launcher/tests/unit/test_auth.py is excluded by !launcher/**
📒 Files selected for processing (1)
  • installer/tests/unit/steps/test_dependencies_playwright.py

Walkthrough

Adds optional specType ("Feature" | "Bugfix") to plan metadata, parses Type from plan files, surfaces spec type in UI (badges and header card), reorganizes PlanRoutes handlers to wrapped private methods, adds property-based testing tooling to installer, updates docs and tests accordingly.

Changes

Cohort / File(s) Summary
PlanInfo Parsing & Utilities
console/src/services/worker/http/routes/utils/planFileReader.ts
Add `specType?: "Feature"
Route Handler Refactor
console/src/services/worker/http/routes/PlanRoutes.ts
Refactor inline route handlers into private methods wrapped by this.wrapHandler; preserve endpoints, access checks, and behavior.
UI: Spec & Dashboard
console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx, console/src/ui/viewer/views/Spec/index.tsx, console/src/ui/viewer/views/Dashboard/PlanStatus.tsx, console/src/ui/viewer/hooks/useStats.ts
Add new SpecHeaderCard component; add specType to PlanInfo typings; render spec-type badges (feature vs bugfix) and status/progress UI; small typings and string-literal normalizations.
Tests
console/tests/worker/plan-stats-endpoint.test.ts, installer/tests/unit/steps/test_dependencies.py, installer/tests/unit/steps/test_dependencies_playwright.py
Add test asserting specType parsing. Add tests for install_pbt_tools() (Hypothesis, fast-check) and mock time.sleep in dependency-playwright tests.
Installer & Setup
installer/steps/dependencies.py, installer/steps/finalize.py
Introduce detection and best-effort install steps for property-based testing tools (Hypothesis, fast-check); integrate PBT tools into dependency reporting; update finalize text to include bug fixes.
Docs & Site Content
README.md, docs/site/index.html, docs/site/src/components/WorkflowSteps.tsx, docs/site/src/pages/docs/QuickModeSection.tsx, docs/site/src/pages/docs/SpecSection.tsx
Update spec/quick-mode descriptions and examples to include bugfix workflow and auto-detection; add Feature and Bugfix spec panels and refreshed Quick Mode examples.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (UI)
    participant Server as PlanRoutes
    participant FS as FileSystem (worktree)
    participant DB as Database

    Client->>Server: GET /plans/:id (fetch plan)
    Server->>FS: read plan file (parsePlanContent)
    FS-->>Server: plan content + mtime (includes Type)
    Server->>DB: fetch plan metadata / stats
    DB-->>Server: metadata
    Server-->>Client: JSON { planInfo: { ..., specType: "Feature"|"Bugfix" }, content, stats }
    Client->>Client: render SpecHeaderCard and PlanStatus (show specType badge)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

released

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% 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 accurately captures the main objectives: adding bugfix spec workflow support and enhancing the spec viewer with type detection and display features.

✏️ 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 dev

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.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/maxritter/claude-pilot/issues/comments/3950343238","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nThis PR introduces spec-type classification (Feature or Bugfix) by parsing \"Type: Bugfix\" from plan files, adding `specType` field to PlanInfo across multiple interfaces, rendering bugfix badges in UI components, and integrating property-based testing tool installation into the setup flow.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Spec Type Parsing** <br> `console/src/services/worker/http/routes/utils/planFileReader.ts`|Added specType field detection and parsing; checks for \"Type: Bugfix\" line in plan content and sets specType accordingly.|\n|**PlanInfo Interface Updates** <br> `console/src/ui/viewer/hooks/useStats.ts`, `console/src/ui/viewer/views/Dashboard/PlanStatus.tsx`, `console/src/ui/viewer/views/Spec/index.tsx`|Added optional `specType?: \"Feature\" | \"Bugfix\"` field to PlanInfo interface across multiple files.|\n|**Spec UI Components** <br> `console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx`, `console/src/ui/viewer/views/Spec/index.tsx`|New SpecHeaderCard component renders detailed header with status, progress, task checklist, and metadata (including bugfix badge). Refactored Spec/index.tsx to use new component and update quote styling for literals.|\n|**UI Display Updates** <br> `console/src/ui/viewer/views/Dashboard/PlanStatus.tsx`|Added rendering of inline bugfix badge when plan.specType is \"Bugfix\".|\n|**Property-Based Testing Installation** <br> `installer/steps/dependencies.py`, `installer/steps/finalize.py`, `installer/tests/unit/steps/test_dependencies.py`|Introduced `install_pbt_tools()` function to install Python hypothesis and TypeScript fast-check; added tests covering presence detection and conditional installation.|\n|**Test Coverage** <br> `console/tests/worker/plan-stats-endpoint.test.ts`|Added test case verifying specType parsing from plan files.|\n|**Documentation** <br> `README.md`, `docs/site/index.html`, `docs/site/src/components/WorkflowSteps.tsx`, `docs/site/src/pages/docs/SpecSection.tsx`, `docs/site/src/pages/docs/QuickModeSection.tsx`|Updated guidance text, examples, and feature descriptions to reflect bug-fix support; added \"Spec Types\" section with Feature and Bugfix cards; expanded Quick Mode and Spec-Driven Mode descriptions.|\n\n## Estimated code review effort\n\n🎯 3 (Moderate) | ⏱️ ~25 minutes\n\n## Possibly related PRs\n\n- maxritter/claude-pilot#46: Directly modifies planFileReader.ts and PlanInfo shape with spec-type field introduction.\n- maxritter/claude-pilot#62: Also extends installer/steps/dependencies.py with new installation step functions.\n- maxritter/claude-pilot#45: Modifies PlanStatus.tsx and useStats.ts PlanInfo interfaces touched in this PR.\n\n## Suggested labels\n\n`released`\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                           | Resolution                                                                         |\n| :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 64.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                                |\n| :---------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                |\n|    Title check    | ✅ Passed | The title accurately summarizes the main changes: adding bugfix spec workflow capability and enhancing the viewer to display bugfix information via badges and components. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings (stacked PR)\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> 📝 Generate docstrings (commit on current branch)\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `dev`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=maxritter/claude-pilot&utm_content=85)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKPR1AGxJcAZiWpcaLT0AthEPvAAHvbcopAA7vgUANY+Hvjx6Bj0EvAk8ZSQZLCYDCRsGLjIABS2kGYAHACsAJQGAKo2ADJcsLi43IgcAPTDROqw2AIaTMzDzGiRFOo0FMMMHmjYSmDc8Om4w9zYHh7DzQYAgniwSVwLSyuUBgDK+NgUZZACVBgMsFwlBJIIAkwhgzlIuG+v3+920GFeuGo2CG/FiCIAwhR/DR6AFIAAmAAMBIAbGASWACQAWaBEgCcHAAzEzmU0AFpGF6OBYufg+RglDCkIYGKCXYKQAAGw0QsQYYFC4Siu02GClCic2UgPiS9lwFGwYg+dG+YR1UR4aow8GFKD+Hm2tqIkAo+HwUIYW0QJCyaA8skQSAANN8SCVcnqmJUqGJEKHMPQIpFTUjEMl9YbjdiNGLIC95ZBaEhuNR/oVqjK5aIpS0cAQwEoaHHIGmM7hZLFINU/MjsZAJDIwsm64nXe8aMgCK3YL60NxePhePBqL7uNbnTr0vFc1AMfgMIh8F4B3kChQuKWKD7pdBO94NRESB4k27mFbMBavPGiyXNvIlWTb4ghFe0izQRBYAEfBnDxbVqwYU98h/McSEiA00DEdBpQLUQAAl/CUCgMVgjVZm4A92F3SAAElDyRU5KECSVF1iCgO0VCDU2kcQ7QIY8ajwzsPVnIMfxecsFlwUSkDradpN9JR0SUP55EQGhuGo9puFoVcEiSVJt1dE5pG7KU2zARMwESFI0gyDRmFoWtW3wIt8AYRx2DNZVogQ385TLWAshySh4AiL1xAPdBSEqMMI3gJJqIAEXczzKmoBKsDHGwAFFLiSgBZHLIGwHTV3oadsTSUQoQUotsH9MBmEUX0/MQQMaGYXMDGgHiPywapywYZIPCQXAWg4PMMRongIJ9GorFkaSouwG1ap4n9bXU/0vD4ScqlDaMjxPfafw7eVZ2Gto9xamwVDUKFsVyfJZsQea8wAIWHS0/POxSSGbXEFAobExADMD10/K8fQoPMADUz0KYs5X/IcfOA2hSH4LBIawL0KFoRAjAAOVchxhBqyRfVmCokT5W0Nm2OgAG57B5Zx5CqrwW0XXIlHoOo2CRXSkWCotpAYZZuEijBuv0YxwCgMh6HwAUtgbGLlCBmn2EvPhBBEMQqZkeQmCI+71C0HR5ZMKA4FQVBP3VwhNaobWWFprgqEyBwnD5OQFHN1RLe0XQwEMBXTAMXL8qKhzaEmgAiZODAsSBLho4gyC101fd5eRVcFTARSMKAkpIZSyAYPJUQlfnIET1jKA4gQuIqnjN34jxEETlyZ19cvK7+GvIFG9TqNlQtZyCZ0uByyJS2yU0iGweBdL+X1p1tYsIs32cYjiVABD63U+F7XATR/JULRTFDtSqzCCGWYUE21IJ6+dxsAcpqLC/Pk0Bxoxvn3II84oS4xtHaOyO48y4UQmhNAzB1zSC4HXU0aBIDkB9og5BB9EKjXIFwROAAxS0dVJYQSCvEWcWAlBeF4i6DALVkDxAmK2RIgp9i0GxBgXuq0iL90gJPOICCkFeGogARVXsNSABUWri0QJLeA0tMpzwXomVMrkGaOiUA3D6J89SIAWKcVsEFkg/jQuuJIGUDyv3oAAR2kRmRxHcDyIA0InPMUj4AyLkbo0RyCuA2ArpsMo9ASFkP3hgE4HgeD4FtKsbyYEUSUA0NwWQvdWHSQbnXbC6QZ52jlLacge1XJ1XJswdQZp+gHl7raQRATxFGHMJYS4HhVg2MPH3OqSgNjOE6cgQulikhAz1McAQo14GVHUDXUukBSZF2FKZacwz2J0COFMSZ6crAzV6ZsN2mVkBNhqnQbqXRbSmX+MXOgXAADUTRhhgGpEYHK6l4BSVNGbX0T0zxFB8KfTgsi6DwEcAYZOnixRRyOseEgspPiykoLkMoiBhg2WSJQYYfQBjDDdHgaQww8D7BRbjUhXhglBFSVUJOKc04ZyziU8qbM/YFwFFcpZRM8x0QNIoI06CsBLhlv6PBd4uxPhfNjawao6K6hARgJhSIaD9TwWFHxAz0DIBITiE0vc9SJy+j5RO1ERUkAklLcBzggx2lKiLTR4tAbYWNaPC53ZjVcH1SOCGaoFCVC8mOH0UIEKOunHq76kRMk0J4NiH0lRWYiUoKwm8gb7yunKPCZA/CSARHILQaiVgpUYBlUdA0RoZaYIyN64sgrTjyG0UzZASauy5AwXKbEQRQysTQEQDKfEokvTFRVMp+9sQXwoNmyVmBpWuQNjVdxeYCq2j1KfKSDD0C0CECiXAtMpxkxIFIKgsSfCrSNm41mlA3R8HSEQcYdoJnuXMSVMqQNpwpMgMwE44gwAEN9Eu6gDDQyhChMfeKepsQLC2oIxAiCSDUQWWy0C05xlbMPX8UtQYiAYD7KZPUqyA0fB8JhUyx9ZAHgqvvd+ud5SOv7RKvNE6C34G6i09O7StaHO6fvPZ/SZaDIFDh00YzNk+KKNM8Q0g5m0YwJO+0qx8NfHqdCrwcKGAIooEi/FaKMVYu4Diic+LCXdyOGqUlJByVEQ0FUAwkBdDp2CDc6UDaSAAH4iHEK1diXuAAfPRobE7MylC0ywBVMBhT6sZ9OGGAwAC9ngGHOeQZAcG7O3NJA8sAABGUkLy3kfPoF8lNz1MiZoBVwAqwLQXgtLlCtxMKlMEvgMMArmn3TmIJT6F4Cr3FUrBTS1pmdXaMrzhzfkiyS55jQXBNEVaLTPnoA5vuEnJ1cAc85hurnMOee8wasCCnYWIHhavBriM1i3HwC1lJ7XqCdfcTABAyA0I0GyFOfeiGfGfvgOi+wJQuyFwW/R74NaHROjtBgv6/klGVIw0/HUYyvUIRVRFNjWTbh4HQCx5+Lo0JjU3P22dzSoBWDdGxDsq7+ZcClA5jUUUEmUFk76KUv3dQanqVKHbtWDuNeO81lF52OvmcQP5qACy418CA2gSM+6RumWOWIOgTG2kdO4+x/6fSDluOG3xlWfAXtTPEKJjl+PCfN3kORhO9nKP3kp1ganFBafSgZ/gJnWAWfVcU3t5T7OjuYq561k1vOqj+dixchLQpSCm9uWlx5RIsviBy4Hb5u7flFZGSVsrzBusQogGAIwrO3d1cO/kDFBWUVJUodBWCwwJMXYvldyI1LPG0r69nN2ud2Z8kLol/XNmP78pUQeIVc3qPTnt0t83sQVuavW5ALzIaDXUWCUvbEeJ7DGNibaL9GNSDDBoOhBIEbcYaDm6gGfyZe7kB3/JZ7XqMNsG6gAbSREQAAus02laO1UX+V/stVQyF4jP41rwTHXGZMTOdAGW4PEWzU3cnUfJzFzNzEgDbY/KIROR3aTGnAjO3fNRnMCZ3Q8GrXPD3AvNYIvYYEvSCMvAmCvNUKvFEPnSIAPOLS5EPJLUkSPaPd5RlPLH5F6JPdiFPYscrFOSFLPAwHPfberDnfPeIFFOBYYOBAiClYiWCOguvVOXrelHOWbVvFlSXDlLlN0WgXlZADBLBSAclLCciSiWKeQwiSgEiAmGcagFNBfYwu1bQLwegaeARfGJMPUDBOBfqDQSAAAaRIAAnDDFwSgvDmXLizVMmtwi3AlFkgnnBQWsAtToGgDMVDCsAyNoAkzsXzHlFK1FkqgrijS8lxkzBLRNFDDbDvlm0LCFlASRGog+m3m3Wwm2mr33ALXgBdAWG4Ghz2lgGxFagVRSQWhymJiShomJgAHFQwMQAB5AqKwLoHKaAHKUMOGHKGwGiYhGiHKJKFodtN0PmTcJgdICgUMHxWxMWTYY+DwaifcJBPFZARcIgKNVEciehOgfcVaXAOoj0f0AEyoQoz474qwLCHwN8Waa8U5eoufZWSgUUKzO2TwOcbUAVTKIVIgGCA9OE6GU0EWNAXMdE/MCYmQECX0FETcbolEXoiIIgck6zAnfAL46QetH+LAJHbCaJNgZYRCNsUxatMWDBdkzkt6YCCgVku2MxSADES6EaMaLgMeKEQueohINhDBK4pIU0VuYaL494bUNiTUZBVDBVEgVmSCDIO7CYQoXU5UlAaMfgPaBUgU4+PgMcXXJpCkkolojBN0eILgHEvvMU206Q7Cd1S0VuTGEgW4hXNjJgQEhMBcc4/vK00MNFA0EgWFYsUGKEZqJQUMYs+HTpVsd5HiHBQouqCIE8a/X0e7WMIGWElgPBDQeskgXI6SaiPCRMb8CtGZcM5woibHPUMMxInHbsBzRM1jNxNM3mXOLM19RQFVOgS4caMWH9KoQRMs8KCsm1EBUedyf0cY9HLSeaSAdoGaZcSpcQKQZAew2gJY2CD6RQWQUMD6Gk0MGiaMQoyU74nUOE5tEoRfa8maUaH4DmbqBZbXbZGaDvdVNeb9OEuqUwmwxQ58ooX/diIIrlSgRIv6ZAUDeEEqM/CiNZHNJjOXN/RXD/cWFXb/XjXC0ZAAiZITdgYAzvEo8AknOzKUTCoiZ81Ars6UMQ93CQz3Eg2QoSuw5QqoegpjILG0PwdSSAMLS4CLWQaLWGQPeLSXMPDLakNggwV5GPTg+Rbgwrf5ZPIFAQtPCrYQ7PF3XbcQqQwvM8GQ+UYYbeNCFQ9PNQ9ORvBlIGQbNvVlZgzvfQnlMJTMZ0N7DpWJc6Z0FhNhWgd4CZX0RxD0UyU+cdSTP7HHJbKksWbgEoH0BMUadDTue8ZANs98S1IgLwMAHKp9VyDKzZEgVq7AXK+gUaJK2dKAeeReeue3f7EnTcT8Sc/vGA8fNbEdBAqfTbE/ZEyGOK20MAPLBk5AIUWgAhF0Jtfkl6Hav8tXQYvYO0WoaY2YhYpY1Y9YzY7YyAXY/Yw4440ce+cofAKQUjNcH5d4etKkpk/okqSDUgaiCUdddSU0Ko6GTuVyF9TqrKnqvqp1QasWCHRdJIZdS4qKsCbEUgaIP1A0TcPag67saDFkuozE0MfE/0OohU347+JMTYIgW4sRcodgCsn0Y9LAZsx+TKFoZE2TJ+U0fJWgXoh7KEMcOhb+AI6cDBZqfsciR+M4lgJAbq1uH0WbDsE8PkhTNeRlM9PUcmqa7UNgN6TtZ0F4l3I21sgGf4a87ob1dSLMUtLeQHXRaa3vRIn0bmXEdkw2c1KgIWQoI6MadgAMbMthCOmG2KcoaWWQMAN27HHaQ08xNa0JU0e7MgIMKQe0L9Tw2wtYZo0k0c0KO0PkjBfmVVIGOSpQhwyw8gWKMcbEda8WiEX0W9YaMAHhIiXOHktK7JUw5uryFETtaDPMbSG1egG8iu9HYY9APm/UVcGoQWqQYYZwf4KmRo0QRAOSVybEcGKKOqH5HWgqqTFI2IW4r2zcQCMhTtBe82+gd5Jc+gI8nchhaiedJhPgG8rfe7VdaGzddgH4/Zdc+gE2vgS28G6QBMNdDdU0M6rACerGHu+9acY+V9d9eAN7cgQo/0fosdPLL+m22ipMtXBizjVXLpH/SitingQA4TXXWZUA5aWbYhzDB9Wesna3W3e3VA3A46NyySjy4gryuQnyvyyIOg/zCk8nKksiZg18dsqUAAcisBurmPmPUeWvUZWLWI2K2L0a83UdeoOKOKSnUY1GnClETi0ZmJ0cQMMceq2MQIsfeqShQMs2sylHKq4mUeuVUffA0dxlMcgHUbfq8FpgifUb3TClkBsb7nsdxkQOia5sqEQISZ8AyXkes3GyYY4sQn7TJ2WzgMn2nxjLDQF3mSnQUnYq2UuB2XsC4cWpqGQz5pRT6TemkDrHPDnEgLdJTWal+tZgSKFWIqdQ+3pzyIkw1AGdXOLCfFfqwGvu7pR1IvqSYQwDAA10YyCpUpC3Us0u0t0qMEYOD2CbuTSyZFSzS3pCjycszxcrwMU1OlRQMgxVxhTo6z2eyAogSXMx4j51UIbw0Oby0OZWGw7yMEuEwRen2kYC4gSAgn4oHUES3r2EM0wBRW6LuwBfiVigcHUE3lchyfkGkicLmy9CwGweJJCaVS7OuzgEGfriRaGkwZKFqkv0/FbQESPA+C+GjCRHA2bRgOmxfFvsZiUCe3PM3CQLDUKKknLFcPXy/vQQ1VdUgGABsloD0ENTqd0L7kxzeTtEvSExA102MP7ASQMN5VoFjQwHBidmCGHMSLmmbkym+HSBkUdgXBRIxbqlNZXVOkY0F1ciQpWVYvWXgpoYGW6lf0oa6WodEC/0V3ob/01yKa2S4r1zmVg3xujYYf/xzc4pE3kHje4zOSD0MruVYIpHYNjy4ITx4Nsr4PspBUcqEJeYMAyoYDxdJd8qXlkb6GYA8DBfUP6zCu0JhairmRnsZTqm8V8XkVJffHqTrPgPOXUoatWwgkA3NGTFMj9RX10N7mDReDPfqNDEsWuM6UKMcR8QzA70yQmE3f3jX2dQACkXhljiYwAugkowJ+3B2aBh2lBR3N1nibtHY0c5Wy2FRZNNxZWlFpZsa+AV2Mw/FrSy0dQj0psLXEIowDxuUD0jIkL3IPJZTmkgr5d5yU3B1P8uM2NM2qLhn4K822GI3jXi2s2NlimWH1A1I2nL47UTlqL9KmDrnIBw9Hk0sm3LLdFrK/litO3BCM8o5QPZQh3c8x7KgUUAB1AyaBdrCuGvSd4KiFgbWd9ved6ex9EkiWM1KmVsIBzCN0aU3ADhYstcTAZ8Yw7UOqfrWJCZD4AQPuWtXRa+Y9wL2bM9pC+pd+d1vuf+KNUMLmSmO0SxDRWbJgUVPUOBMAJKZYKQLAHDsWLD2RFqSRJxar3RVDlzqKRLRlkhPUGLqIE9+Ca9sxHuPuNrvgIxHaUxdMOLyAJ9mRFxN5NxDxPMVlyAELn18L8igRKrir61Jdo+76guxOCbjMWL3ucqqgJqsWKqQoYNIbkxDvUMKb7jQou96xGWOpeiQiKq4hzcPk9Cl6PV8hqAIrkrqmcr+RReZ8MtH2K0p7JwuqNFaBesQgaXXch+6IanWKPduqEUxr5RGWDLkJTCTuZ7AGlEBbpvITI7iCKa6CFHDBQ9ExN2mo8CmHoyfKtLrr3LFgZBaIDvSGkxV4hYR7ZFhwjb9q19LiqKCfdpsWDr2+XuH01yeVPeJwpHtA2KR2PAeH7+GXRl9HhUzH32g58UOitjVNpijNlikt7NzjkTNh4mf9nKC52tlru5AAdkeUyzMuyyU/jwK1U7stKwcsCucr7fcjA9EaOEnpRW06w5w5eB5ICoq3BenZb2hbs+uU72CQ7tm13WUFC5iQBhQE6mQA/d9DW/kV19LT5NMPqK4EABwCXJDBCW+ky6kpJXNmAQe86pAgDAQAXAJWZq/DPlhFVTol66pliCZKAY/VMfFqZNg3oe/IBq+RrNh6lbhMg6p1YgpKlggvB4hnBfQ0VEA5+xxq/58oNBF1HFxDC4w9GkQsq+4omC+9GPP8AvP94vltaSAu+tJHO/qcLcZqAkhKWQDDNCMV9DCJEIK8I2hvD7iY9sGUvUyHyTp7Zg+U/oQMI7FNIbgrqPpHiIqEzT6kwAI4DLtQFnAjFPwdUXgAnkBr6QCYFPCcPljPBTUlUx8U+L6Aya0xOkd+B/I/kgAUMGOnRHpGmxY5q42OjDS3qwxAJQBeKigVpuhm4Ym4uApMcgOwz4poZIcACIXnZgUEkB4YzgFcLfx4RQZTcmg5SsFjUpQhTmKA85lJyuZLIjKEedLASEU7uxlOrbGympz95dsA+vbbTkGHA655SwIoYYNpzgQx8+acfHrFZ0T5Qt84c7VPnMjrhTgOEphO4l0mqD98q4QUMcOyHnCH1BEjoauDsFbRYQ36IyIamYVx7Io8kB4IgPgJxpg1J6OFHBCeHVZ6gwB2pbJBgiR5gAkgeQH1K/U6iQAEARAWANVT6CmgkuzAL0tKx0Tm04eX8QGJ8g9heQ4yENTlDGFionsEWmQavgEWNQH97APJb1KK0gQuhvOcvF6D4TRLWYFqACAIj4TaFBRkhoYLwpQFDBl9MohDJ1OpT/jvA+A67CxJhCCjkDj29AauoqWVIYh4AnwLwASBdIHg5SkAapkUTiB3Dqgn8BHnQH6ZsJHhAwkuq8Oc5Y93h4pT4RqQFC6gPg+fcoP8KdpAjOuIInUmCNEDJAIRUIkgDCOSFtERImwgXoTEjTSAvISXdhIQCuKOAsAUaCiIeFc5fE144pfsOpFkAeFWmlSfZODGjZY5CkQ9GDJGyLZH1ASVZOKJEQnJ4AgwuiQuMlymzz19Ou5McCK3YCswRWbocjuWjAzOt5AudfnmfRRKL1cye/NhF90yA7C6qPrO9AcyTa8CW+VbVjmbyzYcdmGXHcQbRCQQjJ0WXANIX8FgChgshQxPdnkJQp91/ARQ2hMHx06+D4U/g/FEEPlAhCZYdBe3gZUd6ycneLvKkI4IWHODvevBQFO4I06VYRCW0BiLtFlAaQI+FcFEsPGkBpJZAlnOlJEKZTRCU+7KOZDFUv4bDTC8FTpqWj7E7QAA+twAEC4AtxXcRANUDkjcthMDgKNJGiXBG5OIF9faAjQEjoB+wYudwioC8BcBNxpwZAItGWhYBYAwkBSGJG7BHVsAwILuHWDCivokAJ3McB+O7gwB7wpqLHjqAPZbVnSzVQQDtHkBHUMA3ADdgKHlSo5+WAObaIxBzTcDxQwQVwsuAkB6RZwHgM0luKQBbj8M6kLcZy0Yn0QdodAY8dAI15Qh0JrcWJDhI3acTTgFZP+ChM5bUQCKXxNeoIlMJWAPo0AFyMeE3AJImO4sIeNXHiKiTNgpaWHgHGS7A4G4ik5SYeO7B/iKIAEkMMhPUioTGRLQXuDDSGJF97QJEvSd6x9AuIN4pZZwMkDUky1C+uk00NQjIBswGAyKRADTyCKfQIi4uFADIGwEdiuAOzRUL638l2goo+GfYAAmqDDoPgXSaAIaBIB1h8qsEncXuIPHug4JME3cuTF+bmd9ezGZNnwI4wCDaGPGX/tGIEyCc4xneKwMw3XHes5B0oJQAKHKm7j9xh4nieHG+DVShGsEjFM5OHFaSa4E4hgg7xUZ3ImQdgqPO7wspOCveiedtp2NTyeCo4i0tYMtOGBZoiG0WCcVOJCqaFZxQ2ecaNnhZY1/6M0bfJ6GYJgQ6ytoO6TzQ0hcA6ojxUHvlTqitDnJYPFNEEGQDV8CiKATmrTEgAAAyAcKFFyY6h4CY3OAcPSCjQAkowHa0VZRcFd83JNAIIMNmr4aA6ZOMzDATJgDEyxYLbArBoApkLJRc8UkjmsI8D4CKOUVJqYW1T68STekYrqVRQE65sre442sdJxsE3N5OLYtnm2OOlqcugGQc6b2OClrAPmq0dQIOPM6ANWJSkUcdpPcTpJHp1nGdsn0iqxCxslEtzupHqp+EuRE0yqeZKGnhlhOk0CkvtA4nuSKpU06qYgEDn9iw5lkuNGJC3GhSMAW4ypG9FnhnixOUc6yYXyCnuSQpEaROU1XhEByPZIcgSFuPTDKJI5/4mSGHLjk7iKilQLgHnTE5MJKZO0CsnHIGEVzpAcHIibyOjSaBfGUAAubpODlVTi5sEsOSxP3HsTq5uc5OQ3IvETz7JfrTOf2OzlhSZ5wofOTxHDnbjJpI87uCXP8mDBmJB7NicqVjk0Ia5fIuuSnIvFNzYJrciNAvM5aETCI8gcgb3M3msTC5e8sOflNHRhzi0JAc+WQC3E7za5gKOeaZCKnYAWBJFAGAVNXl0tORpYYaOH0fFzgPA3cxadRSgCLtJwOFNUScL6iK0IpHsRQHpD9GaTzZAEN+YFDUlDzd55kzdlvLNlLwxxv81aNvM/FsT9S4pRBmaxdBXgoMiSJIAIkwhMBqBwoAMEalEjUx8adrdYc7N3JUsoQFLZuWJNLTcyoirMkjO6yFREdHxJpH/t/K9kEdvWR1ZqMNFzh0TYkaEUQISjVxjh35VcORYyNxw8COp4Y9qcxUlkiDYxsszvCLPZR9wVBjM9XDG1oDSzy2Ygo5HxNOTGAbexMHKBHDThHNTBGlfYL6C0oWCYslzOtrJxSymVzKHBQ6XQLbZuCzpzzcOJHCVjYk1YavG2QsNYC6xXQaAH2LOwDhfIqAwcTQKHBtiKxNQ95RiYTC3HWU6AJcumFCHlgGBbY3wakNSAYD0gneSy2gPSFoBO8SAdzAkCoCJCkhSQAgJoGgA2VO8MsTIBgNSCaBEgneDQBoEECZBhwI4symmOoGGW/yXB4y5WA8pmUDLyBCcygKQFPluKJlzgKZdUoMAABvXxonCQC2APo6U/4osMqBWBn+uIROL4H9CVUoVtpE4LQDhV3pbAaK5Cd3ATJQqkAyxPdMsFsy8J0VxK4MFCuLC0AbAq0FKAwHazo5EASpRkYSsAV0qrMicBlUyowDuBcAXgTlcNG5XFTeVDcAVcyvxG+0xVyQQlfhlpVQqCE6KWgDRDegwLEAbKwlcnClWJwZ+uABVcEgcDtIe4XAW/L4ysyQqrMdqhuJy2JhQY9V5cRRE1ywAKrE4Uq+1YnB2oSqYF3qu1YnDvaYBOkeqhVfYEPmxB6AN0JQHdB6WABMAl2r9FYAn6LPrEnCo1oCWr405F6utVBrfOeqnfqOmdB5r7VfK7odelBLKknVbAPVW8NqT5qAAvoGttXlrE4jq51UQmFUnhOWZa9tX6tBmSr81fKkNZDkyh6r5uvpOcBFI+CrhwY4VeANFgQ7OjdCzEYsDelDR4IqBhkctF6FLBqABqxubUMUFKD49fQHOaAX+DQAAQt1toHcuYpXAb5We5pKwlUA8SBq+VhaohMWuOH9qfVoMA8MyW1Q0rMV5ahuJWsBkeAFVtahAkQmnWeJ7VLa/NW2p9Wdq61RCFlanTtD7g90k9f9UGsHVEqwN7asdWGsw3B9SadoJgHhqxioBSQ1IDQESCJAABSXfD4geGZzsA/yHxD0M0A3Z48T7cCtJCjS3BxUqABoESCY2saP1I6huFGmPAOLqVDcfvqSzcgDsqNRAV2SAPw4oY2M68k4bOHfDThIM4gKKZS1f4/VlAWMETdIDE05oCNX6lqEWucB/rP1EG5YFWug01qu10qyjeysQ12qm1vjR/AaqNW2BXVaHJ7kQgEAkhSQdAW5tSDSxO8FlTvRZQIAJDbKmQAgNLP8n8BEhTlDQUkM0DQBpYCQDQHLT4AYBEhj4AgfDKSHpANBzlBITZUVtoCqAGgCBMLQe1sA9q4NDcJoJJoY3zK1lTQakCQGPgkBqQTIakAIGpDEg0sRIBgE0FoBkgIpc2tLKoGpA3KmQpIFkHQGY1TbW4aWMrQwD2UnaiQaAakKSCJD9r+V/m50Lhus0kACKo6f0FXn62oaHVrlNnFJSIJSFvKogKRvhBLrPk6ChKr7XyoIAMRiEZitxISrSzubE43sw8IZwmBYbNNFqyAFHiQ2BqO1P23PDDDUwooNMx2foNplxSTgCU4gAzCSkyWmZKUWOyHQ3Gh3+hYdem+HVwCd5I6UdiANHdJAx0BauADg3Hfmvx1vNQ+hBc8ADtIKl4YIlBSvFSXB1cBmdicVnR4HZ1dMEdPOuHajvR0PbhQWOnHUFrx0SU88khGStIxHbK7IAqu9XZru4yEqCQOujnXroF0G6tNhK43VZmQ0+rLpRswYDdKg1LroM6SCHXJrV0gkNduurHYjoj287+dsAQXalQR3Nq8d/u66awpUgWyJx4e8DZHph0x7CVTQF3V00T3J7DdxetPWLv936y1oAelFAHKz1Vw1pYelXRHvt1F6uADQUvdxnL0e6sdDQZtb42C3BbvlEAXkX8ooAArOWYcz5f0on3Owdx3oIBd0VX2TKvlEKw1T1pX20BNywSRrJLQ9jqAwSuANFUSDH2zKl9pYFJMCpoBbj594cIAA -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

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: 5

♻️ Duplicate comments (1)
console/src/ui/viewer/views/Spec/index.tsx (1)

285-291: Same year-omission issue as SpecHeaderCard.tsx.

toLocaleDateString(undefined, { month: "short", day: "numeric" }) in the archived specs dropdown is missing the year option, mirroring the same issue flagged in SpecHeaderCard.tsx lines 147–152.

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

In `@console/src/ui/viewer/views/Spec/index.tsx` around lines 285 - 291, The
archived-specs dropdown builds dateStr from spec.modifiedAt but omits the year;
update the date formatting used when computing dateStr (the block that creates
date and dateStr) to include the year option (e.g., year: "numeric") in the
toLocaleDateString options so the displayed date shows month, day and year for
spec.modifiedAt.
🧹 Nitpick comments (5)
installer/steps/dependencies.py (2)

546-556: [Optional] Move success return to else to satisfy TRY300

The return on the happy path sits inside the try block — ruff TRY300 flags this. Moving it to an else clause also makes the control-flow intent explicit without changing behaviour. (S607 and BLE001 are consistent with _is_vtsls_installed/_is_ccusage_installed throughout the file.)

♻️ Proposed fix
 def _is_fast_check_installed() -> bool:
     """Check if fast-check is installed globally via npm."""
     try:
         result = subprocess.run(
             ["npm", "list", "-g", "fast-check", "--depth=0"],
             capture_output=True,
             text=True,
         )
-        return result.returncode == 0 and "fast-check" in result.stdout
     except Exception:
         return False
+    else:
+        return result.returncode == 0 and "fast-check" in result.stdout
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@installer/steps/dependencies.py` around lines 546 - 556, The try block in
_is_fast_check_installed currently returns on the success path; move the
successful return out of the try into an else clause that executes when no
exception is raised to satisfy TRY300 and match the pattern used by
_is_vtsls_installed/_is_ccusage_installed: keep the subprocess.run call and its
result inside try, catch exceptions and return False in except, then add an else
that returns result.returncode == 0 and "fast-check" in result.stdout so control
flow is explicit without changing behavior.

559-571: [Optional] Accumulate sub-step results to avoid false "installed" success signal

Both _run_bash_with_retry return values are discarded and the function always returns True, so _install_with_spinner will display "PBT tools … installed" and mark "pbt_tools" as done even when both installations fail. If accurate feedback is desired without blocking:

♻️ Proposed fix
 def install_pbt_tools() -> bool:
     """Install property-based testing packages: hypothesis (Python) and fast-check (TypeScript).
 
     Go PBT is handled by the built-in 'go test -fuzz' (Go 1.18+) — no install needed.
     Both packages are best-effort: failure does not block installation.
     """
+    ok = True
     if not command_exists("hypothesis"):
-        _run_bash_with_retry("uv tool install hypothesis")
+        ok = _run_bash_with_retry("uv tool install hypothesis") and ok
 
     if not _is_fast_check_installed():
-        _run_bash_with_retry(npm_global_cmd("npm install -g fast-check"))
+        ok = _run_bash_with_retry(npm_global_cmd("npm install -g fast-check")) and ok
 
-    return True
+    return ok
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@installer/steps/dependencies.py` around lines 559 - 571, install_pbt_tools
always returns True even if the sub-installs fail; change it to capture the
boolean results of the actions and return an aggregate success flag: call
command_exists("hypothesis") and if False assign hypothesis_ok =
_run_bash_with_retry("uv tool install hypothesis"), call
_is_fast_check_installed() and if False assign fastcheck_ok =
_run_bash_with_retry(npm_global_cmd("npm install -g fast-check")), then return
combined_result = (hypothesis_ok if installation attempted else True) and
(fastcheck_ok if installation attempted else True) so that install_pbt_tools
reflects real success/failure.
installer/tests/unit/steps/test_dependencies.py (1)

977-1037: [Optional] Add a test for the best-effort failure contract

The five tests cover all skip/install branches, but none verifies that install_pbt_tools still returns True when _run_bash_with_retry fails for both tools. The documented contract ("failure does not block installation") would go undetected if a future change accidentally propagates failures.

`@patch`("installer.steps.dependencies._run_bash_with_retry", return_value=False)
`@patch`("installer.steps.dependencies._is_fast_check_installed", return_value=False)
`@patch`("installer.steps.dependencies.command_exists", return_value=False)
def test_install_pbt_tools_returns_true_on_install_failure(self, _mock_cmd, _mock_fc, _mock_run):
    """install_pbt_tools returns True even when both installations fail (best-effort)."""
    from installer.steps.dependencies import install_pbt_tools

    result = install_pbt_tools()
    assert result is True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@installer/tests/unit/steps/test_dependencies.py` around lines 977 - 1037, Add
a unit test that asserts install_pbt_tools still returns True when both installs
fail by patching _run_bash_with_retry to return False and ensuring both
_is_fast_check_installed and command_exists return False (so installs are
attempted but fail); call install_pbt_tools and assert result is True to
validate the best-effort failure contract for the install_pbt_tools function.
console/src/ui/viewer/views/Spec/index.tsx (1)

94-100: Calling setState during render is non-idiomatic — prefer a useEffect.

React permits this pattern (it re-renders the component immediately with the new state), but it can be surprising and triggers an extra render cycle. A useEffect keyed on selectedProject is the conventional React approach:

♻️ Proposed refactor
-  const lastProjectRef = useRef(selectedProject);
-
-  if (lastProjectRef.current !== selectedProject) {
-    lastProjectRef.current = selectedProject;
-    setSelectedSpec(null);
-    setContent(null);
-    setError(null);
-    setIsLoading(true);
-  }
+  useEffect(() => {
+    setSelectedSpec(null);
+    setContent(null);
+    setError(null);
+    setIsLoading(true);
+  }, [selectedProject]);

Remove useRef import if it becomes unused.

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

In `@console/src/ui/viewer/views/Spec/index.tsx` around lines 94 - 100, The
component is calling set state during render when comparing
lastProjectRef.current to selectedProject; move this reset logic into a
useEffect that depends on [selectedProject] instead: in the effect compare
lastProjectRef.current and selectedProject, update lastProjectRef.current, then
call setSelectedSpec(null), setContent(null), setError(null), and
setIsLoading(true); ensure you import/use useEffect and remove useRef import
only if it's no longer needed elsewhere.
console/src/services/worker/http/routes/utils/planFileReader.ts (1)

82-82: Conditional spread can be simplified.

Because PlanInfo.specType is already optional (specType?:), consumers rely on value equality (=== "Bugfix"), not key presence ("specType" in obj). Assigning specType directly is cleaner and equally correct:

♻️ Proposed simplification
-    ...(specType && { specType }),
+    specType,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/utils/planFileReader.ts` at line 82,
The object currently uses a conditional spread ...(specType && { specType })
which is unnecessary because PlanInfo.specType is optional; replace that spread
with a direct property assignment specType so the object literal includes
specType: specType (or just specType) directly. Locate the object construction
in planFileReader.ts where specType is added and change the conditional spread
to a plain specType property to simplify the code and keep behavior identical
for consumers checking value equality.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@console/src/services/worker/http/routes/utils/planFileReader.ts`:
- Line 108: getWorktreePlansDirs currently swallows filesystem errors with an
empty catch block after calling fs.readdirSync; change that to catch the error
into a variable and log it the same way scanPlansDir does (e.g., use the same
logger and message style) and then return an empty array or otherwise handle the
failure rather than silently discarding it. Locate the catch following the
readdirSync call in getWorktreePlansDirs and replace the empty catch {} with a
catch (err) that logs the error (including err.message/stack) and returns/falls
back consistently with scanPlansDir's error handling.
- Around line 55-57: The parser currently maps any non-"Bugfix" Type to
undefined, so "Feature" in headers is never produced by specType; update the
extraction in planFileReader.ts to explicitly handle both "Bugfix" and "Feature"
(check typeMatch?.[1] and set specType to "Bugfix" | "Feature" accordingly) so
callers can distinguish real "Feature" values from unmarked, and then update the
related consumers (SpecHeaderCard.tsx and index.tsx) to accept the narrowed
union ("Bugfix" | "Feature") instead of assuming undefined represents "Feature".

In `@console/src/ui/viewer/views/Spec/index.tsx`:
- Around line 142-168: The deleteSpec frontend call is missing the project query
and handleDeletePlan backend uses a hardcoded project root; update deleteSpec to
append the same project parameter logic used in loadContent (add
`${selectedProject ? `&project=${encodeURIComponent(selectedProject)}` : ""}` to
the DELETE URL) so the request is project-scoped, and modify
PlanRoutes.handleDeletePlan to extract `const project = req.query.project as
string | undefined;` then replace the hardcoded `process.env.CLAUDE_PROJECT_ROOT
|| process.cwd()` with `const projectRoot = resolveProjectRoot(this.dbManager,
project);` so deletions use the correct project root and respect project-level
access controls.

In `@console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx`:
- Line 158: The code in SpecHeaderCard.tsx uses spec.filePath.split("/") which
fails on Windows backslashes; update the rendering of the filename (the span
showing spec.filePath.split("/").pop()) to derive the base name in a
cross-platform way — either have the server supply a basename (use path.basename
in planFileReader.ts) or change the client to split on both separators (e.g.,
split by / or \ using a regex) before taking the last segment so spec.filePath
always yields the correct filename regardless of OS.
- Around line 143-155: The displayed modified date for specs omits the year,
making dates ambiguous; update the rendering that uses new
Date(spec.modifiedAt).toLocaleString(...) (in SpecHeaderCard where
spec.modifiedAt is used) to include the year (e.g. add year: "numeric" to the
options) so the formatted string shows month, day, year, hour and minute
consistently.

---

Duplicate comments:
In `@console/src/ui/viewer/views/Spec/index.tsx`:
- Around line 285-291: The archived-specs dropdown builds dateStr from
spec.modifiedAt but omits the year; update the date formatting used when
computing dateStr (the block that creates date and dateStr) to include the year
option (e.g., year: "numeric") in the toLocaleDateString options so the
displayed date shows month, day and year for spec.modifiedAt.

---

Nitpick comments:
In `@console/src/services/worker/http/routes/utils/planFileReader.ts`:
- Line 82: The object currently uses a conditional spread ...(specType && {
specType }) which is unnecessary because PlanInfo.specType is optional; replace
that spread with a direct property assignment specType so the object literal
includes specType: specType (or just specType) directly. Locate the object
construction in planFileReader.ts where specType is added and change the
conditional spread to a plain specType property to simplify the code and keep
behavior identical for consumers checking value equality.

In `@console/src/ui/viewer/views/Spec/index.tsx`:
- Around line 94-100: The component is calling set state during render when
comparing lastProjectRef.current to selectedProject; move this reset logic into
a useEffect that depends on [selectedProject] instead: in the effect compare
lastProjectRef.current and selectedProject, update lastProjectRef.current, then
call setSelectedSpec(null), setContent(null), setError(null), and
setIsLoading(true); ensure you import/use useEffect and remove useRef import
only if it's no longer needed elsewhere.

In `@installer/steps/dependencies.py`:
- Around line 546-556: The try block in _is_fast_check_installed currently
returns on the success path; move the successful return out of the try into an
else clause that executes when no exception is raised to satisfy TRY300 and
match the pattern used by _is_vtsls_installed/_is_ccusage_installed: keep the
subprocess.run call and its result inside try, catch exceptions and return False
in except, then add an else that returns result.returncode == 0 and "fast-check"
in result.stdout so control flow is explicit without changing behavior.
- Around line 559-571: install_pbt_tools always returns True even if the
sub-installs fail; change it to capture the boolean results of the actions and
return an aggregate success flag: call command_exists("hypothesis") and if False
assign hypothesis_ok = _run_bash_with_retry("uv tool install hypothesis"), call
_is_fast_check_installed() and if False assign fastcheck_ok =
_run_bash_with_retry(npm_global_cmd("npm install -g fast-check")), then return
combined_result = (hypothesis_ok if installation attempted else True) and
(fastcheck_ok if installation attempted else True) so that install_pbt_tools
reflects real success/failure.

In `@installer/tests/unit/steps/test_dependencies.py`:
- Around line 977-1037: Add a unit test that asserts install_pbt_tools still
returns True when both installs fail by patching _run_bash_with_retry to return
False and ensuring both _is_fast_check_installed and command_exists return False
(so installs are attempted but fail); call install_pbt_tools and assert result
is True to validate the best-effort failure contract for the install_pbt_tools
function.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9a06ee and b44c979.

⛔ Files ignored due to path filters (13)
  • launcher/statusline/formatter.py is excluded by !launcher/**
  • launcher/tests/unit/statusline/test_formatter.py is excluded by !launcher/**
  • launcher/tests/unit/test_worktree.py is excluded by !launcher/**
  • launcher/worktree.py is excluded by !launcher/**
  • pilot/commands/spec-bugfix-plan.md is excluded by !pilot/**
  • pilot/commands/spec-plan.md is excluded by !pilot/**
  • pilot/commands/spec.md is excluded by !pilot/**
  • pilot/rules/task-and-workflow.md is excluded by !pilot/**
  • pilot/scripts/mcp-server.cjs is excluded by !pilot/**
  • pilot/scripts/worker-service.cjs is excluded by !pilot/**
  • pilot/settings.json is excluded by !pilot/**
  • pilot/ui/viewer-bundle.js is excluded by !pilot/**
  • pilot/ui/viewer.css is excluded by !pilot/**
📒 Files selected for processing (14)
  • README.md
  • console/src/services/worker/http/routes/utils/planFileReader.ts
  • console/src/ui/viewer/hooks/useStats.ts
  • console/src/ui/viewer/views/Dashboard/PlanStatus.tsx
  • console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx
  • console/src/ui/viewer/views/Spec/index.tsx
  • console/tests/worker/plan-stats-endpoint.test.ts
  • docs/site/index.html
  • docs/site/src/components/WorkflowSteps.tsx
  • docs/site/src/pages/docs/QuickModeSection.tsx
  • docs/site/src/pages/docs/SpecSection.tsx
  • installer/steps/dependencies.py
  • installer/steps/finalize.py
  • installer/tests/unit/steps/test_dependencies.py

Comment on lines +55 to +57
const typeMatch = content.match(/^Type:\s*(\w+)/m);
const specType =
typeMatch?.[1] === "Bugfix" ? ("Bugfix" as const) : undefined;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Feature" is a dead value in the specType union — the parser never produces it.

typeMatch?.[1] === "Bugfix" maps every non-Bugfix value (including explicit Type: Feature) to undefined. The interface therefore exposes a value that can never occur at runtime, which can lead future readers/consumers to incorrectly branch on specType === "Feature".

Either also handle the "Feature" case so callers can distinguish "Feature" from "unmarked", or narrow the type to specType?: "Bugfix" to match what the parser actually produces.

🛠️ Option A – also capture "Feature" explicitly
-  const specType =
-    typeMatch?.[1] === "Bugfix" ? ("Bugfix" as const) : undefined;
+  const specType =
+    typeMatch?.[1] === "Bugfix"
+      ? ("Bugfix" as const)
+      : typeMatch?.[1] === "Feature"
+        ? ("Feature" as const)
+        : undefined;
🛠️ Option B – narrow the interface to what is actually produced
-  specType?: "Feature" | "Bugfix";
+  specType?: "Bugfix";

(Same change needed in SpecHeaderCard.tsx and index.tsx.)

📝 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
const typeMatch = content.match(/^Type:\s*(\w+)/m);
const specType =
typeMatch?.[1] === "Bugfix" ? ("Bugfix" as const) : undefined;
const typeMatch = content.match(/^Type:\s*(\w+)/m);
const specType =
typeMatch?.[1] === "Bugfix"
? ("Bugfix" as const)
: typeMatch?.[1] === "Feature"
? ("Feature" as const)
: undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/utils/planFileReader.ts` around lines
55 - 57, The parser currently maps any non-"Bugfix" Type to undefined, so
"Feature" in headers is never produced by specType; update the extraction in
planFileReader.ts to explicitly handle both "Bugfix" and "Feature" (check
typeMatch?.[1] and set specType to "Bugfix" | "Feature" accordingly) so callers
can distinguish real "Feature" values from unmarked, and then update the related
consumers (SpecHeaderCard.tsx and index.tsx) to accept the narrowed union
("Bugfix" | "Feature") instead of assuming undefined represents "Feature".

}
} catch {
}
} catch {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent catch {} discards filesystem errors in getWorktreePlansDirs.

existsSync guards against a missing .worktrees directory, but readdirSync can still throw (e.g. EACCES, ENOTDIR). Those errors are silently swallowed here, while the same category of error in scanPlansDir is properly logged.

🛠️ Proposed fix
-  } catch {}
+  } catch (error) {
+    logger.error("HTTP", "Failed to read worktrees directory", { worktreesDir }, error as Error);
+  }
📝 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
} catch {}
} catch (error) {
logger.error("HTTP", "Failed to read worktrees directory", { worktreesDir }, error as Error);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/utils/planFileReader.ts` at line 108,
getWorktreePlansDirs currently swallows filesystem errors with an empty catch
block after calling fs.readdirSync; change that to catch the error into a
variable and log it the same way scanPlansDir does (e.g., use the same logger
and message style) and then return an empty array or otherwise handle the
failure rather than silently discarding it. Locate the catch following the
readdirSync call in getWorktreePlansDirs and replace the empty catch {} with a
catch (err) that logs the error (including err.message/stack) and returns/falls
back consistently with scanPlansDir's error handling.

Comment on lines +142 to +168
const deleteSpec = useCallback(
async (filePath: string) => {
if (
!confirm(
`Delete spec "${filePath.split("/").pop()}"? This cannot be undone.`,
)
)
return;
setIsDeleting(true);
try {
const res = await fetch(
`/api/plan?path=${encodeURIComponent(filePath)}`,
{ method: "DELETE" },
);
if (!res.ok) throw new Error("Failed to delete spec");
setSelectedSpec(null);
setContent(null);
await loadSpecs();
} catch (err) {
setError("Failed to delete spec");
console.error("Failed to delete spec:", err);
} finally {
setIsDeleting(false);
}
setSelectedSpec(null);
setContent(null);
await loadSpecs();
} catch (err) {
setError('Failed to delete spec');
console.error('Failed to delete spec:', err);
} finally {
setIsDeleting(false);
}
}, [loadSpecs]);
},
[loadSpecs],
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find the DELETE /api/plan route handler
rg -n "DELETE.*plan|plan.*DELETE" --type=ts --type=js -A 5 -B 2 | head -100

Repository: maxritter/claude-pilot

Length of output: 50379


🏁 Script executed:

# Also search for the /api/plan route definition more broadly
rg -n "\/api\/plan" --type=ts --type=js -A 3 -B 3 | head -150

Repository: maxritter/claude-pilot

Length of output: 143


🏁 Script executed:

# Look for the loadContent function to see how it uses the project parameter
rg -n "loadContent|const loadContent" --type=ts -A 10 -B 2 | head -100

Repository: maxritter/claude-pilot

Length of output: 2674


🏁 Script executed:

# Find the deleteSpec function in the Spec file
rg -n "const deleteSpec|deleteSpec\s*=" console/src/ui/viewer/views/Spec/index.tsx -A 20

Repository: maxritter/claude-pilot

Length of output: 801


🏁 Script executed:

# Search for the backend route handler for DELETE /api/plan
fd -e ts -e js -path "*/api/*" -type f | head -20

Repository: maxritter/claude-pilot

Length of output: 234


🏁 Script executed:

# Search in the entire codebase for the plan route handler
rg -l "delete.*plan|plan.*delete" --type=ts --type=js -i | grep -v node_modules | grep -v dist | head -20

Repository: maxritter/claude-pilot

Length of output: 188


🏁 Script executed:

# Examine the PlanRoutes file to find the DELETE handler
cat -n console/src/services/worker/http/routes/PlanRoutes.ts | head -150

Repository: maxritter/claude-pilot

Length of output: 7206


🏁 Script executed:

# Search for DELETE method and plan handling
rg -n "DELETE|delete|router\.delete|app\.delete" console/src/services/worker/http/routes/PlanRoutes.ts -A 10 -B 2

Repository: maxritter/claude-pilot

Length of output: 1413


🏁 Script executed:

# Find the handleDeletePlan method
rg -n "handleDeletePlan" console/src/services/worker/http/routes/PlanRoutes.ts -A 25

Repository: maxritter/claude-pilot

Length of output: 2351


🏁 Script executed:

# Check the full file length to see if there's more context
wc -l console/src/services/worker/http/routes/PlanRoutes.ts

Repository: maxritter/claude-pilot

Length of output: 123


🏁 Script executed:

# Check resolveProjectRoot to understand what it does with the project param
rg -n "resolveProjectRoot" console/src/services/worker/http/routes/PlanRoutes.ts -B 2 -A 2

Repository: maxritter/claude-pilot

Length of output: 2179


🏁 Script executed:

# Look at the implementation of resolveProjectRoot
cat -n console/src/services/worker/http/routes/utils/resolveProjectRoot.ts

Repository: maxritter/claude-pilot

Length of output: 1111


deleteSpec and its backend handler both omit project-scoped authorization—breaking multi-project support.

The frontend omits the project parameter when calling DELETE /api/plan, while loadContent (line 128) correctly includes it. More critically, the backend handleDeletePlan (PlanRoutes.ts:171) hardcodes process.env.CLAUDE_PROJECT_ROOT || process.cwd() instead of using resolveProjectRoot(this.dbManager, project) like every other handler in the class. This means deletions always target the default project root regardless of which project the user intended, breaking multi-project setups and potentially bypassing project-level access controls.

Fix required:

  1. Frontend: Append ${selectedProject ? &project=${encodeURIComponent(selectedProject)} : ""} to the DELETE request URL (like loadContent does).
  2. Backend: Replace the hardcoded process.env.CLAUDE_PROJECT_ROOT || process.cwd() with const projectRoot = resolveProjectRoot(this.dbManager, project); at the start of handleDeletePlan, and extract const project = req.query.project as string | undefined; like other handlers do.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Spec/index.tsx` around lines 142 - 168, The
deleteSpec frontend call is missing the project query and handleDeletePlan
backend uses a hardcoded project root; update deleteSpec to append the same
project parameter logic used in loadContent (add `${selectedProject ?
`&project=${encodeURIComponent(selectedProject)}` : ""}` to the DELETE URL) so
the request is project-scoped, and modify PlanRoutes.handleDeletePlan to extract
`const project = req.query.project as string | undefined;` then replace the
hardcoded `process.env.CLAUDE_PROJECT_ROOT || process.cwd()` with `const
projectRoot = resolveProjectRoot(this.dbManager, project);` so deletions use the
correct project root and respect project-level access controls.

Comment on lines +143 to +155
{spec.modifiedAt && (
<div className="flex items-center gap-1">
<Icon icon="lucide:calendar" size={12} />
<span>
{new Date(spec.modifiedAt).toLocaleString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Date format omits the year — ambiguous for multi-year archives.

toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) renders e.g. "Feb 24, 10:30 AM" with no year. Verified/archived specs visible months or years later will be indistinguishable by date.

♻️ Proposed fix
  {new Date(spec.modifiedAt).toLocaleString(undefined, {
+   year: "numeric",
    month: "short",
    day: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx` around lines 143 - 155,
The displayed modified date for specs omits the year, making dates ambiguous;
update the rendering that uses new Date(spec.modifiedAt).toLocaleString(...) (in
SpecHeaderCard where spec.modifiedAt is used) to include the year (e.g. add
year: "numeric" to the options) so the formatted string shows month, day, year,
hour and minute consistently.

)}
<div className="flex items-center gap-1 ml-auto">
<Icon icon="lucide:file" size={12} />
<span className="font-mono">{spec.filePath.split("/").pop()}</span>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

split("/") assumes Unix-style path separators.

spec.filePath originates from Node's path.join() in planFileReader.ts, which uses \ on Windows. Prefer path.basename (server-side) or a cross-platform split on the client:

♻️ Proposed fix
-  <span className="font-mono">{spec.filePath.split("/").pop()}</span>
+  <span className="font-mono">{spec.filePath.split(/[\\/]/).pop()}</span>
📝 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
<span className="font-mono">{spec.filePath.split("/").pop()}</span>
<span className="font-mono">{spec.filePath.split(/[\\/]/).pop()}</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx` at line 158, The code in
SpecHeaderCard.tsx uses spec.filePath.split("/") which fails on Windows
backslashes; update the rendering of the filename (the span showing
spec.filePath.split("/").pop()) to derive the base name in a cross-platform way
— either have the server supply a basename (use path.basename in
planFileReader.ts) or change the client to split on both separators (e.g., split
by / or \ using a regex) before taking the last segment so spec.filePath always
yields the correct filename regardless of OS.

- Add project query param to deleteSpec frontend call and use
  resolveProjectRoot in handleDeletePlan backend (multi-project fix)
- Replace unreliable command_exists("hypothesis") with _is_hypothesis_installed
  using uv tool list
- Change ruff check to uv run ruff check in spec-bugfix-plan.md
- Add year to date formatting in SpecHeaderCard and archived specs dropdown
- Log errors instead of silently swallowing in getWorktreePlansDirs
- Return actual success/failure from install_pbt_tools instead of always True
@claude
Copy link

claude bot commented Feb 24, 2026

Claude finished @maxritter's task in 3m 52s —— View job


Incremental Code Review: feat: add bugfix spec workflow and viewer enhancements

  • Gather context
  • Read changed files to identify fixed/new issues
  • Post final review

Resolved Issues

The following issues from the previous review have been fixed in this push:

From prior Claude review:

  • Resolvedinstall_pbt_tools silently swallowed failures. Now accumulates ok flag and returns it; _install_with_spinner shows a warning when False is returned.
  • Resolvedcommand_exists("hypothesis") unreliable skip-check. Replaced with _is_hypothesis_installed() that uses uv tool list (dependencies.py:546-557), consistent with _is_fast_check_installed.

From CodeRabbit review:

  • Resolved"Feature" was a dead value in the specType union. Parser now always produces "Feature" | "Bugfix" with no undefined case (planFileReader.ts:56-57).
  • Resolved — Silent catch {} in getWorktreePlansDirs. Now logs the error properly via logger.error (planFileReader.ts:108-115).
  • Resolved (Critical)deleteSpec and handleDeletePlan ignored project scoping. Frontend now appends &project=... to the DELETE URL (index.tsx:152); backend uses resolveProjectRoot(this.dbManager, project) (PlanRoutes.ts:224-225).
  • Resolved — Date format omitted year in SpecHeaderCard.tsx. year: "numeric" added (SpecHeaderCard.tsx:149).
  • Resolved — Date format omitted year in archived specs dropdown. year: "numeric" added (index.tsx:287-290).
  • Resolvedinstall_pbt_tools nitpick: sub-step results now accumulated correctly.
  • Resolved — Best-effort failure test added (test_dependencies.py:1039-1048).
  • Resolved — Conditional spread in planFileReader.ts simplified to plain specType.

Still Open

(One-line each — no full detail since unchanged)

  • split("/") assumes Unix-only separators in SpecHeaderCard.tsx:160
  • TRY300: return inside try block in _is_fast_check_installed (dependencies.py:568)
  • setState called during render in index.tsx:93-99 (should be useEffect)
  • ruff check invocation in spec-bugfix-plan.md:404 (file excluded, unverifiable)
  • spec-bugfix-plan.md cross-references spec-plan.md for worktree creation (fragile doc dependency, excluded)
  • PlanInfo interface duplicated in five files (pre-existing)
  • plan-stats-endpoint.test.ts tests source strings, not behavior (pre-existing pattern)

New Issue

Minor: specType optionality in PlanInfo interface is now misleading

planFileReader.ts:20 and planFileReader.ts:56-57

The parser unconditionally assigns specType to "Feature" or "Bugfix":

const specType: "Feature" | "Bugfix" =
  typeMatch?.[1] === "Bugfix" ? "Bugfix" : "Feature";

Yet the PlanInfo interface still declares specType?: "Feature" | "Bugfix" (optional). Plans without a Type: header now silently default to "Feature" rather than being undefined, which is the right behavior — but the ? on the interface signals to consumers that the field might be absent, causing them to write unnecessary === "Bugfix" ? "Bugfix" : "Feature" ternaries. All consumers (SpecHeaderCard.tsx:117, PlanStatus.tsx:43-45, index.tsx:258-260) use the same fallback pattern that will now always evaluate to the truthy branch for any plan that came from this parser.

The optional marking is technically harmless (TypeScript doesn't error if a field declared optional is always present), but it's confusing. Removing ? from specType in planFileReader.ts's PlanInfo definition (and the four duplicated interface definitions in the other files) would accurately reflect the parser's guarantee.

Fix this →


Overall: this push addresses all blocking issues from the previous review. The remaining open items are minor/nitpick-level.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
installer/tests/unit/steps/test_dependencies.py (1)

34-57: ⚠️ Potential issue | 🟡 Minor

Assert the new PBT tools step is invoked.

The test patches install_pbt_tools but doesn’t assert it was called, so the new dependency step isn’t actually covered.

✅ Suggested assertion
             mock_python_tools.assert_called_once()
             mock_claude.assert_called_once()
             mock_plugin_deps.assert_called_once()
+            _mock_pbt_tools.assert_called_once()

As per coding guidelines, review test code briefly. Focus on: Test coverage for the feature being tested.

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

In `@installer/tests/unit/steps/test_dependencies.py` around lines 34 - 57, The
test test_dependencies_run_installs_core currently patches install_pbt_tools but
never asserts it was invoked; update this test to add an assertion that the
mocked install_pbt_tools was called (use the patched mock parameter named
_mock_pbt_tools or rename it to mock_pbt_tools) after running the code under
test, similar to how other patched installers are asserted; ensure the assertion
checks at least called_once() (or called) so the new PBT tools step is actually
covered.
♻️ Duplicate comments (1)
console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx (1)

158-160: ⚠️ Potential issue | 🟡 Minor

Use a cross‑platform basename for spec.filePath.

Line 160 uses split("/"), which fails on Windows path separators. This pattern also appears in the Spec view delete confirmation. Use a /[\\/]/ split or a shared helper.

🛠️ Proposed fix
-            <span className="font-mono">{spec.filePath.split("/").pop()}</span>
+            <span className="font-mono">
+              {spec.filePath.split(/[\\/]/).pop()}
+            </span>
#!/bin/bash
# Find other filePath basename extractions that use "/" only
rg -n 'filePath\.split\("/"\)' --type=ts --type=tsx
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx` around lines 158 - 160,
The code in SpecHeaderCard (and the duplicate in the Spec view delete
confirmation) uses spec.filePath.split("/") which breaks on Windows; replace
these occurrences with a cross-platform basename approach — either call a shared
helper like getBasename(filePath) or change the split to use a RegExp
split(/[\\/]/) and then .pop(), and update both SpecHeaderCard and the
delete-confirmation code to use the same helper/function to avoid duplication.
🧹 Nitpick comments (2)
docs/site/src/pages/docs/SpecSection.tsx (1)

115-117: Remove the superfluous empty <span>.

{/* bugfix auto-detected */} is a JSX comment; it renders nothing at runtime. The parent <span> is therefore an empty DOM node with Tailwind classes applied to nothing. Move the comment inline and drop the span entirely.

♻️ Proposed fix
-          <span className="text-muted-foreground/60 text-xs ml-2">
-            {/* bugfix auto-detected */}
-          </span>
+          {/* bugfix auto-detected */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/pages/docs/SpecSection.tsx` around lines 115 - 117, Remove the
empty span that contains only a JSX comment in the SpecSection component: the
<span className="text-muted-foreground/60 text-xs ml-2"> wrapping {/* bugfix
auto-detected */} should be deleted and the JSX comment moved inline where it
belongs (or removed entirely), ensuring no empty DOM node remains; search for
the span in SpecSection.tsx to locate the exact element to remove.
console/tests/worker/plan-stats-endpoint.test.ts (1)

57-62: SpecType test only asserts source strings—add a behavioral parse check.

This test only inspects source text, so a regression in parsePlanContent could still pass. Consider a unit test that feeds a plan string with Type: Bugfix (and one without Type) and asserts the parsed specType.

As per coding guidelines, **/tests/**: Review test code briefly. Focus on: Test coverage for the feature being tested

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

In `@console/tests/worker/plan-stats-endpoint.test.ts` around lines 57 - 62, The
current test only checks raw readerSource strings; instead add behavioral
assertions that call parsePlanContent directly: create one plan string
containing "Type: Bugfix" and another without a Type line, pass each to
parsePlanContent and assert the returned object has specType === "Bugfix" for
the first and specType === undefined (or null/empty per implementation) for the
second; update the test in plan-stats-endpoint.test.ts (which currently
references readerSource) to include these parsePlanContent calls and assertions
so regressions in parsing are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@console/src/services/worker/http/routes/PlanRoutes.ts`:
- Around line 51-55: The worktree path checks are not robust: replace the
current startsWith(worktreesDir) and resolvedPath.includes("/docs/plans/") logic
with the same cross-platform, separator-aware checks used for mainPlansDir —
ensure you check for worktreesDir followed by a separator boundary (e.g.,
worktreesDir + path.sep and worktreesDir + "/") to avoid matching prefixes like
".worktreesX", and check for the docs/plans segment using both path.sep and "/"
(or build it via path.join and test both separators) so resolvedPath is properly
matched on Windows and POSIX; update the checks around worktreesDir,
normalizedRoot, and resolvedPath in PlanRoutes.ts accordingly.

In `@installer/steps/dependencies.py`:
- Around line 546-589: The current check/install flow wrongly uses `uv tool
install hypothesis` for a non-CLI library and silences errors; update
_is_hypothesis_installed to detect the correct distribution and change
install_pbt_tools to install the correct package (either use `uv tool install
"hypothesis[cli]"` if you truly need the CLI or switch to `uv add
hypothesis`/project dependency management if you mean the library), and ensure
you call _run_bash_with_retry with the corrected command instead of the existing
string; also modify _is_hypothesis_installed and _is_fast_check_installed to log
exceptions (include the exception text) rather than swallowing them, and when
install_pbt_tools returns False log which tool failed (reference functions:
_is_hypothesis_installed, _is_fast_check_installed, install_pbt_tools,
_run_bash_with_retry, npm_global_cmd) so the user sees clear feedback on
failure.

---

Outside diff comments:
In `@installer/tests/unit/steps/test_dependencies.py`:
- Around line 34-57: The test test_dependencies_run_installs_core currently
patches install_pbt_tools but never asserts it was invoked; update this test to
add an assertion that the mocked install_pbt_tools was called (use the patched
mock parameter named _mock_pbt_tools or rename it to mock_pbt_tools) after
running the code under test, similar to how other patched installers are
asserted; ensure the assertion checks at least called_once() (or called) so the
new PBT tools step is actually covered.

---

Duplicate comments:
In `@console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx`:
- Around line 158-160: The code in SpecHeaderCard (and the duplicate in the Spec
view delete confirmation) uses spec.filePath.split("/") which breaks on Windows;
replace these occurrences with a cross-platform basename approach — either call
a shared helper like getBasename(filePath) or change the split to use a RegExp
split(/[\\/]/) and then .pop(), and update both SpecHeaderCard and the
delete-confirmation code to use the same helper/function to avoid duplication.

---

Nitpick comments:
In `@console/tests/worker/plan-stats-endpoint.test.ts`:
- Around line 57-62: The current test only checks raw readerSource strings;
instead add behavioral assertions that call parsePlanContent directly: create
one plan string containing "Type: Bugfix" and another without a Type line, pass
each to parsePlanContent and assert the returned object has specType ===
"Bugfix" for the first and specType === undefined (or null/empty per
implementation) for the second; update the test in plan-stats-endpoint.test.ts
(which currently references readerSource) to include these parsePlanContent
calls and assertions so regressions in parsing are caught.

In `@docs/site/src/pages/docs/SpecSection.tsx`:
- Around line 115-117: Remove the empty span that contains only a JSX comment in
the SpecSection component: the <span className="text-muted-foreground/60 text-xs
ml-2"> wrapping {/* bugfix auto-detected */} should be deleted and the JSX
comment moved inline where it belongs (or removed entirely), ensuring no empty
DOM node remains; search for the span in SpecSection.tsx to locate the exact
element to remove.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9a06ee and eb3d655.

⛔ Files ignored due to path filters (13)
  • launcher/statusline/formatter.py is excluded by !launcher/**
  • launcher/tests/unit/statusline/test_formatter.py is excluded by !launcher/**
  • launcher/tests/unit/test_worktree.py is excluded by !launcher/**
  • launcher/worktree.py is excluded by !launcher/**
  • pilot/commands/spec-bugfix-plan.md is excluded by !pilot/**
  • pilot/commands/spec-plan.md is excluded by !pilot/**
  • pilot/commands/spec.md is excluded by !pilot/**
  • pilot/rules/task-and-workflow.md is excluded by !pilot/**
  • pilot/scripts/mcp-server.cjs is excluded by !pilot/**
  • pilot/scripts/worker-service.cjs is excluded by !pilot/**
  • pilot/settings.json is excluded by !pilot/**
  • pilot/ui/viewer-bundle.js is excluded by !pilot/**
  • pilot/ui/viewer.css is excluded by !pilot/**
📒 Files selected for processing (15)
  • README.md
  • console/src/services/worker/http/routes/PlanRoutes.ts
  • console/src/services/worker/http/routes/utils/planFileReader.ts
  • console/src/ui/viewer/hooks/useStats.ts
  • console/src/ui/viewer/views/Dashboard/PlanStatus.tsx
  • console/src/ui/viewer/views/Spec/SpecHeaderCard.tsx
  • console/src/ui/viewer/views/Spec/index.tsx
  • console/tests/worker/plan-stats-endpoint.test.ts
  • docs/site/index.html
  • docs/site/src/components/WorkflowSteps.tsx
  • docs/site/src/pages/docs/QuickModeSection.tsx
  • docs/site/src/pages/docs/SpecSection.tsx
  • installer/steps/dependencies.py
  • installer/steps/finalize.py
  • installer/tests/unit/steps/test_dependencies.py

Comment on lines 51 to +55
const worktreesDir = path.join(normalizedRoot, ".worktrees");
if (resolvedPath.startsWith(worktreesDir) && resolvedPath.includes("/docs/plans/")) {
if (
resolvedPath.startsWith(worktreesDir) &&
resolvedPath.includes("/docs/plans/")
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n console/src/services/worker/http/routes/PlanRoutes.ts | sed -n '45,60p'

Repository: maxritter/claude-pilot

Length of output: 519


🏁 Script executed:

rg -n 'docs/plans' --type=ts

Repository: maxritter/claude-pilot

Length of output: 6407


🏁 Script executed:

rg -n 'worktreesDir|\.worktrees' --type=ts -A 2 -B 2

Repository: maxritter/claude-pilot

Length of output: 4949


Normalize worktree path checks to match mainPlansDir pattern.

The worktree validation on lines 53-54 is less robust than the mainPlansDir check immediately above it (lines 46-47). Line 54's hardcoded "/docs/plans/" will fail on Windows, and line 53's startsWith(worktreesDir) lacks a separator boundary, allowing .worktreesX prefixes to match. The mainPlansDir check already uses both path.sep and "/" for cross-platform safety; apply the same approach here:

Proposed fix
-  const worktreesDir = path.join(normalizedRoot, ".worktrees");
-  if (
-    resolvedPath.startsWith(worktreesDir) &&
-    resolvedPath.includes("/docs/plans/")
-  ) {
+  const worktreesDir = path.join(normalizedRoot, ".worktrees");
+  if (
+    (resolvedPath.startsWith(worktreesDir + path.sep) ||
+     resolvedPath.startsWith(worktreesDir + "/")) &&
+    (resolvedPath.includes(`${path.sep}docs${path.sep}plans${path.sep}`) ||
+     resolvedPath.includes("/docs/plans/"))
+  ) {
     return true;
   }
📝 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
const worktreesDir = path.join(normalizedRoot, ".worktrees");
if (resolvedPath.startsWith(worktreesDir) && resolvedPath.includes("/docs/plans/")) {
if (
resolvedPath.startsWith(worktreesDir) &&
resolvedPath.includes("/docs/plans/")
) {
const worktreesDir = path.join(normalizedRoot, ".worktrees");
if (
(resolvedPath.startsWith(worktreesDir + path.sep) ||
resolvedPath.startsWith(worktreesDir + "/")) &&
(resolvedPath.includes(`${path.sep}docs${path.sep}plans${path.sep}`) ||
resolvedPath.includes("/docs/plans/"))
) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/PlanRoutes.ts` around lines 51 - 55,
The worktree path checks are not robust: replace the current
startsWith(worktreesDir) and resolvedPath.includes("/docs/plans/") logic with
the same cross-platform, separator-aware checks used for mainPlansDir — ensure
you check for worktreesDir followed by a separator boundary (e.g., worktreesDir
+ path.sep and worktreesDir + "/") to avoid matching prefixes like
".worktreesX", and check for the docs/plans segment using both path.sep and "/"
(or build it via path.join and test both separators) so resolvedPath is properly
matched on Windows and POSIX; update the checks around worktreesDir,
normalizedRoot, and resolvedPath in PlanRoutes.ts accordingly.

Comment on lines +546 to +589
def _is_hypothesis_installed() -> bool:
"""Check if hypothesis is installed via uv tool."""
try:
result = subprocess.run(
["uv", "tool", "list"],
capture_output=True,
text=True,
timeout=10,
)
return result.returncode == 0 and "hypothesis" in result.stdout
except Exception:
return False


def _is_fast_check_installed() -> bool:
"""Check if fast-check is installed globally via npm."""
try:
result = subprocess.run(
["npm", "list", "-g", "fast-check", "--depth=0"],
capture_output=True,
text=True,
)
return result.returncode == 0 and "fast-check" in result.stdout
except Exception:
return False


def install_pbt_tools() -> bool:
"""Install property-based testing packages: hypothesis (Python) and fast-check (TypeScript).

Go PBT is handled by the built-in 'go test -fuzz' (Go 1.18+) — no install needed.
Both packages are best-effort: failure does not block installation.
"""
ok = True

if not _is_hypothesis_installed():
if not _run_bash_with_retry("uv tool install hypothesis"):
ok = False

if not _is_fast_check_installed():
if not _run_bash_with_retry(npm_global_cmd("npm install -g fast-check")):
ok = False

return ok
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

uv tool install hypothesis Python package support CLI requirements

💡 Result:

uv tool install is for installing executables (“commands”) exposed by a Python package (i.e., console/script entry points) into an isolated “tool” environment and linking them onto your PATH. [1][2]

Hypothesis + CLI with uv tool install

Hypothesis can have a CLI, but the CLI dependencies are provided as an optional extra named cli (e.g., click, black, rich). [3]
So, if you want the Hypothesis CLI available as a globally-invokable tool, install it with the extra:

uv tool install "hypothesis[cli]"

(Or run it without installing via uvx, which is an alias for uv tool run.) [2]

If you mean “requirements” (project dependencies) instead of a global tool

Use project/requirements.txt workflows instead of uv tool install, e.g.:

uv add hypothesis
# or
uv pip install -r requirements.txt

Sources: uv tool install reference [1], uv tools concept + uvx [2], Hypothesis optional cli extra [3].


Fix uv tool install hypothesis — wrong approach for non-CLI library and missing user feedback.

uv tool install is for CLI tools with console entry points; Hypothesis is a library without a CLI by default. This will always fail silently. The try/except blocks (lines 549–551, 564–566) catch exceptions but return False without logging or user feedback, leaving the developer unaware why the installation failed. Additionally, when install_pbt_tools() returns False (lines 586–587), there's no message explaining which tool failed or why.

Recommended fixes:

  • Use uv tool install "hypothesis[cli]" if a CLI is truly needed, or switch to a proper dependency management approach (e.g., uv add hypothesis for project dependencies).
  • Add logging or user-facing error messages when tool installations fail (violates "proper error handling and user feedback" guideline for installer code).
  • Consider documenting the intent: are these testing libraries meant for the installer environment or for projects it manages?
🧰 Tools
🪛 Ruff (0.15.2)

[error] 550-550: Starting a process with a partial executable path

(S607)


[warning] 555-555: Consider moving this statement to an else block

(TRY300)


[warning] 556-556: Do not catch blind exception: Exception

(BLE001)


[error] 564-564: Starting a process with a partial executable path

(S607)


[warning] 568-568: Consider moving this statement to an else block

(TRY300)


[warning] 569-569: Do not catch blind exception: Exception

(BLE001)

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

In `@installer/steps/dependencies.py` around lines 546 - 589, The current
check/install flow wrongly uses `uv tool install hypothesis` for a non-CLI
library and silences errors; update _is_hypothesis_installed to detect the
correct distribution and change install_pbt_tools to install the correct package
(either use `uv tool install "hypothesis[cli]"` if you truly need the CLI or
switch to `uv add hypothesis`/project dependency management if you mean the
library), and ensure you call _run_bash_with_retry with the corrected command
instead of the existing string; also modify _is_hypothesis_installed and
_is_fast_check_installed to log exceptions (include the exception text) rather
than swallowing them, and when install_pbt_tools returns False log which tool
failed (reference functions: _is_hypothesis_installed, _is_fast_check_installed,
install_pbt_tools, _run_bash_with_retry, npm_global_cmd) so the user sees clear
feedback on failure.

…eping

Playwright install tests and auth fallback test were hitting real
time.sleep(2) during retry loops, adding ~14s to CI. Patch
time.sleep in the 4 affected tests.
@claude
Copy link

claude bot commented Feb 24, 2026

Claude finished @maxritter's task in 1m 48s —— View job


Incremental Code Review: feat: add bugfix spec workflow and viewer enhancements

  • Gather context
  • Read changed files from this push
  • Post final review

Push Summary

This push (0b07e3f) adds time.sleep mocking to three Playwright installer tests to eliminate 14s of real sleeping during retry-loop test execution. The launcher/tests/unit/test_auth.py change is in an encrypted path and was skipped.


Changes in This Push

installer/tests/unit/steps/test_dependencies_playwright.py

Three tests had @patch("installer.steps.dependencies.time.sleep") added:

  • test_returns_false_on_failure
  • test_returns_false_on_exception
  • test_returns_false_when_browser_install_fails

The fix is correct. Mock parameter ordering matches decorator application order (bottom-to-top) in all three cases. The _mock_sleep naming convention (underscore prefix for unused params) is consistent with the rest of the test suite.

No new issues introduced.


Still Open

(Unresolved from prior reviews — one line each)

  • split("/") Unix-only separator in SpecHeaderCard.tsx:160
  • TRY300: return inside try in _is_fast_check_installed (dependencies.py)
  • setState during render in index.tsx:93-99 (should be useEffect)
  • specType? optionality in PlanInfo is misleading — parser now always assigns the field
  • Worktree path checks lack separator boundary in PlanRoutes.ts:55
  • _mock_pbt_tools patched but never asserted in test_dependencies_run_installs_core
  • Empty <span> with JSX-only comment in SpecSection.tsx:115-117
  • plan-stats-endpoint.test.ts tests source strings rather than parsing behavior
  • PlanInfo interface duplicated across five files (pre-existing)

@maxritter maxritter merged commit 70c1119 into main Feb 24, 2026
13 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 24, 2026
# [6.11.0](v6.10.3...v6.11.0) (2026-02-24)

### Features

* add bugfix spec workflow and viewer enhancements ([#85](#85)) ([70c1119](70c1119))
@github-actions
Copy link
Contributor

🎉 This PR is included in version 6.11.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant