From 09c5c540816fc0981e8aeb347ed0121587cdfaf3 Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 1 Feb 2026 08:28:45 -0800 Subject: [PATCH 01/10] refactor: consolidate constants, add thread safety, reduce duplication - Add config constants: FileContextRC, EnvCtxDir, EnvCtxTokenBudget, ParserPeekLines, Claude API block types and roles - Add RWMutex to rc package for thread-safe rcOverrideDir access - Use t.Setenv in tests instead of os.Setenv/Unsetenv - Extract ReindexFile helper to eliminate decision/learnings duplication - ScanDirectory now delegates to ScanDirectoryWithErrors - Use iota for task match indices - Add EntryPlural map for entry type pluralization - Add comprehensive tests for config and task packages - Add proper documentation following project conventions Signed-off-by: Jose Alekhinne --- internal/cli/add/{config.go => types.go} | 0 internal/cli/decision/run.go | 40 +- internal/cli/learnings/learnings.go | 42 +- internal/config/config_test.go | 562 ++++++++++++++++++ internal/config/entry.go | 10 + internal/config/file.go | 53 ++ internal/index/index.go | 65 ++ internal/rc/default.go | 1 + internal/rc/load.go | 44 ++ internal/rc/lock.go | 16 + internal/rc/rc.go | 120 ++-- internal/rc/rc_test.go | 66 +- internal/rc/types.go | 1 + internal/recall/parser/claude.go | 226 +------ internal/recall/parser/git.go | 1 + internal/recall/parser/parse.go | 192 ++++++ internal/recall/parser/parser.go | 167 +----- internal/recall/parser/path.go | 1 + internal/recall/parser/query.go | 76 +++ internal/task/task.go | 18 +- internal/task/task_test.go | 295 +++++++++ internal/{templates => tpl}/AGENT_PLAYBOOK.md | 0 internal/{templates => tpl}/ARCHITECTURE.md | 0 internal/{templates => tpl}/CLAUDE.md | 0 internal/{templates => tpl}/CONSTITUTION.md | 0 internal/{templates => tpl}/CONVENTIONS.md | 0 internal/{templates => tpl}/DECISIONS.md | 0 internal/{templates => tpl}/DRIFT.md | 0 internal/{templates => tpl}/GLOSSARY.md | 0 .../{templates => tpl}/IMPLEMENTATION_PLAN.md | 0 internal/{templates => tpl}/LEARNINGS.md | 0 internal/{templates => tpl}/TASKS.md | 0 .../claude/commands/ctx-add-decision.md | 0 .../claude/commands/ctx-add-learning.md | 0 .../claude/commands/ctx-add-task.md | 0 .../claude/commands/ctx-agent.md | 0 .../claude/commands/ctx-archive.md | 0 .../claude/commands/ctx-blog-changelog.md | 0 .../claude/commands/ctx-blog.md | 0 .../claude/commands/ctx-journal-enrich.md | 0 .../claude/commands/ctx-journal-summarize.md | 0 .../claude/commands/ctx-loop.md | 0 .../claude/commands/ctx-prompt-audit.md | 0 .../claude/commands/ctx-recall.md | 0 .../claude/commands/ctx-reflect.md | 0 .../claude/commands/ctx-save.md | 0 .../claude/commands/ctx-status.md | 0 .../claude/hooks/auto-save-session.sh | 0 .../claude/hooks/block-non-path-ctx.sh | 0 .../claude/hooks/prompt-coach.sh | 0 internal/{templates => tpl}/embed.go | 0 internal/{templates => tpl}/embed_test.go | 0 .../entry-templates/decision.md | 0 .../entry-templates/learning.md | 0 54 files changed, 1461 insertions(+), 535 deletions(-) rename internal/cli/add/{config.go => types.go} (100%) create mode 100644 internal/config/config_test.go create mode 100644 internal/rc/default.go create mode 100644 internal/rc/load.go create mode 100644 internal/rc/lock.go create mode 100644 internal/rc/types.go create mode 100644 internal/recall/parser/git.go create mode 100644 internal/recall/parser/parse.go create mode 100644 internal/recall/parser/path.go create mode 100644 internal/recall/parser/query.go create mode 100644 internal/task/task_test.go rename internal/{templates => tpl}/AGENT_PLAYBOOK.md (100%) rename internal/{templates => tpl}/ARCHITECTURE.md (100%) rename internal/{templates => tpl}/CLAUDE.md (100%) rename internal/{templates => tpl}/CONSTITUTION.md (100%) rename internal/{templates => tpl}/CONVENTIONS.md (100%) rename internal/{templates => tpl}/DECISIONS.md (100%) rename internal/{templates => tpl}/DRIFT.md (100%) rename internal/{templates => tpl}/GLOSSARY.md (100%) rename internal/{templates => tpl}/IMPLEMENTATION_PLAN.md (100%) rename internal/{templates => tpl}/LEARNINGS.md (100%) rename internal/{templates => tpl}/TASKS.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-add-decision.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-add-learning.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-add-task.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-agent.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-archive.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-blog-changelog.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-blog.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-journal-enrich.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-journal-summarize.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-loop.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-prompt-audit.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-recall.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-reflect.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-save.md (100%) rename internal/{templates => tpl}/claude/commands/ctx-status.md (100%) rename internal/{templates => tpl}/claude/hooks/auto-save-session.sh (100%) rename internal/{templates => tpl}/claude/hooks/block-non-path-ctx.sh (100%) rename internal/{templates => tpl}/claude/hooks/prompt-coach.sh (100%) rename internal/{templates => tpl}/embed.go (100%) rename internal/{templates => tpl}/embed_test.go (100%) rename internal/{templates => tpl}/entry-templates/decision.md (100%) rename internal/{templates => tpl}/entry-templates/learning.md (100%) diff --git a/internal/cli/add/config.go b/internal/cli/add/types.go similarity index 100% rename from internal/cli/add/config.go rename to internal/cli/add/types.go diff --git a/internal/cli/decision/run.go b/internal/cli/decision/run.go index e3ee9c45a..47545d898 100644 --- a/internal/cli/decision/run.go +++ b/internal/cli/decision/run.go @@ -7,11 +7,8 @@ package decision import ( - "fmt" - "os" "path/filepath" - "github.com/fatih/color" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config" @@ -28,33 +25,12 @@ import ( // Returns: // - error: Non-nil if file read/write fails func runReindex(cmd *cobra.Command, _ []string) error { - filePath := filepath.Join(rc.GetContextDir(), config.FileDecision) - - if _, err := os.Stat(filePath); os.IsNotExist(err) { - return fmt.Errorf("%s not found. Run 'ctx init' first", config.FileDecision) - } - - content, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("failed to read %s: %w", filePath, err) - } - - updated := index.UpdateDecisions(string(content)) - - if err := os.WriteFile(filePath, []byte(updated), 0644); err != nil { - return fmt.Errorf("failed to write %s: %w", filePath, err) - } - - entries := index.ParseHeaders(string(content)) - green := color.New(color.FgGreen).SprintFunc() - if len(entries) == 0 { - cmd.Printf("%s Index cleared (no decisions found)\n", green("✓")) - } else { - cmd.Printf( - "%s Index regenerated with %d entries\n", green("✓"), - len(entries), - ) - } - - return nil + filePath := filepath.Join(rc.ContextDir(), config.FileDecision) + return index.ReindexFile( + cmd.OutOrStdout(), + filePath, + config.FileDecision, + index.UpdateDecisions, + config.EntryPlural[config.EntryDecision], + ) } diff --git a/internal/cli/learnings/learnings.go b/internal/cli/learnings/learnings.go index b0faf9b12..34b88a886 100644 --- a/internal/cli/learnings/learnings.go +++ b/internal/cli/learnings/learnings.go @@ -8,11 +8,8 @@ package learnings import ( - "fmt" - "os" "path/filepath" - "github.com/fatih/color" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config" @@ -79,35 +76,12 @@ Examples: // Returns: // - error: Non-nil if file read/write fails func runReindex(cmd *cobra.Command, _ []string) error { - filePath := filepath.Join(rc.GetContextDir(), config.FileLearning) - - if _, err := os.Stat(filePath); os.IsNotExist(err) { - return fmt.Errorf( - "%s not found. Run 'ctx init' first", config.FileLearning, - ) - } - - content, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("failed to read %s: %w", filePath, err) - } - - updated := index.UpdateLearnings(string(content)) - - if err := os.WriteFile(filePath, []byte(updated), 0644); err != nil { - return fmt.Errorf("failed to write %s: %w", filePath, err) - } - - entries := index.ParseHeaders(string(content)) - green := color.New(color.FgGreen).SprintFunc() - if len(entries) == 0 { - cmd.Printf("%s Index cleared (no learnings found)\n", green("✓")) - } else { - cmd.Printf( - "%s Index regenerated with %d entries\n", green("✓"), - len(entries), - ) - } - - return nil + filePath := filepath.Join(rc.ContextDir(), config.FileLearning) + return index.ReindexFile( + cmd.OutOrStdout(), + filePath, + config.FileLearning, + index.UpdateLearnings, + config.EntryPlural[config.EntryLearning], + ) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 000000000..bc981a029 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,562 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package config + +import "testing" + +func TestUserInputToEntry(t *testing.T) { + tests := []struct { + input string + want string + }{ + // Task variations + {"task", EntryTask}, + {"tasks", EntryTask}, + {"Task", EntryTask}, + {"TASKS", EntryTask}, + + // Decision variations + {"decision", EntryDecision}, + {"decisions", EntryDecision}, + {"Decision", EntryDecision}, + {"DECISION", EntryDecision}, + + // Learning variations + {"learning", EntryLearning}, + {"learnings", EntryLearning}, + {"Learning", EntryLearning}, + {"LEARNINGS", EntryLearning}, + + // Convention variations + {"convention", EntryConvention}, + {"conventions", EntryConvention}, + {"Convention", EntryConvention}, + {"CONVENTIONS", EntryConvention}, + + // Unknown inputs + {"", EntryUnknown}, + {"unknown", EntryUnknown}, + {"foo", EntryUnknown}, + {"taskss", EntryUnknown}, + {"learn", EntryUnknown}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got := UserInputToEntry(tt.input) + if got != tt.want { + t.Errorf("UserInputToEntry(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestRegExFromAttrName(t *testing.T) { + tests := []struct { + name string + attrName string + input string + wantMatch bool + wantValue string + }{ + { + name: "type attribute", + attrName: "type", + input: `type="task"`, + wantMatch: true, + wantValue: "task", + }, + { + name: "context attribute", + attrName: "context", + input: `context="some context here"`, + wantMatch: true, + wantValue: "some context here", + }, + { + name: "attribute in larger string", + attrName: "id", + input: ``, + wantMatch: true, + wantValue: "123", + }, + { + name: "no match", + attrName: "missing", + input: `type="task"`, + wantMatch: false, + wantValue: "", + }, + { + name: "empty value", + attrName: "empty", + input: `empty=""`, + wantMatch: true, + wantValue: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + re := RegExFromAttrName(tt.attrName) + match := re.FindStringSubmatch(tt.input) + + if tt.wantMatch { + if match == nil { + t.Errorf("expected match for %q in %q", tt.attrName, tt.input) + return + } + if len(match) < 2 { + t.Errorf("match has no capture group") + return + } + if match[1] != tt.wantValue { + t.Errorf("got value %q, want %q", match[1], tt.wantValue) + } + } else { + if match != nil { + t.Errorf("expected no match for %q in %q, got %v", tt.attrName, tt.input, match) + } + } + }) + } +} + +func TestRegExEntryHeader(t *testing.T) { + tests := []struct { + name string + input string + wantMatch bool + wantDate string + wantTime string + wantTitle string + }{ + { + name: "valid entry header", + input: "## [2026-01-28-051426] Title here", + wantMatch: true, + wantDate: "2026-01-28", + wantTime: "051426", + wantTitle: "Title here", + }, + { + name: "entry with long title", + input: "## [2026-12-31-235959] A much longer title with spaces and stuff", + wantMatch: true, + wantDate: "2026-12-31", + wantTime: "235959", + wantTitle: "A much longer title with spaces and stuff", + }, + { + name: "invalid - missing time", + input: "## [2026-01-28] Title", + wantMatch: false, + }, + { + name: "invalid - wrong format", + input: "## Title without timestamp", + wantMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := RegExEntryHeader.FindStringSubmatch(tt.input) + + if tt.wantMatch { + if match == nil { + t.Errorf("expected match for %q", tt.input) + return + } + if match[1] != tt.wantDate { + t.Errorf("date = %q, want %q", match[1], tt.wantDate) + } + if match[2] != tt.wantTime { + t.Errorf("time = %q, want %q", match[2], tt.wantTime) + } + if match[3] != tt.wantTitle { + t.Errorf("title = %q, want %q", match[3], tt.wantTitle) + } + } else { + if match != nil { + t.Errorf("expected no match for %q", tt.input) + } + } + }) + } +} + +func TestRegExTask(t *testing.T) { + tests := []struct { + name string + input string + wantMatch bool + wantIndent string + wantState string + wantContent string + }{ + { + name: "pending task", + input: "- [ ] Do something", + wantMatch: true, + wantIndent: "", + wantState: " ", + wantContent: "Do something", + }, + { + name: "completed task", + input: "- [x] Done task", + wantMatch: true, + wantIndent: "", + wantState: "x", + wantContent: "Done task", + }, + { + name: "indented task", + input: " - [ ] Subtask", + wantMatch: true, + wantIndent: " ", + wantState: " ", + wantContent: "Subtask", + }, + { + name: "empty checkbox", + input: "- [] Task with empty checkbox", + wantMatch: true, + wantIndent: "", + wantState: "", + wantContent: "Task with empty checkbox", + }, + { + name: "task with tags", + input: "- [ ] Task #added:2026-01-15-120000 #in-progress", + wantMatch: true, + wantIndent: "", + wantState: " ", + wantContent: "Task #added:2026-01-15-120000 #in-progress", + }, + { + name: "not a task - regular bullet", + input: "- Regular bullet point", + wantMatch: false, + }, + { + name: "not a task - numbered list", + input: "1. Numbered item", + wantMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := RegExTask.FindStringSubmatch(tt.input) + + if tt.wantMatch { + if match == nil { + t.Errorf("expected match for %q", tt.input) + return + } + if match[1] != tt.wantIndent { + t.Errorf("indent = %q, want %q", match[1], tt.wantIndent) + } + if match[2] != tt.wantState { + t.Errorf("state = %q, want %q", match[2], tt.wantState) + } + if match[3] != tt.wantContent { + t.Errorf("content = %q, want %q", match[3], tt.wantContent) + } + } else { + if match != nil { + t.Errorf("expected no match for %q", tt.input) + } + } + }) + } +} + +func TestRegExTaskMultiline(t *testing.T) { + input := `# Tasks + +## Phase 1 + +- [x] First task +- [ ] Second task + - [ ] Subtask A + - [x] Subtask B + +## Phase 2 + +- [ ] Third task +` + + matches := RegExTaskMultiline.FindAllStringSubmatch(input, -1) + + if len(matches) != 5 { + t.Errorf("expected 5 matches, got %d", len(matches)) + } + + // Verify first match + if matches[0][3] != "First task" { + t.Errorf("first match content = %q, want %q", matches[0][3], "First task") + } + if matches[0][2] != "x" { + t.Errorf("first match state = %q, want %q", matches[0][2], "x") + } +} + +func TestRegExPhase(t *testing.T) { + tests := []struct { + input string + wantMatch bool + }{ + {"## Phase 1", true}, + {"### Phase 2: Setup", true}, + {"# Phase", true}, + {"###### Phase 99", true}, + {"Phase 1", false}, + {"##Phase 1", false}, + {"## phase 1", false}, // case sensitive + {"## Not a phase", false}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + matched := RegExPhase.MatchString(tt.input) + if matched != tt.wantMatch { + t.Errorf("RegExPhase.MatchString(%q) = %v, want %v", tt.input, matched, tt.wantMatch) + } + }) + } +} + +func TestRegExTaskDoneTimestamp(t *testing.T) { + tests := []struct { + name string + input string + wantMatch bool + wantTime string + }{ + { + name: "task with done timestamp", + input: "- [x] Task #done:2026-01-15-143022", + wantMatch: true, + wantTime: "2026-01-15-143022", + }, + { + name: "task with multiple tags", + input: "- [x] Task #added:2026-01-01-000000 #done:2026-01-15-143022", + wantMatch: true, + wantTime: "2026-01-15-143022", + }, + { + name: "task without done", + input: "- [ ] Task #added:2026-01-01-000000", + wantMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := RegExTaskDoneTimestamp.FindStringSubmatch(tt.input) + + if tt.wantMatch { + if match == nil { + t.Errorf("expected match for %q", tt.input) + return + } + if match[1] != tt.wantTime { + t.Errorf("timestamp = %q, want %q", match[1], tt.wantTime) + } + } else { + if match != nil { + t.Errorf("expected no match for %q", tt.input) + } + } + }) + } +} + +func TestRegExPath(t *testing.T) { + tests := []struct { + name string + input string + wantMatch bool + wantPath string + }{ + { + name: "go file", + input: "Check `internal/config/config.go` for details", + wantMatch: true, + wantPath: "internal/config/config.go", + }, + { + name: "markdown file", + input: "See `docs/README.md`", + wantMatch: true, + wantPath: "docs/README.md", + }, + { + name: "no extension", + input: "`Makefile`", + wantMatch: false, + }, + { + name: "code snippet not path", + input: "`fmt.Println`", + wantMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := RegExPath.FindStringSubmatch(tt.input) + + if tt.wantMatch { + if match == nil { + t.Errorf("expected match for %q", tt.input) + return + } + if match[1] != tt.wantPath { + t.Errorf("path = %q, want %q", match[1], tt.wantPath) + } + } else { + if match != nil { + t.Errorf("expected no match for %q, got %v", tt.input, match) + } + } + }) + } +} + +func TestFileTypeMap(t *testing.T) { + // Verify FileType map contains expected mappings + expected := map[string]string{ + EntryDecision: FileDecision, + EntryTask: FileTask, + EntryLearning: FileLearning, + EntryConvention: FileConvention, + } + + for entry, file := range expected { + if FileType[entry] != file { + t.Errorf("FileType[%q] = %q, want %q", entry, FileType[entry], file) + } + } +} + +func TestRequiredFiles(t *testing.T) { + // Verify RequiredFiles contains essential files + required := map[string]bool{ + FileConstitution: false, + FileTask: false, + FileDecision: false, + } + + for _, f := range RequiredFiles { + if _, ok := required[f]; ok { + required[f] = true + } + } + + for f, found := range required { + if !found { + t.Errorf("RequiredFiles missing %q", f) + } + } +} + +func TestFileReadOrder(t *testing.T) { + // Verify FileReadOrder has expected files in order + if len(FileReadOrder) == 0 { + t.Error("FileReadOrder is empty") + } + + // Constitution should be first (most important) + if FileReadOrder[0] != FileConstitution { + t.Errorf("FileReadOrder[0] = %q, want %q (constitution should be first)", + FileReadOrder[0], FileConstitution) + } + + // Tasks should be second (what to work on) + if FileReadOrder[1] != FileTask { + t.Errorf("FileReadOrder[1] = %q, want %q (tasks should be second)", + FileReadOrder[1], FileTask) + } +} + +func TestEntryPlural(t *testing.T) { + tests := []struct { + entry string + want string + }{ + {EntryTask, "tasks"}, + {EntryDecision, "decisions"}, + {EntryLearning, "learnings"}, + {EntryConvention, "conventions"}, + } + + for _, tt := range tests { + t.Run(tt.entry, func(t *testing.T) { + got := EntryPlural[tt.entry] + if got != tt.want { + t.Errorf("EntryPlural[%q] = %q, want %q", tt.entry, got, tt.want) + } + }) + } +} + +func TestDefaultClaudePermissions(t *testing.T) { + if len(DefaultClaudePermissions) == 0 { + t.Error("DefaultClaudePermissions should not be empty") + } + + // Check that essential ctx commands are included + expected := []string{ + "Bash(ctx status:*)", + "Bash(ctx agent:*)", + "Bash(ctx add:*)", + "Bash(ctx session:*)", + } + + permSet := make(map[string]bool) + for _, p := range DefaultClaudePermissions { + permSet[p] = true + } + + for _, e := range expected { + if !permSet[e] { + t.Errorf("DefaultClaudePermissions missing: %s", e) + } + } +} + +func TestConstants(t *testing.T) { + // Verify important constants are set correctly + tests := []struct { + name string + got string + want string + }{ + {"DirContext", DirContext, ".context"}, + {"DirClaude", DirClaude, ".claude"}, + {"FileTask", FileTask, "TASKS.md"}, + {"FileDecision", FileDecision, "DECISIONS.md"}, + {"FileLearning", FileLearning, "LEARNINGS.md"}, + {"PrefixTaskUndone", PrefixTaskUndone, "- [ ]"}, + {"PrefixTaskDone", PrefixTaskDone, "- [x]"}, + {"IndexStart", IndexStart, ""}, + {"IndexEnd", IndexEnd, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.want { + t.Errorf("%s = %q, want %q", tt.name, tt.got, tt.want) + } + }) + } +} diff --git a/internal/config/entry.go b/internal/config/entry.go index bac28ebda..5aabc62fe 100644 --- a/internal/config/entry.go +++ b/internal/config/entry.go @@ -27,6 +27,16 @@ const ( EntryUnknown = "unknown" ) +// EntryPlural maps entry type constants to their plural forms. +// +// Used for user-facing messages (e.g., "no decisions found"). +var EntryPlural = map[string]string{ + EntryTask: "tasks", + EntryDecision: "decisions", + EntryLearning: "learnings", + EntryConvention: "conventions", +} + // UserInputToEntry normalizes user input to a canonical entry type. // // Accepts both singular and plural forms (e.g., "task" or "tasks") and diff --git a/internal/config/file.go b/internal/config/file.go index e460ff7b3..cfb7f36eb 100644 --- a/internal/config/file.go +++ b/internal/config/file.go @@ -6,6 +6,46 @@ package config +// Runtime configuration constants. +const ( + // FileContextRC is the optional runtime configuration file. + FileContextRC = ".contextrc" +) + +// Environment configuration. +const ( + // EnvCtxDir is the environment variable for overriding the context directory. + EnvCtxDir = "CTX_DIR" + // EnvCtxTokenBudget is the environment variable for overriding the token budget. + EnvCtxTokenBudget = "CTX_TOKEN_BUDGET" +) + +// Parser configuration. +const ( + // ParserPeekLines is the number of lines to scan when detecting file format. + ParserPeekLines = 50 +) + +// Claude API content block types. +const ( + // ClaudeBlockText is a text content block. + ClaudeBlockText = "text" + // ClaudeBlockThinking is an extended thinking content block. + ClaudeBlockThinking = "thinking" + // ClaudeBlockToolUse is a tool invocation block. + ClaudeBlockToolUse = "tool_use" + // ClaudeBlockToolResult is a tool execution result block. + ClaudeBlockToolResult = "tool_result" +) + +// Claude API message roles. +const ( + // RoleUser is a user message. + RoleUser = "user" + // RoleAssistant is an assistant message. + RoleAssistant = "assistant" +) + // Claude Code integration file names. const ( // FileAutoSave is the hook script for auto-saving sessions. @@ -115,3 +155,16 @@ var Packages = map[string]string{ "requirements.txt": "Python dependencies", "Gemfile": "Ruby dependencies", } + +// DefaultClaudePermissions lists the default permissions for ctx commands. +// +// These permissions allow Claude Code to run ctx CLI commands without +// prompting for approval. All ctx subcommands are pre-approved. +var DefaultClaudePermissions = []string{ + "Bash(ctx status:*)", + "Bash(ctx agent:*)", + "Bash(ctx add:*)", + "Bash(ctx session:*)", + "Bash(ctx tasks:*)", + "Bash(ctx loop:*)", +} diff --git a/internal/index/index.go b/internal/index/index.go index b7af7fae0..45dbbfc11 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -8,8 +8,13 @@ package index import ( + "fmt" + "io" + "os" "strings" + "github.com/fatih/color" + "github.com/ActiveMemory/ctx/internal/config" ) @@ -192,3 +197,63 @@ func UpdateDecisions(content string) string { func UpdateLearnings(content string) string { return Update(content, config.HeadingLearnings, config.ColumnLearning) } + +// ReindexFile reads a context file, regenerates its index, and writes it back. +// +// This is a convenience function that handles the common reindex workflow: +// check the file exists, read content, apply update function, write back, +// report. +// +// Note: This function uses io.Writer instead of *cobra.Command to keep the +// index package decoupled from CLI concerns. Callers pass cmd.OutOrStdout() +// which writes to the same destination as cmd.Printf. +// +// Parameters: +// - w: Writer for status output (typically cmd.OutOrStdout()) +// - filePath: Full path to the context file +// - fileName: Display name for error messages (e.g., "DECISIONS.md") +// - updateFunc: Function to regenerate the index (e.g., UpdateDecisions) +// - entryType: Plural noun for the status message (e.g., "decisions") +// +// Returns: +// - error: Non-nil if file operations fail +func ReindexFile( + w io.Writer, filePath, fileName string, + updateFunc func(string) string, + entryType string, +) error { + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return fmt.Errorf("%s not found. Run 'ctx init' first", fileName) + } + + content, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read %s: %w", filePath, err) + } + + updated := updateFunc(string(content)) + + if err := os.WriteFile(filePath, []byte(updated), 0644); err != nil { + return fmt.Errorf("failed to write %s: %w", filePath, err) + } + + entries := ParseHeaders(string(content)) + green := color.New(color.FgGreen).SprintFunc() + if len(entries) == 0 { + _, err := fmt.Fprintf( + w, "%s Index cleared (no %s found)\n", green("✓"), entryType) + if err != nil { + return err + } + } else { + _, err := fmt.Fprintf( + w, + "%s Index regenerated with %d entries\n", green("✓"), len(entries), + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/rc/default.go b/internal/rc/default.go new file mode 100644 index 000000000..f56055459 --- /dev/null +++ b/internal/rc/default.go @@ -0,0 +1 @@ +package rc diff --git a/internal/rc/load.go b/internal/rc/load.go new file mode 100644 index 000000000..49dc2dafd --- /dev/null +++ b/internal/rc/load.go @@ -0,0 +1,44 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package rc + +import ( + "os" + "strconv" + + "gopkg.in/yaml.v3" + + "github.com/ActiveMemory/ctx/internal/config" +) + +// loadRC loads configuration from the .contextrc file and applies env +// overrides. +// +// Returns: +// - *CtxRC: Configuration with file values and env overrides applied +func loadRC() *CtxRC { + cfg := Default() + + // Try to load .contextrc from the current directory + data, err := os.ReadFile(config.FileContextRC) + if err == nil { + // Parse YAML, ignoring errors (use defaults for invalid config) + _ = yaml.Unmarshal(data, cfg) + } + + // Apply environment variable overrides + if envDir := os.Getenv(config.EnvCtxDir); envDir != "" { + cfg.ContextDir = envDir + } + if envBudget := os.Getenv(config.EnvCtxTokenBudget); envBudget != "" { + if budget, err := strconv.Atoi(envBudget); err == nil && budget > 0 { + cfg.TokenBudget = budget + } + } + + return cfg +} diff --git a/internal/rc/lock.go b/internal/rc/lock.go new file mode 100644 index 000000000..64febba89 --- /dev/null +++ b/internal/rc/lock.go @@ -0,0 +1,16 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package rc + +import "sync" + +var ( + rc *CtxRC + rcOnce sync.Once + rcOverrideDir string + rcMu sync.RWMutex +) diff --git a/internal/rc/rc.go b/internal/rc/rc.go index ab355a8ce..94c6923ea 100644 --- a/internal/rc/rc.go +++ b/internal/rc/rc.go @@ -8,49 +8,18 @@ package rc import ( - "os" - "strconv" "sync" - "gopkg.in/yaml.v3" - "github.com/ActiveMemory/ctx/internal/config" ) -// RC represents the configuration from .contextrc file. -// -// Fields: -// - ContextDir: Name of the context directory (default ".context") -// - TokenBudget: Default token budget for context assembly (default 8000) -// - PriorityOrder: Custom file loading priority order -// - AutoArchive: Whether to auto-archive completed tasks (default true) -// - ArchiveAfterDays: Days before archiving completed tasks (default 7) -type RC struct { - ContextDir string `yaml:"context_dir"` - TokenBudget int `yaml:"token_budget"` - PriorityOrder []string `yaml:"priority_order"` - AutoArchive bool `yaml:"auto_archive"` - ArchiveAfterDays int `yaml:"archive_after_days"` -} - -// DefaultTokenBudget is the default token budget when not configured. -const DefaultTokenBudget = 8000 - -// DefaultArchiveAfterDays is the default days before archiving. -const DefaultArchiveAfterDays = 7 - -var ( - rc *RC - rcOnce sync.Once - rcOverrideDir string -) - -// DefaultRC returns a new RC with hardcoded default values. +// Default returns a new CtxRC with hardcoded default values. // // Returns: -// - *RC: Configuration with defaults (8000 token budget, 7-day archive, etc.) -func DefaultRC() *RC { - return &RC{ +// - *CtxRC: Configuration with defaults +// (8000 token budget, 7-day archive, etc.) +func Default() *CtxRC { + return &CtxRC{ ContextDir: config.DirContext, TokenBudget: DefaultTokenBudget, PriorityOrder: nil, // nil means use config.FileReadOrder @@ -59,93 +28,68 @@ func DefaultRC() *RC { } } -// GetRC returns the loaded configuration, initializing it on first call. +// RC returns the loaded configuration, initializing it on the first call. // // It loads from .contextrc if present, then applies environment overrides. // The result is cached for subsequent calls. // // Returns: -// - *RC: The loaded and cached configuration -func GetRC() *RC { +// - *CtxRC: The loaded and cached configuration +func RC() *CtxRC { rcOnce.Do(func() { rc = loadRC() }) return rc } -// loadRC loads configuration from .contextrc file and applies env overrides. -// -// Returns: -// - *RC: Configuration with file values and env overrides applied -func loadRC() *RC { - cfg := DefaultRC() - - // Try to load .contextrc from current directory - data, err := os.ReadFile(".contextrc") - if err == nil { - // Parse YAML, ignoring errors (use defaults for invalid config) - _ = yaml.Unmarshal(data, cfg) - } - - // Apply environment variable overrides - if envDir := os.Getenv("CTX_DIR"); envDir != "" { - cfg.ContextDir = envDir - } - if envBudget := os.Getenv("CTX_TOKEN_BUDGET"); envBudget != "" { - if budget, err := strconv.Atoi(envBudget); err == nil && budget > 0 { - cfg.TokenBudget = budget - } - } - - return cfg -} - -// GetContextDir returns the configured context directory. +// ContextDir returns the configured context directory. // // Priority: CLI override > env var > .contextrc > default. // // Returns: // - string: The context directory path (e.g., ".context") -func GetContextDir() string { +func ContextDir() string { + rcMu.RLock() + defer rcMu.RUnlock() if rcOverrideDir != "" { return rcOverrideDir } - return GetRC().ContextDir + return RC().ContextDir } -// GetTokenBudget returns the configured default token budget. +// TokenBudget returns the configured default token budget. // // Priority: env var > .contextrc > default (8000). // // Returns: // - int: The token budget for context assembly -func GetTokenBudget() int { - return GetRC().TokenBudget +func TokenBudget() int { + return RC().TokenBudget } -// GetPriorityOrder returns the configured file priority order. +// PriorityOrder returns the configured file priority order. // // Returns: // - []string: File names in priority order, or nil if not configured // (callers should fall back to config.FileReadOrder) -func GetPriorityOrder() []string { - return GetRC().PriorityOrder +func PriorityOrder() []string { + return RC().PriorityOrder } -// GetAutoArchive returns whether auto-archiving is enabled. +// AutoArchive returns whether auto-archiving is enabled. // // Returns: // - bool: True if completed tasks should be auto-archived -func GetAutoArchive() bool { - return GetRC().AutoArchive +func AutoArchive() bool { + return RC().AutoArchive } -// GetArchiveAfterDays returns the configured days before archiving. +// ArchiveAfterDays returns the configured days before archiving. // // Returns: // - int: Number of days after which completed tasks are archived (default 7) -func GetArchiveAfterDays() int { - return GetRC().ArchiveAfterDays +func ArchiveAfterDays() int { + return RC().ArchiveAfterDays } // OverrideContextDir sets a CLI-provided override for the context directory. @@ -153,14 +97,18 @@ func GetArchiveAfterDays() int { // This takes precedence over all other configuration sources. // // Parameters: -// - dir: Directory path to use as override +// - dir: Directory path to use as an override func OverrideContextDir(dir string) { + rcMu.Lock() + defer rcMu.Unlock() rcOverrideDir = dir } -// ResetRC clears the cached configuration, forcing reload on next access. +// Reset clears the cached configuration, forcing reload on the next access. // This is primarily useful for testing. -func ResetRC() { +func Reset() { + rcMu.Lock() + defer rcMu.Unlock() rcOnce = sync.Once{} rc = nil rcOverrideDir = "" @@ -181,7 +129,7 @@ func ResetRC() { // - int: Priority value (1-9 for known files, 100 for unknown) func FilePriority(name string) int { // Check for .contextrc override first - if order := GetPriorityOrder(); order != nil { + if order := PriorityOrder(); order != nil { for i, fName := range order { if fName == name { return i + 1 @@ -191,7 +139,7 @@ func FilePriority(name string) int { return 100 } - // Use default priority from config.FileReadOrder + // Use the default priority from config.FileReadOrder for i, fName := range config.FileReadOrder { if fName == name { return i + 1 diff --git a/internal/rc/rc_test.go b/internal/rc/rc_test.go index 1a1d30ae1..c85dd3ba3 100644 --- a/internal/rc/rc_test.go +++ b/internal/rc/rc_test.go @@ -15,7 +15,7 @@ import ( ) func TestDefaultRC(t *testing.T) { - rc := DefaultRC() + rc := Default() if rc.ContextDir != config.DirContext { t.Errorf("ContextDir = %q, want %q", rc.ContextDir, config.DirContext) @@ -41,9 +41,9 @@ func TestGetRC_NoFile(t *testing.T) { os.Chdir(tempDir) defer os.Chdir(origDir) - ResetRC() + Reset() - rc := GetRC() + rc := RC() if rc.ContextDir != config.DirContext { t.Errorf("ContextDir = %q, want %q", rc.ContextDir, config.DirContext) @@ -70,9 +70,9 @@ archive_after_days: 14 ` os.WriteFile(filepath.Join(tempDir, ".contextrc"), []byte(rcContent), 0644) - ResetRC() + Reset() - rc := GetRC() + rc := RC() if rc.ContextDir != "custom-context" { t.Errorf("ContextDir = %q, want %q", rc.ContextDir, "custom-context") @@ -103,17 +103,13 @@ token_budget: 4000 ` os.WriteFile(filepath.Join(tempDir, ".contextrc"), []byte(rcContent), 0644) - // Set environment variables - os.Setenv("CTX_DIR", "env-context") - os.Setenv("CTX_TOKEN_BUDGET", "2000") - defer func() { - os.Unsetenv("CTX_DIR") - os.Unsetenv("CTX_TOKEN_BUDGET") - }() + // Set environment variables (t.Setenv auto-restores after test) + t.Setenv(config.EnvCtxDir, "env-context") + t.Setenv(config.EnvCtxTokenBudget, "2000") - ResetRC() + Reset() - rc := GetRC() + rc := RC() // Env should override file if rc.ContextDir != "env-context" { @@ -134,19 +130,18 @@ func TestGetContextDir_CLIOverride(t *testing.T) { rcContent := `context_dir: file-context` os.WriteFile(filepath.Join(tempDir, ".contextrc"), []byte(rcContent), 0644) - // Set env override - os.Setenv("CTX_DIR", "env-context") - defer os.Unsetenv("CTX_DIR") + // Set env override (t.Setenv auto-restores after test) + t.Setenv(config.EnvCtxDir, "env-context") - ResetRC() + Reset() // CLI override takes precedence over all OverrideContextDir("cli-context") - defer ResetRC() + defer Reset() - dir := GetContextDir() + dir := ContextDir() if dir != "cli-context" { - t.Errorf("GetContextDir() = %q, want %q (CLI override)", dir, "cli-context") + t.Errorf("ContextDir() = %q, want %q (CLI override)", dir, "cli-context") } } @@ -156,12 +151,12 @@ func TestGetTokenBudget(t *testing.T) { os.Chdir(tempDir) defer os.Chdir(origDir) - ResetRC() + Reset() // Default value - budget := GetTokenBudget() + budget := TokenBudget() if budget != DefaultTokenBudget { - t.Errorf("GetTokenBudget() = %d, want %d", budget, DefaultTokenBudget) + t.Errorf("TokenBudget() = %d, want %d", budget, DefaultTokenBudget) } } @@ -174,10 +169,10 @@ func TestGetRC_InvalidYAML(t *testing.T) { // Create invalid .contextrc file os.WriteFile(filepath.Join(tempDir, ".contextrc"), []byte("invalid: [yaml: content"), 0644) - ResetRC() + Reset() // Should return defaults on invalid YAML - rc := GetRC() + rc := RC() if rc.TokenBudget != DefaultTokenBudget { t.Errorf("TokenBudget = %d, want %d (defaults on invalid YAML)", rc.TokenBudget, DefaultTokenBudget) } @@ -193,9 +188,9 @@ func TestGetRC_PartialConfig(t *testing.T) { rcContent := `token_budget: 5000` os.WriteFile(filepath.Join(tempDir, ".contextrc"), []byte(rcContent), 0644) - ResetRC() + Reset() - rc := GetRC() + rc := RC() // Specified value should be used if rc.TokenBudget != 5000 { @@ -213,13 +208,12 @@ func TestGetRC_InvalidEnvBudget(t *testing.T) { os.Chdir(tempDir) defer os.Chdir(origDir) - os.Setenv("CTX_TOKEN_BUDGET", "not-a-number") - defer os.Unsetenv("CTX_TOKEN_BUDGET") + t.Setenv(config.EnvCtxTokenBudget, "not-a-number") - ResetRC() + Reset() // Invalid env should be ignored, use default - rc := GetRC() + rc := RC() if rc.TokenBudget != DefaultTokenBudget { t.Errorf("TokenBudget = %d, want %d (default on invalid env)", rc.TokenBudget, DefaultTokenBudget) } @@ -231,12 +225,12 @@ func TestGetRC_Singleton(t *testing.T) { os.Chdir(tempDir) defer os.Chdir(origDir) - ResetRC() + Reset() - rc1 := GetRC() - rc2 := GetRC() + rc1 := RC() + rc2 := RC() if rc1 != rc2 { - t.Error("GetRC() should return same instance") + t.Error("RC() should return same instance") } } diff --git a/internal/rc/types.go b/internal/rc/types.go new file mode 100644 index 000000000..f56055459 --- /dev/null +++ b/internal/rc/types.go @@ -0,0 +1 @@ +package rc diff --git a/internal/recall/parser/claude.go b/internal/recall/parser/claude.go index f87867e7e..45f6eb8eb 100644 --- a/internal/recall/parser/claude.go +++ b/internal/recall/parser/claude.go @@ -11,10 +11,11 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "sort" "strings" "time" + + "github.com/ActiveMemory/ctx/internal/config" ) // ClaudeCodeParser parses Claude Code JSONL session files. @@ -56,17 +57,22 @@ func (p *ClaudeCodeParser) CanParse(path string) bool { return false } - // Peek at first few lines to detect Claude Code format + // Peek at the first few lines to detect the Claude Code format file, err := os.Open(path) if err != nil { return false } - defer file.Close() + defer func(file *os.File) { + err := file.Close() + if err != nil { + fmt.Println("Error closing file:", err.Error()) + } + }(file) scanner := bufio.NewScanner(file) - // Check first 50 lines - slug may not appear until later in the file - // (early lines can be file-history-snapshot or messages without slug) - for i := 0; i < 50 && scanner.Scan(); i++ { + // Check the first N lines - slug may not appear until later in the file + // (early lines can be file-history-snapshot or messages without the slug) + for i := 0; i < config.ParserPeekLines && scanner.Scan(); i++ { line := scanner.Bytes() if len(line) == 0 { continue @@ -103,7 +109,12 @@ func (p *ClaudeCodeParser) ParseFile(path string) ([]*Session, error) { if err != nil { return nil, fmt.Errorf("open file: %w", err) } - defer file.Close() + defer func(file *os.File) { + err := file.Close() + if err != nil { + fmt.Println("Error closing file:", err.Error()) + } + }(file) // Group messages by session ID sessionMsgs := make(map[string][]claudeRawMessage) @@ -128,7 +139,7 @@ func (p *ClaudeCodeParser) ParseFile(path string) ([]*Session, error) { } // Skip non-message lines (e.g., file-history-snapshot) - if raw.Type != "user" && raw.Type != "assistant" { + if raw.Type != config.RoleUser && raw.Type != config.RoleAssistant { continue } @@ -184,7 +195,7 @@ func (p *ClaudeCodeParser) ParseLine(line []byte) (*Message, string, error) { } // Skip non-message lines - if raw.Type != "user" && raw.Type != "assistant" { + if raw.Type != config.RoleUser && raw.Type != config.RoleAssistant { return nil, "", nil } @@ -192,193 +203,22 @@ func (p *ClaudeCodeParser) ParseLine(line []byte) (*Message, string, error) { return &msg, raw.SessionID, nil } -// buildSession constructs a Session from raw Claude Code messages. -// -// Parameters: -// - id: Session ID to use -// - rawMsgs: Raw messages belonging to this session -// - sourcePath: Path to the source JSONL file -// -// Returns: -// - *Session: Constructed session with messages, stats, and metadata -func (p *ClaudeCodeParser) buildSession(id string, rawMsgs []claudeRawMessage, sourcePath string) *Session { - if len(rawMsgs) == 0 { - return nil - } - - // Sort by timestamp - sort.Slice(rawMsgs, func(i, j int) bool { - return rawMsgs[i].Timestamp.Before(rawMsgs[j].Timestamp) - }) - - first := rawMsgs[0] - last := rawMsgs[len(rawMsgs)-1] - - session := &Session{ - ID: id, - Slug: first.Slug, - Tool: "claude-code", - SourceFile: sourcePath, - CWD: first.CWD, - Project: filepath.Base(first.CWD), - GitBranch: first.GitBranch, - StartTime: first.Timestamp, - EndTime: last.Timestamp, - Duration: last.Timestamp.Sub(first.Timestamp), - } - - // Convert messages and accumulate stats - for _, raw := range rawMsgs { - msg := p.convertMessage(raw) - session.Messages = append(session.Messages, msg) - - if msg.IsUser() { - session.TurnCount++ - if session.FirstUserMsg == "" && msg.Text != "" { - // Truncate preview - preview := msg.Text - if len(preview) > 100 { - preview = preview[:100] + "..." - } - session.FirstUserMsg = preview - } - } - - session.TotalTokensIn += msg.TokensIn - session.TotalTokensOut += msg.TokensOut - - // Check for errors in tool results - for _, tr := range msg.ToolResults { - if tr.IsError { - session.HasErrors = true - } - } - - // Track model - if raw.Message.Model != "" && session.Model == "" { - session.Model = raw.Message.Model - } - } - - session.TotalTokens = session.TotalTokensIn + session.TotalTokensOut - - return session -} - -// convertMessage converts a Claude Code raw message to the common Message type. -// -// Parameters: -// - raw: Raw message from JSONL parsing -// -// Returns: -// - Message: Normalized message with text, tool uses, and tool results extracted -func (p *ClaudeCodeParser) convertMessage(raw claudeRawMessage) Message { - msg := Message{ - ID: raw.UUID, - Timestamp: raw.Timestamp, - Role: raw.Type, - } - - if raw.Message.Usage != nil { - msg.TokensIn = raw.Message.Usage.InputTokens - msg.TokensOut = raw.Message.Usage.OutputTokens - } - - // Parse content - can be a string or array of blocks - blocks := p.parseContentBlocks(raw.Message.Content) - - // Extract content from blocks - for _, block := range blocks { - switch block.Type { - case "text": - if msg.Text != "" { - msg.Text += "\n" - } - msg.Text += block.Text - - case "thinking": - if msg.Thinking != "" { - msg.Thinking += "\n" - } - msg.Thinking += block.Thinking - - case "tool_use": - inputStr := "" - if block.Input != nil { - inputStr = string(block.Input) - } - msg.ToolUses = append(msg.ToolUses, ToolUse{ - ID: block.ID, - Name: block.Name, - Input: inputStr, - }) - - case "tool_result": - contentStr := "" - if block.Content != nil { - // Try to unmarshal as JSON string first (handles escaping) - var unescaped string - if err := json.Unmarshal(block.Content, &unescaped); err == nil { - contentStr = unescaped - } else { - // Fallback to raw bytes - contentStr = string(block.Content) - } - } - msg.ToolResults = append(msg.ToolResults, ToolResult{ - ToolUseID: block.ToolUseID, - Content: contentStr, - IsError: block.IsError, - }) - } - } - - return msg -} - -// parseContentBlocks parses the content field which can be a string or array. -// -// Parameters: -// - content: Raw JSON content that may be a string or array of blocks -// -// Returns: -// - []claudeRawBlock: Parsed content blocks (text, thinking, tool_use, tool_result) -func (p *ClaudeCodeParser) parseContentBlocks(content json.RawMessage) []claudeRawBlock { - if len(content) == 0 { - return nil - } - - // Try parsing as array of blocks first - var blocks []claudeRawBlock - if err := json.Unmarshal(content, &blocks); err == nil { - return blocks - } - - // Try parsing as a simple string - var text string - if err := json.Unmarshal(content, &text); err == nil && text != "" { - return []claudeRawBlock{{Type: "text", Text: text}} - } - - return nil -} - // Claude Code-specific raw types for parsing JSONL type claudeRawMessage struct { - UUID string `json:"uuid"` - ParentUUID *string `json:"parentUuid"` - SessionID string `json:"sessionId"` - RequestID string `json:"requestId,omitempty"` - Timestamp time.Time `json:"timestamp"` - Type string `json:"type"` // "user", "assistant", or other - UserType string `json:"userType,omitempty"` - IsSidechain bool `json:"isSidechain,omitempty"` - CWD string `json:"cwd"` - GitBranch string `json:"gitBranch,omitempty"` - Version string `json:"version"` - Slug string `json:"slug"` - Message claudeRawContent `json:"message"` + UUID string `json:"uuid"` + ParentUUID *string `json:"parentUuid"` + SessionID string `json:"sessionId"` + RequestID string `json:"requestId,omitempty"` + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` // "user", "assistant", or other + UserType string `json:"userType,omitempty"` + IsSidechain bool `json:"isSidechain,omitempty"` + CWD string `json:"cwd"` + GitBranch string `json:"gitBranch,omitempty"` + Version string `json:"version"` + Slug string `json:"slug"` + Message claudeRawContent `json:"message"` } type claudeRawContent struct { diff --git a/internal/recall/parser/git.go b/internal/recall/parser/git.go new file mode 100644 index 000000000..0bfe2c257 --- /dev/null +++ b/internal/recall/parser/git.go @@ -0,0 +1 @@ +package parser diff --git a/internal/recall/parser/parse.go b/internal/recall/parser/parse.go new file mode 100644 index 000000000..d760d7eb5 --- /dev/null +++ b/internal/recall/parser/parse.go @@ -0,0 +1,192 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "encoding/json" + "path/filepath" + "sort" + + "github.com/ActiveMemory/ctx/internal/config" +) + +// buildSession constructs a Session from raw Claude Code messages. +// +// Parameters: +// - id: Session ID to use +// - rawMsgs: Raw messages belonging to this session +// - sourcePath: Path to the source JSONL file +// +// Returns: +// - *Session: Constructed session with messages, stats, and metadata +func (p *ClaudeCodeParser) buildSession( + id string, rawMsgs []claudeRawMessage, sourcePath string, +) *Session { + if len(rawMsgs) == 0 { + return nil + } + + // Sort by timestamp + sort.Slice(rawMsgs, func(i, j int) bool { + return rawMsgs[i].Timestamp.Before(rawMsgs[j].Timestamp) + }) + + first := rawMsgs[0] + last := rawMsgs[len(rawMsgs)-1] + + session := &Session{ + ID: id, + Slug: first.Slug, + Tool: "claude-code", + SourceFile: sourcePath, + CWD: first.CWD, + Project: filepath.Base(first.CWD), + GitBranch: first.GitBranch, + StartTime: first.Timestamp, + EndTime: last.Timestamp, + Duration: last.Timestamp.Sub(first.Timestamp), + } + + // Convert messages and accumulate stats + for _, raw := range rawMsgs { + msg := p.convertMessage(raw) + session.Messages = append(session.Messages, msg) + + if msg.IsUser() { + session.TurnCount++ + if session.FirstUserMsg == "" && msg.Text != "" { + // Truncate preview + preview := msg.Text + if len(preview) > 100 { + preview = preview[:100] + "..." + } + session.FirstUserMsg = preview + } + } + + session.TotalTokensIn += msg.TokensIn + session.TotalTokensOut += msg.TokensOut + + // Check for errors in tool results + for _, tr := range msg.ToolResults { + if tr.IsError { + session.HasErrors = true + } + } + + // Track model + if raw.Message.Model != "" && session.Model == "" { + session.Model = raw.Message.Model + } + } + + session.TotalTokens = session.TotalTokensIn + session.TotalTokensOut + + return session +} + +// convertMessage converts a Claude Code raw message to the common Message type. +// +// Parameters: +// - raw: Raw message from JSONL parsing +// +// Returns: +// - Message: Normalized message with text, tool uses, +// and tool results extracted +func (p *ClaudeCodeParser) convertMessage(raw claudeRawMessage) Message { + msg := Message{ + ID: raw.UUID, + Timestamp: raw.Timestamp, + Role: raw.Type, + } + + if raw.Message.Usage != nil { + msg.TokensIn = raw.Message.Usage.InputTokens + msg.TokensOut = raw.Message.Usage.OutputTokens + } + + // Parse content - can be a string or array of blocks + blocks := p.parseContentBlocks(raw.Message.Content) + + // Extract content from blocks + for _, block := range blocks { + switch block.Type { + case config.ClaudeBlockText: + if msg.Text != "" { + msg.Text += config.NewlineLF + } + msg.Text += block.Text + + case config.ClaudeBlockThinking: + if msg.Thinking != "" { + msg.Thinking += config.NewlineLF + } + msg.Thinking += block.Thinking + + case config.ClaudeBlockToolUse: + inputStr := "" + if block.Input != nil { + inputStr = string(block.Input) + } + msg.ToolUses = append(msg.ToolUses, ToolUse{ + ID: block.ID, + Name: block.Name, + Input: inputStr, + }) + + case config.ClaudeBlockToolResult: + contentStr := "" + if block.Content != nil { + // Try to unmarshal as JSON string first (handles escaping) + var unescaped string + if err := json.Unmarshal(block.Content, &unescaped); err == nil { + contentStr = unescaped + } else { + // Fallback to raw bytes + contentStr = string(block.Content) + } + } + msg.ToolResults = append(msg.ToolResults, ToolResult{ + ToolUseID: block.ToolUseID, + Content: contentStr, + IsError: block.IsError, + }) + } + } + + return msg +} + +// parseContentBlocks parses the content field, which can be a string or array. +// +// Parameters: +// - content: Raw JSON content that may be a string or array of blocks +// +// Returns: +// - []claudeRawBlock: Parsed content blocks +// (text, thinking, tool_use, tool_result) +func (p *ClaudeCodeParser) parseContentBlocks( + content json.RawMessage, +) []claudeRawBlock { + if len(content) == 0 { + return nil + } + + // Try parsing as the array of blocks first + var blocks []claudeRawBlock + if err := json.Unmarshal(content, &blocks); err == nil { + return blocks + } + + // Try parsing as a simple string + var text string + if err := json.Unmarshal(content, &text); err == nil && text != "" { + return []claudeRawBlock{{Type: config.ClaudeBlockText, Text: text}} + } + + return nil +} diff --git a/internal/recall/parser/parser.go b/internal/recall/parser/parser.go index ad169a8f7..733a2d2a3 100644 --- a/internal/recall/parser/parser.go +++ b/internal/recall/parser/parser.go @@ -9,10 +9,8 @@ package parser import ( "fmt" "os" - "os/exec" "path/filepath" "sort" - "strings" ) // registeredParsers holds all available session parsers. @@ -43,7 +41,9 @@ func ParseFile(path string) ([]*Session, error) { // ScanDirectory recursively scans a directory for session files. // // It finds all parseable files, parses them, and aggregates sessions. -// Sessions are sorted by start time (newest first). +// Sessions are sorted by start time (newest first). Parse errors for +// individual files are silently ignored; use ScanDirectoryWithErrors +// if you need to report them. // // Parameters: // - dir: Root directory to scan recursively @@ -52,44 +52,8 @@ func ParseFile(path string) ([]*Session, error) { // - []*Session: All sessions found, sorted by start time (newest first) // - error: Non-nil if directory traversal fails func ScanDirectory(dir string) ([]*Session, error) { - var allSessions []*Session - var parseErrors []error - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - // Try to parse with any registered parser - for _, parser := range registeredParsers { - if parser.CanParse(path) { - sessions, err := parser.ParseFile(path) - if err != nil { - parseErrors = append(parseErrors, fmt.Errorf("%s: %w", path, err)) - break - } - allSessions = append(allSessions, sessions...) - break - } - } - - return nil - }) - - if err != nil { - return nil, fmt.Errorf("walk directory: %w", err) - } - - // Sort by start time (newest first) - sort.Slice(allSessions, func(i, j int) bool { - return allSessions[i].StartTime.After(allSessions[j].StartTime) - }) - - return allSessions, nil + sessions, _, err := ScanDirectoryWithErrors(dir) + return sessions, err } // ScanDirectoryWithErrors is like ScanDirectory but also returns parse errors. @@ -108,7 +72,9 @@ func ScanDirectoryWithErrors(dir string) ([]*Session, []error, error) { var allSessions []*Session var parseErrors []error - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(dir, func( + path string, info os.FileInfo, err error, + ) error { if err != nil { return err } @@ -161,10 +127,12 @@ func FindSessions(additionalDirs ...string) ([]*Session, error) { return findSessionsWithFilter(nil, additionalDirs...) } -// FindSessionsForCWD searches for sessions matching the given working directory. +// FindSessionsForCWD searches for sessions matching the given +// working directory. // // Matching is done in order of preference: -// 1. Git remote URL match - if both directories are git repos with same remote +// 1. Git remote URL match - if both directories are git repos with +// the same remote // 2. Path relative to home - e.g., "WORKSPACE/ctx" matches across users // 3. Exact CWD match - fallback for non-git, non-home paths // @@ -175,9 +143,11 @@ func FindSessions(additionalDirs ...string) ([]*Session, error) { // Returns: // - []*Session: Filtered sessions sorted by start time (newest first) // - error: Non-nil if scanning fails -func FindSessionsForCWD(cwd string, additionalDirs ...string) ([]*Session, error) { +func FindSessionsForCWD( + cwd string, additionalDirs ...string, +) ([]*Session, error) { // Get current project's git remote (if available) - currentRemote := getGitRemote(cwd) + currentRemote := gitRemote(cwd) // Get path relative to home directory currentRelPath := getPathRelativeToHome(cwd) @@ -185,13 +155,13 @@ func FindSessionsForCWD(cwd string, additionalDirs ...string) ([]*Session, error return findSessionsWithFilter(func(s *Session) bool { // 1. Try git remote match (most robust) if currentRemote != "" { - sessionRemote := getGitRemote(s.CWD) + sessionRemote := gitRemote(s.CWD) if sessionRemote != "" && sessionRemote == currentRemote { return true } } - // 2. Try path relative to home match + // 2. Try the path relative to the home match if currentRelPath != "" { sessionRelPath := getPathRelativeToHome(s.CWD) if sessionRelPath != "" && sessionRelPath == currentRelPath { @@ -199,114 +169,19 @@ func FindSessionsForCWD(cwd string, additionalDirs ...string) ([]*Session, error } } - // 3. Fallback to exact match + // 3. Fallback to an exact match return s.CWD == cwd }, additionalDirs...) } -// getGitRemote returns the git remote origin URL for a directory. -// Returns empty string if not a git repo or no remote configured. -func getGitRemote(dir string) string { - if dir == "" { - return "" - } - - // Check if directory exists - if _, err := os.Stat(dir); err != nil { - return "" - } - - // Try to get git remote - cmd := exec.Command("git", "-C", dir, "remote", "get-url", "origin") - output, err := cmd.Output() - if err != nil { - return "" - } - - return strings.TrimSpace(string(output)) -} - -// getPathRelativeToHome returns the path relative to the user's home directory. -// Returns empty string if path is not under a home directory. -func getPathRelativeToHome(path string) string { - if path == "" { - return "" - } - - // Handle common home directory patterns - // /home/username/... -> strip /home/username - // /Users/username/... -> strip /Users/username (macOS) - parts := strings.Split(path, string(filepath.Separator)) - - for i, part := range parts { - if part == "home" || part == "Users" { - // Next part is username, rest is relative path - if i+2 < len(parts) { - return filepath.Join(parts[i+2:]...) - } - return "" - } - } - - return "" -} - -// findSessionsWithFilter is the internal implementation with optional filtering. -func findSessionsWithFilter(filter func(*Session) bool, additionalDirs ...string) ([]*Session, error) { - var allSessions []*Session - - // Check Claude Code default location - home, err := os.UserHomeDir() - if err == nil { - claudeDir := filepath.Join(home, ".claude", "projects") - if info, err := os.Stat(claudeDir); err == nil && info.IsDir() { - sessions, _ := ScanDirectory(claudeDir) - allSessions = append(allSessions, sessions...) - } - } - - // Check additional directories - for _, dir := range additionalDirs { - if info, err := os.Stat(dir); err == nil && info.IsDir() { - sessions, _ := ScanDirectory(dir) - allSessions = append(allSessions, sessions...) - } - } - - // Apply filter if provided - var filtered []*Session - for _, s := range allSessions { - if filter == nil || filter(s) { - filtered = append(filtered, s) - } - } - - // Deduplicate by session ID - seen := make(map[string]bool) - var unique []*Session - for _, s := range filtered { - if !seen[s.ID] { - seen[s.ID] = true - unique = append(unique, s) - } - } - - // Sort by start time (newest first) - sort.Slice(unique, func(i, j int) bool { - return unique[i].StartTime.After(unique[j].StartTime) - }) - - return unique, nil -} - -// GetParser returns a parser for the specified tool. +// Parser returns a parser for the specified tool. // // Parameters: // - tool: Tool identifier (e.g., "claude-code") // // Returns: // - SessionParser: The parser for the tool, or nil if not found -func GetParser(tool string) SessionParser { +func Parser(tool string) SessionParser { for _, parser := range registeredParsers { if parser.Tool() == tool { return parser diff --git a/internal/recall/parser/path.go b/internal/recall/parser/path.go new file mode 100644 index 000000000..0bfe2c257 --- /dev/null +++ b/internal/recall/parser/path.go @@ -0,0 +1 @@ +package parser diff --git a/internal/recall/parser/query.go b/internal/recall/parser/query.go new file mode 100644 index 000000000..3100d27c7 --- /dev/null +++ b/internal/recall/parser/query.go @@ -0,0 +1,76 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "os" + "path/filepath" + "sort" +) + +// findSessionsWithFilter scans common locations and additional directories +// for session files, applying an optional filter. +// +// It checks ~/.claude/projects/ (Claude Code default) and any additional +// directories provided. Results are deduplicated by session ID and sorted +// by start time (newest first). +// +// Parameters: +// - filter: Optional function to filter sessions (nil includes all) +// - additionalDirs: Optional additional directories to scan +// +// Returns: +// - []*Session: Deduplicated, filtered sessions sorted by start time +// - error: Currently always nil (errors are silently ignored) +func findSessionsWithFilter( + filter func(*Session) bool, additionalDirs ...string, +) ([]*Session, error) { + var allSessions []*Session + + // Check Claude Code default location + home, err := os.UserHomeDir() + if err == nil { + claudeDir := filepath.Join(home, ".claude", "projects") + if info, err := os.Stat(claudeDir); err == nil && info.IsDir() { + sessions, _ := ScanDirectory(claudeDir) + allSessions = append(allSessions, sessions...) + } + } + + // Check additional directories + for _, dir := range additionalDirs { + if info, err := os.Stat(dir); err == nil && info.IsDir() { + sessions, _ := ScanDirectory(dir) + allSessions = append(allSessions, sessions...) + } + } + + // Apply filter if provided + var filtered []*Session + for _, s := range allSessions { + if filter == nil || filter(s) { + filtered = append(filtered, s) + } + } + + // Deduplicate by session ID + seen := make(map[string]bool) + var unique []*Session + for _, s := range filtered { + if !seen[s.ID] { + seen[s.ID] = true + unique = append(unique, s) + } + } + + // Sort by start time (newest first) + sort.Slice(unique, func(i, j int) bool { + return unique[i].StartTime.After(unique[j].StartTime) + }) + + return unique, nil +} diff --git a/internal/task/task.go b/internal/task/task.go index 9231a81e6..03571ab1c 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -7,9 +7,11 @@ // Package task provides task item parsing and matching. // // This package handles the domain logic for task items, independent of -// their markdown representation. +// their Markdown representation. package task +import "github.com/ActiveMemory/ctx/internal/config" + // Match indices for accessing capture groups. // // Usage: @@ -21,10 +23,10 @@ package task // content := match[task.MatchContent] // } const ( - MatchFull = 0 // Full match - MatchIndent = 1 // Leading whitespace - MatchState = 2 // "x" or " " or "" - MatchContent = 3 // Task text + MatchFull = iota // Full match + MatchIndent // Leading whitespace + MatchState // "x" or " " or "" + MatchContent // Task text ) // Completed reports whether a match represents a completed task. @@ -38,7 +40,7 @@ func Completed(match []string) bool { if len(match) <= MatchState { return false } - return match[MatchState] == "x" + return match[MatchState] == config.MarkTaskComplete } // IsPending reports whether a match represents a pending task. @@ -52,7 +54,7 @@ func IsPending(match []string) bool { if len(match) <= MatchState { return false } - return match[MatchState] != "x" + return match[MatchState] != config.MarkTaskComplete } // Indent returns the leading whitespace from a match. @@ -75,7 +77,7 @@ func Indent(match []string) string { // - match: Result from ItemPattern.FindStringSubmatch // // Returns: -// - string: Task content (empty if match is invalid) +// - string: Task content (empty if the match is invalid) func Content(match []string) string { if len(match) <= MatchContent { return "" diff --git a/internal/task/task_test.go b/internal/task/task_test.go new file mode 100644 index 000000000..185ffaf35 --- /dev/null +++ b/internal/task/task_test.go @@ -0,0 +1,295 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package task + +import ( + "testing" + + "github.com/ActiveMemory/ctx/internal/config" +) + +func TestCompleted(t *testing.T) { + tests := []struct { + name string + line string + want bool + }{ + { + name: "completed task", + line: "- [x] Do something", + want: true, + }, + { + name: "pending task with space", + line: "- [ ] Do something", + want: false, + }, + { + name: "pending task empty checkbox", + line: "- [] Do something", + want: false, + }, + { + name: "indented completed task", + line: " - [x] Subtask done", + want: true, + }, + { + name: "indented pending task", + line: " - [ ] Subtask pending", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := config.RegExTask.FindStringSubmatch(tt.line) + if match == nil { + t.Fatalf("line did not match task pattern: %q", tt.line) + } + got := Completed(match) + if got != tt.want { + t.Errorf("Completed() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCompleted_InvalidMatch(t *testing.T) { + // Test with nil/short match slices + if Completed(nil) { + t.Error("Completed(nil) should return false") + } + if Completed([]string{}) { + t.Error("Completed([]) should return false") + } + if Completed([]string{"full", "indent"}) { + t.Error("Completed() with short slice should return false") + } +} + +func TestIsPending(t *testing.T) { + tests := []struct { + name string + line string + want bool + }{ + { + name: "pending task with space", + line: "- [ ] Do something", + want: true, + }, + { + name: "pending task empty checkbox", + line: "- [] Do something", + want: true, + }, + { + name: "completed task", + line: "- [x] Done task", + want: false, + }, + { + name: "indented pending", + line: " - [ ] Deep subtask", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := config.RegExTask.FindStringSubmatch(tt.line) + if match == nil { + t.Fatalf("line did not match task pattern: %q", tt.line) + } + got := IsPending(match) + if got != tt.want { + t.Errorf("IsPending() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsPending_InvalidMatch(t *testing.T) { + if IsPending(nil) { + t.Error("IsPending(nil) should return false") + } + if IsPending([]string{"full", "indent"}) { + t.Error("IsPending() with short slice should return false") + } +} + +func TestIndent(t *testing.T) { + tests := []struct { + name string + line string + want string + }{ + { + name: "no indent", + line: "- [ ] Top level task", + want: "", + }, + { + name: "two space indent", + line: " - [ ] Subtask", + want: " ", + }, + { + name: "four space indent", + line: " - [x] Deep subtask", + want: " ", + }, + { + name: "tab indent", + line: "\t- [ ] Tab indented", + want: "\t", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := config.RegExTask.FindStringSubmatch(tt.line) + if match == nil { + t.Fatalf("line did not match task pattern: %q", tt.line) + } + got := Indent(match) + if got != tt.want { + t.Errorf("Indent() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestIndent_InvalidMatch(t *testing.T) { + if Indent(nil) != "" { + t.Error("Indent(nil) should return empty string") + } + if Indent([]string{}) != "" { + t.Error("Indent([]) should return empty string") + } +} + +func TestContent(t *testing.T) { + tests := []struct { + name string + line string + want string + }{ + { + name: "simple task", + line: "- [ ] Implement feature", + want: "Implement feature", + }, + { + name: "task with tags", + line: "- [ ] Fix bug #added:2026-01-15-120000", + want: "Fix bug #added:2026-01-15-120000", + }, + { + name: "completed task", + line: "- [x] Done task #done:2026-01-15-130000", + want: "Done task #done:2026-01-15-130000", + }, + { + name: "task with special characters", + line: "- [ ] Handle `error` in foo()", + want: "Handle `error` in foo()", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := config.RegExTask.FindStringSubmatch(tt.line) + if match == nil { + t.Fatalf("line did not match task pattern: %q", tt.line) + } + got := Content(match) + if got != tt.want { + t.Errorf("Content() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestContent_InvalidMatch(t *testing.T) { + if Content(nil) != "" { + t.Error("Content(nil) should return empty string") + } + if Content([]string{"full", "indent", "state"}) != "" { + t.Error("Content() with short slice should return empty string") + } +} + +func TestIsSubTask(t *testing.T) { + tests := []struct { + name string + line string + want bool + }{ + { + name: "top level task", + line: "- [ ] Top level", + want: false, + }, + { + name: "single space - not subtask", + line: " - [ ] One space", + want: false, + }, + { + name: "two space subtask", + line: " - [ ] Subtask", + want: true, + }, + { + name: "four space subtask", + line: " - [x] Deep subtask", + want: true, + }, + { + name: "tab subtask", + line: "\t\t- [ ] Tab indented", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := config.RegExTask.FindStringSubmatch(tt.line) + if match == nil { + t.Fatalf("line did not match task pattern: %q", tt.line) + } + got := IsSubTask(match) + if got != tt.want { + t.Errorf("IsSubTask() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatchConstants(t *testing.T) { + // Verify match indices work correctly + line := " - [x] Task content here" + match := config.RegExTask.FindStringSubmatch(line) + if match == nil { + t.Fatal("line did not match task pattern") + } + + if match[MatchFull] != line { + t.Errorf("MatchFull = %q, want %q", match[MatchFull], line) + } + if match[MatchIndent] != " " { + t.Errorf("MatchIndent = %q, want %q", match[MatchIndent], " ") + } + if match[MatchState] != "x" { + t.Errorf("MatchState = %q, want %q", match[MatchState], "x") + } + if match[MatchContent] != "Task content here" { + t.Errorf("MatchContent = %q, want %q", match[MatchContent], "Task content here") + } +} diff --git a/internal/templates/AGENT_PLAYBOOK.md b/internal/tpl/AGENT_PLAYBOOK.md similarity index 100% rename from internal/templates/AGENT_PLAYBOOK.md rename to internal/tpl/AGENT_PLAYBOOK.md diff --git a/internal/templates/ARCHITECTURE.md b/internal/tpl/ARCHITECTURE.md similarity index 100% rename from internal/templates/ARCHITECTURE.md rename to internal/tpl/ARCHITECTURE.md diff --git a/internal/templates/CLAUDE.md b/internal/tpl/CLAUDE.md similarity index 100% rename from internal/templates/CLAUDE.md rename to internal/tpl/CLAUDE.md diff --git a/internal/templates/CONSTITUTION.md b/internal/tpl/CONSTITUTION.md similarity index 100% rename from internal/templates/CONSTITUTION.md rename to internal/tpl/CONSTITUTION.md diff --git a/internal/templates/CONVENTIONS.md b/internal/tpl/CONVENTIONS.md similarity index 100% rename from internal/templates/CONVENTIONS.md rename to internal/tpl/CONVENTIONS.md diff --git a/internal/templates/DECISIONS.md b/internal/tpl/DECISIONS.md similarity index 100% rename from internal/templates/DECISIONS.md rename to internal/tpl/DECISIONS.md diff --git a/internal/templates/DRIFT.md b/internal/tpl/DRIFT.md similarity index 100% rename from internal/templates/DRIFT.md rename to internal/tpl/DRIFT.md diff --git a/internal/templates/GLOSSARY.md b/internal/tpl/GLOSSARY.md similarity index 100% rename from internal/templates/GLOSSARY.md rename to internal/tpl/GLOSSARY.md diff --git a/internal/templates/IMPLEMENTATION_PLAN.md b/internal/tpl/IMPLEMENTATION_PLAN.md similarity index 100% rename from internal/templates/IMPLEMENTATION_PLAN.md rename to internal/tpl/IMPLEMENTATION_PLAN.md diff --git a/internal/templates/LEARNINGS.md b/internal/tpl/LEARNINGS.md similarity index 100% rename from internal/templates/LEARNINGS.md rename to internal/tpl/LEARNINGS.md diff --git a/internal/templates/TASKS.md b/internal/tpl/TASKS.md similarity index 100% rename from internal/templates/TASKS.md rename to internal/tpl/TASKS.md diff --git a/internal/templates/claude/commands/ctx-add-decision.md b/internal/tpl/claude/commands/ctx-add-decision.md similarity index 100% rename from internal/templates/claude/commands/ctx-add-decision.md rename to internal/tpl/claude/commands/ctx-add-decision.md diff --git a/internal/templates/claude/commands/ctx-add-learning.md b/internal/tpl/claude/commands/ctx-add-learning.md similarity index 100% rename from internal/templates/claude/commands/ctx-add-learning.md rename to internal/tpl/claude/commands/ctx-add-learning.md diff --git a/internal/templates/claude/commands/ctx-add-task.md b/internal/tpl/claude/commands/ctx-add-task.md similarity index 100% rename from internal/templates/claude/commands/ctx-add-task.md rename to internal/tpl/claude/commands/ctx-add-task.md diff --git a/internal/templates/claude/commands/ctx-agent.md b/internal/tpl/claude/commands/ctx-agent.md similarity index 100% rename from internal/templates/claude/commands/ctx-agent.md rename to internal/tpl/claude/commands/ctx-agent.md diff --git a/internal/templates/claude/commands/ctx-archive.md b/internal/tpl/claude/commands/ctx-archive.md similarity index 100% rename from internal/templates/claude/commands/ctx-archive.md rename to internal/tpl/claude/commands/ctx-archive.md diff --git a/internal/templates/claude/commands/ctx-blog-changelog.md b/internal/tpl/claude/commands/ctx-blog-changelog.md similarity index 100% rename from internal/templates/claude/commands/ctx-blog-changelog.md rename to internal/tpl/claude/commands/ctx-blog-changelog.md diff --git a/internal/templates/claude/commands/ctx-blog.md b/internal/tpl/claude/commands/ctx-blog.md similarity index 100% rename from internal/templates/claude/commands/ctx-blog.md rename to internal/tpl/claude/commands/ctx-blog.md diff --git a/internal/templates/claude/commands/ctx-journal-enrich.md b/internal/tpl/claude/commands/ctx-journal-enrich.md similarity index 100% rename from internal/templates/claude/commands/ctx-journal-enrich.md rename to internal/tpl/claude/commands/ctx-journal-enrich.md diff --git a/internal/templates/claude/commands/ctx-journal-summarize.md b/internal/tpl/claude/commands/ctx-journal-summarize.md similarity index 100% rename from internal/templates/claude/commands/ctx-journal-summarize.md rename to internal/tpl/claude/commands/ctx-journal-summarize.md diff --git a/internal/templates/claude/commands/ctx-loop.md b/internal/tpl/claude/commands/ctx-loop.md similarity index 100% rename from internal/templates/claude/commands/ctx-loop.md rename to internal/tpl/claude/commands/ctx-loop.md diff --git a/internal/templates/claude/commands/ctx-prompt-audit.md b/internal/tpl/claude/commands/ctx-prompt-audit.md similarity index 100% rename from internal/templates/claude/commands/ctx-prompt-audit.md rename to internal/tpl/claude/commands/ctx-prompt-audit.md diff --git a/internal/templates/claude/commands/ctx-recall.md b/internal/tpl/claude/commands/ctx-recall.md similarity index 100% rename from internal/templates/claude/commands/ctx-recall.md rename to internal/tpl/claude/commands/ctx-recall.md diff --git a/internal/templates/claude/commands/ctx-reflect.md b/internal/tpl/claude/commands/ctx-reflect.md similarity index 100% rename from internal/templates/claude/commands/ctx-reflect.md rename to internal/tpl/claude/commands/ctx-reflect.md diff --git a/internal/templates/claude/commands/ctx-save.md b/internal/tpl/claude/commands/ctx-save.md similarity index 100% rename from internal/templates/claude/commands/ctx-save.md rename to internal/tpl/claude/commands/ctx-save.md diff --git a/internal/templates/claude/commands/ctx-status.md b/internal/tpl/claude/commands/ctx-status.md similarity index 100% rename from internal/templates/claude/commands/ctx-status.md rename to internal/tpl/claude/commands/ctx-status.md diff --git a/internal/templates/claude/hooks/auto-save-session.sh b/internal/tpl/claude/hooks/auto-save-session.sh similarity index 100% rename from internal/templates/claude/hooks/auto-save-session.sh rename to internal/tpl/claude/hooks/auto-save-session.sh diff --git a/internal/templates/claude/hooks/block-non-path-ctx.sh b/internal/tpl/claude/hooks/block-non-path-ctx.sh similarity index 100% rename from internal/templates/claude/hooks/block-non-path-ctx.sh rename to internal/tpl/claude/hooks/block-non-path-ctx.sh diff --git a/internal/templates/claude/hooks/prompt-coach.sh b/internal/tpl/claude/hooks/prompt-coach.sh similarity index 100% rename from internal/templates/claude/hooks/prompt-coach.sh rename to internal/tpl/claude/hooks/prompt-coach.sh diff --git a/internal/templates/embed.go b/internal/tpl/embed.go similarity index 100% rename from internal/templates/embed.go rename to internal/tpl/embed.go diff --git a/internal/templates/embed_test.go b/internal/tpl/embed_test.go similarity index 100% rename from internal/templates/embed_test.go rename to internal/tpl/embed_test.go diff --git a/internal/templates/entry-templates/decision.md b/internal/tpl/entry-templates/decision.md similarity index 100% rename from internal/templates/entry-templates/decision.md rename to internal/tpl/entry-templates/decision.md diff --git a/internal/templates/entry-templates/learning.md b/internal/tpl/entry-templates/learning.md similarity index 100% rename from internal/templates/entry-templates/learning.md rename to internal/tpl/entry-templates/learning.md From ca1023d6f8bf7ec498e88eb8bbbd9f83cea59c04 Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 1 Feb 2026 08:30:28 -0800 Subject: [PATCH 02/10] refactor: use config constants throughout codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- internal/bootstrap/bootstrap.go | 2 +- internal/bootstrap/bootstrap_test.go | 99 ++++++ internal/claude/claude_test.go | 48 ++- internal/claude/cmd.go | 6 +- internal/claude/hook.go | 22 +- internal/claude/perm.go | 25 -- internal/claude/script.go | 21 +- internal/cli/add/content.go | 14 + internal/cli/add/fmt.go | 6 +- internal/cli/add/normalize.go | 17 + internal/cli/add/pos.go | 6 + internal/cli/add/run.go | 5 +- internal/cli/agent/agent.go | 9 +- internal/cli/compact/parse.go | 4 +- internal/cli/compact/process.go | 7 +- internal/cli/compact/run.go | 6 +- internal/cli/compact/sanitize.go | 14 +- internal/cli/compact/task.go | 12 +- internal/cli/compact/types.go | 3 +- internal/cli/complete/run.go | 2 +- internal/cli/decision/decision_test.go | 53 +++ internal/cli/drift/fix.go | 10 +- internal/cli/initialize/claude.go | 4 +- internal/cli/initialize/fs.go | 4 +- internal/cli/initialize/hook.go | 2 +- internal/cli/initialize/run.go | 8 +- internal/cli/initialize/tpl.go | 6 +- internal/cli/journal/journal_test.go | 301 ++++++++++++++++++ internal/cli/journal/site.go | 4 +- internal/cli/learnings/learnings_test.go | 53 +++ internal/cli/load/load.go | 2 +- internal/cli/recall/export.go | 2 +- internal/cli/recall/recall_test.go | 193 +++++++++++ internal/cli/serve/serve.go | 2 +- internal/cli/serve/serve_test.go | 52 +++ internal/cli/session/fs.go | 2 +- internal/cli/session/read.go | 4 +- internal/cli/task/path.go | 4 +- internal/cli/watch/run.go | 2 +- internal/cli/watch/session.go | 4 +- internal/cli/watch/watch_test.go | 8 +- internal/config/marker.go | 5 + internal/config/token.go | 4 + internal/context/loader.go | 2 +- internal/context/verify.go | 2 +- internal/rc/default.go | 12 + internal/rc/types.go | 22 ++ internal/recall/parser/git.go | 34 ++ internal/recall/parser/parser_test.go | 14 +- internal/recall/parser/path.go | 36 +++ .../tpl/claude/commands/ctx-add-decision.md | 3 +- .../tpl/claude/commands/ctx-prompt-audit.md | 19 +- internal/tpl/claude/commands/ctx-recall.md | 3 +- internal/tpl/claude/commands/ctx-reflect.md | 6 +- internal/tpl/embed.go | 33 +- internal/tpl/embed_test.go | 38 +-- internal/validation/validate.go | 2 +- 57 files changed, 1097 insertions(+), 186 deletions(-) create mode 100644 internal/bootstrap/bootstrap_test.go delete mode 100644 internal/claude/perm.go create mode 100644 internal/cli/decision/decision_test.go create mode 100644 internal/cli/journal/journal_test.go create mode 100644 internal/cli/learnings/learnings_test.go create mode 100644 internal/cli/recall/recall_test.go create mode 100644 internal/cli/serve/serve_test.go diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 4d51fab9a..b9e6a69d7 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -62,7 +62,7 @@ func RootCmd() *cobra.Command { cmd := &cobra.Command{ Use: "ctx", Short: "Context - persistent context for AI coding assistants", - Long: `Context (ctx) maintains persistent context files that help + Long: `ctx (Context) maintains persistent context files that help AI coding assistants understand your project's architecture, conventions, decisions, and current tasks. diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go new file mode 100644 index 000000000..f71fe9636 --- /dev/null +++ b/internal/bootstrap/bootstrap_test.go @@ -0,0 +1,99 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package bootstrap + +import ( + "testing" +) + +func TestRootCmd(t *testing.T) { + cmd := RootCmd() + + if cmd == nil { + t.Fatal("RootCmd() returned nil") + } + + if cmd.Use != "ctx" { + t.Errorf("RootCmd().Use = %q, want %q", cmd.Use, "ctx") + } + + if cmd.Short == "" { + t.Error("RootCmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("RootCmd().Long is empty") + } + + // Check global flags exist + contextDirFlag := cmd.PersistentFlags().Lookup("context-dir") + if contextDirFlag == nil { + t.Error("--context-dir flag not found") + } + + noColorFlag := cmd.PersistentFlags().Lookup("no-color") + if noColorFlag == nil { + t.Error("--no-color flag not found") + } +} + +func TestInitialize(t *testing.T) { + root := RootCmd() + cmd := Initialize(root) + + if cmd == nil { + t.Fatal("Initialize() returned nil") + } + + // Verify all expected subcommands are registered + expectedCommands := []string{ + "init", + "status", + "load", + "add", + "complete", + "agent", + "drift", + "sync", + "compact", + "decisions", + "watch", + "hook", + "learnings", + "session", + "tasks", + "loop", + "recall", + "journal", + "serve", + } + + commands := make(map[string]bool) + for _, c := range cmd.Commands() { + commands[c.Use] = true + // Handle commands with args in Use (e.g., "serve [directory]") + for _, exp := range expectedCommands { + if c.Name() == exp { + commands[exp] = true + } + } + } + + for _, exp := range expectedCommands { + if !commands[exp] { + t.Errorf("missing subcommand: %s", exp) + } + } +} + +func TestRootCmdVersion(t *testing.T) { + cmd := RootCmd() + + if cmd.Version == "" { + t.Error("RootCmd().Version is empty") + } +} diff --git a/internal/claude/claude_test.go b/internal/claude/claude_test.go index 3647b2691..d27044ed2 100644 --- a/internal/claude/claude_test.go +++ b/internal/claude/claude_test.go @@ -45,6 +45,28 @@ func TestBlockNonPathCtxScript(t *testing.T) { } } +func TestPromptCoachScript(t *testing.T) { + content, err := PromptCoachScript() + if err != nil { + t.Fatalf("PromptCoachScript() unexpected error: %v", err) + } + + if len(content) == 0 { + t.Error("PromptCoachScript() returned empty content") + } + + // Check for expected script content + script := string(content) + if !strings.Contains(script, "#!/") { + t.Error("PromptCoachScript() script missing shebang") + } + + // Check that it contains pattern detection logic + if !strings.Contains(script, "idiomatic") { + t.Error("PromptCoachScript() should contain anti-pattern detection") + } +} + func TestCommands(t *testing.T) { commands, err := Commands() if err != nil { @@ -156,29 +178,3 @@ func TestSettingsStructure(t *testing.T) { } } -func TestDefaultPermissions(t *testing.T) { - perms := DefaultPermissions() - - if len(perms) == 0 { - t.Error("DefaultPermissions should return permissions") - } - - // Check that essential ctx commands are included - expected := []string{ - "Bash(ctx status:*)", - "Bash(ctx agent:*)", - "Bash(ctx add:*)", - "Bash(ctx session:*)", - } - - permSet := make(map[string]bool) - for _, p := range perms { - permSet[p] = true - } - - for _, e := range expected { - if !permSet[e] { - t.Errorf("Missing expected permission: %s", e) - } - } -} diff --git a/internal/claude/cmd.go b/internal/claude/cmd.go index 01ae7ec5d..6f4daedaf 100644 --- a/internal/claude/cmd.go +++ b/internal/claude/cmd.go @@ -9,7 +9,7 @@ package claude import ( "fmt" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" ) // Commands returns the list of embedded command file names. @@ -22,7 +22,7 @@ import ( // - []string: Filenames of available command definitions // - error: Non-nil if the commands directory cannot be read func Commands() ([]string, error) { - names, err := templates.ListClaudeCommands() + names, err := tpl.ListClaudeCommands() if err != nil { return nil, fmt.Errorf("failed to list commands: %w", err) } @@ -38,7 +38,7 @@ func Commands() ([]string, error) { // - []byte: Raw bytes of the command definition file // - error: Non-nil if the command file does not exist or cannot be read func CommandByName(name string) ([]byte, error) { - content, err := templates.GetClaudeCommand(name) + content, err := tpl.ClaudeCommandByName(name) if err != nil { return nil, fmt.Errorf("failed to read command %s: %w", name, err) } diff --git a/internal/claude/hook.go b/internal/claude/hook.go index 237e66e42..4307e83c2 100644 --- a/internal/claude/hook.go +++ b/internal/claude/hook.go @@ -6,7 +6,11 @@ package claude -import "fmt" +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/config" +) // DefaultHooks returns the default ctx hooks configuration for // Claude Code. @@ -24,9 +28,11 @@ import "fmt" // - HookConfig: Configured hooks for PreToolUse, UserPromptSubmit, and // SessionEnd events func DefaultHooks(projectDir string) HookConfig { - hooksDir := ".claude/hooks" + hooksDir := config.DirClaudeHooks if projectDir != "" { - hooksDir = fmt.Sprintf("%s/.claude/hooks", projectDir) + hooksDir = fmt.Sprintf( + "%s/%s", projectDir, config.DirClaudeHooks, + ) } return HookConfig{ @@ -36,8 +42,10 @@ func DefaultHooks(projectDir string) HookConfig { Matcher: "Bash", Hooks: []Hook{ { - Type: "command", - Command: fmt.Sprintf("%s/block-non-path-ctx.sh", hooksDir), + Type: "command", + Command: fmt.Sprintf( + "%s/%s", hooksDir, config.FileBlockNonPathScript, + ), }, }, }, @@ -58,7 +66,7 @@ func DefaultHooks(projectDir string) HookConfig { Hooks: []Hook{ { Type: "command", - Command: fmt.Sprintf("%s/prompt-coach.sh", hooksDir), + Command: fmt.Sprintf("%s/%s", hooksDir, config.FilePromptCoach), }, }, }, @@ -68,7 +76,7 @@ func DefaultHooks(projectDir string) HookConfig { Hooks: []Hook{ { Type: "command", - Command: fmt.Sprintf("%s/auto-save-session.sh", hooksDir), + Command: fmt.Sprintf("%s/%s", hooksDir, config.FileAutoSave), }, }, }, diff --git a/internal/claude/perm.go b/internal/claude/perm.go deleted file mode 100644 index f034f8010..000000000 --- a/internal/claude/perm.go +++ /dev/null @@ -1,25 +0,0 @@ -// / Context: https://ctx.ist -// ,'`./ do you remember? -// `.,'\ -// \ Copyright 2026-present Context contributors. -// SPDX-License-Identifier: Apache-2.0 - -package claude - -// DefaultPermissions returns the default permissions for ctx commands. -// -// These permissions allow Claude Code to run ctx CLI commands without -// prompting for approval. All ctx subcommands are pre-approved. -// -// Returns: -// - []string: List of permission patterns for ctx commands -func DefaultPermissions() []string { - return []string{ - "Bash(ctx status:*)", - "Bash(ctx agent:*)", - "Bash(ctx add:*)", - "Bash(ctx session:*)", - "Bash(ctx tasks:*)", - "Bash(ctx loop:*)", - } -} diff --git a/internal/claude/script.go b/internal/claude/script.go index e1a51d760..ab7f28af5 100644 --- a/internal/claude/script.go +++ b/internal/claude/script.go @@ -9,7 +9,8 @@ package claude import ( "fmt" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/config" + "github.com/ActiveMemory/ctx/internal/tpl" ) // AutoSaveScript returns the auto-save session script content. @@ -21,9 +22,9 @@ import ( // - []byte: Raw bytes of the auto-save-session.sh script // - error: Non-nil if the embedded file cannot be read func AutoSaveScript() ([]byte, error) { - content, err := templates.ClaudeHookByFileName("auto-save-session.sh") + content, err := tpl.ClaudeHookByFileName(config.FileAutoSave) if err != nil { - return nil, fmt.Errorf("failed to read auto-save-session.sh: %w", err) + return nil, fmt.Errorf("failed to read %s: %w", config.FileAutoSave, err) } return content, nil } @@ -39,16 +40,18 @@ func AutoSaveScript() ([]byte, error) { // - []byte: Raw bytes of the block-non-path-ctx.sh script // - error: Non-nil if the embedded file cannot be read func BlockNonPathCtxScript() ([]byte, error) { - content, err := templates.ClaudeHookByFileName("block-non-path-ctx.sh") + content, err := tpl.ClaudeHookByFileName(config.FileBlockNonPathScript) if err != nil { - return nil, fmt.Errorf("failed to read block-non-path-ctx.sh: %w", err) + return nil, fmt.Errorf( + "failed to read %s: %w", config.FileBlockNonPathScript, err, + ) } return content, nil } // PromptCoachScript returns the prompt coaching hook script. // -// The script detects prompt anti-patterns (e.g., "idiomatic Go") and suggests +// The script detects prompt antipatterns (e.g., "idiomatic Go") and suggests // better alternatives (e.g., "follow project conventions"). It limits // suggestions to 3 per pattern per session to avoid annoying the user. // It is installed to .claude/hooks/ during ctx init --claude. @@ -57,9 +60,11 @@ func BlockNonPathCtxScript() ([]byte, error) { // - []byte: Raw bytes of the prompt-coach.sh script // - error: Non-nil if the embedded file cannot be read func PromptCoachScript() ([]byte, error) { - content, err := templates.ClaudeHookByFileName("prompt-coach.sh") + content, err := tpl.ClaudeHookByFileName(config.FilePromptCoach) if err != nil { - return nil, fmt.Errorf("failed to read prompt-coach.sh: %w", err) + return nil, fmt.Errorf( + "failed to read %s: %w", config.FilePromptCoach, err, + ) } return content, nil } diff --git a/internal/cli/add/content.go b/internal/cli/add/content.go index 1adbc0965..d26fdde98 100644 --- a/internal/cli/add/content.go +++ b/internal/cli/add/content.go @@ -15,6 +15,20 @@ import ( "github.com/ActiveMemory/ctx/internal/config" ) +// extractContent retrieves content from various sources for adding entries. +// +// Content is extracted in priority order: +// 1. From the file specified by --file flag +// 2. From command line arguments (after the entry type) +// 3. From stdin (if piped) +// +// Parameters: +// - args: Command arguments where args[1:] may contain inline content +// - flags: Configuration flags including fromFile path +// +// Returns: +// - string: Extracted and trimmed content +// - error: Non-nil if no content source is available or reading fails func extractContent(args []string, flags addConfig) (string, error) { if flags.fromFile != "" { // Read from the file diff --git a/internal/cli/add/fmt.go b/internal/cli/add/fmt.go index 8633cf263..fb0e590c4 100644 --- a/internal/cli/add/fmt.go +++ b/internal/cli/add/fmt.go @@ -11,7 +11,7 @@ import ( "time" ) -// FormatTask formats a task entry as a markdown checkbox item. +// FormatTask formats a task entry as a Markdown checkbox item. // // The output includes a timestamp tag for session correlation and an optional // priority tag. Format: "- [ ] content #priority:level #added:YYYY-MM-DD-HHMMSS" @@ -32,7 +32,7 @@ func FormatTask(content string, priority string) string { return fmt.Sprintf("- [ ] %s%s #added:%s\n", content, priorityTag, timestamp) } -// FormatLearning formats a learning entry as a structured markdown section. +// FormatLearning formats a learning entry as a structured Markdown section. // // The output includes a timestamped heading and complete sections for context, // lesson, and application. @@ -57,7 +57,7 @@ func FormatLearning(title, context, lesson, application string) string { `, timestamp, title, context, lesson, application) } -// FormatConvention formats a convention entry as a simple markdown list item. +// FormatConvention formats a convention entry as a simple Markdown list item. // // Format: "- content" // diff --git a/internal/cli/add/normalize.go b/internal/cli/add/normalize.go index 5d81f344d..035e60674 100644 --- a/internal/cli/add/normalize.go +++ b/internal/cli/add/normalize.go @@ -1,7 +1,24 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package add import "strings" +// normalizeTargetSection ensures a section heading has a proper Markdown +// format. +// +// If the section is empty, defaults to "## Next Up". If provided without +// a heading prefix, prepends "## " to make it a level-2 heading. +// +// Parameters: +// - section: Raw section name from user input +// +// Returns: +// - string: Normalized section heading (e.g., "## Phase 1") func normalizeTargetSection(section string) string { targetSection := section if targetSection == "" { diff --git a/internal/cli/add/pos.go b/internal/cli/add/pos.go index c7365edc5..0473b1166 100644 --- a/internal/cli/add/pos.go +++ b/internal/cli/add/pos.go @@ -1,3 +1,9 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package add // skipNewline advances pos past a newline (CRLF or LF) if present. diff --git a/internal/cli/add/run.go b/internal/cli/add/run.go index cc8fcbdd3..b7d3162b9 100644 --- a/internal/cli/add/run.go +++ b/internal/cli/add/run.go @@ -114,7 +114,7 @@ func WriteEntry(params EntryParams) error { return fmt.Errorf("unknown type %q", fType) } - filePath := filepath.Join(rc.GetContextDir(), fileName) + filePath := filepath.Join(rc.ContextDir(), fileName) // Check if the file exists if _, err := os.Stat(filePath); os.IsNotExist(err) { @@ -155,7 +155,8 @@ func WriteEntry(params EntryParams) error { return fmt.Errorf("failed to write %s: %w", filePath, err) } - // Update index for decisions and learnings (tasks/conventions don't have indexes) + // Update index for decisions and learnings + // (tasks/conventions don't have indexes) switch config.UserInputToEntry(fType) { case config.EntryDecision: indexed := index.UpdateDecisions(string(newContent)) diff --git a/internal/cli/agent/agent.go b/internal/cli/agent/agent.go index cb919eaad..1c50a32cf 100644 --- a/internal/cli/agent/agent.go +++ b/internal/cli/agent/agent.go @@ -51,15 +51,18 @@ Examples: ctx agent --format json # JSON output for programmatic use ctx agent --budget 2000 --format json`, RunE: func(cmd *cobra.Command, args []string) error { - // Use configured budget if flag not explicitly set + // Use the configured budget if the flag is not explicitly set if !cmd.Flags().Changed("budget") { - budget = rc.GetTokenBudget() + budget = rc.TokenBudget() } return runAgent(cmd, budget, format) }, } - cmd.Flags().IntVar(&budget, "budget", rc.DefaultTokenBudget, "Token budget for context packet") + cmd.Flags().IntVar( + &budget, + "budget", rc.DefaultTokenBudget, "Token budget for context packet", + ) cmd.Flags().StringVar(&format, "format", "md", "Output format: md or json") return cmd diff --git a/internal/cli/compact/parse.go b/internal/cli/compact/parse.go index 539ce694e..c9a5e5c16 100644 --- a/internal/cli/compact/parse.go +++ b/internal/cli/compact/parse.go @@ -50,7 +50,7 @@ func parseBlockAt(lines []string, startIdx int) TaskBlock { for i := startIdx + 1; i < len(lines); i++ { line := lines[i] - // Empty lines: include if followed by more indented content + // Empty lines: Include if followed by more indented content if strings.TrimSpace(line) == "" { // Look ahead to see if there's more indented content hasMoreContent := false @@ -100,7 +100,7 @@ func parseBlockAt(lines []string, startIdx int) TaskBlock { // - line: Task line that may contain #done:YYYY-MM-DD-HHMMSS // // Returns: -// - *time.Time: Parsed time, or nil if no valid timestamp found +// - *time.Time: Parsed time, or nil if no valid timestamp is found func parseDoneTimestamp(line string) *time.Time { match := config.RegExTaskDoneTimestamp.FindStringSubmatch(line) if match == nil || len(match) < 2 { diff --git a/internal/cli/compact/process.go b/internal/cli/compact/process.go index b08f764e3..06b6484bd 100644 --- a/internal/cli/compact/process.go +++ b/internal/cli/compact/process.go @@ -34,7 +34,7 @@ func preCompactAutoSave(cmd *cobra.Command) error { green := color.New(color.FgGreen).SprintFunc() // Ensure sessions directory exists - sessionsDir := filepath.Join(rc.GetContextDir(), "sessions") + sessionsDir := filepath.Join(rc.ContextDir(), "sessions") if err := os.MkdirAll(sessionsDir, 0755); err != nil { return fmt.Errorf("failed to create sessions directory: %w", err) } @@ -84,12 +84,13 @@ func buildPreCompactSession(timestamp time.Time) string { "This snapshot was automatically created before running `ctx compact`." + nl, ) sb.WriteString( - "It preserves the state of context files before any cleanup operations." + nl + nl, + "It preserves the state of context files before any cleanup operations." + + nl + nl, ) sb.WriteString(sep + nl + nl) // Read and include current TASKS.md content - tasksPath := filepath.Join(rc.GetContextDir(), config.FileTask) + tasksPath := filepath.Join(rc.ContextDir(), config.FileTask) if tasksContent, err := os.ReadFile(tasksPath); err == nil { sb.WriteString("## Tasks (Before Compact)" + nl + nl) sb.WriteString("```markdown" + nl) diff --git a/internal/cli/compact/run.go b/internal/cli/compact/run.go index fd6b6ba41..69843d1cd 100644 --- a/internal/cli/compact/run.go +++ b/internal/cli/compact/run.go @@ -43,7 +43,7 @@ func runCompact(cmd *cobra.Command, archive, noAutoSave bool) error { } // Enable archiving if configured in .contextrc - if rc.GetAutoArchive() { + if rc.AutoArchive() { archive = true } @@ -82,7 +82,9 @@ func runCompact(cmd *cobra.Command, archive, noAutoSave bool) error { cleaned, count := removeEmptySections(string(f.Content)) if count > 0 { if err := os.WriteFile(f.Path, []byte(cleaned), 0644); err == nil { - cmd.Printf("%s Removed %d empty sections from %s\n", green("✓"), count, f.Name) + cmd.Printf( + "%s Removed %d empty sections from %s\n", green("✓"), count, f.Name, + ) changes += count } } diff --git a/internal/cli/compact/sanitize.go b/internal/cli/compact/sanitize.go index 4287b2235..fff361223 100644 --- a/internal/cli/compact/sanitize.go +++ b/internal/cli/compact/sanitize.go @@ -6,7 +6,11 @@ package compact -import "strings" +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config" +) // removeEmptySections removes Markdown sections that contain no content. // @@ -20,7 +24,7 @@ import "strings" // - string: Content with empty sections removed // - int: Number of sections removed func removeEmptySections(content string) (string, int) { - lines := strings.Split(content, "\n") + lines := strings.Split(content, config.NewlineLF) var result []string removed := 0 @@ -41,8 +45,8 @@ func removeEmptySections(content string) (string, int) { // Check if we hit another section or end of the file if i >= len(lines) || - strings.HasPrefix(lines[i], "## ") || - strings.HasPrefix(lines[i], "# ") { + strings.HasPrefix(lines[i], config.HeadingLevelTwoStart) || + strings.HasPrefix(lines[i], config.HeadingLevelOneStart) { // Section is empty, skip it removed++ continue @@ -72,5 +76,5 @@ func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s } - return s[:maxLen-3] + "..." + return s[:maxLen-3] + config.Ellipsis } diff --git a/internal/cli/compact/task.go b/internal/cli/compact/task.go index af4837673..e90d7fe42 100644 --- a/internal/cli/compact/task.go +++ b/internal/cli/compact/task.go @@ -25,7 +25,7 @@ import ( // // Scans TASKS.md for checked items ("- [x]") outside the Completed section, // including their nested content (indented lines below the task). -// Only moves tasks where all nested sub-tasks are also complete. +// This only moves tasks where all nested subtasks are also complete. // Optionally archives them to .context/archive/. // // Parameters: @@ -84,13 +84,13 @@ func compactTasks( // Remove archivable blocks from lines newLines := RemoveBlocksFromLines(lines, archivableBlocks) - // Add blocks to Completed section + // Add blocks to the Completed section for i, line := range newLines { if strings.HasPrefix(line, "## Completed") { // Find the next line that's either empty or another section insertIdx := i + 1 for insertIdx < len(newLines) && newLines[insertIdx] != "" && - !strings.HasPrefix(newLines[insertIdx], "## ") { + !strings.HasPrefix(newLines[insertIdx], config.HeadingLevelTwoStart) { insertIdx++ } @@ -112,7 +112,7 @@ func compactTasks( // Archive if requested if archive && len(archivableBlocks) > 0 { // Filter to only tasks old enough to archive - archiveDays := rc.GetArchiveAfterDays() + archiveDays := rc.ArchiveAfterDays() var blocksToArchive []TaskBlock for _, block := range archivableBlocks { if block.OlderThan(archiveDays) { @@ -121,7 +121,7 @@ func compactTasks( } if len(blocksToArchive) > 0 { - archiveDir := filepath.Join(rc.GetContextDir(), "archive") + archiveDir := filepath.Join(rc.ContextDir(), "archive") if err := os.MkdirAll(archiveDir, 0755); err == nil { archiveFile := filepath.Join( archiveDir, @@ -146,7 +146,7 @@ func compactTasks( } // Write back - newContent := strings.Join(newLines, "\n") + newContent := strings.Join(newLines, config.NewlineLF) if newContent != content { if err := os.WriteFile( tasksFile.Path, []byte(newContent), 0644, diff --git a/internal/cli/compact/types.go b/internal/cli/compact/types.go index 478796cde..ff1281dd6 100644 --- a/internal/cli/compact/types.go +++ b/internal/cli/compact/types.go @@ -16,7 +16,8 @@ import "time" // - EndIndex: Index of last line (exclusive) // - IsCompleted: The parent task is checked // - IsArchivable: Completed and no unchecked children -// - DoneTime: When the task was marked done (from #done: timestamp), nil if not present +// - DoneTime: When the task was marked done (from #done: timestamp), +// nil if not present type TaskBlock struct { Lines []string StartIndex int diff --git a/internal/cli/complete/run.go b/internal/cli/complete/run.go index b26d75b4b..e6d91a1d1 100644 --- a/internal/cli/complete/run.go +++ b/internal/cli/complete/run.go @@ -36,7 +36,7 @@ import ( func runComplete(cmd *cobra.Command, args []string) error { query := args[0] - filePath := filepath.Join(rc.GetContextDir(), config.FileTask) + filePath := filepath.Join(rc.ContextDir(), config.FileTask) // Check if the file exists if _, err := os.Stat(filePath); os.IsNotExist(err) { diff --git a/internal/cli/decision/decision_test.go b/internal/cli/decision/decision_test.go new file mode 100644 index 000000000..84df63e7b --- /dev/null +++ b/internal/cli/decision/decision_test.go @@ -0,0 +1,53 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +import ( + "testing" +) + +func TestCmd(t *testing.T) { + cmd := Cmd() + + if cmd == nil { + t.Fatal("Cmd() returned nil") + } + + if cmd.Use != "decisions" { + t.Errorf("Cmd().Use = %q, want %q", cmd.Use, "decisions") + } + + if cmd.Short == "" { + t.Error("Cmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("Cmd().Long is empty") + } +} + +func TestCmd_HasReindexSubcommand(t *testing.T) { + cmd := Cmd() + + var found bool + for _, sub := range cmd.Commands() { + if sub.Use == "reindex" { + found = true + if sub.Short == "" { + t.Error("reindex subcommand has empty Short description") + } + if sub.RunE == nil { + t.Error("reindex subcommand has no RunE function") + } + break + } + } + + if !found { + t.Error("reindex subcommand not found") + } +} diff --git a/internal/cli/drift/fix.go b/internal/cli/drift/fix.go index 946793c13..06e183113 100644 --- a/internal/cli/drift/fix.go +++ b/internal/cli/drift/fix.go @@ -21,7 +21,7 @@ import ( "github.com/ActiveMemory/ctx/internal/drift" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/task" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" ) // fixResult tracks fixes applied during drift fix. @@ -156,7 +156,7 @@ func fixStaleness(cmd *cobra.Command, ctx *context.Context) error { } // Create an archive directory - archiveDir := filepath.Join(rc.GetContextDir(), "archive") + archiveDir := filepath.Join(rc.ContextDir(), "archive") if err := os.MkdirAll(archiveDir, 0755); err != nil { return fmt.Errorf("failed to create archive directory: %w", err) } @@ -205,15 +205,15 @@ func fixStaleness(cmd *cobra.Command, ctx *context.Context) error { // Returns: // - error: Non-nil if the template is not found or file write fails func fixMissingFile(filename string) error { - content, err := templates.GetTemplate(filename) + content, err := tpl.Template(filename) if err != nil { return fmt.Errorf("no template available for %s: %w", filename, err) } - targetPath := filepath.Join(rc.GetContextDir(), filename) + targetPath := filepath.Join(rc.ContextDir(), filename) // Ensure .context/ directory exists - if err := os.MkdirAll(rc.GetContextDir(), 0755); err != nil { + if err := os.MkdirAll(rc.ContextDir(), 0755); err != nil { return fmt.Errorf("failed to create .context/: %w", err) } diff --git a/internal/cli/initialize/claude.go b/internal/cli/initialize/claude.go index 47159eed4..58195b81e 100644 --- a/internal/cli/initialize/claude.go +++ b/internal/cli/initialize/claude.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" ) // handleClaudeMd creates or merges CLAUDE.md in the project root. @@ -41,7 +41,7 @@ func handleClaudeMd(cmd *cobra.Command, force, autoMerge bool) error { yellow := color.New(color.FgYellow).SprintFunc() // Get template content - templateContent, err := templates.GetTemplate("CLAUDE.md") + templateContent, err := tpl.Template("CLAUDE.md") if err != nil { return fmt.Errorf("failed to read CLAUDE.md template: %w", err) } diff --git a/internal/cli/initialize/fs.go b/internal/cli/initialize/fs.go index 63eee45ba..54b505d8e 100644 --- a/internal/cli/initialize/fs.go +++ b/internal/cli/initialize/fs.go @@ -13,7 +13,7 @@ import ( "time" "github.com/ActiveMemory/ctx/internal/config" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -174,7 +174,7 @@ func createImplementationPlan(cmd *cobra.Command, force bool) error { } // Get template content - content, err := templates.GetTemplate(planFileName) + content, err := tpl.Template(planFileName) if err != nil { return fmt.Errorf("failed to read template: %w", err) } diff --git a/internal/cli/initialize/hook.go b/internal/cli/initialize/hook.go index 762c8b036..bc1952ad0 100644 --- a/internal/cli/initialize/hook.go +++ b/internal/cli/initialize/hook.go @@ -149,7 +149,7 @@ func mergeSettingsHooks( // Get our defaults defaultHooks := claude.DefaultHooks(projectDir) - defaultPerms := claude.DefaultPermissions() + defaultPerms := config.DefaultClaudePermissions // Check if hooks already exist hasPreToolUse := len(settings.Hooks.PreToolUse) > 0 diff --git a/internal/cli/initialize/run.go b/internal/cli/initialize/run.go index facf55d84..5df5c7b38 100644 --- a/internal/cli/initialize/run.go +++ b/internal/cli/initialize/run.go @@ -18,7 +18,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config" "github.com/ActiveMemory/ctx/internal/rc" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" ) // runInit executes the init command logic. @@ -40,7 +40,7 @@ func runInit(cmd *cobra.Command, force, minimal, merge bool) error { return err } - contextDir := rc.GetContextDir() + contextDir := rc.ContextDir() // Check if .context/ already exists if _, err := os.Stat(contextDir); err == nil { @@ -70,7 +70,7 @@ func runInit(cmd *cobra.Command, force, minimal, merge bool) error { if minimal { templatesToCreate = config.RequiredFiles } else { - allTemplates, err := templates.ListTemplates() + allTemplates, err := tpl.List() if err != nil { return fmt.Errorf("failed to list templates: %w", err) } @@ -95,7 +95,7 @@ func runInit(cmd *cobra.Command, force, minimal, merge bool) error { continue } - content, err := templates.GetTemplate(name) + content, err := tpl.Template(name) if err != nil { return fmt.Errorf("failed to read template %s: %w", name, err) } diff --git a/internal/cli/initialize/tpl.go b/internal/cli/initialize/tpl.go index d70033be5..5f3d1aa59 100644 --- a/internal/cli/initialize/tpl.go +++ b/internal/cli/initialize/tpl.go @@ -14,7 +14,7 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/ActiveMemory/ctx/internal/templates" + "github.com/ActiveMemory/ctx/internal/tpl" ) // createEntryTemplates creates .context/templates/ with entry templates for @@ -42,7 +42,7 @@ func createEntryTemplates( } // Get list of entry templates - entryTemplates, err := templates.ListEntryTemplates() + entryTemplates, err := tpl.ListEntry() if err != nil { return fmt.Errorf("failed to list entry templates: %w", err) } @@ -56,7 +56,7 @@ func createEntryTemplates( continue } - content, err := templates.GetEntryTemplate(name) + content, err := tpl.Entry(name) if err != nil { return fmt.Errorf("failed to read entry template %s: %w", name, err) } diff --git a/internal/cli/journal/journal_test.go b/internal/cli/journal/journal_test.go new file mode 100644 index 000000000..be3dce1c9 --- /dev/null +++ b/internal/cli/journal/journal_test.go @@ -0,0 +1,301 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package journal + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCmd(t *testing.T) { + cmd := Cmd() + + if cmd == nil { + t.Fatal("Cmd() returned nil") + } + + if cmd.Use != "journal" { + t.Errorf("Cmd().Use = %q, want %q", cmd.Use, "journal") + } + + if cmd.Short == "" { + t.Error("Cmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("Cmd().Long is empty") + } +} + +func TestCmd_HasSiteSubcommand(t *testing.T) { + cmd := Cmd() + + var found bool + for _, sub := range cmd.Commands() { + if sub.Use == "site" { + found = true + if sub.Short == "" { + t.Error("site subcommand has empty Short description") + } + if sub.RunE == nil { + t.Error("site subcommand has no RunE function") + } + + // Check flags + outputFlag := sub.Flags().Lookup("output") + if outputFlag == nil { + t.Error("site subcommand missing --output flag") + } + + buildFlag := sub.Flags().Lookup("build") + if buildFlag == nil { + t.Error("site subcommand missing --build flag") + } + + serveFlag := sub.Flags().Lookup("serve") + if serveFlag == nil { + t.Error("site subcommand missing --serve flag") + } + + break + } + } + + if !found { + t.Error("site subcommand not found") + } +} + +func TestFormatSize(t *testing.T) { + tests := []struct { + bytes int64 + want string + }{ + {0, "0B"}, + {100, "100B"}, + {1023, "1023B"}, + {1024, "1.0KB"}, + {1536, "1.5KB"}, + {10240, "10.0KB"}, + {1048576, "1.0MB"}, + {1572864, "1.5MB"}, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + got := formatSize(tt.bytes) + if got != tt.want { + t.Errorf("formatSize(%d) = %q, want %q", tt.bytes, got, tt.want) + } + }) + } +} + +func TestParseJournalEntry(t *testing.T) { + // Create a temp file with journal content + tmpDir := t.TempDir() + filename := "2026-01-21-test-slug-abc12345.md" + content := `# Test Session + +**Time**: 14:30:00 +**Project**: my-project + +Some content here. +` + path := filepath.Join(tmpDir, filename) + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + entry := parseJournalEntry(path, filename) + + if entry.Filename != filename { + t.Errorf("Filename = %q, want %q", entry.Filename, filename) + } + + if entry.Date != "2026-01-21" { + t.Errorf("Date = %q, want %q", entry.Date, "2026-01-21") + } + + if entry.Title != "Test Session" { + t.Errorf("Title = %q, want %q", entry.Title, "Test Session") + } + + if entry.Time != "14:30:00" { + t.Errorf("Time = %q, want %q", entry.Time, "14:30:00") + } + + if entry.Project != "my-project" { + t.Errorf("Project = %q, want %q", entry.Project, "my-project") + } + + if entry.Size != int64(len(content)) { + t.Errorf("Size = %d, want %d", entry.Size, len(content)) + } +} + +func TestParseJournalEntry_SuggestionMode(t *testing.T) { + tmpDir := t.TempDir() + filename := "2026-01-21-suggestion-abc12345.md" + content := `# Suggestion + +[SUGGESTION MODE: some suggestion] + +Content here. +` + path := filepath.Join(tmpDir, filename) + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + entry := parseJournalEntry(path, filename) + + if !entry.IsSuggestion { + t.Error("IsSuggestion should be true for suggestion mode sessions") + } +} + +func TestParseJournalEntry_MissingFile(t *testing.T) { + entry := parseJournalEntry("/nonexistent/path.md", "2026-01-21-test.md") + + // Should use filename as title fallback + if entry.Title != "2026-01-21-test" { + t.Errorf("Title = %q, want %q", entry.Title, "2026-01-21-test") + } +} + +func TestGenerateIndex(t *testing.T) { + entries := []journalEntry{ + { + Filename: "2026-01-21-session-one-abc12345.md", + Title: "Session One", + Date: "2026-01-21", + Time: "14:30:00", + Project: "project-a", + Size: 1024, + }, + { + Filename: "2026-01-20-session-two-def67890.md", + Title: "Session Two", + Date: "2026-01-20", + Time: "10:00:00", + Project: "project-b", + Size: 2048, + }, + { + Filename: "2026-01-19-suggestion-ghi11111.md", + Title: "Suggestion", + Date: "2026-01-19", + Time: "09:00:00", + IsSuggestion: true, + Size: 512, + }, + } + + index := generateIndex(entries) + + // Should have header + if !strings.Contains(index, "# Session Journal") { + t.Error("index missing header") + } + + // Should have session count + if !strings.Contains(index, "**Sessions**: 2") { + t.Error("index missing session count") + } + + // Should have suggestions count + if !strings.Contains(index, "**Suggestions**: 1") { + t.Error("index missing suggestions count") + } + + // Should have month headers + if !strings.Contains(index, "## 2026-01") { + t.Error("index missing month header") + } + + // Should have entry links + if !strings.Contains(index, "[Session One]") { + t.Error("index missing session one link") + } + + // Should have suggestions section + if !strings.Contains(index, "## Suggestions") { + t.Error("index missing suggestions section") + } +} + +func TestFormatIndexEntry(t *testing.T) { + entry := journalEntry{ + Filename: "2026-01-21-test-abc12345.md", + Title: "Test Session", + Date: "2026-01-21", + Time: "14:30:00", + Project: "my-project", + Size: 1536, + } + + result := formatIndexEntry(entry, "\n") + + // Should have time prefix + if !strings.Contains(result, "14:30") { + t.Error("entry missing time prefix") + } + + // Should have title link + if !strings.Contains(result, "[Test Session]") { + t.Error("entry missing title") + } + + // Should have link to md file + if !strings.Contains(result, "(2026-01-21-test-abc12345.md)") { + t.Error("entry missing link") + } + + // Should have project + if !strings.Contains(result, "(my-project)") { + t.Error("entry missing project") + } + + // Should have size + if !strings.Contains(result, "1.5KB") { + t.Error("entry missing size") + } +} + +func TestGenerateZensicalToml(t *testing.T) { + entries := []journalEntry{ + { + Filename: "2026-01-21-test.md", + Title: "Test Session", + }, + } + + toml := generateZensicalToml(entries) + + // Should have project section + if !strings.Contains(toml, "[project]") { + t.Error("toml missing [project] section") + } + + // Should have site name + if !strings.Contains(toml, `site_name = "Session Journal"`) { + t.Error("toml missing site_name") + } + + // Should have nav + if !strings.Contains(toml, "nav = [") { + t.Error("toml missing nav") + } + + // Should have theme section + if !strings.Contains(toml, "[project.theme]") { + t.Error("toml missing theme section") + } +} diff --git a/internal/cli/journal/site.go b/internal/cli/journal/site.go index 2ec674e92..6e7bdaaa9 100644 --- a/internal/cli/journal/site.go +++ b/internal/cli/journal/site.go @@ -55,7 +55,7 @@ Examples: }, } - defaultOutput := filepath.Join(rc.GetContextDir(), "journal-site") + defaultOutput := filepath.Join(rc.ContextDir(), "journal-site") cmd.Flags().StringVarP(&output, "output", "o", defaultOutput, "Output directory for site") cmd.Flags().BoolVar(&build, "build", false, "Run zensical build after generating") cmd.Flags().BoolVar(&serve, "serve", false, "Run zensical serve after generating") @@ -89,7 +89,7 @@ type journalEntry struct { // Returns: // - error: Non-nil if generation fails func runJournalSite(cmd *cobra.Command, output string, build, serve bool) error { - journalDir := filepath.Join(rc.GetContextDir(), "journal") + journalDir := filepath.Join(rc.ContextDir(), "journal") // Check if journal directory exists if _, err := os.Stat(journalDir); os.IsNotExist(err) { diff --git a/internal/cli/learnings/learnings_test.go b/internal/cli/learnings/learnings_test.go new file mode 100644 index 000000000..f0cc5d328 --- /dev/null +++ b/internal/cli/learnings/learnings_test.go @@ -0,0 +1,53 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package learnings + +import ( + "testing" +) + +func TestCmd(t *testing.T) { + cmd := Cmd() + + if cmd == nil { + t.Fatal("Cmd() returned nil") + } + + if cmd.Use != "learnings" { + t.Errorf("Cmd().Use = %q, want %q", cmd.Use, "learnings") + } + + if cmd.Short == "" { + t.Error("Cmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("Cmd().Long is empty") + } +} + +func TestCmd_HasReindexSubcommand(t *testing.T) { + cmd := Cmd() + + var found bool + for _, sub := range cmd.Commands() { + if sub.Use == "reindex" { + found = true + if sub.Short == "" { + t.Error("reindex subcommand has empty Short description") + } + if sub.RunE == nil { + t.Error("reindex subcommand has no RunE function") + } + break + } + } + + if !found { + t.Error("reindex subcommand not found") + } +} diff --git a/internal/cli/load/load.go b/internal/cli/load/load.go index 06e8c7fe4..ce6b28d7a 100644 --- a/internal/cli/load/load.go +++ b/internal/cli/load/load.go @@ -51,7 +51,7 @@ Use --budget to limit output to a specific token count (default from .contextrc RunE: func(cmd *cobra.Command, args []string) error { // Use configured budget if flag not explicitly set if !cmd.Flags().Changed("budget") { - budget = rc.GetTokenBudget() + budget = rc.TokenBudget() } return runLoad(cmd, budget, raw) }, diff --git a/internal/cli/recall/export.go b/internal/cli/recall/export.go index d6d8b866d..7ecae4390 100644 --- a/internal/cli/recall/export.go +++ b/internal/cli/recall/export.go @@ -135,7 +135,7 @@ func runRecallExport(cmd *cobra.Command, args []string, all, allProjects, force } // Ensure journal directory exists - journalDir := filepath.Join(rc.GetContextDir(), "journal") + journalDir := filepath.Join(rc.ContextDir(), "journal") if err := os.MkdirAll(journalDir, 0755); err != nil { return fmt.Errorf("failed to create journal directory: %w", err) } diff --git a/internal/cli/recall/recall_test.go b/internal/cli/recall/recall_test.go new file mode 100644 index 000000000..65ac35e2d --- /dev/null +++ b/internal/cli/recall/recall_test.go @@ -0,0 +1,193 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package recall + +import ( + "strings" + "testing" + "time" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/recall/parser" +) + +func TestCmd(t *testing.T) { + cmd := Cmd() + + if cmd == nil { + t.Fatal("Cmd() returned nil") + } + + if cmd.Use != "recall" { + t.Errorf("Cmd().Use = %q, want %q", cmd.Use, "recall") + } + + if cmd.Short == "" { + t.Error("Cmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("Cmd().Long is empty") + } +} + +func TestCmd_HasSubcommands(t *testing.T) { + cmd := Cmd() + + expectedSubs := []string{"list", "show", "export"} + subs := make(map[string]bool) + + for _, sub := range cmd.Commands() { + subs[sub.Name()] = true + } + + for _, exp := range expectedSubs { + if !subs[exp] { + t.Errorf("missing subcommand: %s", exp) + } + } +} + +func TestRecallListCmd_Flags(t *testing.T) { + cmd := Cmd() + + var listCmd *cobra.Command + for _, sub := range cmd.Commands() { + if sub.Name() == "list" { + listCmd = sub + break + } + } + + if listCmd == nil { + t.Fatal("list subcommand not found") + } + + // Check flags + flags := []string{"limit", "project", "tool", "all-projects"} + for _, f := range flags { + if listCmd.Flags().Lookup(f) == nil { + t.Errorf("list subcommand missing --%s flag", f) + } + } +} + +func TestRecallShowCmd_Flags(t *testing.T) { + cmd := Cmd() + + var showCmd *cobra.Command + for _, sub := range cmd.Commands() { + if sub.Name() == "show" { + showCmd = sub + break + } + } + + if showCmd == nil { + t.Fatal("show subcommand not found") + } + + // Check flags + flags := []string{"latest", "full", "all-projects"} + for _, f := range flags { + if showCmd.Flags().Lookup(f) == nil { + t.Errorf("show subcommand missing --%s flag", f) + } + } +} + +func TestRecallExportCmd_Flags(t *testing.T) { + cmd := Cmd() + + var exportCmd *cobra.Command + for _, sub := range cmd.Commands() { + if sub.Name() == "export" { + exportCmd = sub + break + } + } + + if exportCmd == nil { + t.Fatal("export subcommand not found") + } + + // Check flags + flags := []string{"all", "all-projects", "force"} + for _, f := range flags { + if exportCmd.Flags().Lookup(f) == nil { + t.Errorf("export subcommand missing --%s flag", f) + } + } +} + +func TestFormatJournalFilename(t *testing.T) { + session := &parser.Session{ + ID: "abc12345-6789-0123-4567-890123456789", + Slug: "gleaming-wobbling-sutherland", + StartTime: time.Date(2026, 1, 21, 14, 30, 0, 0, time.UTC), + } + + filename := formatJournalFilename(session) + + // Should contain slug + if !strings.Contains(filename, "gleaming-wobbling-sutherland") { + t.Errorf("filename missing slug: %q", filename) + } + + // Should contain short ID (first 8 chars) + if !strings.Contains(filename, "abc12345") { + t.Errorf("filename missing short ID: %q", filename) + } + + // Should end with .md + if !strings.HasSuffix(filename, ".md") { + t.Errorf("filename missing .md extension: %q", filename) + } +} + +func TestIsEmptyMessage(t *testing.T) { + tests := []struct { + name string + msg parser.Message + want bool + }{ + { + name: "empty message", + msg: parser.Message{}, + want: true, + }, + { + name: "message with text", + msg: parser.Message{Text: "Hello"}, + want: false, + }, + { + name: "message with tool uses", + msg: parser.Message{ + ToolUses: []parser.ToolUse{{Name: "Bash"}}, + }, + want: false, + }, + { + name: "message with tool results", + msg: parser.Message{ + ToolResults: []parser.ToolResult{{Content: "output"}}, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isEmptyMessage(tt.msg) + if got != tt.want { + t.Errorf("isEmptyMessage() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/cli/serve/serve.go b/internal/cli/serve/serve.go index 778e77d24..5b866f3fd 100644 --- a/internal/cli/serve/serve.go +++ b/internal/cli/serve/serve.go @@ -62,7 +62,7 @@ func runServe(cmd *cobra.Command, args []string) error { dir = args[0] } else { // Default: journal site - dir = filepath.Join(rc.GetContextDir(), "journal-site") + dir = filepath.Join(rc.ContextDir(), "journal-site") } // Verify directory exists diff --git a/internal/cli/serve/serve_test.go b/internal/cli/serve/serve_test.go new file mode 100644 index 000000000..52d5a6519 --- /dev/null +++ b/internal/cli/serve/serve_test.go @@ -0,0 +1,52 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package serve + +import ( + "testing" +) + +func TestCmd(t *testing.T) { + cmd := Cmd() + + if cmd == nil { + t.Fatal("Cmd() returned nil") + } + + if cmd.Use != "serve [directory]" { + t.Errorf("Cmd().Use = %q, want %q", cmd.Use, "serve [directory]") + } + + if cmd.Short == "" { + t.Error("Cmd().Short is empty") + } + + if cmd.Long == "" { + t.Error("Cmd().Long is empty") + } + + if cmd.RunE == nil { + t.Error("Cmd().RunE is nil") + } +} + +func TestCmd_AcceptsArgs(t *testing.T) { + cmd := Cmd() + + // Should accept 0 or 1 args + if err := cmd.Args(cmd, []string{}); err != nil { + t.Errorf("should accept 0 args: %v", err) + } + + if err := cmd.Args(cmd, []string{"./docs"}); err != nil { + t.Errorf("should accept 1 arg: %v", err) + } + + if err := cmd.Args(cmd, []string{"a", "b"}); err == nil { + t.Error("should reject 2 args") + } +} diff --git a/internal/cli/session/fs.go b/internal/cli/session/fs.go index c765c23ae..1a5479268 100644 --- a/internal/cli/session/fs.go +++ b/internal/cli/session/fs.go @@ -18,5 +18,5 @@ import ( // Returns: // - string: Full path to .context/sessions/ func sessionsDirPath() string { - return filepath.Join(rc.GetContextDir(), config.DirSessions) + return filepath.Join(rc.ContextDir(), config.DirSessions) } diff --git a/internal/cli/session/read.go b/internal/cli/session/read.go index 9fb10fd86..558f7c0e1 100644 --- a/internal/cli/session/read.go +++ b/internal/cli/session/read.go @@ -25,7 +25,7 @@ import ( // - string: File content // - error: Non-nil if the file cannot be read func readContextFile(fileName string) (string, error) { - filePath := filepath.Join(rc.GetContextDir(), fileName) + filePath := filepath.Join(rc.ContextDir(), fileName) content, err := os.ReadFile(filePath) if err != nil { return "", err @@ -52,7 +52,7 @@ func readContextFile(fileName string) (string, error) { func readContextSection( filename, startHeader, endHeader string, ) (string, error) { - filePath := filepath.Join(rc.GetContextDir(), filename) + filePath := filepath.Join(rc.ContextDir(), filename) content, err := os.ReadFile(filePath) if err != nil { return "", err diff --git a/internal/cli/task/path.go b/internal/cli/task/path.go index 6861ae075..6f2c9b30d 100644 --- a/internal/cli/task/path.go +++ b/internal/cli/task/path.go @@ -12,7 +12,7 @@ import ( // Returns: // - string: Full path to .context/TASKS.md func tasksFilePath() string { - return filepath.Join(rc.GetContextDir(), config.FileTask) + return filepath.Join(rc.ContextDir(), config.FileTask) } // archiveDirPath returns the path to the archive directory. @@ -20,5 +20,5 @@ func tasksFilePath() string { // Returns: // - string: Full path to .context/archive/ func archiveDirPath() string { - return filepath.Join(rc.GetContextDir(), config.DirArchive) + return filepath.Join(rc.ContextDir(), config.DirArchive) } diff --git a/internal/cli/watch/run.go b/internal/cli/watch/run.go index 7f5b9a7aa..523af45bd 100644 --- a/internal/cli/watch/run.go +++ b/internal/cli/watch/run.go @@ -124,7 +124,7 @@ func runCompleteSilent(args []string) error { } query := args[0] - filePath := filepath.Join(rc.GetContextDir(), config.FileTask) + filePath := filepath.Join(rc.ContextDir(), config.FileTask) nl := config.NewlineLF content, err := os.ReadFile(filePath) diff --git a/internal/cli/watch/session.go b/internal/cli/watch/session.go index 5dd3443da..a802562c5 100644 --- a/internal/cli/watch/session.go +++ b/internal/cli/watch/session.go @@ -29,7 +29,7 @@ import ( // Returns: // - error: Non-nil if directory creation or file write fails func watchAutoSaveSession(updates []ContextUpdate) error { - sessionsDir := filepath.Join(rc.GetContextDir(), config.DirSessions) + sessionsDir := filepath.Join(rc.ContextDir(), config.DirSessions) if err := os.MkdirAll(sessionsDir, 0755); err != nil { return fmt.Errorf("failed to create sessions directory: %w", err) } @@ -100,7 +100,7 @@ func buildWatchSession(timestamp time.Time, updates []ContextUpdate) string { sb.WriteString(config.Separator + nl + nl) sb.WriteString("## Context Snapshot" + nl + nl) - tasksPath := filepath.Join(rc.GetContextDir(), config.FileTask) + tasksPath := filepath.Join(rc.ContextDir(), config.FileTask) if tasksContent, err := os.ReadFile(tasksPath); err == nil { sb.WriteString("### Current Tasks" + nl + nl) sb.WriteString("```markdown" + nl) diff --git a/internal/cli/watch/watch_test.go b/internal/cli/watch/watch_test.go index 02b4190a7..d8ef3f6f8 100644 --- a/internal/cli/watch/watch_test.go +++ b/internal/cli/watch/watch_test.go @@ -116,7 +116,7 @@ func TestApplyUpdate(t *testing.T) { } // Verify content was added - filePath := filepath.Join(rc.GetContextDir(), tt.checkFile) + filePath := filepath.Join(rc.ContextDir(), tt.checkFile) content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("failed to read %s: %v", tt.checkFile, err) @@ -150,7 +150,7 @@ func TestApplyCompleteUpdate(t *testing.T) { } // Add a task to complete - tasksPath := filepath.Join(rc.GetContextDir(), config.FileTask) + tasksPath := filepath.Join(rc.ContextDir(), config.FileTask) tasksContent := `# Tasks ## Next Up @@ -263,7 +263,7 @@ More output } // Verify task was written - tasksPath := filepath.Join(rc.GetContextDir(), config.FileTask) + tasksPath := filepath.Join(rc.ContextDir(), config.FileTask) content, err := os.ReadFile(tasksPath) if err != nil { t.Fatalf("failed to read tasks: %v", err) @@ -314,7 +314,7 @@ More output } // Verify learning was written with structured fields - learningsPath := filepath.Join(rc.GetContextDir(), config.FileLearning) + learningsPath := filepath.Join(rc.ContextDir(), config.FileLearning) content, err := os.ReadFile(learningsPath) if err != nil { t.Fatalf("failed to read learnings: %v", err) diff --git a/internal/config/marker.go b/internal/config/marker.go index 0d5acb292..ae48d07bb 100644 --- a/internal/config/marker.go +++ b/internal/config/marker.go @@ -37,3 +37,8 @@ const ( // PrefixTaskDone is the prefix for a checked (completed) task item. PrefixTaskDone = "- [x]" ) + +const ( + // MarkTaskComplete is the unchecked task marker. + MarkTaskComplete = "x" +) diff --git a/internal/config/token.go b/internal/config/token.go index 13eeea84c..6cad20217 100644 --- a/internal/config/token.go +++ b/internal/config/token.go @@ -17,4 +17,8 @@ const ( Separator = "---" // Ellipsis is a Markdown ellipsis. Ellipsis = "..." + // HeadingLevelOneStart is the Markdown heading for the first section. + HeadingLevelOneStart = "# " + // HeadingLevelTwoStart is the Markdown heading for subsequent sections. + HeadingLevelTwoStart = "## " ) diff --git a/internal/context/loader.go b/internal/context/loader.go index 77e196133..94145c662 100644 --- a/internal/context/loader.go +++ b/internal/context/loader.go @@ -27,7 +27,7 @@ import ( // - error: NotFoundError if directory doesn't exist, or other IO errors func Load(dir string) (*Context, error) { if dir == "" { - dir = rc.GetContextDir() + dir = rc.ContextDir() } // Check if the directory exists diff --git a/internal/context/verify.go b/internal/context/verify.go index 6c4cb23b2..caa7cc35b 100644 --- a/internal/context/verify.go +++ b/internal/context/verify.go @@ -23,7 +23,7 @@ import ( // - bool: True if the directory exists and is a directory func Exists(dir string) bool { if dir == "" { - dir = rc.GetContextDir() + dir = rc.ContextDir() } info, err := os.Stat(dir) return err == nil && info.IsDir() diff --git a/internal/rc/default.go b/internal/rc/default.go index f56055459..0179518fe 100644 --- a/internal/rc/default.go +++ b/internal/rc/default.go @@ -1 +1,13 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package rc + +// DefaultTokenBudget is the default token budget when not configured. +const DefaultTokenBudget = 8000 + +// DefaultArchiveAfterDays is the default days before archiving. +const DefaultArchiveAfterDays = 7 diff --git a/internal/rc/types.go b/internal/rc/types.go index f56055459..e05ffaa16 100644 --- a/internal/rc/types.go +++ b/internal/rc/types.go @@ -1 +1,23 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package rc + +// CtxRC represents the configuration from the .contextrc file. +// +// Fields: +// - ContextDir: Name of the context directory (default ".context") +// - TokenBudget: Default token budget for context assembly (default 8000) +// - PriorityOrder: Custom file loading priority order +// - AutoArchive: Whether to auto-archive completed tasks (default true) +// - ArchiveAfterDays: Days before archiving completed tasks (default 7) +type CtxRC struct { + ContextDir string `yaml:"context_dir"` + TokenBudget int `yaml:"token_budget"` + PriorityOrder []string `yaml:"priority_order"` + AutoArchive bool `yaml:"auto_archive"` + ArchiveAfterDays int `yaml:"archive_after_days"` +} diff --git a/internal/recall/parser/git.go b/internal/recall/parser/git.go index 0bfe2c257..8dd2fed81 100644 --- a/internal/recall/parser/git.go +++ b/internal/recall/parser/git.go @@ -1 +1,35 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package parser + +import ( + "os" + "os/exec" + "strings" +) + +// gitRemote returns the git remote origin URL for a directory. +// Returns an empty string if not a git repo or no remote configured. +func gitRemote(dir string) string { + if dir == "" { + return "" + } + + // Check if the directory exists + if _, err := os.Stat(dir); err != nil { + return "" + } + + // Try to get git remote + cmd := exec.Command("git", "-C", dir, "remote", "get-url", "origin") + output, err := cmd.Output() + if err != nil { + return "" + } + + return strings.TrimSpace(string(output)) +} diff --git a/internal/recall/parser/parser_test.go b/internal/recall/parser/parser_test.go index db1686d7a..540532b19 100644 --- a/internal/recall/parser/parser_test.go +++ b/internal/recall/parser/parser_test.go @@ -334,7 +334,7 @@ func TestRegisteredTools(t *testing.T) { } func TestGetParser(t *testing.T) { - parser := GetParser("claude-code") + parser := Parser("claude-code") if parser == nil { t.Error("expected parser for 'claude-code'") } @@ -342,7 +342,7 @@ func TestGetParser(t *testing.T) { t.Errorf("expected tool 'claude-code', got '%s'", parser.Tool()) } - unknown := GetParser("unknown-tool") + unknown := Parser("unknown-tool") if unknown != nil { t.Error("expected nil for unknown tool") } @@ -352,14 +352,14 @@ func TestFindSessions_Integration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } - + sessions, err := FindSessions() if err != nil { t.Fatalf("FindSessions failed: %v", err) } - + t.Logf("Found %d sessions", len(sessions)) - + for i, s := range sessions { if i >= 3 { t.Logf("... and %d more", len(sessions)-3) @@ -383,7 +383,9 @@ func TestDebugSession(t *testing.T) { t.Logf("Session: %s", s.ID) t.Logf("Messages: %d", len(s.Messages)) for i, m := range s.Messages { - if i > 5 { break } + if i > 5 { + break + } t.Logf(" %d. %s: text=%d chars, tools=%d", i, m.Role, len(m.Text), len(m.ToolUses)) if len(m.ToolUses) > 0 { t.Logf(" tool: %s, input: %.100s", m.ToolUses[0].Name, m.ToolUses[0].Input) diff --git a/internal/recall/parser/path.go b/internal/recall/parser/path.go index 0bfe2c257..9a239325e 100644 --- a/internal/recall/parser/path.go +++ b/internal/recall/parser/path.go @@ -1 +1,37 @@ +// / Context: https://ctx.ist +// ,'`./ do you remember? +// `.,'\\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + package parser + +import ( + "path/filepath" + "strings" +) + +// getPathRelativeToHome returns the path relative to the user's home directory. +// Returns an empty string if the path is not under a home directory. +func getPathRelativeToHome(path string) string { + if path == "" { + return "" + } + + // Handle common home directory patterns + // /home/username/... -> strip /home/username + // /Users/username/... -> strip /Users/username (macOS) + parts := strings.Split(path, string(filepath.Separator)) + + for i, part := range parts { + if part == "home" || part == "Users" { + // Next part is username, rest is relative path + if i+2 < len(parts) { + return filepath.Join(parts[i+2:]...) + } + return "" + } + } + + return "" +} diff --git a/internal/tpl/claude/commands/ctx-add-decision.md b/internal/tpl/claude/commands/ctx-add-decision.md index 99315c91d..f84b0fd28 100644 --- a/internal/tpl/claude/commands/ctx-add-decision.md +++ b/internal/tpl/claude/commands/ctx-add-decision.md @@ -3,7 +3,8 @@ description: "Add a decision to DECISIONS.md (requires context, rationale, conse argument-hint: "\"decision title\"" --- -When the user runs /ctx-add-decision, you need to gather the complete ADR (Architecture Decision Record) format. +When the user runs /ctx-add-decision, you need to gather the complete ADR +(Architecture Decision Record) format. If $ARGUMENTS contains only a title (no flags), ask the user for: 1. **Context**: What prompted this decision? diff --git a/internal/tpl/claude/commands/ctx-prompt-audit.md b/internal/tpl/claude/commands/ctx-prompt-audit.md index 488cef950..6beb6ec7f 100644 --- a/internal/tpl/claude/commands/ctx-prompt-audit.md +++ b/internal/tpl/claude/commands/ctx-prompt-audit.md @@ -2,7 +2,9 @@ description: "Analyze session logs to identify vague prompts and suggest improvements" --- -Analyze recent session transcripts to identify prompts that led to unnecessary clarification back-and-forth. This helps the user improve their prompting patterns. +Analyze recent session transcripts to identify prompts that led to unnecessary +clarification back-and-forth. This helps the user improve their prompting +patterns. ## Your Task @@ -12,7 +14,8 @@ Analyze recent session transcripts to identify prompts that led to unnecessary c ## What Makes a Prompt "Vague" -Look for user prompts where Claude's immediate response was to ask clarifying questions rather than take action. Signs include: +Look for user prompts where Claude's immediate response was to ask clarifying +questions rather than take action. Signs include: - **Missing file context**: "fix the bug" without specifying which file or error - **Ambiguous scope**: "optimize it" without what to optimize or success criteria @@ -45,9 +48,11 @@ Generate a report like this: **Your prompt**: "fix the bug" -**What happened**: I had to ask which file and what error you were seeing, adding 2 messages of back-and-forth. +**What happened**: I had to ask which file and what error you were seeing, a +dding 2 messages of back-and-forth. -**Better prompt**: "fix the authentication error in src/auth/login.ts where JWT validation fails with 401" +**Better prompt**: "fix the authentication error in src/auth/login.ts where +JWT validation fails with 401" **Cost**: ~2 extra messages, ~30 seconds @@ -57,9 +62,11 @@ Generate a report like this: **Your prompt**: "optimize the component" -**What happened**: Multiple components exist. I asked which one and what performance issue to address. +**What happened**: Multiple components exist. I asked which one and what +performance issue to address. -**Better prompt**: "optimize UserList in src/components/UserList.tsx to reduce re-renders when parent state updates" +**Better prompt**: "optimize UserList in src/components/UserList.tsx to reduce +re-renders when parent state updates" **Cost**: ~3 extra messages, ~1 minute diff --git a/internal/tpl/claude/commands/ctx-recall.md b/internal/tpl/claude/commands/ctx-recall.md index d2f48f93f..c770ac10c 100644 --- a/internal/tpl/claude/commands/ctx-recall.md +++ b/internal/tpl/claude/commands/ctx-recall.md @@ -9,4 +9,5 @@ ctx recall list --limit 10 ``` Show the user's recent sessions with project, time, and turn count. -If the user asks about a specific session, use `ctx recall show ` to get details. +If the user asks about a specific session, use `ctx recall show ` to +get details. diff --git a/internal/tpl/claude/commands/ctx-reflect.md b/internal/tpl/claude/commands/ctx-reflect.md index 20e68331d..593e09000 100644 --- a/internal/tpl/claude/commands/ctx-reflect.md +++ b/internal/tpl/claude/commands/ctx-reflect.md @@ -2,7 +2,8 @@ description: "Reflect on session and suggest what to persist" --- -Pause and reflect on this session. Review what has been accomplished and identify context worth persisting. +Pause and reflect on this session. Review what has been accomplished and +identify context worth persisting. ## Reflection Checklist @@ -36,7 +37,8 @@ After reflecting, provide: 3. **Offer**: Ask if the user wants you to persist any of these Example: -> "This session fixed the auth bug and we discovered the token refresh gotcha. I'd suggest: +> "This session fixed the auth bug and we discovered the token refresh gotcha. +> I'd suggest: > - Learning: Token refresh requires explicit cache invalidation > - Task: Mark 'Fix auth bug' as done > Want me to persist these?" diff --git a/internal/tpl/embed.go b/internal/tpl/embed.go index 87727a844..e8035ff22 100644 --- a/internal/tpl/embed.go +++ b/internal/tpl/embed.go @@ -4,32 +4,33 @@ // \ Copyright 2026-present Context contributors. // SPDX-License-Identifier: Apache-2.0 -// Package templates provides embedded template files for initializing .context/ directories. -package templates +// Package tpl provides embedded template files for initializing +// .context/ directories. +package tpl import "embed" //go:embed *.md entry-templates/*.md claude/commands/*.md claude/hooks/*.sh var FS embed.FS -// GetTemplate reads a template file by name from the embedded filesystem. +// Template reads a template file by name from the embedded filesystem. // // Parameters: // - name: Template filename (e.g., "TASKS.md") // // Returns: // - []byte: Template content -// - error: Non-nil if file not found or read fails -func GetTemplate(name string) ([]byte, error) { +// - error: Non-nil if the file is not found or read fails +func Template(name string) ([]byte, error) { return FS.ReadFile(name) } -// ListTemplates returns all available template file names. +// List returns all available template file names. // // Returns: // - []string: List of template filenames in the root templates directory // - error: Non-nil if directory read fails -func ListTemplates() ([]string, error) { +func List() ([]string, error) { entries, err := FS.ReadDir(".") if err != nil { return nil, err @@ -44,12 +45,12 @@ func ListTemplates() ([]string, error) { return names, nil } -// ListEntryTemplates returns available entry template file names. +// ListEntry returns available entry template file names. // // Returns: // - []string: List of template filenames in entry-templates/ // - error: Non-nil if directory read fails -func ListEntryTemplates() ([]string, error) { +func ListEntry() ([]string, error) { entries, err := FS.ReadDir("entry-templates") if err != nil { return nil, err @@ -64,15 +65,15 @@ func ListEntryTemplates() ([]string, error) { return names, nil } -// GetEntryTemplate reads an entry template by name. +// Entry reads an entry template by name. // // Parameters: // - name: Template filename (e.g., "decision.md") // // Returns: // - []byte: Template content from entry-templates/ -// - error: Non-nil if file not found or read fails -func GetEntryTemplate(name string) ([]byte, error) { +// - error: Non-nil if the file is not found or read fails +func Entry(name string) ([]byte, error) { return FS.ReadFile("entry-templates/" + name) } @@ -96,15 +97,15 @@ func ListClaudeCommands() ([]string, error) { return names, nil } -// GetClaudeCommand reads a Claude Code slash command template by name. +// ClaudeCommandByName reads a Claude Code slash command template by name. // // Parameters: // - name: Command filename (e.g., "ctx-status.md") // // Returns: // - []byte: Command template content from claude/commands/ -// - error: Non-nil if file not found or read fails -func GetClaudeCommand(name string) ([]byte, error) { +// - error: Non-nil if the file not found or read fails +func ClaudeCommandByName(name string) ([]byte, error) { return FS.ReadFile("claude/commands/" + name) } @@ -115,7 +116,7 @@ func GetClaudeCommand(name string) ([]byte, error) { // // Returns: // - []byte: Hook script content from claude/hooks/ -// - error: Non-nil if file not found or read fails +// - error: Non-nil if the file is not found or read fails func ClaudeHookByFileName(name string) ([]byte, error) { return FS.ReadFile("claude/hooks/" + name) } diff --git a/internal/tpl/embed_test.go b/internal/tpl/embed_test.go index ebd09d13c..c6f903ed7 100644 --- a/internal/tpl/embed_test.go +++ b/internal/tpl/embed_test.go @@ -4,7 +4,7 @@ // \ Copyright 2026-present Context contributors. // SPDX-License-Identifier: Apache-2.0 -package templates +package tpl import ( "strings" @@ -87,32 +87,32 @@ func TestGetTemplate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - content, err := GetTemplate(tt.template) + content, err := Template(tt.template) if tt.wantErr { if err == nil { - t.Errorf("GetTemplate(%q) expected error, got nil", tt.template) + t.Errorf("Template(%q) expected error, got nil", tt.template) } return } if err != nil { - t.Errorf("GetTemplate(%q) unexpected error: %v", tt.template, err) + t.Errorf("Template(%q) unexpected error: %v", tt.template, err) return } if !strings.Contains(string(content), tt.wantContain) { - t.Errorf("GetTemplate(%q) content does not contain %q", tt.template, tt.wantContain) + t.Errorf("Template(%q) content does not contain %q", tt.template, tt.wantContain) } }) } } func TestListTemplates(t *testing.T) { - templates, err := ListTemplates() + templates, err := List() if err != nil { - t.Fatalf("ListTemplates() unexpected error: %v", err) + t.Fatalf("List() unexpected error: %v", err) } if len(templates) == 0 { - t.Error("ListTemplates() returned empty list") + t.Error("List() returned empty list") } // Check for required templates @@ -130,19 +130,19 @@ func TestListTemplates(t *testing.T) { for _, req := range required { if !templateSet[req] { - t.Errorf("ListTemplates() missing required template: %s", req) + t.Errorf("List() missing required template: %s", req) } } } func TestListEntryTemplates(t *testing.T) { - templates, err := ListEntryTemplates() + templates, err := ListEntry() if err != nil { - t.Fatalf("ListEntryTemplates() unexpected error: %v", err) + t.Fatalf("ListEntry() unexpected error: %v", err) } if len(templates) == 0 { - t.Error("ListEntryTemplates() returned empty list") + t.Error("ListEntry() returned empty list") } // Check for expected entry templates @@ -158,7 +158,7 @@ func TestListEntryTemplates(t *testing.T) { for _, exp := range expected { if !templateSet[exp] { - t.Errorf("ListEntryTemplates() missing expected template: %s", exp) + t.Errorf("ListEntry() missing expected template: %s", exp) } } } @@ -191,19 +191,19 @@ func TestGetEntryTemplate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - content, err := GetEntryTemplate(tt.template) + content, err := Entry(tt.template) if tt.wantErr { if err == nil { - t.Errorf("GetEntryTemplate(%q) expected error, got nil", tt.template) + t.Errorf("Entry(%q) expected error, got nil", tt.template) } return } if err != nil { - t.Errorf("GetEntryTemplate(%q) unexpected error: %v", tt.template, err) + t.Errorf("Entry(%q) unexpected error: %v", tt.template, err) return } if !strings.Contains(string(content), tt.wantContain) { - t.Errorf("GetEntryTemplate(%q) content does not contain %q", tt.template, tt.wantContain) + t.Errorf("Entry(%q) content does not contain %q", tt.template, tt.wantContain) } }) } @@ -239,9 +239,9 @@ func TestListClaudeCommands(t *testing.T) { } func TestGetClaudeCommand(t *testing.T) { - content, err := GetClaudeCommand("ctx-recall.md") + content, err := ClaudeCommandByName("ctx-recall.md") if err != nil { - t.Fatalf("GetClaudeCommand(ctx-recall.md) error: %v", err) + t.Fatalf("ClaudeCommandByName(ctx-recall.md) error: %v", err) } if !strings.Contains(string(content), "recall") { t.Error("ctx-recall.md does not contain 'recall'") diff --git a/internal/validation/validate.go b/internal/validation/validate.go index 5cb870d2b..0d9192029 100644 --- a/internal/validation/validate.go +++ b/internal/validation/validate.go @@ -9,7 +9,7 @@ import ( // SanitizeFilename converts a topic string to a safe filename component. // // Replaces spaces and special characters with hyphens, converts to lowercase, -// and limits length to 50 characters. Returns "session" if input is empty. +// and limits length to 50 characters. Returns "session" if the input is empty. // // Parameters: // - s: Topic string to sanitize From 316a782ed66d22b8fdc2e99a64572d83b1126a13 Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 1 Feb 2026 08:30:34 -0800 Subject: [PATCH 03/10] fix: shell script linter warnings - Replace useless cat with input redirection in release.sh and tag.sh - Use find instead of ls for file listing in release.sh Signed-off-by: Jose Alekhinne --- hack/release.sh | 4 ++-- hack/tag.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/release.sh b/hack/release.sh index 8752ea9b5..9745da6ea 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -57,7 +57,7 @@ if [ ! -f "$ROOT_DIR/VERSION" ]; then exit 1 fi -VERSION="v$(cat "$ROOT_DIR/VERSION" | tr -d '[:space:]')" +VERSION="v$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")" # ----------------------------------------------------------------------------- # Derived values @@ -179,7 +179,7 @@ echo " - Tag: ${TAG_NAME}" echo " - Tag: latest -> ${TAG_NAME}" echo "" echo "Built artifacts in dist/:" -ls -1 dist/ctx-* 2>/dev/null | sed 's/^/ /' +find dist -maxdepth 1 -name 'ctx-*' 2>/dev/null | sed 's/^/ /' echo "" echo "Next step:" echo "" diff --git a/hack/tag.sh b/hack/tag.sh index 550520ade..64457f292 100755 --- a/hack/tag.sh +++ b/hack/tag.sh @@ -26,7 +26,7 @@ if [ ! -f "$ROOT_DIR/VERSION" ]; then exit 1 fi -VERSION="v$(cat "$ROOT_DIR/VERSION" | tr -d '[:space:]')" +VERSION="v$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")" echo "Creating tag: $VERSION" From 18492c37115b4dde1690cf9220b73705ca0c762d Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 1 Feb 2026 08:30:40 -0800 Subject: [PATCH 04/10] docs(examples): update demo project to reflect current practices - Add AGENT_PLAYBOOK.md with bootstrap instructions - Add LEARNINGS.md with example entries - Add PROMPT.md for Ralph Loop workflow - Add specs/ directory with oauth2.md example - Update existing context files with richer examples Signed-off-by: Jose Alekhinne --- examples/demo/.context/AGENT_PLAYBOOK.md | 113 ++++++++++++++ examples/demo/.context/ARCHITECTURE.md | 92 ++++++++++-- examples/demo/.context/CONSTITUTION.md | 39 ++++- examples/demo/.context/CONVENTIONS.md | 74 +++++++-- examples/demo/.context/DECISIONS.md | 74 +++++++-- examples/demo/.context/LEARNINGS.md | 57 +++++++ examples/demo/.context/TASKS.md | 31 ++-- ...5-143000-database-timeout-investigation.md | 64 ++++++++ examples/demo/PROMPT.md | 95 ++++++++++++ examples/demo/README.md | 140 ++++++++++++++---- examples/demo/specs/oauth2.md | 94 ++++++++++++ 11 files changed, 786 insertions(+), 87 deletions(-) create mode 100644 examples/demo/.context/AGENT_PLAYBOOK.md create mode 100644 examples/demo/.context/LEARNINGS.md create mode 100644 examples/demo/.context/sessions/2026-01-15-143000-database-timeout-investigation.md create mode 100644 examples/demo/PROMPT.md create mode 100644 examples/demo/specs/oauth2.md diff --git a/examples/demo/.context/AGENT_PLAYBOOK.md b/examples/demo/.context/AGENT_PLAYBOOK.md new file mode 100644 index 000000000..4aefe6827 --- /dev/null +++ b/examples/demo/.context/AGENT_PLAYBOOK.md @@ -0,0 +1,113 @@ +# Agent Playbook + +## Mental Model + +This system does not persist experience. + +- Each session is a fresh execution in a shared workshop. +- Work continuity comes from artifacts left on the bench. + +### Work → Reflect → Persist + +After completing meaningful work, follow this cycle: + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ +│ Work │ ──▶ │ Reflect │ ──▶ │ Persist │ ──▶ (continue) +└─────────┘ └─────────┘ └─────────┘ + │ + ▼ + Did I just... + • Complete a task? + • Make a decision? + • Learn something? + • Hit a milestone? +``` + +**Don't wait for session end** — it may never come cleanly. Persist as you go. + +## Read Order + +1. CONSTITUTION.md — Hard rules, NEVER violate +2. TASKS.md — What to work on next +3. CONVENTIONS.md — How to write code +4. ARCHITECTURE.md — Where things go +5. DECISIONS.md — Why things are the way they are +6. LEARNINGS.md — Gotchas to avoid + +## How Your Memory Works + +When asked "Do you remember?" or similar: + +1. **Check the context files** (sessions/, TASKS.md, LEARNINGS.md, etc.) +2. **Respond with what you found** — "From the sessions, I see we discussed X..." +3. **Be honest about mechanism only if asked** — the user wants results, not disclaimers + +**Wrong**: "I don't have memory, but I can check files. Want me to?" +**Right**: *checks files* → "Yes, I see from the last session we were working on..." + +The context IS your memory. Load it and use it — don't lead with caveats. + +## Session History + +Check `.context/sessions/` for session dumps from previous sessions. + +Session files are named `YYYY-MM-DD-HHMMSS-.md`. + +## When to Update Memory + +| Event | Action | +|-----------------------------|-----------------------| +| Made architectural decision | Add to DECISIONS.md | +| Discovered gotcha/bug | Add to LEARNINGS.md | +| Established new pattern | Add to CONVENTIONS.md | +| Completed task | Mark [x] in TASKS.md | +| Had important discussion | Save to sessions/ | + +### Prefer `ctx add` Over Direct File Edits + +When adding learnings, decisions, or tasks, **use `ctx add` commands**: + +```bash +# ✓ Preferred - ensures consistent format, timestamps, structure +ctx add learning "Title" --context "..." --lesson "..." --application "..." +ctx add decision "Title" --context "..." --rationale "..." --consequences "..." +ctx add task "Description" + +# ✗ Avoid - bypasses structure, easy to write incomplete entries +Edit LEARNINGS.md directly with a one-liner +``` + +**Exception:** Direct edits are fine for: +- Marking tasks complete (`[ ]` → `[x]`) +- Minor corrections to existing entries + +## Proactive Context Persistence + +**Don't wait for session end** — persist context at natural milestones. + +### Milestone Triggers + +| Milestone | Action | +|------------------------------------|-------------------------------------------------| +| Complete a task | Mark done in TASKS.md, offer to add learnings | +| Make an architectural decision | `ctx add decision "..."` | +| Discover a gotcha or bug | `ctx add learning "..."` | +| Finish a significant code change | Offer to summarize what was done | + +### Self-Check Prompt + +Periodically ask yourself: + +> "If this session ended right now, would the next session know what happened?" + +If no — persist something before continuing. + +## How to Avoid Hallucinating Memory + +Never assume: If you don't see it in files, you don't know it. + +- Don't claim "we discussed X" without file evidence +- Don't invent history - check sessions/ for actual discussions +- If uncertain, say "I don't see this documented" +- Trust files over intuition diff --git a/examples/demo/.context/ARCHITECTURE.md b/examples/demo/.context/ARCHITECTURE.md index 766ba5269..7ff400f2e 100644 --- a/examples/demo/.context/ARCHITECTURE.md +++ b/examples/demo/.context/ARCHITECTURE.md @@ -1,28 +1,72 @@ # Architecture -System overview and key design decisions. +System overview and component organization. -## System Overview +## High-Level Architecture ``` -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ Frontend │───▶│ API Server │───▶│ Database │ -│ (React) │ │ (Go) │ │ (Postgres) │ -└──────────────┘ └──────────────┘ └──────────────┘ - │ - ▼ - ┌──────────────┐ - │ Redis Cache │ - └──────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ Clients │ +│ (Web App, Mobile App, CLI) │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Load Balancer │ +│ (nginx / AWS ALB) │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ API Server │ +│ (Go / net/http) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Handlers │ │ Services │ │ Repositories │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────────┐ + │ PostgreSQL│ │ Redis │ │ Object Store │ + │ (primary) │ │ (cache) │ │ (S3) │ + └───────────┘ └───────────┘ └───────────────┘ ``` ## Directory Structure -- `/cmd/` - Application entry points -- `/internal/` - Private application code -- `/pkg/` - Public library code -- `/api/` - API definitions and OpenAPI specs -- `/web/` - Frontend React application +``` +. +├── cmd/ +│ ├── api/ # API server entrypoint +│ └── worker/ # Background worker entrypoint +├── internal/ +│ ├── handler/ # HTTP handlers +│ ├── service/ # Business logic +│ ├── repository/ # Data access +│ └── model/ # Domain types +├── pkg/ # Shared libraries (importable) +├── migrations/ # Database migrations +├── docs/ # Documentation +└── .context/ # AI context files +``` + +## Key Components + +### API Server (`cmd/api`) +- Handles HTTP requests +- Validates input, calls services, returns responses +- Stateless — all state in database or cache + +### Services (`internal/service`) +- Contains business logic +- Orchestrates multiple repositories +- Enforces business rules + +### Repositories (`internal/repository`) +- Data access layer +- One repository per aggregate root +- Handles database queries and caching ## Key Patterns @@ -38,3 +82,19 @@ easier and components more modular. The system uses an event bus for decoupled component communication. Events are published when state changes, and interested components subscribe to relevant events. + +## Data Flow + +1. Request arrives at handler +2. Handler validates input, extracts user context +3. Handler calls service with validated data +4. Service applies business logic, calls repositories +5. Repository reads/writes to database +6. Response flows back up the stack + +## Scaling Strategy + +- **Horizontal**: Add more API server instances behind load balancer +- **Database**: Read replicas for read-heavy workloads +- **Cache**: Redis for session data and frequently accessed records +- **Background work**: Separate worker processes for async jobs diff --git a/examples/demo/.context/CONSTITUTION.md b/examples/demo/.context/CONSTITUTION.md index 3c11befd4..2732066d3 100644 --- a/examples/demo/.context/CONSTITUTION.md +++ b/examples/demo/.context/CONSTITUTION.md @@ -1,11 +1,36 @@ # Constitution -Core invariants that must NEVER be violated. Read these first. +These rules are INVIOLABLE. If a task requires violating these, the task is wrong. -## Inviolable Rules +## Security Invariants -- [ ] All code changes must include tests -- [ ] Never commit secrets or credentials to the repository -- [ ] Breaking changes require a deprecation period -- [ ] Security vulnerabilities must be fixed within 24 hours -- [ ] All public APIs must be documented +- [ ] Never commit secrets, tokens, API keys, or credentials +- [ ] Never store customer/user data in context files +- [ ] All user input must be validated and sanitized + +## Quality Invariants + +- [ ] All code must pass tests before commit +- [ ] No TODO comments in main branch (move to TASKS.md) +- [ ] Breaking API changes require deprecation period + +## Process Invariants + +- [ ] All architectural changes require a decision record in DECISIONS.md + +## TASKS.md Structure Invariants + +TASKS.md must remain a replayable checklist. Uncheck all items and re-run +the loop = verify/redo all tasks in order. + +- [ ] **Never move tasks** — tasks stay in their Phase section permanently +- [ ] **Never remove Phase headers** — Phase labels provide structure and order +- [ ] **Never delete tasks** — mark as `[x]` completed, or `[-]` skipped with reason +- [ ] **Use inline labels for status** — add `#in-progress` to task text, don't move it +- [ ] **No "In Progress" sections** — these encourage moving tasks +- [ ] **Ask before restructuring** — if structure changes seem needed, ask the user first + +## Context Preservation Invariants + +- [ ] **Archival is allowed, deletion is not** — use `ctx tasks archive` to move completed tasks, never delete context history +- [ ] **Archive preserves structure** — archived tasks keep their Phase headers for traceability diff --git a/examples/demo/.context/CONVENTIONS.md b/examples/demo/.context/CONVENTIONS.md index 5978b6575..7f742f390 100644 --- a/examples/demo/.context/CONVENTIONS.md +++ b/examples/demo/.context/CONVENTIONS.md @@ -2,28 +2,82 @@ Coding standards and patterns used in this project. -## Code Style +## Naming - Use camelCase for variables and functions - Use PascalCase for types and interfaces +- Use SCREAMING_SNAKE_CASE for constants + +## Code Style + - Prefer early returns over nested conditionals - Maximum line length: 100 characters +- One component per file -## File Organization +## Patterns -- One component per file -- Group related files in directories -- Test files should be adjacent to source files +### Error Handling + +Always return errors, never panic in library code: + +```go +// ✓ Correct +func ProcessData(data []byte) (Result, error) { + if len(data) == 0 { + return Result{}, fmt.Errorf("empty data") + } + // ... +} + +// ✗ Wrong +func ProcessData(data []byte) Result { + if len(data) == 0 { + panic("empty data") // Never panic in libraries + } + // ... +} +``` + +Wrap errors with context: + +```go +if err != nil { + return fmt.Errorf("processing user %s: %w", userID, err) +} +``` + +### Configuration + +Load order (highest priority first): +1. Environment variables +2. Config file (config.yaml) +3. Default values + +Log config source at startup for debuggability. + +## Testing + +- Test files adjacent to source files (`foo.go` → `foo_test.go`) +- Use table-driven tests for multiple cases +- Mock external dependencies, never call real services in tests ## Git Practices -- Commit messages follow Conventional Commits +- Commit messages follow Conventional Commits format - Feature branches: `feature/` - Bug fixes: `fix/` - All PRs require at least one approval -## Error Handling +## Documentation + +### Doc-Impact Rule + +When modifying code that affects user-facing behavior, update the corresponding +documentation: -- Always return errors, never panic in libraries -- Wrap errors with context using `fmt.Errorf` -- Log errors at the boundary, not in helpers +| Code Change | Doc Update Required | +|--------------------------|------------------------| +| API endpoint changes | `docs/api.md` | +| CLI command changes | `docs/cli.md` | +| Configuration changes | `docs/configuration.md`| +| New features | `README.md` | diff --git a/examples/demo/.context/DECISIONS.md b/examples/demo/.context/DECISIONS.md index 63a49b75b..229d83308 100644 --- a/examples/demo/.context/DECISIONS.md +++ b/examples/demo/.context/DECISIONS.md @@ -1,37 +1,87 @@ # Decisions -Record of significant technical decisions with rationale. +Architectural decisions with rationale and consequences. -## 2024-01-15 Use PostgreSQL for primary database +--- + +## [2026-01-05-110000] Use PostgreSQL for Primary Database -**Context**: We need a reliable, scalable database for our application. +**Context**: Needed to choose a database for the application. Options were +PostgreSQL, MySQL, and MongoDB. -**Decision**: Use PostgreSQL instead of MySQL or MongoDB. +**Decision**: PostgreSQL **Rationale**: - Strong ACID compliance for financial transactions - Excellent JSON support for flexible schema needs +- Team has existing PostgreSQL expertise - Rich ecosystem of tools and extensions -- Team has existing expertise **Consequences**: -- Need to learn PostgreSQL-specific features -- Deployment requires PostgreSQL setup +- Need to manage schema migrations explicitly +- Requires more upfront schema design than document stores +- Horizontal scaling requires additional tooling (Citus, read replicas) + +--- + +## [2026-01-08-140000] JWT for API Authentication + +**Context**: Needed to choose authentication mechanism for the REST API. +Options were session cookies, JWT tokens, and API keys. + +**Decision**: JWT tokens with short expiry + refresh tokens + +**Rationale**: +- Stateless authentication scales horizontally without session storage +- Works well for both web and mobile clients +- Can embed user claims to reduce database lookups +- Industry standard with good library support + +**Consequences**: +- Cannot immediately revoke tokens (must wait for expiry) +- Need secure storage for refresh tokens +- Must implement token refresh flow in all clients +- Larger request payload than session cookies --- -## 2024-01-10 Use Go for API server +## [2026-01-10-090000] Use Go for API Server -**Context**: Choosing a backend language for our API. +**Context**: Choosing a backend language for the API. Options were Go, +Node.js, and Python. -**Decision**: Use Go instead of Node.js or Python. +**Decision**: Go **Rationale**: - Excellent performance characteristics - Strong typing catches bugs at compile time - Simple deployment with single binary -- Great concurrency primitives +- Great concurrency primitives for handling many connections **Consequences**: -- Smaller talent pool than JavaScript +- Smaller talent pool than JavaScript/Python - Some team members need Go training +- Compile step required (vs interpreted languages) + +--- + +## [2026-01-12-160000] Monorepo Structure + +**Context**: Starting with multiple services (API, worker, CLI). Needed to +decide between monorepo and multi-repo structure. + +**Decision**: Monorepo with shared packages + +**Rationale**: +- Atomic commits across services +- Easier code sharing and refactoring +- Single CI/CD pipeline to maintain +- Better visibility into cross-service changes + +**Consequences**: +- Need tooling to handle partial builds (only changed services) +- Repository will grow large over time +- All developers need access to entire codebase +- Must establish clear package boundaries + +--- diff --git a/examples/demo/.context/LEARNINGS.md b/examples/demo/.context/LEARNINGS.md new file mode 100644 index 000000000..3813eae83 --- /dev/null +++ b/examples/demo/.context/LEARNINGS.md @@ -0,0 +1,57 @@ +# Learnings + + +| Date | Learning | +|------|----------| +| 2026-01-15 | Database connections need explicit timeouts | +| 2026-01-10 | Environment variables override config files | +| 2026-01-05 | Rate limiter must be per-user, not global | + + +--- + +## [2026-01-15-143022] Database connections need explicit timeouts + +**Context**: Production outage caused by database connection pool exhaustion. +Connections were hanging indefinitely waiting for slow queries. + +**Lesson**: Always set explicit timeouts on database connections: connect timeout, +read timeout, and write timeout. Default "no timeout" is never acceptable in production. + +**Application**: Add to connection config: +```go +db.SetConnMaxLifetime(5 * time.Minute) +db.SetConnMaxIdleTime(1 * time.Minute) +ctx, cancel := context.WithTimeout(ctx, 30*time.Second) +``` + +--- + +## [2026-01-10-091500] Environment variables override config files + +**Context**: Debugging why staging had different behavior than local. Config file +was correct, but an old environment variable was overriding it. + +**Lesson**: Document the precedence order clearly: ENV > config file > defaults. +When debugging config issues, always check environment variables first. + +**Application**: Add config source logging at startup: +``` +Config loaded: database.host=localhost (source: ENV) +Config loaded: database.port=5432 (source: config.yaml) +``` + +--- + +## [2026-01-05-160030] Rate limiter must be per-user, not global + +**Context**: Implemented global rate limiter (100 req/sec total). One heavy user +could starve all other users. + +**Lesson**: Rate limiting should be per-user (or per-API-key) to ensure fair +resource allocation. Global limits are only useful as a last-resort circuit breaker. + +**Application**: Use user ID or API key as the rate limiter bucket key, not a +single global bucket. + +--- diff --git a/examples/demo/.context/TASKS.md b/examples/demo/.context/TASKS.md index 7986db523..77657f181 100644 --- a/examples/demo/.context/TASKS.md +++ b/examples/demo/.context/TASKS.md @@ -1,21 +1,26 @@ # Tasks -Current work items, prioritized from top to bottom. +Current work items, organized by phase. Tasks stay in their phase permanently. -## Active Tasks +## Phase 1: Foundation -- [ ] Implement user authentication with OAuth2 -- [ ] Add rate limiting to API endpoints -- [ ] Write integration tests for payment flow +- [x] Initial project setup #added:2026-01-01-090000 #done:2026-01-01-120000 +- [x] Database schema design #added:2026-01-01-090000 #done:2026-01-02-150000 +- [x] Core API scaffolding #added:2026-01-01-090000 #done:2026-01-03-110000 -## Backlog +## Phase 2: Authentication -- [ ] Add support for WebSocket connections -- [ ] Implement caching layer for frequently accessed data -- [ ] Set up monitoring and alerting +- [x] Implement user registration #added:2026-01-04-100000 #done:2026-01-05-140000 +- [ ] Implement OAuth2 login #added:2026-01-04-100000 #in-progress +- [ ] Add session management #added:2026-01-04-100000 -## Completed +## Phase 3: API Features -- [x] Initial project setup -- [x] Database schema design -- [x] Core API scaffolding +- [ ] Add rate limiting to API endpoints #added:2026-01-10-090000 +- [ ] Write integration tests for payment flow #added:2026-01-10-090000 + +## Phase 4: Infrastructure + +- [ ] Add support for WebSocket connections #added:2026-01-15-140000 +- [ ] Implement caching layer #added:2026-01-15-140000 +- [ ] Set up monitoring and alerting #added:2026-01-15-140000 diff --git a/examples/demo/.context/sessions/2026-01-15-143000-database-timeout-investigation.md b/examples/demo/.context/sessions/2026-01-15-143000-database-timeout-investigation.md new file mode 100644 index 000000000..4ffacfbb9 --- /dev/null +++ b/examples/demo/.context/sessions/2026-01-15-143000-database-timeout-investigation.md @@ -0,0 +1,64 @@ +# Session: Database Timeout Investigation + +**Date**: 2026-01-15 +**start_time**: 2026-01-15-140000 +**end_time**: 2026-01-15-160000 +**Topic**: Investigating production database connection issues +**Type**: bugfix + +--- + +## Summary + +Investigated production outage caused by database connection pool exhaustion. +Found that connections were hanging indefinitely on slow queries. Implemented +explicit timeouts and connection lifecycle management. + +## Problem + +- Production API started returning 503 errors +- Database connection pool was exhausted (all 100 connections in use) +- Connections were stuck waiting for queries that never returned +- No timeout configured on database connections + +## Root Cause + +Default Go database driver has no timeout. When the database is slow or +unresponsive, connections wait forever, eventually exhausting the pool. + +## Fix Applied + +```go +// Before: no timeouts +db, err := sql.Open("postgres", connStr) + +// After: explicit lifecycle management +db, err := sql.Open("postgres", connStr) +db.SetConnMaxLifetime(5 * time.Minute) +db.SetConnMaxIdleTime(1 * time.Minute) +db.SetMaxOpenConns(100) +db.SetMaxIdleConns(10) + +// Query-level timeouts +ctx, cancel := context.WithTimeout(ctx, 30*time.Second) +defer cancel() +rows, err := db.QueryContext(ctx, query) +``` + +## Key Decisions + +- Set connection max lifetime to 5 minutes (prevents stale connections) +- Set query timeout to 30 seconds (fail fast on slow queries) +- Added circuit breaker for database calls + +## Tasks for Next Session + +- Add monitoring for connection pool metrics +- Set up alerting for connection pool utilization > 80% +- Review other services for similar timeout issues + +## Files Changed + +- `internal/repository/db.go` +- `internal/config/database.go` +- `docs/operations.md` diff --git a/examples/demo/PROMPT.md b/examples/demo/PROMPT.md new file mode 100644 index 000000000..d5df48fe3 --- /dev/null +++ b/examples/demo/PROMPT.md @@ -0,0 +1,95 @@ +# PROMPT.md — Demo Project + +## CORE PRINCIPLE + +You have NO conversational memory. Your memory IS the file system. +Your goal: advance the project by exactly ONE task, update context, and exit. + +--- + +## PROJECT CONTEXT + +**Project**: Demo API Server +**Language**: Go 1.22+ +**Current Focus**: Phase 2 — Authentication + +--- + +## PHASE 0: ORIENT + +1. Read `.context/TASKS.md` — Current work items +2. Read `.context/CONSTITUTION.md` — Rules to never violate +3. Read `.context/CONVENTIONS.md` — How to write code +4. Read relevant spec in `specs/` for the current task + +--- + +## PHASE 1: SELECT TASK + +1. Read `.context/TASKS.md` +2. Find the **first unchecked item** (line starting with `- [ ]`) +3. That is your ONE task for this iteration + +**IF NO UNCHECKED ITEMS:** +1. Run validation: `go build ./...`, `go test ./...` +2. If all pass, output `PHASE_COMPLETE` +3. If any fail, add fix task and continue + +--- + +## PHASE 2: EXECUTE + +1. **Read the spec** — Check `specs/` for detailed requirements +2. **Search first** — Don't assume code doesn't exist +3. **Implement ONE task** — Complete it fully. No placeholders. +4. **Follow conventions** — Check `.context/CONVENTIONS.md` + +--- + +## PHASE 3: VALIDATE + +After implementing, run: + +```bash +go build ./... # Must compile +go test ./... # Tests must pass +go vet ./... # No vet errors +``` + +--- + +## PHASE 4: UPDATE CONTEXT + +1. Mark completed task `[x]` in `.context/TASKS.md` +2. Add `#done:YYYY-MM-DD-HHMMSS` timestamp +3. If you made an architectural decision → add to `.context/DECISIONS.md` +4. If you learned a gotcha → add to `.context/LEARNINGS.md` + +**EXIT.** Do not continue to next task. The loop will restart you. + +--- + +## CRITICAL CONSTRAINTS + +### ONE TASK ONLY +Complete ONE task, then stop. The loop handles continuation. + +### NO CHAT +Never ask questions. If blocked: +1. Add reason to task in `.context/TASKS.md` +2. Move to next task + +### MEMORY IS THE FILESYSTEM +You will not remember this conversation. Write everything important to files. + +--- + +## REFERENCE: SPECS + +| Spec | Description | +|------|-------------| +| `specs/oauth2.md` | OAuth2 authentication implementation | + +--- + +Now read `.context/TASKS.md` and begin. diff --git a/examples/demo/README.md b/examples/demo/README.md index 5dbddfc2b..5fce5b5f4 100644 --- a/examples/demo/README.md +++ b/examples/demo/README.md @@ -2,42 +2,124 @@ # Demo Project -This is a sample project demonstrating Context context structure. +This is a sample project demonstrating Context (ctx) structure and best practices. -## Using Context +## Quick Start -1. View the context files in `.context/`: - - `CONSTITUTION.md` - Inviolable rules - - `TASKS.md` - Current work items - - `CONVENTIONS.md` - Coding standards - - `ARCHITECTURE.md` - System overview - - `DECISIONS.md` - Technical decisions +```bash +# View context status +ctx status -2. Run Context commands: - ```bash - # View context status - ctx status +# Get AI-ready context packet +ctx agent - # Get AI-ready context packet - ctx agent +# Add a new task +ctx add task "Implement feature X" - # Add a new task - ctx add task "Implement feature X" +# Mark a task complete +ctx complete "feature X" - # Mark a task complete - ctx complete "feature X" +# Check for stale context +ctx drift +``` - # Check for stale context - ctx drift - ``` - -## Context Structure +## Context Files The `.context/` directory contains markdown files that provide persistent -context for AI coding assistants. This helps AI tools understand: +context for AI coding assistants: + +| File | Purpose | +|----------------------|---------------------------------------------------| +| `AGENT_PLAYBOOK.md` | **Read first** — How agents should use this system | +| `CONSTITUTION.md` | Inviolable rules — NEVER violate these | +| `TASKS.md` | Current work items with phases and timestamps | +| `CONVENTIONS.md` | Coding standards and patterns | +| `ARCHITECTURE.md` | System overview and component layout | +| `DECISIONS.md` | Technical decisions with rationale | +| `LEARNINGS.md` | Gotchas, tips, lessons learned | + +## Key Concepts + +### Agent Playbook + +`AGENT_PLAYBOOK.md` is the bootstrap file for AI agents. It explains: +- The mental model (memory = files, not conversation) +- Read order for context files +- When and how to persist learnings/decisions +- How to avoid hallucinating memory + +### Phase-Based Tasks + +Tasks in `TASKS.md` stay in their phase permanently. Use inline labels +(`#in-progress`) instead of moving tasks between sections: + +```markdown +## Phase 2: Authentication + +- [x] Implement user registration #added:2026-01-04-100000 #done:2026-01-05-140000 +- [ ] Implement OAuth2 login #added:2026-01-04-100000 #in-progress +- [ ] Add session management #added:2026-01-04-100000 +``` + +### Structured Entries + +Learnings and decisions follow structured formats with timestamps: + +```markdown +## [2026-01-15-143022] Database connections need explicit timeouts + +**Context**: What situation led to this learning + +**Lesson**: What we learned + +**Application**: How to apply it going forward +``` + +## Adding Context + +```bash +# Add a learning with full structure +ctx add learning "Title" \ + --context "What happened" \ + --lesson "What we learned" \ + --application "How to apply it" + +# Add a decision with rationale +ctx add decision "Title" \ + --context "What prompted this" \ + --rationale "Why this choice" \ + --consequences "What changes" + +# Add a task +ctx add task "Implement feature X" +``` + +## Ralph Loop Integration + +This demo includes Ralph Loop infrastructure for iterative AI development: + +| File | Purpose | +|------|---------| +| `PROMPT.md` | Directive for AI agents — defines the work loop | +| `specs/` | Detailed specifications for features | + +The Ralph Loop pattern: +1. AI reads `PROMPT.md` to understand the workflow +2. Picks ONE task from `.context/TASKS.md` +3. Reads relevant spec from `specs/` for requirements +4. Implements the task +5. Updates context files +6. Exits — the loop restarts with fresh context + +This is separate from but complementary to ctx: +- **ctx** = context persistence (`.context/`) +- **Ralph Loop** = iterative AI workflow (`PROMPT.md` + `specs/`) + +## Session Persistence + +Session dumps are saved to `.context/sessions/` with timestamps: +- `2026-01-20-164600-feature-discussion.md` — Manual session notes +- Auto-saved transcripts (if Claude Code hooks are configured) -- What rules must never be broken (CONSTITUTION) -- What work is currently in progress (TASKS) -- How code should be written (CONVENTIONS) -- How the system is organized (ARCHITECTURE) -- Why things are the way they are (DECISIONS) +This allows future sessions to understand past context without relying on +conversation memory. diff --git a/examples/demo/specs/oauth2.md b/examples/demo/specs/oauth2.md new file mode 100644 index 000000000..2349a7010 --- /dev/null +++ b/examples/demo/specs/oauth2.md @@ -0,0 +1,94 @@ +# OAuth2 Authentication Spec + +## Overview + +Implement OAuth2 authentication supporting Google and GitHub providers. + +## Requirements + +### Functional + +1. **Provider Support** + - Google OAuth2 + - GitHub OAuth2 + - Extensible provider interface for future additions + +2. **Flow** + - User clicks "Sign in with Google/GitHub" + - Redirect to provider's authorization page + - Provider redirects back with authorization code + - Exchange code for access token + - Fetch user profile from provider + - Create or update local user record + - Issue JWT session token + +3. **User Linking** + - If email already exists, link OAuth identity to existing account + - If new email, create new user account + - Store provider ID for future logins + +### Non-Functional + +- Token exchange must complete in < 2 seconds +- Handle provider downtime gracefully (show user-friendly error) +- Log all OAuth events for security auditing + +## API Endpoints + +``` +GET /auth/oauth/{provider} # Initiate OAuth flow +GET /auth/oauth/{provider}/callback # Handle OAuth callback +POST /auth/logout # Revoke session +``` + +## Data Model + +```go +type OAuthIdentity struct { + ID string `json:"id"` + UserID string `json:"user_id"` + Provider string `json:"provider"` // "google", "github" + ProviderID string `json:"provider_id"` + Email string `json:"email"` + CreatedAt time.Time `json:"created_at"` +} +``` + +## Configuration + +```yaml +oauth: + google: + client_id: ${GOOGLE_CLIENT_ID} + client_secret: ${GOOGLE_CLIENT_SECRET} + redirect_url: https://example.com/auth/oauth/google/callback + github: + client_id: ${GITHUB_CLIENT_ID} + client_secret: ${GITHUB_CLIENT_SECRET} + redirect_url: https://example.com/auth/oauth/github/callback +``` + +## Security Considerations + +- Use `state` parameter to prevent CSRF attacks +- Validate redirect URLs against allowlist +- Never log access tokens or client secrets +- Store only necessary user data from provider + +## Testing + +- Unit tests for token exchange logic +- Integration tests with mock OAuth provider +- E2E test with real providers in staging environment + +## Tasks + +These map to `.context/TASKS.md` Phase 2: + +1. [ ] Create OAuth provider interface +2. [ ] Implement Google OAuth provider +3. [ ] Implement GitHub OAuth provider +4. [ ] Add OAuth callback handler +5. [ ] Implement user linking logic +6. [ ] Add OAuth configuration loading +7. [ ] Write integration tests From 87765e9730e4cea4c18e2dcde175588ce8b424fd Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 1 Feb 2026 08:30:44 -0800 Subject: [PATCH 05/10] docs: update documentation and regenerate site Signed-off-by: Jose Alekhinne --- .../blog/2026-01-27-building-ctx-using-ctx.md | 78 +++++++++---- docs/blog/index.md | 4 +- docs/cli-reference.md | 42 +++---- docs/context-files.md | 18 +-- docs/index.md | 12 +- docs/integrations.md | 48 ++++---- docs/security.md | 2 + docs/versions.md | 4 +- .../index.html | 106 +++++++++++++----- site/blog/index.html | 10 +- site/cli-reference/index.html | 12 +- site/context-files/index.html | 18 +-- site/index.html | 11 +- site/integrations/index.html | 6 + site/search.json | 2 +- site/security/index.html | 1 + site/versions/index.html | 33 ++++-- 17 files changed, 268 insertions(+), 139 deletions(-) diff --git a/docs/blog/2026-01-27-building-ctx-using-ctx.md b/docs/blog/2026-01-27-building-ctx-using-ctx.md index 61d9d116c..6a26c1dc0 100644 --- a/docs/blog/2026-01-27-building-ctx-using-ctx.md +++ b/docs/blog/2026-01-27-building-ctx-using-ctx.md @@ -4,12 +4,17 @@ date: 2026-01-27 author: Jose Alekhinne --- -*Jose Alekhinne / 2026-01-27* +# Building ctx Using ctx + +![ctx](../images/ctx-banner.png) -# Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development +## A Meta-Experiment in AI-Assisted Development -> What happens when you build a tool designed to give AI memory, using that very -same tool to remember what you are building? +*Jose Alekhinne / 2026-01-27* + +!!! question "Can a tool design itself?" + What happens when you build a tool designed to give AI memory, + using that very same tool to remember what you are building? This is the story of `ctx`, how it evolved from a hasty "*YOLO mode*" experiment to a disciplined system for **persistent AI context**, and what I have @@ -29,7 +34,7 @@ Every developer who works with AI code generators knows the frustration: you have a deep, productive session where the AI understands your codebase, your conventions, your decisions. And then you close the terminal. -Tomorrow it's a blank slate. The AI has forgotten everything. +Tomorrow; it's a blank slate. The AI has forgotten everything. That is "*reset amnesia*", and it's not just annoying: it's expensive. @@ -52,7 +57,10 @@ Markdown files for decisions, learnings, tasks, and conventions. The AI reads these at session start and writes to them before the session ends. **The first commit** was just scaffolding. But within hours, the -**Ralph Loop**—an iterative AI development workflow—had produced a working CLI: +[**Ralph Loop**][ralph]—an iterative AI development workflow—had produced +a working CLI: + +[ralph]: https://ctx.ist/autonomous-loop/ "Autonomous Loop" ``` feat(cli): implement amem init command @@ -62,10 +70,14 @@ feat(cli): implement amem agent command ... ``` -Fourteen core commands shipped in rapid succession. +Not one, not two, but a whopping **fourteen** core commands shipped in rapid +succession! I was YOLO'ing like there was no tomorrow: -auto-accept every change, let the AI run free, ship features fast. + +* auto-accept every change, +* let the AI run free, +* ship features fast. ## The Meta-Experiment: Using `amem` to Build `amem` @@ -75,8 +87,8 @@ Here's where it gets interesting: On January 20th, I asked: The answer was yes—but with a gap: -Auto-load worked (*via Claude Code's `PreToolUse` hook*), but auto-save was -missing. If the user quit with Ctrl+C, everything since the last manual save +Autoload worked (*via Claude Code's `PreToolUse` hook*), but auto-save was +missing. If the user quit, with Ctrl+C, everything since the last manual save was lost. That session became the first real test of the system. @@ -95,10 +107,15 @@ development workflow. They're complementary but separate. ### 2. Two Tiers of Context Persistence -| Tier | What | Why | Where | -|-----------|-----------------------------|-------------------------------|------------------------| -| Curated | Learnings, decisions, tasks | Quick reload, token-efficient | .context/*.md | -| Full dump | Entire conversation | Safety net, nothing lost | .context/sessions/*.md | +| Tier | What | Why | +|-----------|-----------------------------|-------------------------------| +| Curated | Learnings, decisions, tasks | Quick reload, token-efficient | +| Full dump | Entire conversation | Safety net, nothing lost | + +| Where | +|------------------------| +| .context/*.md | +| .context/sessions/*.md | ``` This session file—written by the AI to preserve its own context—became the @@ -161,7 +178,8 @@ Human-Guided Mode (Post-040ce99) - Canonical naming: Package name = folder name ``` -The fix required a human-guided refactoring session. +The fix required a **human-guided refactoring session**. I continued to do +that before every major release, from that point on. We introduced `internal/config/config.go` with semantic prefixes: @@ -178,8 +196,7 @@ const ( What I begrudgingly learned was: **YOLO mode is effective for velocity but accumulates debt**. -So I took a mental note to schedule **periodic consolidation sessions** -from that point onward. +So I took a mental note to schedule **periodic consolidation sessions**. ## The Dogfooding Test That Failed @@ -220,7 +237,7 @@ So I added: ## The Constitution versus Conventions As lessons accumulated, there was the temptation to add everything to -`CONSTITUTION.md` as "inviolable rules". +`CONSTITUTION.md` as "*inviolable rules*". But I resisted. @@ -307,7 +324,7 @@ Each **session file** is a timestamped Markdown with: * Tasks for the next session * Technical context (*platform, versions*) -These files are **not auto-loaded** (*that would bust the token budget*). +These files are **not autoloaded** (*that would bust the token budget*). They are what I see as the "*archaeological record*" of `ctx`: When the AI needs deeper information about why something was done, it @@ -347,7 +364,8 @@ and LEARNINGS.md. **Context**: Original implementation hardcoded absolute paths in hooks. This breaks when sharing configs with other developers. -**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before proceeding. +**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before +proceeding. ``` **Generic core with Claude enhancements (2026-01-20)** @@ -368,8 +386,10 @@ forgotten. Each has Context, Lesson, and Application sections: **CGO on ARM64** ```markdown -**Context**: `go test` failed with `gcc: error: unrecognized command-line option '-m64'` -**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. Always use `CGO_ENABLED=0`. +**Context**: `go test` failed with +`gcc: error: unrecognized command-line option '-m64'` +**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. +Always use `CGO_ENABLED=0`. ``` **Claude Code skills format** @@ -382,7 +402,8 @@ frontmatter (*description, argument-hint, allowed-tools*). Body is the prompt. **"Do you remember?" handling** ```markdown -**Lesson**: In a `ctx`-enabled project, "*do you remember?*" has an obvious meaning: +**Lesson**: In a `ctx`-enabled project, "*do you remember?*" +has an obvious meaning: check the `.context/` files. Don't ask for clarification—just do it. ``` @@ -487,7 +508,16 @@ If you are reading this, chances are that you already have heard about `ctx`. [github.com/ActiveMemory/ctx](https://github.com/ActiveMemory/ctx), * and the documentation lives at [ctx.ist](https://ctx.ist). -If you're a mere mortal tired of reset amnesia, give `ctx` a try. +!!! note "Session Records are a Gold Mine" +By the time of this writing, I have **more than 70 megabytes** of +**text-only** session capture, spread across >100 markdown and JSONL +files. + + I am analyzing, synthesizing, encriching them with AI, running RAG + (*Retrieval-Augmented Generation*) models on them, and the outcome + surprises me every day. + +If you are a mere mortal tired of reset amnesia, give `ctx` a try. And when you do, check `.context/sessions/` sometime. diff --git a/docs/blog/index.md b/docs/blog/index.md index 70c0e2cd6..0532cc6e4 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -3,7 +3,7 @@ title: Blog icon: lucide/newspaper --- -# Blog +![ctx](../images/ctx-banner.png) Stories, insights, and lessons learned from building and using ctx. @@ -25,4 +25,4 @@ persistence, architectural decisions --- -*More posts coming soon.* +*more posts are coming soon* diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 7c86f18e0..f840131c2 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -485,11 +485,11 @@ ctx recall list [flags] **Flags**: -| Flag | Short | Description | -|-------------------|-------|------------------------------------------| -| `--limit` | `-n` | Maximum sessions to display (default: 20)| -| `--project` | `-p` | Filter by project name | -| `--tool` | `-t` | Filter by tool (e.g., `claude-code`) | +| Flag | Short | Description | +|-------------|-------|-------------------------------------------| +| `--limit` | `-n` | Maximum sessions to display (default: 20) | +| `--project` | `-p` | Filter by project name | +| `--tool` | `-t` | Filter by tool (e.g., `claude-code`) | Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage. @@ -578,16 +578,17 @@ ctx journal site [flags] **Flags**: -| Flag | Short | Description | -|------------|-------|------------------------------------------| +| Flag | Short | Description | +|------------|-------|---------------------------------------------------| | `--output` | `-o` | Output directory (default: .context/journal-site) | -| `--build` | | Run zensical build after generating | -| `--serve` | | Run zensical serve after generating | +| `--build` | | Run zensical build after generating | +| `--serve` | | Run zensical serve after generating | -Creates a zensical-compatible site structure with an index page listing +Creates a `zensical`-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry. -Requires zensical to be installed for `--build` or `--serve`: +Requires `zensical` to be installed for `--build` or `--serve`: + ```bash pip install zensical ``` @@ -605,7 +606,7 @@ ctx journal site --serve # Generate and serve locally ### `ctx serve` -Serve a static site locally via zensical. +Serve a static site locally via `zensical`. ```bash ctx serve [directory] @@ -613,7 +614,8 @@ ctx serve [directory] If no directory is specified, serves the journal site (`.context/journal-site`). -Requires zensical to be installed: +Requires `zensical` to be installed: + ```bash pip install zensical ``` @@ -800,13 +802,13 @@ ctx loop [flags] **Flags**: -| Flag | Short | Description | Default | -|--------------------------|-------|-------------------------------------------------|--------------------| -| `--tool ` | `-t` | AI tool: `claude`, `aider`, or `generic` | `claude` | -| `--prompt ` | `-p` | Prompt file to use | `PROMPT.md` | -| `--max-iterations ` | `-n` | Maximum iterations (0 = unlimited) | `0` | -| `--completion ` | `-c` | Completion signal to detect | `SYSTEM_CONVERGED` | -| `--output ` | `-o` | Output script filename | `loop.sh` | +| Flag | Short | Description | Default | +|--------------------------|-------|------------------------------------------|--------------------| +| `--tool ` | `-t` | AI tool: `claude`, `aider`, or `generic` | `claude` | +| `--prompt ` | `-p` | Prompt file to use | `PROMPT.md` | +| `--max-iterations ` | `-n` | Maximum iterations (0 = unlimited) | `0` | +| `--completion ` | `-c` | Completion signal to detect | `SYSTEM_CONVERGED` | +| `--output ` | `-o` | Output script filename | `loop.sh` | **Example**: diff --git a/docs/context-files.md b/docs/context-files.md index 8bbd1bf23..1495e0727 100644 --- a/docs/context-files.md +++ b/docs/context-files.md @@ -64,7 +64,7 @@ The priority order follows a logical progression for AI tools: --- -## CONSTITUTION.md +## `CONSTITUTION.md` **Purpose:** Define hard invariants—rules that must **NEVER** be violated, regardless of the task. @@ -107,7 +107,7 @@ is wrong. --- -## TASKS.md +## `TASKS.md` **Purpose:** Track current work, planned work, and blockers. @@ -183,7 +183,7 @@ session started vs completed work. --- -## DECISIONS.md +## `DECISIONS.md` **Purpose:** Record architectural decisions with rationale so they don't get re-debated. @@ -242,7 +242,7 @@ third-party libraries need type assertions. --- -## LEARNINGS.md +## `LEARNINGS.md` **Purpose:** Capture lessons learned, gotchas, and tips that shouldn't be forgotten. @@ -296,7 +296,7 @@ Organize learnings by topic: --- -## CONVENTIONS.md +## `CONVENTIONS.md` **Purpose**: Document project patterns, naming conventions, and standards. @@ -333,7 +333,7 @@ Organize learnings by topic: --- -## ARCHITECTURE.md +## `ARCHITECTURE.md` **Purpose**: Provide system overview and component relationships. @@ -376,7 +376,7 @@ What's in scope vs out of scope for this codebase. --- -## GLOSSARY.md +## `GLOSSARY.md` **Purpose**: Define domain terms, abbreviations, and project vocabulary. @@ -411,7 +411,7 @@ What's in scope vs out of scope for this codebase. --- -## DRIFT.md +## `DRIFT.md` **Purpose**: Define signals that the context is stale and needs updating. @@ -460,7 +460,7 @@ Update context when: --- -## AGENT_PLAYBOOK.md +## `AGENT_PLAYBOOK.md` **Purpose**: Explicit instructions for how AI tools should read, apply, and update context. diff --git a/docs/index.md b/docs/index.md index 86b9e1427..9ad22c1ec 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,9 +27,15 @@ conventions, and learnings: **Open source is better together**. -> ⭐️ **If the idea behind `ctx` resonates, a star helps it reach engineers who run into context drift every day.** -> -> → https://github.com/ActiveMemory/ctx + +!!! tip "Help `ctx` Change How AI Remembers" + **If the idea behind `ctx` resonates, a star helps it reach engineers + who run into context drift every day.** + + → https://github.com/ActiveMemory/ctx + + `ctx` is free and open source software, and **contributions are always + welcome** and appreciated. Join the community to ask questions, share feedback, and connect with other users: diff --git a/docs/integrations.md b/docs/integrations.md index 03ded8561..a09143b14 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -133,42 +133,48 @@ you can invoke in Claude Code with `/command-name`. #### Context Commands -| Command | Description | -|---------|-------------| -| `/ctx-status` | Show context summary (tasks, decisions, learnings) | -| `/ctx-agent` | Get AI-optimized context packet | -| `/ctx-save` | Save current session to `.context/sessions/` | -| `/ctx-reflect` | Review session and suggest what to persist | +| Command | Description | +|----------------|----------------------------------------------------| +| `/ctx-status` | Show context summary (tasks, decisions, learnings) | +| `/ctx-agent` | Get AI-optimized context packet | +| `/ctx-save` | Save current session to `.context/sessions/` | +| `/ctx-reflect` | Review session and suggest what to persist | #### Adding Context -| Command | Description | -|---------|-------------| -| `/ctx-add-task` | Add a task to TASKS.md | -| `/ctx-add-learning` | Add a learning to LEARNINGS.md | +| Command | Description | +|---------------------|----------------------------------------------------| +| `/ctx-add-task` | Add a task to TASKS.md | +| `/ctx-add-learning` | Add a learning to LEARNINGS.md | | `/ctx-add-decision` | Add a decision with context/rationale/consequences | -| `/ctx-archive` | Archive completed tasks | +| `/ctx-archive` | Archive completed tasks | #### Session History -| Command | Description | -|---------|-------------| -| `/ctx-recall` | Browse AI session history | -| `/ctx-journal-enrich` | Enrich a journal entry with frontmatter/tags | +| Command | Description | +|--------------------------|-------------------------------------------------| +| `/ctx-recall` | Browse AI session history | +| `/ctx-journal-enrich` | Enrich a journal entry with frontmatter/tags | | `/ctx-journal-summarize` | Generate summary of sessions over a time period | #### Blogging -| Command | Description | -|---------|-------------| -| `/ctx-blog` | Generate blog post from recent activity | +!!! tip "Blogging is a Better Way of Creating Release Notes" + The blogging workflow can also double as generating release notes: + + AI reads your git commit history and creates a "*narrative*", + which is essentially what a *release note* is for. + +| Command | Description | +|-----------------------|-------------------------------------------------| +| `/ctx-blog` | Generate blog post from recent activity | | `/ctx-blog-changelog` | Generate blog post from commit range with theme | #### Development -| Command | Description | -|---------|-------------| -| `/ctx-loop` | Generate a Ralph Loop iteration script | +| Command | Description | +|---------------------|----------------------------------------| +| `/ctx-loop` | Generate a Ralph Loop iteration script | | `/ctx-prompt-audit` | Analyze session logs for vague prompts | #### Usage Examples diff --git a/docs/security.md b/docs/security.md index cd14f4d35..bf1f0ffd5 100644 --- a/docs/security.md +++ b/docs/security.md @@ -9,6 +9,8 @@ title: Security icon: lucide/shield --- +![ctx](images/ctx-banner.png) + ## Reporting Vulnerabilities At `ctx` we take security very seriously. diff --git a/docs/versions.md b/docs/versions.md index aa0398e4a..e23bcf9c4 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -9,7 +9,9 @@ title: Version History icon: lucide/history --- -# Version History +![ctx](images/ctx-banner.png) + +## Version History Documentation snapshots for each release. Click a version to view the docs as they were at that release. diff --git a/site/blog/2026-01-27-building-ctx-using-ctx/index.html b/site/blog/2026-01-27-building-ctx-using-ctx/index.html index 2ca0ee373..76d0d2bbc 100644 --- a/site/blog/2026-01-27-building-ctx-using-ctx/index.html +++ b/site/blog/2026-01-27-building-ctx-using-ctx/index.html @@ -83,7 +83,7 @@
- + Skip to content @@ -630,6 +630,17 @@
    +
  • + + + + A Meta-Experiment in AI-Assisted Development + + + + +
  • +
  • @@ -838,6 +849,17 @@
      +
    • + + + + A Meta-Experiment in AI-Assisted Development + + + + +
    • +
    • @@ -1069,12 +1091,15 @@ +

      Building ctx Using ctx

      +

      ctx

      +

      A Meta-Experiment in AI-Assisted Development

      Jose Alekhinne / 2026-01-27

      -

      Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development

      -
      -

      What happens when you build a tool designed to give AI memory, using that very -same tool to remember what you are building?

      -
      +
      +

      Can a tool design itself?

      +

      What happens when you build a tool designed to give AI memory, +using that very same tool to remember what you are building?

      +

      This is the story of ctx, how it evolved from a hasty "YOLO mode" experiment to a disciplined system for persistent AI context, and what I have learned along the way.

      @@ -1089,7 +1114,7 @@

      AI Amnesiareset amnesia", and it's not just annoying: it's expensive.

      Every session starts with re-explaining context, re-reading files, re-discovering decisions that were already made.

      @@ -1106,24 +1131,30 @@

      The Genesis

      The first commit was just scaffolding. But within hours, the -Ralph Loop—an iterative AI development workflow—had produced a working CLI:

      +
      Ralph Loop—an iterative AI development workflow—had produced +a working CLI:

      feat(cli): implement amem init command
       feat(cli): implement amem status command
       feat(cli): implement amem add command
       feat(cli): implement amem agent command
       ...
       
      -

      Fourteen core commands shipped in rapid succession.

      -

      I was YOLO'ing like there was no tomorrow: -auto-accept every change, let the AI run free, ship features fast.

      +

      Not one, not two, but a whopping fourteen core commands shipped in rapid +succession!

      +

      I was YOLO'ing like there was no tomorrow:

      +
        +
      • auto-accept every change,
      • +
      • let the AI run free,
      • +
      • ship features fast.
      • +

      The Meta-Experiment: Using amem to Build amem

      Here's where it gets interesting: On January 20th, I asked:

      "Can I use amem to help you remember this context when I restart?"

      The answer was yes—but with a gap:

      -

      Auto-load worked (via Claude Code's PreToolUse hook), but auto-save was -missing. If the user quit with Ctrl+C, everything since the last manual save +

      Autoload worked (via Claude Code's PreToolUse hook), but auto-save was +missing. If the user quit, with Ctrl+C, everything since the last manual save was lost.

      That session became the first real test of the system.

      Here is the first session file we recorded:

      @@ -1138,10 +1169,15 @@

      The Meta-Experiment: Using ### 2. Two Tiers of Context Persistence -| Tier | What | Why | Where | -|-----------|-----------------------------|-------------------------------|------------------------| -| Curated | Learnings, decisions, tasks | Quick reload, token-efficient | .context/*.md | -| Full dump | Entire conversation | Safety net, nothing lost | .context/sessions/*.md | +| Tier | What | Why | +|-----------|-----------------------------|-------------------------------| +| Curated | Learnings, decisions, tasks | Quick reload, token-efficient | +| Full dump | Entire conversation | Safety net, nothing lost | + +| Where | +|------------------------| +| .context/*.md | +| .context/sessions/*.md |

This session file—written by the AI to preserve its own context—became the template for how ctx handles session persistence.

@@ -1206,7 +1242,8 @@

YOLO Mode: Fast, But Dangerous- Colocated tests: Tests next to implementations - Canonical naming: Package name = folder name -

The fix required a human-guided refactoring session.

+

The fix required a human-guided refactoring session. I continued to do +that before every major release, from that point on.

We introduced internal/config/config.go with semantic prefixes:

const (
     DirContext     = ".context"
@@ -1218,8 +1255,7 @@ 

YOLO Mode: Fast, But DangerousThe Dogfooding Test That Failed

On January 21st, I ran an experiment: have another Claude instance rebuild ctx from scratch using only the specs and PROMPT.md.

@@ -1247,7 +1283,7 @@

The Dogfooding Test That Failed

The Constitution versus Conventions

As lessons accumulated, there was the temptation to add everything to -CONSTITUTION.md as "inviolable rules".

+CONSTITUTION.md as "inviolable rules".

But I resisted.

The constitution should contain only truly inviolable invariants:

    @@ -1309,7 +1345,7 @@

    The Session Filesarchaeological record" of ctx: When the AI needs deeper information about why something was done, it digs into the sessions.

    @@ -1335,7 +1371,8 @@

    The Decision Log: 18 Archit
    **Context**: Original implementation hardcoded absolute paths in hooks.
     This breaks when sharing configs with other developers.
     
    -**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before proceeding.
    +**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before 
    +proceeding.
     

    Generic core with Claude enhancements (2026-01-20)

    **Context**: ctx should work with any AI tool, but Claude Code users could
    @@ -1348,16 +1385,19 @@ 

    The Learning Log: 24 Gotchas a

    The .context/LEARNINGS.md file captures gotchas that would otherwise be forgotten. Each has Context, Lesson, and Application sections:

    CGO on ARM64

    -
    **Context**: `go test` failed with `gcc: error: unrecognized command-line option '-m64'`
    -**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. Always use `CGO_ENABLED=0`.
    +
    **Context**: `go test` failed with 
    +`gcc: error: unrecognized command-line option '-m64'`
    +**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. 
    +Always use `CGO_ENABLED=0`.
     

    Claude Code skills format

    **Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`
     frontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.
     

    "Do you remember?" handling

    -
    **Lesson**: In a `ctx`-enabled project, "*do you remember?*" has an obvious meaning:
    -check the `.context/` files. Don't ask for clarification—just do it.
    +
    **Lesson**: In a `ctx`-enabled project, "*do you remember?*" 
    +has an obvious meaning:
    +check the `.context/` files. Don't ask for clarification—just do it.
     

    Task Archives: The Completed Work

    Completed tasks are archived to .context/archive/ with timestamps.

    @@ -1430,7 +1470,17 @@

    Conclusiongithub.com/ActiveMemory/ctx,
  • and the documentation lives at ctx.ist.
-

If you're a mere mortal tired of reset amnesia, give ctx a try.

+
+

Session Records are a Gold Mine

+
+

By the time of this writing, I have more than 70 megabytes of +text-only session capture, spread across >100 markdown and JSONL +files.

+
I am analyzing, synthesizing, encriching them with AI, running RAG
+(*Retrieval-Augmented Generation*) models on them, and the outcome
+surprises me every day.
+
+

If you are a mere mortal tired of reset amnesia, give ctx a try.

And when you do, check .context/sessions/ sometime.

The archaeological record might surprise you.


diff --git a/site/blog/index.html b/site/blog/index.html index 79fd88d42..34633ccb9 100644 --- a/site/blog/index.html +++ b/site/blog/index.html @@ -85,7 +85,7 @@
- + Skip to content @@ -618,8 +618,6 @@ - -

Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions


-

More posts coming soon.

+

more posts are coming soon

diff --git a/site/cli-reference/index.html b/site/cli-reference/index.html index 23ff09170..4301df643 100644 --- a/site/cli-reference/index.html +++ b/site/cli-reference/index.html @@ -2259,11 +2259,11 @@

ctx journal site -

Creates a zensical-compatible site structure with an index page listing +

Creates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

-

Requires zensical to be installed for --build or --serve: +

Requires zensical to be installed for --build or --serve:

pip install zensical
-

+

Example:

ctx journal site                    # Generate in .context/journal-site/
 ctx journal site --output ~/public  # Custom output directory
@@ -2272,13 +2272,13 @@ 

ctx journal site


ctx serve

-

Serve a static site locally via zensical.

+

Serve a static site locally via zensical.

ctx serve [directory]
 

If no directory is specified, serves the journal site (.context/journal-site).

-

Requires zensical to be installed: +

Requires zensical to be installed:

pip install zensical
-

+

Example:

ctx serve                           # Serve journal site
 ctx serve .context/journal-site     # Serve specific directory
diff --git a/site/context-files/index.html b/site/context-files/index.html
index 524b0240e..9b316ec7d 100644
--- a/site/context-files/index.html
+++ b/site/context-files/index.html
@@ -1655,7 +1655,7 @@ 

Read Order RationaleCONSTITUTION.md

+

CONSTITUTION.md

Purpose: Define hard invariants—rules that must NEVER be violated, regardless of the task.

AI tools read this first and should refuse tasks that violate these rules.

@@ -1691,7 +1691,7 @@

GuidelinesTASKS.md

+

TASKS.md

Purpose: Track current work, planned work, and blockers.

Structure

Tasks are organized by Phase — logical groupings that preserve order and @@ -1814,7 +1814,7 @@

GuidelinesMark current work with #in-progress inline tag
-

DECISIONS.md

+

DECISIONS.md

Purpose: Record architectural decisions with rationale so they don't get re-debated.

Structure

@@ -1879,7 +1879,7 @@

Status Values
-

LEARNINGS.md

+

LEARNINGS.md

Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.

Structure

@@ -1923,7 +1923,7 @@

CategoriesCONVENTIONS.md

+

CONVENTIONS.md

Purpose: Document project patterns, naming conventions, and standards.

Structure

# Conventions
@@ -1954,7 +1954,7 @@ 

GuidelinesKeep patterns minimal—only document what's non-obvious
-

ARCHITECTURE.md

+

ARCHITECTURE.md

Purpose: Provide system overview and component relationships.

Structure

# Architecture
@@ -1991,7 +1991,7 @@ 

GuidelinesUpdate when major structural changes occur
-

GLOSSARY.md

+

GLOSSARY.md

Purpose: Define domain terms, abbreviations, and project vocabulary.

Structure

# Glossary
@@ -2020,7 +2020,7 @@ 

GuidelinesInclude abbreviations used in code or docs
-

DRIFT.md

+

DRIFT.md

Purpose: Define signals that the context is stale and needs updating.

Used by ctx drift command to detect staleness.

Structure

@@ -2062,7 +2062,7 @@

Structure| LEARNINGS.md | >20 items | Consolidate or archive |


-

AGENT_PLAYBOOK.md

+

AGENT_PLAYBOOK.md

Purpose: Explicit instructions for how AI tools should read, apply, and update context.

Key Sections

diff --git a/site/index.html b/site/index.html index d831583b5..14c25497a 100644 --- a/site/index.html +++ b/site/index.html @@ -1185,10 +1185,15 @@

ctxCommunity

Open source is better together.

-
-

⭐️ If the idea behind ctx resonates, a star helps it reach engineers who run into context drift every day.

+ +
+

Help ctx Change How AI Remembers

+

If the idea behind ctx resonates, a star helps it reach engineers +who run into context drift every day.

https://github.com/ActiveMemory/ctx

-
+

ctx is free and open source software, and contributions are always +welcome and appreciated.

+

Join the community to ask questions, share feedback, and connect with other users:

    diff --git a/site/integrations/index.html b/site/integrations/index.html index eef63b151..4b3067905 100644 --- a/site/integrations/index.html +++ b/site/integrations/index.html @@ -1949,6 +1949,12 @@

    Session HistoryBlogging

    +
    +

    Blogging is a Better Way of Creating Release Notes

    +

    The blogging workflow can also double as generating release notes:

    +

    AI reads your git commit history and creates a "narrative", +which is essentially what a release note is for.

    +
    diff --git a/site/search.json b/site/search.json index 7f0f22260..77b1fde3c 100644 --- a/site/search.json +++ b/site/search.json @@ -1 +1 @@ -{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Getting Started","text":"","path":["Getting Started"],"tags":[]},{"location":"#ctx","level":2,"title":"ctx","text":"

    ctx (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. Instead of re-explaining your codebase every time, context files let AI tools remember decisions, conventions, and learnings:

    • A session is interactive.
    • ctx enables cognitive continuity.
    • Cognitive continuity enables durable, symbiotic-like human–AI workflows.
    ","path":["Getting Started"],"tags":[]},{"location":"#community","level":2,"title":"Community","text":"

    Open source is better together.

    ⭐️ If the idea behind ctx resonates, a star helps it reach engineers who run into context drift every day.

    → https://github.com/ActiveMemory/ctx

    Join the community to ask questions, share feedback, and connect with other users:

    • Discord: Chat, get help, share your workflows
    • GitHub: Star the repo, report issues, contribute
    ","path":["Getting Started"],"tags":[]},{"location":"#why","level":2,"title":"Why?","text":"

    Most AI-driven development fails not because models are weak—they fail because context is ephemeral. Every new session starts near zero:

    • You re-explain architecture
    • The AI repeats past mistakes
    • Decisions get rediscovered instead of remembered

    ctx solves this by treating context as infrastructure: files that version with your code and persist across sessions.

    ","path":["Getting Started"],"tags":[]},{"location":"#installation","level":2,"title":"Installation","text":"","path":["Getting Started"],"tags":[]},{"location":"#binary-downloads-recommended","level":3,"title":"Binary Downloads (Recommended)","text":"

    Download pre-built binaries from the releases page.

    Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-amd64\nchmod +x ctx-0.1.2-linux-amd64\nsudo mv ctx-0.1.2-linux-amd64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-arm64\nchmod +x ctx-0.1.2-linux-arm64\nsudo mv ctx-0.1.2-linux-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-darwin-arm64\nchmod +x ctx-0.1.2-darwin-arm64\nsudo mv ctx-0.1.2-darwin-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-darwin-amd64\nchmod +x ctx-0.1.2-darwin-amd64\nsudo mv ctx-0.1.2-darwin-amd64 /usr/local/bin/ctx\n

    Download ctx-0.1.2-windows-amd64.exe from the releases page and add it to your PATH.

    ","path":["Getting Started"],"tags":[]},{"location":"#verifying-checksums","level":3,"title":"Verifying Checksums","text":"

    Each binary has a corresponding .sha256 checksum file. To verify your download:

    # Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.1.2-linux-amd64.sha256\n

    On macOS, use shasum -a 256 -c instead of sha256sum -c.

    ","path":["Getting Started"],"tags":[]},{"location":"#build-from-source","level":3,"title":"Build from Source","text":"

    Building from the source gives you the latest features and bug fixes; however, it also means you will be using an unreleased version of ctx that has not been fully tested.

    Want to help test the latest features?

    If you like living on the edge and want to test the latest features before they are released, then building from source is the way to go.

    Requires Go 1.25+:

    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n# or:\n# sudo mv ctx /usr/local/bin/\n

    Verify installation:

    ctx --version\n
    ","path":["Getting Started"],"tags":[]},{"location":"#quick-start","level":2,"title":"Quick Start","text":"","path":["Getting Started"],"tags":[]},{"location":"#1-initialize-context","level":3,"title":"1. Initialize Context","text":"
    cd your-project\nctx init\n

    This creates a .context/ directory with template files and configures AI tool hooks (for Claude Code).

    ","path":["Getting Started"],"tags":[]},{"location":"#2-check-status","level":3,"title":"2. Check Status","text":"
    ctx status\n

    Shows context summary: files present, token estimate, and recent activity.

    ","path":["Getting Started"],"tags":[]},{"location":"#3-start-using-with-ai","level":3,"title":"3. Start Using with AI","text":"

    With Claude Code, context loads automatically via hooks. For other tools, paste the output of:

    ctx agent --budget 8000\n
    ","path":["Getting Started"],"tags":[]},{"location":"#4-verify-it-works","level":3,"title":"4. Verify It Works","text":"

    Ask your AI: \"Do you remember?\"

    It should cite specific context: current tasks, recent decisions, or previous session topics.

    ","path":["Getting Started"],"tags":[]},{"location":"#what-gets-created","level":2,"title":"What Gets Created","text":"
    .context/\n├── CONSTITUTION.md     # Hard rules — NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n├── DRIFT.md            # Staleness signals\n├── AGENT_PLAYBOOK.md   # How AI tools should use this\n└── sessions/           # Session snapshots\n\n.claude/                # Claude Code integration (if detected)\n├── hooks/              # Auto-save and enforcement scripts\n├── commands/           # ctx slash command definitions\n└── settings.local.json # Hook configuration\n

    See Context Files for detailed documentation of each file.

    ","path":["Getting Started"],"tags":[]},{"location":"#common-workflows","level":2,"title":"Common Workflows","text":"","path":["Getting Started"],"tags":[]},{"location":"#add-a-task","level":3,"title":"Add a Task","text":"
    ctx add task \"Implement user authentication\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#record-a-decision","level":3,"title":"Record a Decision","text":"
    ctx add decision \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequences \"Team needs PostgreSQL training\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#note-a-learning","level":3,"title":"Note a Learning","text":"
    ctx add learning \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#mark-task-complete","level":3,"title":"Mark Task Complete","text":"
    ctx complete \"user auth\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#check-for-stale-context","level":3,"title":"Check for Stale Context","text":"
    ctx drift\n
    ","path":["Getting Started"],"tags":[]},{"location":"#next-steps","level":2,"title":"Next Steps","text":"
    • Prompting Guide — Effective prompts for AI sessions
    • CLI Reference — All commands and options
    • Context Files — File formats and structure
    • Autonomous Loops — Iterative AI development workflows
    • Integrations — Setup for Claude Code, Cursor, Aider
    ","path":["Getting Started"],"tags":[]},{"location":"autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"

    Iterate until done.

    An autonomous loop is an iterative AI development workflow where an agent works on tasks until completion—without constant human intervention. Context (ctx) provides the memory that makes this possible:

    • ctx provides the memory: persistent context that survives across iterations
    • The loop provides the automation: continuous execution until done

    Together, they enable fully autonomous AI development where the agent remembers everything across iterations.

    Origin

    This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique. We use generic terminology here so the concepts remain clear regardless of trends.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"
    graph TD\n    A[Start Loop] --> B[Load PROMPT.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B
    1. Loop reads PROMPT.md and invokes AI
    2. AI loads context from .context/
    3. AI picks one task and completes it
    4. AI updates context files (mark task done, add learnings)
    5. AI commits changes
    6. Loop checks for completion signals
    7. Repeat until converged or blocked
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#quick-start-with-claude-code","level":2,"title":"Quick Start with Claude Code","text":"

    Claude Code has built-in loop support:

    # Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n

    That's it. The loop will:

    1. Read your PROMPT.md for instructions
    2. Pick tasks from .context/TASKS.md
    3. Work until SYSTEM_CONVERGED or SYSTEM_BLOCKED
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#manual-loop-setup","level":2,"title":"Manual Loop Setup","text":"

    For other AI tools, create a loop.sh:

    #!/bin/bash\n# loop.sh — an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-PROMPT.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n

    Make it executable and run:

    chmod +x loop.sh\n./loop.sh\n

    You can also generate this script with ctx loop (see CLI Reference).

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#the-promptmd-file","level":2,"title":"The PROMPT.md File","text":"

    The prompt file instructs the AI on how to work autonomously. Here's a template:

    # Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n1. `.context/CONSTITUTION.md` — NEVER violate these rules\n2. `.context/TASKS.md` — Find work to do\n3. `.context/CONVENTIONS.md` — Follow these patterns\n4. `.context/DECISIONS.md` — Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n- Mark task complete in TASKS.md\n- Add any learnings to LEARNINGS.md\n- Add any decisions to DECISIONS.md\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED` — All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED` — Cannot proceed, need human input (explain why)\n- (no signal) — More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"

    The loop watches for these signals in AI output:

    Signal Meaning When to Use SYSTEM_CONVERGED All tasks complete No pending tasks in TASKS.md SYSTEM_BLOCKED Cannot proceed Needs clarification, access, or decision BOOTSTRAP_COMPLETE Initial setup done Project scaffolding finished","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"
    I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n
    I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#why-context-loops-work-well-together","level":2,"title":"Why Context + Loops Work Well Together","text":"Without ctx With ctx Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in DECISIONS.md Learnings are lost Learnings accumulate in LEARNINGS.md Tasks can be forgotten Tasks tracked in TASKS.md","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"

    During the loop, the AI should update context files:

    Mark task complete:

    ctx complete \"implement user auth\"\n

    Or emit an update command (parsed by ctx watch):

    <context-update type=\"complete\">user auth</context-update>\n

    Add learning:

    ctx add learning \"Rate limiting requires Redis connection\"\n

    Or via update command:

    <context-update type=\"learning\">Rate limiting requires Redis connection</context-update>\n

    Record decision:

    ctx add decision \"Use JWT tokens for API authentication\"\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"

    Run ctx watch alongside the loop to automatically process context updates:

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log --auto-save\n

    The --auto-save flag periodically saves session snapshots, creating a history of the loop's progress.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#example-project-setup","level":2,"title":"Example Project Setup","text":"
    my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── PROMPT.md             # Instructions for the AI\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample TASKS.md for Autonomous Loops","text":"
    # Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n

    The loop will work through these systematically, marking each complete.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"

    Cause: AI not emitting completion signals

    Fix: Ensure PROMPT.md explicitly instructs signaling:

    End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"

    Cause: AI not updating context files

    Fix: Add explicit instructions to PROMPT.md:

    After completing a task, you MUST:\n1. Run: ctx complete \"<task>\"\n2. Add learnings: ctx add learning \"...\"\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"

    Cause: Task not marked complete before next iteration

    Fix: Ensure commit happens after context update:

    Order of operations:\n1. Complete coding work\n2. Update context files (ctx complete, ctx add)\n3. Commit ALL changes including .context/\n4. Then signal status\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"

    Cause: Constitution not read first

    Fix: Make constitution check explicit in PROMPT.md:

    BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#resources","level":2,"title":"Resources","text":"
    • Geoffrey Huntley's Ralph Wiggum Technique — Original inspiration
    • Context CLI — Command reference
    • Integrations — Tool-specific setup
    ","path":["Autonomous Loops"],"tags":[]},{"location":"cli-reference/","level":1,"title":"CLI Reference","text":"","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-cli","level":2,"title":"ctx CLI","text":"

    This is a complete reference for all ctx commands.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#global-options","level":2,"title":"Global Options","text":"

    All commands support these flags:

    Flag Description --help Show command help --version Show version --context-dir <path> Override context directory (default: .context/) --no-color Disable colored output

    The NO_COLOR=1 environment variable also disables colored output.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#commands","level":2,"title":"Commands","text":"","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-init","level":3,"title":"ctx init","text":"

    Initialize a new .context/ directory with template files.

    ctx init [flags]\n

    Flags:

    Flag Short Description --force-f Overwrite existing context files --minimal-m Only create essential files (TASKS.md, DECISIONS.md, CONSTITUTION.md) --merge Auto-merge ctx content into existing CLAUDE.md

    Creates:

    • .context/ directory with all template files
    • .claude/hooks/ with auto-save and enforcement scripts (for Claude Code)
    • .claude/commands/ with ctx slash command definitions
    • .claude/settings.local.json with hook configuration and pre-approved ctx permissions
    • CLAUDE.md with bootstrap instructions (or merges into existing)

    Example:

    # Standard initialization\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --force\n\n# Merge into existing CLAUDE.md\nctx init --merge\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-status","level":3,"title":"ctx status","text":"

    Show the current context summary.

    ctx status [flags]\n

    Flags:

    Flag Short Description --json Output as JSON --verbose-v Include file contents summary

    Output:

    • Context directory path
    • Total files and token estimate
    • Status of each file (loaded, empty, missing)
    • Recent activity (modification times)
    • Drift warnings if any

    Example:

    ctx status\nctx status --json\nctx status --verbose\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-agent","level":3,"title":"ctx agent","text":"

    Print an AI-ready context packet optimized for LLM consumption.

    ctx agent [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget (default: 8000) --format md\\|json Output format (default: md)

    Output:

    • Read order for context files
    • Constitution rules (never truncated)
    • Current tasks
    • Key conventions
    • Recent decisions

    Example:

    # Default (8000 tokens, markdown)\nctx agent\n\n# Custom budget\nctx agent --budget 4000\n\n# JSON format\nctx agent --format json\n

    Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-load","level":3,"title":"ctx load","text":"

    Load and display assembled context as AI would see it.

    ctx load [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget for assembly (default: 8000) --raw Output raw file contents without assembly

    Example:

    ctx load\nctx load --budget 16000\nctx load --raw\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-add","level":3,"title":"ctx add","text":"

    Add a new item to a context file.

    ctx add <type> <content> [flags]\n

    Types:

    Type Target File task TASKS.md decision DECISIONS.md learning LEARNINGS.md convention CONVENTIONS.md

    Flags:

    Flag Short Description --priority <level> Priority for tasks: high, medium, low--section <name>-s Target section within file --context-c Context (required for decisions and learnings) --rationale-r Rationale for decisions (required for decisions) --consequences Consequences for decisions (required for decisions) --lesson-l Key insight (required for learnings) --application-a How to apply going forward (required for learnings) --file-f Read content from file instead of argument

    Examples:

    # Add a task\nctx add task \"Implement user authentication\"\nctx add task \"Fix login bug\" --priority high\n\n# Record a decision (requires all ADR—Architectural Decision Record—fields)\nctx add decision \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequences \"Team needs PostgreSQL training\"\n\n# Note a learning (requires context, lesson, and application)\nctx add learning \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\"\n\n# Add to specific section\nctx add convention \"Use kebab-case for filenames\" --section \"Naming\"\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-complete","level":3,"title":"ctx complete","text":"

    Mark a task as completed.

    ctx complete <task-id-or-text>\n

    Arguments:

    • task-id-or-text: Task number or partial text match

    Examples:

    # By text (partial match)\nctx complete \"user auth\"\n\n# By task number\nctx complete 3\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-drift","level":3,"title":"ctx drift","text":"

    Detect stale or invalid context.

    ctx drift [flags]\n

    Flags:

    Flag Description --json Output machine-readable JSON --fix Auto-fix simple issues

    Checks:

    • Path references in ARCHITECTURE.md and CONVENTIONS.md exist
    • Task references are valid
    • Constitution rules aren't violated (heuristic)
    • Staleness indicators (old files, many completed tasks)

    Example:

    ctx drift\nctx drift --json\nctx drift --fix\n

    Exit codes:

    Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-sync","level":3,"title":"ctx sync","text":"

    Reconcile context with the current codebase state.

    ctx sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would change without modifying

    What it does:

    • Scans codebase for structural changes
    • Compares with ARCHITECTURE.md
    • Suggests documenting dependencies if package files exist
    • Identifies stale or outdated context

    Example:

    ctx sync\nctx sync --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate and clean up context files.

    • Moves completed tasks older than 7 days to the archive
    • Deduplicates the \"learning\"s with similar content
    • Removes empty sections
    ctx compact [flags]\n

    Flags:

    Flag Description --archive Create .context/archive/ for old content --no-auto-save Skip auto-saving session before compact

    Example:

    ctx compact\nctx compact --archive\nctx compact --no-auto-save\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks","level":3,"title":"ctx tasks","text":"

    Manage task archival and snapshots.

    ctx tasks <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks-archive","level":4,"title":"ctx tasks archive","text":"

    Move completed tasks from TASKS.md to a timestamped archive file.

    ctx tasks archive [flags]\n

    Flags:

    Flag Description --dry-run Preview changes without modifying files

    Archive files are stored in .context/archive/ with timestamped names (tasks-YYYY-MM-DD.md). Completed tasks (marked with [x]) are moved; pending tasks ([ ]) remain in TASKS.md.

    Example:

    ctx tasks archive\nctx tasks archive --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks-snapshot","level":4,"title":"ctx tasks snapshot","text":"

    Create a point-in-time snapshot of TASKS.md without modifying the original.

    ctx tasks snapshot [name]\n

    Arguments:

    • name: Optional name for the snapshot (defaults to \"snapshot\")

    Snapshots are stored in .context/archive/ with timestamped names (tasks-<name>-YYYY-MM-DD-HHMM.md).

    Example:

    ctx tasks snapshot\nctx tasks snapshot \"before-refactor\"\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-decisions","level":3,"title":"ctx decisions","text":"

    Manage the DECISIONS.md file.

    ctx decisions <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-decisions-reindex","level":4,"title":"ctx decisions reindex","text":"

    Regenerate the quick-reference index at the top of DECISIONS.md.

    ctx decisions reindex\n

    The index is a compact table showing date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to DECISIONS.md or when migrating existing files to use the index format.

    Example:

    ctx decisions reindex\n# ✓ Index regenerated with 12 entries\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-learnings","level":3,"title":"ctx learnings","text":"

    Manage the LEARNINGS.md file.

    ctx learnings <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-learnings-reindex","level":4,"title":"ctx learnings reindex","text":"

    Regenerate the quick-reference index at the top of LEARNINGS.md.

    ctx learnings reindex\n

    The index is a compact table showing date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to LEARNINGS.md or when migrating existing files to use the index format.

    Example:

    ctx learnings reindex\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall","level":3,"title":"ctx recall","text":"

    Browse and search AI session history from Claude Code and other tools.

    ctx recall <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-list","level":4,"title":"ctx recall list","text":"

    List all parsed sessions.

    ctx recall list [flags]\n

    Flags:

    Flag Short Description --limit-n Maximum sessions to display (default: 20) --project-p Filter by project name --tool-t Filter by tool (e.g., claude-code)

    Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.

    Example:

    ctx recall list\nctx recall list --limit 5\nctx recall list --project ctx\nctx recall list --tool claude-code\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-show","level":4,"title":"ctx recall show","text":"

    Show details of a specific session.

    ctx recall show [session-id] [flags]\n

    Flags:

    Flag Description --latest Show the most recent session --full Show full message content

    The session ID can be a full UUID, partial match, or session slug name.

    Example:

    ctx recall show abc123\nctx recall show gleaming-wobbling-sutherland\nctx recall show --latest\nctx recall show --latest --full\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-export","level":4,"title":"ctx recall export","text":"

    Export sessions to editable journal files in .context/journal/.

    ctx recall export [session-id] [flags]\n

    Flags:

    Flag Description --all Export all sessions --force Overwrite existing files

    Exported files include session metadata, tool usage summary, and the full conversation. Existing files are skipped by default to preserve your edits.

    The journal/ directory should be gitignored (like sessions/) since it contains raw conversation data.

    Example:

    ctx recall export abc123          # Export one session\nctx recall export --all           # Export all sessions\nctx recall export --all --force   # Overwrite existing exports\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-journal","level":3,"title":"ctx journal","text":"

    Analyze and synthesize exported session files.

    ctx journal <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-journal-site","level":4,"title":"ctx journal site","text":"

    Generate a static site from journal entries in .context/journal/.

    ctx journal site [flags]\n

    Flags:

    Flag Short Description --output-o Output directory (default: .context/journal-site) --build Run zensical build after generating --serve Run zensical serve after generating

    Creates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

    Requires zensical to be installed for --build or --serve:

    pip install zensical\n

    Example:

    ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-serve","level":3,"title":"ctx serve","text":"

    Serve a static site locally via zensical.

    ctx serve [directory]\n

    If no directory is specified, serves the journal site (.context/journal-site).

    Requires zensical to be installed:

    pip install zensical\n

    Example:

    ctx serve                           # Serve journal site\nctx serve .context/journal-site     # Serve specific directory\nctx serve ./docs                    # Serve docs folder\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-watch","level":3,"title":"ctx watch","text":"

    Watch for AI output and auto-apply context updates.

    Parses <context-update> XML commands from AI output and applies them to context files.

    ctx watch [flags]\n

    Flags:

    Flag Description --log <file> Log file to watch (default: stdin) --dry-run Preview updates without applying --auto-save Periodically save session snapshots

    Example:

    # Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-hook","level":3,"title":"ctx hook","text":"

    Generate AI tool integration configuration.

    ctx hook <tool>\n

    Supported tools:

    Tool Description claude-code Claude Code CLI cursor Cursor IDE aider Aider CLI copilot GitHub Copilot windsurf Windsurf IDE

    Example:

    ctx hook claude-code\nctx hook cursor\nctx hook aider\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session","level":3,"title":"ctx session","text":"

    Manage session snapshots.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-save","level":4,"title":"ctx session save","text":"

    Save the current context snapshot.

    ctx session save [topic] [flags]\n

    Flags:

    Flag Short Description --type <type>-t Session type: feature, bugfix, refactor, session

    Example:

    ctx session save\nctx session save \"feature-auth\"\nctx session save \"bugfix\" --type bugfix\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-list","level":4,"title":"ctx session list","text":"

    List saved sessions.

    ctx session list [flags]\n

    Flags:

    Flag Short Description --limit-n Maximum sessions to display (default: 10)

    Output: Table of sessions with index, date, topic, and type.

    Example:

    ctx session list\nctx session list --limit 5\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-load","level":4,"title":"ctx session load","text":"

    Load and display a previous session.

    ctx session load <index|date|topic>\n

    Arguments:

    • index: Numeric index from session list
    • date: Date pattern (e.g., 2026-01-21)
    • topic: Topic keyword match

    Example:

    ctx session load 1           # by index\nctx session load 2026-01-21  # by date\nctx session load auth        # by topic\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-parse","level":4,"title":"ctx session parse","text":"

    Parse JSONL transcript to readable markdown.

    ctx session parse <file> [flags]\n

    Flags:

    Flag Short Description --output-o Output file (default: stdout) --extract Extract decisions and learnings from transcript

    Example:

    ctx session parse ~/.claude/projects/.../transcript.jsonl\nctx session parse transcript.jsonl --extract\nctx session parse transcript.jsonl -o conversation.md\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-loop","level":3,"title":"ctx loop","text":"

    Generate a shell script for running an autonomous loop.

    An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.

    ctx loop [flags]\n

    Flags:

    Flag Short Description Default --tool <tool>-t AI tool: claude, aider, or genericclaude--prompt <file>-p Prompt file to use PROMPT.md--max-iterations <n>-n Maximum iterations (0 = unlimited) 0--completion <signal>-c Completion signal to detect SYSTEM_CONVERGED--output <file>-o Output script filename loop.sh

    Example:

    # Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n

    Usage:

    # Generate and run the loop\nctx loop\nchmod +x loop.sh\n./loop.sh\n

    See Autonomous Loops for detailed workflow documentation.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error 2 Context not found 3 Invalid arguments 4 File operation error","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description CTX_DIR Override default context directory path CTX_TOKEN_BUDGET Override default token budget NO_COLOR Disable colored output when set","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#configuration-file","level":2,"title":"Configuration File","text":"

    Optional .contextrc (YAML format) at project root:

    # .contextrc\ncontext_dir: .context # Context directory name\ntoken_budget: 8000    # Default token budget\npriority_order:       # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true    # Auto-archive old items\narchive_after_days: 7 # Days before archiving\n

    Priority order: CLI flags > Environment variables > .contextrc > Defaults

    All settings are optional. Missing values use defaults.

    ","path":["CLI Reference"],"tags":[]},{"location":"comparison/","level":1,"title":"ctx and Similar Tools","text":"","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"

    Many tools help AI think.

    ctx helps AI remember.

    Not by storing thoughts, but by preserving intent.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How ctx Differs from Similar Tools","text":"

    There are many tools in the AI ecosystem that touch parts of the context problem:

    • Some manage prompts.
    • Some retrieve data.
    • Some provide runtime context objects.
    • Some offer enterprise platforms.

    ctx focuses on a different layer entirely.

    This page explains where ctx fits, and where it intentionally does not.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"

    Most tools treat context as input.

    ctx treats context as infrastructure.

    That single difference explains nearly all of ctx's design choices.

    Question Most tools ctx Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"

    Examples include:

    • prompt templates
    • reusable system prompts
    • prompt libraries
    • prompt versioning tools

    These tools help you start a session.

    They do not help you continue one.

    Prompt tools:

    • inject text at session start
    • are ephemeral by design
    • do not evolve with the project

    ctx:

    • persists knowledge over time
    • accumulates decisions and learnings
    • makes the context part of the repository itself

    Prompt tooling and ctx are complementary; not competing. Yet, they operate at different layers.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"

    RAG systems typically:

    • index documents
    • embed text
    • retrieve chunks dynamically at runtime

    They are excellent for:

    • large knowledge bases
    • static documentation
    • reference material

    RAG answers questions like:

    \"What information might be relevant right now?\"

    ctx answers a different question:

    \"What have we already decided, learned, or committed to?\"

    Here are some key differences:

    RAG ctx Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory

    ctx does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.

    RAG belongs to the data plane; ctx defines the context control plane.

    It focuses on project memory, not knowledge search.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"

    Agent frameworks often provide:

    • task loops
    • tool orchestration
    • planner/executor patterns
    • autonomous iteration

    These systems are powerful, but they typically assume that:

    • memory is external
    • context is injected
    • state is transient

    Agent frameworks answer:

    \"How should the agent act?\"

    ctx answers:

    \"What should the agent remember?\"

    Without persistent context, agents tend to:

    • rediscover decisions
    • repeat mistakes
    • lose architectural intent

    This is why ctx pairs well with autonomous loop workflows:

    • The loop provides iteration
    • ctx provides continuity

    Together, loops become cumulative instead of forgetful.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"

    Some SDKs expose \"context\" objects that exist:

    • inside a process
    • during a request
    • for the lifetime of a call chain

    These are extremely useful and completely different.

    SDK context objects:

    • are in-memory
    • disappear when the process ends
    • are not shared across sessions

    ctx:

    • survives process restarts
    • survives new chats
    • survives new days

    They share a name, not a purpose.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"

    Enterprise platforms often provide:

    • centralized context services
    • dashboards
    • access control
    • organizational knowledge layers

    These tools are designed for:

    • teams
    • governance
    • compliance
    • managed environments

    ctx is intentionally:

    • local-first
    • file-based
    • dependency-free
    • CLI-driven
    • developer-controlled

    It does not require:

    • a server
    • a database
    • an account
    • a SaaS backend

    ctx optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#when-ctx-is-a-good-fit","level":2,"title":"When ctx Is a Good Fit","text":"

    ctx works best when:

    • you want AI work to compound over time
    • architectural decisions matter
    • context must be inspectable
    • humans and AI must share the same source of truth
    • Git history should include why, not just what
    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When ctx Is Not the Right Tool","text":"

    ctx is probably not what you want if:

    • you only need one-off prompts
    • you rely exclusively on RAG
    • you want autonomous agents without a human-readable state
    • you require centralized enterprise control
    • you want black-box memory systems

    These are valid goals; just different ones.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"context-files/","level":1,"title":"Context Files","text":"","path":["Context Files"],"tags":[]},{"location":"context-files/#context","level":2,"title":".context/","text":"

    Each context file in .context/ serves a specific purpose.

    Files are designed to be human-readable, AI-parseable, and token-efficient.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#file-overview","level":2,"title":"File Overview","text":"File Purpose Priority CONSTITUTION.md Hard rules that must NEVER be violated 1 (highest) TASKS.md Current and planned work 2 CONVENTIONS.md Project patterns and standards 3 ARCHITECTURE.md System overview and components 4 DECISIONS.md Architectural decisions with rationale 5 LEARNINGS.md Lessons learned, gotchas, tips 6 GLOSSARY.md Domain terms and abbreviations 7 DRIFT.md Staleness signals and update triggers 8 AGENT_PLAYBOOK.md Instructions for AI tools 9 (lowest)","path":["Context Files"],"tags":[]},{"location":"context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"

    The priority order follows a logical progression for AI tools:

    1. CONSTITUTION — Inviolable rules first. The AI tool must know what it cannot do before attempting anything.

    2. TASKS — Current work items. What the AI tool should focus on.

    3. CONVENTIONS — How to write code. Patterns and standards to follow when implementing tasks.

    4. ARCHITECTURE — System structure. Understanding of components and boundaries before making changes.

    5. DECISIONS — Historical context. Why things are the way they are, to avoid re-debating settled decisions.

    6. LEARNINGS — Gotchas and tips. Lessons from past work that inform current implementation.

    7. GLOSSARY — Reference material. Domain terms and abbreviations for lookup as needed.

    8. DRIFT — Staleness indicators. Lower priority since it's primarily for maintenance workflows, not active development.

    9. AGENT_PLAYBOOK — Meta instructions last. How to use this context system itself. Loaded last because the agent should understand the content (rules, tasks, patterns) before the operating manual.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#constitutionmd","level":2,"title":"CONSTITUTION.md","text":"

    Purpose: Define hard invariants—rules that must NEVER be violated, regardless of the task.

    AI tools read this first and should refuse tasks that violate these rules.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure","level":3,"title":"Structure","text":"
    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n- [ ] Never commit secrets, tokens, API keys, or credentials\n- [ ] Never store customer/user data in context files\n- [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n- [ ] All code must pass tests before commit\n- [ ] No `any` types in TypeScript without documented reason\n- [ ] No TODO comments in main branch (move to TASKS.md)\n\n## Process Invariants\n\n- [ ] All architectural changes require a decision record\n- [ ] Breaking changes require version bump\n- [ ] Generated files are never committed\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines","level":3,"title":"Guidelines","text":"
    • Keep rules minimal and absolute
    • Each rule should be enforceable (can verify compliance)
    • Use checkbox format for clarity
    • Never compromise on these rules
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#tasksmd","level":2,"title":"TASKS.md","text":"

    Purpose: Track current work, planned work, and blockers.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_1","level":3,"title":"Structure","text":"

    Tasks are organized by Phase — logical groupings that preserve order and enable replay. Tasks stay in their Phase permanently; status is tracked via checkboxes and inline tags.

    # Tasks\n\n## Phase 1: Initial Setup\n\n- [x] Set up project structure\n- [x] Configure linting and formatting\n- [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n- [ ] Implement user authentication `#priority:high`\n- [ ] Add API rate limiting `#priority:medium`\n  - Blocked by: Need to finalize auth first\n\n## Backlog\n\n- [ ] Performance optimization `#priority:low`\n- [ ] Add metrics dashboard `#priority:deferred`\n

    Key principles: - Tasks never move between sections — mark as [x] or [-] in place - Use #in-progress inline tag to indicate current work - Phase headers provide structure and replay order - Backlog section for unscheduled work

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#tags","level":3,"title":"Tags","text":"

    Use inline backtick-wrapped tags for metadata:

    Tag Values Purpose #priorityhigh, medium, low Task urgency #areacore, cli, docs, tests Codebase area #estimate1h, 4h, 1d Time estimate (optional) #in-progress (none) Currently being worked on

    Lifecycle tags (for session correlation):

    Tag Format When to add #addedYYYY-MM-DD-HHMMSS Auto-added by ctx add task#startedYYYY-MM-DD-HHMMSS When beginning work on the task #doneYYYY-MM-DD-HHMMSS When marking the task [x]

    These timestamps help correlate tasks with session files and track which session started vs completed work.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning [ ] Pending [x] Completed [-] Skipped (include reason)","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_1","level":3,"title":"Guidelines","text":"
    • Never delete tasks — mark as [x] completed or [-] skipped
    • Never move tasks between sections — use inline tags for status
    • Use ctx tasks archive periodically to move completed tasks to archive
    • Mark current work with #in-progress inline tag
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#decisionsmd","level":2,"title":"DECISIONS.md","text":"

    Purpose: Record architectural decisions with rationale so they don't get re-debated.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_2","level":3,"title":"Structure","text":"
    # Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequences**: What are the implications?\n\n**Alternatives Considered**:\n- Alternative A: Why rejected\n- Alternative B: Why rejected\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#example","level":3,"title":"Example","text":"
    ## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting new project, need to choose type checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequences**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Context Files"],"tags":[]},{"location":"context-files/#learningsmd","level":2,"title":"LEARNINGS.md","text":"

    Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_3","level":3,"title":"Structure","text":"
    # Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#example_1","level":3,"title":"Example","text":"
    ## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#categories","level":3,"title":"Categories","text":"

    Organize learnings by topic:

    • Testing
    • Build & Deploy
    • Performance
    • Security
    • Third-Party Libraries
    • Git & Workflow
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#conventionsmd","level":2,"title":"CONVENTIONS.md","text":"

    Purpose: Document project patterns, naming conventions, and standards.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_4","level":3,"title":"Structure","text":"
    # Conventions\n\n## Naming\n\n- **Files**: kebab-case for all source files\n- **Components**: PascalCase for React components\n- **Functions**: camelCase, verb-first (getUser, parseConfig)\n- **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_2","level":3,"title":"Guidelines","text":"
    • Include concrete examples
    • Explain the \"why\" not just the \"what\"
    • Keep patterns minimal—only document what's non-obvious
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#architecturemd","level":2,"title":"ARCHITECTURE.md","text":"

    Purpose: Provide system overview and component relationships.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_5","level":3,"title":"Structure","text":"
    # Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n- path/to/file.ts — Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_3","level":3,"title":"Guidelines","text":"
    • Keep diagrams simple (Mermaid works well)
    • Focus on boundaries and interfaces
    • Update when major structural changes occur
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#glossarymd","level":2,"title":"GLOSSARY.md","text":"

    Purpose: Define domain terms, abbreviations, and project vocabulary.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_6","level":3,"title":"Structure","text":"
    # Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_4","level":3,"title":"Guidelines","text":"
    • Define project-specific meanings
    • Clarify potentially ambiguous terms
    • Include abbreviations used in code or docs
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#driftmd","level":2,"title":"DRIFT.md","text":"

    Purpose: Define signals that the context is stale and needs updating.

    Used by ctx drift command to detect staleness.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_7","level":3,"title":"Structure","text":"
    # Drift Detection\n\n## Automatic Checks\n\nThese are checked by `ctx drift`:\n\n### Path References\n\n- [ ] All paths in ARCHITECTURE.md exist in filesystem\n- [ ] All paths in CONVENTIONS.md exist\n- [ ] All file references in DECISIONS.md are valid\n\n### Task References\n\n- [ ] All issues referenced in TASKS.md exist\n- [ ] No tasks older than 30 days without update\n\n### Constitution Violations\n\n- [ ] No secrets patterns detected in committed files\n\n## Manual Review Triggers\n\nUpdate context when:\n\n- [ ] New team member joins (review CONVENTIONS.md)\n- [ ] Major dependency upgraded (review ARCHITECTURE.md)\n- [ ] Sprint/milestone completed (archive old tasks)\n\n## Staleness Indicators\n\n| File            | Stale If       | Action                 |\n|-----------------|----------------|------------------------|\n| ARCHITECTURE.md | >30 days old   | Review component list  |\n| TASKS.md        | >50% completed | Archive and refresh    |\n| LEARNINGS.md    | >20 items      | Consolidate or archive |\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#agent_playbookmd","level":2,"title":"AGENT_PLAYBOOK.md","text":"

    Purpose: Explicit instructions for how AI tools should read, apply, and update context.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#key-sections","level":3,"title":"Key Sections","text":"

    Read Order: Priority order for loading context files

    When to Update: Events that trigger context updates

    How to Avoid Hallucinating Memory: Critical rules:

    1. Never assume—if not in files, you don't know it
    2. Never invent history—don't claim \"we discussed\" without evidence
    3. Verify before referencing—search files before citing
    4. When uncertain, say so
    5. Trust files over intuition

    Context Update Commands: Format for automated updates via ctx watch:

    <context-update type=\"learning\">Key takeaway from today's work</context-update>\n<context-update type=\"decision\">Use Redis for caching</context-update>\n<context-update type=\"complete\">user auth</context-update>\n

    See Integrations for full documentation.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"

    All context files follow these conventions:

    1. Headers define structure — # for title, ## for sections, ### for items
    2. Bold keys for fields — **Key**: followed by value
    3. Code blocks are literal — Never parse code block content as structure
    4. Lists are ordered — Items appear in priority/chronological order
    5. Tags are inline — Backtick-wrapped tags like #priority:high
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"

    Keep context files concise:

    • Use abbreviations in tags, not prose
    • Omit obvious words (\"The\", \"This\")
    • Prefer bullet points over paragraphs
    • Keep examples minimal but illustrative
    • Archive old completed items periodically
    ","path":["Context Files"],"tags":[]},{"location":"integrations/","level":1,"title":"AI Tool Integrations","text":"","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#ai-tool-integrations","level":2,"title":"AI Tool Integrations","text":"

    Context works with any AI tool that can read files. This guide covers setup for popular AI coding assistants.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"

    Claude Code has the deepest integration with automatic context loading and session persistence.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#automatic-setup","level":3,"title":"Automatic Setup","text":"

    Running ctx init automatically configures Claude Code:

    ctx init\n

    This creates:

    File/Directory Purpose .context/ All context files .claude/hooks/ Auto-save scripts .claude/settings.local.json Hook configuration CLAUDE.md Bootstrap instructions","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#how-it-works","level":3,"title":"How It Works","text":"
    graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]\n    F --> G[SessionEnd hook saves snapshot]
    1. Session start: Claude reads CLAUDE.md, which tells it to check .context/
    2. During session: PreToolUse hook runs ctx agent --budget 4000 before each tool use
    3. Session end: SessionEnd hook saves context snapshot to .context/sessions/
    4. Next session: Claude sees previous sessions and continues with context
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#generated-configuration","level":3,"title":"Generated Configuration","text":"

    .claude/settings.local.json:

    {\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"ctx agent --budget 4000 2>/dev/null || true\"\n          }\n        ]\n      }\n    ],\n    \"SessionEnd\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \".claude/hooks/auto-save-session.sh\"\n          }\n        ]\n      }\n    ]\n  }\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#customizing-token-budget","level":3,"title":"Customizing Token Budget","text":"

    Edit the PreToolUse command to change the token budget:

    \"command\": \"ctx agent --budget 8000 2>/dev/null || true\"\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"
    1. Start a new Claude Code session
    2. Ask: \"Do you remember?\"
    3. Claude should cite specific context:
    4. Current tasks from .context/TASKS.md
    5. Recent decisions or learnings
    6. Previous session topics from .context/sessions/
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check ctx is in PATH: which ctx No sessions saved Verify .claude/settings.local.json has SessionEnd hook Hook errors Check script permissions: chmod +x .claude/hooks/*.sh Missing sessions dir Create it: mkdir -p .context/sessions","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"

    If hooks aren't working, manually load context:

    # Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#slash-commands","level":3,"title":"Slash Commands","text":"

    ctx init installs slash commands to .claude/commands/. These are shortcuts you can invoke in Claude Code with /command-name.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#context-commands","level":4,"title":"Context Commands","text":"Command Description /ctx-status Show context summary (tasks, decisions, learnings) /ctx-agent Get AI-optimized context packet /ctx-save Save current session to .context/sessions//ctx-reflect Review session and suggest what to persist","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#adding-context","level":4,"title":"Adding Context","text":"Command Description /ctx-add-task Add a task to TASKS.md /ctx-add-learning Add a learning to LEARNINGS.md /ctx-add-decision Add a decision with context/rationale/consequences /ctx-archive Archive completed tasks","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#session-history","level":4,"title":"Session History","text":"Command Description /ctx-recall Browse AI session history /ctx-journal-enrich Enrich a journal entry with frontmatter/tags /ctx-journal-summarize Generate summary of sessions over a time period","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#blogging","level":4,"title":"Blogging","text":"Command Description /ctx-blog Generate blog post from recent activity /ctx-blog-changelog Generate blog post from commit range with theme","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#development","level":4,"title":"Development","text":"Command Description /ctx-loop Generate a Ralph Loop iteration script /ctx-prompt-audit Analyze session logs for vague prompts","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-examples","level":4,"title":"Usage Examples","text":"
    /ctx-status\n/ctx-add-learning \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-summarize last week\n

    Slash commands support partial matching where applicable (e.g., session slugs).

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"

    Cursor can use context files through its system prompt or by reading files directly.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup","level":3,"title":"Setup","text":"
    # Generate Cursor configuration\nctx hook cursor\n\n# Initialize context\nctx init --minimal\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration","level":3,"title":"Configuration","text":"

    Add to Cursor settings (.cursor/settings.json):

    // split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage","level":3,"title":"Usage","text":"
    1. Open your project in Cursor
    2. Context files are available in the file tree
    3. Reference them in prompts: \"Check .context/DECISIONS.md for our approach to...\"
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"

    For more control, paste context directly:

    # Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n

    Paste into Cursor's chat.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#aider","level":2,"title":"Aider","text":"

    Aider works well with context files through its --read flag.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_1","level":3,"title":"Setup","text":"
    # Generate Aider configuration\nctx hook aider\n\n# Initialize context\nctx init\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration_1","level":3,"title":"Configuration","text":"

    Create .aider.conf.yml:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage_1","level":3,"title":"Usage","text":"
    # Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"

    Run ctx watch alongside Aider to capture context updates:

    # Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"

    Copilot reads open files for context. Keep context files open or reference them in comments.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_2","level":3,"title":"Setup","text":"
    # Generate Copilot tips\nctx hook copilot\n\n# Initialize context\nctx init --minimal\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-patterns","level":3,"title":"Usage Patterns","text":"

    Pattern 1: Keep context files open

    Open .context/CONVENTIONS.md in a split pane. Copilot will reference it.

    Pattern 2: Reference in comments

    // See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n

    Pattern 3: Paste context into Copilot Chat

    ctx agent --budget 2000\n

    Paste output into Copilot Chat for context-aware responses.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"

    Windsurf supports custom instructions and file-based context.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_3","level":3,"title":"Setup","text":"
    # Generate Windsurf configuration\nctx hook windsurf\n\n# Initialize context\nctx init\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration_2","level":3,"title":"Configuration","text":"

    Add to Windsurf settings:

    // Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage_2","level":3,"title":"Usage","text":"

    Context files appear in the file tree. Reference them when chatting:

    • \"What's in our task list?\" → AI reads .context/TASKS.md
    • \"What convention do we use for naming?\" → AI reads .context/CONVENTIONS.md
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#generic-integration","level":2,"title":"Generic Integration","text":"

    For any AI tool that can read files, use these patterns:

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"
    # Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"
    You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#automated-updates","level":3,"title":"Automated Updates","text":"

    If your AI tool outputs to a log, use ctx watch:

    # Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    The AI can emit updates like:

    <context-update type=\"learning\">Important thing learned today</context-update>\n<context-update type=\"complete\">implement caching</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"

    The ctx watch command parses update commands from AI output. Use this format:

    <context-update type=\"TYPE\" [attributes]>Content</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes task TASKS.md None decision DECISIONS.md context, rationale, consequenceslearning LEARNINGS.md context, lesson, applicationconvention CONVENTIONS.md None complete TASKS.md None","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (tasks, conventions, complete)","text":"
    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (learnings, decisions)","text":"

    Learnings and decisions support structured attributes for better documentation:

    Learning with full structure:

    <context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Use jq to parse: COMMAND=$(echo $INPUT | jq -r .tool_input.command)\"\n>Hook Input Format</context-update>\n

    Decision with full structure:

    <context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequences=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n

    If attributes are omitted, placeholders are inserted that should be updated manually.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#legacy-format-still-supported","level":3,"title":"Legacy Format (still supported)","text":"

    Simple format without attributes still works but creates placeholder text:

    <context-update type=\"learning\">Mock functions must be hoisted</context-update>\n<context-update type=\"decision\">Use PostgreSQL for primary database</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-with-ctx-watch","level":3,"title":"Usage with ctx watch","text":"
    # Pipe AI output through watch\nyour-ai-tool | ctx watch\n\n# Or watch a log file\nctx watch --log /tmp/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"prompting-guide/","level":1,"title":"Prompting Guide","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#prompting-guide","level":2,"title":"Prompting Guide","text":"

    Effective prompts for working with AI assistants in ctx-enabled projects.

    Tip

    AI assistants may not automatically read context files.

    The right prompt triggers the right behavior.

    This guide documents prompts that reliably produce good results.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#do-you-remember","level":3,"title":"\"Do you remember?\"","text":"

    Triggers the AI to read AGENT_PLAYBOOK, CONSTITUTION, sessions/, and other context files before responding.

    Use this during the start of every important session.

    Do you remember what we were working on?\n

    This question implies prior context exists. So, the AI checks files rather than admitting ignorance.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the current state?\"","text":"

    Prompts reading of TASKS.md, recent sessions, and status overview.

    Use this when resuming work after a break.

    Variants:

    • \"Where did we leave off?\"
    • \"What's in progress?\"
    • \"Show me the open tasks\"
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why doesn't X work?\"","text":"

    This triggers root cause analysis rather than surface-level fixes.

    Use this when something fails unexpectedly.

    Framing as \"why\" encourages investigation before action. The AI will trace through code, check configurations, and identify the actual cause.

    Real Example

    \"Why can't I run /ctx-save?\" led to discovering missing permissions in settings.local.json bootstrapping—a fix that benefited all users.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is this consistent with our decisions?\"","text":"

    This prompts checking DECISIONS.md before implementing.

    Use this before making architectural choices.

    Variants:

    • \"Check if we've decided on this before\"
    • \"Does this align with our conventions?\"
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-would-break-if-we","level":3,"title":"\"What would break if we...\"","text":"

    This triggers defensive thinking and impact analysis.

    Use this before making significant changes.

    What would break if we change the Settings struct?\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before you start, read X\"","text":"

    This ensures specific context is loaded before work begins.

    Use this when you know the relevant context exists in a specific file.

    Before you start, read .context/sessions/2026-01-20-auth-discussion.md\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-did-we-learn","level":3,"title":"\"What did we learn?\"","text":"

    This prompts reflection on the session and often triggers adding learnings to LEARNINGS.md.

    Use this after completing a task or debugging session.

    This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add this as a learning/decision\"","text":"

    This is an explicit persistence request.

    Use this when you have discovered something worth remembering.

    Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save context before we end\"","text":"

    This triggers context persistence before the session closes.

    Use it at the end of the session or before switching topics.

    Variants:

    • \"Let's persist what we did\"
    • \"Update the context files\"
    • /ctx-save (slash command in Claude Code)
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the codebase for X\"","text":"

    This triggers thorough codebase search rather than guessing.

    Use this when you need to understand how something works.

    This works because \"Explore\" signals that investigation is needed, not immediate action.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How does X work in this codebase?\"","text":"

    This prompts reading actual code rather than explaining general concepts.

    Use this to understand the existing implementation.

    How does session saving work in this codebase?\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find all places where X\"","text":"

    This triggers a comprehensive search across the codebase.

    Use this before refactoring or understanding the impact.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What should we document from this?\"","text":"

    This prompts identifying learnings, decisions, and conventions worth persisting.

    Use this after complex discussions or implementations.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is this the right approach?\"","text":"

    This invites the AI to challenge the current direction.

    Use this when you want a sanity check.

    This works because it allows AI to disagree. AIs often default to agreeing; this prompt signals you want an honest assessment.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-am-i-missing","level":3,"title":"\"What am I missing?\"","text":"

    This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.

    Use this before finalizing a design or implementation.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"

    Based on our ctx development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:

    Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#quick-reference","level":2,"title":"Quick Reference","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect \"What did we learn?\" Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"

    Tasks in TASKS.md are indirect prompts to the AI. How you write them shapes how the AI approaches the work.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"

    Bad task (implementation-focused):

    - [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?

    Good task (deliverable-focused):

    - [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx recall list` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    Now the AI knows the subtasks serve a specific user-facing deliverable.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"

    For complex tasks, add explicit \"done when\" criteria:

    - [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n

    This prevents premature \"task complete\" when only the implementation details are done but the feature doesn't actually work.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"

    Completing all subtasks does not mean the parent task is complete.

    The parent task describes what the user gets. Subtasks describe how to build it.

    Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#contributing","level":2,"title":"Contributing","text":"

    Found a prompt that works well? Open an issue or PR with:

    1. The prompt text
    2. What behavior it triggers
    3. When to use it
    4. Why it works (optional but helpful)
    ","path":["Prompting Guide"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"

    At ctx we take security very seriously.

    If you discover a security vulnerability in ctx, please report it responsibly.

    Do NOT open a public issue for security vulnerabilities.

    ","path":["Security"],"tags":[]},{"location":"security/#email","level":3,"title":"Email","text":"

    Send details to security@ctx.ist

    ","path":["Security"],"tags":[]},{"location":"security/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"
    1. Go to the Security tab
    2. Click \"Report a vulnerability\"
    3. Provide a detailed description
    ","path":["Security"],"tags":[]},{"location":"security/#what-to-include","level":3,"title":"What to Include","text":"
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)
    ","path":["Security"],"tags":[]},{"location":"security/#response-timeline","level":3,"title":"Response Timeline","text":"Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security"],"tags":[]},{"location":"security/#security-design","level":2,"title":"Security Design","text":"

    ctx is designed with security in mind:

    • No secrets in context: The constitution explicitly forbids storing secrets, tokens, API keys, or credentials in .context/ files
    • Local only: ctx runs entirely locally with no external network calls
    • No code execution: ctx reads and writes Markdown files only; it does not execute arbitrary code
    • Git-tracked: Core context files are meant to be committed, so they should never contain sensitive data. Exception: sessions/ and journal/ contain raw conversation data and should be gitignored
    ","path":["Security"],"tags":[]},{"location":"security/#best-practices","level":2,"title":"Best Practices","text":"
    1. Review before committing: Always review .context/ files before committing
    2. Use .gitignore: If you must store sensitive notes locally, add them to .gitignore
    3. Drift detection: Run ctx drift to check for potential issues
    ","path":["Security"],"tags":[]},{"location":"security/#attribution","level":2,"title":"Attribution","text":"

    We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).

    ","path":["Security"],"tags":[]},{"location":"versions/","level":1,"title":"Version History","text":"

    Documentation snapshots for each release. Click a version to view the docs as they were at that release.

    ","path":["Version History"],"tags":[]},{"location":"versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.1.2 2026-01-27 View docs v0.1.1 2026-01-26 View docs v0.1.0 2026-01-25 View docs","path":["Version History"],"tags":[]},{"location":"versions/#latest","level":2,"title":"Latest","text":"

    The main documentation always reflects the latest development version.

    For the most recent stable release, see v0.1.2.

    ","path":["Version History"],"tags":[]},{"location":"versions/#changelog","level":2,"title":"Changelog","text":"

    For detailed changes between versions, see the GitHub Releases page.

    ","path":["Version History"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"

    Stories, insights, and lessons learned from building and using ctx.

    ","path":["Blog"],"tags":[]},{"location":"blog/#posts","level":2,"title":"Posts","text":"","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / January 27, 2026

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of ctx—how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what we learned along the way.

    Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions

    More posts coming soon.

    ","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / 2026-01-27

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":1,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you are building?

    This is the story of ctx, how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what I have learned along the way.

    Context is a Record

    Context is a persistent record.

    By \"context\", I don’t mean model memory or stored thoughts:

    I mean the durable record of decisions, learnings, and intent that normally evaporates between sessions.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"

    Every developer who works with AI code generators knows the frustration: you have a deep, productive session where the AI understands your codebase, your conventions, your decisions. And then you close the terminal.

    Tomorrow it's a blank slate. The AI has forgotten everything.

    That is \"reset amnesia\", and it's not just annoying: it's expensive.

    Every session starts with re-explaining context, re-reading files, re-discovering decisions that were already made.

    I Needed Context

    I don't want to lose this discussion... I am a brain-dead developer YOLO'ing my way out

    ☝️ that's exactly what I said to Claude when I first started working on ctx.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"

    The project started as \"Active Memory\" (amem): a CLI tool to persist AI context across sessions.

    The core idea was simple: create a .context/ directory with structured Markdown files for decisions, learnings, tasks, and conventions. The AI reads these at session start and writes to them before the session ends.

    The first commit was just scaffolding. But within hours, the Ralph Loop—an iterative AI development workflow—had produced a working CLI:

    feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n

    Fourteen core commands shipped in rapid succession.

    I was YOLO'ing like there was no tomorrow: auto-accept every change, let the AI run free, ship features fast.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using amem to Build amem","text":"

    Here's where it gets interesting: On January 20th, I asked:

    \"Can I use amem to help you remember this context when I restart?\"

    The answer was yes—but with a gap:

    Auto-load worked (via Claude Code's PreToolUse hook), but auto-save was missing. If the user quit with Ctrl+C, everything since the last manual save was lost.

    That session became the first real test of the system.

    Here is the first session file we recorded:

    ## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: amem is for context management, Ralph Loop is for \ndevelopment workflow. They're complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           | Where                  |\n|-----------|-----------------------------|-------------------------------|------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient | .context/*.md          |\n| Full dump | Entire conversation         | Safety net, nothing lost      | .context/sessions/*.md |\n

    This session file—written by the AI to preserve its own context—became the template for how ctx handles session persistence.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"

    By January 21st, I realized \"Active Memory\" was too generic, and (arguably) too marketing-smelly.

    Besides, the binary was already called ctx (short for Context), the directory was .context/, and the slash commands would be /ctx-*.

    So it followed that the project should be renamed to ctx to make things make sense.

    The rename touched 100+ files but was clean—a find-and-replace with Go's type system catching any misses.

    The git history tells the story:

    0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, But Dangerous","text":"

    The Ralph Loop made feature development incredibly fast.

    But it created technical debt that I didn't notice until later.

    A comparison session on January 25th revealed the patterns:

    YOLO Pattern What We Found \"TASKS.md\" scattered in 10 files Same string literal everywhere, no constants dir + \"/\" + file Should be filepath.Join() Monolithic cli_test.go (1500+ lines) Tests disconnected from implementations package initcmd in init/ folder Go's \"init\" is reserved—subtle naming collision

    Here is another analysis made by ctx:

    ● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n

    The fix required a human-guided refactoring session.

    We introduced internal/config/config.go with semantic prefixes:

    const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n

    What I begrudgingly learned was: YOLO mode is effective for velocity but accumulates debt.

    So I took a mental note to schedule periodic consolidation sessions from that point onward.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"

    On January 21st, I ran an experiment: have another Claude instance rebuild ctx from scratch using only the specs and PROMPT.md.

    The Ralph Loop ran, all tasks got checked off, the loop exited successfully.

    But the binary was broken!

    Commands just printed help text instead of executing.

    All tasks were marked \"complete\" but the implementation didn't work.

    Here's what ctx discovered:

    ## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute — they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n

    This was humbling—to say the least.

    I realized, I had the same blind spot in my own codebase: no integration tests that actually invoked the binary.

    So I added:

    • Integration tests for all commands
    • Coverage targets (60-80% per package)
    • Smoke tests in CI
    • A constitution rule: \"All code must pass tests before commit\"
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"

    As lessons accumulated, there was the temptation to add everything to CONSTITUTION.md as \"inviolable rules\".

    But I resisted.

    The constitution should contain only truly inviolable invariants:

    • Security (no secrets, no customer data)
    • Quality (tests must pass)
    • Process (decisions need records)
    • ctx invocation (always use PATH, never fallback)

    Everything else—coding style, file organization, naming conventions—should go in to CONVENTIONS.md.

    Here's how ctx explained why the distinction was important:

    \"Overly strict constitution creates friction and gets ignored. Conventions can be bent; constitution cannot.\" — Decision record, 2026-01-25

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder Than They Look","text":"

    Claude Code hooks seemed simple: run a script before/after certain events.

    But I hit multiple gotchas:

    1. Key names matter

    // WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n

    2. Blocking requires specific output

    # WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n

    3. Go's JSON escaping

    json.Marshal escapes >, <, & as unicode (\\u003e) by default.

    When generating shell commands in JSON:

    encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n

    4. Regex overfitting

    Our hook to block non-PATH ctx invocations initially matched too broadly:

    # WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"

    By the time of this writing this project's ctx sessions (.context/sessions/) contains 40+ files from this project's development.

    They are not part of the source code due to security, privacy, and size concerns.

    However, they are invaluable for the project's progress.

    Each session file is a timestamped Markdown with:

    • Summary of what has been accomplished
    • Key decisions made
    • Learnings discovered
    • Tasks for the next session
    • Technical context (platform, versions)

    These files are not auto-loaded (that would bust the token budget).

    They are what I see as the \"archaeological record\" of ctx: When the AI needs deeper information about why something was done, it digs into the sessions.

    Auto-generated session files use a naming convention:

    2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n

    Also, the SessionEnd hook captures transcripts automatically. Even Ctrl+Cis caught.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"

    ctx helps record every significant architectural choice in .context/DECISIONS.md.

    Here are some highlights:

    Reverse-chronological order (2026-01-27)

    **Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n

    PATH over hardcoded paths (2026-01-21)

    **Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before proceeding.\n

    Generic core with Claude enhancements (2026-01-20)

    **Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"

    The .context/LEARNINGS.md file captures gotchas that would otherwise be forgotten. Each has Context, Lesson, and Application sections:

    CGO on ARM64

    **Context**: `go test` failed with `gcc: error: unrecognized command-line option '-m64'`\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. Always use `CGO_ENABLED=0`.\n

    Claude Code skills format

    **Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n

    \"Do you remember?\" handling

    **Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" has an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification—just do it.\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"

    Completed tasks are archived to .context/archive/ with timestamps.

    The archive from January 23rd shows 13 phases of work:

    • Phase 1: Project Scaffolding (Go module, Cobra CLI)
    • Phase 2-4: Core Commands (init, status, agent, add, complete, drift, sync, compact, watch, hook)
    • Phase 5: Session Management (save, list, load, parse, --extract)
    • Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)
    • Phase 7: Testing & Verification
    • Phase 8: Task Archival
    • Phase 9: Slash Commands
    • Phase 9b: Ralph Loop Integration
    • Phase 10: Project Rename
    • Phase 11: Documentation
    • Phase 12: Timestamp Correlation
    • Phase 13: Rich Context Entries

    That's an impressive 173 commits across 8 days of development.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned About AI-Assisted Development","text":"

    1. Memory changes everything

    When the AI remembers decisions, it doesn't repeat mistakes. When it knows your conventions, it follows them.

    ctx makes the AI a better collaborator because it's not starting from zero.

    2. Two-tier persistence works

    Curated context (DECISIONS.md, LEARNINGS.md, TASKS.md) is for quick reload.

    Full session dumps are for archaeology.

    It's a futile effort to try to fit everything in the token budget.

    Persist more, load less.

    3. YOLO mode has its place

    For rapid prototyping, letting the AI run free is effective.

    But I had to schedule consolidation sessions.

    Technical debt accumulates silently.

    4. The constitution should be small

    Only truly inviolable rules go in CONSTITUTION.md. Everything else is a convention.

    If you put too much in the constitution, it will get ignored.

    5. Verification is non-negotiable

    \"All tasks complete\" means nothing if you haven't run the tests.

    Integration tests that invoke the actual binary caught bugs that the unit tests missed.

    6. Session files are underrated

    The ability to grep through 40 session files and find exactly when and why a decision was made helped me a lot.

    It's not about loading them into context: It is about having them when you need them.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"

    The next phase of ctx is the Recall System:

    • Parser: Parse session capture markdowns, enrich with JSONL data
    • Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI
    • Server: Local HTTP server for browsing sessions
    • Search: Inverted index for searching across sessions
    • CLI: ctx recall serve <path> to start the server

    The goal is to make the archaeological record browsable—not just grep-able.

    Because not everyone always lives in the terminal—me included.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"

    Building ctx using ctx was a meta-experiment in AI-assisted development.

    I learned that memory isn't just convenient—it's transformative:

    • An AI that remembers your decisions doesn't repeat mistakes.
    • An AI that knows your conventions doesn't need them re-explained.

    If you are reading this, chances are that you already have heard about ctx.

    • ctx is open source at github.com/ActiveMemory/ctx,
    • and the documentation lives at ctx.ist.

    If you're a mere mortal tired of reset amnesia, give ctx a try.

    And when you do, check .context/sessions/ sometime.

    The archaeological record might surprise you.

    This blog post was written with the help of ctx with full access to the ctx session files, decision log, learning log, task archives, and git history of ctx—The meta continues.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]}]} \ No newline at end of file +{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Getting Started","text":"","path":["Getting Started"],"tags":[]},{"location":"#ctx","level":2,"title":"ctx","text":"

    ctx (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. Instead of re-explaining your codebase every time, context files let AI tools remember decisions, conventions, and learnings:

    • A session is interactive.
    • ctx enables cognitive continuity.
    • Cognitive continuity enables durable, symbiotic-like human–AI workflows.
    ","path":["Getting Started"],"tags":[]},{"location":"#community","level":2,"title":"Community","text":"

    Open source is better together.

    Help ctx Change How AI Remembers

    If the idea behind ctx resonates, a star helps it reach engineers who run into context drift every day.

    → https://github.com/ActiveMemory/ctx

    ctx is free and open source software, and contributions are always welcome and appreciated.

    Join the community to ask questions, share feedback, and connect with other users:

    • Discord: Chat, get help, share your workflows
    • GitHub: Star the repo, report issues, contribute
    ","path":["Getting Started"],"tags":[]},{"location":"#why","level":2,"title":"Why?","text":"

    Most AI-driven development fails not because models are weak—they fail because context is ephemeral. Every new session starts near zero:

    • You re-explain architecture
    • The AI repeats past mistakes
    • Decisions get rediscovered instead of remembered

    ctx solves this by treating context as infrastructure: files that version with your code and persist across sessions.

    ","path":["Getting Started"],"tags":[]},{"location":"#installation","level":2,"title":"Installation","text":"","path":["Getting Started"],"tags":[]},{"location":"#binary-downloads-recommended","level":3,"title":"Binary Downloads (Recommended)","text":"

    Download pre-built binaries from the releases page.

    Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-amd64\nchmod +x ctx-0.1.2-linux-amd64\nsudo mv ctx-0.1.2-linux-amd64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-arm64\nchmod +x ctx-0.1.2-linux-arm64\nsudo mv ctx-0.1.2-linux-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-darwin-arm64\nchmod +x ctx-0.1.2-darwin-arm64\nsudo mv ctx-0.1.2-darwin-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-darwin-amd64\nchmod +x ctx-0.1.2-darwin-amd64\nsudo mv ctx-0.1.2-darwin-amd64 /usr/local/bin/ctx\n

    Download ctx-0.1.2-windows-amd64.exe from the releases page and add it to your PATH.

    ","path":["Getting Started"],"tags":[]},{"location":"#verifying-checksums","level":3,"title":"Verifying Checksums","text":"

    Each binary has a corresponding .sha256 checksum file. To verify your download:

    # Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.1.2/ctx-0.1.2-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.1.2-linux-amd64.sha256\n

    On macOS, use shasum -a 256 -c instead of sha256sum -c.

    ","path":["Getting Started"],"tags":[]},{"location":"#build-from-source","level":3,"title":"Build from Source","text":"

    Building from the source gives you the latest features and bug fixes; however, it also means you will be using an unreleased version of ctx that has not been fully tested.

    Want to help test the latest features?

    If you like living on the edge and want to test the latest features before they are released, then building from source is the way to go.

    Requires Go 1.25+:

    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n# or:\n# sudo mv ctx /usr/local/bin/\n

    Verify installation:

    ctx --version\n
    ","path":["Getting Started"],"tags":[]},{"location":"#quick-start","level":2,"title":"Quick Start","text":"","path":["Getting Started"],"tags":[]},{"location":"#1-initialize-context","level":3,"title":"1. Initialize Context","text":"
    cd your-project\nctx init\n

    This creates a .context/ directory with template files and configures AI tool hooks (for Claude Code).

    ","path":["Getting Started"],"tags":[]},{"location":"#2-check-status","level":3,"title":"2. Check Status","text":"
    ctx status\n

    Shows context summary: files present, token estimate, and recent activity.

    ","path":["Getting Started"],"tags":[]},{"location":"#3-start-using-with-ai","level":3,"title":"3. Start Using with AI","text":"

    With Claude Code, context loads automatically via hooks. For other tools, paste the output of:

    ctx agent --budget 8000\n
    ","path":["Getting Started"],"tags":[]},{"location":"#4-verify-it-works","level":3,"title":"4. Verify It Works","text":"

    Ask your AI: \"Do you remember?\"

    It should cite specific context: current tasks, recent decisions, or previous session topics.

    ","path":["Getting Started"],"tags":[]},{"location":"#what-gets-created","level":2,"title":"What Gets Created","text":"
    .context/\n├── CONSTITUTION.md     # Hard rules — NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n├── DRIFT.md            # Staleness signals\n├── AGENT_PLAYBOOK.md   # How AI tools should use this\n└── sessions/           # Session snapshots\n\n.claude/                # Claude Code integration (if detected)\n├── hooks/              # Auto-save and enforcement scripts\n├── commands/           # ctx slash command definitions\n└── settings.local.json # Hook configuration\n

    See Context Files for detailed documentation of each file.

    ","path":["Getting Started"],"tags":[]},{"location":"#common-workflows","level":2,"title":"Common Workflows","text":"","path":["Getting Started"],"tags":[]},{"location":"#add-a-task","level":3,"title":"Add a Task","text":"
    ctx add task \"Implement user authentication\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#record-a-decision","level":3,"title":"Record a Decision","text":"
    ctx add decision \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequences \"Team needs PostgreSQL training\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#note-a-learning","level":3,"title":"Note a Learning","text":"
    ctx add learning \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#mark-task-complete","level":3,"title":"Mark Task Complete","text":"
    ctx complete \"user auth\"\n
    ","path":["Getting Started"],"tags":[]},{"location":"#check-for-stale-context","level":3,"title":"Check for Stale Context","text":"
    ctx drift\n
    ","path":["Getting Started"],"tags":[]},{"location":"#next-steps","level":2,"title":"Next Steps","text":"
    • Prompting Guide — Effective prompts for AI sessions
    • CLI Reference — All commands and options
    • Context Files — File formats and structure
    • Autonomous Loops — Iterative AI development workflows
    • Integrations — Setup for Claude Code, Cursor, Aider
    ","path":["Getting Started"],"tags":[]},{"location":"autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"

    Iterate until done.

    An autonomous loop is an iterative AI development workflow where an agent works on tasks until completion—without constant human intervention. Context (ctx) provides the memory that makes this possible:

    • ctx provides the memory: persistent context that survives across iterations
    • The loop provides the automation: continuous execution until done

    Together, they enable fully autonomous AI development where the agent remembers everything across iterations.

    Origin

    This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique. We use generic terminology here so the concepts remain clear regardless of trends.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"
    graph TD\n    A[Start Loop] --> B[Load PROMPT.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B
    1. Loop reads PROMPT.md and invokes AI
    2. AI loads context from .context/
    3. AI picks one task and completes it
    4. AI updates context files (mark task done, add learnings)
    5. AI commits changes
    6. Loop checks for completion signals
    7. Repeat until converged or blocked
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#quick-start-with-claude-code","level":2,"title":"Quick Start with Claude Code","text":"

    Claude Code has built-in loop support:

    # Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n

    That's it. The loop will:

    1. Read your PROMPT.md for instructions
    2. Pick tasks from .context/TASKS.md
    3. Work until SYSTEM_CONVERGED or SYSTEM_BLOCKED
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#manual-loop-setup","level":2,"title":"Manual Loop Setup","text":"

    For other AI tools, create a loop.sh:

    #!/bin/bash\n# loop.sh — an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-PROMPT.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n

    Make it executable and run:

    chmod +x loop.sh\n./loop.sh\n

    You can also generate this script with ctx loop (see CLI Reference).

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#the-promptmd-file","level":2,"title":"The PROMPT.md File","text":"

    The prompt file instructs the AI on how to work autonomously. Here's a template:

    # Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n1. `.context/CONSTITUTION.md` — NEVER violate these rules\n2. `.context/TASKS.md` — Find work to do\n3. `.context/CONVENTIONS.md` — Follow these patterns\n4. `.context/DECISIONS.md` — Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n- Mark task complete in TASKS.md\n- Add any learnings to LEARNINGS.md\n- Add any decisions to DECISIONS.md\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED` — All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED` — Cannot proceed, need human input (explain why)\n- (no signal) — More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"

    The loop watches for these signals in AI output:

    Signal Meaning When to Use SYSTEM_CONVERGED All tasks complete No pending tasks in TASKS.md SYSTEM_BLOCKED Cannot proceed Needs clarification, access, or decision BOOTSTRAP_COMPLETE Initial setup done Project scaffolding finished","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"
    I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n
    I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#why-context-loops-work-well-together","level":2,"title":"Why Context + Loops Work Well Together","text":"Without ctx With ctx Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in DECISIONS.md Learnings are lost Learnings accumulate in LEARNINGS.md Tasks can be forgotten Tasks tracked in TASKS.md","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"

    During the loop, the AI should update context files:

    Mark task complete:

    ctx complete \"implement user auth\"\n

    Or emit an update command (parsed by ctx watch):

    <context-update type=\"complete\">user auth</context-update>\n

    Add learning:

    ctx add learning \"Rate limiting requires Redis connection\"\n

    Or via update command:

    <context-update type=\"learning\">Rate limiting requires Redis connection</context-update>\n

    Record decision:

    ctx add decision \"Use JWT tokens for API authentication\"\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"

    Run ctx watch alongside the loop to automatically process context updates:

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log --auto-save\n

    The --auto-save flag periodically saves session snapshots, creating a history of the loop's progress.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#example-project-setup","level":2,"title":"Example Project Setup","text":"
    my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── PROMPT.md             # Instructions for the AI\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n
    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample TASKS.md for Autonomous Loops","text":"
    # Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n

    The loop will work through these systematically, marking each complete.

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"

    Cause: AI not emitting completion signals

    Fix: Ensure PROMPT.md explicitly instructs signaling:

    End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"

    Cause: AI not updating context files

    Fix: Add explicit instructions to PROMPT.md:

    After completing a task, you MUST:\n1. Run: ctx complete \"<task>\"\n2. Add learnings: ctx add learning \"...\"\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"

    Cause: Task not marked complete before next iteration

    Fix: Ensure commit happens after context update:

    Order of operations:\n1. Complete coding work\n2. Update context files (ctx complete, ctx add)\n3. Commit ALL changes including .context/\n4. Then signal status\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"

    Cause: Constitution not read first

    Fix: Make constitution check explicit in PROMPT.md:

    BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n

    ","path":["Autonomous Loops"],"tags":[]},{"location":"autonomous-loop/#resources","level":2,"title":"Resources","text":"
    • Geoffrey Huntley's Ralph Wiggum Technique — Original inspiration
    • Context CLI — Command reference
    • Integrations — Tool-specific setup
    ","path":["Autonomous Loops"],"tags":[]},{"location":"cli-reference/","level":1,"title":"CLI Reference","text":"","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-cli","level":2,"title":"ctx CLI","text":"

    This is a complete reference for all ctx commands.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#global-options","level":2,"title":"Global Options","text":"

    All commands support these flags:

    Flag Description --help Show command help --version Show version --context-dir <path> Override context directory (default: .context/) --no-color Disable colored output

    The NO_COLOR=1 environment variable also disables colored output.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#commands","level":2,"title":"Commands","text":"","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-init","level":3,"title":"ctx init","text":"

    Initialize a new .context/ directory with template files.

    ctx init [flags]\n

    Flags:

    Flag Short Description --force-f Overwrite existing context files --minimal-m Only create essential files (TASKS.md, DECISIONS.md, CONSTITUTION.md) --merge Auto-merge ctx content into existing CLAUDE.md

    Creates:

    • .context/ directory with all template files
    • .claude/hooks/ with auto-save and enforcement scripts (for Claude Code)
    • .claude/commands/ with ctx slash command definitions
    • .claude/settings.local.json with hook configuration and pre-approved ctx permissions
    • CLAUDE.md with bootstrap instructions (or merges into existing)

    Example:

    # Standard initialization\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --force\n\n# Merge into existing CLAUDE.md\nctx init --merge\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-status","level":3,"title":"ctx status","text":"

    Show the current context summary.

    ctx status [flags]\n

    Flags:

    Flag Short Description --json Output as JSON --verbose-v Include file contents summary

    Output:

    • Context directory path
    • Total files and token estimate
    • Status of each file (loaded, empty, missing)
    • Recent activity (modification times)
    • Drift warnings if any

    Example:

    ctx status\nctx status --json\nctx status --verbose\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-agent","level":3,"title":"ctx agent","text":"

    Print an AI-ready context packet optimized for LLM consumption.

    ctx agent [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget (default: 8000) --format md\\|json Output format (default: md)

    Output:

    • Read order for context files
    • Constitution rules (never truncated)
    • Current tasks
    • Key conventions
    • Recent decisions

    Example:

    # Default (8000 tokens, markdown)\nctx agent\n\n# Custom budget\nctx agent --budget 4000\n\n# JSON format\nctx agent --format json\n

    Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-load","level":3,"title":"ctx load","text":"

    Load and display assembled context as AI would see it.

    ctx load [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget for assembly (default: 8000) --raw Output raw file contents without assembly

    Example:

    ctx load\nctx load --budget 16000\nctx load --raw\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-add","level":3,"title":"ctx add","text":"

    Add a new item to a context file.

    ctx add <type> <content> [flags]\n

    Types:

    Type Target File task TASKS.md decision DECISIONS.md learning LEARNINGS.md convention CONVENTIONS.md

    Flags:

    Flag Short Description --priority <level> Priority for tasks: high, medium, low--section <name>-s Target section within file --context-c Context (required for decisions and learnings) --rationale-r Rationale for decisions (required for decisions) --consequences Consequences for decisions (required for decisions) --lesson-l Key insight (required for learnings) --application-a How to apply going forward (required for learnings) --file-f Read content from file instead of argument

    Examples:

    # Add a task\nctx add task \"Implement user authentication\"\nctx add task \"Fix login bug\" --priority high\n\n# Record a decision (requires all ADR—Architectural Decision Record—fields)\nctx add decision \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequences \"Team needs PostgreSQL training\"\n\n# Note a learning (requires context, lesson, and application)\nctx add learning \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\"\n\n# Add to specific section\nctx add convention \"Use kebab-case for filenames\" --section \"Naming\"\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-complete","level":3,"title":"ctx complete","text":"

    Mark a task as completed.

    ctx complete <task-id-or-text>\n

    Arguments:

    • task-id-or-text: Task number or partial text match

    Examples:

    # By text (partial match)\nctx complete \"user auth\"\n\n# By task number\nctx complete 3\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-drift","level":3,"title":"ctx drift","text":"

    Detect stale or invalid context.

    ctx drift [flags]\n

    Flags:

    Flag Description --json Output machine-readable JSON --fix Auto-fix simple issues

    Checks:

    • Path references in ARCHITECTURE.md and CONVENTIONS.md exist
    • Task references are valid
    • Constitution rules aren't violated (heuristic)
    • Staleness indicators (old files, many completed tasks)

    Example:

    ctx drift\nctx drift --json\nctx drift --fix\n

    Exit codes:

    Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-sync","level":3,"title":"ctx sync","text":"

    Reconcile context with the current codebase state.

    ctx sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would change without modifying

    What it does:

    • Scans codebase for structural changes
    • Compares with ARCHITECTURE.md
    • Suggests documenting dependencies if package files exist
    • Identifies stale or outdated context

    Example:

    ctx sync\nctx sync --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate and clean up context files.

    • Moves completed tasks older than 7 days to the archive
    • Deduplicates the \"learning\"s with similar content
    • Removes empty sections
    ctx compact [flags]\n

    Flags:

    Flag Description --archive Create .context/archive/ for old content --no-auto-save Skip auto-saving session before compact

    Example:

    ctx compact\nctx compact --archive\nctx compact --no-auto-save\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks","level":3,"title":"ctx tasks","text":"

    Manage task archival and snapshots.

    ctx tasks <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks-archive","level":4,"title":"ctx tasks archive","text":"

    Move completed tasks from TASKS.md to a timestamped archive file.

    ctx tasks archive [flags]\n

    Flags:

    Flag Description --dry-run Preview changes without modifying files

    Archive files are stored in .context/archive/ with timestamped names (tasks-YYYY-MM-DD.md). Completed tasks (marked with [x]) are moved; pending tasks ([ ]) remain in TASKS.md.

    Example:

    ctx tasks archive\nctx tasks archive --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-tasks-snapshot","level":4,"title":"ctx tasks snapshot","text":"

    Create a point-in-time snapshot of TASKS.md without modifying the original.

    ctx tasks snapshot [name]\n

    Arguments:

    • name: Optional name for the snapshot (defaults to \"snapshot\")

    Snapshots are stored in .context/archive/ with timestamped names (tasks-<name>-YYYY-MM-DD-HHMM.md).

    Example:

    ctx tasks snapshot\nctx tasks snapshot \"before-refactor\"\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-decisions","level":3,"title":"ctx decisions","text":"

    Manage the DECISIONS.md file.

    ctx decisions <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-decisions-reindex","level":4,"title":"ctx decisions reindex","text":"

    Regenerate the quick-reference index at the top of DECISIONS.md.

    ctx decisions reindex\n

    The index is a compact table showing date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to DECISIONS.md or when migrating existing files to use the index format.

    Example:

    ctx decisions reindex\n# ✓ Index regenerated with 12 entries\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-learnings","level":3,"title":"ctx learnings","text":"

    Manage the LEARNINGS.md file.

    ctx learnings <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-learnings-reindex","level":4,"title":"ctx learnings reindex","text":"

    Regenerate the quick-reference index at the top of LEARNINGS.md.

    ctx learnings reindex\n

    The index is a compact table showing date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to LEARNINGS.md or when migrating existing files to use the index format.

    Example:

    ctx learnings reindex\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall","level":3,"title":"ctx recall","text":"

    Browse and search AI session history from Claude Code and other tools.

    ctx recall <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-list","level":4,"title":"ctx recall list","text":"

    List all parsed sessions.

    ctx recall list [flags]\n

    Flags:

    Flag Short Description --limit-n Maximum sessions to display (default: 20) --project-p Filter by project name --tool-t Filter by tool (e.g., claude-code)

    Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.

    Example:

    ctx recall list\nctx recall list --limit 5\nctx recall list --project ctx\nctx recall list --tool claude-code\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-show","level":4,"title":"ctx recall show","text":"

    Show details of a specific session.

    ctx recall show [session-id] [flags]\n

    Flags:

    Flag Description --latest Show the most recent session --full Show full message content

    The session ID can be a full UUID, partial match, or session slug name.

    Example:

    ctx recall show abc123\nctx recall show gleaming-wobbling-sutherland\nctx recall show --latest\nctx recall show --latest --full\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-recall-export","level":4,"title":"ctx recall export","text":"

    Export sessions to editable journal files in .context/journal/.

    ctx recall export [session-id] [flags]\n

    Flags:

    Flag Description --all Export all sessions --force Overwrite existing files

    Exported files include session metadata, tool usage summary, and the full conversation. Existing files are skipped by default to preserve your edits.

    The journal/ directory should be gitignored (like sessions/) since it contains raw conversation data.

    Example:

    ctx recall export abc123          # Export one session\nctx recall export --all           # Export all sessions\nctx recall export --all --force   # Overwrite existing exports\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-journal","level":3,"title":"ctx journal","text":"

    Analyze and synthesize exported session files.

    ctx journal <subcommand>\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-journal-site","level":4,"title":"ctx journal site","text":"

    Generate a static site from journal entries in .context/journal/.

    ctx journal site [flags]\n

    Flags:

    Flag Short Description --output-o Output directory (default: .context/journal-site) --build Run zensical build after generating --serve Run zensical serve after generating

    Creates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

    Requires zensical to be installed for --build or --serve:

    pip install zensical\n

    Example:

    ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-serve","level":3,"title":"ctx serve","text":"

    Serve a static site locally via zensical.

    ctx serve [directory]\n

    If no directory is specified, serves the journal site (.context/journal-site).

    Requires zensical to be installed:

    pip install zensical\n

    Example:

    ctx serve                           # Serve journal site\nctx serve .context/journal-site     # Serve specific directory\nctx serve ./docs                    # Serve docs folder\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-watch","level":3,"title":"ctx watch","text":"

    Watch for AI output and auto-apply context updates.

    Parses <context-update> XML commands from AI output and applies them to context files.

    ctx watch [flags]\n

    Flags:

    Flag Description --log <file> Log file to watch (default: stdin) --dry-run Preview updates without applying --auto-save Periodically save session snapshots

    Example:

    # Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-hook","level":3,"title":"ctx hook","text":"

    Generate AI tool integration configuration.

    ctx hook <tool>\n

    Supported tools:

    Tool Description claude-code Claude Code CLI cursor Cursor IDE aider Aider CLI copilot GitHub Copilot windsurf Windsurf IDE

    Example:

    ctx hook claude-code\nctx hook cursor\nctx hook aider\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session","level":3,"title":"ctx session","text":"

    Manage session snapshots.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-save","level":4,"title":"ctx session save","text":"

    Save the current context snapshot.

    ctx session save [topic] [flags]\n

    Flags:

    Flag Short Description --type <type>-t Session type: feature, bugfix, refactor, session

    Example:

    ctx session save\nctx session save \"feature-auth\"\nctx session save \"bugfix\" --type bugfix\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-list","level":4,"title":"ctx session list","text":"

    List saved sessions.

    ctx session list [flags]\n

    Flags:

    Flag Short Description --limit-n Maximum sessions to display (default: 10)

    Output: Table of sessions with index, date, topic, and type.

    Example:

    ctx session list\nctx session list --limit 5\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-load","level":4,"title":"ctx session load","text":"

    Load and display a previous session.

    ctx session load <index|date|topic>\n

    Arguments:

    • index: Numeric index from session list
    • date: Date pattern (e.g., 2026-01-21)
    • topic: Topic keyword match

    Example:

    ctx session load 1           # by index\nctx session load 2026-01-21  # by date\nctx session load auth        # by topic\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-session-parse","level":4,"title":"ctx session parse","text":"

    Parse JSONL transcript to readable markdown.

    ctx session parse <file> [flags]\n

    Flags:

    Flag Short Description --output-o Output file (default: stdout) --extract Extract decisions and learnings from transcript

    Example:

    ctx session parse ~/.claude/projects/.../transcript.jsonl\nctx session parse transcript.jsonl --extract\nctx session parse transcript.jsonl -o conversation.md\n
    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#ctx-loop","level":3,"title":"ctx loop","text":"

    Generate a shell script for running an autonomous loop.

    An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.

    ctx loop [flags]\n

    Flags:

    Flag Short Description Default --tool <tool>-t AI tool: claude, aider, or genericclaude--prompt <file>-p Prompt file to use PROMPT.md--max-iterations <n>-n Maximum iterations (0 = unlimited) 0--completion <signal>-c Completion signal to detect SYSTEM_CONVERGED--output <file>-o Output script filename loop.sh

    Example:

    # Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n

    Usage:

    # Generate and run the loop\nctx loop\nchmod +x loop.sh\n./loop.sh\n

    See Autonomous Loops for detailed workflow documentation.

    ","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error 2 Context not found 3 Invalid arguments 4 File operation error","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description CTX_DIR Override default context directory path CTX_TOKEN_BUDGET Override default token budget NO_COLOR Disable colored output when set","path":["CLI Reference"],"tags":[]},{"location":"cli-reference/#configuration-file","level":2,"title":"Configuration File","text":"

    Optional .contextrc (YAML format) at project root:

    # .contextrc\ncontext_dir: .context # Context directory name\ntoken_budget: 8000    # Default token budget\npriority_order:       # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true    # Auto-archive old items\narchive_after_days: 7 # Days before archiving\n

    Priority order: CLI flags > Environment variables > .contextrc > Defaults

    All settings are optional. Missing values use defaults.

    ","path":["CLI Reference"],"tags":[]},{"location":"comparison/","level":1,"title":"ctx and Similar Tools","text":"","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"

    Many tools help AI think.

    ctx helps AI remember.

    Not by storing thoughts, but by preserving intent.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How ctx Differs from Similar Tools","text":"

    There are many tools in the AI ecosystem that touch parts of the context problem:

    • Some manage prompts.
    • Some retrieve data.
    • Some provide runtime context objects.
    • Some offer enterprise platforms.

    ctx focuses on a different layer entirely.

    This page explains where ctx fits, and where it intentionally does not.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"

    Most tools treat context as input.

    ctx treats context as infrastructure.

    That single difference explains nearly all of ctx's design choices.

    Question Most tools ctx Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"

    Examples include:

    • prompt templates
    • reusable system prompts
    • prompt libraries
    • prompt versioning tools

    These tools help you start a session.

    They do not help you continue one.

    Prompt tools:

    • inject text at session start
    • are ephemeral by design
    • do not evolve with the project

    ctx:

    • persists knowledge over time
    • accumulates decisions and learnings
    • makes the context part of the repository itself

    Prompt tooling and ctx are complementary; not competing. Yet, they operate at different layers.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"

    RAG systems typically:

    • index documents
    • embed text
    • retrieve chunks dynamically at runtime

    They are excellent for:

    • large knowledge bases
    • static documentation
    • reference material

    RAG answers questions like:

    \"What information might be relevant right now?\"

    ctx answers a different question:

    \"What have we already decided, learned, or committed to?\"

    Here are some key differences:

    RAG ctx Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory

    ctx does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.

    RAG belongs to the data plane; ctx defines the context control plane.

    It focuses on project memory, not knowledge search.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"

    Agent frameworks often provide:

    • task loops
    • tool orchestration
    • planner/executor patterns
    • autonomous iteration

    These systems are powerful, but they typically assume that:

    • memory is external
    • context is injected
    • state is transient

    Agent frameworks answer:

    \"How should the agent act?\"

    ctx answers:

    \"What should the agent remember?\"

    Without persistent context, agents tend to:

    • rediscover decisions
    • repeat mistakes
    • lose architectural intent

    This is why ctx pairs well with autonomous loop workflows:

    • The loop provides iteration
    • ctx provides continuity

    Together, loops become cumulative instead of forgetful.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"

    Some SDKs expose \"context\" objects that exist:

    • inside a process
    • during a request
    • for the lifetime of a call chain

    These are extremely useful and completely different.

    SDK context objects:

    • are in-memory
    • disappear when the process ends
    • are not shared across sessions

    ctx:

    • survives process restarts
    • survives new chats
    • survives new days

    They share a name, not a purpose.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"

    Enterprise platforms often provide:

    • centralized context services
    • dashboards
    • access control
    • organizational knowledge layers

    These tools are designed for:

    • teams
    • governance
    • compliance
    • managed environments

    ctx is intentionally:

    • local-first
    • file-based
    • dependency-free
    • CLI-driven
    • developer-controlled

    It does not require:

    • a server
    • a database
    • an account
    • a SaaS backend

    ctx optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#when-ctx-is-a-good-fit","level":2,"title":"When ctx Is a Good Fit","text":"

    ctx works best when:

    • you want AI work to compound over time
    • architectural decisions matter
    • context must be inspectable
    • humans and AI must share the same source of truth
    • Git history should include why, not just what
    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When ctx Is Not the Right Tool","text":"

    ctx is probably not what you want if:

    • you only need one-off prompts
    • you rely exclusively on RAG
    • you want autonomous agents without a human-readable state
    • you require centralized enterprise control
    • you want black-box memory systems

    These are valid goals; just different ones.

    ","path":["ctx and Similar Tools"],"tags":[]},{"location":"context-files/","level":1,"title":"Context Files","text":"","path":["Context Files"],"tags":[]},{"location":"context-files/#context","level":2,"title":".context/","text":"

    Each context file in .context/ serves a specific purpose.

    Files are designed to be human-readable, AI-parseable, and token-efficient.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#file-overview","level":2,"title":"File Overview","text":"File Purpose Priority CONSTITUTION.md Hard rules that must NEVER be violated 1 (highest) TASKS.md Current and planned work 2 CONVENTIONS.md Project patterns and standards 3 ARCHITECTURE.md System overview and components 4 DECISIONS.md Architectural decisions with rationale 5 LEARNINGS.md Lessons learned, gotchas, tips 6 GLOSSARY.md Domain terms and abbreviations 7 DRIFT.md Staleness signals and update triggers 8 AGENT_PLAYBOOK.md Instructions for AI tools 9 (lowest)","path":["Context Files"],"tags":[]},{"location":"context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"

    The priority order follows a logical progression for AI tools:

    1. CONSTITUTION — Inviolable rules first. The AI tool must know what it cannot do before attempting anything.

    2. TASKS — Current work items. What the AI tool should focus on.

    3. CONVENTIONS — How to write code. Patterns and standards to follow when implementing tasks.

    4. ARCHITECTURE — System structure. Understanding of components and boundaries before making changes.

    5. DECISIONS — Historical context. Why things are the way they are, to avoid re-debating settled decisions.

    6. LEARNINGS — Gotchas and tips. Lessons from past work that inform current implementation.

    7. GLOSSARY — Reference material. Domain terms and abbreviations for lookup as needed.

    8. DRIFT — Staleness indicators. Lower priority since it's primarily for maintenance workflows, not active development.

    9. AGENT_PLAYBOOK — Meta instructions last. How to use this context system itself. Loaded last because the agent should understand the content (rules, tasks, patterns) before the operating manual.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#constitutionmd","level":2,"title":"CONSTITUTION.md","text":"

    Purpose: Define hard invariants—rules that must NEVER be violated, regardless of the task.

    AI tools read this first and should refuse tasks that violate these rules.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure","level":3,"title":"Structure","text":"
    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n- [ ] Never commit secrets, tokens, API keys, or credentials\n- [ ] Never store customer/user data in context files\n- [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n- [ ] All code must pass tests before commit\n- [ ] No `any` types in TypeScript without documented reason\n- [ ] No TODO comments in main branch (move to TASKS.md)\n\n## Process Invariants\n\n- [ ] All architectural changes require a decision record\n- [ ] Breaking changes require version bump\n- [ ] Generated files are never committed\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines","level":3,"title":"Guidelines","text":"
    • Keep rules minimal and absolute
    • Each rule should be enforceable (can verify compliance)
    • Use checkbox format for clarity
    • Never compromise on these rules
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#tasksmd","level":2,"title":"TASKS.md","text":"

    Purpose: Track current work, planned work, and blockers.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_1","level":3,"title":"Structure","text":"

    Tasks are organized by Phase — logical groupings that preserve order and enable replay. Tasks stay in their Phase permanently; status is tracked via checkboxes and inline tags.

    # Tasks\n\n## Phase 1: Initial Setup\n\n- [x] Set up project structure\n- [x] Configure linting and formatting\n- [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n- [ ] Implement user authentication `#priority:high`\n- [ ] Add API rate limiting `#priority:medium`\n  - Blocked by: Need to finalize auth first\n\n## Backlog\n\n- [ ] Performance optimization `#priority:low`\n- [ ] Add metrics dashboard `#priority:deferred`\n

    Key principles: - Tasks never move between sections — mark as [x] or [-] in place - Use #in-progress inline tag to indicate current work - Phase headers provide structure and replay order - Backlog section for unscheduled work

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#tags","level":3,"title":"Tags","text":"

    Use inline backtick-wrapped tags for metadata:

    Tag Values Purpose #priorityhigh, medium, low Task urgency #areacore, cli, docs, tests Codebase area #estimate1h, 4h, 1d Time estimate (optional) #in-progress (none) Currently being worked on

    Lifecycle tags (for session correlation):

    Tag Format When to add #addedYYYY-MM-DD-HHMMSS Auto-added by ctx add task#startedYYYY-MM-DD-HHMMSS When beginning work on the task #doneYYYY-MM-DD-HHMMSS When marking the task [x]

    These timestamps help correlate tasks with session files and track which session started vs completed work.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning [ ] Pending [x] Completed [-] Skipped (include reason)","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_1","level":3,"title":"Guidelines","text":"
    • Never delete tasks — mark as [x] completed or [-] skipped
    • Never move tasks between sections — use inline tags for status
    • Use ctx tasks archive periodically to move completed tasks to archive
    • Mark current work with #in-progress inline tag
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#decisionsmd","level":2,"title":"DECISIONS.md","text":"

    Purpose: Record architectural decisions with rationale so they don't get re-debated.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_2","level":3,"title":"Structure","text":"
    # Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequences**: What are the implications?\n\n**Alternatives Considered**:\n- Alternative A: Why rejected\n- Alternative B: Why rejected\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#example","level":3,"title":"Example","text":"
    ## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting new project, need to choose type checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequences**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Context Files"],"tags":[]},{"location":"context-files/#learningsmd","level":2,"title":"LEARNINGS.md","text":"

    Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_3","level":3,"title":"Structure","text":"
    # Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#example_1","level":3,"title":"Example","text":"
    ## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#categories","level":3,"title":"Categories","text":"

    Organize learnings by topic:

    • Testing
    • Build & Deploy
    • Performance
    • Security
    • Third-Party Libraries
    • Git & Workflow
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#conventionsmd","level":2,"title":"CONVENTIONS.md","text":"

    Purpose: Document project patterns, naming conventions, and standards.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_4","level":3,"title":"Structure","text":"
    # Conventions\n\n## Naming\n\n- **Files**: kebab-case for all source files\n- **Components**: PascalCase for React components\n- **Functions**: camelCase, verb-first (getUser, parseConfig)\n- **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_2","level":3,"title":"Guidelines","text":"
    • Include concrete examples
    • Explain the \"why\" not just the \"what\"
    • Keep patterns minimal—only document what's non-obvious
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#architecturemd","level":2,"title":"ARCHITECTURE.md","text":"

    Purpose: Provide system overview and component relationships.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_5","level":3,"title":"Structure","text":"
    # Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n- path/to/file.ts — Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_3","level":3,"title":"Guidelines","text":"
    • Keep diagrams simple (Mermaid works well)
    • Focus on boundaries and interfaces
    • Update when major structural changes occur
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#glossarymd","level":2,"title":"GLOSSARY.md","text":"

    Purpose: Define domain terms, abbreviations, and project vocabulary.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_6","level":3,"title":"Structure","text":"
    # Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#guidelines_4","level":3,"title":"Guidelines","text":"
    • Define project-specific meanings
    • Clarify potentially ambiguous terms
    • Include abbreviations used in code or docs
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#driftmd","level":2,"title":"DRIFT.md","text":"

    Purpose: Define signals that the context is stale and needs updating.

    Used by ctx drift command to detect staleness.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#structure_7","level":3,"title":"Structure","text":"
    # Drift Detection\n\n## Automatic Checks\n\nThese are checked by `ctx drift`:\n\n### Path References\n\n- [ ] All paths in ARCHITECTURE.md exist in filesystem\n- [ ] All paths in CONVENTIONS.md exist\n- [ ] All file references in DECISIONS.md are valid\n\n### Task References\n\n- [ ] All issues referenced in TASKS.md exist\n- [ ] No tasks older than 30 days without update\n\n### Constitution Violations\n\n- [ ] No secrets patterns detected in committed files\n\n## Manual Review Triggers\n\nUpdate context when:\n\n- [ ] New team member joins (review CONVENTIONS.md)\n- [ ] Major dependency upgraded (review ARCHITECTURE.md)\n- [ ] Sprint/milestone completed (archive old tasks)\n\n## Staleness Indicators\n\n| File            | Stale If       | Action                 |\n|-----------------|----------------|------------------------|\n| ARCHITECTURE.md | >30 days old   | Review component list  |\n| TASKS.md        | >50% completed | Archive and refresh    |\n| LEARNINGS.md    | >20 items      | Consolidate or archive |\n
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#agent_playbookmd","level":2,"title":"AGENT_PLAYBOOK.md","text":"

    Purpose: Explicit instructions for how AI tools should read, apply, and update context.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#key-sections","level":3,"title":"Key Sections","text":"

    Read Order: Priority order for loading context files

    When to Update: Events that trigger context updates

    How to Avoid Hallucinating Memory: Critical rules:

    1. Never assume—if not in files, you don't know it
    2. Never invent history—don't claim \"we discussed\" without evidence
    3. Verify before referencing—search files before citing
    4. When uncertain, say so
    5. Trust files over intuition

    Context Update Commands: Format for automated updates via ctx watch:

    <context-update type=\"learning\">Key takeaway from today's work</context-update>\n<context-update type=\"decision\">Use Redis for caching</context-update>\n<context-update type=\"complete\">user auth</context-update>\n

    See Integrations for full documentation.

    ","path":["Context Files"],"tags":[]},{"location":"context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"

    All context files follow these conventions:

    1. Headers define structure — # for title, ## for sections, ### for items
    2. Bold keys for fields — **Key**: followed by value
    3. Code blocks are literal — Never parse code block content as structure
    4. Lists are ordered — Items appear in priority/chronological order
    5. Tags are inline — Backtick-wrapped tags like #priority:high
    ","path":["Context Files"],"tags":[]},{"location":"context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"

    Keep context files concise:

    • Use abbreviations in tags, not prose
    • Omit obvious words (\"The\", \"This\")
    • Prefer bullet points over paragraphs
    • Keep examples minimal but illustrative
    • Archive old completed items periodically
    ","path":["Context Files"],"tags":[]},{"location":"integrations/","level":1,"title":"AI Tool Integrations","text":"","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#ai-tool-integrations","level":2,"title":"AI Tool Integrations","text":"

    Context works with any AI tool that can read files. This guide covers setup for popular AI coding assistants.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"

    Claude Code has the deepest integration with automatic context loading and session persistence.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#automatic-setup","level":3,"title":"Automatic Setup","text":"

    Running ctx init automatically configures Claude Code:

    ctx init\n

    This creates:

    File/Directory Purpose .context/ All context files .claude/hooks/ Auto-save scripts .claude/settings.local.json Hook configuration CLAUDE.md Bootstrap instructions","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#how-it-works","level":3,"title":"How It Works","text":"
    graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]\n    F --> G[SessionEnd hook saves snapshot]
    1. Session start: Claude reads CLAUDE.md, which tells it to check .context/
    2. During session: PreToolUse hook runs ctx agent --budget 4000 before each tool use
    3. Session end: SessionEnd hook saves context snapshot to .context/sessions/
    4. Next session: Claude sees previous sessions and continues with context
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#generated-configuration","level":3,"title":"Generated Configuration","text":"

    .claude/settings.local.json:

    {\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"ctx agent --budget 4000 2>/dev/null || true\"\n          }\n        ]\n      }\n    ],\n    \"SessionEnd\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \".claude/hooks/auto-save-session.sh\"\n          }\n        ]\n      }\n    ]\n  }\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#customizing-token-budget","level":3,"title":"Customizing Token Budget","text":"

    Edit the PreToolUse command to change the token budget:

    \"command\": \"ctx agent --budget 8000 2>/dev/null || true\"\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"
    1. Start a new Claude Code session
    2. Ask: \"Do you remember?\"
    3. Claude should cite specific context:
    4. Current tasks from .context/TASKS.md
    5. Recent decisions or learnings
    6. Previous session topics from .context/sessions/
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check ctx is in PATH: which ctx No sessions saved Verify .claude/settings.local.json has SessionEnd hook Hook errors Check script permissions: chmod +x .claude/hooks/*.sh Missing sessions dir Create it: mkdir -p .context/sessions","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"

    If hooks aren't working, manually load context:

    # Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#slash-commands","level":3,"title":"Slash Commands","text":"

    ctx init installs slash commands to .claude/commands/. These are shortcuts you can invoke in Claude Code with /command-name.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#context-commands","level":4,"title":"Context Commands","text":"Command Description /ctx-status Show context summary (tasks, decisions, learnings) /ctx-agent Get AI-optimized context packet /ctx-save Save current session to .context/sessions//ctx-reflect Review session and suggest what to persist","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#adding-context","level":4,"title":"Adding Context","text":"Command Description /ctx-add-task Add a task to TASKS.md /ctx-add-learning Add a learning to LEARNINGS.md /ctx-add-decision Add a decision with context/rationale/consequences /ctx-archive Archive completed tasks","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#session-history","level":4,"title":"Session History","text":"Command Description /ctx-recall Browse AI session history /ctx-journal-enrich Enrich a journal entry with frontmatter/tags /ctx-journal-summarize Generate summary of sessions over a time period","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#blogging","level":4,"title":"Blogging","text":"

    Blogging is a Better Way of Creating Release Notes

    The blogging workflow can also double as generating release notes:

    AI reads your git commit history and creates a \"narrative\", which is essentially what a release note is for.

    Command Description /ctx-blog Generate blog post from recent activity /ctx-blog-changelog Generate blog post from commit range with theme","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#development","level":4,"title":"Development","text":"Command Description /ctx-loop Generate a Ralph Loop iteration script /ctx-prompt-audit Analyze session logs for vague prompts","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-examples","level":4,"title":"Usage Examples","text":"
    /ctx-status\n/ctx-add-learning \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-summarize last week\n

    Slash commands support partial matching where applicable (e.g., session slugs).

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"

    Cursor can use context files through its system prompt or by reading files directly.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup","level":3,"title":"Setup","text":"
    # Generate Cursor configuration\nctx hook cursor\n\n# Initialize context\nctx init --minimal\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration","level":3,"title":"Configuration","text":"

    Add to Cursor settings (.cursor/settings.json):

    // split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage","level":3,"title":"Usage","text":"
    1. Open your project in Cursor
    2. Context files are available in the file tree
    3. Reference them in prompts: \"Check .context/DECISIONS.md for our approach to...\"
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"

    For more control, paste context directly:

    # Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n

    Paste into Cursor's chat.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#aider","level":2,"title":"Aider","text":"

    Aider works well with context files through its --read flag.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_1","level":3,"title":"Setup","text":"
    # Generate Aider configuration\nctx hook aider\n\n# Initialize context\nctx init\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration_1","level":3,"title":"Configuration","text":"

    Create .aider.conf.yml:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage_1","level":3,"title":"Usage","text":"
    # Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"

    Run ctx watch alongside Aider to capture context updates:

    # Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"

    Copilot reads open files for context. Keep context files open or reference them in comments.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_2","level":3,"title":"Setup","text":"
    # Generate Copilot tips\nctx hook copilot\n\n# Initialize context\nctx init --minimal\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-patterns","level":3,"title":"Usage Patterns","text":"

    Pattern 1: Keep context files open

    Open .context/CONVENTIONS.md in a split pane. Copilot will reference it.

    Pattern 2: Reference in comments

    // See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n

    Pattern 3: Paste context into Copilot Chat

    ctx agent --budget 2000\n

    Paste output into Copilot Chat for context-aware responses.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"

    Windsurf supports custom instructions and file-based context.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#setup_3","level":3,"title":"Setup","text":"
    # Generate Windsurf configuration\nctx hook windsurf\n\n# Initialize context\nctx init\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#configuration_2","level":3,"title":"Configuration","text":"

    Add to Windsurf settings:

    // Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage_2","level":3,"title":"Usage","text":"

    Context files appear in the file tree. Reference them when chatting:

    • \"What's in our task list?\" → AI reads .context/TASKS.md
    • \"What convention do we use for naming?\" → AI reads .context/CONVENTIONS.md
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#generic-integration","level":2,"title":"Generic Integration","text":"

    For any AI tool that can read files, use these patterns:

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"
    # Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"
    You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#automated-updates","level":3,"title":"Automated Updates","text":"

    If your AI tool outputs to a log, use ctx watch:

    # Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    The AI can emit updates like:

    <context-update type=\"learning\">Important thing learned today</context-update>\n<context-update type=\"complete\">implement caching</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"

    The ctx watch command parses update commands from AI output. Use this format:

    <context-update type=\"TYPE\" [attributes]>Content</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes task TASKS.md None decision DECISIONS.md context, rationale, consequenceslearning LEARNINGS.md context, lesson, applicationconvention CONVENTIONS.md None complete TASKS.md None","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (tasks, conventions, complete)","text":"
    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (learnings, decisions)","text":"

    Learnings and decisions support structured attributes for better documentation:

    Learning with full structure:

    <context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Use jq to parse: COMMAND=$(echo $INPUT | jq -r .tool_input.command)\"\n>Hook Input Format</context-update>\n

    Decision with full structure:

    <context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequences=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n

    If attributes are omitted, placeholders are inserted that should be updated manually.

    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#legacy-format-still-supported","level":3,"title":"Legacy Format (still supported)","text":"

    Simple format without attributes still works but creates placeholder text:

    <context-update type=\"learning\">Mock functions must be hoisted</context-update>\n<context-update type=\"decision\">Use PostgreSQL for primary database</context-update>\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"integrations/#usage-with-ctx-watch","level":3,"title":"Usage with ctx watch","text":"
    # Pipe AI output through watch\nyour-ai-tool | ctx watch\n\n# Or watch a log file\nctx watch --log /tmp/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["AI Tool Integrations"],"tags":[]},{"location":"prompting-guide/","level":1,"title":"Prompting Guide","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#prompting-guide","level":2,"title":"Prompting Guide","text":"

    Effective prompts for working with AI assistants in ctx-enabled projects.

    Tip

    AI assistants may not automatically read context files.

    The right prompt triggers the right behavior.

    This guide documents prompts that reliably produce good results.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#do-you-remember","level":3,"title":"\"Do you remember?\"","text":"

    Triggers the AI to read AGENT_PLAYBOOK, CONSTITUTION, sessions/, and other context files before responding.

    Use this during the start of every important session.

    Do you remember what we were working on?\n

    This question implies prior context exists. So, the AI checks files rather than admitting ignorance.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the current state?\"","text":"

    Prompts reading of TASKS.md, recent sessions, and status overview.

    Use this when resuming work after a break.

    Variants:

    • \"Where did we leave off?\"
    • \"What's in progress?\"
    • \"Show me the open tasks\"
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why doesn't X work?\"","text":"

    This triggers root cause analysis rather than surface-level fixes.

    Use this when something fails unexpectedly.

    Framing as \"why\" encourages investigation before action. The AI will trace through code, check configurations, and identify the actual cause.

    Real Example

    \"Why can't I run /ctx-save?\" led to discovering missing permissions in settings.local.json bootstrapping—a fix that benefited all users.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is this consistent with our decisions?\"","text":"

    This prompts checking DECISIONS.md before implementing.

    Use this before making architectural choices.

    Variants:

    • \"Check if we've decided on this before\"
    • \"Does this align with our conventions?\"
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-would-break-if-we","level":3,"title":"\"What would break if we...\"","text":"

    This triggers defensive thinking and impact analysis.

    Use this before making significant changes.

    What would break if we change the Settings struct?\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before you start, read X\"","text":"

    This ensures specific context is loaded before work begins.

    Use this when you know the relevant context exists in a specific file.

    Before you start, read .context/sessions/2026-01-20-auth-discussion.md\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-did-we-learn","level":3,"title":"\"What did we learn?\"","text":"

    This prompts reflection on the session and often triggers adding learnings to LEARNINGS.md.

    Use this after completing a task or debugging session.

    This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add this as a learning/decision\"","text":"

    This is an explicit persistence request.

    Use this when you have discovered something worth remembering.

    Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save context before we end\"","text":"

    This triggers context persistence before the session closes.

    Use it at the end of the session or before switching topics.

    Variants:

    • \"Let's persist what we did\"
    • \"Update the context files\"
    • /ctx-save (slash command in Claude Code)
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the codebase for X\"","text":"

    This triggers thorough codebase search rather than guessing.

    Use this when you need to understand how something works.

    This works because \"Explore\" signals that investigation is needed, not immediate action.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How does X work in this codebase?\"","text":"

    This prompts reading actual code rather than explaining general concepts.

    Use this to understand the existing implementation.

    How does session saving work in this codebase?\n
    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find all places where X\"","text":"

    This triggers a comprehensive search across the codebase.

    Use this before refactoring or understanding the impact.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What should we document from this?\"","text":"

    This prompts identifying learnings, decisions, and conventions worth persisting.

    Use this after complex discussions or implementations.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is this the right approach?\"","text":"

    This invites the AI to challenge the current direction.

    Use this when you want a sanity check.

    This works because it allows AI to disagree. AIs often default to agreeing; this prompt signals you want an honest assessment.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#what-am-i-missing","level":3,"title":"\"What am I missing?\"","text":"

    This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.

    Use this before finalizing a design or implementation.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"

    Based on our ctx development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:

    Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#quick-reference","level":2,"title":"Quick Reference","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect \"What did we learn?\" Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\"","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"

    Tasks in TASKS.md are indirect prompts to the AI. How you write them shapes how the AI approaches the work.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"

    Bad task (implementation-focused):

    - [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?

    Good task (deliverable-focused):

    - [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx recall list` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    Now the AI knows the subtasks serve a specific user-facing deliverable.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"

    For complex tasks, add explicit \"done when\" criteria:

    - [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n

    This prevents premature \"task complete\" when only the implementation details are done but the feature doesn't actually work.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"

    Completing all subtasks does not mean the parent task is complete.

    The parent task describes what the user gets. Subtasks describe how to build it.

    Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.

    ","path":["Prompting Guide"],"tags":[]},{"location":"prompting-guide/#contributing","level":2,"title":"Contributing","text":"

    Found a prompt that works well? Open an issue or PR with:

    1. The prompt text
    2. What behavior it triggers
    3. When to use it
    4. Why it works (optional but helpful)
    ","path":["Prompting Guide"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"

    At ctx we take security very seriously.

    If you discover a security vulnerability in ctx, please report it responsibly.

    Do NOT open a public issue for security vulnerabilities.

    ","path":["Security"],"tags":[]},{"location":"security/#email","level":3,"title":"Email","text":"

    Send details to security@ctx.ist

    ","path":["Security"],"tags":[]},{"location":"security/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"
    1. Go to the Security tab
    2. Click \"Report a vulnerability\"
    3. Provide a detailed description
    ","path":["Security"],"tags":[]},{"location":"security/#what-to-include","level":3,"title":"What to Include","text":"
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)
    ","path":["Security"],"tags":[]},{"location":"security/#response-timeline","level":3,"title":"Response Timeline","text":"Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security"],"tags":[]},{"location":"security/#security-design","level":2,"title":"Security Design","text":"

    ctx is designed with security in mind:

    • No secrets in context: The constitution explicitly forbids storing secrets, tokens, API keys, or credentials in .context/ files
    • Local only: ctx runs entirely locally with no external network calls
    • No code execution: ctx reads and writes Markdown files only; it does not execute arbitrary code
    • Git-tracked: Core context files are meant to be committed, so they should never contain sensitive data. Exception: sessions/ and journal/ contain raw conversation data and should be gitignored
    ","path":["Security"],"tags":[]},{"location":"security/#best-practices","level":2,"title":"Best Practices","text":"
    1. Review before committing: Always review .context/ files before committing
    2. Use .gitignore: If you must store sensitive notes locally, add them to .gitignore
    3. Drift detection: Run ctx drift to check for potential issues
    ","path":["Security"],"tags":[]},{"location":"security/#attribution","level":2,"title":"Attribution","text":"

    We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).

    ","path":["Security"],"tags":[]},{"location":"versions/","level":1,"title":"Version History","text":"","path":["Version History"],"tags":[]},{"location":"versions/#version-history","level":2,"title":"Version History","text":"

    Documentation snapshots for each release. Click a version to view the docs as they were at that release.

    ","path":["Version History"],"tags":[]},{"location":"versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.1.2 2026-01-27 View docs v0.1.1 2026-01-26 View docs v0.1.0 2026-01-25 View docs","path":["Version History"],"tags":[]},{"location":"versions/#latest","level":2,"title":"Latest","text":"

    The main documentation always reflects the latest development version.

    For the most recent stable release, see v0.1.2.

    ","path":["Version History"],"tags":[]},{"location":"versions/#changelog","level":2,"title":"Changelog","text":"

    For detailed changes between versions, see the GitHub Releases page.

    ","path":["Version History"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"

    Stories, insights, and lessons learned from building and using ctx.

    ","path":["Blog"],"tags":[]},{"location":"blog/#posts","level":2,"title":"Posts","text":"","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / January 27, 2026

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of ctx—how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what we learned along the way.

    Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions

    more posts are coming soon

    ","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building ctx Using ctx","text":"","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#a-meta-experiment-in-ai-assisted-development","level":2,"title":"A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / 2026-01-27

    Can a tool design itself?

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you are building?

    This is the story of ctx, how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what I have learned along the way.

    Context is a Record

    Context is a persistent record.

    By \"context\", I don’t mean model memory or stored thoughts:

    I mean the durable record of decisions, learnings, and intent that normally evaporates between sessions.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"

    Every developer who works with AI code generators knows the frustration: you have a deep, productive session where the AI understands your codebase, your conventions, your decisions. And then you close the terminal.

    Tomorrow; it's a blank slate. The AI has forgotten everything.

    That is \"reset amnesia\", and it's not just annoying: it's expensive.

    Every session starts with re-explaining context, re-reading files, re-discovering decisions that were already made.

    I Needed Context

    I don't want to lose this discussion... I am a brain-dead developer YOLO'ing my way out

    ☝️ that's exactly what I said to Claude when I first started working on ctx.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"

    The project started as \"Active Memory\" (amem): a CLI tool to persist AI context across sessions.

    The core idea was simple: create a .context/ directory with structured Markdown files for decisions, learnings, tasks, and conventions. The AI reads these at session start and writes to them before the session ends.

    The first commit was just scaffolding. But within hours, the Ralph Loop—an iterative AI development workflow—had produced a working CLI:

    feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n

    Not one, not two, but a whopping fourteen core commands shipped in rapid succession!

    I was YOLO'ing like there was no tomorrow:

    • auto-accept every change,
    • let the AI run free,
    • ship features fast.
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using amem to Build amem","text":"

    Here's where it gets interesting: On January 20th, I asked:

    \"Can I use amem to help you remember this context when I restart?\"

    The answer was yes—but with a gap:

    Autoload worked (via Claude Code's PreToolUse hook), but auto-save was missing. If the user quit, with Ctrl+C, everything since the last manual save was lost.

    That session became the first real test of the system.

    Here is the first session file we recorded:

    ## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: amem is for context management, Ralph Loop is for \ndevelopment workflow. They're complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           |\n|-----------|-----------------------------|-------------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient |\n| Full dump | Entire conversation         | Safety net, nothing lost      |\n\n| Where                  |\n|------------------------|\n| .context/*.md          |\n| .context/sessions/*.md |\n

    This session file—written by the AI to preserve its own context—became the template for how ctx handles session persistence.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"

    By January 21st, I realized \"Active Memory\" was too generic, and (arguably) too marketing-smelly.

    Besides, the binary was already called ctx (short for Context), the directory was .context/, and the slash commands would be /ctx-*.

    So it followed that the project should be renamed to ctx to make things make sense.

    The rename touched 100+ files but was clean—a find-and-replace with Go's type system catching any misses.

    The git history tells the story:

    0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, But Dangerous","text":"

    The Ralph Loop made feature development incredibly fast.

    But it created technical debt that I didn't notice until later.

    A comparison session on January 25th revealed the patterns:

    YOLO Pattern What We Found \"TASKS.md\" scattered in 10 files Same string literal everywhere, no constants dir + \"/\" + file Should be filepath.Join() Monolithic cli_test.go (1500+ lines) Tests disconnected from implementations package initcmd in init/ folder Go's \"init\" is reserved—subtle naming collision

    Here is another analysis made by ctx:

    ● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n

    The fix required a human-guided refactoring session. I continued to do that before every major release, from that point on.

    We introduced internal/config/config.go with semantic prefixes:

    const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n

    What I begrudgingly learned was: YOLO mode is effective for velocity but accumulates debt.

    So I took a mental note to schedule periodic consolidation sessions.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"

    On January 21st, I ran an experiment: have another Claude instance rebuild ctx from scratch using only the specs and PROMPT.md.

    The Ralph Loop ran, all tasks got checked off, the loop exited successfully.

    But the binary was broken!

    Commands just printed help text instead of executing.

    All tasks were marked \"complete\" but the implementation didn't work.

    Here's what ctx discovered:

    ## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute — they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n

    This was humbling—to say the least.

    I realized, I had the same blind spot in my own codebase: no integration tests that actually invoked the binary.

    So I added:

    • Integration tests for all commands
    • Coverage targets (60-80% per package)
    • Smoke tests in CI
    • A constitution rule: \"All code must pass tests before commit\"
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"

    As lessons accumulated, there was the temptation to add everything to CONSTITUTION.md as \"inviolable rules\".

    But I resisted.

    The constitution should contain only truly inviolable invariants:

    • Security (no secrets, no customer data)
    • Quality (tests must pass)
    • Process (decisions need records)
    • ctx invocation (always use PATH, never fallback)

    Everything else—coding style, file organization, naming conventions—should go in to CONVENTIONS.md.

    Here's how ctx explained why the distinction was important:

    \"Overly strict constitution creates friction and gets ignored. Conventions can be bent; constitution cannot.\" — Decision record, 2026-01-25

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder Than They Look","text":"

    Claude Code hooks seemed simple: run a script before/after certain events.

    But I hit multiple gotchas:

    1. Key names matter

    // WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n

    2. Blocking requires specific output

    # WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n

    3. Go's JSON escaping

    json.Marshal escapes >, <, & as unicode (\\u003e) by default.

    When generating shell commands in JSON:

    encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n

    4. Regex overfitting

    Our hook to block non-PATH ctx invocations initially matched too broadly:

    # WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"

    By the time of this writing this project's ctx sessions (.context/sessions/) contains 40+ files from this project's development.

    They are not part of the source code due to security, privacy, and size concerns.

    However, they are invaluable for the project's progress.

    Each session file is a timestamped Markdown with:

    • Summary of what has been accomplished
    • Key decisions made
    • Learnings discovered
    • Tasks for the next session
    • Technical context (platform, versions)

    These files are not autoloaded (that would bust the token budget).

    They are what I see as the \"archaeological record\" of ctx: When the AI needs deeper information about why something was done, it digs into the sessions.

    Auto-generated session files use a naming convention:

    2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n

    Also, the SessionEnd hook captures transcripts automatically. Even Ctrl+Cis caught.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"

    ctx helps record every significant architectural choice in .context/DECISIONS.md.

    Here are some highlights:

    Reverse-chronological order (2026-01-27)

    **Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n

    PATH over hardcoded paths (2026-01-21)

    **Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before \nproceeding.\n

    Generic core with Claude enhancements (2026-01-20)

    **Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"

    The .context/LEARNINGS.md file captures gotchas that would otherwise be forgotten. Each has Context, Lesson, and Application sections:

    CGO on ARM64

    **Context**: `go test` failed with \n`gcc: error: unrecognized command-line option '-m64'`\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. \nAlways use `CGO_ENABLED=0`.\n

    Claude Code skills format

    **Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n

    \"Do you remember?\" handling

    **Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" \nhas an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification—just do it.\n
    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"

    Completed tasks are archived to .context/archive/ with timestamps.

    The archive from January 23rd shows 13 phases of work:

    • Phase 1: Project Scaffolding (Go module, Cobra CLI)
    • Phase 2-4: Core Commands (init, status, agent, add, complete, drift, sync, compact, watch, hook)
    • Phase 5: Session Management (save, list, load, parse, --extract)
    • Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)
    • Phase 7: Testing & Verification
    • Phase 8: Task Archival
    • Phase 9: Slash Commands
    • Phase 9b: Ralph Loop Integration
    • Phase 10: Project Rename
    • Phase 11: Documentation
    • Phase 12: Timestamp Correlation
    • Phase 13: Rich Context Entries

    That's an impressive 173 commits across 8 days of development.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned About AI-Assisted Development","text":"

    1. Memory changes everything

    When the AI remembers decisions, it doesn't repeat mistakes. When it knows your conventions, it follows them.

    ctx makes the AI a better collaborator because it's not starting from zero.

    2. Two-tier persistence works

    Curated context (DECISIONS.md, LEARNINGS.md, TASKS.md) is for quick reload.

    Full session dumps are for archaeology.

    It's a futile effort to try to fit everything in the token budget.

    Persist more, load less.

    3. YOLO mode has its place

    For rapid prototyping, letting the AI run free is effective.

    But I had to schedule consolidation sessions.

    Technical debt accumulates silently.

    4. The constitution should be small

    Only truly inviolable rules go in CONSTITUTION.md. Everything else is a convention.

    If you put too much in the constitution, it will get ignored.

    5. Verification is non-negotiable

    \"All tasks complete\" means nothing if you haven't run the tests.

    Integration tests that invoke the actual binary caught bugs that the unit tests missed.

    6. Session files are underrated

    The ability to grep through 40 session files and find exactly when and why a decision was made helped me a lot.

    It's not about loading them into context: It is about having them when you need them.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"

    The next phase of ctx is the Recall System:

    • Parser: Parse session capture markdowns, enrich with JSONL data
    • Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI
    • Server: Local HTTP server for browsing sessions
    • Search: Inverted index for searching across sessions
    • CLI: ctx recall serve <path> to start the server

    The goal is to make the archaeological record browsable—not just grep-able.

    Because not everyone always lives in the terminal—me included.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"

    Building ctx using ctx was a meta-experiment in AI-assisted development.

    I learned that memory isn't just convenient—it's transformative:

    • An AI that remembers your decisions doesn't repeat mistakes.
    • An AI that knows your conventions doesn't need them re-explained.

    If you are reading this, chances are that you already have heard about ctx.

    • ctx is open source at github.com/ActiveMemory/ctx,
    • and the documentation lives at ctx.ist.

    Session Records are a Gold Mine

    By the time of this writing, I have more than 70 megabytes of text-only session capture, spread across >100 markdown and JSONL files.

    I am analyzing, synthesizing, encriching them with AI, running RAG\n(*Retrieval-Augmented Generation*) models on them, and the outcome\nsurprises me every day.\n

    If you are a mere mortal tired of reset amnesia, give ctx a try.

    And when you do, check .context/sessions/ sometime.

    The archaeological record might surprise you.

    This blog post was written with the help of ctx with full access to the ctx session files, decision log, learning log, task archives, and git history of ctx—The meta continues.

    ","path":["Blog","Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]}]} \ No newline at end of file diff --git a/site/security/index.html b/site/security/index.html index 35eb049ad..1d31da4db 100644 --- a/site/security/index.html +++ b/site/security/index.html @@ -885,6 +885,7 @@

    Security

    +

    ctx

    Reporting Vulnerabilities

    At ctx we take security very seriously.

    If you discover a security vulnerability in ctx, please report it responsibly.

    diff --git a/site/versions/index.html b/site/versions/index.html index 352b48396..71a12d3ab 100644 --- a/site/versions/index.html +++ b/site/versions/index.html @@ -447,8 +447,6 @@ - -
    + + + + + + + + + + + + + + + + + + + + + + + + +
    ShellCommand
    bashctx completion bash
    zshctx completion zsh
    fishctx completion fish
    powershellctx completion powershell
    +

    Installation

    +
    +
    +
    +
    # Add to ~/.bashrc
    +source <(ctx completion bash)
    +
    +
    +
    +
    # Add to ~/.zshrc
    +source <(ctx completion zsh)
    +
    +
    +
    +
    ctx completion fish | source
    +# Or save to completions directory
    +ctx completion fish > ~/.config/fish/completions/ctx.fish
    +
    +
    +
    +
    +

    ctx tasks

    Manage task archival and snapshots.

    -
    ctx tasks <subcommand>
    +
    ctx tasks <subcommand>
     

    ctx tasks archive

    Move completed tasks from TASKS.md to a timestamped archive file.

    -
    ctx tasks archive [flags]
    +
    ctx tasks archive [flags]
     

    Flags:

    @@ -2066,12 +2197,12 @@

    ctx tasks archivetasks-YYYY-MM-DD.md). Completed tasks (marked with [x]) are moved; pending tasks ([ ]) remain in TASKS.md.

    Example:

    -
    ctx tasks archive
    -ctx tasks archive --dry-run
    +
    ctx tasks archive
    +ctx tasks archive --dry-run
     

    ctx tasks snapshot

    Create a point-in-time snapshot of TASKS.md without modifying the original.

    -
    ctx tasks snapshot [name]
    +
    ctx tasks snapshot [name]
     

    Arguments:

      @@ -2080,51 +2211,51 @@

      ctx tasks snapshotSnapshots are stored in .context/archive/ with timestamped names (tasks-<name>-YYYY-MM-DD-HHMM.md).

      Example:

      -
      ctx tasks snapshot
      -ctx tasks snapshot "before-refactor"
      +
      ctx tasks snapshot
      +ctx tasks snapshot "before-refactor"
       

      ctx decisions

      Manage the DECISIONS.md file.

      -
      ctx decisions <subcommand>
      +
      ctx decisions <subcommand>
       

      ctx decisions reindex

      Regenerate the quick-reference index at the top of DECISIONS.md.

      -
      ctx decisions reindex
      +
      ctx decisions reindex
       

      The index is a compact table showing date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.

      Use this after manual edits to DECISIONS.md or when migrating existing files to use the index format.

      Example:

      -
      ctx decisions reindex
      -# ✓ Index regenerated with 12 entries
      +
      ctx decisions reindex
      +# ✓ Index regenerated with 12 entries
       

      ctx learnings

      Manage the LEARNINGS.md file.

      -
      ctx learnings <subcommand>
      +
      ctx learnings <subcommand>
       

      ctx learnings reindex

      Regenerate the quick-reference index at the top of LEARNINGS.md.

      -
      ctx learnings reindex
      +
      ctx learnings reindex
       

      The index is a compact table showing date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.

      Use this after manual edits to LEARNINGS.md or when migrating existing files to use the index format.

      Example:

      -
      ctx learnings reindex
      -# ✓ Index regenerated with 8 entries
      +
      ctx learnings reindex
      +# ✓ Index regenerated with 8 entries
       

      ctx recall

      Browse and search AI session history from Claude Code and other tools.

      -
      ctx recall <subcommand>
      +
      ctx recall <subcommand>
       

      ctx recall list

      List all parsed sessions.

      -
      ctx recall list [flags]
      +
      ctx recall list [flags]
       

      Flags:

    @@ -2151,19 +2282,24 @@

    ctx recall list-t

    + + + + +
    Filter by tool (e.g., claude-code)
    --all-projectsInclude sessions from all projects

    Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.

    Example:

    -
    ctx recall list
    -ctx recall list --limit 5
    -ctx recall list --project ctx
    -ctx recall list --tool claude-code
    +
    ctx recall list
    +ctx recall list --limit 5
    +ctx recall list --project ctx
    +ctx recall list --tool claude-code
     

    ctx recall show

    Show details of a specific session.

    -
    ctx recall show [session-id] [flags]
    +
    ctx recall show [session-id] [flags]
     

    Flags:

    @@ -2182,18 +2318,22 @@

    ctx recall show--full

    + + + +
    Show full message content
    --all-projectsSearch across all projects

    The session ID can be a full UUID, partial match, or session slug name.

    Example:

    -
    ctx recall show abc123
    -ctx recall show gleaming-wobbling-sutherland
    -ctx recall show --latest
    -ctx recall show --latest --full
    +
    ctx recall show abc123
    +ctx recall show gleaming-wobbling-sutherland
    +ctx recall show --latest
    +ctx recall show --latest --full
     

    ctx recall export

    Export sessions to editable journal files in .context/journal/.

    -
    ctx recall export [session-id] [flags]
    +
    ctx recall export [session-id] [flags]
     

    Flags:

    @@ -2212,6 +2352,10 @@

    ctx recall export--force

    + + + +
    Overwrite existing files
    --all-projectsExport from all projects

    Exported files include session metadata, tool usage summary, and the full @@ -2219,18 +2363,18 @@

    ctx recall exportThe journal/ directory should be gitignored (like sessions/) since it contains raw conversation data.

    Example:

    -
    ctx recall export abc123          # Export one session
    -ctx recall export --all           # Export all sessions
    -ctx recall export --all --force   # Overwrite existing exports
    +
    ctx recall export abc123          # Export one session
    +ctx recall export --all           # Export all sessions
    +ctx recall export --all --force   # Overwrite existing exports
     

    ctx journal

    Analyze and synthesize exported session files.

    -
    ctx journal <subcommand>
    +
    ctx journal <subcommand>
     

    ctx journal site

    Generate a static site from journal entries in .context/journal/.

    -
    ctx journal site [flags]
    +
    ctx journal site [flags]
     

    Flags:

    @@ -2262,34 +2406,34 @@

    ctx journal siteCreates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

    Requires zensical to be installed for --build or --serve:

    -
    pip install zensical
    +
    pip install zensical
     

    Example:

    -
    ctx journal site                    # Generate in .context/journal-site/
    -ctx journal site --output ~/public  # Custom output directory
    -ctx journal site --build            # Generate and build HTML
    -ctx journal site --serve            # Generate and serve locally
    +
    ctx journal site                    # Generate in .context/journal-site/
    +ctx journal site --output ~/public  # Custom output directory
    +ctx journal site --build            # Generate and build HTML
    +ctx journal site --serve            # Generate and serve locally
     

    ctx serve

    Serve a static site locally via zensical.

    -
    ctx serve [directory]
    +
    ctx serve [directory]
     

    If no directory is specified, serves the journal site (.context/journal-site).

    Requires zensical to be installed:

    -
    pip install zensical
    +
    pip install zensical
     

    Example:

    -
    ctx serve                           # Serve journal site
    -ctx serve .context/journal-site     # Serve specific directory
    -ctx serve ./docs                    # Serve docs folder
    +
    ctx serve                           # Serve journal site
    +ctx serve .context/journal-site     # Serve specific directory
    +ctx serve ./docs                    # Serve docs folder
     

    ctx watch

    Watch for AI output and auto-apply context updates.

    Parses <context-update> XML commands from AI output and applies them to context files.

    -
    ctx watch [flags]
    +
    ctx watch [flags]
     

    Flags:

    @@ -2315,19 +2459,19 @@

    ctx watch

    Example:

    -
    # Watch stdin
    -ai-tool | ctx watch
    -
    -# Watch a log file
    -ctx watch --log /path/to/ai-output.log
    -
    -# Preview without applying
    -ctx watch --dry-run
    +
    # Watch stdin
    +ai-tool | ctx watch
    +
    +# Watch a log file
    +ctx watch --log /path/to/ai-output.log
    +
    +# Preview without applying
    +ctx watch --dry-run
     

    ctx hook

    Generate AI tool integration configuration.

    -
    ctx hook <tool>
    +
    ctx hook <tool>
     

    Supported tools:

    @@ -2361,16 +2505,16 @@

    ctx hook

    Example:

    -
    ctx hook claude-code
    -ctx hook cursor
    -ctx hook aider
    +
    ctx hook claude-code
    +ctx hook cursor
    +ctx hook aider
     

    ctx session

    Manage session snapshots.

    ctx session save

    Save the current context snapshot.

    -
    ctx session save [topic] [flags]
    +
    ctx session save [topic] [flags]
     

    Flags:

    @@ -2390,13 +2534,13 @@

    ctx session save
    ctx session save
    -ctx session save "feature-auth"
    -ctx session save "bugfix" --type bugfix
    +
    ctx session save
    +ctx session save "feature-auth"
    +ctx session save "bugfix" --type bugfix
     

    ctx session list

    List saved sessions.

    -
    ctx session list [flags]
    +
    ctx session list [flags]
     

    Flags:

    @@ -2417,12 +2561,12 @@

    ctx session list

    Output: Table of sessions with index, date, topic, and type.

    Example:

    -
    ctx session list
    -ctx session list --limit 5
    +
    ctx session list
    +ctx session list --limit 5
     

    ctx session load

    Load and display a previous session.

    -
    ctx session load <index|date|topic>
    +
    ctx session load <index|date|topic>
     

    Arguments:

    Example:

    -
    ctx session load 1           # by index
    -ctx session load 2026-01-21  # by date
    -ctx session load auth        # by topic
    +
    ctx session load 1           # by index
    +ctx session load 2026-01-21  # by date
    +ctx session load auth        # by topic
     

    ctx session parse

    Parse JSONL transcript to readable markdown.

    -
    ctx session parse <file> [flags]
    +
    ctx session parse <file> [flags]
     

    Flags:

    @@ -2462,9 +2606,9 @@

    ctx session parse

    Example:

    -
    ctx session parse ~/.claude/projects/.../transcript.jsonl
    -ctx session parse transcript.jsonl --extract
    -ctx session parse transcript.jsonl -o conversation.md
    +
    ctx session parse ~/.claude/projects/.../transcript.jsonl
    +ctx session parse transcript.jsonl --extract
    +ctx session parse transcript.jsonl -o conversation.md
     

    ctx loop

    @@ -2472,7 +2616,7 @@

    ctx loopAn autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.

    -
    ctx loop [flags]
    +
    ctx loop [flags]
     

    Flags:

    @@ -2518,23 +2662,23 @@

    ctx loop

    Example:

    -
    # Generate loop.sh for Claude Code
    -ctx loop
    -
    -# Generate for Aider with custom prompt
    -ctx loop --tool aider --prompt TASKS.md
    -
    -# Limit to 10 iterations
    -ctx loop --max-iterations 10
    -
    -# Output to custom file
    -ctx loop -o my-loop.sh
    +
    # Generate loop.sh for Claude Code
    +ctx loop
    +
    +# Generate for Aider with custom prompt
    +ctx loop --tool aider --prompt TASKS.md
    +
    +# Limit to 10 iterations
    +ctx loop --max-iterations 10
    +
    +# Output to custom file
    +ctx loop -o my-loop.sh
     

    Usage:

    -
    # Generate and run the loop
    -ctx loop
    -chmod +x loop.sh
    -./loop.sh
    +
    # Generate and run the loop
    +ctx loop
    +chmod +x loop.sh
    +./loop.sh
     

    See Autonomous Loops for detailed workflow documentation.


    @@ -2594,15 +2738,15 @@

    Environment VariablesConfiguration File

    Optional .contextrc (YAML format) at project root:

    -
    # .contextrc
    -context_dir: .context # Context directory name
    -token_budget: 8000    # Default token budget
    -priority_order:       # File loading priority
    -  - TASKS.md
    -  - DECISIONS.md
    -  - CONVENTIONS.md
    -auto_archive: true    # Auto-archive old items
    -archive_after_days: 7 # Days before archiving
    +
    # .contextrc
    +context_dir: .context # Context directory name
    +token_budget: 8000    # Default token budget
    +priority_order:       # File loading priority
    +  - TASKS.md
    +  - DECISIONS.md
    +  - CONVENTIONS.md
    +auto_archive: true    # Auto-archive old items
    +archive_after_days: 7 # Days before archiving
     

    Priority order: CLI flags > Environment variables > .contextrc > Defaults

    All settings are optional. Missing values use defaults.

    diff --git a/site/index.html b/site/index.html index 14c25497a..5e83b0a7e 100644 --- a/site/index.html +++ b/site/index.html @@ -1219,41 +1219,41 @@