Skip to content

Feature/collocated docs#3

Merged
barisgit merged 6 commits into
mainfrom
feature/collocated-docs
Sep 5, 2025
Merged

Feature/collocated docs#3
barisgit merged 6 commits into
mainfrom
feature/collocated-docs

Conversation

@barisgit
Copy link
Copy Markdown
Owner

@barisgit barisgit commented Sep 4, 2025

Make docs be with code so it is easier to maintain.

Summary by CodeRabbit

  • New Features

    • Revamped docs site with a new homepage design (aurora background, improved cards/CTAs).
    • Added automated syncing of embedded CLI docs to keep the CLI Reference up to date.
  • Documentation

    • Major overhaul: new Installation, Getting Started, Configuration, Development Setup, and Contributing guides.
    • Added detailed CLI command pages (e.g., add-3d, add-hook, init, list, pin/unpin, setup, status, update).
    • Cleaned and reorganized content; removed outdated pages.
  • Chores

    • Bumped version to 0.3.0.
    • Dropped Python 3.7; minimum supported Python is now 3.8.

@vercel
Copy link
Copy Markdown

vercel Bot commented Sep 4, 2025

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

Project Deployment Preview Comments Updated (UTC)
kilm Ready Ready Preview Comment Sep 5, 2025 7:55am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 4, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Docs site restructured and expanded, including new MDX pages and styles. Introduces a doc sync script that aggregates embedded command docs into the CLI reference. CLI packages refactored to expose public symbols via init and deeper relative imports. Tests updated to new import paths. Version bumped to 0.3.0; Python min set to 3.8.

Changes

Cohort / File(s) Summary
• Docs content overhaul
docs/src/content/docs/index.mdx, .../guides/getting-started.mdx, .../guides/installation.mdx, .../guides/configuration.mdx, .../community/contributing.mdx, .../community/development.mdx, .../community/development.md (deleted), .../guides/installation.md (deleted)
Rewrote homepage, guides, and community docs; added MDX pages; removed legacy MD pages; updated structure, examples, and workflows.
• Docs reference restructuring (CLI)
docs/src/content/docs/reference/cli/.gitignore, docs/src/content/docs/reference/cli/add-3d.md (deleted), .../add-hook.md (deleted), .../init.md (deleted), .../list.md (deleted)
Moved CLI docs to collocated MDX under command packages; added .gitignore to keep index only; removed legacy CLI reference pages.
• Collocated command docs (MDX added)
kicad_lib_manager/commands/add_3d/docs.mdx, .../add_hook/docs.mdx, .../init/docs.mdx, .../list_libraries/docs.mdx, .../pin/docs.mdx, .../setup/docs.mdx, .../status/docs.mdx, .../unpin/docs.mdx, .../update/docs.mdx
Added/updated per-command MDX documentation using Starlight components; minor front-matter tweaks.
• Docs tooling & config
docs/package.json, docs/astro.config.mjs, docs/scripts/sync-embedded-docs.ts, docs/src/styles/global.css, docs/README.md
Added doc sync scripts (sync/watch/clean), added starlight-site-graph dep and optional integration, introduced sync TypeScript script, revamped CSS (aurora, cards, hero), cleaned README headings.
• CLI command packaging (public exports)
kicad_lib_manager/commands/*/__init__.py for add_3d, add_hook, config, init, list_libraries, pin, setup, status, template, unpin, update
Added re-exports via all to expose command symbols (e.g., add_hook, config, setup, update, etc.).
• Import path refactor (deeper relatives)
kicad_lib_manager/commands/*/command.py for add_3d, add_hook, config, init, list_libraries, pin, setup, status, template, unpin, update
Adjusted imports from .. to ... modules; added exception chaining in add_hook; updated dynamic import in update; minor comments. No logic changes.
• CLI registration tweak
kicad_lib_manager/cli.py, kicad_lib_manager/commands/add_hook/.command, kicad_lib_manager/commands/add_3d/.command, kicad_lib_manager/commands/list_libraries/.command
Registered add_hook under default function name; added .command files specifying CLI names (e.g., “add-hook”, “add-3d”, “list”).
• Tests aligned to new paths
tests/test_config_commands.py, tests/test_unpin_command.py, tests/test_update_command.py
Updated monkeypatch targets to new module paths (...command.*).
• Versioning & metadata
kicad_lib_manager/__init__.py, pyproject.toml
Bumped version to 0.3.0; raised Python requires-python to >=3.8; removed Python 3.7 classifier.
• Repo docs refresh
README.md, CONTRIBUTING.md
Updated project README and contributing guide with streamlined quick-starts and links to full docs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant PN as pnpm scripts
  participant SYNC as sync-embedded-docs.ts
  participant FS as File System
  participant Docs as docs/src/content/docs/reference/cli

  Dev->>PN: pnpm sync-docs
  PN->>SYNC: tsx scripts/sync-embedded-docs.ts sync
  SYNC->>FS: Scan kicad_lib_manager/commands/* for docs.(md|mdx) and .command
  SYNC->>SYNC: Resolve command name (from .command or dir)
  alt Missing frontmatter
    SYNC->>SYNC: Generate frontmatter (title/description/label)
    SYNC->>SYNC: Strip first heading if present
  end
  SYNC->>FS: Write Docs/<command>.md(x) (preserve extension)
  SYNC-->>Dev: Log synced files

  Dev->>PN: pnpm sync-docs:watch
  PN->>SYNC: watch mode
  SYNC->>FS: Watch embedded docs for changes
  FS-->>SYNC: Change event
  SYNC->>FS: Re-sync changed doc
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A rabbit taps keys by the moon’s soft glow,
Syncing docs where the command winds blow.
Exports tidy, imports aligned—
Pages dance with aurora design.
Version hops to zero-three-oh,
“kilm status” ready—now let’s go! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fbe37a0 and 8d96af7.

📒 Files selected for processing (13)
  • CONTRIBUTING.md (1 hunks)
  • README.md (3 hunks)
  • docs/scripts/sync-embedded-docs.ts (1 hunks)
  • docs/src/content/docs/community/contributing.mdx (1 hunks)
  • docs/src/content/docs/community/development.mdx (1 hunks)
  • docs/src/content/docs/guides/getting-started.mdx (1 hunks)
  • docs/src/content/docs/guides/installation.mdx (1 hunks)
  • docs/src/content/docs/index.mdx (2 hunks)
  • docs/src/content/docs/reference/cli/.gitignore (1 hunks)
  • kicad_lib_manager/__init__.py (1 hunks)
  • kicad_lib_manager/commands/list_libraries/docs.mdx (1 hunks)
  • kicad_lib_manager/commands/setup/command.py (2 hunks)
  • pyproject.toml (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/collocated-docs

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (8)
kicad_lib_manager/commands/pin/command.py (2)

9-14: Fix incorrect import source for list_libraries.

list_libraries is defined in utils/file_ops, not library_manager. Current import will fail at runtime unless re-exported.

-from ...library_manager import find_kicad_config, list_libraries
+from ...library_manager import find_kicad_config
+from ...utils.file_ops import list_libraries

If you prefer a single import point, re-export list_libraries from library_manager and keep callers consistent.


36-40: Make “-s/-f” imply “--selected” and avoid shadowing built-in all().

Docs say -s/-f implicitly activate “selected”, but validation currently runs only when all is False. Also, all shadows the built-in. Compute a local pin_all flag instead.

-@click.option(
-    "--all/--selected",
+@click.option(
+    "--all/--selected",
     default=True,
     show_default=True,
     help="Pin all available libraries or only selected ones",
 )
 ...
-def pin(kicad_lib_dir, symbols, footprints, all, dry_run, max_backups, verbose):
+def pin(kicad_lib_dir, symbols, footprints, all, dry_run, max_backups, verbose):
     """Pin libraries in KiCad for quick access"""
+    # Derive behavior: if any -s/-f are provided, treat as --selected
+    pin_all = bool(all) and not (symbols or footprints)
 ...
-    if all and not symbols and not footprints:
+    if pin_all:
         try:
             symbol_libs, footprint_libs = list_libraries(kicad_lib_dir)
             symbols = symbol_libs
             footprints = footprint_libs
 ...
-    if not all and (symbols or footprints):
+    if not pin_all and (symbols or footprints):
         try:
             available_symbols, available_footprints = list_libraries(kicad_lib_dir)

Also applies to: 58-66, 82-95, 102-104

kicad_lib_manager/commands/setup/command.py (1)

436-457: Back up tables before modifying them, not after.

Backups currently occur post-write, defeating the purpose. Back up first (or move this into add_libraries).

-        # Create backups only if changes are needed
-        if changes_needed and not dry_run:
-            sym_table = kicad_config / "sym-lib-table"
-            fp_table = kicad_config / "fp-lib-table"
-
-            if sym_table.exists():
-                create_backup(sym_table, max_backups)
-                click.echo("Created backup of symbol library table")
-
-            if fp_table.exists():
-                create_backup(fp_table, max_backups)
-                click.echo("Created backup of footprint library table")
+        # Back up tables before applying any modifications
+        if not dry_run:
+            sym_table = kicad_config / "sym-lib-table"
+            fp_table = kicad_config / "fp-lib-table"
+            if sym_table.exists():
+                create_backup(sym_table, max_backups)
+            if fp_table.exists():
+                create_backup(fp_table, max_backups)
+        # (Optional) echo after successful add_libraries call:
+        if changes_needed and not dry_run:
+            if (kicad_config / "sym-lib-table").exists():
+                click.echo("Created backup of symbol library table")
+            if (kicad_config / "fp-lib-table").exists():
+                click.echo("Created backup of footprint library table")

If minimizing unnecessary backups is important, consider moving backup creation into add_libraries so it only triggers when writes occur.

kicad_lib_manager/commands/setup/docs.mdx (1)

25-70: Align docs with implementation for --all-libraries precedence

  • The docs state that --symbol-lib-dirs / --threed-lib-dirs take precedence over --all-libraries, but the code checks --all-libraries first and ignores specific-library flags when it’s set. Update the documentation to show that --all-libraries overrides any --symbol-lib-dirs or --threed-lib-dirs values.
kicad_lib_manager/commands/update/command.py (4)

96-105: Add a timeout and handle hung git processes

External calls should not be unbounded. Add a timeout and surface timeouts distinctly.

-            result = subprocess.run(
+            result = subprocess.run(
                 ["git", "pull"],
                 cwd=lib_path,
                 capture_output=True,
                 text=True,
                 check=False,
+                timeout=60,
             )
@@
-        except Exception as e:
-            click.echo(f"    Error: {str(e)}")
-            failed_count += 1
+        except subprocess.TimeoutExpired:
+            click.echo("    Error: git pull timed out (60s).", err=True)
+            failed_count += 1
+        except Exception as e:
+            click.echo(f"    Error: {str(e)}", err=True)
+            failed_count += 1

Also applies to: 136-139


106-118: Counters don’t update in verbose mode; also handle both “Already up-to-date” variants

Right now, updated_count/up_to_date_count are only incremented in non-verbose runs. Also, Git sometimes prints “Already up-to-date.” Use a single branch and a regex to detect both.

             if result.returncode == 0:
-                output = result.stdout.strip() or "Already up to date."
-                if verbose:
-                    click.echo(f"    Success: {output}")
-                else:
-                    is_updated = "Already up to date" not in output
-                    if is_updated:
-                        click.echo("    Updated")
-                        updated_count += 1
-                    else:
-                        click.echo("    Up to date")
-                        up_to_date_count += 1
+                output = result.stdout.strip() or "Already up to date."
+                # Treat both "Already up to date" and "Already up-to-date"
+                is_updated = not re.search(r"Already up[- ]to date\.?", output, re.IGNORECASE)
+                if verbose:
+                    click.echo(f"    Success: {output}")
+                else:
+                    click.echo("    Updated" if is_updated else "    Up to date")
+                if is_updated:
+                    updated_count += 1
+                else:
+                    up_to_date_count += 1

119-132: “New library content” detection will produce false positives; diff the actual changed files

Current heuristic checks for presence of any library files on disk (not newly added). This will repeatedly flag “new content” after any pull. Recommend diffing the last pull’s delta.

-    # Check git output for new files in key directories
-    pattern = r"[^\s]+\s+\|\s+\d+ [+]+(?:-+)?\s+(?:symbols|footprints|templates|3d)"
-    if re.search(pattern, git_output, re.IGNORECASE):
-        # This is a basic heuristic that indicates changes in library directories
-        # For more accuracy, we'll check the actual directories
-        pass
-
-    # Check for specific library file types
-    symbols_path = lib_path / "symbols"
-    footprints_path = lib_path / "footprints"
-    templates_path = lib_path / "templates"
-
-    # Look for symbol libraries (.kicad_sym files)
-    if symbols_path.exists() and symbols_path.is_dir() and any(
-        f.name.endswith(".kicad_sym") for f in symbols_path.glob("**/*.kicad_sym")
-    ):
-        changes.append("symbols")
-
-    # Look for footprint libraries (.pretty directories)
-    if footprints_path.exists() and footprints_path.is_dir() and any(
-        f.is_dir() and f.name.endswith(".pretty")
-        for f in footprints_path.glob("**/*.pretty")
-    ):
-        changes.append("footprints")
-
-    # Look for project templates (directories with metadata.yaml)
-    if templates_path.exists() and templates_path.is_dir() and any(
-        (f / "metadata.yaml").exists()
-        for f in templates_path.glob("*")
-        if f.is_dir()
-    ):
-        changes.append("templates")
+    # Inspect files changed by the last pull: prefer reflog range; fallback to last commit
+    changed_files: list[str] = []
+    try:
+        diff = subprocess.run(
+            ["git", "-C", str(lib_path), "diff", "--name-only", "--diff-filter=AR", "HEAD@{1}..HEAD"],
+            capture_output=True,
+            text=True,
+            check=False,
+            timeout=10,
+        )
+        if diff.returncode == 0 and diff.stdout.strip():
+            changed_files = diff.stdout.splitlines()
+        else:
+            # Fallback: names from last commit
+            show = subprocess.run(
+                ["git", "-C", str(lib_path), "show", "--name-only", "--pretty=", "HEAD"],
+                capture_output=True,
+                text=True,
+                check=False,
+                timeout=10,
+            )
+            if show.returncode == 0 and show.stdout.strip():
+                changed_files = show.stdout.splitlines()
+    except Exception:
+        changed_files = []
+
+    # Classify changes
+    for p in (s.strip().lower() for s in changed_files if s.strip()):
+        if p.startswith("symbols/") and p.endswith(".kicad_sym"):
+            if "symbols" not in changes:
+                changes.append("symbols")
+        elif p.startswith("footprints/") and (p.endswith(".kicad_mod") or p.endswith(".pretty") or "/.pretty/" in p):
+            if "footprints" not in changes:
+                changes.append("footprints")
+        elif p.startswith("templates/") and p.endswith("metadata.yaml"):
+            if "templates" not in changes:
+                changes.append("templates")

Additionally, consider removing “3d” from detection here since update operates on library_type="github" (3D “cloud” libs are handled elsewhere).

Also applies to: 190-225


153-165: Avoid CLI-to-CLI invocation and accidental process exit

setup can call sys.exit(1) on fatal paths. Use Click’s main(..., standalone_mode=False) and trap SystemExit so update doesn’t terminate mid-run.

-                ctx = click.Context(setup_cmd)
-                setup_cmd.invoke(ctx)
+                try:
+                    # Run with defaults; don't let click/sys.exit terminate this command
+                    setup_cmd.main(args=[], standalone_mode=False)
+                except SystemExit as e:
+                    if e.code:
+                        click.echo(
+                            f"Error: setup exited with code {e.code}. Please run 'kilm setup' manually.",
+                            err=True,
+                        )

Also applies to: 159-161

🧹 Nitpick comments (55)
kicad_lib_manager/commands/status/docs.mdx (1)

6-7: Remove unused imports or use the Card components.

LinkCard, Card, and CardGrid aren’t used in this page.

Apply this diff to drop the unused import:

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
kicad_lib_manager/commands/update/docs.mdx (2)

3-3: Keep formatting consistent for git commands.

Elsewhere in the doc you use backticks for git pull. Mirror that in the description.

-description: Update configured Git-based libraries using git pull.
+description: Update configured Git-based libraries using `git pull`.

6-7: Remove unused Card imports or add Card UI.

The imported components aren’t used.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
kicad_lib_manager/commands/add_3d/.command (1)

1-1: Add trailing newline for POSIX text-file convention.

Some tools expect newline-terminated files.

-add-3d
+add-3d
+
kicad_lib_manager/commands/unpin/docs.mdx (1)

6-7: Remove unused component imports (or add cards).

These components aren’t used in this MDX. Drop the import to avoid lint warnings and dead code, or add the Card UI if intended.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+// import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
kicad_lib_manager/commands/pin/command.py (1)

139-144: Clarify backup messaging.

update_pinned_libraries creates the backup internally; this echo can mislead if no write occurred. Consider printing only when the function actually wrote changes (you can have update_pinned_libraries return a second flag or message).

kicad_lib_manager/commands/pin/docs.mdx (1)

6-7: Drop unused component imports (or use them).

Same as unpin: either remove import or add Card UI.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+// import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
kicad_lib_manager/commands/setup/command.py (2)

42-42: Remove duplicate import inside function.

create_backup is already imported at module scope.

-    from ...utils.backup import create_backup
+    # use module-scope create_backup

57-66: Regex safety: constrain replacements.

The pattern may over-match if URI contains nested braces. Recommend using a non-greedy inner group and anchoring on balanced “${/…}” segments; add a unit test.

-            pattern = r'\(uri "\${(\/[^}]+)}\/(.*?)"\)'
+            pattern = r'\(uri "\${(\/[^}]+)}\/([^"]+)"\)'

Would you like a quick PyTest for fix_invalid_uris() covering macOS/Linux-style paths and a Windows-style case?

docs/astro.config.mjs (2)

6-6: Avoid importing unused plugin.

Either enable the plugin or remove the import to keep the bundle and dev logs clean.

-import starlightSiteGraph from "starlight-site-graph";
+// import starlightSiteGraph from "starlight-site-graph";

18-18: If keeping the plugin, enable it via config; otherwise keep the import commented.

-      // plugins: [starlightSiteGraph()],
+      // plugins: [starlightSiteGraph()],

(Optional) If you want the site graph now:

-      // plugins: [starlightSiteGraph()],
+      plugins: [starlightSiteGraph()],
kicad_lib_manager/commands/setup/__init__.py (1)

1-3: Prefer lazy re-export to avoid import-time side effects.
Small win for startup time and circular-import resilience.

Apply this diff:

-from .command import setup
-
-__all__ = ["setup"]
+from typing import Any
+
+def __getattr__(name: str) -> Any:
+    if name == "setup":
+        from .command import setup as _setup
+        return _setup
+    raise AttributeError(name)
+
+__all__ = ["setup"]
kicad_lib_manager/commands/setup/docs.mdx (1)

6-6: Imported Card components are unused.
Either remove the import or add a “Related commands” section using them.

Option A (remove import):

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+import { LinkCard, CardGrid } from "@astrojs/starlight/components";

Option B (keep import; add section near end):

+## Related
+
+<CardGrid>
+  <LinkCard title="status" href="/reference/cli/status/" description="Inspect current KiCad setup" />
+  <LinkCard title="pin" href="/reference/cli/pin/" description="Pin libraries for quick access" />
+  <LinkCard title="unpin" href="/reference/cli/unpin/" description="Unpin libraries" />
+  <LinkCard title="update" href="/reference/cli/update/" description="Update libraries and optionally auto-run setup" />
+</CardGrid>
kicad_lib_manager/commands/pin/__init__.py (1)

1-3: Optional: lazy re-export to reduce import costs.
Same pattern as suggested for setup.

-from .command import pin
-
-__all__ = ["pin"]
+from typing import Any
+
+def __getattr__(name: str) -> Any:
+    if name == "pin":
+        from .command import pin as _pin
+        return _pin
+    raise AttributeError(name)
+
+__all__ = ["pin"]
kicad_lib_manager/commands/list_libraries/docs.mdx (2)

6-6: Remove unused Card import.
CardGrid and LinkCard are used; Card isn’t.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+import { LinkCard, CardGrid } from "@astrojs/starlight/components";

94-97: Align error example with actual CLI output.
Docs show a specific message that may not match runtime.

-# Directory doesn't exist
-kilm list --kicad-lib-dir /nonexistent/path
-# Error: Directory '/nonexistent/path' does not exist
+# Directory doesn't exist
+kilm list --kicad-lib-dir /nonexistent/path
+# Error: Error listing libraries: [directory not found]
kicad_lib_manager/commands/init/docs.mdx (1)

173-182: Sample metadata: drop version unless it’s actually written.
updated_with is set in code; version isn’t obviously present.

 env_var: "KICAD_LIB_MY_LIBRARY"
 capabilities:
   symbols: true
   footprints: true
   templates: false
-version: "1.0.0"
 updated_with: "kilm"
docs/scripts/sync-embedded-docs.ts (2)

141-143: Log the actual filename, not “.mdx” always.

-    console.log(`Synced ${commandDoc.commandName}.mdx`);
+    console.log(`Synced ${path.basename(commandDoc.outputPath)}`);

56-87: Guard against duplicate command names overwriting each other.

If two dirs resolve to the same sanitized name, the latter overwrites the former silently.

Minimal warning just before return:

   const commandDocs: CommandDoc[] = [];
@@
-  return commandDocs;
+  // Warn on duplicates
+  const seen = new Map<string, string>();
+  for (const d of commandDocs) {
+    const prev = seen.get(d.commandName);
+    if (prev && prev !== d.embeddedPath) {
+      console.warn(`Duplicate command name "${d.commandName}" from:\n - ${prev}\n - ${d.embeddedPath}`);
+    }
+    seen.set(d.commandName, d.embeddedPath);
+  }
+  return commandDocs;
docs/package.json (2)

6-11: Add a dedicated clean script in root CI to avoid stale outputs.

Recommend wiring pnpm --filter kilm-docs run sync-docs:clean in CI before build to prevent drift.


25-29: Pinning dev tooling looks fine; consider adding a “typecheck” script.

Optional: "typecheck": "tsx --no-cache --tsconfig tsconfig.json --version >/dev/null && tsc -p . --noEmit" to catch TS issues in the sync script.

docs/src/styles/global.css (1)

62-77: Respect reduced motion preferences for the aurora animation.

Pause background animation for users with motion sensitivity.

   .aurora-bg {
@@
     animation: auroraShift 22s ease-in-out infinite;
   }
+  @media (prefers-reduced-motion: reduce) {
+    .aurora-bg { animation: none; }
+  }
kicad_lib_manager/commands/list_libraries/.command (1)

1-1: Add trailing newline for POSIX style consistency.

Minor style nit to reduce diff churn across editors.

-list
+list
+
kicad_lib_manager/commands/add_hook/.command (1)

1-1: Add trailing newline for consistency.

-add-hook
+add-hook
+
kicad_lib_manager/commands/unpin/command.py (2)

28-31: Avoid shadowing built-in all; improve UX and accuracy for counts and backup path.

  • Rename the Click parameter for --all to unpin_all to avoid overshadowing Python’s all().
  • Report actual numbers unpinned (diff of before/after), not the length of inputs.
  • Capture and print the backup path; use explicit UTF-8 for file I/O.
@@
-@click.option(
-    "--all",
-    is_flag=True,
-    help="Unpin all libraries",
-)
+@click.option(
+    "--all",
+    "unpin_all",
+    is_flag=True,
+    help="Unpin all libraries",
+)
@@
-def unpin(symbols, footprints, all, dry_run, max_backups, verbose):
+def unpin(symbols, footprints, unpin_all, dry_run, max_backups, verbose):
@@
-    if all and (symbols or footprints):
+    if unpin_all and (symbols or footprints):
         raise click.UsageError("'--all' cannot be used with '--symbols' or '--footprints'")
@@
-    if all:
+    if unpin_all:
         try:
-            with kicad_common.open() as f:
+            with kicad_common.open(encoding="utf-8") as f:
                 config = json.load(f)
@@
-    if not symbols and not footprints and not all:
+    if not symbols and not footprints and not unpin_all:
         click.echo("Error: No libraries specified to unpin", err=True)
@@
-        with kicad_common.open() as f:
+        with kicad_common.open(encoding="utf-8") as f:
             config = json.load(f)
@@
-        changes_needed = False
+        changes_needed = False
+        removed_symbol_count = 0
+        removed_fp_count = 0
@@
-        if "pinned_symbol_libs" in config["session"] and symbols:
+        if "pinned_symbol_libs" in config["session"] and symbols:
             current_symbols = config["session"]["pinned_symbol_libs"]
             new_symbols = [lib for lib in current_symbols if lib not in symbols]
 
             if len(new_symbols) != len(current_symbols):
                 changes_needed = True
+                removed_symbol_count = len(current_symbols) - len(new_symbols)
                 if not dry_run:
                     config["session"]["pinned_symbol_libs"] = new_symbols
@@
-        if "pinned_fp_libs" in config["session"] and footprints:
+        if "pinned_fp_libs" in config["session"] and footprints:
             current_footprints = config["session"]["pinned_fp_libs"]
             new_footprints = [
                 lib for lib in current_footprints if lib not in footprints
             ]
 
             if len(new_footprints) != len(current_footprints):
                 changes_needed = True
+                removed_fp_count = len(current_footprints) - len(new_footprints)
                 if not dry_run:
                     config["session"]["pinned_fp_libs"] = new_footprints
@@
-        if changes_needed and not dry_run:
+        if changes_needed and not dry_run:
             # Create backup before making changes
-            create_backup(kicad_common, max_backups)
+            backup_path = create_backup(kicad_common, max_backups)
@@
-            with kicad_common.open("w") as f:
+            with kicad_common.open("w", encoding="utf-8") as f:
                 json.dump(config, f, indent=2)
@@
-        if changes_needed:
+        if changes_needed:
             if dry_run:
                 click.echo(
-                    f"Would unpin {len(symbols) if symbols else 0} symbol and {len(footprints) if footprints else 0} footprint libraries in KiCad"
+                    f"Would unpin {removed_symbol_count} symbol and {removed_fp_count} footprint libraries in KiCad"
                 )
             else:
                 click.echo(
-                    f"Unpinned {len(symbols) if symbols else 0} symbol and {len(footprints) if footprints else 0} footprint libraries in KiCad"
+                    f"Unpinned {removed_symbol_count} symbol and {removed_fp_count} footprint libraries in KiCad"
                 )
-                click.echo("Created backup of kicad_common.json")
+                if 'backup_path' in locals():
+                    click.echo(f"Created backup: {backup_path}")
                 click.echo("Restart KiCad for changes to take effect")

Also applies to: 49-54, 70-97, 101-109


118-121: Minor: early-return on missing session duplicative.

You read the file twice and check “session” in two places. Consider consolidating to a single read/check.

Also applies to: 124-129

kicad_lib_manager/commands/init/command.py (2)

173-201: YAML template write: ensure valid YAML and encoding.

You’re already using UTF-8; consider validating the template once in tests to avoid regressions.

I can add a tiny unit test that parses library_descriptions.yaml with PyYAML.


224-237: Persist more context in metadata (optional).

Add CLI version to updated_with (e.g., kilm vX.Y) for traceability.

-    metadata["capabilities"] = updated_capabilities
-    write_github_metadata(current_dir, metadata)
+    metadata["capabilities"] = updated_capabilities
+    metadata["updated_with"] = f"kilm {click.get_current_context().command.help.splitlines()[0] if __name__ else ''}"
+    write_github_metadata(current_dir, metadata)

Also applies to: 238-244

kicad_lib_manager/commands/config/command.py (2)

30-45: Avoid shadowing built-in list.

Rename the function but keep the CLI name stable.

-@config.command()
-def list(library_type, verbose):
+@config.command(name="list")
+def list_cmd(library_type, verbose):

Also applies to: 45-56


344-363: Removing the current default library: clarify behavior.

Config keeps current_library; removing its entry won’t update it. Make the UX explicit.

@@
-        # Find libraries matching the name and type
+        # Find libraries matching the name and type
         matching_libraries = []
         for lib in all_libraries:
             if lib.get("name") == library_name and (library_type == "all" or lib.get("type") == library_type):
                 matching_libraries.append(lib)
@@
-        # Remove libraries
+        # Track if we’re removing the current default
+        removed_current = any(lib.get("path") == current_library for lib in matching_libraries)
+
+        # Remove libraries
         removed_count = 0
         for lib in matching_libraries:
             lib_type = lib.get("type", "unknown")
             removed = config.remove_library(library_name, lib_type)
             if removed:
                 removed_count += 1
@@
-            # Check if we removed the current library
-            current_library_new = config.get_current_library()
-            if current_library and current_library != current_library_new:
-                click.echo(
-                    "Note: Default library was changed as the previous default was removed."
-                )
+            # Note if the previous default was removed
+            if removed_current:
+                click.echo("Note: The previous default library was removed.")
+                click.echo('Run "kilm config set-default" to select a new default.')

Also applies to: 381-405

kicad_lib_manager/commands/add_hook/command.py (1)

89-96: Specify encoding on write for parity with reads.

Small consistency/portability win.

-        with post_merge_hook.open("w") as f:
+        with post_merge_hook.open("w", encoding="utf-8") as f:
             f.write(new_content)
kicad_lib_manager/commands/add_3d/command.py (1)

154-171: Avoid double recursive scans for model files.

You scan once to detect presence and again to count. Consider counting once and using that for both checks.

Apply:

-    found_models = False
-    # Do a quick check for model files
-    for ext in model_extensions:
-        if list(directory.glob(f"**/*{ext}")):
-            found_models = True
-            break
+    # Count models once
+    model_count = sum(len(list(directory.glob(f"**/*{ext}"))) for ext in model_extensions)
+    found_models = model_count > 0
@@
-    # Update metadata with actual model count
-    model_count = 0
-    for ext in model_extensions:
-        model_count += len(list(directory.glob(f"**/*{ext}")))
+    # Update metadata with actual model count (already computed)
kicad_lib_manager/commands/status/command.py (1)

21-23: Use config constants instead of hardcoding the config path.

Reduces drift with Config implementation.

Example:

-            config_file = Path.home() / ".config" / "kicad-lib-manager" / "config.yaml"
+            from ...config import CONFIG_DIR_NAME, CONFIG_FILE_NAME
+            config_file = Path.home() / ".config" / CONFIG_DIR_NAME / CONFIG_FILE_NAME
kicad_lib_manager/commands/template/command.py (1)

951-957: Avoid shadowing the json module with a parameter.

Rename the option’s parameter to avoid confusion.

-@click.option(
-    "--json",
+@click.option(
+    "--json",
     is_flag=True,
     default=False,
     help="Output in JSON format",
     show_default=True,
 )
-def list_templates_cmd(library, verbose, json):
+def list_templates_cmd(library, verbose, json: bool):
@@
-    if json:
-        import json as json_lib
+    if json:
+        import json as json_lib

Alternatively, keep the CLI flag as --json but name the parameter as_json: click.option("--json", "as_json", is_flag=True, ...).

Also applies to: 1004-1008

tests/test_unpin_command.py (4)

40-48: Patch target looks right; also stub create_backup to avoid FS side effects.

Nice redirect to unpin.command. To keep tests pure when changes are written, stub create_backup alongside find_kicad_config and patch Path.open explicitly.

-        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config:
+        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config, \
+             patch('kicad_lib_manager.commands.unpin.command.create_backup'):
             mock_find_config.return_value = Path("/tmp/kicad")
@@
-            with patch('builtins.open') as mock_open:
+            with patch('pathlib.Path.open') as mock_open:
                 mock_open.return_value.__enter__.return_value.read.return_value = '{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": []}}'

56-69: Same here: good target; also patch create_backup to avoid unintended writes.

Without stubbing, a successful unpin path will call create_backup. Patch it out.

-        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config:
+        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config, \
+             patch('kicad_lib_manager.commands.unpin.command.create_backup'):
             mock_find_config.return_value = Path("/tmp/kicad")
@@
-                with patch('builtins.open') as mock_open:
+                with patch('pathlib.Path.open') as mock_open:
                     mock_open.return_value.__enter__.return_value.read.return_value = '{"session": {"pinned_symbol_libs": ["lib1"], "pinned_fp_libs": []}}'

72-85: Ditto for footprints case; stub create_backup and prefer Path.open.

Prevents backup writes and keeps IO mocked consistently.

-        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config:
+        with patch('kicad_lib_manager.commands.unpin.command.find_kicad_config') as mock_find_config, \
+             patch('kicad_lib_manager.commands.unpin.command.create_backup'):
             mock_find_config.return_value = Path("/tmp/kicad")
@@
-                with patch('builtins.open') as mock_open:
+                with patch('pathlib.Path.open') as mock_open:
                     mock_open.return_value.__enter__.return_value.read.return_value = '{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": ["lib1"]}}'

40-40: Optionally assert output for stronger guarantees.

Instead of exit_code in [0, 1], assert specific messages (e.g., “No pinned libraries found” vs “Unpinned …”). Improves signal.

tests/test_update_command.py (1)

168-173: Good; also simplify auto-setup mocking by patching setup_cmd directly.

Avoids sys.modules shimming and couples to the symbol update.command actually uses.

-        with patch.dict(
-            "sys.modules", {"kicad_lib_manager.commands.setup": setup_module_mock}
-        ):
-            runner = CliRunner()
-            result = runner.invoke(main, ["update", "--auto-setup"])
+        with patch("kicad_lib_manager.commands.update.command.setup_cmd") as setup_cmd:
+            runner = CliRunner()
+            result = runner.invoke(main, ["update", "--auto-setup"])
+            setup_cmd.assert_called()
kicad_lib_manager/commands/list_libraries/command.py (1)

47-49: Optional: raise ClickException instead of sys.exit for better UX.

Leverages Click’s error handling.

-    except Exception as e:
-        click.echo(f"Error listing libraries: {e}", err=True)
-        sys.exit(1)
+    except Exception as e:
+        raise click.ClickException(f"Error listing libraries: {e}")
kicad_lib_manager/commands/update/command.py (3)

133-135: Send failures to stderr

Surface failures on stderr for better UX and scripting.

-                click.echo(f"    Failed: {result.stderr.strip()}")
+                click.echo(f"    Failed: {result.stderr.strip()}", err=True)
@@
-            click.echo(f"    Error: {str(e)}")
+            click.echo(f"    Error: {str(e)}", err=True)

Also applies to: 137-138


91-95: Dry-run should not increment “updated” count

Currently dry-run adds to updated_count (“Actually changed”), which skews the summary. Track a separate planned_update_count or only print the intent without touching counters.


178-178: Move helper to a services/utils module

Agree with the TODO. Extract check_for_library_changes into a testable utility (e.g., utils/git_changes.py) and add unit tests around the diff classification.

docs/README.md (2)

16-26: Surface doc-sync commands and clarify where to run them

These commands execute from the docs/ workspace, and it’s useful to expose the new sync scripts.

Apply:

- All commands are run from the root of the project, from a terminal:
+ Run these from the docs/ directory:

 | Command        | Action                                       |
 | :------------- | :------------------------------------------- |
 | `pnpm install` | Installs dependencies                        |
 | `pnpm dev`     | Starts local dev server at `localhost:4321`  |
 | `pnpm build`   | Build your production site to `./dist/`      |
 | `pnpm preview` | Preview your build locally, before deploying |
+| `pnpm sync-docs`        | One-shot sync of embedded CLI docs            |
+| `pnpm sync-docs:watch`  | Watch and continuously sync embedded CLI docs |
+| `pnpm sync-docs:clean`  | Remove generated CLI docs                      |

29-43: Add a language to the fenced code block (markdownlint MD040)

Specifying a language improves rendering and satisfies linting.

Apply:

-```
+```text
 docs/
 ├── public/               # Static assets
 ├── src/
 │   ├── assets/           # Images and other assets
 │   ├── content/
 │   │   ├── docs/         # Documentation content
 │   │   │   ├── guides/   # User guides and tutorials
 │   │   │   ├── reference/ # Command reference and API docs
 │   │   │   └── community/ # Contributing guidelines
 │   └── content.config.ts # Content collection config
 ├── astro.config.mjs      # Astro configuration
 ├── package.json
 └── README.md            # This file
-```
+```
docs/src/content/docs/guides/getting-started.mdx (1)

134-139: Include --env-var in the Consumer 3D registration example for consistency

The preceding text instructs using the same --env-var as the Creator, but the example omits it.

Apply:

     # Example: Pointing to your local path for the shared models
     kilm add-3d --name shared-3d-models \
-                --directory /Users/myuser/GoogleDrive/kicad-3d-models
+                --directory /Users/myuser/GoogleDrive/kicad-3d-models \
+                --env-var ACME_3D_MODELS
docs/src/content/docs/guides/installation.mdx (1)

37-47: Prefer uv tool install for CLI installation

uv pip install targets the current environment; uv tool install is the intended CLI workflow with shims.

Apply:

-# Install KiLM using uv
-uv pip install kilm
+# Install KiLM as a CLI tool using uv
+uv tool install kilm
kicad_lib_manager/commands/add_3d/docs.mdx (3)

6-6: Remove unused import.

Card isn’t used.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+import { LinkCard, CardGrid } from "@astrojs/starlight/components";

32-36: Fix grammar and overstatement about metadata contents.

  • “associate” → “associated”.
  • “Last updated timestamp” isn’t guaranteed by the current implementation; it sets updated_with and model_count, not a timestamp.
-1. **Creates/Updates Metadata**: Creates or modifies `.kilm_cloud_metadata` in the target directory containing:
-   - Library name, description, and associated environment variable
-   - Count of detected model files
-   - Last updated timestamp
+1. **Creates/Updates Metadata**: Creates or modifies `.kilm_cloud_metadata` in the target directory containing:
+   - Library name, description, and associated environment variable
+   - Count of detected model files
+   - Update markers (e.g., tool/version info)

If you do want a timestamp, I can add metadata["updated_at"] = datetime.utcnow().isoformat() in the command. Want a patch?


32-32: Unify metadata filename across code and docs.

This page uses .kilm_cloud_metadata, while the add_3d docstring references .kilm_metadata. Pick one and make CLI help/messages consistent to avoid confusion.

kicad_lib_manager/commands/add_hook/docs.mdx (2)

6-6: Remove unused import.

Card isn’t used.

-import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";
+import { LinkCard, CardGrid } from "@astrojs/starlight/components";

178-183: Windows note (optional).

Git for Windows runs hooks via sh; chmod semantics differ. Consider a brief note that hooks still work on Windows with Git Bash.

docs/src/content/docs/community/development.mdx (2)

12-15: Re-evaluate Python version floor.

Python 3.7/3.8 are EOL. Recommend 3.9+ (or 3.10+) unless you must support older.

-- **Python 3.7+**: Required for KiLM development
+- **Python 3.9+**: Recommended for KiLM development

125-130: Avoid advertising a non-existent “make quality”.

Either add the target or mark as “coming soon”.

-# Run complete quality check (to be implemented)
-# This should include: pyrefly, black, ruff, pytest
-make quality  # or equivalent script
+# Run complete quality check (coming soon)
+# Intended to include: type-check, black, ruff, pytest
+# make quality  # or equivalent script
docs/src/content/docs/guides/configuration.mdx (1)

64-76: Clarify metadata formats: kilm.yaml vs .kilm_cloud_metadata.

Elsewhere (add-3d) we document .kilm_cloud_metadata for 3D (cloud) libs. Here we introduce kilm.yaml. Explain coexistence and when each is used to avoid confusion.

-Each library directory can contain a `kilm.yaml` file with metadata:
+Each library directory can contain a `kilm.yaml` file with metadata.
+Cloud-based 3D libraries additionally use a per-folder `.kilm_cloud_metadata` file managed by `kilm add-3d`.

Comment thread docs/scripts/sync-embedded-docs.ts
Comment thread docs/scripts/sync-embedded-docs.ts
Comment thread docs/scripts/sync-embedded-docs.ts
Comment thread docs/src/content/docs/community/development.mdx
Comment thread docs/src/content/docs/guides/getting-started.mdx
Comment thread kicad_lib_manager/__init__.py Outdated
Comment thread kicad_lib_manager/commands/list_libraries/docs.mdx
Comment thread kicad_lib_manager/commands/setup/command.py
Comment thread kicad_lib_manager/commands/setup/command.py Outdated
Comment thread kicad_lib_manager/commands/update/__init__.py
@barisgit barisgit merged commit a4ac89d into main Sep 5, 2025
8 of 9 checks passed
@barisgit barisgit deleted the feature/collocated-docs branch September 5, 2025 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant