Skip to content

Add namespaced --category filter to skern skill list (#96)#98

Open
devrimcavusoglu wants to merge 1 commit into
mainfrom
feature/category-tag-filter
Open

Add namespaced --category filter to skern skill list (#96)#98
devrimcavusoglu wants to merge 1 commit into
mainfrom
feature/category-tag-filter

Conversation

@devrimcavusoglu

Copy link
Copy Markdown
Owner

Closes #96.

Summary

Adds a repeatable, domain-agnostic --category category:value flag to skern skill list for narrowing a skill set by structured tag dimensions (e.g. lang:python, topic:testing) without skern knowing what any category means. The namespace is whatever precedes the first :; skern never enumerates or special-cases known categories. The existing flat --tag filter is unchanged, and the two compose with AND.

Design decisions

The issue left two semantics questions open. Resolved as follows (these were confirmed with the maintainer before implementation):

1. Untagged / category-absent handling — strict by default, wildcard opt-in.
A skill with no tag in a requested category is excluded. --include-untagged opts into "absent = applies to all". A category the skill does declare must still match a requested value even with the flag — --include-untagged only relaxes the absent case, never the present-but-mismatched case.

2. Combination — OR within a category, AND across categories.
--category lang:python,go --category topic:testing matches skills tagged (lang:python or lang:go) and topic:testing. Values can be supplied as repeated flags or a comma list; --category lang:python --category lang:go--category lang:python,go.

Implementation

  • parseCategoryFilters (in skill_helpers.go) parses the repeated flag into a namespace -> []value map, lowercasing for case-insensitive matching (consistent with hasTag). Malformed input — missing colon, empty category, empty value — returns a ValidationError (exit code 2).
  • matchesCategories sits beside hasTag and applies OR-within / AND-across with the untagged rule. Flat tags (no colon) are not categorical and are ignored by it — they remain --tag territory.
  • skill_list.go parses filters once up front (so validation errors surface immediately) and ANDs the matcher with the existing --tag check in the discovery loop.

Edge cases handled (per acceptance criteria)

  • Tag with no colon → not categorical, never matches a --category
  • Empty value / empty category / missing colon in the flag → exit code 2 with an actionable message
  • Skill with zero tags → category-absent (excluded by default, included with --include-untagged)
  • Case-insensitive matching on both namespace and value

Test plan

  • go test ./... — all packages pass
  • gofmt clean; golangci-lint run ./internal/cli/... → 0 issues
  • Unit tests: parseCategoryFilters (9 cases incl. all malformed inputs) and matchesCategories (13 cases: OR/AND, strict vs include-untagged, zero-tag, flat-tag, case-insensitivity)
  • --json contract test end-to-end (TestSkillList_FilterByCategory): single value, OR-within, AND-across, strict default, --include-untagged
  • --tag + --category composition (TestSkillList_TagAndCategory) and the exit-code-2 path (TestSkillList_FilterByCategory_Invalid)
  • Manual smoke test of the built binary confirms all five behaviors and exit code 2 on malformed input

Manual-test gate: the acceptance criteria name the agent-driven manual-test gate, which is an interactive release-time process (build binary + drive scenarios with an agent in /tmp). This change doesn't alter any existing scenario; the --json contract is covered by the automated tests above. The maintainer runs the full manual gate before release as usual.

Docs

docs/reference/commands.md: added --category / --include-untagged to the skill list flag table plus a "Categorical-tag filtering" section documenting repeatability, the OR/AND model, the strict-vs---include-untagged rule, and the exit-code-2 behavior.

🤖 Generated with Claude Code

Adds a repeatable, domain-agnostic `--category category:value` flag to
`skern skill list` for narrowing a skill set by structured tag dimensions
(e.g. `lang:python`, `topic:testing`) without skern knowing what any
category means. The existing flat `--tag` filter is unchanged; the two
compose with AND.

Semantics (the two design questions the issue left open):

- Untagged / category-absent handling: strict by default — a skill with
  no tag in a requested category is excluded. `--include-untagged` opts
  into "absent = applies to all". A category the skill *does* declare must
  still match a requested value even with the flag.
- Combination: OR within a category, AND across categories. Values can be
  supplied as repeated flags (`--category lang:python --category lang:go`)
  or a comma list (`--category lang:python,go`); the two forms are
  equivalent.

The matcher (`matchesCategories`) sits beside `hasTag` in
skill_helpers.go; `parseCategoryFilters` validates flag input and returns
a ValidationError (exit code 2) for a missing colon, empty category, or
empty value. Matching is case-insensitive, consistent with `hasTag`. Flat
tags (no colon) are never categorical and remain `--tag` territory.

Tests cover the parser, the matcher (OR/AND, strict vs include-untagged,
zero-tag and flat-tag edge cases, case-insensitivity), and the end-to-end
`--json` contract including --tag/--category composition and the
exit-code-2 path. Docs updated in reference/commands.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Add a namespaced categorical-tag query to skern skill list

1 participant