Skip to content

Add hooks-first onboarding setup flow#33

Merged
JordanCoin merged 3 commits intomainfrom
codex/hooks-first-onboarding-setup
Mar 1, 2026
Merged

Add hooks-first onboarding setup flow#33
JordanCoin merged 3 commits intomainfrom
codex/hooks-first-onboarding-setup

Conversation

@JordanCoin
Copy link
Owner

@JordanCoin JordanCoin commented Mar 1, 2026

Summary

  • add codemap setup as the single recommended onboarding path
  • auto-create .codemap/config.json (if missing) and install codemap hooks into Claude settings
  • make setup idempotent and add tests for hook settings merge/dedup behavior
  • refactor config init internals for reuse by setup
  • update README + HOOKS docs to emphasize install -> codemap setup (no repo clone required)
  • add onboarding helper scripts for macOS/Linux and Windows (scripts/onboard.sh, scripts/onboard.ps1)

Validation

  • go test ./...

Copilot AI review requested due to automatic review settings March 1, 2026 01:22
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new “hooks-first” onboarding flow centered around codemap setup, aiming to make initial project configuration + Claude hook installation automated and idempotent.

Changes:

  • Introduces codemap setup to create .codemap/config.json (if missing) and install codemap Claude hooks into project-local or global Claude settings.
  • Refactors config initialization internals for reuse (shared initProjectConfig result/error handling) and adds tests for settings merge/dedup behavior.
  • Updates docs/README and adds helper onboarding scripts for macOS/Linux and Windows.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/onboard.sh Bash helper to install codemap (via Homebrew) and run codemap setup for a target project.
scripts/onboard.ps1 PowerShell helper to install codemap (Scoop/Winget) and run codemap setup for a target project.
main.go Adds early handling for the new setup subcommand and updates --help output.
docs/HOOKS.md Updates hook docs to emphasize codemap setup as the recommended path and refreshes install commands.
cmd/setup.go Implements RunSetup + Claude settings merge/dedup logic for hook installation.
cmd/setup_test.go Adds tests for creating settings, preserving fields, avoiding duplicates, and rejecting invalid JSON.
cmd/config.go Refactors config init into reusable initProjectConfig for use by setup.
README.md Reworks onboarding docs to make codemap setup the primary recommended setup flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

cmd/setup.go Outdated
Comment on lines +198 to +202
hooksRaw, err := json.Marshal(hooksByEvent)
if err != nil {
return result, fmt.Errorf("encode hooks: %w", err)
}
root["hooks"] = hooksRaw
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

root is a map[string]json.RawMessage and you assign root["hooks"] = hooksRaw where hooksRaw was produced by json.Marshal(...). When you later json.MarshalIndent(root, ...), json.RawMessage is emitted verbatim, so the hooks object will stay compact/minified (one long line) even though the rest of the file is indented. If the goal is a readable settings file, consider representing the top-level settings as map[string]any (or a struct) and assigning the structured hooksByEvent value so MarshalIndent can format it normally.

Copilot uses AI. Check for mistakes.
Comment on lines +214 to +216
if err := os.WriteFile(settingsPath, out, 0644); err != nil {
return result, fmt.Errorf("write %s: %w", settingsPath, err)
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

ensureClaudeHooks writes settingsPath unconditionally, even when AddedHooks == 0 (i.e., already configured). That makes the operation non-no-op (touches mtime / may reorder JSON keys) despite printing "already configured". Consider short-circuiting before marshalling/writing when no changes are needed (while still creating the file when it doesn't exist).

Copilot uses AI. Check for mistakes.
cmd/setup.go Outdated
Comment on lines +222 to +223
target := strings.TrimSpace(command)
for _, entry := range entries {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

hasHookCommand dedupes purely on (type==command && command string), ignoring the hook entry's matcher. For the recommended PreToolUse/PostToolUse hooks the matcher (Edit|Write) is part of the behavior; if a user already has codemap hook pre-edit without the matcher (or with a different matcher), ensureClaudeHooks will treat it as present and won't install the recommended matcher-scoped hook. Consider including matcher in the match criteria for specs that require it, or updating existing entries to add/fix the matcher when the command matches.

Suggested change
target := strings.TrimSpace(command)
for _, entry := range entries {
target := strings.TrimSpace(command)
// Some recommended hooks (e.g. PreToolUse/PostToolUse) require a specific matcher
// as part of their intended behavior. For those commands, only treat an existing
// hook as present if both the command and the entry's matcher match the
// recommended values. For all other commands, continue to ignore the matcher.
requiredMatcher := ""
switch target {
case "codemap hook pre-edit", "codemap hook post-edit":
requiredMatcher = "Edit|Write"
}
for _, entry := range entries {
if requiredMatcher != "" && !strings.EqualFold(strings.TrimSpace(entry.Matcher), requiredMatcher) {
// This entry doesn't have the required matcher; don't let it
// suppress installation of the matcher-scoped recommended hook.
continue
}

Copilot uses AI. Check for mistakes.
cmd/setup.go Outdated
Comment on lines +57 to +63
fs := flag.NewFlagSet("setup", flag.ExitOnError)
useGlobalHooks := fs.Bool("global", false, "Install hooks into ~/.claude/settings.json instead of project-local .claude/settings.local.json")
skipConfig := fs.Bool("no-config", false, "Skip creating .codemap/config.json")
skipHooks := fs.Bool("no-hooks", false, "Skip writing Claude hook settings")
if err := fs.Parse(args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

RunSetup uses flag.NewFlagSet("setup", flag.ExitOnError). With ExitOnError, fs.Parse calls os.Exit(2) on parse errors/help and never returns a non-nil error, so the if err := fs.Parse(args); err != nil { ... } block is effectively dead code and you lose control over exit codes/usage text. Consider switching to flag.ContinueOnError and handling -h/-help + parse errors explicitly (or remove the manual error check and rely on the default behavior).

Suggested change
fs := flag.NewFlagSet("setup", flag.ExitOnError)
useGlobalHooks := fs.Bool("global", false, "Install hooks into ~/.claude/settings.json instead of project-local .claude/settings.local.json")
skipConfig := fs.Bool("no-config", false, "Skip creating .codemap/config.json")
skipHooks := fs.Bool("no-hooks", false, "Skip writing Claude hook settings")
if err := fs.Parse(args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
fs := flag.NewFlagSet("setup", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
useGlobalHooks := fs.Bool("global", false, "Install hooks into ~/.claude/settings.json instead of project-local .claude/settings.local.json")
skipConfig := fs.Bool("no-config", false, "Skip creating .codemap/config.json")
skipHooks := fs.Bool("no-hooks", false, "Skip writing Claude hook settings")
if err := fs.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
fmt.Fprintln(os.Stderr, "Usage: codemap setup [--global] [--no-config] [--no-hooks] [path]")
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
os.Exit(2)

Copilot uses AI. Check for mistakes.
@JordanCoin JordanCoin merged commit e10fe80 into main Mar 1, 2026
12 checks passed
@JordanCoin JordanCoin deleted the codex/hooks-first-onboarding-setup branch March 1, 2026 01:57
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.

2 participants