<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: drmikecrowe</title>
    <description>The latest articles on DEV Community by drmikecrowe (@drmikecrowe).</description>
    <link>https://dev.to/drmikecrowe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236482%2F461a5f5c-6342-4928-a444-57e99a32b97a.png</url>
      <title>DEV Community: drmikecrowe</title>
      <link>https://dev.to/drmikecrowe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/drmikecrowe"/>
    <language>en</language>
    <item>
      <title>Context Binoculars: Understanding LLM Context Windows</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sat, 13 Sep 2025 04:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/context-binoculars-understanding-llm-context-windows-2h95</link>
      <guid>https://dev.to/drmikecrowe/context-binoculars-understanding-llm-context-windows-2h95</guid>
      <description>&lt;p&gt;Remember as a kid (or in my case as an adult) when you pulled out binoculars and gazed around the room? It’s a bit disorienting for sure, but also fun. And I guess if you’ve ever wondered about what I do for fun, now you know.&lt;/p&gt;

&lt;p&gt;It struck me the other day that this was the ideal representation for how LLM context works. While my demonstration you’re going to see below is extreme, this is the reality of what we’re facing when we’re trying to get LLMs to do big projects. The context window is everything, and you’re hearing more and more about context engineering in online articles. Ensuring that the LLM has exactly the right context greatly improves the accuracy of the results that you get out the other end.&lt;/p&gt;

&lt;p&gt;There's not much more to this article. All I'm really trying to do is viscerally explain what the context window is and give you a feeling of why managing your context is so important.&lt;/p&gt;

&lt;p&gt;Check out the visual demonstration here: &lt;a href="https://drmikecrowe.github.io/context-binoculars/" rel="noopener noreferrer"&gt;context-binoculars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From Linter Chaos to Orchestrated Tasks - Part 4 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah</link>
      <guid>https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 4 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the fourth installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;In my last post, I talked about how my AI pair programmer and I created a custom ESLint rule to enforce our new frontend architecture. It was a huge success. The linter, now armed with our specific rules, scanned the codebase and… gave us a giant list of things to fix.&lt;/p&gt;

&lt;p&gt;This is a classic “good news, bad news” scenario. The good news? We had a precise, automated way to detect architectural drift. The bad news? We had a long, intimidating wall of terminal output listing every single violation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/home/mcrowe/.../AlertDialog.tsx
  19:8 warning Unregistered class detected: data-[state=open]:animate-in
  19:37 warning Unregistered class detected: data-[state=closed]:animate-out
...
/home/mcrowe/.../EmptyState.tsx
  25:21 error UI Component contains a forbidden margin class: 'mb-4'
...
✖ 60 problems (2 errors, 58 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A raw list of errors is a starting point, but it’s not a plan. How do you manage this work? How do you distribute it? How do you ensure each fix is made with the right context? Just handing a developer (or an AI) a raw log is inefficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool: TaskFlow MCP for Orchestration
&lt;/h2&gt;

&lt;p&gt;This is where another tool in our AI-assisted workflow comes in: &lt;a href="https://github.com/pinkpixel-dev/taskflow-mcp" rel="noopener noreferrer"&gt;TaskFlow MCP&lt;/a&gt;. It’s an open-source task orchestration server designed specifically for managing development work between humans and AI agents.&lt;/p&gt;

&lt;p&gt;Instead of just working from a simple prompt, TaskFlow allows us to break down a high-level request (like “Fix all these linter warnings”) into a structured plan of discrete, trackable tasks.&lt;/p&gt;

&lt;p&gt;Crucially, as the user, I can insist that &lt;strong&gt;each task be configured with specific context and instructions&lt;/strong&gt;. This is the killer feature. It turns a simple “fix this” command into a rich, context-aware work order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process: Turning Chaos into a Plan
&lt;/h2&gt;

&lt;p&gt;I fed the messy ESLint output to my AI assistant and gave it a new prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For each of the UI warnings below, use &lt;code&gt;plan_task&lt;/code&gt; to create a ‘Fix UI layout vs. styling overlap’ request. For each violation, create a sub-task. Ensure that each task specifically includes these instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;memory-bank/AGENTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Review &lt;code&gt;memory-bank/project-rules/*&lt;/code&gt;"&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI, using the TaskFlow MCP tool, did exactly that. It parsed the linter output, grouped the violations by file, and created a formal plan. Each task was not just a file path and a line number; it was a complete work package containing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Clear Title&lt;/strong&gt; : e.g., “Fix AppLayout.tsx layout component styling violations”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Detailed Description&lt;/strong&gt; : Explaining exactly which classes were violating our architectural rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded Context&lt;/strong&gt; : The prerequisite instructions to review our project’s agent protocols and architectural rules before starting work.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Result: An Actionable, Context-Aware Plan
&lt;/h2&gt;

&lt;p&gt;Tasks are maintained locally&lt;/p&gt;

&lt;p&gt;The output wasn’t a log file; it was a professional task report, ready for any developer—human or AI—to pick up and execute. It transformed a chaotic list of problems into a manageable, parallelizable set of tasks.&lt;/p&gt;

&lt;p&gt;Here is the exact markdown file that the tool generated. It’s clear, organized, and ensures that whoever does the work has the full context required to do it right.&lt;/p&gt;




&lt;h1&gt;
  
  
  Task Status Report: Fix UI layout vs. styling overlap issues identified in ESLint warnings
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Generated on: 2025-08-17&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overall Progress: 0%
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total Tasks:&lt;/strong&gt; 9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completed Tasks:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved Tasks:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remaining Tasks:&lt;/strong&gt; 9&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Task Status
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Fix AppLayout.tsx layout component styling violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove non-layout classes from AppLayout.tsx component. The component contains styling classes like bg-gray-100, rounded-md, hover:bg-gray-100, focus:ring-2, text-purple-700, ml-2, text-lg, font-bold, text-gray-900, bg-opacity-30, bg-black that should be moved to separate styling components or variants. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fix Sidebar.tsx layout component styling violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove non-layout classes from Sidebar.tsx component. The component contains styling classes like ml-2, text-lg, font-bold, tracking-tight, text-gray-900, mt-4, text-sm, text-gray-500, hover:text-gray-700, rounded-md, font-medium, text-gray-600, transition-colors, hover:bg-blue-50, hover:text-blue-700, text-gray-400, mt-2 that should be moved to separate styling components or variants. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fix EmptyState.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from EmptyState.tsx component. The component contains margin classes mb-4, mb-2, mb-6 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Fix ErrorState.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from ErrorState.tsx component. The component contains margin classes mt-4, mt-2 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Fix FileDropzone.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from FileDropzone.tsx component. The component contains margin classes mx-auto, mr-2 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Fix MainContent.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from MainContent.tsx component. The component contains margin class mx-auto that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Fix StatCard.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from StatCard.tsx component. The component contains margin classes mt-1, mt-2, ml-1 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Fix ClearSearchButton.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from ClearSearchButton.tsx component. The component contains margin class my-auto that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Fix PageHeader.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from PageHeader.tsx component. The component contains margin class mt-1 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The Ultimate Design Review: Orchestrating AI with Task-Based Workflows - Part 6 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0</link>
      <guid>https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 6 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the final installment in our six-part series on building an AI-assisted development workflow. We’ve set up our infrastructure, taught the AI our coding standards, and established a robust “memory bank” of project rules. Now, it’s time to put it all to the test with the ultimate challenge: a comprehensive, end-to-end design review of the entire codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Want to see what we're building? I've created two example posts that show the actual output from this automated design review system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://mikesshinyobjects.tech/posts/2025/2025-09-06-1-design-review-feedback/" rel="noopener noreferrer"&gt;Design Review Feedback Example&lt;/a&gt;&lt;/strong&gt; - See the comprehensive analysis that the AI generates, including architectural strengths, critical issues, and detailed findings across all layers of the codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://mikesshinyobjects.tech/posts/2025/2025-09-06-2-design-review-action-items/" rel="noopener noreferrer"&gt;Design Review Action Items Example&lt;/a&gt;&lt;/strong&gt; - See how the AI transforms the analysis into a structured, actionable backlog with 18 major tasks and 72 subtasks, each with clear implementation steps and acceptance criteria.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples show the real output from running this system on an actual codebase. The AI identified critical architectural violations, generated specific improvement tasks, and created a comprehensive project plan - all automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series Overview:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk"&gt;MCP Servers, Ports, and Sharing - Setting up the foundation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47"&gt;ESLint Configuration Refactoring - Cleaning up tooling with AI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o"&gt;Custom Architectural Rules - Teaching AI to enforce design patterns&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah"&gt;Task Orchestration - Managing complex refactoring workflows&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c"&gt;Project Rules for AI - Creating effective memory banks and guidelines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0"&gt;The Ultimate Design Review - Putting it all together&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Challenge: From “Codebase” to “Action Plan”
&lt;/h2&gt;

&lt;p&gt;How do you review an entire &lt;code&gt;src/&lt;/code&gt; directory? You can’t just feed it to an AI and say, “find problems.” The context is too large, the potential feedback is too broad, and the output would be a wall of text, not an actionable plan. To understand the issue, grab a pair of binoculars and use them in your office: Have a coworker toss an index card somewhere in your office and then search for that with the binoculars. If you have a really large code base, have your coworker put it in the office somewhere. That’s what your fighting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The binoculars symbolize the AI context. It’s a fixed width window of information the AI can maintain&lt;/li&gt;
&lt;li&gt;The index card is what you are asking the AI to find&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My goal was to create a system that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Systematically analyze the entire codebase in manageable chunks.&lt;/li&gt;
&lt;li&gt;Evaluate each chunk against multiple, specific architectural rules.&lt;/li&gt;
&lt;li&gt;Produce a structured, prioritized, and actionable list of tasks.&lt;/li&gt;
&lt;li&gt;Be entirely automated and repeatable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To achieve this, I created a multi-layered prompting strategy that uses the &lt;a href="https://github.com/pinkpixel-dev/taskflow-mcp" rel="noopener noreferrer"&gt;TaskFlow MCP server&lt;/a&gt; to orchestrate the entire workflow. The process is driven by two key prompt files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Master Plan: &lt;code&gt;new-design-review.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first file, &lt;code&gt;@memory-bank/prompts/design-review/new-design-review.md&lt;/code&gt;, is the high-level orchestrator. It doesn’t perform the analysis itself; it instructs the AI on how to set up the entire project plan within TaskFlow tasks.&lt;/p&gt;

&lt;p&gt;Here’s the strategy it lays out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create Two Buckets:&lt;/strong&gt; The first thing it does is create two separate requests in the task server: one for the &lt;em&gt;analysis process&lt;/em&gt; and one for the &lt;em&gt;action items&lt;/em&gt;. This is a critical separation of concerns. The analysis tasks will be marked as “done” once the review is complete, but the action items will form a living backlog of work to be done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Divide and Conquer:&lt;/strong&gt; It then breaks the entire &lt;code&gt;src/&lt;/code&gt; directory into logical chunks: Core App Files, Components, Services, Machines, Hooks, etc. For each chunk, it creates a parent task in the “analysis” request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Mini-Review Subtasks:&lt;/strong&gt; This is where the granularity comes in. For each parent task (e.g., &lt;code&gt;DR Part 3 - Components (Dashboard)&lt;/code&gt;), the prompt instructs the AI to create four specific subtasks:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Instruction Manual: &lt;code&gt;request-2-instructions.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If the first prompt is the “what,” then &lt;code&gt;@memory-bank/prompts/design-review/request-2-instructions.md&lt;/code&gt; is the “how.” This file is a set of instructions for the AI on how to behave &lt;em&gt;while it is executing&lt;/em&gt; the analysis subtasks.&lt;/p&gt;

&lt;p&gt;Its most critical directive is this: &lt;strong&gt;You MUST create new tasks in the “Action Items” request for every issue you find.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It strictly forbids the AI from simply summarizing its findings. Instead, it must translate every identified improvement into a well-formed task, complete with a title, description, and severity, and add it to the other request bucket. This is what turns a qualitative review into a quantitative project plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: An Actionable Backlog
&lt;/h2&gt;

&lt;p&gt;After running the AI through this workflow, the &lt;code&gt;taskflow-mcp&lt;/code&gt; server populates a YAML file with the results. The file from my design review, &lt;code&gt;@DESIGN-REVIEWS/2025-08-19/tasks.yaml&lt;/code&gt;, is over 800 lines long, but here are a few snippets that show the power of this system.&lt;/p&gt;

&lt;p&gt;First, you can see the completed analysis task for the “Core App Files,” with its four subtasks all marked as done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-62
  title: DR Part 1 - Core App Files
  description: |-
    Analyze the following files for design patterns, code quality, and architectural consistency:nn- src/App.tsxn- src/appMachineContext.tsn- src/config.tsn- src/context/DashboardDataContext.tsx
    ...
  done: true
  subtasks:
    - id: subtask-71
      title: Overall Design Analysis
      done: true
    - id: subtask-72
      title: Effect Pattern Analysis
      done: true
    - id: subtask-73
      title: Component Styling Analysis
      done: true
    - id: subtask-74
      title: State Machine Analysis
      done: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More importantly, the &lt;em&gt;real&lt;/em&gt; output is the list of actionable tasks generated in the other request. The AI identified dozens of specific, granular improvements, each perfectly formatted according to the instruction prompt.&lt;/p&gt;

&lt;p&gt;Here are a few examples of the generated tasks:&lt;/p&gt;

&lt;p&gt;A high-priority refactoring task based on our Effect pattern rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-98
  title: Add Effect-based validation to ExcelService
  description: ExcelService.ts contains complex validation logic in generateExpenseReport function. Add Effect-based validation using E.succeed().pipe(E.filterOrFail()) pattern for report and expense data validation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A task to fix a clear violation of our Tailwind CSS architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-139
  title: Fix EmptyState.tsx UI component margin violations
  description: Remove margin classes from EmptyState.tsx component. The component contains margin classes mb-4, mb-2, mb-6 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A task for a significant architectural improvement to break up a monolithic file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-99
  title: Extract ExcelService helper functions to separate modules
  description: ExcelService.ts is a 485-line monolith with many helper functions. Extract helper functions into separate modules (excel-formatting.ts, excel-data.ts, excel-styles.ts) to improve maintainability and testability.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion: The Sum of the Parts
&lt;/h2&gt;

&lt;p&gt;This automated design review is the culmination of our entire AI-assisted workflow. It seamlessly integrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Agent Protocol (&lt;code&gt;AGENTS.md&lt;/code&gt;):&lt;/strong&gt; Provides the core principles for the AI’s analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Project Rules (&lt;code&gt;project-rules/&lt;/code&gt;):&lt;/strong&gt; Act as the specific, enforceable standards for the review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Prompting Strategy (&lt;code&gt;prompts/&lt;/code&gt;):&lt;/strong&gt; Orchestrates the complex workflow, breaking it down into manageable steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Task Server (&lt;code&gt;taskflow-mcp&lt;/code&gt;):&lt;/strong&gt; Captures the output, transforming a review into an actionable project backlog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By investing the time to teach the AI &lt;em&gt;how&lt;/em&gt; we build software, we’ve unlocked the ability to automate one of the most time-consuming and critical parts of the development lifecycle. We’ve moved beyond simple code generation to a world where our AI partner can actively help us manage quality, reduce technical debt, and enforce architectural consistency at scale. It’s a powerful glimpse into the future of software development.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Project Rules for AI - Part 5 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c</link>
      <guid>https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 5 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the fifth installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;In the previous posts, we’ve taught our AI assistant &lt;em&gt;how&lt;/em&gt; to understand and enforce specific architectural rules. We’ve built a robust system for linting, testing, and managing complex tasks. But how do we ensure the AI behaves consistently and predictably over the long term? How do we give it a “personality” and a “memory” that aligns with our project’s philosophy?&lt;/p&gt;

&lt;p&gt;The answer lies in creating a comprehensive set of guidelines that the AI can consult before every action. In my setup, this is split into two key parts: the &lt;strong&gt;Agent Protocol&lt;/strong&gt; and the &lt;strong&gt;Project Rules&lt;/strong&gt;. Together, they form the “brain” of my AI development partner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agent Protocol: Defining the Personality
&lt;/h2&gt;

&lt;p&gt;The first piece of the puzzle is the &lt;code&gt;memory-bank/AGENTS.md&lt;/code&gt; file. I think of this as the AI’s constitution or its core programming. It doesn’t contain project-specific code rules; instead, it defines the AI’s high-level behavior, its interaction style, and its core principles.&lt;/p&gt;

&lt;p&gt;You can see some of the key sections from my &lt;code&gt;AGENTS.md&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Philosophy &amp;amp; Attitude&lt;/strong&gt; : This sets the tone. I want an “Expert Peer,” not a subservient assistant. It should be direct, proactive, and value clean code principles above all else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction Protocol&lt;/strong&gt; : This defines how we communicate. No lectures, no fluff. It must clarify ambiguity and warn me if I’m asking it to do something that violates a convention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Generation &amp;amp; Modification&lt;/strong&gt; : This governs how it writes code. It should edit files directly, respect formatting, and, most importantly, follow a specific code review process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context &amp;amp; Memory Bank&lt;/strong&gt; : This establishes the hierarchy of knowledge. The &lt;code&gt;memory-bank/&lt;/code&gt; is the single source of truth, more important than the codebase, conversation history, or its own external knowledge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This file establishes a baseline of behavior. It ensures that no matter what the specific task is, the AI approaches it with the same professional, efficient, and principle-driven mindset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project Rules: The Letter of the Law
&lt;/h2&gt;

&lt;p&gt;If the &lt;code&gt;AGENTS.md&lt;/code&gt; is the constitution, the files in &lt;code&gt;memory-bank/project-rules/&lt;/code&gt; are the specific, legally-binding statutes of the project. These are granular, often file-specific rules that the AI &lt;em&gt;must&lt;/em&gt; follow.&lt;/p&gt;

&lt;p&gt;These rules cover everything from technology usage to coding patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;00-pnpm.md&lt;/code&gt;: “Always use pnpm for package management. Period.”&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;02-effect-pattern.md&lt;/code&gt;: A detailed, multi-point guide on how to use the Effect library, including import styles, composition patterns, and where &lt;code&gt;E.runPromise&lt;/code&gt; is allowed to be called (hint: only in XState machines).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;08-tailwind.md&lt;/code&gt;: A treatise on our Tailwind CSS architecture, enforcing the strict separation of layout and style components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;09-state-machine-patterns.md&lt;/code&gt;: Defines the actor-based hierarchy for our XState implementation, ensuring actions are routed correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules are not suggestions; they are directives. The &lt;code&gt;AGENTS.md&lt;/code&gt; file instructs the AI to treat the &lt;code&gt;memory-bank/&lt;/code&gt; as its authoritative source of truth, and these files are the content of that memory. When I ask the AI to write a new component, it knows it must use CVA, it must not include margins, and it must get its available actions from a state machine actor—not because I told it to in the prompt, but because the rules demand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the Rules into Practice: Prompting with Precision
&lt;/h2&gt;

&lt;p&gt;Having this well-documented set of rules is powerful, but the real leverage comes from how I use them to guide the AI’s work. The rules aren’t just a passive library; they are an active part of my prompting strategy.&lt;/p&gt;

&lt;p&gt;A perfect example is my process for a comprehensive design review, which will be the topic of the next blog post. I don’t just ask the AI to “review the code.” I give it a highly structured plan that explicitly references the project rules at each step.&lt;/p&gt;

&lt;p&gt;Using a task planning tool, I create a master “Design Review” request, which is then broken down into smaller, logical chunks (e.g., “DR Part 1 - Core App Files”, “DR Part 2 - Components”). For each of these chunks, I create a series of subtasks, and this is where the magic happens. The description for each subtask points the AI to the exact rule it needs to enforce.&lt;/p&gt;

&lt;p&gt;For example, the subtask for analyzing Effect patterns looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title&lt;/strong&gt; : “Effect Pattern Analysis”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Description&lt;/strong&gt; : “Analyze Effect usage against &lt;code&gt;memory-bank/project-rules/02-effect-pattern.md&lt;/code&gt;. Identify pattern violations, missing Effect implementations, and improvement opportunities. Generate specific action items.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do the same for other concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component Styling Analysis&lt;/strong&gt; : “Review component styling against &lt;code&gt;memory-bank/project-rules/08-tailwind.md&lt;/code&gt;…”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Machine Analysis&lt;/strong&gt; : “Assess state machine implementations against &lt;code&gt;memory-bank/project-rules/09-state-machine-patterns.md&lt;/code&gt;…”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach transforms the memory bank from a simple knowledge base into a set of precise instruments. I can direct the AI’s focus with surgical accuracy, ensuring that each part of the codebase is evaluated against the correct, pre-defined standards. It’s a clear, explicit contract: “For this task, these are the rules that matter.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Killer Feature: The AI Code Review
&lt;/h2&gt;

&lt;p&gt;This brings me to the single most valuable instruction in the entire system. Buried in the &lt;code&gt;AGENTS.md&lt;/code&gt; file is a simple-sounding, yet profound, directive:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Code Review Process&lt;/strong&gt; : After making ANY significant code changes, conduct an internal round table discussion with David Thomas (The Pragmatic Programmer), Andrew Hunt (The Pragmatic Programmer), and Uncle Bob (Clean Code). If they would approve, proceed; if not, iterate until they would approve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where the magic happens.&lt;/p&gt;

&lt;p&gt;This instruction forces the AI to pause and reflect. It takes its generated code, which already conforms to the strict &lt;strong&gt;Project Rules&lt;/strong&gt; I’ve pointed it to, and then evaluates it against the higher-level principles of clean, pragmatic, and maintainable code embodied by these three legends of software engineering.&lt;/p&gt;

&lt;p&gt;It’s a two-layer filter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the code follow the rules?&lt;/strong&gt; (e.g., Is the Effect &lt;code&gt;pipe()&lt;/code&gt; structured correctly? Are the Tailwind classes semantic?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the code &lt;em&gt;good&lt;/em&gt;?&lt;/strong&gt; (e.g., Is it simple? Is it maintainable? Is there a better way to do this?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This internal dialogue has been a game-changer. The AI doesn’t just produce code that works; it produces code that is thought-out, clean, and aligned with the project’s deepest architectural and philosophical goals. It catches things I would miss in a manual code review, and it does it instantly. It’s like having a senior architect constantly looking over your shoulder, providing expert feedback in real-time.&lt;/p&gt;

&lt;p&gt;By combining a high-level “personality” with a set of explicit, enforceable project rules, and then actively using those rules in our daily prompts, we create an AI assistant that is not just a tool, but a true partner in the development process.&lt;/p&gt;

&lt;p&gt;In the final post of this series, we’ll put all these pieces together and see how this system performs in a full-scale, complex design review.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How I Taught My AI Pair Programmer to Be Our Team's Tailwind CSS Cop - Part 3 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o</link>
      <guid>https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 3 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the third installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;We’ve all been there. You start a new project with Tailwind CSS, and everything is beautiful. The utility-first approach is fast, flexible, and keeps you right in your HTML. But as the project grows and the team expands, the CSS landscape can start to feel like the Wild West. Utility classes get sprinkled everywhere, components start to blur the lines between structure and style, and soon you’re overriding margins and fighting for specificity.&lt;/p&gt;

&lt;p&gt;I love Tailwind, but I knew we needed to introduce some architectural discipline before things got out of hand. The problem is, architectural rules are only as good as their enforcement. Documentation gets stale, and nagging in code reviews doesn’t scale.&lt;/p&gt;

&lt;p&gt;So, I had a thought. What if I could teach our team’s AI pair programmer to be our automated style cop? What if it could not only understand our rules but actively enforce them?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Convention: A Three-Tier System for Sanity
&lt;/h2&gt;

&lt;p&gt;First, we needed a clear, simple convention. We decided on a three-tier component architecture designed to enforce a strict separation of concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layout Components (&lt;code&gt;src/components/layout/&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI Components (&lt;code&gt;src/components/ui/&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature &amp;amp; Page Components (&lt;code&gt;src/pages/&lt;/code&gt;, etc.)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This system ensures that our UI components are truly reusable and that our layout logic is centralized and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Hiring an AI for Architectural Enforcement
&lt;/h2&gt;

&lt;p&gt;With the convention defined, I turned to my AI assistant. I described the three-tier system and asked, “Can you build a custom ESLint rule to enforce this?”&lt;/p&gt;

&lt;p&gt;Working with the AI was like having a hyper-competent junior developer who knew the ESLint AST (Abstract Syntax Tree) better than I ever will. I provided the high-level architectural goals, and it handled the implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Custom Plugin Works
&lt;/h3&gt;

&lt;p&gt;The resulting ESLint rule is a simple but powerful detective. For every component file, it performs three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Location Check&lt;/strong&gt; : First, it looks at the component’s file path. Does it live in &lt;code&gt;src/components/layout/&lt;/code&gt;, &lt;code&gt;src/components/ui/&lt;/code&gt;, or somewhere else? This determines which rulebook to apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class Inspection&lt;/strong&gt; : Next, it parses the JSX and extracts all the Tailwind classes from the &lt;code&gt;className&lt;/code&gt; prop. It’s smart enough to handle string literals and basic template literals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule Application&lt;/strong&gt; : Finally, it applies the logic:

&lt;ul&gt;
&lt;li&gt;If it’s a &lt;strong&gt;UI Component&lt;/strong&gt; , it scans the class list for any margin patterns (&lt;code&gt;m-*&lt;/code&gt;, &lt;code&gt;mx-*&lt;/code&gt;, &lt;code&gt;mt-*&lt;/code&gt;, etc.) and flags them as errors.&lt;/li&gt;
&lt;li&gt;If it’s a &lt;strong&gt;Layout Component&lt;/strong&gt; , it checks for any non-layout classes (like &lt;code&gt;bg-red-500&lt;/code&gt;, &lt;code&gt;font-bold&lt;/code&gt;, etc.) and flags those.&lt;/li&gt;
&lt;li&gt;If it’s a &lt;strong&gt;Feature/Page Component&lt;/strong&gt; , it does nothing. They are free to compose as they see fit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI generated the rule file, a utility file to hold the class definitions, and even modified our main &lt;code&gt;eslint.config.js&lt;/code&gt; to wire it all up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: The Moment of Truth
&lt;/h2&gt;

&lt;p&gt;I ran the linter across our codebase, and it worked perfectly. It immediately flagged several components that were violating our new convention. Here’s a summary of what it caught:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AppLayout.tsx &amp;amp; Sidebar.tsx&lt;/strong&gt; : These core layout components were caught using styling classes like &lt;code&gt;bg-gray-100&lt;/code&gt;, &lt;code&gt;text-purple-700&lt;/code&gt;, and &lt;code&gt;font-bold&lt;/code&gt;. The linter correctly identified these as style concerns that should not be in a layout component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EmptyState.tsx &amp;amp; ErrorState.tsx&lt;/strong&gt; : These UI components had &lt;code&gt;mb-4&lt;/code&gt; and &lt;code&gt;mt-2&lt;/code&gt; margins, violating our “marginless UI” rule. The parent component should be responsible for this spacing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FileDropzone.tsx &amp;amp; MainContent.tsx&lt;/strong&gt; : These UI components were using &lt;code&gt;mx-auto&lt;/code&gt;, another margin-related property that is the responsibility of a layout container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StatCard.tsx&lt;/strong&gt; : This UI component was using &lt;code&gt;mt-1&lt;/code&gt; and &lt;code&gt;ml-1&lt;/code&gt;, which are small but important violations of the architectural pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these warnings was a clear, actionable task for us to clean up the codebase and align it with our new, more maintainable architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: More Than Just Code Generation
&lt;/h2&gt;

&lt;p&gt;This experience was a powerful demonstration of how to use LLMs for more than just generating boilerplate or fixing simple bugs. By teaching the AI our team’s specific architectural patterns, we were able to turn it into a partner for enforcing code quality and consistency at scale. It automated the tedious work of code validation, freeing us up to focus on building features.&lt;br&gt;&lt;br&gt;
tures.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Must read</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sat, 06 Sep 2025 21:59:25 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/must-read-1m1o</link>
      <guid>https://dev.to/drmikecrowe/must-read-1m1o</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-story__hidden-navigation-link"&gt;Getting Started with Multi-MCP Using Hatago MCP Hub — One Config to Connect Them All&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/himorishige" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3473639%2F7a720bf9-6781-43fa-a22a-997fd3002667.jpg" alt="himorishige profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/himorishige" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hi MORISHIGE
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hi MORISHIGE
                
              
              &lt;div id="story-author-preview-content-2813109" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/himorishige" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3473639%2F7a720bf9-6781-43fa-a22a-997fd3002667.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hi MORISHIGE&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 1 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" id="article-link-2813109"&gt;
          Getting Started with Multi-MCP Using Hatago MCP Hub — One Config to Connect Them All
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloudflare"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloudflare&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hono"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hono&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>mcp</category>
      <category>ai</category>
      <category>cloudflare</category>
      <category>hono</category>
    </item>
    <item>
      <title>MCP Servers, Ports, and Sharing - Part 1 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk</link>
      <guid>https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 1 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the first installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of using an AI-assisted development environment effectively, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;I’ve been spending a lot of time lately in the world of AI agents… wait, no, that’s not it. The &lt;em&gt;Model Context Protocol&lt;/em&gt;. MCP. It’s a fancy way of saying “a way for AI models to talk to tools,” and it’s pretty powerful. But like any new toy, it comes with its own set of “some assembly required” headaches. Today, I want to talk about one of those: managing MCP servers, avoiding port conflicts, and generally keeping your digital workspace from turning into a tangled mess of wires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What in the World is an MCP Server?
&lt;/h2&gt;

&lt;p&gt;Think of an MCP server as a translator. Your AI model speaks “I want to do something,” and the MCP server translates that into “Okay, computer, run this specific command.” It’s a bridge between the high-level thinking of the AI and the low-level reality of your machine.&lt;/p&gt;

&lt;p&gt;You need to know about them because, suddenly, you’re not just running a single AI model; you’re running a whole suite of tiny little helper applications. Each one of these helpers, or “tools,” might need its own MCP server. Want to browse the web? That’s a server. Want to read and write files? That’s another server. Before you know it, you’ve got a whole digital office full of very specialized, very chatty interns.&lt;/p&gt;

&lt;p&gt;So, when do you start one? The simple answer is: when you want to use a tool. The more complicated answer is: when you want to use a tool that needs a dedicated process to listen for instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nomenclature
&lt;/h2&gt;

&lt;p&gt;Before we dive deeper, let’s clarify some terminology that might seem counterintuitive at first. In the MCP ecosystem, what we call an “MCP server” is actually a &lt;strong&gt;client&lt;/strong&gt; from the perspective of your editor or CLI tool.&lt;/p&gt;

&lt;p&gt;Here’s the mental model: Your editor (like VS Code, Cursor, Windsurf) or CLI tool is the “server” that coordinates everything. It’s the central hub that manages the conversation with the AI model and decides which tools to invoke. The MCP servers are specialized “clients” that connect to this central hub to provide specific capabilities.&lt;/p&gt;

&lt;p&gt;Think of it like a restaurant: Your editor is the head chef who takes orders and coordinates the kitchen. The MCP servers are like specialized sous chefs - each one is an expert at a particular type of cooking (browsing the web, reading files, querying databases, etc.). The head chef doesn’t know how to do everything, but they know which sous chef to call for each specific task.&lt;/p&gt;

&lt;p&gt;So when we say “MCP server,” we’re really talking about a specialized client that serves a particular function to the main AI coordination system. This naming convention can be confusing, but it’s become standard in the MCP community.&lt;/p&gt;

&lt;p&gt;Where this can get really confusing is if the tool you are wanting to use also requires communicating with a server. I really like &lt;a href="https://browsertools.agentdesk.ai/" rel="noopener noreferrer"&gt;BrowserTools MCP&lt;/a&gt;. This tool exposes your console logs to your browser, giving it access to your frontend development logs as you make changes. It’s a complex tool, though, which requires:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawfpxmiag4mwhkmymexp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawfpxmiag4mwhkmymexp.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So when you say “BrowserTools MCP,” you’re actually talking about three separate components working together - the browser extension, the MCP server, and the BrowserTools server. This is a perfect example of why port management becomes crucial in AI-assisted development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of MCP Servers
&lt;/h2&gt;

&lt;p&gt;MCP servers come in two main flavors, and understanding the difference is crucial for managing your development environment effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  stdio-based Servers
&lt;/h3&gt;

&lt;p&gt;These are the simplest type of MCP server. They communicate through standard input/output streams, which means they’re designed to be short-lived processes that start when needed and exit when the work is done. Think of them like command-line tools that your AI can invoke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No port management needed&lt;/strong&gt; - They don’t bind to any network ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless by design&lt;/strong&gt; - Each invocation is independent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for one-off tasks&lt;/strong&gt; - File operations, code generation, data processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to debug&lt;/strong&gt; - You can run them directly from the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File system operations (reading, writing, searching files)&lt;/li&gt;
&lt;li&gt;Code formatting and linting&lt;/li&gt;
&lt;li&gt;Data transformation and analysis&lt;/li&gt;
&lt;li&gt;Simple API calls and data fetching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Port-based Servers
&lt;/h3&gt;

&lt;p&gt;These are the more complex beasts that we’ll focus on in this post. They run as persistent HTTP servers that bind to specific ports on your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent processes&lt;/strong&gt; - They stay running and maintain state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port binding required&lt;/strong&gt; - Each server needs its own port&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateful operations&lt;/strong&gt; - Can maintain context across multiple requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network communication&lt;/strong&gt; - Use HTTP/WebSocket protocols&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource intensive&lt;/strong&gt; - They consume memory and CPU while running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connections and queries&lt;/li&gt;
&lt;li&gt;Web browsing and page interaction&lt;/li&gt;
&lt;li&gt;Complex tool integrations (like GitHub, Slack, etc.)&lt;/li&gt;
&lt;li&gt;Real-time data streams and monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Matters for Your Workflow
&lt;/h3&gt;

&lt;p&gt;The distinction becomes important when you’re building an AI-assisted development environment. stdio-based servers are great for simple, stateless operations, but you need to understand which type of server you are utilizing.&lt;/p&gt;

&lt;p&gt;I tend to have multiple windows open of Cursor, or might be running Cursor and gemini in a terminal shell. What if I want to share an MCP server (like we will discuss in part 4) across my different windows/sessions?&lt;/p&gt;

&lt;p&gt;This is where the port management challenges come in. To make it even more complicated, you might have several port-based servers running simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A browser automation server on port 3000&lt;/li&gt;
&lt;li&gt;A database connection server on port 3001&lt;/li&gt;
&lt;li&gt;A file system watcher on port 3002&lt;/li&gt;
&lt;li&gt;A custom tool integration on port 3003&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Managing these ports, ensuring they don’t conflict, and keeping track of which server is doing what becomes a real challenge. That’s exactly the problem we’ll solve with our tmux-based orchestration system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Port Authority is Now in Session
&lt;/h2&gt;

&lt;p&gt;Many of these MCP servers use HTTP to communicate. That means they need to “bind” to a specific port on your computer. A port is like a numbered door on your computer. Only one application can be listening at a specific door at a time. If you try to start two servers on the same port, the second one will crash and burn, complaining about the port being “already in use.”&lt;/p&gt;

&lt;p&gt;This is a classic problem in software development, and it’s now a problem for those of us building with AI. But here’s the cool part: if an MCP server is just a simple HTTP server, you can often &lt;em&gt;share&lt;/em&gt; it between different AI models. As long as they’re all speaking the same language (i.e., the same MCP specification), they can all talk to the same server on the same port. No need to spin up a new server for each model.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Not-So-Secret Weapon: &lt;code&gt;tmux&lt;/code&gt; and &lt;code&gt;servers.mcp&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As I started collecting more and more of these MCP servers, I realized I needed a better way to manage them. I’m a big fan of &lt;code&gt;tmux&lt;/code&gt;, a terminal multiplexer that lets you have multiple terminal sessions running in the same window. It’s like having a whole bunch of little command-line windows open at once, but without the clutter.&lt;/p&gt;

&lt;p&gt;So, I came up with a little convention for myself. I created a file called &lt;code&gt;servers.mcp&lt;/code&gt; that lists all the servers I want to run. It’s a simple text file, with each line containing a name for the server and the command to start it, separated by an equals sign.&lt;/p&gt;

&lt;p&gt;Here’s what my &lt;code&gt;servers.mcp&lt;/code&gt; file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser-tools=npx -y @agentdeskai/browser-tools-server@latest
mpc-tasks=TRANSPORT=http PORT=4680 npx mcp-tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I wrote a little shell script called &lt;code&gt;start-mcp-servers.sh&lt;/code&gt; that reads this file and starts up a new &lt;code&gt;tmux&lt;/code&gt; session with a separate window for each server.&lt;/p&gt;

&lt;p&gt;Here’s the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# MCP Server Launcher Script
# Reads servers.mcp and creates tmux windows for each server

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Check if tmux is installed
if ! command -v tmux &amp;amp;&amp;gt;/dev/null; then
    echo -e "${RED}Error: tmux is not installed${NC}"
    echo "Please install tmux first:"
    echo " Ubuntu/Debian: sudo apt install tmux"
    echo " macOS: brew install tmux"
    echo " Arch: sudo pacman -S tmux"
    exit 1
fi

# Change to the script's directory
cd "$(dirname "$0")"

# Check if servers.mcp exists
if [! -f "servers.mcp"]; then
    echo -e "${RED}Error: servers.mcp file not found${NC}"
    exit 1
fi

# Check if tmux session already exists
if tmux has-session -t mcp-servers 2&amp;gt;/dev/null; then
    echo -e "${YELLOW}MCP servers session already exists. Attaching...${NC}"
    tmux attach-session -t mcp-servers
    exit 0
fi

# Function to clean up tmux session if script is interrupted
cleanup() {
    echo -e "\n${YELLOW}Cleaning up...${NC}"
    tmux kill-session -t mcp-servers 2&amp;gt;/dev/null
    exit 1
}

# Set up signal handlers
trap cleanup SIGINT SIGTERM

echo -e "${BLUE}Starting MCP servers in tmux session...${NC}"

# Create new tmux session
tmux new-session -d -s mcp-servers -n "mcp-servers"

# Read servers.mcp file and create windows
window_count=0
while IFS='=' read -r title command;
do
    # Skip empty lines and comments
    if [[-z "$title" || "$title" =~ ^[[:space:]]*# ]]; then
        continue
    fi

    # Trim whitespace
    title=$(echo "$title" | xargs)
    command=$(echo "$command" | xargs)

    if [-n "$title"] &amp;amp;&amp;amp; [-n "$command"]; then
        echo -e "${GREEN}Creating window: $title${NC}"
        echo -e " Command: $command"

        if [$window_count -eq 0]; then
            # First window - rename the default window
            tmux rename-window -t mcp-servers:0 "$title"
            tmux send-keys -t mcp-servers:0 "$command &amp;amp;" C-m
        else
            # Create new window
            tmux new-window -t mcp-servers -n "$title" "$command"
        fi

        window_count=$((window_count + 1))
    fi
done &amp;lt;servers.mcp

if [$window_count -eq 0]; then
    echo -e "${RED}No valid servers found in servers.mcp${NC}"
    tmux kill-session -t mcp-servers 2&amp;gt;/dev/null
    exit 1
fi

echo -e "${GREEN}Created $window_count MCP server windows${NC}"
echo -e "${BLUE}Attaching to tmux session...${NC}"
echo -e "${YELLOW}Use Ctrl+B then D to detach from the session${NC}"
echo -e "${YELLOW}Use 'tmux attach -t mcp-servers' to reattach later${NC}"

# Attach to the session
tmux attach-session -t mcp-servers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when I want to start up my MCP environment, I just run &lt;code&gt;./start-mcp-servers.sh&lt;/code&gt;, and I get a nice, clean &lt;code&gt;tmux&lt;/code&gt; session with all my servers running in their own windows.&lt;/p&gt;

&lt;p&gt;Here’s what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaxxedi2uq095v00bvbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaxxedi2uq095v00bvbz.png" alt="My tmux session with MCP servers running" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup makes it easy to see what’s going on with each server, and I can easily restart a server if it crashes. It’s a simple solution, but it’s made my life a lot easier. And, most importantly, it keeps me from tripping over my own digital feet.&lt;br&gt;&lt;br&gt;
m tripping over my own digital feet.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>My ESLint Config Was a Mess. I Asked an AI to Fix It. - Part 2 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Tue, 19 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47</link>
      <guid>https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 2 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the second installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;If you’re a frontend developer, you know the love-hate relationship we have with ESLint. We love that it keeps our code clean and consistent. We hate spending hours wrestling with config files, trying to get plugins to play nicely with each other, especially in a modern TypeScript and Tailwind CSS v4 world.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;eslint.config.js&lt;/code&gt; was starting to feel like a house of cards. I wanted to add better Tailwind CSS linting, but my first attempt with a popular plugin led to a cascade of peer dependency warnings and configuration errors. After a few failed attempts, I realized I was spending more time configuring the linter than writing code. This is not the way.&lt;/p&gt;

&lt;p&gt;So, I turned to my AI pair programmer with a simple plea: “Help me fix this mess.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is eslint locking up?
&lt;/h2&gt;

&lt;p&gt;The first issue I needed help with was that ESLint was trying to use type-aware rules with multiple TypeScript config files, which can cause it to hang when processing large codebases. Type-aware rules like &lt;code&gt;@typescript-eslint/no-unused-vars&lt;/code&gt; and &lt;code&gt;@typescript-eslint/no-floating-promises&lt;/code&gt; require ESLint to perform complex type checking across your entire project, and when you have multiple &lt;code&gt;tsconfig.json&lt;/code&gt; files or conflicting TypeScript configurations, this can lead to infinite loops or excessive memory usage.&lt;/p&gt;

&lt;p&gt;The solution was to create a &lt;strong&gt;dual-config approach&lt;/strong&gt; that separates type-aware and non-type-aware rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App files&lt;/strong&gt; use the fast, non-type-aware config for quick feedback during development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test files&lt;/strong&gt; use the comprehensive type-aware config for thorough validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared configuration&lt;/strong&gt; provides common rules and plugins across both configs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation allows ESLint to run quickly during development while still providing comprehensive type checking where it matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mission: Clean, Refactored ESLint Config
&lt;/h2&gt;

&lt;p&gt;My goal was to not just get a new Tailwind plugin working, but to clean up the entire ESLint configuration. The process, guided by the AI, was a masterclass in untangling complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Result
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Refactoring for Readability and DRYness
&lt;/h3&gt;

&lt;p&gt;The first thing it did was apply the Don’t Repeat Yourself (DRY) principle to my config. Instead of having separate, nearly identical objects for different TypeScript configurations, it created common, reusable building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COMMON_PLUGINS&lt;/code&gt;: A single object defining all the ESLint plugins we use, like &lt;code&gt;@typescript-eslint&lt;/code&gt;, &lt;code&gt;prettier&lt;/code&gt;, &lt;code&gt;react-hooks&lt;/code&gt;, and the new Tailwind plugins.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMMON_SETTINGS&lt;/code&gt;: A shared object for settings, like the import resolver paths.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMMON_RULES&lt;/code&gt;: A comprehensive object containing all the rules that apply across the entire project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately made the config file shorter, cleaner, and much easier to understand. If we need to add a new rule everywhere, we now only have to add it in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Installing and Configuring the &lt;em&gt;Right&lt;/em&gt; Plugins
&lt;/h3&gt;

&lt;p&gt;The initial journey involved some trial and error. We first tried &lt;code&gt;eslint-plugin-tailwindcss&lt;/code&gt;, but it had issues with our Tailwind v4 setup. The AI helped diagnose this and suggested a better alternative. The final commit shows the installation of two key packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eslint-plugin-better-tailwindcss&lt;/code&gt;: A fantastic plugin for sorting classes and catching duplicates and conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@poupe/eslint-plugin-tailwindcss&lt;/code&gt;: Another great plugin that adds more advanced checks, like preferring theme tokens and validating CSS modifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Dialing in the Rules
&lt;/h3&gt;

&lt;p&gt;This was the most crucial part. The AI configured the new plugins with a sensible set of rules, turning some on as warnings and others as errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Tailwind CSS rules
'better-tailwindcss/sort-classes': 'warn',
'better-tailwindcss/no-duplicate-classes': 'error',
'better-tailwindcss/no-conflicting-classes': 'error',
'better-tailwindcss/no-unregistered-classes': 'off', // We turned this off because we use custom design tokens
'better-tailwindcss/enforce-shorthand-classes': 'warn',

// Poupe Tailwind CSS rules
'tailwindcss/no-conflicting-utilities': 'error',
'tailwindcss/prefer-theme-tokens': 'warn',
'tailwindcss/valid-modifier-syntax': 'error',
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;no-unregistered-classes&lt;/code&gt; rule is off. The AI correctly identified from the linter output that our custom design tokens (like &lt;code&gt;bg-primary&lt;/code&gt;) were being flagged, and recommended disabling this rule to avoid noise, which was exactly the right call.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Automatically Fixing What It Could
&lt;/h3&gt;

&lt;p&gt;The best part? The new &lt;code&gt;sort-classes&lt;/code&gt; rule didn’t just flag inconsistent class ordering—it fixed it. The git commit is full of small, satisfying changes where messy class strings were automatically reordered into a logical, consistent format:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div className='flex justify-center items-center h-32'&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div className='flex h-32 items-center justify-center'&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happened across dozens of files. It’s a small change, but it adds up to a huge improvement in code quality and developer experience, and it was all done automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result: Effortless Code Quality
&lt;/h2&gt;

&lt;p&gt;After the AI finished its work, our ESLint setup was transformed. It’s now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Easy to read and maintain.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using modern, effective plugins&lt;/strong&gt; for our specific stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatically enforcing&lt;/strong&gt; a consistent style for our utility classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This experience was a powerful reminder that AI assistants are more than just code generators. They can be expert systems administrators, helping to configure and maintain the complex tooling that modern development relies on. It took a task that was a source of frustration and turned it into a clean, automated part of our workflow. And for that, I am very grateful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Architecture: Dual-Config with Shared Rules
&lt;/h2&gt;

&lt;p&gt;The AI helped me implement a sophisticated dual-config architecture that solves the performance issues while maintaining comprehensive linting coverage. Here’s how it works:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Shared Configuration (&lt;code&gt;eslint.shared.config.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The shared config provides the foundation with common plugins, settings, and rules that apply across the entire project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Common plugins used across all TypeScript configs
const COMMON_PLUGINS = {
    '@typescript-eslint': tseslint.plugin,
    'react-hooks': reactHooks,
    'react-refresh': reactRefresh,
    import: importPlugin,
    prettier: prettierPlugin,
    'react-dom': reactDom,
    perfectionist,
    'react-x': reactX,
    'unused-imports': unusedImports,
    visual: visualComplexity,
    'better-tailwindcss': betterTailwindcss,
    tailwindcss: poupeTailwindcssPlugin,
}

// Factory function to create TypeScript configs with different rule sets
const createTypeScriptConfig = (...tsRuleSets) =&amp;gt; ({
    languageOptions: {
        parser: tseslint.parser,
        parserOptions: {
            ecmaFeatures: {
                jsx: true,
            },
        },
    },
    plugins: COMMON_PLUGINS,
    rules: {
        ...COMMON_RULES,
        // Merge all provided TypeScript rule sets
        ...Object.assign({}, ...tsRuleSets),
    },
    settings: COMMON_SETTINGS,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This factory pattern allows us to create different TypeScript configurations with varying levels of strictness.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. App Configuration (&lt;code&gt;eslint.app.config.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The app config implements the performance-optimized approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default [
    // Global ignores
    {
        ignores: [...sharedIgnores, 'shared/**'],
    },
    // App TypeScript files WITHOUT type-aware rules (for speed)
    {
        files: appFiles,
        languageOptions: {
            ...typescriptConfig.languageOptions,
            globals: browserGlobals,
        },
        plugins: typescriptConfig.plugins,
        rules: typescriptConfig.rules,
        settings: typescriptConfig.settings,
    },
    // Test files override WITH type-aware rules
    {
        files: ['**/*.test.{js,jsx,ts,tsx}'],
        languageOptions: {
            ...typescriptConfig.languageOptions,
            parserOptions: {
                ...typescriptConfig.languageOptions.parserOptions,
                project: './tsconfig.test.json',
                tsconfigRootDir: import.meta.dirname,
            },
            globals: {
                ...globals.jest,
                ...browserGlobals,
            },
        },
        plugins: typescriptConfig.plugins,
        rules: {
            ...typescriptConfig.rules,
            ...testConfig.rules,
        },
        settings: typescriptConfig.settings,
    },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The Performance Solution
&lt;/h3&gt;

&lt;p&gt;The key insight was creating two distinct TypeScript configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;typescriptConfig&lt;/code&gt;&lt;/strong&gt; : Uses &lt;code&gt;tseslint.configs.recommended&lt;/code&gt; and &lt;code&gt;tseslint.configs.stylistic&lt;/code&gt; - fast, syntax-based rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;typeAwareConfig&lt;/code&gt;&lt;/strong&gt; : Uses &lt;code&gt;tseslint.configs.recommendedTypeChecked&lt;/code&gt;, &lt;code&gt;tseslint.configs.strictTypeChecked&lt;/code&gt;, and &lt;code&gt;tseslint.configs.stylisticTypeChecked&lt;/code&gt; - comprehensive type-aware rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By applying the fast config to app files and the comprehensive config only to test files, we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast development feedback&lt;/strong&gt; - ESLint runs quickly during coding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thorough validation&lt;/strong&gt; - Type-aware rules catch issues in tests where they matter most&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainable architecture&lt;/strong&gt; - Shared configuration keeps everything DRY&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture demonstrates how AI can help design sophisticated solutions that balance performance with functionality, turning a frustrating configuration problem into an elegant, scalable system.  &lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>When AppImages Fail You: Building a Robust Extraction Tool (and Why Cursor Drove Me to It)</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 10 Aug 2025 14:30:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/when-appimages-fail-you-building-a-robust-extraction-tool-and-why-cursor-drove-me-to-it-26ha</link>
      <guid>https://dev.to/drmikecrowe/when-appimages-fail-you-building-a-robust-extraction-tool-and-why-cursor-drove-me-to-it-26ha</guid>
      <description>&lt;p&gt;Let me start this with a confession:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I really wanted to like AppImages&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The promise is compelling: universal Linux binaries that run everywhere without installation. Just download, &lt;code&gt;chmod +x&lt;/code&gt;, and go. It’s the kind of elegant simplicity that makes you think “why didn’t we do this sooner?”&lt;/p&gt;

&lt;p&gt;Then reality hits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cursor Catalyst
&lt;/h2&gt;

&lt;p&gt;This whole adventure started when I decided to try &lt;a href="https://cursor.sh/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, the AI-powered code editor that’s my daily code editor. Unfortunately, it’s not updated frequently in AUR, so I decided to try the AppImage. When I tried to run it, I got the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./Cursor-1.4.3.AppImage
zsh: no such file or directory: ./Cursor-1.4.3.AppImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some experimentation, I got it to run but I now started seeing the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mise ERROR Cursor-1.4.3-x86_64_5054c3a796764b4195108ade2714e281.AppImage is not a valid shim. This likely means you uninstalled a tool and the shim does not point to anything. Run `mise use &amp;lt;TOOL&amp;gt;` to reinstall the tool.
mise ERROR Run with --verbose or MISE_VERBOSE=1 for more information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regardless, this was a nogo for me, because &lt;a href="https://mise.jdx.dev/" rel="noopener noreferrer"&gt;mise&lt;/a&gt; has replaced direnv for me and I use it everywhere. Perplexity informed me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mise can be run in many environments, but there are common issues when using mise within AppImages (such as when using the Cursor editor as an AppImage), especially with advanced shell integrations and shims. The error message you are seeing—“AppImage is not a valid shim. This likely means you uninstalled a tool and the shim does not point to anything”—is a known problem occurring specifically in AppImage-packed shells and terminals, often with Zsh.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The AppImage Reality Check
&lt;/h2&gt;

&lt;p&gt;Don’t get me wrong — AppImages solve real problems. Before them, we had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RPM/DEB Hell&lt;/strong&gt; : Dependency conflicts, version mismatches, and the joy of maintaining packages across different distributions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snap Confinement&lt;/strong&gt; : Sandboxing that sometimes broke legitimate app functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flatpak Complexity&lt;/strong&gt; : Great technology, but the runtime management can get overwhelming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AppImages promised to be different. Just a filesystem bundle with everything included. But in practice, they still have dependencies — on FUSE, on specific kernel features, on runtime libraries that may or may not be present.&lt;/p&gt;

&lt;p&gt;I started thinking about this differently. What if instead of trying to fix the technical issues, I just extracted everything and ran it natively?&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an Extraction-First Manager
&lt;/h2&gt;

&lt;p&gt;The core idea was simple: instead of running AppImages, extract them and create proper desktop entries that point to the extracted executables. This completely sidesteps the FUSE requirement and gives us some nice benefits:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Extraction Chain
&lt;/h3&gt;

&lt;p&gt;The first challenge was that extraction isn’t always straightforward. Some AppImages work fine with the built-in &lt;code&gt;--appimage-extract&lt;/code&gt;, but others don’t. I ended up building a chain of fallback methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Method 1: Native AppImage extraction
if ( cd "$tmpdir" &amp;amp;&amp;amp; "$appimage" --appimage-extract &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 ); then
    printf "%s/squashfs-root" "$tmpdir"
    return 0
fi

# Method 2: Direct SquashFS with offset detection
if has_cmd unsquashfs; then
    offset=$(LC_ALL=C grep -aob -- 'hsqs' "$appimage" | head -n1 | cut -d: -f1 || true)
    if [[-n "${offset:-}"]]; then
        if ( cd "$tmpdir" &amp;amp;&amp;amp; unsquashfs -o "$offset" -d squashfs-root "$appimage" &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 ); then
            printf "%s/squashfs-root" "$tmpdir"
            return 0
        fi
    fi
fi

# Method 3: Enhanced binwalk analysis
if has_cmd binwalk &amp;amp;&amp;amp; has_cmd unsquashfs; then
    # ... more sophisticated offset detection
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach handles even the most stubborn AppImages. I’ve tested it on everything from simple utilities to complex applications like Cursor, and it consistently works where the standard AppImage runtime fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Idempotency Problem
&lt;/h3&gt;

&lt;p&gt;Here’s something that annoyed me: downloading &lt;code&gt;Cursor-1.4.3.AppImage&lt;/code&gt; and later &lt;code&gt;Cursor-1.5.0.AppImage&lt;/code&gt; would create two separate entries in my Applications folder. That’s not how updates should work.&lt;/p&gt;

&lt;p&gt;The solution was to use the application’s internal name instead of the filename. Every AppImage contains a &lt;code&gt;.desktop&lt;/code&gt; file that specifies the real application name. My script extracts this and uses it as the canonical identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read_internal_desktop() {
    local root="$1"
    local d
    d=$(find "$root" -maxdepth 2 -type f -name '*.desktop' | head -n 1 || true)
    [[-n "${d:-}"]] &amp;amp;&amp;amp; printf "%s" "$d"
}

parse_desktop_key() {
    local df="$1" key="$2"
    awk -F= -v key="$key" 'tolower($1)==tolower(key){print $2; exit}' "$df"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when I install a newer version of Cursor, it updates the existing installation instead of creating a duplicate. Much better.&lt;/p&gt;

&lt;h2&gt;
  
  
  File Structure and Integration
&lt;/h2&gt;

&lt;p&gt;The end result is a clean, organized structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Applications/cursor/ # Based on internal app name
├── cursor.AppImage # Original AppImage file
├── extracted/ # Extracted contents
│ ├── AppRun # Main executable
│ ├── cursor.desktop # Internal desktop file
│ ├── usr/ # Application files
│ └── ...
├── icon # Extracted icon
└── .appimage-manager-meta # Manager metadata

~/.local/share/applications/cursor.desktop # System desktop entry
~/.local/share/icons/hicolor/256x256/apps/cursor.png # System icon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop entry points directly to the extracted &lt;code&gt;AppRun&lt;/code&gt; executable, completely bypassing any AppImage runtime requirements. KDE and GNOME see it as a normal application, complete with proper icons and metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Experience
&lt;/h2&gt;

&lt;p&gt;Using the tool is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install or update
./appimage-manager.sh install ./Cursor-1.4.3.AppImage

# Later, updating to a newer version
./appimage-manager.sh install ./Cursor-1.5.0.AppImage # Updates existing install

# List what's installed
./appimage-manager.sh list

# Clean removal
./appimage-manager.sh uninstall cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script handles all the complexity: extraction, icon processing, desktop file creation, and menu integration. And since it’s extraction-based, everything starts faster than traditional AppImages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;This isn’t just about avoiding FUSE dependencies (though that’s nice). It’s about reliability. AppImages are supposed to be the “just works” solution for Linux software distribution, but too often they don’t. By extracting first and running natively, we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Runtime Dependencies&lt;/strong&gt; : No FUSE, no AppImage runtime, no mysterious failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Startup&lt;/strong&gt; : No filesystem mounting overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Integration&lt;/strong&gt; : Standard desktop entries that every DE understands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner Updates&lt;/strong&gt; : Idempotent installs based on actual app names&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Troubleshooting&lt;/strong&gt; : When something breaks, you can inspect the extracted files directly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Irony
&lt;/h2&gt;

&lt;p&gt;The funny thing? By avoiding the AppImage runtime entirely, I ended up with a more reliable way to use AppImages. They still serve their purpose as distribution bundles — I just treat them as fancy zip files instead of executable containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source and Available
&lt;/h2&gt;

&lt;p&gt;I’ve released the &lt;a href="https://github.com/drmikecrowe/appimage-extracted-installer/" rel="noopener noreferrer"&gt;AppImage Extract Installer&lt;/a&gt; under Apache 2.0. It’s a single bash script with no dependencies beyond standard Linux utilities (though &lt;code&gt;squashfs-tools&lt;/code&gt; and &lt;code&gt;binwalk&lt;/code&gt; enable the enhanced extraction features).&lt;/p&gt;

&lt;p&gt;If you’re dealing with AppImage frustrations, give it a try. And if you find bugs or have suggestions, the issue tracker is open.&lt;/p&gt;

&lt;p&gt;Sometimes the best way to fix a technology is to work around it entirely.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have your own AppImage horror stories? Found a better solution? Hit me up on &lt;a href="https://github.com/drmikecrowe" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or wherever you found this post. I’m always interested in hearing how others solve these kinds of practical problems.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introducing VPEM-Visual Studio Profile Extension Manager</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Tue, 08 Oct 2024 11:22:04 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/introducing-vpem-visual-studio-profile-extension-manager-4a1m</link>
      <guid>https://dev.to/drmikecrowe/introducing-vpem-visual-studio-profile-extension-manager-4a1m</guid>
      <description>&lt;p&gt;VS Code Profile Extension Manager (VPEM) is a powerful command-line tool designed to help you manage your Visual Studio Code extensions across different profiles. It allows you to dump, categorize, and apply extensions with ease, streamlining your VS Code setup process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;This project has been developed strictly on Linux, though it should work everywhere.  If you find an issue, please report it in the &lt;a href="https://github.com/drmikecrowe/vscode-profile-extension-manager/issues" rel="noopener noreferrer"&gt;GitHub issue tracker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Executables are built using &lt;a href="https://github.com/pyinstaller/pyinstaller" rel="noopener noreferrer"&gt;pyinstaller&lt;/a&gt; and are available for Linux, Windows and MacOS.  Again, if any issues are found, please report them in the &lt;a href="https://github.com/drmikecrowe/vscode-profile-extension-manager/issues" rel="noopener noreferrer"&gt;GitHub issue tracker&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;I tend to use a lot of extensions.  Further, I find myself switching frequently between TypeScript, Python, and now C#.  Sometimes I work in Azure.  Other times in AWS.&lt;/p&gt;

&lt;p&gt;I realized that multiple profiles might make more sense than tons of extensions in a single profile.  However, it's also really easy to experiment with extensions and leave unused ones polluting the profile.  This tool helps me keep things organized.&lt;/p&gt;

&lt;p&gt;For example, here's how to apply a group of extensions to a profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxpybf6waw6g0n4biwev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxpybf6waw6g0n4biwev.png" alt="Apply Extensions" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Process
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;First, dump your extensions for all your profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7ljf1ap96z6si7h347i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7ljf1ap96z6si7h347i.png" alt="Dump Extensions" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next, categorize extensions by searching for common strings and assign to categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjawdjkl1qzdo38fd1dox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjawdjkl1qzdo38fd1dox.png" alt="Categorize" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finally, apply extensions to a profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa58vjdqoa1gejnybzh9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa58vjdqoa1gejnybzh9q.png" alt=" " width="488" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To install VPEM, you can use pip:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended way:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;vscode_profile_extension_manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you may download the released binaries in the repo.  However, these are mass-produced by actions I've pulled from various places, so I can't guarantee they're tested.&lt;/p&gt;

&lt;p&gt;Finally, you can also use &lt;a href="https://github.com/jpillora/installer" rel="noopener noreferrer"&gt;jpillora/installer&lt;/a&gt; to automate the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-q&lt;/span&gt; https://i.jpillora.com/drmikecrowe/vscode-profile-extension-manager! | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Here are some basic usage examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dump extensions from a profile:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   vpem dump &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="s2"&gt;"Default"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Apply extensions to a profile:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   vpem apply &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="s2"&gt;"Work"&lt;/span&gt; &lt;span class="nt"&gt;--category&lt;/span&gt; &lt;span class="s2"&gt;"Python Development"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;List all categories:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   vpem list-categories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;We welcome contributions to VPEM! If you'd like to contribute, please follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the repository&lt;/li&gt;
&lt;li&gt;Create a new branch for your feature or bug fix&lt;/li&gt;
&lt;li&gt;Make your changes and commit them with a clear message&lt;/li&gt;
&lt;li&gt;Push your changes to your fork&lt;/li&gt;
&lt;li&gt;Create a pull request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Please make sure to update tests as appropriate and adhere to the project's coding standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;This project is licensed under the Apache-2.0 License - see the &lt;a href="//LICENSE.md"&gt;LICENSE&lt;/a&gt; file for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you encounter any issues or have questions, please file an issue on the &lt;a href="https://github.com/drmikecrowe/vscode-profile-extension-manager/issues" rel="noopener noreferrer"&gt;GitHub issue tracker&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Databricks Multiple Filters using a Python Lambda statement</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Tue, 02 Jul 2024 08:00:06 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/databricks-multiple-filters-using-a-python-lambda-statement-bad</link>
      <guid>https://dev.to/drmikecrowe/databricks-multiple-filters-using-a-python-lambda-statement-bad</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8psnsvphghb5qjdj8af.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8psnsvphghb5qjdj8af.png" alt=" " width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplifying Multiple Null Checks in Databricks
&lt;/h2&gt;

&lt;p&gt;Recently, I ran into a case where I needed to check if 11 different fields were null. Yes, I could have used Copilot (or my preferred Codeium) to generate it for me, but I knew it had to be easier. There had to be an easier way…&lt;/p&gt;

&lt;h3&gt;
  
  
  The Scenario
&lt;/h3&gt;

&lt;p&gt;You have a list of conditions in a python list (in my case, I pasted it as separate lines and did the following. Doesn’t matter how you specify your conditions, just that it’s a list somehow&lt;/p&gt;

&lt;p&gt;&lt;code&gt;conditions = """&lt;br&gt;
condition 1&lt;br&gt;
condition2&lt;br&gt;
...".split("\n")&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  You have a way of associating this list with a set of Databricks columns
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;reduce&lt;/code&gt; from &lt;code&gt;functools&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here’s what I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There’s a python function &lt;code&gt;reduce&lt;/code&gt; in &lt;code&gt;functools&lt;/code&gt; that takes a list a reduces it down to a result&lt;/li&gt;
&lt;li&gt;You specify &lt;em&gt;how&lt;/em&gt; this list is “reduced”, such as “and each element of this list together”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Example Implementation
&lt;/h3&gt;

&lt;p&gt;Here’s how it works. first, I have an array of &lt;code&gt;join_columns&lt;/code&gt; which are the common columns between two datasets. In my setup, each column of &lt;code&gt;join_columns&lt;/code&gt; maps to the corresponding filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conditions = [F.col(join_columns[i]) == filter_cols[i] for i in range(2)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we use the &lt;code&gt;reduce&lt;/code&gt; function to combine these into a condition for filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;condition = functools.reduce(lambda a, b: a &amp;amp; b, conditions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Combining Conditions
&lt;/h3&gt;

&lt;p&gt;So, in this case, we are saying all these conditions should be &lt;code&gt;and&lt;/code&gt;’d together – or rather all of them should match. Naturally, had I wanted &lt;em&gt;any of them to match&lt;/em&gt;, this would have have been:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;condition = functools.reduce(lambda a, b: a | b, conditions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(the &lt;code&gt;&amp;amp;&lt;/code&gt; is now a &lt;code&gt;|&lt;/code&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Condition for Filtering
&lt;/h3&gt;

&lt;p&gt;That’s it–I now have a condition that I can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;res_df = input_df.filter(condition)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>databricks</category>
    </item>
    <item>
      <title>Making Databricks Widgets Smarter</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Tue, 25 Jun 2024 08:00:40 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/making-databricks-widgets-smarter-4k3j</link>
      <guid>https://dev.to/drmikecrowe/making-databricks-widgets-smarter-4k3j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmax482as0tk21j08vn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmax482as0tk21j08vn5.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever wished the Databricks widgets were a little more intelligent? When you change one, the others adjust?&lt;/p&gt;

&lt;p&gt;That’s not too hard, but it’s a bit tricky. Here’s the secret: The widgets must be replaced as they change!&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Widget Replacement
&lt;/h3&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Let’s say you have a date input, and whenever it changes, you want a second widget to change. That second widget needs to be suffixed with an increasing number as the contents change. Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    if interest_date != last_interest_date:
        try:
            dbutils.widgets.remove(f"issue{index1}")
        except:
            pass
        index1 += 1
    dbutils.widgets.dropdown(f"issue{index1}", "", ...)        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need the try/catch the first time thru because the remove will fail. Et Voila, changing widgets.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Working Example
&lt;/h3&gt;

&lt;p&gt;Here’s a functioning example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interest_dates = {
    "20230730": [
        {"issue_name": "Issue 1", "upcs": ["1234", "5678"]},
        {"issue_name": "Issue 2", "upcs": ["9876", "5432"]},
    ],
    "20240114": [
        {"issue_name": "Issue 3", "upcs": ["1111", "2222"]},
        {"issue_name": "Issue 4", "upcs": ["3333", "4444"]},
    ],
}

last_interest_date, last_issue = (None, None)
index1, index2 = (1, 1)
dbutils.widgets.removeAll()

from random import sample
from time import sleep

keys = sorted(list(interest_dates.keys()))

dbutils.widgets.removeAll()
dbutils.widgets.dropdown(
    "date", "", [""] + [str(k) for k in sorted(interest_dates)], "1. Date"
)
interest_date = dbutils.widgets.get("date")
if interest_date:
    if interest_date != last_interest_date:
        try:
            dbutils.widgets.remove(f"issue{index1}")
            dbutils.widgets.remove(f"upc{index2}")
        except:
            pass
        index1 += 1
        index2 += 1
        last_interest_date = interest_date

    dbutils.widgets.dropdown(
        f"issue{index1}",
        "",
        [""] + [k["issue_name"] for k in interest_dates[interest_date]],
        "2. Issue",
    )
    issue = dbutils.widgets.get(f"issue{index1}")
    if issue:
        if issue != last_issue:
            try:
                dbutils.widgets.remove(f"upc{index2}")
            except:
                pass
            index2 += 1
            last_issue = issue
        current_issue = [
            k for k in interest_dates[interest_date] if k["issue_name"] == issue
        ].pop(0)

        dbutils.widgets.dropdown(
            f"upc{index2}",
            "",
            [""] + current_issue["upcs"],
            "3. UPC",
        )
        upc = dbutils.widgets.get(f"upc{index2}")
        if upc:
            displayHTML(f"You selected: &amp;lt;b&amp;gt;{upc}&amp;lt;/b&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>databricks</category>
      <category>python</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
