You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
opskit go: config-driven enabled-projects list (list only enabled, recurse for nested) #440
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
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.
Activity
growlf commentedon Oct 13, 2026
Closes #440
What & why
opskit gowas listing every top-level git repo underprojects_dirand 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.ymlthe source of truth.Changes
opskit/_cache.py):projects:key +get_projects(); missing/null normalised; cache invalidates whenconfig.ymlis newer than the cache.bin/opskit-projects.py): walksprojects_dir(depth ≤ 4, pruning dotdirs/VCS/build dirs); grouping dirs are descended into; git worktrees (.gitfile or*-wt-*) excluded; each record carries arelpath.opskit projects bootstrap|add|remove;list/rescanrespect the list.bin/opskit-go.py): empty list + TTY → offer to scan for.opskit/pack.ymlmembers and enable them; non-TTY never blocks.--height); git detail computed lazily so scanning no longer shells out togitper project; close-match suggestions.Verification
make test→ 1675 passed, 1 skipped.bootstrapenables only thepack.ymlmember; non-contiguous and rel-path queries match it.Plan:
plans/440-opskit-go-config-driven-enabled-projects.md.