Skip to content

opskit go: config-driven enabled-projects list (list only enabled, recurse for nested) #440

Closed

Description

@growlf

opskit go lists/selects from every top-level git repo under projects_dir and never recurses, so:

  • opskit go (no arg) feeds fzf all detected repos instead of the user's enabled project set.
  • Nested projects are invisible. A grouping folder that itself is not a repo (no .git/, no .opskit/pack.yml) is skipped entirely, so real projects inside it are never seen: opskit go <nested-name> → No project matches.

Root cause: bin/opskit-projects.py::scan_project_dir iterates only the top level (iterdir()) and includes any directory with .git/ or .opskit/pack.yml.

Intended behaviour (#437 Goal: "any OpsKit-enabled project"): the set offered by opskit go/web should be the user's enabled projects, determined from a config file in ~/.opskit/. That logic was never built — opskit/_cache.py::read_config() honours only projects_dir, and ~/.opskit/config.yml is not created at all.

Fix:

  • Add a projects: allow-list to ~/.opskit/config.yml; opskit go/web list only enabled projects (the config is the source of truth).
  • Recurse under projects_dir so nested projects resolve (match by basename and by relative path).
  • opskit projects add/remove/bootstrap manage the list; list/rescan respect it.
  • Fix latent launcher bugs: the no-op sort condition in sort_projects_by_last_used, substring matching (→ subsequence), and the git-per-project scan cost.

Repro:

opskit go                 # dumps all repos, not the enabled set
opskit go <nested-name>   # "No project matches" though the project exists nested

Activity

  1. self-assigned this
    on Oct 13, 2026
  2. growlf commented on Oct 13, 2026

    @growlf
    OwnerAuthor

    Closes #440

    What & why

    opskit go was listing every top-level git repo under projects_dir and never recursing, so nested projects were invisible and the TUI was noise. The "enabled" mechanism the original plan promised (a config file in ~/.opskit/) was never built.

    This makes ~/.opskit/config.yml the source of truth.

    Changes

    • Config allow-list (opskit/_cache.py): projects: key + get_projects(); missing/null normalised; cache invalidates when config.yml is newer than the cache.
    • Recursive, bounded discovery (bin/opskit-projects.py): walks projects_dir (depth ≤ 4, pruning dotdirs/VCS/build dirs); grouping dirs are descended into; git worktrees (.git file or *-wt-*) excluded; each record carries a rel path.
    • Enabled filter: selectors resolve by rel path / basename / name / abs path; unmatched selectors warn.
    • Management: opskit projects bootstrap|add|remove; list/rescan respect the list.
    • First-run prompt (bin/opskit-go.py): empty list + TTY → offer to scan for .opskit/pack.yml members and enable them; non-TTY never blocks.
    • Launcher fixes: no-op sort condition fixed; substring match → subsequence (fzf-style) over name/basename/rel; inline fzf (--height); git detail computed lazily so scanning no longer shells out to git per project; close-match suggestions.

    Verification

    • make test → 1675 passed, 1 skipped.
    • New tests: config round-trip + config-mtime staleness, recursive discovery through a grouping dir, worktree exclusion, enabled filter, unmatched-selector warning, bootstrap members-only, add/remove, no git at scan time, subsequence + nested-rel matching.
    • Sandbox demo: a nested project is found through its grouping dir; worktree excluded; bootstrap enables only the pack.yml member; non-contiguous and rel-path queries match it.

    Plan: plans/440-opskit-go-config-driven-enabled-projects.md.

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

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions