Skip to content

Commit 8655fd4

Browse files
release v0.13.0: pack create redesign, profile resolution fix, TUI input fixes
Signed-off-by: davidfos <david.d.foster@oracle.com>
1 parent 083e5be commit 8655fd4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+473
-418
lines changed

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Four harness adapters (`claudecode`, `opencode`, `codex`, `cline`) handle both f
2929

3030
- Wrap errors with `%w` — always preserve context
3131
- Exit codes: `cmdutil.ExitOK` (0), `ExitFail` (1), `ExitUsage` (2)
32-
- Tests: `t.Parallel()` where safe, `t.TempDir()` for isolation, NEVER `t.Parallel()` with `t.Setenv()`
32+
- Tests: `t.Parallel()` where safe, `t.TempDir()` for isolation, NEVER `t.Parallel()` with `t.Setenv()` or `t.Chdir()`
3333
- Use `make fmt` (not raw `gofmt -w`) for formatting
3434
- `--skip-settings` skips settings only; MCP configs and plugins always sync
3535
- Version injected via ldflags at build time (`-X main.version`, `-X main.commit`)
@@ -56,8 +56,9 @@ Four harness adapters (`claudecode`, `opencode`, `codex`, `cline`) handle both f
5656
## Workflow
5757

5858
- Before editing: read nearby code and related tests
59-
- After editing: `go test ./...`, then `go vet ./...`
60-
- Before declaring done: `make build && make test && go vet ./...` must all pass
59+
- After editing: `go test ./...`, then `make lint`
60+
- Before declaring done: `make build && make test && make lint` must all pass
61+
- `make lint` runs `go vet`, `staticcheck` (if installed), and `go fix ./...` (applies Go modernization fixes in-place).
6162
- Pre-commit: `go build ./...``go test ./...``make fmt` → check `git diff` for fmt changes → stage any → commit
6263
- Feature work going to main: single atomic commit, not per-task intermediaries
6364
- Commits use `git commit --signoff`

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on Keep a Changelog, and releases use semantic versioning ta
66

77
## Unreleased
88

9+
## [0.13.0]
10+
11+
### Changed
12+
13+
- **`pack create` redesigned.** Takes a pack name instead of a directory path. Packs are created in the current directory and symlinked into the packs directory by default (`--link` behavior). Use `--local` to create directly inside the packs directory. Registration in sync-config is now automatic.
14+
- Registry search and list output uses a structured multi-line format with labeled fields (Name, Description, Owner, Repo, Path, Ref, Contact).
15+
- `make lint` now runs `go fix ./...` alongside `go vet` and `staticcheck`.
16+
17+
### Fixed
18+
19+
- Disabled packs no longer participate in override conflict resolution during profile resolution. Previously, a disabled pack declaring overrides could incorrectly suppress duplicate-resource errors between enabled packs.
20+
- TUI text input handles multi-byte key events correctly, preventing phantom characters from special key presses.
21+
- TUI pack name input no longer leaks keypresses to global hotkeys. Previously, typing characters that matched global shortcuts (e, s, r, w, digits) while entering a new pack name would trigger tab switches, sync, or other actions instead of appending to the input.
22+
- TUI "Save to pack" action (`.` menu) now scopes to the cursor item instead of carrying forward all selected files.
23+
924
## [0.12.1]
1025

1126
### Changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ fmt: ## Format Go source
3232
fmt-check: ## Fail if Go source is not formatted
3333
@test -z "$$(gofmt -l . | grep -v '^dist/' )" || { gofmt -l . | grep -v '^dist/'; echo "Go files need formatting. Run: make fmt"; exit 1; }
3434

35-
lint: ## Run static analysis (go vet + staticcheck if available)
35+
lint: ## Run static analysis (go vet + staticcheck + go fix)
3636
go vet $(GO_TAGS) ./...
3737
@if command -v staticcheck >/dev/null 2>&1; then staticcheck $(GO_TAGS) ./...; fi
38+
go fix ./...
3839

3940
release-tag-check: ## Validate TAG against VERSION (supports prereleases)
4041
@test -n "$(TAG)" || { echo "usage: make release-tag-check TAG=vX.Y.Z[-suffix]"; exit 1; }

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.12.1
1+
0.13.0

cmd/aipack/architecture_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestArchitecture_ServicePackagesDoNotImportCmd(t *testing.T) {
2020
}
2121

2222
forbidden := "github.com/shrug-labs/aipack/cmd/"
23-
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
23+
for line := range strings.SplitSeq(strings.TrimSpace(string(out)), "\n") {
2424
if strings.TrimSpace(line) == "" {
2525
continue
2626
}
@@ -60,7 +60,7 @@ func TestArchitecture_NoDeletedPackages(t *testing.T) {
6060
"github.com/shrug-labs/aipack/internal/app/doctor",
6161
"github.com/shrug-labs/aipack/internal/app/seed",
6262
}
63-
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
63+
for line := range strings.SplitSeq(strings.TrimSpace(string(out)), "\n") {
6464
if strings.TrimSpace(line) == "" {
6565
continue
6666
}
@@ -95,7 +95,7 @@ func TestArchitecture_HarnessAndRenderDoNotImportConfig(t *testing.T) {
9595
if err != nil {
9696
continue // package may have no Go files
9797
}
98-
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
98+
for line := range strings.SplitSeq(strings.TrimSpace(string(out)), "\n") {
9999
parts := strings.SplitN(line, " ", 2)
100100
if len(parts) < 2 {
101101
continue
@@ -114,7 +114,6 @@ func TestCLIExitCodes_HelpReturnsZero(t *testing.T) {
114114

115115
commands := []string{"doctor", "save", "init", "clean", "sync", "render", "version", "manage", "pack", "profile", "search", "query", "restore", "trace", "status", "install"}
116116
for _, cmd := range commands {
117-
cmd := cmd
118117
t.Run(cmd, func(t *testing.T) {
119118
t.Parallel()
120119
_, _, code := runApp(t, cmd, "--help")

cmd/aipack/install_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestInstall_HiddenFromTopLevelHelp(t *testing.T) {
2323
}
2424
// "install" (the hidden alias for "pack install") should not appear as a
2525
// visible top-level command. "install-completions" is a separate, visible command.
26-
for _, line := range strings.Split(stdout, "\n") {
26+
for line := range strings.SplitSeq(stdout, "\n") {
2727
trimmed := strings.TrimSpace(line)
2828
if strings.HasPrefix(trimmed, "install") && !strings.HasPrefix(trimmed, "install-completions") {
2929
t.Fatalf("install should be hidden from top-level help, but found: %q", line)

cmd/aipack/pack.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,48 @@ Packs are installed under ~/.config/aipack/packs/<name>/.`
3838
// --- pack create ---
3939

4040
type PackCreateCmd struct {
41-
Dir string `arg:"" help:"Directory path to create the pack in"`
42-
Name string `help:"Pack name for pack.json (default: directory basename)" name:"name"`
41+
Name string `arg:"" help:"Pack name"`
42+
ConfigDir string `help:"Config directory (default: ~/.config/aipack)" name:"config-dir" type:"path"`
43+
Local bool `help:"Create pack inside the packs directory instead of the current directory" name:"local"`
4344
}
4445

4546
func (c *PackCreateCmd) Help() string {
4647
return `Scaffolds a new pack directory with a pack.json manifest and standard
47-
subdirectories: rules/, agents/, workflows/, skills/, mcp/, configs/.
48+
subdirectories, then installs and registers it so it is immediately available
49+
for profiles, sync, and save.
50+
51+
By default, the pack is created in the current directory and symlinked into
52+
the packs directory (--link behavior). Use --local to create the pack directly
53+
inside the packs directory instead.
4854
4955
Examples:
50-
# Create a new pack
51-
aipack pack create ./my-new-pack
56+
# Create a pack in CWD, symlink into packs dir (default)
57+
aipack pack create my-new-pack
5258
53-
# Create with a custom name (overrides directory basename)
54-
aipack pack create ./path/to/dir --name custom-pack-name
59+
# Create a pack directly in the packs dir
60+
aipack pack create my-new-pack --local
5561
5662
See also: pack install, pack show`
5763
}
5864

5965
func (c *PackCreateCmd) Run(ctx context.Context, g *Globals) error {
60-
if err := app.PackCreate(app.PackCreateRequest{Dir: c.Dir, Name: c.Name}); err != nil {
66+
cfgDir, err := cmdutil.EnsureConfigDir(c.ConfigDir, config.HomeDir(), g.Stderr)
67+
if err != nil {
6168
return err
6269
}
63-
fmt.Fprintf(g.Stdout, "Created pack at %s\n", c.Dir)
70+
71+
if err := app.PackCreate(app.PackCreateRequest{
72+
Name: c.Name,
73+
ConfigDir: cfgDir,
74+
Local: c.Local,
75+
}); err != nil {
76+
return err
77+
}
78+
if c.Local {
79+
fmt.Fprintf(g.Stdout, "Created pack %q in %s\n", c.Name, filepath.Join(cfgDir, "packs", c.Name))
80+
} else {
81+
fmt.Fprintf(g.Stdout, "Created pack %q in ./%s (linked)\n", c.Name, c.Name)
82+
}
6483
return nil
6584
}
6685

cmd/aipack/pack_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestPackList_JSON_Empty(t *testing.T) {
2828
t.Fatalf("pack list --json exit=%d, want %d; stderr=%s", code, cmdutil.ExitOK, stderr)
2929
}
3030

31-
var entries []interface{}
31+
var entries []any
3232
if err := json.Unmarshal([]byte(stdout), &entries); err != nil {
3333
t.Fatalf("invalid JSON: %v\noutput=%s", err, stdout)
3434
}
@@ -57,7 +57,7 @@ func TestPackList_JSON_WithPack(t *testing.T) {
5757
t.Fatalf("pack list --json exit=%d, want %d; stderr=%s", code, cmdutil.ExitOK, stderr)
5858
}
5959

60-
var entries []map[string]interface{}
60+
var entries []map[string]any
6161
if err := json.Unmarshal([]byte(stdout), &entries); err != nil {
6262
t.Fatalf("invalid JSON: %v\noutput=%s", err, stdout)
6363
}
@@ -120,7 +120,7 @@ func TestPackShow_NotInstalled(t *testing.T) {
120120

121121
func writePackManifestCmd(t *testing.T, dir string, name string) {
122122
t.Helper()
123-
m := map[string]interface{}{
123+
m := map[string]any{
124124
"schema_version": 1,
125125
"name": name,
126126
"version": "1.0.0",

cmd/aipack/registry.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -254,26 +254,30 @@ func (c *RegistrySourcesCmd) Run(ctx context.Context, g *Globals) error {
254254
}
255255

256256
func printRegistryResults(g *Globals, results []app.RegistrySearchResult) {
257-
for _, r := range results {
258-
installed := ""
257+
for i, r := range results {
258+
if i > 0 {
259+
fmt.Fprintln(g.Stdout)
260+
}
261+
status := ""
259262
if r.Installed {
260-
installed = " [installed]"
263+
status = " [installed]"
261264
}
262-
desc := ""
265+
fmt.Fprintf(g.Stdout, "Name: %s%s\n", r.Name, status)
263266
if r.Description != "" {
264-
desc = " — " + r.Description
267+
fmt.Fprintf(g.Stdout, "Description: %s\n", r.Description)
268+
}
269+
if r.Owner != "" {
270+
fmt.Fprintf(g.Stdout, "Owner: %s\n", r.Owner)
265271
}
266-
fmt.Fprintf(g.Stdout, " %s%s%s\n", r.Name, installed, desc)
267-
details := []string{r.Repo}
272+
fmt.Fprintf(g.Stdout, "Repo: %s\n", r.Repo)
268273
if r.Path != "" {
269-
details = append(details, "path: "+r.Path)
274+
fmt.Fprintf(g.Stdout, "Path: %s\n", r.Path)
270275
}
271276
if r.Ref != "" {
272-
details = append(details, "ref: "+r.Ref)
277+
fmt.Fprintf(g.Stdout, "Ref: %s\n", r.Ref)
273278
}
274-
if r.Owner != "" {
275-
details = append(details, "owner: "+r.Owner)
279+
if r.Contact != "" {
280+
fmt.Fprintf(g.Stdout, "Contact: %s\n", r.Contact)
276281
}
277-
fmt.Fprintf(g.Stdout, " %s\n", strings.Join(details, ", "))
278282
}
279283
}

cmd/aipack/save.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func (c *SaveCmd) runToPack(ctx context.Context, g *Globals) error {
313313

314314
func parseCategories(raw string) ([]domain.PackCategory, error) {
315315
var cats []domain.PackCategory
316-
for _, p := range strings.Split(raw, ",") {
316+
for p := range strings.SplitSeq(raw, ",") {
317317
p = strings.TrimSpace(p)
318318
if p == "" {
319319
continue

0 commit comments

Comments
 (0)