Skip to content

Commit ca1023d

Browse files
committed
refactor: use config constants throughout codebase
- Update claude package to use config constants for hook scripts - Move DefaultPermissions from claude/perm.go to config package - Rename internal/templates to internal/tpl - Update all imports for templates→tpl rename - Add tests for bootstrap, decision, journal, learnings, recall, serve - Use config constants in marker.go and token.go Signed-off-by: Jose Alekhinne <alekhinejose@gmail.com>
1 parent 09c5c54 commit ca1023d

57 files changed

Lines changed: 1097 additions & 186 deletions

Some content is hidden

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

internal/bootstrap/bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func RootCmd() *cobra.Command {
6262
cmd := &cobra.Command{
6363
Use: "ctx",
6464
Short: "Context - persistent context for AI coding assistants",
65-
Long: `Context (ctx) maintains persistent context files that help
65+
Long: `ctx (Context) maintains persistent context files that help
6666
AI coding assistants understand your project's architecture, conventions,
6767
decisions, and current tasks.
6868
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// / Context: https://ctx.ist
2+
// ,'`./ do you remember?
3+
// `.,'\
4+
// \ Copyright 2026-present Context contributors.
5+
// SPDX-License-Identifier: Apache-2.0
6+
7+
package bootstrap
8+
9+
import (
10+
"testing"
11+
)
12+
13+
func TestRootCmd(t *testing.T) {
14+
cmd := RootCmd()
15+
16+
if cmd == nil {
17+
t.Fatal("RootCmd() returned nil")
18+
}
19+
20+
if cmd.Use != "ctx" {
21+
t.Errorf("RootCmd().Use = %q, want %q", cmd.Use, "ctx")
22+
}
23+
24+
if cmd.Short == "" {
25+
t.Error("RootCmd().Short is empty")
26+
}
27+
28+
if cmd.Long == "" {
29+
t.Error("RootCmd().Long is empty")
30+
}
31+
32+
// Check global flags exist
33+
contextDirFlag := cmd.PersistentFlags().Lookup("context-dir")
34+
if contextDirFlag == nil {
35+
t.Error("--context-dir flag not found")
36+
}
37+
38+
noColorFlag := cmd.PersistentFlags().Lookup("no-color")
39+
if noColorFlag == nil {
40+
t.Error("--no-color flag not found")
41+
}
42+
}
43+
44+
func TestInitialize(t *testing.T) {
45+
root := RootCmd()
46+
cmd := Initialize(root)
47+
48+
if cmd == nil {
49+
t.Fatal("Initialize() returned nil")
50+
}
51+
52+
// Verify all expected subcommands are registered
53+
expectedCommands := []string{
54+
"init",
55+
"status",
56+
"load",
57+
"add",
58+
"complete",
59+
"agent",
60+
"drift",
61+
"sync",
62+
"compact",
63+
"decisions",
64+
"watch",
65+
"hook",
66+
"learnings",
67+
"session",
68+
"tasks",
69+
"loop",
70+
"recall",
71+
"journal",
72+
"serve",
73+
}
74+
75+
commands := make(map[string]bool)
76+
for _, c := range cmd.Commands() {
77+
commands[c.Use] = true
78+
// Handle commands with args in Use (e.g., "serve [directory]")
79+
for _, exp := range expectedCommands {
80+
if c.Name() == exp {
81+
commands[exp] = true
82+
}
83+
}
84+
}
85+
86+
for _, exp := range expectedCommands {
87+
if !commands[exp] {
88+
t.Errorf("missing subcommand: %s", exp)
89+
}
90+
}
91+
}
92+
93+
func TestRootCmdVersion(t *testing.T) {
94+
cmd := RootCmd()
95+
96+
if cmd.Version == "" {
97+
t.Error("RootCmd().Version is empty")
98+
}
99+
}

internal/claude/claude_test.go

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@ func TestBlockNonPathCtxScript(t *testing.T) {
4545
}
4646
}
4747

48+
func TestPromptCoachScript(t *testing.T) {
49+
content, err := PromptCoachScript()
50+
if err != nil {
51+
t.Fatalf("PromptCoachScript() unexpected error: %v", err)
52+
}
53+
54+
if len(content) == 0 {
55+
t.Error("PromptCoachScript() returned empty content")
56+
}
57+
58+
// Check for expected script content
59+
script := string(content)
60+
if !strings.Contains(script, "#!/") {
61+
t.Error("PromptCoachScript() script missing shebang")
62+
}
63+
64+
// Check that it contains pattern detection logic
65+
if !strings.Contains(script, "idiomatic") {
66+
t.Error("PromptCoachScript() should contain anti-pattern detection")
67+
}
68+
}
69+
4870
func TestCommands(t *testing.T) {
4971
commands, err := Commands()
5072
if err != nil {
@@ -156,29 +178,3 @@ func TestSettingsStructure(t *testing.T) {
156178
}
157179
}
158180

159-
func TestDefaultPermissions(t *testing.T) {
160-
perms := DefaultPermissions()
161-
162-
if len(perms) == 0 {
163-
t.Error("DefaultPermissions should return permissions")
164-
}
165-
166-
// Check that essential ctx commands are included
167-
expected := []string{
168-
"Bash(ctx status:*)",
169-
"Bash(ctx agent:*)",
170-
"Bash(ctx add:*)",
171-
"Bash(ctx session:*)",
172-
}
173-
174-
permSet := make(map[string]bool)
175-
for _, p := range perms {
176-
permSet[p] = true
177-
}
178-
179-
for _, e := range expected {
180-
if !permSet[e] {
181-
t.Errorf("Missing expected permission: %s", e)
182-
}
183-
}
184-
}

internal/claude/cmd.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package claude
99
import (
1010
"fmt"
1111

12-
"github.com/ActiveMemory/ctx/internal/templates"
12+
"github.com/ActiveMemory/ctx/internal/tpl"
1313
)
1414

1515
// Commands returns the list of embedded command file names.
@@ -22,7 +22,7 @@ import (
2222
// - []string: Filenames of available command definitions
2323
// - error: Non-nil if the commands directory cannot be read
2424
func Commands() ([]string, error) {
25-
names, err := templates.ListClaudeCommands()
25+
names, err := tpl.ListClaudeCommands()
2626
if err != nil {
2727
return nil, fmt.Errorf("failed to list commands: %w", err)
2828
}
@@ -38,7 +38,7 @@ func Commands() ([]string, error) {
3838
// - []byte: Raw bytes of the command definition file
3939
// - error: Non-nil if the command file does not exist or cannot be read
4040
func CommandByName(name string) ([]byte, error) {
41-
content, err := templates.GetClaudeCommand(name)
41+
content, err := tpl.ClaudeCommandByName(name)
4242
if err != nil {
4343
return nil, fmt.Errorf("failed to read command %s: %w", name, err)
4444
}

internal/claude/hook.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
package claude
88

9-
import "fmt"
9+
import (
10+
"fmt"
11+
12+
"github.com/ActiveMemory/ctx/internal/config"
13+
)
1014

1115
// DefaultHooks returns the default ctx hooks configuration for
1216
// Claude Code.
@@ -24,9 +28,11 @@ import "fmt"
2428
// - HookConfig: Configured hooks for PreToolUse, UserPromptSubmit, and
2529
// SessionEnd events
2630
func DefaultHooks(projectDir string) HookConfig {
27-
hooksDir := ".claude/hooks"
31+
hooksDir := config.DirClaudeHooks
2832
if projectDir != "" {
29-
hooksDir = fmt.Sprintf("%s/.claude/hooks", projectDir)
33+
hooksDir = fmt.Sprintf(
34+
"%s/%s", projectDir, config.DirClaudeHooks,
35+
)
3036
}
3137

3238
return HookConfig{
@@ -36,8 +42,10 @@ func DefaultHooks(projectDir string) HookConfig {
3642
Matcher: "Bash",
3743
Hooks: []Hook{
3844
{
39-
Type: "command",
40-
Command: fmt.Sprintf("%s/block-non-path-ctx.sh", hooksDir),
45+
Type: "command",
46+
Command: fmt.Sprintf(
47+
"%s/%s", hooksDir, config.FileBlockNonPathScript,
48+
),
4149
},
4250
},
4351
},
@@ -58,7 +66,7 @@ func DefaultHooks(projectDir string) HookConfig {
5866
Hooks: []Hook{
5967
{
6068
Type: "command",
61-
Command: fmt.Sprintf("%s/prompt-coach.sh", hooksDir),
69+
Command: fmt.Sprintf("%s/%s", hooksDir, config.FilePromptCoach),
6270
},
6371
},
6472
},
@@ -68,7 +76,7 @@ func DefaultHooks(projectDir string) HookConfig {
6876
Hooks: []Hook{
6977
{
7078
Type: "command",
71-
Command: fmt.Sprintf("%s/auto-save-session.sh", hooksDir),
79+
Command: fmt.Sprintf("%s/%s", hooksDir, config.FileAutoSave),
7280
},
7381
},
7482
},

internal/claude/perm.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

internal/claude/script.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ package claude
99
import (
1010
"fmt"
1111

12-
"github.com/ActiveMemory/ctx/internal/templates"
12+
"github.com/ActiveMemory/ctx/internal/config"
13+
"github.com/ActiveMemory/ctx/internal/tpl"
1314
)
1415

1516
// AutoSaveScript returns the auto-save session script content.
@@ -21,9 +22,9 @@ import (
2122
// - []byte: Raw bytes of the auto-save-session.sh script
2223
// - error: Non-nil if the embedded file cannot be read
2324
func AutoSaveScript() ([]byte, error) {
24-
content, err := templates.ClaudeHookByFileName("auto-save-session.sh")
25+
content, err := tpl.ClaudeHookByFileName(config.FileAutoSave)
2526
if err != nil {
26-
return nil, fmt.Errorf("failed to read auto-save-session.sh: %w", err)
27+
return nil, fmt.Errorf("failed to read %s: %w", config.FileAutoSave, err)
2728
}
2829
return content, nil
2930
}
@@ -39,16 +40,18 @@ func AutoSaveScript() ([]byte, error) {
3940
// - []byte: Raw bytes of the block-non-path-ctx.sh script
4041
// - error: Non-nil if the embedded file cannot be read
4142
func BlockNonPathCtxScript() ([]byte, error) {
42-
content, err := templates.ClaudeHookByFileName("block-non-path-ctx.sh")
43+
content, err := tpl.ClaudeHookByFileName(config.FileBlockNonPathScript)
4344
if err != nil {
44-
return nil, fmt.Errorf("failed to read block-non-path-ctx.sh: %w", err)
45+
return nil, fmt.Errorf(
46+
"failed to read %s: %w", config.FileBlockNonPathScript, err,
47+
)
4548
}
4649
return content, nil
4750
}
4851

4952
// PromptCoachScript returns the prompt coaching hook script.
5053
//
51-
// The script detects prompt anti-patterns (e.g., "idiomatic Go") and suggests
54+
// The script detects prompt antipatterns (e.g., "idiomatic Go") and suggests
5255
// better alternatives (e.g., "follow project conventions"). It limits
5356
// suggestions to 3 per pattern per session to avoid annoying the user.
5457
// It is installed to .claude/hooks/ during ctx init --claude.
@@ -57,9 +60,11 @@ func BlockNonPathCtxScript() ([]byte, error) {
5760
// - []byte: Raw bytes of the prompt-coach.sh script
5861
// - error: Non-nil if the embedded file cannot be read
5962
func PromptCoachScript() ([]byte, error) {
60-
content, err := templates.ClaudeHookByFileName("prompt-coach.sh")
63+
content, err := tpl.ClaudeHookByFileName(config.FilePromptCoach)
6164
if err != nil {
62-
return nil, fmt.Errorf("failed to read prompt-coach.sh: %w", err)
65+
return nil, fmt.Errorf(
66+
"failed to read %s: %w", config.FilePromptCoach, err,
67+
)
6368
}
6469
return content, nil
6570
}

internal/cli/add/content.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ import (
1515
"github.com/ActiveMemory/ctx/internal/config"
1616
)
1717

18+
// extractContent retrieves content from various sources for adding entries.
19+
//
20+
// Content is extracted in priority order:
21+
// 1. From the file specified by --file flag
22+
// 2. From command line arguments (after the entry type)
23+
// 3. From stdin (if piped)
24+
//
25+
// Parameters:
26+
// - args: Command arguments where args[1:] may contain inline content
27+
// - flags: Configuration flags including fromFile path
28+
//
29+
// Returns:
30+
// - string: Extracted and trimmed content
31+
// - error: Non-nil if no content source is available or reading fails
1832
func extractContent(args []string, flags addConfig) (string, error) {
1933
if flags.fromFile != "" {
2034
// Read from the file

internal/cli/add/fmt.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"time"
1212
)
1313

14-
// FormatTask formats a task entry as a markdown checkbox item.
14+
// FormatTask formats a task entry as a Markdown checkbox item.
1515
//
1616
// The output includes a timestamp tag for session correlation and an optional
1717
// priority tag. Format: "- [ ] content #priority:level #added:YYYY-MM-DD-HHMMSS"
@@ -32,7 +32,7 @@ func FormatTask(content string, priority string) string {
3232
return fmt.Sprintf("- [ ] %s%s #added:%s\n", content, priorityTag, timestamp)
3333
}
3434

35-
// FormatLearning formats a learning entry as a structured markdown section.
35+
// FormatLearning formats a learning entry as a structured Markdown section.
3636
//
3737
// The output includes a timestamped heading and complete sections for context,
3838
// lesson, and application.
@@ -57,7 +57,7 @@ func FormatLearning(title, context, lesson, application string) string {
5757
`, timestamp, title, context, lesson, application)
5858
}
5959

60-
// FormatConvention formats a convention entry as a simple markdown list item.
60+
// FormatConvention formats a convention entry as a simple Markdown list item.
6161
//
6262
// Format: "- content"
6363
//

0 commit comments

Comments
 (0)