Skip to content

feat: CLI agent-friendly optimizations (P0+P1)#16

Merged
eddieran merged 2 commits intomainfrom
feat/cli-agent-friendly
Feb 27, 2026
Merged

feat: CLI agent-friendly optimizations (P0+P1)#16
eddieran merged 2 commits intomainfrom
feat/cli-agent-friendly

Conversation

@eddieran
Copy link
Copy Markdown
Owner

@eddieran eddieran commented Feb 27, 2026

Summary

  • JSON error wrapping: --json mode now outputs {"error": "..."} to stderr so agents can uniformly parse errors via json.Unmarshal
  • Git quiet mode: Suppresses git clone/fetch progress noise on stderr when --json is active, preventing agents from misinterpreting progress output as errors
  • Unified camelCase JSON tags: All CLI JSON output now uses consistent camelCase keys (was a mix of PascalCase, snake_case, camelCase, and lowercase)
  • Help examples: Core commands (install, inject, sync, source add, uninstall, upgrade) now include usage examples in --help so agents know parameter formats
  • skillpm status --json: New one-stop aggregated status command returning version, scope, health, installed count, source count, enabled adapters, and memory status

Changed files (13)

File Change
cmd/skillpm/main.go JSON error wrapping, help examples, status command, observeResult tag fix
cmd/skillpm/main_test.go Add "status" to core commands test
internal/app/service.go Add JSONMode to Options, pass quiet to source manager
internal/config/types.go Add camelCase JSON tags to SourceConfig, AdapterConfig, SyncConfig, MemoryConfig
internal/memory/context/context.go snake_case → camelCase JSON tags on Profile, SkillContextAffinity
internal/memory/scoring/scoring.go snake_case → camelCase JSON tags on SkillScore, ScoreBoard
internal/source/git_provider.go Add quiet field, newGitExec factory
internal/source/manager.go NewManager accepts quiet parameter
internal/source/model.go Add JSON tags to UpdateResult
internal/store/types.go Add camelCase JSON tags to InstalledSkill
internal/resolver/resolver_test.go Update NewManager call signature
internal/source/clawhub_provider_test.go Update NewManager call signature
internal/sync/sync_test.go Update NewManager call signature

Test plan

  • go vet ./... passes
  • go test ./... -count=1 all packages pass (except e2e network-dependent)
  • skillpm install nonexistent/skill --json 2>&1 → stderr is JSON {"error": "..."}
  • skillpm source list --json → camelCase keys
  • skillpm memory scores --json → camelCase keys
  • skillpm status --json → aggregated output
  • skillpm install --help → shows Examples section
  • go build -o ./bin/skillpm ./cmd/skillpm compiles

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new status command showing version, health, scope, installed/source counts, and enabled adapters.
    • Global JSON output mode supported consistently across many commands for machine-readable responses.
  • Bug Fixes

    • Suppressed noisy git progress when JSON mode is enabled for cleaner output.
  • Style

    • Standardized JSON keys to camelCase.
    • Expanded command help texts and examples for clearer usage.

- JSON error wrapping: --json mode outputs {"error": "..."} to stderr
- Git quiet mode: suppress git clone/fetch progress noise in --json mode
- Unify JSON tags to camelCase across all CLI output types
- Add help examples to core commands (install, inject, sync, etc.)
- Add `skillpm status --json` aggregated status command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e727d5 and 3e2b908.

📒 Files selected for processing (1)
  • internal/source/model.go

📝 Walkthrough

Walkthrough

Adds a JSON output mode and propagates it from the CLI root into the app and source manager, adds a new status subcommand, implements quiet git execution for source manager, and converts many struct JSON tags to camelCase for consistent JSON serialization.

Changes

Cohort / File(s) Summary
CLI Command Framework
cmd/skillpm/main.go, cmd/skillpm/main_test.go
Adds status subcommand, JSON-mode detection (isJSONMode), JSON-aware error handling, wires JSONMode into app initialization, and updates tests to expect status.
App Service JSONMode Configuration
internal/app/service.go
Adds JSONMode bool to Options and passes it into source.NewManager(...).
Source Manager & Git Executor
internal/source/manager.go, internal/source/git_provider.go
NewManager gains quiet bool param; new newGitExec(quiet bool) factory and gitProvider quiet field to suppress progress output when quiet.
Config & Model JSON Tags
internal/config/types.go, internal/source/model.go, internal/store/types.go
Adds JSON struct tags (camelCase) across config, source model, and store types for consistent JSON serialization.
Memory Serialization Changes
internal/memory/context/context.go, internal/memory/scoring/scoring.go
Updates JSON tags to camelCase for memory context and scoring structs (e.g., projectType, skillRef, eventCount).
Test Callsite Updates
internal/resolver/resolver_test.go, internal/source/clawhub_provider_test.go, internal/sync/sync_test.go
All source.NewManager call sites updated to include the new quiet boolean (tests pass false).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (cmd/skillpm)
    participant App as App (internal/app)
    participant Source as SourceManager (internal/source)
    participant Git as GitExec (git subprocess)

    CLI->>App: root command executes (JSONMode flag)
    App->>Source: NewManager(httpClient, stateRoot, JSONMode)
    Source->>Git: execute clone/fetch (quiet based on JSONMode)
    Git-->>Source: result / error (progress suppressed when quiet)
    Source-->>App: manager result
    App-->>CLI: status / JSON output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through flags and camelCase,
Quieted git to keep the pace,
A status beep and JSON song,
Burrows tidy, outputs strong —
The little rabbit cheers along.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: CLI optimizations for agent-friendly behavior including JSON output, camelCase tags, quiet mode, and new status command.
Description check ✅ Passed The description includes a comprehensive summary with specific changes, a detailed file change table, and a test plan. However, the Type of change checkbox is not marked, and validation/risk sections are incomplete per template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cli-agent-friendly

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/skillpm/main.go`:
- Around line 1692-1694: svc.ListInstalled()'s error is being ignored in the
status flow (installed, _ := svc.ListInstalled()), which can hide read failures;
change that call to capture the error (installed, err := svc.ListInstalled()),
and if err != nil return or surface the error (e.g., log and return a non-zero
exit / propagate the error up from the status command) instead of treating
installed as empty—only rely on len(installed) when err == nil; update the
surrounding code that builds the status output (the block that uses report :=
svc.DoctorRun(...) and sources := svc.SourceList()) to handle the propagated
error path consistently.

In `@internal/source/git_provider.go`:
- Around line 35-43: The clone/fetch branch currently calls cmd.Output() and
returns an error without including stderr when quiet=true; change this to use
cmd.CombinedOutput() (like the other branch) so you capture stdout+stderr into
out, and on error wrap it into the returned error message (use fmt.Errorf with
strings.Join(args, " ") and include string(out) alongside the original error).
Keep the existing cmd.Stderr = os.Stderr behavior for !quiet but always use
CombinedOutput() for the actual execution and error reporting in this
clone/fetch block (referencing cmd and the args slice).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 224f001 and 4e727d5.

📒 Files selected for processing (13)
  • cmd/skillpm/main.go
  • cmd/skillpm/main_test.go
  • internal/app/service.go
  • internal/config/types.go
  • internal/memory/context/context.go
  • internal/memory/scoring/scoring.go
  • internal/resolver/resolver_test.go
  • internal/source/clawhub_provider_test.go
  • internal/source/git_provider.go
  • internal/source/manager.go
  • internal/source/model.go
  • internal/store/types.go
  • internal/sync/sync_test.go

Comment thread cmd/skillpm/main.go
Comment on lines +1692 to +1694
report := svc.DoctorRun(cmd.Context())
installed, _ := svc.ListInstalled()
sources := svc.SourceList()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not swallow installed-state read failures in status.

At Line 1693, svc.ListInstalled() errors are ignored, which can silently report installedCount=0 on read failures instead of surfacing the real problem.

💡 Proposed fix
-			installed, _ := svc.ListInstalled()
+			installed, err := svc.ListInstalled()
+			if err != nil {
+				return err
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/skillpm/main.go` around lines 1692 - 1694, svc.ListInstalled()'s error is
being ignored in the status flow (installed, _ := svc.ListInstalled()), which
can hide read failures; change that call to capture the error (installed, err :=
svc.ListInstalled()), and if err != nil return or surface the error (e.g., log
and return a non-zero exit / propagate the error up from the status command)
instead of treating installed as empty—only rely on len(installed) when err ==
nil; update the surrounding code that builds the status output (the block that
uses report := svc.DoctorRun(...) and sources := svc.SourceList()) to handle the
propagated error path consistently.

Comment on lines +35 to +43
if len(args) > 0 && (args[0] == "clone" || args[0] == "fetch") {
if !quiet {
cmd.Stderr = os.Stderr
}
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
}
return out, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n internal/source/git_provider.go | sed -n '20,55p'

Repository: eddieran/skillpm

Length of output: 1290


🌐 Web query:

Go os/exec cmd.Output() stderr handling behavior

💡 Result:

(*exec.Cmd).Output() captures only stdout and returns it as []byte. If the command exits non-zero, the returned error is usually *exec.ExitError. Stderr handling depends on Cmd.Stderr:

  • If cmd.Stderr == nil and the command fails with *exec.ExitError, Output() will capture stderr (a subset, possibly truncated) and store it in exitErr.Stderr for debugging/error messages. [1]
  • If cmd.Stderr is non-nil, stderr is written there and exitErr.Stderr will not be auto-populated by Output(). [1]

If you want stdout + stderr together regardless of success, use cmd.CombinedOutput(). [1]

Source: Go os/exec docs (Cmd.Output, Cmd.CombinedOutput, ExitError.Stderr). [1]

References
[1] https://pkg.go.dev/os/exec


Include stderr details in clone/fetch error messages when in quiet mode.

At Lines 35-43, when quiet=true, the clone/fetch branch uses cmd.Output() without explicitly capturing stderr in the error message. Although cmd.Output() auto-populates ExitError.Stderr internally when cmd.Stderr==nil, this detail is not explicitly included in the formatted error message returned at Line 41. In JSON mode (quiet), callers cannot diagnose failures without inspecting the ExitError internals. Lines 46-50 correctly use CombinedOutput() and include output in the error message—apply the same pattern here.

💡 Proposed fix
 		if len(args) > 0 && (args[0] == "clone" || args[0] == "fetch") {
 			if !quiet {
 				cmd.Stderr = os.Stderr
+				out, err := cmd.Output()
+				if err != nil {
+					return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
+				}
+				return out, nil
 			}
-			out, err := cmd.Output()
+			out, err := cmd.CombinedOutput()
 			if err != nil {
-				return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
+				return nil, fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, string(out))
 			}
 			return out, nil
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(args) > 0 && (args[0] == "clone" || args[0] == "fetch") {
if !quiet {
cmd.Stderr = os.Stderr
}
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
}
return out, nil
if len(args) > 0 && (args[0] == "clone" || args[0] == "fetch") {
if !quiet {
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
}
return out, nil
}
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, string(out))
}
return out, nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/source/git_provider.go` around lines 35 - 43, The clone/fetch branch
currently calls cmd.Output() and returns an error without including stderr when
quiet=true; change this to use cmd.CombinedOutput() (like the other branch) so
you capture stdout+stderr into out, and on error wrap it into the returned error
message (use fmt.Errorf with strings.Join(args, " ") and include string(out)
alongside the original error). Keep the existing cmd.Stderr = os.Stderr behavior
for !quiet but always use CombinedOutput() for the actual execution and error
reporting in this clone/fetch block (referencing cmd and the args slice).

@eddieran eddieran merged commit a9c35f7 into main Feb 27, 2026
5 checks passed
eddieran added a commit that referenced this pull request Mar 2, 2026
feat: CLI agent-friendly optimizations (P0+P1)
eddieran added a commit that referenced this pull request Mar 2, 2026
feat: CLI agent-friendly optimizations (P0+P1)
@eddieran eddieran deleted the feat/cli-agent-friendly branch March 15, 2026 14:00
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.

1 participant