feat(vendor): --exclude plus content-type selection (--types/--exclude-types)

vendor add gains --exclude a,b,c (all mode minus those ids) as the mirror
of --include, for taking the majority of a source while dropping a few
items. Mutual exclusion with --include is a hard error, unknown ids fail
naming the available ones (generalized checkIDEntries).

vendor add and vendor select gain --types (allow-list) and --exclude-types
(deny-list) over the four content types. The filter is stored as the
optional, v2-compatible Selection.exclude_types field and survives vendor
update: new upstream items of an excluded type never enter the work
repository (unlike id expansion, which would silently re-admit them).
--types/--exclude-types replace the filter on select, absent flags keep
it, and --mode all clears it along with include/exclude.

Also fixes materializeTree to create the staging directory for an empty
selection — previously select-to-zero failed at the staging rename.
This commit is contained in:
m3ta-chiron committed 2026-10-02 12:35:39 +02:00
1 parent 1ab97bff43
commit 0c779bc970
9 files changed
+533 -50

No files matched your search

+30 -14
View File
@@ -26,7 +26,7 @@ func newVendorCmd() *cobra.Command {
func newVendorAddCmd() *cobra.Command {
var ref string
var include []string
var include, exclude, onlyTypes, excludeTypes []string
var renames []string
cmd := &cobra.Command{
Use: "add <name> <url>",
@@ -34,7 +34,10 @@ func newVendorAddCmd() *cobra.Command {
Long: "Clones the source (in-process, via go-git), discovers skills, commands,\n" +
"agents and MCP fragments, materializes the selection under external/<name>/\n" +
"and pins URL, ref, revision, selection and renames in the lockfile.\n" +
"Default selection is everything; --include switches to an explicit list.\n" +
"Default selection is everything; --include switches to an explicit list,\n" +
"--exclude keeps everything except the listed ids. --types (allow-list) and\n" +
"--exclude-types (deny-list) filter whole content types and survive updates:\n" +
"new upstream items of an excluded type never enter the work repository.\n" +
"Collisions abort with a hard error; resolve them with --rename upstream=deployed.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
@@ -43,11 +46,14 @@ func newVendorAddCmd() *cobra.Command {
return err
}
opts := vendor.AddOptions{
Name: args[0],
URL: args[1],
Ref: ref,
Include: splitList(include),
Renames: renameMap,
Name: args[0],
URL: args[1],
Ref: ref,
Include: splitList(include),
Exclude: splitList(exclude),
OnlyTypes: splitList(onlyTypes),
ExcludeTypes: splitList(excludeTypes),
Renames: renameMap,
}
if err := vendor.Add(".", opts, cmd.OutOrStdout()); err != nil {
return fmt.Errorf("vendor add: %w", err)
@@ -57,6 +63,9 @@ func newVendorAddCmd() *cobra.Command {
}
cmd.Flags().StringVar(&ref, "ref", "", "branch or tag to track (default: the source's default branch)")
cmd.Flags().StringSliceVar(&include, "include", nil, "comma-separated item ids to vendor (switches selection to include mode)")
cmd.Flags().StringSliceVar(&exclude, "exclude", nil, "comma-separated item ids to skip (keeps all mode, drops these ids)")
cmd.Flags().StringSliceVar(&onlyTypes, "types", nil, "comma-separated content types to take: skills, commands, agents, mcp (allow-list)")
cmd.Flags().StringSliceVar(&excludeTypes, "exclude-types", nil, "comma-separated content types to skip: skills, commands, agents, mcp (deny-list)")
cmd.Flags().StringSliceVar(&renames, "rename", nil, "deployed-name override upstream-id=deployed-name (repeatable)")
return cmd
}
@@ -75,6 +84,7 @@ func splitList(in []string) []string {
func newVendorSelectCmd() *cobra.Command {
var add, remove []string
var onlyTypes, excludeTypes []string
var mode string
cmd := &cobra.Command{
Use: "select <name>",
@@ -83,9 +93,11 @@ func newVendorSelectCmd() *cobra.Command {
"Include mode: --add appends ids (validated against the pinned upstream),\n" +
"--remove drops ids and deletes their folders. All mode: --remove moves ids\n" +
"onto the exclude list; --add is an error (everything is selected already).\n" +
"--mode all resets to everything: include/exclude lists are cleared and the\n" +
"full pinned upstream is re-materialized. Collisions re-run on every change;\n" +
"renames from the lockfile are honored. The revision never moves — that is\n" +
"--types/--exclude-types replace the content-type filter (skills, commands,\n" +
"agents, mcp); absent flags keep it as-is. --mode all resets to everything:\n" +
"include/exclude lists and the type filter are cleared and the full pinned\n" +
"upstream is re-materialized. Collisions re-run on every change; renames\n" +
"from the lockfile are honored. The revision never moves — that is\n" +
"vendor update's job.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
@@ -93,9 +105,11 @@ func newVendorSelectCmd() *cobra.Command {
return fmt.Errorf("--mode accepts only \"all\" (got %q)", mode)
}
opts := vendor.SelectOptions{
Add: splitList(add),
Remove: splitList(remove),
ModeAll: mode == "all",
Add: splitList(add),
Remove: splitList(remove),
ModeAll: mode == "all",
OnlyTypes: splitList(onlyTypes),
ExcludeTypes: splitList(excludeTypes),
}
if _, err := vendor.Select(".", args[0], opts, cmd.OutOrStdout()); err != nil {
return fmt.Errorf("vendor select: %w", err)
@@ -105,6 +119,8 @@ func newVendorSelectCmd() *cobra.Command {
}
cmd.Flags().StringSliceVar(&add, "add", nil, "comma-separated upstream ids to add to the selection")
cmd.Flags().StringSliceVar(&remove, "remove", nil, "comma-separated upstream ids to remove from the selection")
cmd.Flags().StringVar(&mode, "mode", "", "switch selection mode (only \"all\": clear include/exclude, take everything)")
cmd.Flags().StringSliceVar(&onlyTypes, "types", nil, "content types to take: skills, commands, agents, mcp (allow-list; replaces the type filter)")
cmd.Flags().StringSliceVar(&excludeTypes, "exclude-types", nil, "content types to skip: skills, commands, agents, mcp (deny-list; replaces the type filter)")
cmd.Flags().StringVar(&mode, "mode", "", "switch selection mode (only \"all\": clear include/exclude and the type filter, take everything)")
return cmd
}