diff --git a/.claude/skills/_ctx-release-notes/SKILL.md b/.claude/skills/_ctx-release-notes/SKILL.md index 193b034b9..e586dc835 100644 --- a/.claude/skills/_ctx-release-notes/SKILL.md +++ b/.claude/skills/_ctx-release-notes/SKILL.md @@ -27,7 +27,9 @@ Generate release notes for the next release of Context CLI. 1. **Read version and find baseline**: ```bash cat VERSION -git describe --tags --abbrev=0 2>/dev/null +# Use the latest tag globally, not ancestry-based git describe +# (git describe follows reachability and misses tags on other branches) +git tag --sort=-v:refname | head -1 ``` 2. **Gather commits since last tag** (filter out noise): diff --git a/.claude/skills/_ctx-release/SKILL.md b/.claude/skills/_ctx-release/SKILL.md index 7e227f9f6..404468a72 100644 --- a/.claude/skills/_ctx-release/SKILL.md +++ b/.claude/skills/_ctx-release/SKILL.md @@ -13,8 +13,8 @@ All three prerequisites must be true: 2. **`dist/RELEASE_NOTES.md`** exists (generate with `/_ctx-release-notes`) 3. **Working tree is clean** (all changes committed) -If any prerequisite fails, stop — running the release script with -missing notes or a dirty tree can produce an incomplete or unsigned +If any prerequisite fails, stop. Running the release script with +missing notes or a dirty tree produces an incomplete or unsigned tag that must be manually deleted. ## When to Use @@ -42,17 +42,23 @@ make release ``` This script: -- Updates version references in `docs/index.md` +- Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) +- Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) +- Adds new row to versions.md - Rebuilds the documentation site -- Commits the docs update +- Commits the version and docs update - Runs tests and smoke tests -- Builds binaries for all platforms +- Builds binaries for all 6 platforms - Creates and pushes a signed git tag - Updates the `latest` tag -3. **After completion**, tell the user to create the GitHub release - at the URL shown in the script output and upload binaries from - `dist/`. +3. **After completion**, verify the GitHub release was created by CI + at `https://github.com/ActiveMemory/ctx/releases`. + +## Full Runbook + +See [Cutting a Release](https://ctx.ist/operations/release/) for the +complete step-by-step guide including troubleshooting. ## Quality Checklist @@ -61,4 +67,4 @@ This script: - [ ] Working tree is clean - [ ] Tests and smoke tests pass - [ ] Tag is pushed to origin -- [ ] User reminded to create GitHub release +- [ ] GitHub release created by CI with all 6 binaries diff --git a/.context/DECISIONS.md b/.context/DECISIONS.md index aa5a1f6bb..90aca4797 100644 --- a/.context/DECISIONS.md +++ b/.context/DECISIONS.md @@ -3,6 +3,7 @@ | Date | Decision | |------|--------| +| 2026-03-23 | Pre/pre HTML tags promoted to shared constants in config/marker | | 2026-03-23 | Pure-data param structs in entity — replace function pointers with text keys | | 2026-03-22 | No runtime pluralization — use singular/plural text key pairs | | 2026-03-22 | Output functions belong in write/, never in core/ or cmd/ | @@ -62,6 +63,20 @@ | 2026-02-27 | Webhook and notification design (consolidated) | +## [2026-03-23-165612] Pre/pre HTML tags promoted to shared constants in config/marker + +**Status**: Accepted + +**Context**: Two packages (normalize and format) used hardcoded pre strings independently + +**Decision**: Pre/pre HTML tags promoted to shared constants in config/marker + +**Rationale**: Cross-package magic strings belong in config constants per CONVENTIONS.md + +**Consequence**: marker.TagPre and marker.TagPreClose are the canonical references; package-local constants deleted + +--- + ## [2026-03-23-003346] Pure-data param structs in entity — replace function pointers with text keys **Status**: Accepted diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md index 79900dace..ad70bc894 100644 --- a/.context/LEARNINGS.md +++ b/.context/LEARNINGS.md @@ -3,6 +3,8 @@ | Date | Learning | |------|--------| +| 2026-03-23 | Typography detection script needs exclusion lists for intentional uses | +| 2026-03-23 | Subagents rename functions and restructure code beyond their scope | | 2026-03-23 | Splitting core/ into subpackages reveals hidden structure | | 2026-03-23 | Higher-order callbacks in param structs are a code smell | | 2026-03-22 | Types in god-object files create circular dependencies | @@ -91,6 +93,26 @@ --- +## [2026-03-23-165611] Typography detection script needs exclusion lists for intentional uses + +**Context**: detect-ai-typography.sh flagged config/token/delim.go (intentional delimiter constants) and test files (test data containing em-dashes) + +**Lesson**: Detection scripts for convention enforcement need exclusion patterns for files where the flagged patterns are intentional data, not prose + +**Application**: Add exclusion patterns proactively when creating detection scripts; *_test.go and constant-definition files are common false positive sources + +--- + +## [2026-03-23-165610] Subagents rename functions and restructure code beyond their scope + +**Context**: Agents tasked with fixing em-dashes in comments also renamed exported functions, changed import aliases, and modified function signatures + +**Lesson**: Always diff-audit agent output for structural changes before accepting edits, even when the task is narrowly scoped + +**Application**: After any agent batch edit, run git diff --stat and scan for non-comment changes before staging + +--- + ## [2026-03-23-003544] Splitting core/ into subpackages reveals hidden structure **Context**: init core/ was a flat bag of domain objects — splitting into backup/, claude/, entry/, merge/, plan/, plugin/, project/, prompt/, tpl/, validate/ exposed duplicated logic, misplaced types, and function-pointer smuggling that were invisible in the flat layout diff --git a/.context/TASKS.md b/.context/TASKS.md index 2e76538bb..10e20e9ed 100644 --- a/.context/TASKS.md +++ b/.context/TASKS.md @@ -41,6 +41,8 @@ checks field presence, not content safety. - [ ] Make TitleSlugMaxLen configurable via .ctxrc #added:2026-03-21-070944 +- [ ] Replace hack/lint-drift.sh with AST-based Go tests in internal/audit/. Spec: `specs/ast-audit-tests.md` #added:2026-03-23-210000 + - [ ] Add AST-based lint test to detect exported functions with no external callers #added:2026-03-21-070357 - [ ] Audit exported functions used only within their own package and make them private #added:2026-03-21-070346 diff --git a/.context/scratchpad.enc b/.context/scratchpad.enc index d6ba937ff..1e5bb130e 100644 Binary files a/.context/scratchpad.enc and b/.context/scratchpad.enc differ diff --git a/CONTRIBUTING-SKILLS.md b/CONTRIBUTING-SKILLS.md index 129a34b3a..1f49586ed 100644 --- a/CONTRIBUTING-SKILLS.md +++ b/CONTRIBUTING-SKILLS.md @@ -6,7 +6,7 @@ session. This page is for contributors who want to understand what each skill does without reading individual `SKILL.md` files. For ctx plugin skills (`/ctx-status`, `/ctx-recall`, etc.), see the -[ctx skills documentation](docs/skills.md). +[ctx skills documentation](docs/reference/skills.md). ## Skill Catalog diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f22ebc802..574a8959d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thank you for your interest in contributing to `ctx`. -**Full contributing guide**: https://ctx.ist/contributing/ +**Full contributing guide**: https://ctx.ist/home/contributing/ The guide covers: diff --git a/docs/cli/tools.md b/docs/cli/tools.md index f4fd3f790..9c541c1f6 100644 --- a/docs/cli/tools.md +++ b/docs/cli/tools.md @@ -821,7 +821,7 @@ ctx prompt rm - `name`: Template name (without `.md` extension) -**See also**: [Prompt Templates](../recipes/prompts.md) +**See also**: [Prompt Templates](../recipes/prompt-templates.md) --- diff --git a/docs/home/about.md b/docs/home/about.md index 059bc7f63..44b21e31d 100644 --- a/docs/home/about.md +++ b/docs/home/about.md @@ -135,7 +135,7 @@ makes the *next* session **smarter**. **Connect with `ctx`** * [Join the Community →](community.md): ask questions, share workflows, and help shape what comes next -* [Read the Blog →](../../blog/): real-world patterns, ponderings, and lessons learned from building `ctx` using `ctx` +* [Read the Blog →](../blog/): real-world patterns, ponderings, and lessons learned from building `ctx` using `ctx` ---- diff --git a/docs/home/contributing.md b/docs/home/contributing.md index 2fe46d771..878276462 100644 --- a/docs/home/contributing.md +++ b/docs/home/contributing.md @@ -120,7 +120,7 @@ ctx/ | Directory | What lives here | Distributed to users? | |----------------------------------|-------------------------------------------------|-----------------------| -| `internal/assets/claude/skills/` | The 29 `ctx-*` skills that ship with the plugin | Yes | +| `internal/assets/claude/skills/` | The 39 `ctx-*` skills that ship with the plugin | Yes | | `.claude/skills/` | Dev-only skills (release, QA, backup, etc.) | No | **`internal/assets/claude/skills/`** is the single source of truth for @@ -164,7 +164,7 @@ and are now available to all ctx users: `/ctx-brainstorm`, `/ctx-check-links`, 6. Add a section to the appropriate CLI doc page in `docs/cli/`. Pattern to follow: `internal/cli/pad/pad.go` (parent with subcommands) or -`internal/cli/complete/complete.go` (single command). +`internal/cli/drift/drift.go` (single command). ### Adding a New Session Parser diff --git a/docs/operations/index.md b/docs/operations/index.md index 950fbca8b..90e5c7088 100644 --- a/docs/operations/index.md +++ b/docs/operations/index.md @@ -31,6 +31,13 @@ Windsurf, and other AI coding tools. --- +### [Cutting a Release](release.md) + +Step-by-step runbook for maintainers: bump version, generate +release notes, run the release script, and verify the result. + +--- + ### [Autonomous Loops](autonomous-loop.md) Run an unattended AI agent that works through tasks overnight, diff --git a/docs/operations/release.md b/docs/operations/release.md new file mode 100644 index 000000000..bae4496ce --- /dev/null +++ b/docs/operations/release.md @@ -0,0 +1,178 @@ +--- +# / ctx: https://ctx.ist +# ,'`./ do you remember? +# `.,'\ +# \ Copyright 2026-present Context contributors. +# SPDX-License-Identifier: Apache-2.0 + +title: Cutting a Release +icon: lucide/package +--- + +![ctx](../images/ctx-banner.png) + +## Prerequisites + +Before you can cut a release you need: + +* Push access to `origin` (GitHub) +* GPG signing configured (`make gpg-test`) +* [Go](https://go.dev/) installed (version in `go.mod`) +* [Zensical](https://github.com/zensical/zensical) installed (`make site-setup`) +* A clean working tree (`git status` shows nothing to commit) + +--- + +## Step-by-Step + +### 1. Update the VERSION File + +```bash +echo "0.9.0" > VERSION +git add VERSION +git commit -m "chore: bump version to 0.9.0" +``` + +The VERSION file uses bare semver (`0.9.0`), no `v` prefix. +The release script adds the `v` prefix for git tags. + +### 2. Generate Release Notes + +In Claude Code: + +``` +/_ctx-release-notes +``` + +This analyzes commits since the last tag and writes +`dist/RELEASE_NOTES.md`. The release script refuses to +proceed without this file. + +### 3. Commit Any Remaining Changes + +```bash +git status # must be clean +make audit # full check: fmt, vet, lint, test +``` + +### 4. Run the Release + +```bash +make release +``` + +Or, if you are in a Claude Code session: + +``` +/_ctx-release +``` + +The release script does everything in order: + +| Step | What happens | +|------|-------------| +| 1 | Reads `VERSION`, verifies release notes exist | +| 2 | Verifies working tree is clean | +| 3 | Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) | +| 4 | Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) | +| 5 | Adds new row to versions.md | +| 6 | Rebuilds the documentation site (`make site`) | +| 7 | Commits all version and docs updates | +| 8 | Runs `make test` and `make smoke` | +| 9 | Builds binaries for all 6 platforms via `hack/build-all.sh` | +| 10 | Creates a signed git tag (`v0.9.0`) | +| 11 | Pushes the tag to origin | +| 12 | Updates and pushes the `latest` tag | + +### 5. GitHub CI Takes Over + +Pushing a `v*` tag triggers `.github/workflows/release.yml`: + +1. Checks out the tagged commit +2. Runs the full test suite +3. Builds binaries for all platforms +4. Creates a GitHub Release with auto-generated notes +5. Uploads binaries and SHA256 checksums + +### 6. Verify + +- [ ] [GitHub Releases](https://github.com/ActiveMemory/ctx/releases) shows the new version +- [ ] All 6 binaries are attached (linux/darwin x amd64/arm64, windows x amd64) +- [ ] SHA256 files are attached +- [ ] Release notes look correct + +--- + +## What Gets Updated Automatically + +The release script updates 8 files so you do not have to: + +| File | What changes | +|------|-------------| +| `internal/assets/claude/.claude-plugin/plugin.json` | Plugin version | +| `.claude-plugin/marketplace.json` | Marketplace version (2 fields) | +| `editors/vscode/package.json` | VS Code extension version | +| `editors/vscode/package-lock.json` | VS Code lock version (2 fields) | +| `docs/index.md` | Download URLs | +| `docs/home/getting-started.md` | Download URLs | +| `docs/operations/integrations.md` | VSIX filename version | +| `docs/reference/versions.md` | New version row + latest pointer | + +The Go binary version is injected at build time via `-ldflags` +from the VERSION file. No source file needs editing. + +--- + +## Build Targets Reference + +| Target | What it does | +|--------|-------------| +| `make release` | Full release (script + tag + push) | +| `make build` | Build binary for current platform | +| `make build-all` | Build all 6 platform binaries | +| `make test` | Unit tests | +| `make smoke` | Integration smoke tests | +| `make audit` | Full check (fmt + vet + lint + drift + docs + test) | +| `make site` | Rebuild documentation site | + +--- + +## Troubleshooting + +### "Release notes not found" + +``` +ERROR: dist/RELEASE_NOTES.md not found. +``` + +Run `/_ctx-release-notes` in Claude Code first, or write +`dist/RELEASE_NOTES.md` manually. + +### "Working tree is not clean" + +``` +ERROR: Working tree is not clean. +``` + +Commit or stash all changes before running `make release`. + +### "Tag already exists" + +``` +ERROR: Tag v0.9.0 already exists. +``` + +You cannot release the same version twice. Either bump VERSION +to a new version, or delete the old tag if the release was +incomplete: + +```bash +git tag -d v0.9.0 +git push origin :refs/tags/v0.9.0 +``` + +### CI build fails after tag push + +The tag is already published. Fix the issue, bump to a patch +version (e.g. `0.9.1`), and release again. Do not force-push +tags that others may have already fetched. diff --git a/editors/vscode/package.json b/editors/vscode/package.json index d8e3656a1..5457ed034 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "ctx-context", "displayName": "ctx — Persistent Context for AI", "description": "Chat participant (@ctx) for persistent project context across AI coding sessions", - "version": "0.7.0", + "version": "0.8.0", "publisher": "activememory", "license": "Apache-2.0", "homepage": "https://github.com/ActiveMemory/ctx", @@ -86,17 +86,13 @@ "description": "Reconcile context with codebase" }, { - "name": "complete", - "description": "Mark a task as completed" + "name": "task", + "description": "Task operations: complete, archive, snapshot" }, { "name": "remind", "description": "Manage session-scoped reminders" }, - { - "name": "tasks", - "description": "Archive or snapshot tasks" - }, { "name": "pad", "description": "Encrypted scratchpad for sensitive notes" @@ -108,6 +104,66 @@ { "name": "system", "description": "System diagnostics and bootstrap" + }, + { + "name": "memory", + "description": "Memory bridge operations (sync, status, diff, import, publish)" + }, + { + "name": "journal", + "description": "Journal management (site, obsidian)" + }, + { + "name": "doctor", + "description": "Context health diagnostics" + }, + { + "name": "config", + "description": "Runtime configuration (switch, status, schema)" + }, + { + "name": "prompt", + "description": "Prompt templates (list, add, show, rm)" + }, + { + "name": "why", + "description": "Show rationale for context design decisions" + }, + { + "name": "change", + "description": "Show recent codebase changes" + }, + { + "name": "dep", + "description": "Show project dependencies" + }, + { + "name": "guide", + "description": "Quick start guide" + }, + { + "name": "permission", + "description": "Permission snapshot and restore" + }, + { + "name": "site", + "description": "Documentation site (feed)" + }, + { + "name": "loop", + "description": "Generate autonomous iteration scripts" + }, + { + "name": "pause", + "description": "Pause context hooks for current session" + }, + { + "name": "resume", + "description": "Resume context hooks" + }, + { + "name": "reindex", + "description": "Rebuild context file indices" } ], "disambiguation": [ diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 99391619f..87feb6f6e 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -584,40 +584,82 @@ async function handleSync( return { metadata: { command: "sync" } }; } -async function handleComplete( +async function handleTask( stream: vscode.ChatResponseStream, prompt: string, cwd: string, token: vscode.CancellationToken ): Promise { - const taskRef = prompt.trim(); - if (!taskRef) { - stream.markdown( - "**Usage:** `@ctx /complete `\n\n" + - "Example: `@ctx /complete 3` or `@ctx /complete Fix login bug`" - ); - return { metadata: { command: "complete" } }; + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + const rest = parts.slice(1).join(" "); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "complete": { + const taskRef = rest.trim(); + if (!taskRef) { + stream.markdown( + "**Usage:** `@ctx /task complete `\n\n" + + "Example: `@ctx /task complete 3` or " + + "`@ctx /task complete Fix login bug`" + ); + return { metadata: { command: "task" } }; + } + args = ["task", "complete", taskRef]; + progressMsg = "Marking task as completed..."; + break; + } + case "archive": + args = ["task", "archive"]; + progressMsg = "Archiving completed tasks..."; + break; + case "snapshot": + args = rest ? ["task", "snapshot", rest] : ["task", "snapshot"]; + progressMsg = "Creating task snapshot..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /task `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `complete ` | Mark a task as completed |\n" + + "| `archive` | Move completed tasks to archive |\n" + + "| `snapshot [name]` | Create point-in-time snapshot |\n\n" + + "Example: `@ctx /task complete 3` or " + + "`@ctx /task archive`" + ); + return { metadata: { command: "task" } }; } + args.push("--no-color"); - stream.progress("Marking task as completed..."); + stream.progress(progressMsg); try { - const { stdout, stderr } = await runCtx( - ["complete", taskRef, "--no-color"], - cwd, - token - ); + const { stdout, stderr } = await runCtx(args, cwd, token); const output = (stdout + stderr).trim(); if (output) { stream.markdown("```\n" + output + "\n```"); } else { - stream.markdown(`Task **${taskRef}** marked as completed.`); + switch (subcmd) { + case "complete": + stream.markdown(`Task **${rest.trim()}** marked as completed.`); + break; + case "archive": + stream.markdown("Completed tasks archived."); + break; + default: + stream.markdown("Task snapshot created."); + break; + } } } catch (err: unknown) { stream.markdown( - `**Error:** Failed to complete task.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + `**Error:** Failed to ${subcmd} task.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` ); } - return { metadata: { command: "complete" } }; + return { metadata: { command: "task" } }; } async function handleRemind( @@ -678,62 +720,6 @@ async function handleRemind( return { metadata: { command: "remind" } }; } -async function handleTasks( - stream: vscode.ChatResponseStream, - prompt: string, - cwd: string, - token: vscode.CancellationToken -): Promise { - const parts = prompt.trim().split(/\s+/); - const subcmd = parts[0]?.toLowerCase(); - const rest = parts.slice(1).join(" "); - - let args: string[]; - let progressMsg: string; - - switch (subcmd) { - case "archive": - args = ["tasks", "archive"]; - progressMsg = "Archiving completed tasks..."; - break; - case "snapshot": - args = rest ? ["tasks", "snapshot", rest] : ["tasks", "snapshot"]; - progressMsg = "Creating task snapshot..."; - break; - default: - stream.markdown( - "**Usage:** `@ctx /tasks `\n\n" + - "| Subcommand | Description |\n" + - "|------------|-------------|\n" + - "| `archive` | Move completed tasks to archive |\n" + - "| `snapshot [name]` | Create point-in-time snapshot |\n\n" + - "Example: `@ctx /tasks archive` or `@ctx /tasks snapshot pre-refactor`" - ); - return { metadata: { command: "tasks" } }; - } - args.push("--no-color"); - - stream.progress(progressMsg); - try { - const { stdout, stderr } = await runCtx(args, cwd, token); - const output = (stdout + stderr).trim(); - if (output) { - stream.markdown("```\n" + output + "\n```"); - } else { - stream.markdown( - subcmd === "archive" - ? "Completed tasks archived." - : "Task snapshot created." - ); - } - } catch (err: unknown) { - stream.markdown( - `**Error:** Failed to ${subcmd} tasks.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` - ); - } - return { metadata: { command: "tasks" } }; -} - async function handlePad( stream: vscode.ChatResponseStream, prompt: string, @@ -813,7 +799,6 @@ async function handleNotify( ): Promise { const parts = prompt.trim().split(/\s+/); const subcmd = parts[0]?.toLowerCase(); - const rest = parts.slice(1).join(" "); let args: string[]; let progressMsg: string; @@ -903,7 +888,7 @@ async function handleSystem( "|------------|-------------|\n" + "| `resources` | Show system resource usage |\n" + "| `bootstrap` | Print context location for AI agents |\n" + - "| `message list\|show\|edit\|reset` | Manage hook messages |\n\n" + + "| `message list|show|edit|reset` | Manage hook messages |\n\n" + "Example: `@ctx /system resources` or `@ctx /system bootstrap`" ); return { metadata: { command: "system" } }; @@ -927,6 +912,553 @@ async function handleSystem( return { metadata: { command: "system" } }; } +async function handleMemory( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "sync": + args = ["memory", "sync"]; + progressMsg = "Syncing memory bridge..."; + break; + case "status": + args = ["memory", "status"]; + progressMsg = "Checking memory status..."; + break; + case "diff": + args = ["memory", "diff"]; + progressMsg = "Diffing memory state..."; + break; + case "import": + args = ["memory", "import"]; + progressMsg = "Importing memory..."; + break; + case "publish": + args = ["memory", "publish"]; + progressMsg = "Publishing memory..."; + break; + case "unpublish": + args = ["memory", "unpublish"]; + progressMsg = "Unpublishing memory..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /memory `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `sync` | Synchronize memory bridge |\n" + + "| `status` | Show memory bridge status |\n" + + "| `diff` | Show memory diff |\n" + + "| `import` | Import external memory |\n" + + "| `publish` | Publish curated context |\n" + + "| `unpublish` | Remove published context |\n\n" + + "Example: `@ctx /memory status` or `@ctx /memory sync`" + ); + return { metadata: { command: "memory" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Memory ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run memory ${subcmd}.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "memory" } }; +} + +async function handleJournal( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "site": + args = ["journal", "site"]; + progressMsg = "Generating journal site..."; + break; + case "obsidian": + args = ["journal", "obsidian"]; + progressMsg = "Exporting journal to Obsidian..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /journal `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `site` | Generate journal site |\n" + + "| `obsidian` | Export journal to Obsidian |\n\n" + + "Example: `@ctx /journal site`" + ); + return { metadata: { command: "journal" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Journal ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run journal ${subcmd}.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "journal" } }; +} + +async function handleDoctor( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Running context health diagnostics..."); + try { + const { stdout, stderr } = await runCtx(["doctor", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("Context health check passed."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run doctor.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "doctor" } }; +} + +async function handleConfig( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + const rest = parts.slice(1).join(" "); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "switch": + args = rest ? ["config", "switch", rest] : ["config", "switch"]; + progressMsg = "Switching configuration..."; + break; + case "status": + args = ["config", "status"]; + progressMsg = "Checking configuration status..."; + break; + case "schema": + args = ["config", "schema"]; + progressMsg = "Showing configuration schema..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /config `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `switch` | Switch active configuration |\n" + + "| `status` | Show current configuration |\n" + + "| `schema` | Show configuration schema |\n\n" + + "Example: `@ctx /config status` or `@ctx /config switch minimal`" + ); + return { metadata: { command: "config" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Config ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run config ${subcmd}.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "config" } }; +} + +async function handlePrompt( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + const rest = parts.slice(1).join(" "); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "list": + case "ls": + args = ["prompt", "list"]; + progressMsg = "Listing prompt templates..."; + break; + case "add": + args = rest ? ["prompt", "add", rest] : ["prompt", "add"]; + progressMsg = "Adding prompt template..."; + break; + case "show": + args = rest ? ["prompt", "show", rest] : ["prompt", "show"]; + progressMsg = "Showing prompt template..."; + break; + case "rm": + args = rest ? ["prompt", "rm", rest] : ["prompt", "rm"]; + progressMsg = "Removing prompt template..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /prompt `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `list` | List prompt templates |\n" + + "| `add ` | Add a prompt template |\n" + + "| `show ` | Show a prompt template |\n" + + "| `rm ` | Remove a prompt template |\n\n" + + "Example: `@ctx /prompt list` or `@ctx /prompt show review`" + ); + return { metadata: { command: "prompt" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Prompt ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run prompt ${subcmd}.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "prompt" } }; +} + +async function handleWhy( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const filename = prompt.trim(); + const args = filename ? ["why", filename] : ["why"]; + args.push("--no-color"); + + stream.progress("Looking up design rationale..."); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("No rationale found."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to look up rationale.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "why" } }; +} + +async function handleChange( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Checking recent codebase changes..."); + try { + const { stdout, stderr } = await runCtx(["change", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("No recent changes found."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to check changes.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "change" } }; +} + +async function handleDep( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Checking project dependencies..."); + try { + const { stdout, stderr } = await runCtx(["dep", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("No dependencies found."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to check dependencies.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "dep" } }; +} + +async function handleGuide( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Loading quick start guide..."); + try { + const { stdout, stderr } = await runCtx(["guide", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown(output); + } else { + stream.markdown("No guide content available."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to load guide.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "guide" } }; +} + +async function handlePermission( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "snapshot": + args = ["permission", "snapshot"]; + progressMsg = "Taking permission snapshot..."; + break; + case "restore": + args = ["permission", "restore"]; + progressMsg = "Restoring permissions..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /permission `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `snapshot` | Capture current file permissions |\n" + + "| `restore` | Restore saved permissions |\n\n" + + "Example: `@ctx /permission snapshot`" + ); + return { metadata: { command: "permission" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Permission ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to ${subcmd} permissions.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "permission" } }; +} + +async function handleSite( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const parts = prompt.trim().split(/\s+/); + const subcmd = parts[0]?.toLowerCase(); + + let args: string[]; + let progressMsg: string; + + switch (subcmd) { + case "feed": + args = ["site", "feed"]; + progressMsg = "Generating site feed..."; + break; + default: + stream.markdown( + "**Usage:** `@ctx /site `\n\n" + + "| Subcommand | Description |\n" + + "|------------|-------------|\n" + + "| `feed` | Generate documentation site feed |\n\n" + + "Example: `@ctx /site feed`" + ); + return { metadata: { command: "site" } }; + } + args.push("--no-color"); + + stream.progress(progressMsg); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown(`Site ${subcmd} completed.`); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to run site ${subcmd}.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "site" } }; +} + +async function handleLoop( + stream: vscode.ChatResponseStream, + prompt: string, + cwd: string, + token: vscode.CancellationToken +): Promise { + const toolName = prompt.trim(); + const args = toolName ? ["loop", toolName] : ["loop"]; + args.push("--no-color"); + + stream.progress("Generating iteration script..."); + try { + const { stdout, stderr } = await runCtx(args, cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("No loop script generated."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to generate loop script.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "loop" } }; +} + +async function handlePause( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Pausing context hooks..."); + try { + const { stdout, stderr } = await runCtx(["pause", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("Context hooks paused for this session."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to pause hooks.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "pause" } }; +} + +async function handleResume( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Resuming context hooks..."); + try { + const { stdout, stderr } = await runCtx(["resume", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("Context hooks resumed."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to resume hooks.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "resume" } }; +} + +async function handleReindex( + stream: vscode.ChatResponseStream, + cwd: string, + token: vscode.CancellationToken +): Promise { + stream.progress("Rebuilding context file indices..."); + try { + const { stdout, stderr } = await runCtx(["reindex", "--no-color"], cwd, token); + const output = (stdout + stderr).trim(); + if (output) { + stream.markdown("```\n" + output + "\n```"); + } else { + stream.markdown("Context indices rebuilt."); + } + } catch (err: unknown) { + stream.markdown( + `**Error:** Failed to reindex.\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\`` + ); + } + return { metadata: { command: "reindex" } }; +} + async function handleFreeform( request: vscode.ChatRequest, stream: vscode.ChatResponseStream, @@ -949,13 +1481,13 @@ async function handleFreeform( return handleRecall(stream, request.prompt, cwd, token); } if (prompt.includes("complete") || prompt.includes("done") || prompt.includes("finish")) { - return handleComplete(stream, request.prompt, cwd, token); + return handleTask(stream, "complete " + request.prompt, cwd, token); } if (prompt.includes("remind")) { return handleRemind(stream, request.prompt, cwd, token); } if (prompt.includes("task")) { - return handleTasks(stream, request.prompt, cwd, token); + return handleTask(stream, request.prompt, cwd, token); } if (prompt.includes("pad") || prompt.includes("scratchpad") || prompt.includes("scratch")) { return handlePad(stream, request.prompt, cwd, token); @@ -966,10 +1498,55 @@ async function handleFreeform( if (prompt.includes("system") || prompt.includes("resource") || prompt.includes("bootstrap")) { return handleSystem(stream, request.prompt, cwd, token); } + if (prompt.includes("memory")) { + return handleMemory(stream, request.prompt, cwd, token); + } + if (prompt.includes("journal")) { + return handleJournal(stream, request.prompt, cwd, token); + } + if (prompt.includes("doctor") || prompt.includes("health")) { + return handleDoctor(stream, cwd, token); + } + if (prompt.includes("config") || prompt.includes("configuration")) { + return handleConfig(stream, request.prompt, cwd, token); + } + if (prompt.includes("prompt") || prompt.includes("template")) { + return handlePrompt(stream, request.prompt, cwd, token); + } + if (prompt.includes("why") || prompt.includes("rationale")) { + return handleWhy(stream, request.prompt, cwd, token); + } + if (prompt.includes("change") || prompt.includes("recent")) { + return handleChange(stream, cwd, token); + } + if (prompt.includes("dep") || prompt.includes("dependenc")) { + return handleDep(stream, cwd, token); + } + if (prompt.includes("guide") || prompt.includes("quickstart") || prompt.includes("getting started")) { + return handleGuide(stream, cwd, token); + } + if (prompt.includes("permission")) { + return handlePermission(stream, request.prompt, cwd, token); + } + if (prompt.includes("site") || prompt.includes("feed")) { + return handleSite(stream, request.prompt, cwd, token); + } + if (prompt.includes("loop") || prompt.includes("iterate")) { + return handleLoop(stream, request.prompt, cwd, token); + } + if (prompt.includes("pause")) { + return handlePause(stream, cwd, token); + } + if (prompt.includes("resume")) { + return handleResume(stream, cwd, token); + } + if (prompt.includes("reindex") || prompt.includes("rebuild")) { + return handleReindex(stream, cwd, token); + } // Default: show help with available commands stream.markdown( - "## ctx — Persistent Context for AI\n\n" + + "## ctx -- Persistent Context for AI\n\n" + "Available commands:\n\n" + "| Command | Description |\n" + "|---------|-------------|\n" + @@ -983,12 +1560,26 @@ async function handleFreeform( "| `/load` | Output assembled context |\n" + "| `/compact` | Archive completed tasks |\n" + "| `/sync` | Reconcile context with codebase |\n" + - "| `/complete` | Mark a task as completed |\n" + + "| `/task` | Task operations (complete, archive, snapshot) |\n" + "| `/remind` | Manage session reminders |\n" + - "| `/tasks` | Archive or snapshot tasks |\n" + "| `/pad` | Encrypted scratchpad |\n" + "| `/notify` | Webhook notifications |\n" + - "| `/system` | System diagnostics |\n\n" + + "| `/system` | System diagnostics |\n" + + "| `/memory` | Memory bridge operations |\n" + + "| `/journal` | Journal management |\n" + + "| `/doctor` | Context health diagnostics |\n" + + "| `/config` | Runtime configuration |\n" + + "| `/prompt` | Prompt templates |\n" + + "| `/why` | Design rationale for context files |\n" + + "| `/change` | Recent codebase changes |\n" + + "| `/dep` | Project dependencies |\n" + + "| `/guide` | Quick start guide |\n" + + "| `/permission` | Permission snapshot/restore |\n" + + "| `/site` | Documentation site |\n" + + "| `/loop` | Generate iteration scripts |\n" + + "| `/pause` | Pause context hooks |\n" + + "| `/resume` | Resume context hooks |\n" + + "| `/reindex` | Rebuild context indices |\n\n" + "Example: `@ctx /status` or `@ctx /add task Fix login bug`" ); return { metadata: { command: "help" } }; @@ -1043,18 +1634,46 @@ const handler: vscode.ChatRequestHandler = async ( return handleCompact(stream, cwd, token); case "sync": return handleSync(stream, cwd, token); - case "complete": - return handleComplete(stream, request.prompt, cwd, token); + case "task": + return handleTask(stream, request.prompt, cwd, token); case "remind": return handleRemind(stream, request.prompt, cwd, token); - case "tasks": - return handleTasks(stream, request.prompt, cwd, token); case "pad": return handlePad(stream, request.prompt, cwd, token); case "notify": return handleNotify(stream, request.prompt, cwd, token); case "system": return handleSystem(stream, request.prompt, cwd, token); + case "memory": + return handleMemory(stream, request.prompt, cwd, token); + case "journal": + return handleJournal(stream, request.prompt, cwd, token); + case "doctor": + return handleDoctor(stream, cwd, token); + case "config": + return handleConfig(stream, request.prompt, cwd, token); + case "prompt": + return handlePrompt(stream, request.prompt, cwd, token); + case "why": + return handleWhy(stream, request.prompt, cwd, token); + case "change": + return handleChange(stream, cwd, token); + case "dep": + return handleDep(stream, cwd, token); + case "guide": + return handleGuide(stream, cwd, token); + case "permission": + return handlePermission(stream, request.prompt, cwd, token); + case "site": + return handleSite(stream, request.prompt, cwd, token); + case "loop": + return handleLoop(stream, request.prompt, cwd, token); + case "pause": + return handlePause(stream, cwd, token); + case "resume": + return handleResume(stream, cwd, token); + case "reindex": + return handleReindex(stream, cwd, token); default: return handleFreeform(request, stream, cwd, token); } @@ -1099,7 +1718,8 @@ export function activate(extensionContext: vscode.ExtensionContext) { case "status": followups.push( { prompt: "Detect context drift", command: "drift" }, - { prompt: "Load full context", command: "load" } + { prompt: "Load full context", command: "load" }, + { prompt: "Run health check", command: "doctor" } ); break; case "drift": @@ -1108,10 +1728,10 @@ export function activate(extensionContext: vscode.ExtensionContext) { { prompt: "Show context status", command: "status" } ); break; - case "complete": + case "task": followups.push( { prompt: "Show context status", command: "status" }, - { prompt: "Archive completed tasks", command: "tasks" } + { prompt: "Compact context", command: "compact" } ); break; case "remind": @@ -1119,21 +1739,48 @@ export function activate(extensionContext: vscode.ExtensionContext) { { prompt: "Show context status", command: "status" } ); break; - case "tasks": + case "pad": + followups.push( + { prompt: "List scratchpad", command: "pad" } + ); + break; + case "memory": + followups.push( + { prompt: "Check memory status", command: "memory" }, + { prompt: "Show context status", command: "status" } + ); + break; + case "doctor": followups.push( { prompt: "Show context status", command: "status" }, - { prompt: "Compact context", command: "compact" } + { prompt: "Detect drift", command: "drift" } ); break; - case "pad": + case "config": followups.push( - { prompt: "List scratchpad", command: "pad" } + { prompt: "Show config status", command: "config" } + ); + break; + case "change": + followups.push( + { prompt: "Show context status", command: "status" } + ); + break; + case "pause": + followups.push( + { prompt: "Resume hooks", command: "resume" } + ); + break; + case "resume": + followups.push( + { prompt: "Show context status", command: "status" } ); break; case "help": followups.push( { prompt: "Initialize project context", command: "init" }, - { prompt: "Show context status", command: "status" } + { prompt: "Show context status", command: "status" }, + { prompt: "Quick start guide", command: "guide" } ); break; } @@ -1152,12 +1799,26 @@ export { ensureCtxAvailable, bootstrap, getPlatformInfo, - handleComplete, + handleTask, handleRemind, - handleTasks, handlePad, handleNotify, handleSystem, + handleMemory, + handleJournal, + handleDoctor, + handleConfig, + handlePrompt, + handleWhy, + handleChange, + handleDep, + handleGuide, + handlePermission, + handleSite, + handleLoop, + handlePause, + handleResume, + handleReindex, }; export function deactivate() {} diff --git a/hack/lint-drift.sh b/hack/lint-drift.sh index 08f83326a..8bb2be8de 100755 --- a/hack/lint-drift.sh +++ b/hack/lint-drift.sh @@ -13,6 +13,7 @@ # 3. Magic directory strings that have config.Dir* constants # 4. Literal ".md" (should use config.ExtMarkdown) # 5. Go DescKey ↔ YAML key linkage (embed/{cmd,flag,text} vs commands/) +# Only DescKey* constants are checked — Use* and ExampleKey* are excluded. # # Exit code: number of issues found (0 = clean). @@ -47,7 +48,7 @@ drift_count() { # ── 1. Literal "\n" ───────────────────────────────────────────────── # Match "\n" as a Go string (not inside comments or imports). # Skip config/token.go where the constant is defined. -hits=$(drift_grep '"\\n"' 'token.go') +hits=$(drift_grep '"\\n"' 'whitespace.go') count=$(drift_count "$hits") if [ "$count" -gt 0 ]; then echo "==> Literal \"\\n\" found ($count occurrences, use config.NewlineLF):" @@ -82,7 +83,7 @@ done # ── 4. Literal ".md" ──────────────────────────────────────────────── # Skip config/file.go where ExtMarkdown is defined. -hits=$(drift_grep '"\.md"' 'file.go') +hits=$(drift_grep '"\.md"' 'ext.go') count=$(drift_count "$hits") if [ "$count" -gt 0 ]; then echo "==> Literal \".md\" found ($count occurrences, use config.ExtMarkdown):" @@ -130,11 +131,11 @@ check_linkage() { } # cmd constants ↔ commands.yaml -# ExampleKey* constants map to examples.yaml, not commands.yaml — exclude them. +# Only DescKey* constants are YAML lookup keys. +# Use* constants are cobra Use fields; ExampleKey* map to examples.yaml. cmd_go_dir="internal/config/embed/cmd" -cmd_go_keys=$(grep -rh '= "' "$cmd_go_dir"/*.go 2>/dev/null \ +cmd_go_keys=$(grep -rh 'DescKey.*= "' "$cmd_go_dir"/*.go 2>/dev/null \ | grep -v '//' \ - | grep -v 'ExampleKey' \ | sed 's/.*= "//; s/"//' \ | sort -u) cmd_yaml_keys=$(grep -E '^[a-z]' "internal/assets/commands/commands.yaml" \ @@ -170,20 +171,10 @@ check_linkage "text↔text/*.yaml" \ "$text_yaml_merged" rm -f "$text_yaml_merged" -# Cross-namespace duplicates (same key value in cmd + flag + text) -dupes=$( - grep -rh '= "' internal/config/embed/cmd/*.go internal/config/embed/flag/*.go internal/config/embed/text/*.go 2>/dev/null \ - | grep -v '//' \ - | sed 's/.*= "//; s/"//' \ - | sort | uniq -d -) -if [ -n "$dupes" ]; then - dupe_count=$(echo "$dupes" | wc -l | tr -d ' ') - echo "==> Cross-namespace duplicates ($dupe_count key(s) appear in multiple embed packages):" - echo "$dupes" | sed 's/^/ /' - echo "" - issues=$((issues + dupe_count)) -fi +# Cross-namespace duplicates: cmd, flag, and text are separate YAML files +# with separate Go packages — the same key in each is by design (e.g. "agent" +# appears as a command description, a flag description, and a text string). +# No check needed. # ── Summary ────────────────────────────────────────────────────────── if [ "$issues" -eq 0 ]; then diff --git a/hack/release.sh b/hack/release.sh index 1771c5e51..d9885be21 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -100,15 +100,31 @@ rm -f "${PLUGIN_JSON}.bak" sed -i.bak -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"${VERSION_NUM}\"/" "${MARKETPLACE_JSON}" rm -f "${MARKETPLACE_JSON}.bak" +# Update VS Code extension version +VSCODE_PKG="editors/vscode/package.json" +VSCODE_LOCK="editors/vscode/package-lock.json" +echo "Updating VS Code extension version in ${VSCODE_PKG} and ${VSCODE_LOCK}..." +sed -i.bak -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"${VERSION_NUM}\"/" "${VSCODE_PKG}" +rm -f "${VSCODE_PKG}.bak" +sed -i.bak -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"${VERSION_NUM}\"/" "${VSCODE_LOCK}" +rm -f "${VSCODE_LOCK}.bak" + # Update version references in documentation -echo "Updating version references in docs/index.md..." -sed -i.bak -E "s|/v[0-9]+\.[0-9]+\.[0-9]+/ctx-[0-9]+\.[0-9]+\.[0-9]+|/v${VERSION_NUM}/ctx-${VERSION_NUM}|g" docs/index.md -sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-linux-amd64|ctx-${VERSION_NUM}-linux-amd64|g" docs/index.md -sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-linux-arm64|ctx-${VERSION_NUM}-linux-arm64|g" docs/index.md -sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-darwin-amd64|ctx-${VERSION_NUM}-darwin-amd64|g" docs/index.md -sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-darwin-arm64|ctx-${VERSION_NUM}-darwin-arm64|g" docs/index.md -sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-windows-amd64|ctx-${VERSION_NUM}-windows-amd64|g" docs/index.md -rm -f docs/index.md.bak +echo "Updating version references in docs/index.md and docs/home/getting-started.md..." +for doc in docs/index.md docs/home/getting-started.md; do + sed -i.bak -E "s|/v[0-9]+\.[0-9]+\.[0-9]+/ctx-[0-9]+\.[0-9]+\.[0-9]+|/v${VERSION_NUM}/ctx-${VERSION_NUM}|g" "${doc}" + sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-linux-amd64|ctx-${VERSION_NUM}-linux-amd64|g" "${doc}" + sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-linux-arm64|ctx-${VERSION_NUM}-linux-arm64|g" "${doc}" + sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-darwin-amd64|ctx-${VERSION_NUM}-darwin-amd64|g" "${doc}" + sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-darwin-arm64|ctx-${VERSION_NUM}-darwin-arm64|g" "${doc}" + sed -i.bak -E "s|ctx-[0-9]+\.[0-9]+\.[0-9]+-windows-amd64|ctx-${VERSION_NUM}-windows-amd64|g" "${doc}" + rm -f "${doc}.bak" +done + +# Update VSIX version in integrations doc +echo "Updating VSIX version in docs/operations/integrations.md..." +sed -i.bak -E "s|ctx-context-[0-9]+\.[0-9]+\.[0-9]+\.vsix|ctx-context-${VERSION_NUM}.vsix|g" docs/operations/integrations.md +rm -f docs/operations/integrations.md.bak # Update versions.md with new release echo "Updating docs/versions.md..." @@ -129,7 +145,7 @@ make site # Commit docs and site updates echo "Committing documentation updates..." -git add docs/index.md docs/versions.md site/ "${PLUGIN_JSON}" +git add docs/index.md docs/home/getting-started.md docs/operations/integrations.md docs/versions.md site/ "${PLUGIN_JSON}" "${MARKETPLACE_JSON}" "${VSCODE_PKG}" "${VSCODE_LOCK}" git diff --cached --quiet || git commit -m "docs: update download links and versions page for ${VERSION}" echo "" diff --git a/internal/assets/hooks/messages/doc.go b/internal/assets/hooks/messages/doc.go new file mode 100644 index 000000000..02c753ed4 --- /dev/null +++ b/internal/assets/hooks/messages/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package messages provides metadata for hook message templates. +package messages diff --git a/internal/assets/hooks/messages/registry.go b/internal/assets/hooks/messages/registry.go index 7bd757fb6..fb8f17715 100644 --- a/internal/assets/hooks/messages/registry.go +++ b/internal/assets/hooks/messages/registry.go @@ -4,19 +4,14 @@ // \ Copyright 2026-present Context contributors. // SPDX-License-Identifier: Apache-2.0 -// Package messages provides metadata for hook message templates. -// -// The registry is parsed from an embedded registry.yaml that lives -// alongside the .txt template files. It is the metadata layer over -// the embedded FS and changes only when hooks are added or removed. package messages import ( "sync" "github.com/ActiveMemory/ctx/internal/assets/read/hook" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" - errparser "github.com/ActiveMemory/ctx/internal/err/parser" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" + errParser "github.com/ActiveMemory/ctx/internal/err/parser" "gopkg.in/yaml.v3" ) @@ -59,12 +54,12 @@ func Registry() []HookMessageInfo { registryOnce.Do(func() { raw, readErr := hook.MessageRegistry() if readErr != nil { - registryErr = fserr.FileRead("registry.yaml", readErr) + registryErr = errFs.FileRead("registry.yaml", readErr) return } var entries []HookMessageInfo if unmarshalErr := yaml.Unmarshal(raw, &entries); unmarshalErr != nil { - registryErr = errparser.ParseFile("registry.yaml", unmarshalErr) + registryErr = errParser.ParseFile("registry.yaml", unmarshalErr) return } registryData = entries diff --git a/internal/assets/why/about.md b/internal/assets/why/about.md index 059bc7f63..44b21e31d 100644 --- a/internal/assets/why/about.md +++ b/internal/assets/why/about.md @@ -135,7 +135,7 @@ makes the *next* session **smarter**. **Connect with `ctx`** * [Join the Community →](community.md): ask questions, share workflows, and help shape what comes next -* [Read the Blog →](../../blog/): real-world patterns, ponderings, and lessons learned from building `ctx` using `ctx` +* [Read the Blog →](../blog/): real-world patterns, ponderings, and lessons learned from building `ctx` using `ctx` ---- diff --git a/internal/bootstrap/cmd.go b/internal/bootstrap/cmd.go index 7dab83c9b..870d82d06 100644 --- a/internal/bootstrap/cmd.go +++ b/internal/bootstrap/cmd.go @@ -20,7 +20,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/flag" ctxContext "github.com/ActiveMemory/ctx/internal/context/validate" "github.com/ActiveMemory/ctx/internal/err/fs" - errInitialize "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/validation" ) @@ -89,7 +89,7 @@ func RootCmd() *cobra.Command { // Require initialization. if !ctxContext.Initialized(rc.ContextDir()) { - return errInitialize.NotInitialized() + return errInit.NotInitialized() } return nil diff --git a/internal/claude/claude_test.go b/internal/claude/claude_test.go index 2a5d55349..6100557cd 100644 --- a/internal/claude/claude_test.go +++ b/internal/claude/claude_test.go @@ -13,7 +13,7 @@ import ( "testing" "github.com/ActiveMemory/ctx/internal/assets" - ctxerr "github.com/ActiveMemory/ctx/internal/err/skill" + errSkill "github.com/ActiveMemory/ctx/internal/err/skill" ) func TestSkills(t *testing.T) { @@ -98,29 +98,29 @@ func TestSettingsStructure(t *testing.T) { func TestErrSkillList(t *testing.T) { cause := errors.New("read dir failed") - err := ctxerr.List(cause) + err := errSkill.List(cause) if err == nil { - t.Fatal("ctxerr.List() returned nil") + t.Fatal("errSkill.List() returned nil") } if !strings.Contains(err.Error(), "failed to list skills") { - t.Errorf("ctxerr.List() error missing prefix: %v", err) + t.Errorf("errSkill.List() error missing prefix: %v", err) } if !errors.Is(err, cause) { - t.Error("ctxerr.List() does not wrap the cause error") + t.Error("errSkill.List() does not wrap the cause error") } } func TestErrSkillRead(t *testing.T) { cause := errors.New("not found") - err := ctxerr.Read("my-skill", cause) + err := errSkill.Read("my-skill", cause) if err == nil { - t.Fatal("ctxerr.Read() returned nil") + t.Fatal("errSkill.Read() returned nil") } if !strings.Contains(err.Error(), "my-skill") { - t.Errorf("ctxerr.Read() error missing skill name: %v", err) + t.Errorf("errSkill.Read() error missing skill name: %v", err) } if !errors.Is(err, cause) { - t.Error("ctxerr.Read() does not wrap the cause error") + t.Error("errSkill.Read() does not wrap the cause error") } } diff --git a/internal/cli/add/cmd/coverage_test.go b/internal/cli/add/cmd/coverage_test.go index dd25a7978..76cdc8148 100644 --- a/internal/cli/add/cmd/coverage_test.go +++ b/internal/cli/add/cmd/coverage_test.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/cli/add/cmd/root" - entry2 "github.com/ActiveMemory/ctx/internal/cli/add/core/entry" + coreEntry "github.com/ActiveMemory/ctx/internal/cli/add/core/entry" "github.com/ActiveMemory/ctx/internal/cli/add/core/example" "github.com/ActiveMemory/ctx/internal/cli/add/core/extract" "github.com/ActiveMemory/ctx/internal/cli/add/core/format" @@ -27,7 +27,7 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/cli/initialize" - entrytype "github.com/ActiveMemory/ctx/internal/config/entry" + entryType "github.com/ActiveMemory/ctx/internal/config/entry" "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/entry" ) @@ -44,7 +44,7 @@ func TestErrNoContent(t *testing.T) { } func TestErrNoContentProvided(t *testing.T) { - for _, fType := range []string{entrytype.Decision, entrytype.Task, entrytype.Learning, entrytype.Convention, entrytype.Unknown} { + for _, fType := range []string{entryType.Decision, entryType.Task, entryType.Learning, entryType.Convention, entryType.Unknown} { t.Run(fType, func(t *testing.T) { err := add.ErrNoContentProvided(fType, example.ForType(fType)) if err == nil { @@ -152,11 +152,11 @@ func TestExamplesForType(t *testing.T) { fType string contains string }{ - {entrytype.Decision, "ctx add decision"}, - {entrytype.Task, "ctx add task"}, - {entrytype.Learning, "ctx add learning"}, - {entrytype.Convention, "ctx add convention"}, - {entrytype.Unknown, "ctx add "}, + {entryType.Decision, "ctx add decision"}, + {entryType.Task, "ctx add task"}, + {entryType.Learning, "ctx add learning"}, + {entryType.Convention, "ctx add convention"}, + {entryType.Unknown, "ctx add "}, } for _, tt := range tests { t.Run(tt.fType, func(t *testing.T) { @@ -742,23 +742,23 @@ func TestRun_TaskWithSection(t *testing.T) { func TestPredicates(t *testing.T) { // Test plural forms - if !entry2.FileTypeIsTask("tasks") { + if !coreEntry.FileTypeIsTask("tasks") { t.Error("FileTypeIsTask should accept 'tasks'") } - if !entry2.FileTypeIsDecision("decisions") { + if !coreEntry.FileTypeIsDecision("decisions") { t.Error("FileTypeIsDecision should accept 'decisions'") } - if !entry2.FileTypeIsLearning("learnings") { + if !coreEntry.FileTypeIsLearning("learnings") { t.Error("FileTypeIsLearning should accept 'learnings'") } // Test negative cases - if entry2.FileTypeIsTask("decision") { + if coreEntry.FileTypeIsTask("decision") { t.Error("FileTypeIsTask should reject 'decision'") } - if entry2.FileTypeIsDecision("task") { + if coreEntry.FileTypeIsDecision("task") { t.Error("FileTypeIsDecision should reject 'task'") } - if entry2.FileTypeIsLearning("convention") { + if coreEntry.FileTypeIsLearning("convention") { t.Error("FileTypeIsLearning should reject 'convention'") } } diff --git a/internal/cli/agent/agent.go b/internal/cli/agent/agent.go index ef4806f99..832baa3b2 100644 --- a/internal/cli/agent/agent.go +++ b/internal/cli/agent/agent.go @@ -9,10 +9,10 @@ package agent import ( "github.com/spf13/cobra" - agentroot "github.com/ActiveMemory/ctx/internal/cli/agent/cmd/root" + agentRoot "github.com/ActiveMemory/ctx/internal/cli/agent/cmd/root" ) // Cmd returns the "ctx agent" command for generating AI-ready context packets. func Cmd() *cobra.Command { - return agentroot.Cmd() + return agentRoot.Cmd() } diff --git a/internal/cli/agent/cmd/root/run.go b/internal/cli/agent/cmd/root/run.go index 345cce07d..00f7973e5 100644 --- a/internal/cli/agent/cmd/root/run.go +++ b/internal/cli/agent/cmd/root/run.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fmt" "github.com/ActiveMemory/ctx/internal/context/load" errCtx "github.com/ActiveMemory/ctx/internal/err/context" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" ) // Run executes the agent command logic. @@ -53,7 +53,7 @@ func Run( if err != nil { var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return ctxErr.NotInitialized() + return errInit.NotInitialized() } return err } diff --git a/internal/cli/agent/core/budget/assemble.go b/internal/cli/agent/core/budget/assemble.go index 082246e15..24d189040 100644 --- a/internal/cli/agent/core/budget/assemble.go +++ b/internal/cli/agent/core/budget/assemble.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/agent/core/score" "github.com/ActiveMemory/ctx/internal/cli/agent/core/sort" "github.com/ActiveMemory/ctx/internal/config/agent" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" ctxToken "github.com/ActiveMemory/ctx/internal/context/token" "github.com/ActiveMemory/ctx/internal/entity" @@ -85,8 +85,8 @@ func AssemblePacket(ctx *entity.Context, budget int) *assembledPacket { keywords := score.ExtractTaskKeywords(pkt.Tasks) // Tier 4+5: Decisions + Learnings (share remaining budget) - decisionBlocks := ParseEntryBlocks(ctx, ctxCfg.Decision) - learningBlocks := ParseEntryBlocks(ctx, ctxCfg.Learning) + decisionBlocks := ParseEntryBlocks(ctx, cfgCtx.Decision) + learningBlocks := ParseEntryBlocks(ctx, cfgCtx.Learning) scoredDecisions := score.ScoreEntries(decisionBlocks, keywords, now) scoredLearnings := score.ScoreEntries(learningBlocks, keywords, now) diff --git a/internal/cli/agent/core/budget/parse.go b/internal/cli/agent/core/budget/parse.go index a44e02d89..ded1bb501 100644 --- a/internal/cli/agent/core/budget/parse.go +++ b/internal/cli/agent/core/budget/parse.go @@ -9,7 +9,7 @@ package budget import ( "github.com/ActiveMemory/ctx/internal/cli/agent/core/extract" "github.com/ActiveMemory/ctx/internal/config/agent" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/index" ) @@ -38,7 +38,7 @@ func ParseEntryBlocks(ctx *entity.Context, fileName string) []index.EntryBlock { // Returns: // - []string: All convention bullet items; nil if the file is not found func ExtractAllConventions(ctx *entity.Context) []string { - if f := ctx.File(ctxCfg.Convention); f != nil { + if f := ctx.File(cfgCtx.Convention); f != nil { return extract.BulletItems(string(f.Content), agent.BulletItemLimit) } return nil diff --git a/internal/cli/agent/core/extract/extract.go b/internal/cli/agent/core/extract/extract.go index 9db92f83f..8070ced9e 100644 --- a/internal/cli/agent/core/extract/extract.go +++ b/internal/cli/agent/core/extract/extract.go @@ -9,7 +9,7 @@ package extract import ( "strings" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/marker" "github.com/ActiveMemory/ctx/internal/config/regex" "github.com/ActiveMemory/ctx/internal/config/token" @@ -69,7 +69,7 @@ func CheckboxItems(content string) []string { // Returns: // - []string: List of constitution rules; nil if the file is not found func ConstitutionRules(ctx *entity.Context) []string { - if f := ctx.File(ctxCfg.Constitution); f != nil { + if f := ctx.File(cfgCtx.Constitution); f != nil { return CheckboxItems(string(f.Content)) } return nil @@ -105,7 +105,7 @@ func UncheckedTasks(content string) []string { // - []string: List of active tasks with "- [ ]" prefix; nil if // the file is not found func ActiveTasks(ctx *entity.Context) []string { - if f := ctx.File(ctxCfg.Task); f != nil { + if f := ctx.File(cfgCtx.Task); f != nil { return UncheckedTasks(string(f.Content)) } return nil diff --git a/internal/cli/agent/core/sort/sort.go b/internal/cli/agent/core/sort/sort.go index 68e78aa46..6fc0b5bb0 100644 --- a/internal/cli/agent/core/sort/sort.go +++ b/internal/cli/agent/core/sort/sort.go @@ -9,7 +9,7 @@ package sort import ( "path/filepath" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/entity" ) @@ -26,7 +26,7 @@ import ( // - []string: File paths in reading order (e.g., ".context/CONSTITUTION.md") func ReadOrder(ctx *entity.Context) []string { var order []string - for _, name := range ctxCfg.ReadOrder { + for _, name := range cfgCtx.ReadOrder { if f := ctx.File(name); f != nil && !f.IsEmpty { order = append(order, filepath.Join(ctx.Dir, f.Name)) } diff --git a/internal/cli/change/cmd/root/run.go b/internal/cli/change/cmd/root/run.go index 303b9a14a..9df4e665e 100644 --- a/internal/cli/change/cmd/root/run.go +++ b/internal/cli/change/cmd/root/run.go @@ -12,7 +12,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/change/core/scan" "github.com/spf13/cobra" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" writeChange "github.com/ActiveMemory/ctx/internal/write/change" ) @@ -30,7 +30,7 @@ import ( func Run(cmd *cobra.Command, since string) error { refTime, refLabel, err := detect.ReferenceTime(since) if err != nil { - return ctxErr.DetectReferenceTime(err) + return errInit.DetectReferenceTime(err) } ctxChanges, _ := scan.FindContextChanges(refTime) diff --git a/internal/cli/change/core/cmd_test.go b/internal/cli/change/core/cmd_test.go index 680f1743b..fe09f7310 100644 --- a/internal/cli/change/core/cmd_test.go +++ b/internal/cli/change/core/cmd_test.go @@ -15,7 +15,7 @@ import ( "time" "github.com/ActiveMemory/ctx/internal/cli/change/core/detect" - format2 "github.com/ActiveMemory/ctx/internal/cli/change/core/render" + coreRender "github.com/ActiveMemory/ctx/internal/cli/change/core/render" "github.com/ActiveMemory/ctx/internal/cli/change/core/scan" "github.com/ActiveMemory/ctx/internal/format" @@ -134,7 +134,7 @@ func TestRenderChanges(t *testing.T) { Authors: []string{"Volkan"}, } - out := format2.Changes("6 hours ago", ctxChanges, code) + out := coreRender.Changes("6 hours ago", ctxChanges, code) if !strings.Contains(out, "## Changes Since Last Session") { t.Error("missing header") } @@ -155,7 +155,7 @@ func TestRenderChangesForHook(t *testing.T) { } code := entity.CodeSummary{CommitCount: 3, LatestMsg: "Fix bug"} - out := format2.ChangesForHook("2 hours ago", ctxChanges, code) + out := coreRender.ChangesForHook("2 hours ago", ctxChanges, code) if !strings.Contains(out, "Changes since last session") { t.Error("missing hook header") } @@ -164,14 +164,14 @@ func TestRenderChangesForHook(t *testing.T) { } // Empty case. - out = format2.ChangesForHook("1 hour ago", nil, entity.CodeSummary{}) + out = coreRender.ChangesForHook("1 hour ago", nil, entity.CodeSummary{}) if out != "" { t.Errorf("expected empty for no changes, got: %q", out) } } func TestRenderChanges_NoChanges(t *testing.T) { - out := format2.Changes("1 hour ago", nil, entity.CodeSummary{}) + out := coreRender.Changes("1 hour ago", nil, entity.CodeSummary{}) if !strings.Contains(out, "No changes detected") { t.Error("expected 'No changes detected' message") } diff --git a/internal/cli/change/core/detect/doc.go b/internal/cli/change/core/detect/doc.go new file mode 100644 index 000000000..7157ad832 --- /dev/null +++ b/internal/cli/change/core/detect/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package detect determines the reference time for change detection using markers, events, or flags. +package detect diff --git a/internal/cli/change/core/render/doc.go b/internal/cli/change/core/render/doc.go new file mode 100644 index 000000000..4b18e9ce1 --- /dev/null +++ b/internal/cli/change/core/render/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package render formats change summaries into human-readable Markdown output. +package render diff --git a/internal/cli/change/core/scan/doc.go b/internal/cli/change/core/scan/doc.go new file mode 100644 index 000000000..a40734733 --- /dev/null +++ b/internal/cli/change/core/scan/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package scan finds context and code files modified after a reference time. +package scan diff --git a/internal/cli/compact/cmd/root/run.go b/internal/cli/compact/cmd/root/run.go index 06b26a7d8..da110b7b6 100644 --- a/internal/cli/compact/cmd/root/run.go +++ b/internal/cli/compact/cmd/root/run.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/context/load" errCtx "github.com/ActiveMemory/ctx/internal/err/context" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/tidy" writeCompact "github.com/ActiveMemory/ctx/internal/write/compact" @@ -38,7 +38,7 @@ func Run(cmd *cobra.Command, archive bool) error { if err != nil { var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return ctxErr.ContextNotInitialized() + return errInit.ContextNotInitialized() } return err } diff --git a/internal/cli/compact/compact_test.go b/internal/cli/compact/compact_test.go index dc0746d90..bb3af9540 100644 --- a/internal/cli/compact/compact_test.go +++ b/internal/cli/compact/compact_test.go @@ -12,7 +12,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/add" "github.com/ActiveMemory/ctx/internal/cli/initialize" - taskcomplete "github.com/ActiveMemory/ctx/internal/cli/task/cmd/complete" + taskComplete "github.com/ActiveMemory/ctx/internal/cli/task/cmd/complete" ) // TestCompactCommand tests the compact command. @@ -72,7 +72,7 @@ func TestCompactWithTasks(t *testing.T) { t.Fatalf("add task failed: %v", err) } - completeCmd := taskcomplete.Cmd() + completeCmd := taskComplete.Cmd() completeCmd.SetArgs([]string{"Task to complete"}) if err := completeCmd.Execute(); err != nil { t.Fatalf("complete task failed: %v", err) diff --git a/internal/cli/compact/core/task.go b/internal/cli/compact/core/task.go index 3274061c1..572be40f6 100644 --- a/internal/cli/compact/core/task.go +++ b/internal/cli/compact/core/task.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/assets/read/desc" - archiveCfg "github.com/ActiveMemory/ctx/internal/config/archive" + cfgArchive "github.com/ActiveMemory/ctx/internal/config/archive" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/token" @@ -82,7 +82,7 @@ func CompactTasks( archiveContent += block.BlockContent() + nl + nl } if archiveFile, archiveErr := tidy.WriteArchive( - archiveCfg.ArchiveScopeTasks, + cfgArchive.ArchiveScopeTasks, desc.Text(text.DescKeyHeadingArchivedTasks), archiveContent, ); archiveErr == nil { diff --git a/internal/cli/config/cmd/status/cmd.go b/internal/cli/config/cmd/status/cmd.go index 63d1a59d6..4520db746 100644 --- a/internal/cli/config/cmd/status/cmd.go +++ b/internal/cli/config/cmd/status/cmd.go @@ -8,7 +8,7 @@ package status import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" - internalConfig "github.com/ActiveMemory/ctx/internal/config/cli" + cfgCli "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/spf13/cobra" @@ -25,7 +25,7 @@ func Cmd() *cobra.Command { return &cobra.Command{ Use: cmd.UseConfigStatus, Short: short, - Annotations: map[string]string{internalConfig.AnnotationSkipInit: ""}, + Annotations: map[string]string{cfgCli.AnnotationSkipInit: ""}, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { root, rootErr := core.GitRoot() diff --git a/internal/cli/config/cmd/switchcmd/cmd.go b/internal/cli/config/cmd/switchcmd/cmd.go index 5b4498c0b..a54527de7 100644 --- a/internal/cli/config/cmd/switchcmd/cmd.go +++ b/internal/cli/config/cmd/switchcmd/cmd.go @@ -11,7 +11,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/cli/config/core" - internalConfig "github.com/ActiveMemory/ctx/internal/config/cli" + cfgCli "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/embed/cmd" ) @@ -25,7 +25,7 @@ func Cmd() *cobra.Command { return &cobra.Command{ Use: cmd.UseConfigSwitch, Short: short, - Annotations: map[string]string{internalConfig.AnnotationSkipInit: ""}, + Annotations: map[string]string{cfgCli.AnnotationSkipInit: ""}, Long: long, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cli/config/cmd/switchcmd/run.go b/internal/cli/config/cmd/switchcmd/run.go index dcf454932..6afdfbab8 100644 --- a/internal/cli/config/cmd/switchcmd/run.go +++ b/internal/cli/config/cmd/switchcmd/run.go @@ -11,7 +11,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/config/core" "github.com/ActiveMemory/ctx/internal/config/file" - ctxErr "github.com/ActiveMemory/ctx/internal/err/config" + errConfig "github.com/ActiveMemory/ctx/internal/err/config" writeConfig "github.com/ActiveMemory/ctx/internal/write/config" ) @@ -50,7 +50,7 @@ func Run(cmd *cobra.Command, root string, args []string) error { profile = file.ProfileDev } default: - return ctxErr.UnknownProfile(target) + return errConfig.UnknownProfile(target) } msg, switchErr := core.SwitchTo(root, profile) diff --git a/internal/cli/config/core/core.go b/internal/cli/config/core/core.go index 7fda5308d..b45e9e07f 100644 --- a/internal/cli/config/core/core.go +++ b/internal/cli/config/core/core.go @@ -20,7 +20,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fs" cfgGit "github.com/ActiveMemory/ctx/internal/config/git" "github.com/ActiveMemory/ctx/internal/err/config" - ctxErr "github.com/ActiveMemory/ctx/internal/err/git" + errGit "github.com/ActiveMemory/ctx/internal/err/git" "github.com/ActiveMemory/ctx/internal/io" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -95,14 +95,14 @@ func SwitchTo(root, profile string) (string, error) { // degrade gracefully when this returns an error. func GitRoot() (string, error) { if _, lookErr := exec.LookPath(cfgGit.Binary); lookErr != nil { - return "", ctxErr.NotFound() + return "", errGit.NotFound() } out, execErr := exec.Command( //nolint:gosec // args are literal constants cfgGit.Binary, cfgGit.RevParse, cfgGit.FlagShowToplevel, ).Output() if execErr != nil { - return "", ctxErr.NotInRepo(execErr) + return "", errGit.NotInRepo(execErr) } return strings.TrimSpace(string(out)), nil } diff --git a/internal/cli/dep/cmd/root/run.go b/internal/cli/dep/cmd/root/run.go index 07ccf5349..6c97aa95f 100644 --- a/internal/cli/dep/cmd/root/run.go +++ b/internal/cli/dep/cmd/root/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/dep/core" "github.com/ActiveMemory/ctx/internal/config/fmt" "github.com/ActiveMemory/ctx/internal/config/token" - ctxErr "github.com/ActiveMemory/ctx/internal/err/config" + errConfig "github.com/ActiveMemory/ctx/internal/err/config" "github.com/ActiveMemory/ctx/internal/write/deps" ) @@ -37,14 +37,14 @@ func Run(cmd *cobra.Command, format string, external bool, projType string) erro switch format { case fmt.FormatMermaid, fmt.FormatTable, fmt.FormatJSON: default: - return ctxErr.UnknownFormat(format, supportedFormats) + return errConfig.UnknownFormat(format, supportedFormats) } var builder core.GraphBuilder if projType != "" { builder = core.FindBuilder(projType) if builder == nil { - return ctxErr.UnknownProjectType(projType, strings.Join(core.BuilderNames(), token.CommaSpace)) + return errConfig.UnknownProjectType(projType, strings.Join(core.BuilderNames(), token.CommaSpace)) } } else { builder = core.DetectBuilder() diff --git a/internal/cli/drift/cmd/root/run.go b/internal/cli/drift/cmd/root/run.go index 200af357d..0209ab97b 100644 --- a/internal/cli/drift/cmd/root/run.go +++ b/internal/cli/drift/cmd/root/run.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/context/load" "github.com/ActiveMemory/ctx/internal/drift" errCtx "github.com/ActiveMemory/ctx/internal/err/context" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" writeDrift "github.com/ActiveMemory/ctx/internal/write/drift" ) @@ -37,7 +37,7 @@ func Run(cmd *cobra.Command, jsonOutput, fix bool) error { if err != nil { var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return ctxErr.NotInitialized() + return errInit.NotInitialized() } return err } diff --git a/internal/cli/drift/core/fix.go b/internal/cli/drift/core/fix.go index 7bf1989b7..fe304e029 100644 --- a/internal/cli/drift/core/fix.go +++ b/internal/cli/drift/core/fix.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/assets/read/template" "github.com/ActiveMemory/ctx/internal/config/archive" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/marker" @@ -27,7 +27,7 @@ import ( "github.com/ActiveMemory/ctx/internal/entity" errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/err/prompt" - ctxErr "github.com/ActiveMemory/ctx/internal/err/task" + errTask "github.com/ActiveMemory/ctx/internal/err/task" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/task" "github.com/ActiveMemory/ctx/internal/tidy" @@ -106,10 +106,10 @@ func ApplyFixes( // Returns: // - error: Non-nil if file operations fail func FixStaleness(cmd *cobra.Command, ctx *entity.Context) error { - tasksFile := ctx.File(ctxCfg.Task) + tasksFile := ctx.File(cfgCtx.Task) if tasksFile == nil { - return ctxErr.FileNotFound() + return errTask.FileNotFound() } nl := token.NewlineLF @@ -145,7 +145,7 @@ func FixStaleness(cmd *cobra.Command, ctx *entity.Context) error { } if len(completedTasks) == 0 { - return ctxErr.NoneCompleted() + return errTask.NoneCompleted() } // Build archive content @@ -167,7 +167,7 @@ func FixStaleness(cmd *cobra.Command, ctx *entity.Context) error { if writeErr := os.WriteFile( tasksFile.Path, []byte(newContent), fs.PermFile, ); writeErr != nil { - return ctxErr.FileWrite(writeErr) + return errTask.FileWrite(writeErr) } writeDrift.Archived(cmd, len(completedTasks), archiveFile) diff --git a/internal/cli/drift/core/out.go b/internal/cli/drift/core/out.go index 891a9b527..18d15bb14 100644 --- a/internal/cli/drift/core/out.go +++ b/internal/cli/drift/core/out.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/drift" - errdrift "github.com/ActiveMemory/ctx/internal/err/drift" + errDrift "github.com/ActiveMemory/ctx/internal/err/drift" writeDrift "github.com/ActiveMemory/ctx/internal/write/drift" ) @@ -119,7 +119,7 @@ func OutputDriftText(cmd *cobra.Command, report *drift.Report) error { switch status { case drift.StatusViolation: writeDrift.StatusViolation(cmd) - return errdrift.Violations() + return errDrift.Violations() case drift.StatusWarning: writeDrift.StatusWarning(cmd) default: diff --git a/internal/cli/hook/cmd/root/run.go b/internal/cli/hook/cmd/root/run.go index 34ecc9084..7204f6dd9 100644 --- a/internal/cli/hook/cmd/root/run.go +++ b/internal/cli/hook/cmd/root/run.go @@ -18,11 +18,11 @@ import ( "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/fs" - hookCfg "github.com/ActiveMemory/ctx/internal/config/hook" + cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" "github.com/ActiveMemory/ctx/internal/config/marker" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/err/config" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" writeErr "github.com/ActiveMemory/ctx/internal/write/err" "github.com/ActiveMemory/ctx/internal/write/hook" ) @@ -44,16 +44,16 @@ func Run(cmd *cobra.Command, args []string, writeFile bool) error { tool := strings.ToLower(args[0]) switch tool { - case hookCfg.ToolClaudeCode, hookCfg.ToolClaude: + case cfgHook.ToolClaudeCode, cfgHook.ToolClaude: hook.InfoTool(cmd, desc.Text(text.DescKeyHookClaude)) - case hookCfg.ToolCursor: + case cfgHook.ToolCursor: hook.InfoTool(cmd, desc.Text(text.DescKeyHookCursor)) - case hookCfg.ToolAider: + case cfgHook.ToolAider: hook.InfoTool(cmd, desc.Text(text.DescKeyHookAider)) - case hookCfg.ToolCopilot: + case cfgHook.ToolCopilot: if writeFile { return WriteCopilotInstructions(cmd) } @@ -65,7 +65,7 @@ func Run(cmd *cobra.Command, args []string, writeFile bool) error { } hook.Content(cmd, string(content)) - case hookCfg.ToolWindsurf: + case cfgHook.ToolWindsurf: hook.InfoTool(cmd, desc.Text(text.DescKeyHookWindsurf)) default: @@ -89,11 +89,11 @@ func Run(cmd *cobra.Command, args []string, writeFile bool) error { // Returns: // - error: Non-nil if directory creation or file write fails func WriteCopilotInstructions(cmd *cobra.Command) error { - targetFile := filepath.Join(hookCfg.DirGitHub, hookCfg.FileCopilotInstructions) + targetFile := filepath.Join(cfgHook.DirGitHub, cfgHook.FileCopilotInstructions) // Create .github/ directory if needed - if err := os.MkdirAll(hookCfg.DirGitHub, fs.PermExec); err != nil { - return ctxErr.Mkdir(hookCfg.DirGitHub, err) + if err := os.MkdirAll(cfgHook.DirGitHub, fs.PermExec); err != nil { + return errFs.Mkdir(cfgHook.DirGitHub, err) } // Load the copilot instructions from embedded assets @@ -116,7 +116,7 @@ func WriteCopilotInstructions(cmd *cobra.Command) error { // File exists without ctx markers: append ctx content merged := existingStr + token.NewlineLF + string(instructions) if wErr := os.WriteFile(targetFile, []byte(merged), fs.PermFile); wErr != nil { - return ctxErr.FileWrite(targetFile, wErr) + return errFs.FileWrite(targetFile, wErr) } hook.InfoCopilotMerged(cmd, targetFile) return nil @@ -126,7 +126,7 @@ func WriteCopilotInstructions(cmd *cobra.Command) error { if wErr := os.WriteFile( targetFile, instructions, fs.PermFile, ); wErr != nil { - return ctxErr.FileWrite(targetFile, wErr) + return errFs.FileWrite(targetFile, wErr) } hook.InfoCopilotCreated(cmd, targetFile) diff --git a/internal/cli/hook/hook.go b/internal/cli/hook/hook.go index fea9ee339..2a661ccf4 100644 --- a/internal/cli/hook/hook.go +++ b/internal/cli/hook/hook.go @@ -9,10 +9,10 @@ package hook import ( "github.com/spf13/cobra" - hookroot "github.com/ActiveMemory/ctx/internal/cli/hook/cmd/root" + hookRoot "github.com/ActiveMemory/ctx/internal/cli/hook/cmd/root" ) // Cmd returns the "ctx hook" command for generating AI tool integrations. func Cmd() *cobra.Command { - return hookroot.Cmd() + return hookRoot.Cmd() } diff --git a/internal/cli/hook/hook_test.go b/internal/cli/hook/hook_test.go index 0b1bc75a2..11afc4ab1 100644 --- a/internal/cli/hook/hook_test.go +++ b/internal/cli/hook/hook_test.go @@ -13,7 +13,7 @@ import ( "strings" "testing" - hookroot "github.com/ActiveMemory/ctx/internal/cli/hook/cmd/root" + hookRoot "github.com/ActiveMemory/ctx/internal/cli/hook/cmd/root" "github.com/spf13/cobra" ) @@ -70,7 +70,7 @@ func hookCmdOutput(cmd *cobra.Command) string { func TestWriteCopilotInstructions_NewFile(t *testing.T) { tmpDir := t.TempDir() - // hookroot.WriteCopilotInstructions uses relative paths, so chdir. + // hookRoot.WriteCopilotInstructions uses relative paths, so chdir. origDir, wdErr := os.Getwd() if wdErr != nil { t.Fatal(wdErr) @@ -83,8 +83,8 @@ func TestWriteCopilotInstructions_NewFile(t *testing.T) { }) cmd := newHookTestCmd() - if runErr := hookroot.WriteCopilotInstructions(cmd); runErr != nil { - t.Fatalf("hookroot.WriteCopilotInstructions failed: %v", runErr) + if runErr := hookRoot.WriteCopilotInstructions(cmd); runErr != nil { + t.Fatalf("hookRoot.WriteCopilotInstructions failed: %v", runErr) } targetFile := filepath.Join(tmpDir, ".github", "copilot-instructions.md") @@ -129,8 +129,8 @@ func TestWriteCopilotInstructions_ExistingWithMarker(t *testing.T) { } cmd := newHookTestCmd() - if runErr := hookroot.WriteCopilotInstructions(cmd); runErr != nil { - t.Fatalf("hookroot.WriteCopilotInstructions failed: %v", runErr) + if runErr := hookRoot.WriteCopilotInstructions(cmd); runErr != nil { + t.Fatalf("hookRoot.WriteCopilotInstructions failed: %v", runErr) } // File should be unchanged (skipped). @@ -175,8 +175,8 @@ func TestWriteCopilotInstructions_ExistingWithoutMarker(t *testing.T) { } cmd := newHookTestCmd() - if runErr := hookroot.WriteCopilotInstructions(cmd); runErr != nil { - t.Fatalf("hookroot.WriteCopilotInstructions failed: %v", runErr) + if runErr := hookRoot.WriteCopilotInstructions(cmd); runErr != nil { + t.Fatalf("hookRoot.WriteCopilotInstructions failed: %v", runErr) } data, readErr := os.ReadFile(targetFile) diff --git a/internal/cli/initialize/cmd/root/run.go b/internal/cli/initialize/cmd/root/run.go index 5907484ee..b32e811a5 100644 --- a/internal/cli/initialize/cmd/root/run.go +++ b/internal/cli/initialize/cmd/root/run.go @@ -12,12 +12,12 @@ import ( "path/filepath" "strings" - claude2 "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude" + coreClaude "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/entry" coreMerge "github.com/ActiveMemory/ctx/internal/cli/initialize/core/merge" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/plan" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/plugin" - project2 "github.com/ActiveMemory/ctx/internal/cli/initialize/core/project" + coreProject "github.com/ActiveMemory/ctx/internal/cli/initialize/core/project" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/prompt" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/validate" "github.com/spf13/cobra" @@ -39,7 +39,7 @@ import ( "github.com/ActiveMemory/ctx/internal/crypto" errCrypto "github.com/ActiveMemory/ctx/internal/err/crypto" errFs "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/prompt" + errPrompt "github.com/ActiveMemory/ctx/internal/err/prompt" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/initialize" ) @@ -102,7 +102,7 @@ func Run( var listErr error templatesToCreate, listErr = catalog.List() if listErr != nil { - return ctxErr.ListTemplates(listErr) + return errPrompt.ListTemplates(listErr) } } @@ -118,7 +118,7 @@ func Run( content, err := template.Template(name) if err != nil { - return ctxErr.ReadTemplate(name, err) + return errPrompt.ReadTemplate(name, err) } if err := os.WriteFile(targetPath, content, fs.PermFile); err != nil { @@ -152,7 +152,7 @@ func Run( initialize.InfoCreatingRootFiles(cmd) // Create specs/ and ideas/ directories with README.md - if err := project2.CreateProjectDirs(cmd); err != nil { + if err := coreProject.CreateProjectDirs(cmd); err != nil { initialize.InfoWarnNonFatal(cmd, desc.Text(text.DescKeyInitLabelProjectDirs), err) } @@ -184,13 +184,13 @@ func Run( } // Handle CLAUDE.md creation/merge - if err := claude2.HandleClaudeMd(cmd, force, merge); err != nil { + if err := coreClaude.HandleClaudeMd(cmd, force, merge); err != nil { // Non-fatal: warn but continue initialize.InfoWarnNonFatal(cmd, claude.Md, err) } // Deploy Makefile.ctx and amend user Makefile - if err := project2.HandleMakefileCtx(cmd); err != nil { + if err := coreProject.HandleMakefileCtx(cmd); err != nil { // Non-fatal: warn but continue initialize.InfoWarnNonFatal(cmd, sync.PatternMakefile, err) } diff --git a/internal/cli/initialize/core/backup/file.go b/internal/cli/initialize/core/backup/file.go index a9185f9ce..05a9d778c 100644 --- a/internal/cli/initialize/core/backup/file.go +++ b/internal/cli/initialize/core/backup/file.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/config/fs" - backupPkg "github.com/ActiveMemory/ctx/internal/err/backup" + errBackup "github.com/ActiveMemory/ctx/internal/err/backup" "github.com/ActiveMemory/ctx/internal/write/initialize" "github.com/spf13/cobra" ) @@ -31,7 +31,7 @@ func File(cmd *cobra.Command, filename string, content []byte) error { timestamp := time.Now().Unix() backupName := fmt.Sprintf(file.BackupFormat, filename, timestamp) if writeErr := os.WriteFile(backupName, content, fs.PermFile); writeErr != nil { - return backupPkg.Create(backupName, writeErr) + return errBackup.Create(backupName, writeErr) } initialize.Backup(cmd, backupName) return nil diff --git a/internal/cli/initialize/core/claude/claude.go b/internal/cli/initialize/core/claude/claude.go index 3ace980a8..f87bc42d7 100644 --- a/internal/cli/initialize/core/claude/claude.go +++ b/internal/cli/initialize/core/claude/claude.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/marker" "github.com/ActiveMemory/ctx/internal/entity" - errInitialize "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/write/initialize" ) @@ -32,7 +32,7 @@ import ( func HandleClaudeMd(cmd *cobra.Command, force, autoMerge bool) error { templateContent, err := readClaude.Md() if err != nil { - return errInitialize.ReadTemplate(claude.Md, err) + return errInit.ReadTemplate(claude.Md, err) } created, mergeErr := merge.CreateOrMerge(cmd, entity.MergeParams{ diff --git a/internal/cli/initialize/core/merge/create.go b/internal/cli/initialize/core/merge/create.go index 2e3aef0a4..26438450a 100644 --- a/internal/cli/initialize/core/merge/create.go +++ b/internal/cli/initialize/core/merge/create.go @@ -20,7 +20,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" errFs "github.com/ActiveMemory/ctx/internal/err/fs" - promptErr "github.com/ActiveMemory/ctx/internal/err/prompt" + errPrompt "github.com/ActiveMemory/ctx/internal/err/prompt" "github.com/ActiveMemory/ctx/internal/write/initialize" ) @@ -126,7 +126,7 @@ func UpdateMarkedSection( ) error { startIdx := strings.Index(existing, markerStart) if startIdx == -1 { - return promptErr.MarkerNotFound(filename) + return errPrompt.MarkerNotFound(filename) } endIdx := strings.Index(existing, markerEnd) @@ -140,7 +140,7 @@ func UpdateMarkedSection( templateStart := strings.Index(templateStr, markerStart) templateEnd := strings.Index(templateStr, markerEnd) if templateStart == -1 || templateEnd == -1 { - return promptErr.TemplateMissingMarkers(filename) + return errPrompt.TemplateMissingMarkers(filename) } sectionContent := templateStr[templateStart : templateEnd+len(markerEnd)] diff --git a/internal/cli/initialize/core/project/dir.go b/internal/cli/initialize/core/project/dir.go index d254e642b..3869204fe 100644 --- a/internal/cli/initialize/core/project/dir.go +++ b/internal/cli/initialize/core/project/dir.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/config/fs" errFs "github.com/ActiveMemory/ctx/internal/err/fs" - errInitialize "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/write/initialize" ) @@ -42,7 +42,7 @@ func CreateProjectDirs(cmd *cobra.Command) error { readme, readErr := project.Readme(d) if readErr != nil { - return errInitialize.ReadProjectReadme(d, readErr) + return errInit.ReadProjectReadme(d, readErr) } readmePath := filepath.Join(d, file.Readme) diff --git a/internal/cli/initialize/core/project/makefile.go b/internal/cli/initialize/core/project/makefile.go index 87961b2bb..2881a8e84 100644 --- a/internal/cli/initialize/core/project/makefile.go +++ b/internal/cli/initialize/core/project/makefile.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/project" "github.com/ActiveMemory/ctx/internal/config/token" errFs "github.com/ActiveMemory/ctx/internal/err/fs" - errInitialize "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/write/initialize" ) @@ -32,7 +32,7 @@ func HandleMakefileCtx(cmd *cobra.Command) error { content, err := makefile.Ctx() if err != nil { - return errInitialize.ReadTemplate(project.MakefileCtx, err) + return errInit.ReadTemplate(project.MakefileCtx, err) } if err = os.WriteFile(project.MakefileCtx, content, fs.PermFile); err != nil { @@ -47,7 +47,7 @@ func HandleMakefileCtx(cmd *cobra.Command) error { if err := os.WriteFile( project.Makefile, []byte(minimal), fs.PermFile, ); err != nil { - return errInitialize.CreateMakefile(err) + return errInit.CreateMakefile(err) } initialize.MakefileCreated(cmd) return nil diff --git a/internal/cli/initialize/init_test.go b/internal/cli/initialize/init_test.go index 72959fb1d..fa5336f4e 100644 --- a/internal/cli/initialize/init_test.go +++ b/internal/cli/initialize/init_test.go @@ -14,7 +14,7 @@ import ( "testing" "github.com/ActiveMemory/ctx/internal/claude" - claude2 "github.com/ActiveMemory/ctx/internal/config/claude" + cfgClaude "github.com/ActiveMemory/ctx/internal/config/claude" "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/env" ) @@ -417,7 +417,7 @@ func TestRunInit_Merge(t *testing.T) { t.Setenv("HOME", tmpDir) t.Setenv(env.SkipPathCheck, env.True) - if err = os.WriteFile(claude2.Md, []byte("# My Project\n\nExisting.\n"), 0600); err != nil { + if err = os.WriteFile(cfgClaude.Md, []byte("# My Project\n\nExisting.\n"), 0600); err != nil { t.Fatal(err) } @@ -427,7 +427,7 @@ func TestRunInit_Merge(t *testing.T) { t.Fatalf("init --merge failed: %v", err) } - content, _ := os.ReadFile(claude2.Md) + content, _ := os.ReadFile(cfgClaude.Md) if !strings.Contains(string(content), "My Project") { t.Error("original content lost with --merge") } diff --git a/internal/cli/journal/cmd/site/doc.go b/internal/cli/journal/cmd/site/doc.go new file mode 100644 index 000000000..8dfc31aa1 --- /dev/null +++ b/internal/cli/journal/cmd/site/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package site provides the journal site subcommand for generating a static site from journal entries. +package site diff --git a/internal/cli/journal/core/collapse/doc.go b/internal/cli/journal/core/collapse/doc.go new file mode 100644 index 000000000..97dedf946 --- /dev/null +++ b/internal/cli/journal/core/collapse/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package collapse wraps long tool output turns in collapsible HTML details blocks. +package collapse diff --git a/internal/cli/journal/core/consolidate/doc.go b/internal/cli/journal/core/consolidate/doc.go new file mode 100644 index 000000000..16501781a --- /dev/null +++ b/internal/cli/journal/core/consolidate/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package consolidate collapses consecutive identical tool turns into a single turn with a count. +package consolidate diff --git a/internal/cli/journal/core/format/doc.go b/internal/cli/journal/core/format/doc.go new file mode 100644 index 000000000..5409c6e20 --- /dev/null +++ b/internal/cli/journal/core/format/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package format renders file sizes and other journal metadata in human-readable form. +package format diff --git a/internal/cli/journal/core/frontmatter/doc.go b/internal/cli/journal/core/frontmatter/doc.go new file mode 100644 index 000000000..58106e3dd --- /dev/null +++ b/internal/cli/journal/core/frontmatter/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package frontmatter transforms journal YAML frontmatter into Obsidian-compatible format. +package frontmatter diff --git a/internal/cli/journal/core/generate/doc.go b/internal/cli/journal/core/generate/doc.go new file mode 100644 index 000000000..229306a14 --- /dev/null +++ b/internal/cli/journal/core/generate/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package generate builds site index pages, READMEs, and navigation for journal static sites. +package generate diff --git a/internal/cli/journal/core/group/doc.go b/internal/cli/journal/core/group/doc.go new file mode 100644 index 000000000..2cb6b9618 --- /dev/null +++ b/internal/cli/journal/core/group/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package group organizes journal entries by month for chronological display. +package group diff --git a/internal/cli/journal/core/moc/doc.go b/internal/cli/journal/core/moc/doc.go new file mode 100644 index 000000000..5bfc4898b --- /dev/null +++ b/internal/cli/journal/core/moc/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package moc generates Obsidian Maps of Content for journal vault navigation. +package moc diff --git a/internal/cli/journal/core/normalize/doc.go b/internal/cli/journal/core/normalize/doc.go new file mode 100644 index 000000000..226f807cf --- /dev/null +++ b/internal/cli/journal/core/normalize/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package normalize sanitizes journal Markdown for static site rendering by fixing fences, headings, and list formatting. +package normalize diff --git a/internal/cli/journal/core/parse/doc.go b/internal/cli/journal/core/parse/doc.go new file mode 100644 index 000000000..f5470bec1 --- /dev/null +++ b/internal/cli/journal/core/parse/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package parse scans journal directories and extracts entry metadata from Markdown files. +package parse diff --git a/internal/cli/journal/core/reduce/doc.go b/internal/cli/journal/core/reduce/doc.go new file mode 100644 index 000000000..ecbe1cb35 --- /dev/null +++ b/internal/cli/journal/core/reduce/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package reduce strips code fence markers from journal content to eliminate nesting conflicts. +package reduce diff --git a/internal/cli/journal/core/section/doc.go b/internal/cli/journal/core/section/doc.go new file mode 100644 index 000000000..6874c4ed7 --- /dev/null +++ b/internal/cli/journal/core/section/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package section builds topic indexes and section pages for journal site navigation. +package section diff --git a/internal/cli/journal/core/section/section.go b/internal/cli/journal/core/section/section.go index 1b360f342..72ba972d5 100644 --- a/internal/cli/journal/core/section/section.go +++ b/internal/cli/journal/core/section/section.go @@ -22,7 +22,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/regex" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" ) // WriteSection creates a subdirectory, writes its index page, and calls @@ -43,14 +43,14 @@ func WriteSection( ) error { dir := filepath.Join(docsDir, subdir) if mkErr := os.MkdirAll(dir, fs.PermExec); mkErr != nil { - return ctxErr.Mkdir(dir, mkErr) + return errFs.Mkdir(dir, mkErr) } indexPath := filepath.Join(dir, file.Index) if writeErr := os.WriteFile( indexPath, []byte(indexContent), fs.PermFile, ); writeErr != nil { - return ctxErr.FileWrite(indexPath, writeErr) + return errFs.FileWrite(indexPath, writeErr) } writePages(dir) diff --git a/internal/cli/journal/core/session/doc.go b/internal/cli/journal/core/session/doc.go new file mode 100644 index 000000000..df428b764 --- /dev/null +++ b/internal/cli/journal/core/session/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package session counts unique sessions across topic data. +package session diff --git a/internal/cli/journal/core/turn/doc.go b/internal/cli/journal/core/turn/doc.go new file mode 100644 index 000000000..a5f9366c7 --- /dev/null +++ b/internal/cli/journal/core/turn/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package turn extracts and merges conversation turn bodies from journal entries. +package turn diff --git a/internal/cli/journal/core/wikilink/doc.go b/internal/cli/journal/core/wikilink/doc.go new file mode 100644 index 000000000..2182cdb51 --- /dev/null +++ b/internal/cli/journal/core/wikilink/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package wikilink converts standard Markdown links to Obsidian wikilink format. +package wikilink diff --git a/internal/cli/journal/core/wrap/doc.go b/internal/cli/journal/core/wrap/doc.go new file mode 100644 index 000000000..b1b1c3b6b --- /dev/null +++ b/internal/cli/journal/core/wrap/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package wrap soft-wraps long lines in journal files to approximately 80 characters. +package wrap diff --git a/internal/cli/load/cmd/root/run.go b/internal/cli/load/cmd/root/run.go index 06a5f6274..927172adf 100644 --- a/internal/cli/load/cmd/root/run.go +++ b/internal/cli/load/cmd/root/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/load/core" "github.com/ActiveMemory/ctx/internal/context/load" errCtx "github.com/ActiveMemory/ctx/internal/err/context" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" writeLoad "github.com/ActiveMemory/ctx/internal/write/load" ) @@ -35,7 +35,7 @@ func Run(cmd *cobra.Command, budget int, raw bool) error { if err != nil { var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return ctxErr.NotInitialized() + return errInit.NotInitialized() } return err } diff --git a/internal/cli/loop/cmd/root/run.go b/internal/cli/loop/cmd/root/run.go index 82af6928e..c92a6f68b 100644 --- a/internal/cli/loop/cmd/root/run.go +++ b/internal/cli/loop/cmd/root/run.go @@ -14,9 +14,9 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/fs" - loopCfg "github.com/ActiveMemory/ctx/internal/config/loop" + cfgLoop "github.com/ActiveMemory/ctx/internal/config/loop" "github.com/ActiveMemory/ctx/internal/err/config" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/write/loop" ) @@ -41,7 +41,7 @@ func Run( maxIterations int, completionMsg, outputFile string, ) error { - if !loopCfg.ValidTools[tool] { + if !cfgLoop.ValidTools[tool] { return config.InvalidTool(tool) } @@ -50,7 +50,7 @@ func Run( if writeErr := os.WriteFile( outputFile, []byte(script), fs.PermExec, ); writeErr != nil { - return ctxErr.FileWrite(outputFile, writeErr) + return errFs.FileWrite(outputFile, writeErr) } loop.InfoGenerated( diff --git a/internal/cli/loop/cmd/root/script.go b/internal/cli/loop/cmd/root/script.go index a5c87d67c..1a13eab93 100644 --- a/internal/cli/loop/cmd/root/script.go +++ b/internal/cli/loop/cmd/root/script.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/assets/tpl" "github.com/ActiveMemory/ctx/internal/config/embed/text" - loopCfg "github.com/ActiveMemory/ctx/internal/config/loop" + cfgLoop "github.com/ActiveMemory/ctx/internal/config/loop" ) // GenerateLoopScript creates a bash script for running a Ralph loop. @@ -37,11 +37,11 @@ func GenerateLoopScript( var aiCommand string switch tool { - case loopCfg.DefaultTool: + case cfgLoop.DefaultTool: aiCommand = fmt.Sprintf(`claude --print "$(cat %s)"`, absPrompt) - case loopCfg.ToolAider: + case cfgLoop.ToolAider: aiCommand = fmt.Sprintf(`aider --message-file %s`, absPrompt) - case loopCfg.ToolGeneric: + case cfgLoop.ToolGeneric: aiCommand = fmt.Sprintf(`# Replace with your AI CLI command cat %s | your-ai-cli`, absPrompt) } diff --git a/internal/cli/loop/loop.go b/internal/cli/loop/loop.go index eb5669f5d..70ec6ed5c 100644 --- a/internal/cli/loop/loop.go +++ b/internal/cli/loop/loop.go @@ -9,10 +9,10 @@ package loop import ( "github.com/spf13/cobra" - looproot "github.com/ActiveMemory/ctx/internal/cli/loop/cmd/root" + loopRoot "github.com/ActiveMemory/ctx/internal/cli/loop/cmd/root" ) // Cmd returns the "ctx loop" command for generating Ralph loop scripts. func Cmd() *cobra.Command { - return looproot.Cmd() + return loopRoot.Cmd() } diff --git a/internal/cli/mcp/cmd/root/doc.go b/internal/cli/mcp/cmd/root/doc.go new file mode 100644 index 000000000..ff8028673 --- /dev/null +++ b/internal/cli/mcp/cmd/root/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package root provides the run function for the ctx MCP server command. +package root diff --git a/internal/cli/memory/cmd/diff/run.go b/internal/cli/memory/cmd/diff/run.go index fda14375d..46a6586ce 100644 --- a/internal/cli/memory/cmd/diff/run.go +++ b/internal/cli/memory/cmd/diff/run.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" - ctxErr "github.com/ActiveMemory/ctx/internal/err/memory" + errMemory "github.com/ActiveMemory/ctx/internal/err/memory" mem "github.com/ActiveMemory/ctx/internal/memory" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/memory" @@ -31,12 +31,12 @@ func Run(cmd *cobra.Command) error { sourcePath, discoverErr := mem.DiscoverMemoryPath(projectRoot) if discoverErr != nil { - return ctxErr.DiscoverFailed(discoverErr) + return errMemory.DiscoverFailed(discoverErr) } diff, diffErr := mem.Diff(contextDir, sourcePath) if diffErr != nil { - return ctxErr.DiffFailed(diffErr) + return errMemory.DiffFailed(diffErr) } if diff == "" { diff --git a/internal/cli/memory/cmd/publish/run.go b/internal/cli/memory/cmd/publish/run.go index 5ee5ed48c..cb2a0a508 100644 --- a/internal/cli/memory/cmd/publish/run.go +++ b/internal/cli/memory/cmd/publish/run.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" - ctxErr "github.com/ActiveMemory/ctx/internal/err/memory" + errMemory "github.com/ActiveMemory/ctx/internal/err/memory" mem "github.com/ActiveMemory/ctx/internal/memory" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/publish" @@ -35,12 +35,12 @@ func Run(cmd *cobra.Command, budget int, dryRun bool) error { memoryPath, discoverErr := mem.DiscoverMemoryPath(projectRoot) if discoverErr != nil { sync.ErrAutoMemoryNotActive(cmd, discoverErr) - return ctxErr.NotFound() + return errMemory.NotFound() } result, selectErr := mem.SelectContent(contextDir, budget) if selectErr != nil { - return ctxErr.SelectContentFailed(selectErr) + return errMemory.SelectContentFailed(selectErr) } publish.PublishPlan(cmd, budget, @@ -57,7 +57,7 @@ func Run(cmd *cobra.Command, budget int, dryRun bool) error { if _, publishErr := mem.Publish( contextDir, memoryPath, budget, ); publishErr != nil { - return ctxErr.PublishFailed(publishErr) + return errMemory.PublishFailed(publishErr) } publish.PublishDone(cmd) diff --git a/internal/cli/memory/cmd/sync/run.go b/internal/cli/memory/cmd/sync/run.go index 276c45fd5..3bf133dea 100644 --- a/internal/cli/memory/cmd/sync/run.go +++ b/internal/cli/memory/cmd/sync/run.go @@ -13,7 +13,7 @@ import ( cfgMem "github.com/ActiveMemory/ctx/internal/config/memory" errMem "github.com/ActiveMemory/ctx/internal/err/memory" - ctxErr "github.com/ActiveMemory/ctx/internal/err/state" + errState "github.com/ActiveMemory/ctx/internal/err/state" "github.com/ActiveMemory/ctx/internal/memory" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/sync" @@ -59,11 +59,11 @@ func Run(cmd *cobra.Command, dryRun bool) error { // Update sync state state, loadErr := memory.LoadState(contextDir) if loadErr != nil { - return ctxErr.Load(loadErr) + return errState.Load(loadErr) } state.MarkSynced() if saveErr := memory.SaveState(contextDir, state); saveErr != nil { - return ctxErr.Save(saveErr) + return errState.Save(saveErr) } return nil diff --git a/internal/cli/notify/cmd/setup/run.go b/internal/cli/notify/cmd/setup/run.go index ca7c60497..55a035ad5 100644 --- a/internal/cli/notify/cmd/setup/run.go +++ b/internal/cli/notify/cmd/setup/run.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/crypto" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/notify" + errNotify "github.com/ActiveMemory/ctx/internal/err/notify" iNotify "github.com/ActiveMemory/ctx/internal/notify" "github.com/ActiveMemory/ctx/internal/write/notify" ) @@ -39,11 +39,11 @@ func Run(cmd *cobra.Command, stdin *os.File) error { } url := strings.TrimSpace(scanner.Text()) if url == "" { - return ctxErr.WebhookEmpty() + return errNotify.WebhookEmpty() } if saveErr := iNotify.SaveWebhook(url); saveErr != nil { - return ctxErr.SaveWebhook(saveErr) + return errNotify.SaveWebhook(saveErr) } notify.SetupDone(cmd, iNotify.MaskURL(url), crypto.NotifyEnc) diff --git a/internal/cli/notify/cmd/test/run.go b/internal/cli/notify/cmd/test/run.go index ed414fb40..27b94f3e8 100644 --- a/internal/cli/notify/cmd/test/run.go +++ b/internal/cli/notify/cmd/test/run.go @@ -15,10 +15,10 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config/crypto" - ctxErr "github.com/ActiveMemory/ctx/internal/err/notify" + errNotify "github.com/ActiveMemory/ctx/internal/err/notify" "github.com/ActiveMemory/ctx/internal/notify" "github.com/ActiveMemory/ctx/internal/rc" - iNotify "github.com/ActiveMemory/ctx/internal/write/notify" + writeNotify "github.com/ActiveMemory/ctx/internal/write/notify" ) // runTest sends a test notification to the configured webhook. @@ -31,10 +31,10 @@ import ( func runTest(cmd *cobra.Command) error { url, loadErr := notify.LoadWebhook() if loadErr != nil { - return ctxErr.LoadWebhook(loadErr) + return errNotify.LoadWebhook(loadErr) } if url == "" { - iNotify.TestNoWebhook(cmd) + writeNotify.TestNoWebhook(cmd) return nil } @@ -52,20 +52,20 @@ func runTest(cmd *cobra.Command) error { body, marshalErr := json.Marshal(payload) if marshalErr != nil { - return ctxErr.MarshalPayload(marshalErr) + return errNotify.MarshalPayload(marshalErr) } if !notify.EventAllowed("test", rc.NotifyEvents()) { - iNotify.TestFiltered(cmd) + writeNotify.TestFiltered(cmd) } resp, postErr := notify.PostJSON(url, body) if postErr != nil { - return ctxErr.SendNotification(postErr) + return errNotify.SendNotification(postErr) } defer func() { _ = resp.Body.Close() }() - iNotify.TestResult(cmd, resp.StatusCode, crypto.NotifyEnc) + writeNotify.TestResult(cmd, resp.StatusCode, crypto.NotifyEnc) return nil } diff --git a/internal/cli/notify/notify_test.go b/internal/cli/notify/notify_test.go index 92ef8058e..90a39c95e 100644 --- a/internal/cli/notify/notify_test.go +++ b/internal/cli/notify/notify_test.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/notify/cmd/setup" "github.com/ActiveMemory/ctx/internal/config/ctx" - notifylib "github.com/ActiveMemory/ctx/internal/notify" + libNotify "github.com/ActiveMemory/ctx/internal/notify" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -139,9 +139,9 @@ func TestMaskURL(t *testing.T) { } for _, tc := range tests { - got := notifylib.MaskURL(tc.input) + got := libNotify.MaskURL(tc.input) if got != tc.want { - t.Errorf("notifylib.MaskURL(%q) = %q, want %q", tc.input, got, tc.want) + t.Errorf("libNotify.MaskURL(%q) = %q, want %q", tc.input, got, tc.want) } } } @@ -206,7 +206,7 @@ func TestTest_WebhookSuccess(t *testing.T) { defer server.Close() // Save the test server URL as the webhook. - if saveErr := notifylib.SaveWebhook(server.URL); saveErr != nil { + if saveErr := libNotify.SaveWebhook(server.URL); saveErr != nil { t.Fatalf("SaveWebhook() error = %v", saveErr) } @@ -240,7 +240,7 @@ func TestTest_WebhookServerError(t *testing.T) { })) defer server.Close() - if saveErr := notifylib.SaveWebhook(server.URL); saveErr != nil { + if saveErr := libNotify.SaveWebhook(server.URL); saveErr != nil { t.Fatalf("SaveWebhook() error = %v", saveErr) } diff --git a/internal/cli/pad/cmd/add/run.go b/internal/cli/pad/cmd/add/run.go index d6a24d95b..367c23d35 100644 --- a/internal/cli/pad/cmd/add/run.go +++ b/internal/cli/pad/cmd/add/run.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/pad" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" "github.com/ActiveMemory/ctx/internal/io" writePad "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -58,7 +58,7 @@ func runAddBlob(cmd *cobra.Command, label, filePath string) error { } if len(data) > pad.MaxBlobSize { - return ctxErr.FileTooLarge(len(data), pad.MaxBlobSize) + return errPad.FileTooLarge(len(data), pad.MaxBlobSize) } entries, readErr := store.ReadEntries() diff --git a/internal/cli/pad/cmd/edit/cmd.go b/internal/cli/pad/cmd/edit/cmd.go index eabf01247..986aaf45f 100644 --- a/internal/cli/pad/cmd/edit/cmd.go +++ b/internal/cli/pad/cmd/edit/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - ctxErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" ) // Cmd returns the pad edit subcommand. @@ -48,7 +48,7 @@ func Cmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { n, err := strconv.Atoi(args[0]) if err != nil { - return ctxErr.InvalidIndex(args[0]) + return errPad.InvalidIndex(args[0]) } hasPositional := len(args) == 2 @@ -59,7 +59,7 @@ func Cmd() *cobra.Command { // --file/--label conflict with positional/--append/--prepend. if (hasFile || hasLabel) && (hasPositional || hasAppend || hasPrepend) { - return ctxErr.EditBlobTextConflict() + return errPad.EditBlobTextConflict() } // Blob edit mode. @@ -80,10 +80,10 @@ func Cmd() *cobra.Command { } if flagCount == 0 { - return ctxErr.EditNoMode() + return errPad.EditNoMode() } if flagCount > 1 { - return ctxErr.EditTextConflict() + return errPad.EditTextConflict() } switch { diff --git a/internal/cli/pad/cmd/edit/run.go b/internal/cli/pad/cmd/edit/run.go index 957814ea2..370c938b0 100644 --- a/internal/cli/pad/cmd/edit/run.go +++ b/internal/cli/pad/cmd/edit/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/pad" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" "github.com/ActiveMemory/ctx/internal/io" writePad "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -68,7 +68,7 @@ func runEditAppend(cmd *cobra.Command, n int, text string) error { } if blob.ContainsBlob(entries[n-1]) { - return ctxErr.BlobAppendNotAllowed() + return errPad.BlobAppendNotAllowed() } entries[n-1] = entries[n-1] + " " + text @@ -101,7 +101,7 @@ func runEditPrepend(cmd *cobra.Command, n int, text string) error { } if blob.ContainsBlob(entries[n-1]) { - return ctxErr.BlobPrependNotAllowed() + return errPad.BlobPrependNotAllowed() } entries[n-1] = text + " " + entries[n-1] @@ -136,7 +136,7 @@ func runEditBlob(cmd *cobra.Command, n int, filePath, labelText string) error { oldLabel, oldData, ok := blob.SplitBlob(entries[n-1]) if !ok { - return ctxErr.NotBlobEntry(n) + return errPad.NotBlobEntry(n) } newLabel := oldLabel @@ -152,7 +152,7 @@ func runEditBlob(cmd *cobra.Command, n int, filePath, labelText string) error { return fs.ReadFile(readErr) } if len(data) > pad.MaxBlobSize { - return ctxErr.FileTooLarge(len(data), pad.MaxBlobSize) + return errPad.FileTooLarge(len(data), pad.MaxBlobSize) } newData = data } diff --git a/internal/cli/pad/cmd/export/run.go b/internal/cli/pad/cmd/export/run.go index d56aa3045..b07113e30 100644 --- a/internal/cli/pad/cmd/export/run.go +++ b/internal/cli/pad/cmd/export/run.go @@ -18,7 +18,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/token" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/write/export" "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -53,7 +53,7 @@ func runExport(cmd *cobra.Command, dir string, force, dryRun bool) error { if !dryRun { if mkErr := os.MkdirAll(dir, fs.PermExec); mkErr != nil { - return ctxErr.Mkdir(dir, mkErr) + return errFs.Mkdir(dir, mkErr) } } diff --git a/internal/cli/pad/cmd/imp/run.go b/internal/cli/pad/cmd/imp/run.go index 0ac62861b..9bf391e99 100644 --- a/internal/cli/pad/cmd/imp/run.go +++ b/internal/cli/pad/cmd/imp/run.go @@ -18,7 +18,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/pad" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" internalIo "github.com/ActiveMemory/ctx/internal/io" writePad "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -38,7 +38,7 @@ func runImport(cmd *cobra.Command, file string) error { } else { f, err := internalIo.SafeOpenUserFile(file) if err != nil { - return ctxErr.OpenFile(file, err) + return errFs.OpenFile(file, err) } defer func() { if cErr := f.Close(); cErr != nil { @@ -64,7 +64,7 @@ func runImport(cmd *cobra.Command, file string) error { count++ } if scanErr := scanner.Err(); scanErr != nil { - return ctxErr.ReadInput(scanErr) + return errFs.ReadInput(scanErr) } if count == 0 { @@ -92,15 +92,15 @@ func runImport(cmd *cobra.Command, file string) error { func runImportBlobs(cmd *cobra.Command, path string) error { info, statErr := os.Stat(path) if statErr != nil { - return ctxErr.StatPath(path, statErr) + return errFs.StatPath(path, statErr) } if !info.IsDir() { - return ctxErr.NotDirectory(path) + return errFs.NotDirectory(path) } dirEntries, readErr := os.ReadDir(path) if readErr != nil { - return ctxErr.ReadDirectory(path, readErr) + return errFs.ReadDirectory(path, readErr) } entries, loadErr := store.ReadEntries() diff --git a/internal/cli/pad/cmd/merge/run.go b/internal/cli/pad/cmd/merge/run.go index 2e2127637..6d756e5ba 100644 --- a/internal/cli/pad/cmd/merge/run.go +++ b/internal/cli/pad/cmd/merge/run.go @@ -12,7 +12,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/pad/core/store" "github.com/spf13/cobra" - ctEerr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -53,7 +53,7 @@ func Run( for _, file := range files { entries, fileErr := merge.ReadFileEntries(file, key) if fileErr != nil { - return ctEerr.OpenFile(file, fileErr) + return errFs.OpenFile(file, fileErr) } if merge.HasBinaryEntries(entries) { diff --git a/internal/cli/pad/cmd/resolve/run.go b/internal/cli/pad/cmd/resolve/run.go index 6d9a572a2..a5b9a81ad 100644 --- a/internal/cli/pad/cmd/resolve/run.go +++ b/internal/cli/pad/cmd/resolve/run.go @@ -7,14 +7,14 @@ package resolve import ( - crypto2 "github.com/ActiveMemory/ctx/internal/cli/pad/core/crypto" + padCrypto "github.com/ActiveMemory/ctx/internal/cli/pad/core/crypto" "github.com/ActiveMemory/ctx/internal/cli/pad/core/store" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config/pad" "github.com/ActiveMemory/ctx/internal/crypto" errCrypto "github.com/ActiveMemory/ctx/internal/err/crypto" - ctxErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" "github.com/ActiveMemory/ctx/internal/rc" writePad "github.com/ActiveMemory/ctx/internal/write/pad" ) @@ -28,7 +28,7 @@ import ( // - error: Non-nil if no conflict files found or decryption fails func Run(cmd *cobra.Command) error { if !rc.ScratchpadEncrypt() { - return ctxErr.ResolveNotEncrypted() + return errPad.ResolveNotEncrypted() } kp := store.KeyPath() @@ -39,11 +39,11 @@ func Run(cmd *cobra.Command) error { dir := rc.ContextDir() - ours, errOurs := crypto2.DecryptFile(key, dir, pad.EncOurs) - theirs, errTheirs := crypto2.DecryptFile(key, dir, pad.EncTheirs) + ours, errOurs := padCrypto.DecryptFile(key, dir, pad.EncOurs) + theirs, errTheirs := padCrypto.DecryptFile(key, dir, pad.EncTheirs) if errOurs != nil && errTheirs != nil { - return ctxErr.NoConflictFiles(pad.Enc) + return errPad.NoConflictFiles(pad.Enc) } if errOurs == nil { diff --git a/internal/cli/pad/cmd/show/cmd.go b/internal/cli/pad/cmd/show/cmd.go index f34db02b9..589af9b57 100644 --- a/internal/cli/pad/cmd/show/cmd.go +++ b/internal/cli/pad/cmd/show/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - ctxErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" ) // Cmd returns the pad show subcommand. @@ -39,7 +39,7 @@ func Cmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { n, err := strconv.Atoi(args[0]) if err != nil { - return ctxErr.InvalidIndex(args[0]) + return errPad.InvalidIndex(args[0]) } return Run(cmd, n, outPath) }, diff --git a/internal/cli/pad/core/blob/doc.go b/internal/cli/pad/core/blob/doc.go new file mode 100644 index 000000000..6d0d979fb --- /dev/null +++ b/internal/cli/pad/core/blob/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package blob handles binary blob encoding and decoding within scratchpad entries. +package blob diff --git a/internal/cli/pad/core/crypto/crypto.go b/internal/cli/pad/core/crypto/crypto.go index 00c086e53..d58df0515 100644 --- a/internal/cli/pad/core/crypto/crypto.go +++ b/internal/cli/pad/core/crypto/crypto.go @@ -9,7 +9,7 @@ package crypto import ( "github.com/ActiveMemory/ctx/internal/cli/pad/core/parse" "github.com/ActiveMemory/ctx/internal/crypto" - ctxErr "github.com/ActiveMemory/ctx/internal/err/crypto" + errCrypto "github.com/ActiveMemory/ctx/internal/err/crypto" "github.com/ActiveMemory/ctx/internal/io" ) @@ -31,7 +31,7 @@ func DecryptFile(key []byte, baseDir, filename string) ([]string, error) { plaintext, decErr := crypto.Decrypt(key, data) if decErr != nil { - return nil, ctxErr.DecryptFailed() + return nil, errCrypto.DecryptFailed() } return parse.ParseEntries(plaintext), nil diff --git a/internal/cli/pad/core/crypto/doc.go b/internal/cli/pad/core/crypto/doc.go new file mode 100644 index 000000000..82960565a --- /dev/null +++ b/internal/cli/pad/core/crypto/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package crypto reads and decrypts encrypted scratchpad files. +package crypto diff --git a/internal/cli/pad/core/merge/doc.go b/internal/cli/pad/core/merge/doc.go new file mode 100644 index 000000000..99ec238fb --- /dev/null +++ b/internal/cli/pad/core/merge/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package merge reads scratchpad files with automatic decryption fallback and merges entries. +package merge diff --git a/internal/cli/pad/core/parse/doc.go b/internal/cli/pad/core/parse/doc.go new file mode 100644 index 000000000..05e46615b --- /dev/null +++ b/internal/cli/pad/core/parse/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package parse splits raw scratchpad content into individual entry lines. +package parse diff --git a/internal/cli/pad/core/store/doc.go b/internal/cli/pad/core/store/doc.go new file mode 100644 index 000000000..db368d1fa --- /dev/null +++ b/internal/cli/pad/core/store/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package store manages scratchpad file persistence including encryption and path resolution. +package store diff --git a/internal/cli/pad/core/validate/doc.go b/internal/cli/pad/core/validate/doc.go new file mode 100644 index 000000000..292fbfe66 --- /dev/null +++ b/internal/cli/pad/core/validate/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package validate checks that scratchpad entry indexes are within valid bounds. +package validate diff --git a/internal/cli/pad/core/validate/validate.go b/internal/cli/pad/core/validate/validate.go index 55ef7f5c9..eafec942f 100644 --- a/internal/cli/pad/core/validate/validate.go +++ b/internal/cli/pad/core/validate/validate.go @@ -7,7 +7,7 @@ package validate import ( - padErr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" ) // ValidateIndex checks that n is a valid 1-based index into entries. @@ -20,7 +20,7 @@ import ( // - error: Non-nil if n is out of range func ValidateIndex(n int, entries []string) error { if n < 1 || n > len(entries) { - return padErr.EntryRange(n, len(entries)) + return errPad.EntryRange(n, len(entries)) } return nil } diff --git a/internal/cli/pad/pad_test.go b/internal/cli/pad/pad_test.go index 31e41c7ca..76fca73d9 100644 --- a/internal/cli/pad/pad_test.go +++ b/internal/cli/pad/pad_test.go @@ -16,13 +16,13 @@ import ( "testing" "github.com/ActiveMemory/ctx/internal/cli/pad/core/blob" - crypto2 "github.com/ActiveMemory/ctx/internal/cli/pad/core/crypto" + padCrypto "github.com/ActiveMemory/ctx/internal/cli/pad/core/crypto" "github.com/ActiveMemory/ctx/internal/cli/pad/core/parse" "github.com/ActiveMemory/ctx/internal/cli/pad/core/store" "github.com/ActiveMemory/ctx/internal/cli/pad/core/validate" "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/pad" - ctxerr "github.com/ActiveMemory/ctx/internal/err/pad" + errPad "github.com/ActiveMemory/ctx/internal/err/pad" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/config/fs" @@ -632,7 +632,7 @@ func TestValidateIndex_EmptySlice(t *testing.T) { } func TestErrEntryRange(t *testing.T) { - err := ctxerr.EntryRange(5, 3) + err := errPad.EntryRange(5, 3) msg := err.Error() if !strings.Contains(msg, "5") || !strings.Contains(msg, "3") { t.Errorf("EntryRange = %q, want indices 5 and 3 mentioned", msg) @@ -1123,7 +1123,7 @@ func TestDecryptFile_BadData(t *testing.T) { t.Fatal(writeErr) } - _, err := crypto2.DecryptFile(key, tmpDir, "bad.enc") + _, err := padCrypto.DecryptFile(key, tmpDir, "bad.enc") if err == nil { t.Fatal("expected decryption error for bad data") } @@ -1136,7 +1136,7 @@ func TestDecryptFile_MissingFile(t *testing.T) { key, _ := crypto.GenerateKey() tmpDir := t.TempDir() - _, err := crypto2.DecryptFile(key, tmpDir, "nonexistent.enc") + _, err := padCrypto.DecryptFile(key, tmpDir, "nonexistent.enc") if err == nil { t.Fatal("expected error for missing file") } @@ -1155,7 +1155,7 @@ func TestDecryptFile_ValidData(t *testing.T) { t.Fatal(writeErr) } - entries, err := crypto2.DecryptFile(key, tmpDir, "good.enc") + entries, err := padCrypto.DecryptFile(key, tmpDir, "good.enc") if err != nil { t.Fatalf("decryptFile error: %v", err) } diff --git a/internal/cli/pause/cmd/root/run.go b/internal/cli/pause/cmd/root/run.go index 58ba077b0..4f327b31e 100644 --- a/internal/cli/pause/cmd/root/run.go +++ b/internal/cli/pause/cmd/root/run.go @@ -10,7 +10,7 @@ import ( "os" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" - session2 "github.com/ActiveMemory/ctx/internal/cli/system/core/session" + coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/write/session" @@ -26,7 +26,7 @@ import ( // - error: Always nil func Run(cmd *cobra.Command, sessionID string) error { if sessionID == "" { - sessionID = session2.ReadSessionID(os.Stdin) + sessionID = coreSession.ReadSessionID(os.Stdin) } nudge.Pause(sessionID) session.SessionPaused(cmd, sessionID) diff --git a/internal/cli/permission/cmd/restore/run.go b/internal/cli/permission/cmd/restore/run.go index af50d3562..075aa38de 100644 --- a/internal/cli/permission/cmd/restore/run.go +++ b/internal/cli/permission/cmd/restore/run.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/claude" "github.com/ActiveMemory/ctx/internal/cli/permission/core" - configClaude "github.com/ActiveMemory/ctx/internal/config/claude" + cfgClaude "github.com/ActiveMemory/ctx/internal/config/claude" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/err/config" errFs "github.com/ActiveMemory/ctx/internal/err/fs" @@ -31,26 +31,26 @@ import ( // Returns: // - error: Non-nil on read/write/parse failure or missing golden file func Run(cmd *cobra.Command) error { - goldenBytes, goldenReadErr := os.ReadFile(configClaude.SettingsGolden) + goldenBytes, goldenReadErr := os.ReadFile(cfgClaude.SettingsGolden) if goldenReadErr != nil { if os.IsNotExist(goldenReadErr) { return config.GoldenNotFound() } - return errFs.FileRead(configClaude.SettingsGolden, goldenReadErr) + return errFs.FileRead(cfgClaude.SettingsGolden, goldenReadErr) } - localBytes, localReadErr := os.ReadFile(configClaude.Settings) + localBytes, localReadErr := os.ReadFile(cfgClaude.Settings) if localReadErr != nil { if os.IsNotExist(localReadErr) { if writeErr := os.WriteFile( - configClaude.Settings, goldenBytes, fs.PermFile, + cfgClaude.Settings, goldenBytes, fs.PermFile, ); writeErr != nil { - return errFs.FileWrite(configClaude.Settings, writeErr) + return errFs.FileWrite(cfgClaude.Settings, writeErr) } restore.RestoreNoLocal(cmd) return nil } - return errFs.FileRead(configClaude.Settings, localReadErr) + return errFs.FileRead(cfgClaude.Settings, localReadErr) } if bytes.Equal(goldenBytes, localBytes) { @@ -60,10 +60,10 @@ func Run(cmd *cobra.Command) error { var golden, local claude.Settings if goldenParseErr := json.Unmarshal(goldenBytes, &golden); goldenParseErr != nil { - return errParser.ParseFile(configClaude.SettingsGolden, goldenParseErr) + return errParser.ParseFile(cfgClaude.SettingsGolden, goldenParseErr) } if localParseErr := json.Unmarshal(localBytes, &local); localParseErr != nil { - return errParser.ParseFile(configClaude.Settings, localParseErr) + return errParser.ParseFile(cfgClaude.Settings, localParseErr) } restored, dropped := core.DiffStringSlices( @@ -76,9 +76,9 @@ func Run(cmd *cobra.Command) error { restore.RestoreDiff(cmd, dropped, restored, denyDropped, denyRestored) if writeErr := os.WriteFile( - configClaude.Settings, goldenBytes, fs.PermFile, + cfgClaude.Settings, goldenBytes, fs.PermFile, ); writeErr != nil { - return errFs.FileWrite(configClaude.Settings, writeErr) + return errFs.FileWrite(cfgClaude.Settings, writeErr) } restore.RestoreDone(cmd) diff --git a/internal/cli/permission/cmd/snapshot/run.go b/internal/cli/permission/cmd/snapshot/run.go index d338a44f5..5d221a0d7 100644 --- a/internal/cli/permission/cmd/snapshot/run.go +++ b/internal/cli/permission/cmd/snapshot/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/claude" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/err/config" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/write/restore" ) @@ -31,7 +31,7 @@ func Run(cmd *cobra.Command) error { if os.IsNotExist(readErr) { return config.SettingsNotFound() } - return ctxErr.FileRead(claude.Settings, readErr) + return errFs.FileRead(claude.Settings, readErr) } updated := false @@ -42,7 +42,7 @@ func Run(cmd *cobra.Command) error { if writeErr := os.WriteFile( claude.SettingsGolden, content, fs.PermFile, ); writeErr != nil { - return ctxErr.FileWrite(claude.SettingsGolden, writeErr) + return errFs.FileWrite(claude.SettingsGolden, writeErr) } restore.SnapshotDone(cmd, updated, claude.SettingsGolden) diff --git a/internal/cli/prompt/cmd/add/run.go b/internal/cli/prompt/cmd/add/run.go index 6a4860c51..4bd06d582 100644 --- a/internal/cli/prompt/cmd/add/run.go +++ b/internal/cli/prompt/cmd/add/run.go @@ -18,7 +18,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/config/fs" errFs "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/prompt" + errPrompt "github.com/ActiveMemory/ctx/internal/err/prompt" "github.com/ActiveMemory/ctx/internal/write/prompt" ) @@ -41,7 +41,7 @@ func runAdd(cmd *cobra.Command, name string, fromStdin bool) error { // Check if the file already exists. if _, statErr := os.Stat(path); statErr == nil { - return ctxErr.Exists(name) + return errPrompt.Exists(name) } var content []byte @@ -57,7 +57,7 @@ func runAdd(cmd *cobra.Command, name string, fromStdin bool) error { var templateErr error content, templateErr = readPrompt.Template(name + file.ExtMarkdown) if templateErr != nil { - return ctxErr.NoPromptTemplate(name) + return errPrompt.NoPromptTemplate(name) } } diff --git a/internal/cli/prompt/cmd/list/run.go b/internal/cli/prompt/cmd/list/run.go index c0fabfefb..221393b80 100644 --- a/internal/cli/prompt/cmd/list/run.go +++ b/internal/cli/prompt/cmd/list/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/prompt/core" "github.com/ActiveMemory/ctx/internal/config/file" - ctxErr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/write/prompt" ) @@ -34,7 +34,7 @@ func Run(cmd *cobra.Command) error { prompt.PromptNone(cmd) return nil } - return ctxErr.ReadDirectory(dir, readErr) + return errFs.ReadDirectory(dir, readErr) } var found bool diff --git a/internal/cli/prompt/cmd/rm/run.go b/internal/cli/prompt/cmd/rm/run.go index 25c05c55f..8448e7add 100644 --- a/internal/cli/prompt/cmd/rm/run.go +++ b/internal/cli/prompt/cmd/rm/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/prompt/core" "github.com/ActiveMemory/ctx/internal/config/file" - ctxErr "github.com/ActiveMemory/ctx/internal/err/prompt" + errPrompt "github.com/ActiveMemory/ctx/internal/err/prompt" "github.com/ActiveMemory/ctx/internal/write/prompt" ) @@ -30,11 +30,11 @@ func Run(cmd *cobra.Command, name string) error { path := filepath.Join(core.PromptsDir(), name+file.ExtMarkdown) if _, statErr := os.Stat(path); os.IsNotExist(statErr) { - return ctxErr.NotFound(name) + return errPrompt.NotFound(name) } if removeErr := os.Remove(path); removeErr != nil { - return ctxErr.Remove(removeErr) + return errPrompt.Remove(removeErr) } prompt.Removed(cmd, name) diff --git a/internal/cli/prompt/cmd/show/run.go b/internal/cli/prompt/cmd/show/run.go index 8c9bfa545..463667ca3 100644 --- a/internal/cli/prompt/cmd/show/run.go +++ b/internal/cli/prompt/cmd/show/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/prompt/core" "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/prompt" + errPrompt "github.com/ActiveMemory/ctx/internal/err/prompt" "github.com/ActiveMemory/ctx/internal/io" writePrompt "github.com/ActiveMemory/ctx/internal/write/prompt" ) @@ -33,7 +33,7 @@ func Run(cmd *cobra.Command, name string) error { ) if readErr != nil { if os.IsNotExist(readErr) { - return ctxErr.NotFound(name) + return errPrompt.NotFound(name) } return fs.ReadFile(readErr) } diff --git a/internal/cli/recall/cmd/export/run.go b/internal/cli/recall/cmd/export/run.go index 512dc562c..5a7b53289 100644 --- a/internal/cli/recall/cmd/export/run.go +++ b/internal/cli/recall/cmd/export/run.go @@ -27,7 +27,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/journal" errFs "github.com/ActiveMemory/ctx/internal/err/fs" errJournal "github.com/ActiveMemory/ctx/internal/err/journal" - ctxErr "github.com/ActiveMemory/ctx/internal/err/session" + errSession "github.com/ActiveMemory/ctx/internal/err/session" "github.com/ActiveMemory/ctx/internal/journal/state" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/err" @@ -64,7 +64,7 @@ func Run(cmd *cobra.Command, args []string, opts entity.ExportOpts) error { // 3. Resolve sessions. sessions, scanErr := query.FindSessions(opts.AllProjects) if scanErr != nil { - return ctxErr.Find(scanErr) + return errSession.Find(scanErr) } if len(sessions) == 0 { @@ -85,12 +85,12 @@ func Run(cmd *cobra.Command, args []string, opts entity.ExportOpts) error { } } if len(toExport) == 0 { - return ctxErr.NotFound(args[0]) + return errSession.NotFound(args[0]) } if len(toExport) > 1 { lines := format.SessionMatchLines(toExport) recall.AmbiguousSessionMatch(cmd, args[0], lines) - return ctxErr.AmbiguousQuery() + return errSession.AmbiguousQuery() } singleSession = true } diff --git a/internal/cli/recall/cmd/list/run.go b/internal/cli/recall/cmd/list/run.go index 9919a0d09..c439ce1db 100644 --- a/internal/cli/recall/cmd/list/run.go +++ b/internal/cli/recall/cmd/list/run.go @@ -22,7 +22,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/err/date" - ctxErr "github.com/ActiveMemory/ctx/internal/err/session" + errSession "github.com/ActiveMemory/ctx/internal/err/session" "github.com/ActiveMemory/ctx/internal/parse" "github.com/ActiveMemory/ctx/internal/write/recall" ) @@ -64,7 +64,7 @@ func Run( sessions, scanErr := query.FindSessions(allProjects) if scanErr != nil { - return ctxErr.Find(scanErr) + return errSession.Find(scanErr) } if len(sessions) == 0 { diff --git a/internal/cli/recall/cmd/show/run.go b/internal/cli/recall/cmd/show/run.go index 82b6a3176..247c18b05 100644 --- a/internal/cli/recall/cmd/show/run.go +++ b/internal/cli/recall/cmd/show/run.go @@ -19,7 +19,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" - ctxErr "github.com/ActiveMemory/ctx/internal/err/session" + errSession "github.com/ActiveMemory/ctx/internal/err/session" "github.com/ActiveMemory/ctx/internal/write/recall" ) @@ -42,14 +42,14 @@ func Run( ) error { sessions, scanErr := query.FindSessions(allProjects) if scanErr != nil { - return ctxErr.Find(scanErr) + return errSession.Find(scanErr) } if len(sessions) == 0 { if allProjects { - return ctxErr.NoneFound("") + return errSession.NoneFound("") } - return ctxErr.NoneFound(desc.Text(text.DescKeyLabelHintUseAllProjects)) + return errSession.NoneFound(desc.Text(text.DescKeyLabelHintUseAllProjects)) } var session *entity.Session @@ -58,7 +58,7 @@ func Run( case latest: session = sessions[0] case len(args) == 0: - return ctxErr.IDRequired() + return errSession.IDRequired() default: query := strings.ToLower(args[0]) var matches []*entity.Session @@ -69,14 +69,14 @@ func Run( } } if len(matches) == 0 { - return ctxErr.NotFound(args[0]) + return errSession.NotFound(args[0]) } if len(matches) > 1 { lines := format.SessionMatchLines(matches) recall.AmbiguousSessionMatchWithHint( cmd, args[0], lines, matches[0].ID[:journal.SessionIDHintLen], ) - return ctxErr.AmbiguousQuery() + return errSession.AmbiguousQuery() } session = matches[0] } diff --git a/internal/cli/recall/cmd/sync/run.go b/internal/cli/recall/cmd/sync/run.go index 805c22be1..00f0604f9 100644 --- a/internal/cli/recall/cmd/sync/run.go +++ b/internal/cli/recall/cmd/sync/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/journal" - ctxErr "github.com/ActiveMemory/ctx/internal/err/journal" + errJournal "github.com/ActiveMemory/ctx/internal/err/journal" "github.com/ActiveMemory/ctx/internal/journal/state" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/write/recall" @@ -33,7 +33,7 @@ func Run(cmd *cobra.Command) error { jstate, loadErr := state.Load(journalDir) if loadErr != nil { - return ctxErr.LoadState(loadErr) + return errJournal.LoadState(loadErr) } files, matchErr := lock.MatchJournalFiles(journalDir, nil, true) @@ -65,7 +65,7 @@ func Run(cmd *cobra.Command) error { } if saveErr := jstate.Save(journalDir); saveErr != nil { - return ctxErr.SaveState(saveErr) + return errJournal.SaveState(saveErr) } recall.JournalSyncSummary(cmd, locked, unlocked) diff --git a/internal/cli/recall/core/confirm/doc.go b/internal/cli/recall/core/confirm/doc.go new file mode 100644 index 000000000..608d83999 --- /dev/null +++ b/internal/cli/recall/core/confirm/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package confirm prompts the user to confirm an export plan before execution. +package confirm diff --git a/internal/cli/recall/core/execute/doc.go b/internal/cli/recall/core/execute/doc.go new file mode 100644 index 000000000..2041f71f4 --- /dev/null +++ b/internal/cli/recall/core/execute/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package execute writes journal files according to an export plan. +package execute diff --git a/internal/cli/recall/core/extract/doc.go b/internal/cli/recall/core/extract/doc.go new file mode 100644 index 000000000..b4e04786d --- /dev/null +++ b/internal/cli/recall/core/extract/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package extract extracts YAML frontmatter blocks from Markdown content. +package extract diff --git a/internal/cli/recall/core/format/doc.go b/internal/cli/recall/core/format/doc.go new file mode 100644 index 000000000..fa077e84d --- /dev/null +++ b/internal/cli/recall/core/format/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package format transforms session data into formatted journal Markdown with frontmatter and turn headers. +package format diff --git a/internal/cli/recall/core/frontmatter/doc.go b/internal/cli/recall/core/frontmatter/doc.go new file mode 100644 index 000000000..388a2d953 --- /dev/null +++ b/internal/cli/recall/core/frontmatter/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package frontmatter resolves headings and builds YAML frontmatter for journal entries. +package frontmatter diff --git a/internal/cli/recall/core/index/doc.go b/internal/cli/recall/core/index/doc.go new file mode 100644 index 000000000..ef34d39dd --- /dev/null +++ b/internal/cli/recall/core/index/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package index builds a session-ID-to-filename index from existing journal files. +package index diff --git a/internal/cli/recall/core/lock/doc.go b/internal/cli/recall/core/lock/doc.go new file mode 100644 index 000000000..887ee5e0a --- /dev/null +++ b/internal/cli/recall/core/lock/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package lock marks journal entries as locked to prevent re-export and manages multipart files. +package lock diff --git a/internal/cli/recall/core/lock/lock.go b/internal/cli/recall/core/lock/lock.go index ab3dfa931..2ce46d763 100644 --- a/internal/cli/recall/core/lock/lock.go +++ b/internal/cli/recall/core/lock/lock.go @@ -21,7 +21,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/session" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/err/journal" - ctxErr "github.com/ActiveMemory/ctx/internal/err/session" + errSession "github.com/ActiveMemory/ctx/internal/err/session" "github.com/ActiveMemory/ctx/internal/io" "github.com/ActiveMemory/ctx/internal/journal/state" "github.com/ActiveMemory/ctx/internal/rc" @@ -244,7 +244,7 @@ func RunLockUnlock( return cmd.Help() } if len(args) > 0 && all { - return ctxErr.AllWithPattern() + return errSession.AllWithPattern() } journalDir := filepath.Join(rc.ContextDir(), dir.Journal) diff --git a/internal/cli/recall/core/plan/doc.go b/internal/cli/recall/core/plan/doc.go new file mode 100644 index 000000000..c4c0d0967 --- /dev/null +++ b/internal/cli/recall/core/plan/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package plan builds an export plan that maps sessions to journal output files. +package plan diff --git a/internal/cli/recall/core/query/doc.go b/internal/cli/recall/core/query/doc.go new file mode 100644 index 000000000..17ca9a780 --- /dev/null +++ b/internal/cli/recall/core/query/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package query finds sessions for the current or all projects. +package query diff --git a/internal/cli/recall/core/query/query.go b/internal/cli/recall/core/query/query.go index efbe7cd57..c57ea8705 100644 --- a/internal/cli/recall/core/query/query.go +++ b/internal/cli/recall/core/query/query.go @@ -10,7 +10,7 @@ import ( "os" "github.com/ActiveMemory/ctx/internal/entity" - errfs "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/recall/parser" ) @@ -29,7 +29,7 @@ func FindSessions(allProjects bool) ([]*entity.Session, error) { } cwd, cwdErr := os.Getwd() if cwdErr != nil { - return nil, errfs.WorkingDirectory(cwdErr) + return nil, errFs.WorkingDirectory(cwdErr) } return parser.FindSessionsForCWD(cwd) } diff --git a/internal/cli/recall/core/slug/doc.go b/internal/cli/recall/core/slug/doc.go new file mode 100644 index 000000000..70e3ef884 --- /dev/null +++ b/internal/cli/recall/core/slug/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package slug converts human-readable titles into URL-friendly slugs for journal filenames. +package slug diff --git a/internal/cli/recall/core/validate/doc.go b/internal/cli/recall/core/validate/doc.go new file mode 100644 index 000000000..c8f65330e --- /dev/null +++ b/internal/cli/recall/core/validate/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package validate checks messages and flag combinations for recall export validity. +package validate diff --git a/internal/cli/recall/core/validate/validate.go b/internal/cli/recall/core/validate/validate.go index 0454f68a2..994020f27 100644 --- a/internal/cli/recall/core/validate/validate.go +++ b/internal/cli/recall/core/validate/validate.go @@ -9,7 +9,7 @@ package validate import ( "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/err/journal" - ctxErr "github.com/ActiveMemory/ctx/internal/err/session" + errSession "github.com/ActiveMemory/ctx/internal/err/session" ) // EmptyMessage reports whether a message has no meaningful content @@ -34,7 +34,7 @@ func EmptyMessage(msg entity.Message) bool { // - error: non-nil if flags conflict. func ExportFlags(args []string, opts entity.ExportOpts) error { if len(args) > 0 && opts.All { - return ctxErr.AllWithID() + return errSession.AllWithID() } if opts.Regenerate && !opts.All { return journal.RegenerateRequiresAll() diff --git a/internal/cli/remind/cmd/dismiss/cmd.go b/internal/cli/remind/cmd/dismiss/cmd.go index 009ce105a..2a11b20df 100644 --- a/internal/cli/remind/cmd/dismiss/cmd.go +++ b/internal/cli/remind/cmd/dismiss/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - ctxErr "github.com/ActiveMemory/ctx/internal/err/reminder" + errReminder "github.com/ActiveMemory/ctx/internal/err/reminder" ) // Cmd returns the remind dismiss subcommand. @@ -34,7 +34,7 @@ func Cmd() *cobra.Command { return RunDismissAll(cmd) } if len(args) == 0 { - return ctxErr.IDRequired() + return errReminder.IDRequired() } return RunDismiss(cmd, args[0]) }, diff --git a/internal/cli/remind/cmd/dismiss/run.go b/internal/cli/remind/cmd/dismiss/run.go index 9074cee90..0ee4bb190 100644 --- a/internal/cli/remind/cmd/dismiss/run.go +++ b/internal/cli/remind/cmd/dismiss/run.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/cli/remind/core" - ctxErr "github.com/ActiveMemory/ctx/internal/err/reminder" + errReminder "github.com/ActiveMemory/ctx/internal/err/reminder" "github.com/ActiveMemory/ctx/internal/write/remind" ) @@ -27,7 +27,7 @@ import ( func RunDismiss(cmd *cobra.Command, idStr string) error { id, parseErr := strconv.Atoi(idStr) if parseErr != nil { - return ctxErr.InvalidID(idStr) + return errReminder.InvalidID(idStr) } reminders, readErr := core.ReadReminders() @@ -44,7 +44,7 @@ func RunDismiss(cmd *cobra.Command, idStr string) error { } if found < 0 { - return ctxErr.NotFound(id) + return errReminder.NotFound(id) } remind.ReminderDismissed(cmd, reminders[found].ID, reminders[found].Message) diff --git a/internal/cli/remind/core/store.go b/internal/cli/remind/core/store.go index fd86a96af..41e19b7e7 100644 --- a/internal/cli/remind/core/store.go +++ b/internal/cli/remind/core/store.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/reminder" - ctxErr "github.com/ActiveMemory/ctx/internal/err/reminder" + errReminder "github.com/ActiveMemory/ctx/internal/err/reminder" "github.com/ActiveMemory/ctx/internal/io" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -30,11 +30,11 @@ func ReadReminders() ([]Reminder, error) { if errors.Is(readErr, os.ErrNotExist) { return nil, nil } - return nil, ctxErr.Read(readErr) + return nil, errReminder.Read(readErr) } var reminders []Reminder if parseErr := json.Unmarshal(data, &reminders); parseErr != nil { - return nil, ctxErr.Parse(parseErr) + return nil, errReminder.Parse(parseErr) } return reminders, nil } diff --git a/internal/cli/resume/cmd/root/run.go b/internal/cli/resume/cmd/root/run.go index 15162038c..bd311477d 100644 --- a/internal/cli/resume/cmd/root/run.go +++ b/internal/cli/resume/cmd/root/run.go @@ -10,7 +10,7 @@ import ( "os" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" - session2 "github.com/ActiveMemory/ctx/internal/cli/system/core/session" + coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/write/session" @@ -26,7 +26,7 @@ import ( // - error: Always nil func Run(cmd *cobra.Command, sessionID string) error { if sessionID == "" { - sessionID = session2.ReadSessionID(os.Stdin) + sessionID = coreSession.ReadSessionID(os.Stdin) } nudge.Resume(sessionID) session.SessionResumed(cmd, sessionID) diff --git a/internal/cli/serve/cmd/root/run.go b/internal/cli/serve/cmd/root/run.go index 217687028..a3e68ac8c 100644 --- a/internal/cli/serve/cmd/root/run.go +++ b/internal/cli/serve/cmd/root/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/zensical" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/site" + errSite "github.com/ActiveMemory/ctx/internal/err/site" zensicalBin "github.com/ActiveMemory/ctx/internal/exec/zensical" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -48,13 +48,13 @@ func Run(args []string) error { // Check zensical.toml exists tomlPath := filepath.Join(d, zensical.Toml) if _, statErr = os.Stat(tomlPath); os.IsNotExist(statErr) { - return ctxErr.NoConfig(d) + return errSite.NoConfig(d) } // Check if zensical is available _, lookErr := exec.LookPath(zensical.Bin) if lookErr != nil { - return ctxErr.ZensicalNotFound() + return errSite.ZensicalNotFound() } return zensicalBin.Run(d) diff --git a/internal/cli/serve/serve_test.go b/internal/cli/serve/serve_test.go index 8ddb76030..db081a84a 100644 --- a/internal/cli/serve/serve_test.go +++ b/internal/cli/serve/serve_test.go @@ -12,10 +12,10 @@ import ( "strings" "testing" - serveroot "github.com/ActiveMemory/ctx/internal/cli/serve/cmd/root" + serveRoot "github.com/ActiveMemory/ctx/internal/cli/serve/cmd/root" "github.com/ActiveMemory/ctx/internal/config/zensical" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxerr "github.com/ActiveMemory/ctx/internal/err/site" + errSite "github.com/ActiveMemory/ctx/internal/err/site" ) func TestCmd(t *testing.T) { @@ -60,7 +60,7 @@ func TestCmd_AcceptsArgs(t *testing.T) { } func TestRunServe_DirNotFound(t *testing.T) { - err := serveroot.Run([]string{"/tmp/nonexistent-dir-ctx-test-xyz"}) + err := serveRoot.Run([]string{"/tmp/nonexistent-dir-ctx-test-xyz"}) if err == nil { t.Fatal("expected error for nonexistent directory") } @@ -77,7 +77,7 @@ func TestRunServe_NotADirectory(t *testing.T) { defer func() { _ = os.Remove(tmpFile.Name()) }() _ = tmpFile.Close() - serveErr := serveroot.Run([]string{tmpFile.Name()}) + serveErr := serveRoot.Run([]string{tmpFile.Name()}) if serveErr == nil { t.Fatal("expected error for non-directory path") } @@ -93,7 +93,7 @@ func TestRunServe_NoSiteConfig(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - serveErr := serveroot.Run([]string{tmpDir}) + serveErr := serveRoot.Run([]string{tmpDir}) if serveErr == nil { t.Fatal("expected error for missing zensical.toml") } @@ -118,7 +118,7 @@ func TestRunServe_ZensicalNotFound(t *testing.T) { // Ensure zensical is not in PATH t.Setenv("PATH", "") - serveErr := serveroot.Run([]string{tmpDir}) + serveErr := serveRoot.Run([]string{tmpDir}) if serveErr == nil { t.Fatal("expected error for missing zensical binary") } @@ -128,9 +128,9 @@ func TestRunServe_ZensicalNotFound(t *testing.T) { } func TestRunServe_DefaultDir(t *testing.T) { - // When no args are given, serveroot.Run uses the default journal-site directory + // When no args are given, serveRoot.Run uses the default journal-site directory // which won't exist in test, so we expect directory not found - err := serveroot.Run([]string{}) + err := serveRoot.Run([]string{}) if err == nil { t.Fatal("expected error when default dir doesn't exist") } @@ -168,7 +168,7 @@ func TestErrNotDir(t *testing.T) { } func TestErrNoSiteConfig(t *testing.T) { - err := ctxerr.NoConfig("/some/dir") + err := errSite.NoConfig("/some/dir") if err == nil { t.Fatal("expected non-nil error") } @@ -210,14 +210,14 @@ func TestRunServe_WithMockZensical(t *testing.T) { origPath := os.Getenv("PATH") t.Setenv("PATH", binDir+":"+origPath) - serveErr := serveroot.Run([]string{tmpDir}) + serveErr := serveRoot.Run([]string{tmpDir}) if serveErr != nil { t.Errorf("unexpected error: %v", serveErr) } } func TestCmd_RunE(t *testing.T) { - // Test that Cmd().RunE actually invokes serveroot.Run via the command + // Test that Cmd().RunE actually invokes serveRoot.Run via the command cmd := Cmd() // Set args to a nonexistent dir so we get a predictable error cmd.SetArgs([]string{"/tmp/nonexistent-ctx-test-xyz"}) @@ -234,7 +234,7 @@ func TestCmd_RunE(t *testing.T) { } func TestErrZensicalNotFound(t *testing.T) { - err := ctxerr.ZensicalNotFound() + err := errSite.ZensicalNotFound() if err == nil { t.Fatal("expected non-nil error") } diff --git a/internal/cli/site/core/rss/doc.go b/internal/cli/site/core/rss/doc.go new file mode 100644 index 000000000..abdf67d02 --- /dev/null +++ b/internal/cli/site/core/rss/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package rss generates Atom XML feeds from blog post metadata. +package rss diff --git a/internal/cli/site/core/scan/doc.go b/internal/cli/site/core/scan/doc.go new file mode 100644 index 000000000..3666a3e0f --- /dev/null +++ b/internal/cli/site/core/scan/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package scan reads blog post directories, parses frontmatter, and returns sorted post metadata. +package scan diff --git a/internal/cli/status/cmd/root/run.go b/internal/cli/status/cmd/root/run.go index f283cff2a..b22e87f46 100644 --- a/internal/cli/status/cmd/root/run.go +++ b/internal/cli/status/cmd/root/run.go @@ -11,10 +11,10 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/status/core/out" "github.com/ActiveMemory/ctx/internal/context/load" - ctxerr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/spf13/cobra" - errctx "github.com/ActiveMemory/ctx/internal/err/context" + errCtx "github.com/ActiveMemory/ctx/internal/err/context" ) // Run executes the status command logic. @@ -29,9 +29,9 @@ import ( func Run(cmd *cobra.Command, jsonOutput, verbose bool) error { ctx, err := load.Do("") if err != nil { - var notFoundError *errctx.NotFoundError + var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return ctxerr.ContextNotInitialized() + return errInit.ContextNotInitialized() } return err } diff --git a/internal/cli/status/core/out/doc.go b/internal/cli/status/core/out/doc.go new file mode 100644 index 000000000..4dd83c49d --- /dev/null +++ b/internal/cli/status/core/out/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package out writes context status as JSON or formatted text to command output. +package out diff --git a/internal/cli/status/core/preview/doc.go b/internal/cli/status/core/preview/doc.go new file mode 100644 index 000000000..23f7dec66 --- /dev/null +++ b/internal/cli/status/core/preview/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package preview extracts short content previews from context files for status display. +package preview diff --git a/internal/cli/status/core/sort/doc.go b/internal/cli/status/core/sort/doc.go new file mode 100644 index 000000000..f7ae350f1 --- /dev/null +++ b/internal/cli/status/core/sort/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sort orders context files by priority and recency for status output. +package sort diff --git a/internal/cli/status/status.go b/internal/cli/status/status.go index 6a832faa3..e889a9104 100644 --- a/internal/cli/status/status.go +++ b/internal/cli/status/status.go @@ -9,10 +9,10 @@ package status import ( "github.com/spf13/cobra" - statuRroot "github.com/ActiveMemory/ctx/internal/cli/status/cmd/root" + statusRoot "github.com/ActiveMemory/ctx/internal/cli/status/cmd/root" ) // Cmd returns the status command. func Cmd() *cobra.Command { - return statuRroot.Cmd() + return statusRoot.Cmd() } diff --git a/internal/cli/sync/cmd/root/run.go b/internal/cli/sync/cmd/root/run.go index 885c0214a..9ba94bf0d 100644 --- a/internal/cli/sync/cmd/root/run.go +++ b/internal/cli/sync/cmd/root/run.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/context/load" errCtx "github.com/ActiveMemory/ctx/internal/err/context" - initErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/write/sync" ) @@ -35,7 +35,7 @@ func Run(cmd *cobra.Command, dryRun bool) error { if err != nil { var notFoundError *errCtx.NotFoundError if errors.As(err, ¬FoundError) { - return initErr.ContextNotInitialized() + return errInit.ContextNotInitialized() } return err } diff --git a/internal/cli/sync/core/action/doc.go b/internal/cli/sync/core/action/doc.go new file mode 100644 index 000000000..1190dfc24 --- /dev/null +++ b/internal/cli/sync/core/action/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package action detects discrepancies between the codebase and context documentation. +package action diff --git a/internal/cli/sync/core/validate/doc.go b/internal/cli/sync/core/validate/doc.go new file mode 100644 index 000000000..d16827b2f --- /dev/null +++ b/internal/cli/sync/core/validate/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package validate checks for undocumented package managers, config files, and directories. +package validate diff --git a/internal/cli/sync/core/validate/validate.go b/internal/cli/sync/core/validate/validate.go index 148ba23bf..e27d1f157 100644 --- a/internal/cli/sync/core/validate/validate.go +++ b/internal/cli/sync/core/validate/validate.go @@ -15,10 +15,10 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/assets/read/lookup" "github.com/ActiveMemory/ctx/internal/cli/sync/core" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/dep" "github.com/ActiveMemory/ctx/internal/config/embed/text" - syncCfg "github.com/ActiveMemory/ctx/internal/config/sync" + cfgSync "github.com/ActiveMemory/ctx/internal/config/sync" "github.com/ActiveMemory/ctx/internal/entity" ) @@ -41,12 +41,12 @@ func CheckPackageFiles(ctx *entity.Context) []core.Action { if _, err := os.Stat(f); err == nil { // File exists, check if we have DEPENDENCIES.md or similar hasDepsDoc := false - if f := ctx.File(ctxCfg.Dependency); f != nil { + if f := ctx.File(cfgCtx.Dependency); f != nil { hasDepsDoc = true } else { for _, f := range ctx.Files { if strings.Contains(strings.ToLower(string(f.Content)), - syncCfg.KeywordDependencies, + cfgSync.KeywordDependencies, ) { hasDepsDoc = true break @@ -56,15 +56,15 @@ func CheckPackageFiles(ctx *entity.Context) []core.Action { if !hasDepsDoc { actions = append(actions, core.Action{ - Type: syncCfg.ActionDeps, - File: ctxCfg.Architecture, + Type: cfgSync.ActionDeps, + File: cfgCtx.Architecture, Description: fmt.Sprintf( lookup.TextDesc(text.DescKeySyncDepsDescription), f, d, ), Suggestion: fmt.Sprintf( lookup.TextDesc(text.DescKeySyncDepsSuggestion), - ctxCfg.Architecture, ctxCfg.Dependency, + cfgCtx.Architecture, cfgCtx.Dependency, ), }) } @@ -93,7 +93,7 @@ func CheckConfigFiles(ctx *entity.Context) []core.Action { if len(matches) > 0 { // Check if CONVENTIONS.md mentions this var convContent string - if f := ctx.File(ctxCfg.Convention); f != nil { + if f := ctx.File(cfgCtx.Convention); f != nil { convContent = strings.ToLower(string(f.Content)) } @@ -101,15 +101,15 @@ func CheckConfigFiles(ctx *entity.Context) []core.Action { keyword = strings.TrimSuffix(keyword, "*") if convContent == "" || !strings.Contains(convContent, keyword) { actions = append(actions, core.Action{ - Type: syncCfg.ActionConfig, - File: ctxCfg.Convention, + Type: cfgSync.ActionConfig, + File: cfgCtx.Convention, Description: fmt.Sprintf( desc.Text(text.DescKeySyncConfigDescription), matches[0], cfg.Topic, ), Suggestion: fmt.Sprintf( desc.Text(text.DescKeySyncConfigSuggestion), - cfg.Topic, ctxCfg.Convention, + cfg.Topic, cfgCtx.Convention, ), }) } @@ -136,7 +136,7 @@ func CheckNewDirectories(ctx *entity.Context) []core.Action { // Get ARCHITECTURE.md content var archContent string - if f := ctx.File(ctxCfg.Architecture); f != nil { + if f := ctx.File(cfgCtx.Architecture); f != nil { archContent = strings.ToLower(string(f.Content)) } @@ -151,21 +151,21 @@ func CheckNewDirectories(ctx *entity.Context) []core.Action { continue } name := entry.Name() - if strings.HasPrefix(name, ".") || syncCfg.SkipDirs[name] { + if strings.HasPrefix(name, ".") || cfgSync.SkipDirs[name] { continue } - if syncCfg.ImportantDirs[name] && !strings.Contains(archContent, name) { + if cfgSync.ImportantDirs[name] && !strings.Contains(archContent, name) { actions = append(actions, core.Action{ - Type: syncCfg.ActionNewDir, - File: ctxCfg.Architecture, + Type: cfgSync.ActionNewDir, + File: cfgCtx.Architecture, Description: fmt.Sprintf( desc.Text(text.DescKeySyncDirDescription), name, ), Suggestion: fmt.Sprintf( desc.Text(text.DescKeySyncDirSuggestion), - name, ctxCfg.Architecture, + name, cfgCtx.Architecture, ), }) } diff --git a/internal/cli/system/cmd/backup/run.go b/internal/cli/system/cmd/backup/run.go index 3e21ed8b6..808c5a7c7 100644 --- a/internal/cli/system/cmd/backup/run.go +++ b/internal/cli/system/cmd/backup/run.go @@ -11,7 +11,7 @@ import ( "os" "time" - archive2 "github.com/ActiveMemory/ctx/internal/cli/system/core/archive" + coreArchive "github.com/ActiveMemory/ctx/internal/cli/system/core/archive" "github.com/ActiveMemory/ctx/internal/entity" "github.com/spf13/cobra" @@ -19,7 +19,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/env" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" errBackup "github.com/ActiveMemory/ctx/internal/err/backup" - ctxErr "github.com/ActiveMemory/ctx/internal/err/initialize" + errInit "github.com/ActiveMemory/ctx/internal/err/initialize" "github.com/ActiveMemory/ctx/internal/write/backup" ) @@ -46,15 +46,15 @@ func Run(cmd *cobra.Command) error { home, homeErr := os.UserHomeDir() if homeErr != nil { - return ctxErr.HomeDir(homeErr) + return errInit.HomeDir(homeErr) } smbURL := os.Getenv(env.BackupSMBURL) smbSubdir := os.Getenv(env.BackupSMBSubdir) - var smb *archive2.SMBConfig + var smb *coreArchive.SMBConfig if smbURL != "" { var smbErr error - smb, smbErr = archive2.ParseSMBConfig(smbURL, smbSubdir) + smb, smbErr = coreArchive.ParseSMBConfig(smbURL, smbSubdir) if smbErr != nil { return errBackup.SMBConfig(smbErr) } @@ -64,7 +64,7 @@ func Run(cmd *cobra.Command) error { var results []entity.BackupResult if scope == archive.BackupScopeProject || scope == archive.BackupScopeAll { - result, projErr := archive2.BackupProject(cmd.ErrOrStderr(), home, timestamp, smb) + result, projErr := coreArchive.BackupProject(cmd.ErrOrStderr(), home, timestamp, smb) if projErr != nil { return errBackup.Project(projErr) } @@ -72,7 +72,7 @@ func Run(cmd *cobra.Command) error { } if scope == archive.BackupScopeGlobal || scope == archive.BackupScopeAll { - result, globalErr := archive2.BackupGlobal(cmd.ErrOrStderr(), home, timestamp, smb) + result, globalErr := coreArchive.BackupGlobal(cmd.ErrOrStderr(), home, timestamp, smb) if globalErr != nil { return errBackup.Global(globalErr) } diff --git a/internal/cli/system/cmd/bootstrap/run.go b/internal/cli/system/cmd/bootstrap/run.go index 108db15b6..4f1cfbde3 100644 --- a/internal/cli/system/cmd/bootstrap/run.go +++ b/internal/cli/system/cmd/bootstrap/run.go @@ -9,7 +9,7 @@ package bootstrap import ( "os" - bootstrap2 "github.com/ActiveMemory/ctx/internal/cli/system/core/bootstrap" + coreBootstrap "github.com/ActiveMemory/ctx/internal/cli/system/core/bootstrap" "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/assets/read/desc" @@ -43,14 +43,14 @@ func Run(cmd *cobra.Command) error { return nil } - files := bootstrap2.ListContextFiles(dir) - rules := bootstrap2.ParseNumberedLines( + files := coreBootstrap.ListContextFiles(dir) + rules := coreBootstrap.ParseNumberedLines( desc.Text(text.DescKeyBootstrapRules), ) - nextSteps := bootstrap2.ParseNumberedLines( + nextSteps := coreBootstrap.ParseNumberedLines( desc.Text(text.DescKeyBootstrapNextSteps), ) - warning := bootstrap2.PluginWarning() + warning := coreBootstrap.PluginWarning() jsonFlag, _ := cmd.Flags().GetBool(cFlag.JSON) if jsonFlag { @@ -58,7 +58,7 @@ func Run(cmd *cobra.Command) error { return nil } - fileList := bootstrap2.WrapFileList( + fileList := coreBootstrap.WrapFileList( files, cfgBootstrap.BootstrapFileListWidth, cfgBootstrap.BootstrapFileListIndent, diff --git a/internal/cli/system/cmd/check_journal/run.go b/internal/cli/system/cmd/check_journal/run.go index bf224db8e..442d97b24 100644 --- a/internal/cli/system/cmd/check_journal/run.go +++ b/internal/cli/system/cmd/check_journal/run.go @@ -11,8 +11,8 @@ import ( "os" "path/filepath" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" - journal2 "github.com/ActiveMemory/ctx/internal/cli/system/core/journal" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreJournal "github.com/ActiveMemory/ctx/internal/cli/system/core/journal" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" @@ -46,7 +46,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { if !state.Initialized() { return nil } - input, _, paused := hook2.Preamble(stdin) + input, _, paused := coreCheck.Preamble(stdin) if paused { return nil } @@ -58,7 +58,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { ) // Only remind once per day - if hook2.DailyThrottled(remindedFile) { + if coreCheck.DailyThrottled(remindedFile) { return nil } @@ -72,13 +72,13 @@ func Run(cmd *cobra.Command, stdin *os.File) error { } // Stage 1: Unexported sessions - newestJournal := journal2.NewestMtime(jDir, file.ExtMarkdown) - unexported := journal2.CountNewerFiles( + newestJournal := coreJournal.NewestMtime(jDir, file.ExtMarkdown) + unexported := coreJournal.CountNewerFiles( claudeProjectsDir, file.ExtJSONL, newestJournal, ) // Stage 2: Unenriched entries - unenriched := journal2.CountUnenriched(jDir) + unenriched := coreJournal.CountUnenriched(jDir) if unexported == 0 && unenriched == 0 { return nil diff --git a/internal/cli/system/cmd/check_knowledge/run.go b/internal/cli/system/cmd/check_knowledge/run.go index 5a46570c5..b6475ea6c 100644 --- a/internal/cli/system/cmd/check_knowledge/run.go +++ b/internal/cli/system/cmd/check_knowledge/run.go @@ -11,7 +11,7 @@ import ( "path/filepath" "github.com/ActiveMemory/ctx/internal/cli/system/core/check" - knowledge2 "github.com/ActiveMemory/ctx/internal/cli/system/core/knowledge" + coreKnowledge "github.com/ActiveMemory/ctx/internal/cli/system/core/knowledge" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" "github.com/spf13/cobra" @@ -48,7 +48,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { return nil } - if box, warned := knowledge2.CheckKnowledgeHealth(sessionID); warned { + if box, warned := coreKnowledge.CheckKnowledgeHealth(sessionID); warned { writeHook.Nudge(cmd, box) internalIo.TouchFile(markerPath) } diff --git a/internal/cli/system/cmd/check_memory_drift/run.go b/internal/cli/system/cmd/check_memory_drift/run.go index d456643e6..b248c4400 100644 --- a/internal/cli/system/cmd/check_memory_drift/run.go +++ b/internal/cli/system/cmd/check_memory_drift/run.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" "github.com/spf13/cobra" @@ -28,7 +28,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { return nil } - input, sessionID, paused := hook2.Preamble(stdin) + input, sessionID, paused := coreCheck.Preamble(stdin) if paused { return nil } diff --git a/internal/cli/system/cmd/check_reminder/run.go b/internal/cli/system/cmd/check_reminder/run.go index 10529a38b..ef50a5df0 100644 --- a/internal/cli/system/cmd/check_reminder/run.go +++ b/internal/cli/system/cmd/check_reminder/run.go @@ -11,7 +11,7 @@ import ( "os" "time" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" @@ -45,7 +45,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { return nil } - input, _, paused := hook2.Preamble(stdin) + input, _, paused := coreCheck.Preamble(stdin) if paused { return nil } diff --git a/internal/cli/system/cmd/events/run.go b/internal/cli/system/cmd/events/run.go index 12beb1a1d..879575355 100644 --- a/internal/cli/system/cmd/events/run.go +++ b/internal/cli/system/cmd/events/run.go @@ -11,7 +11,7 @@ import ( coreEvent "github.com/ActiveMemory/ctx/internal/cli/system/core/event" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - errRcall "github.com/ActiveMemory/ctx/internal/err/recall" + errRecall "github.com/ActiveMemory/ctx/internal/err/recall" "github.com/ActiveMemory/ctx/internal/log" writeEvents "github.com/ActiveMemory/ctx/internal/write/events" ) @@ -42,7 +42,7 @@ func Run(cmd *cobra.Command) error { evts, queryErr := log.Query(opts) if queryErr != nil { - return errRcall.EventLogRead(queryErr) + return errRecall.EventLogRead(queryErr) } if len(evts) == 0 { diff --git a/internal/cli/system/cmd/heartbeat/run.go b/internal/cli/system/cmd/heartbeat/run.go index a5e2312d3..95e52e479 100644 --- a/internal/cli/system/cmd/heartbeat/run.go +++ b/internal/cli/system/cmd/heartbeat/run.go @@ -11,10 +11,10 @@ import ( "os" "path/filepath" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/counter" - heartbeat2 "github.com/ActiveMemory/ctx/internal/cli/system/core/heartbeat" - log2 "github.com/ActiveMemory/ctx/internal/cli/system/core/log" + coreHeartbeat "github.com/ActiveMemory/ctx/internal/cli/system/core/heartbeat" + coreLog "github.com/ActiveMemory/ctx/internal/cli/system/core/log" "github.com/ActiveMemory/ctx/internal/cli/system/core/session" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" "github.com/ActiveMemory/ctx/internal/cli/system/core/time" @@ -48,7 +48,7 @@ func Run(_ *cobra.Command, stdin *os.File) error { if !state.Initialized() { return nil } - _, sessionID, paused := hook2.Preamble(stdin) + _, sessionID, paused := coreCheck.Preamble(stdin) if paused { return nil } @@ -69,9 +69,9 @@ func Run(_ *cobra.Command, stdin *os.File) error { // Detect context modification since the last heartbeat. currentMtime := time.GetLatestContextMtime(contextDir) - lastMtime := heartbeat2.ReadMtime(mtimeFile) + lastMtime := coreHeartbeat.ReadMtime(mtimeFile) contextModified := currentMtime > lastMtime - heartbeat2.WriteMtime(mtimeFile, currentMtime) + coreHeartbeat.WriteMtime(mtimeFile, currentMtime) // Read token usage for this session. info, _ := session.ReadSessionTokenInfo(sessionID) @@ -113,7 +113,7 @@ func Run(_ *cobra.Command, stdin *os.File) error { logLine = fmt.Sprintf(desc.Text(text.DescKeyHeartbeatLogPlain), count, contextModified) } - log2.Message(logFile, sessionID, logLine) + coreLog.Message(logFile, sessionID, logLine) // No stdout - agent never sees this hook. return nil diff --git a/internal/cli/system/cmd/message/cmd/edit/doc.go b/internal/cli/system/cmd/message/cmd/edit/doc.go new file mode 100644 index 000000000..e95fbeb28 --- /dev/null +++ b/internal/cli/system/cmd/message/cmd/edit/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package edit provides the ctx system message edit subcommand for customizing hook templates. +package edit diff --git a/internal/cli/system/cmd/message/cmd/edit/run.go b/internal/cli/system/cmd/message/cmd/edit/run.go index 0ba8d2aba..dc5171233 100644 --- a/internal/cli/system/cmd/message/cmd/edit/run.go +++ b/internal/cli/system/cmd/message/cmd/edit/run.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/hook" "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/err/fs" - ctxErr "github.com/ActiveMemory/ctx/internal/err/hook" + errHook "github.com/ActiveMemory/ctx/internal/err/hook" writeMessage "github.com/ActiveMemory/ctx/internal/write/message" ) @@ -34,13 +34,13 @@ import ( func Run(cmd *cobra.Command, hk, variant string) error { info := messages.Lookup(hk, variant) if info == nil { - return ctxErr.Validate(messages.Variants(hk) != nil, hk, variant) + return errHook.Validate(messages.Variants(hk) != nil, hk, variant) } oPath := message.OverridePath(hk, variant) if _, statErr := os.Stat(oPath); statErr == nil { - return ctxErr.OverrideExists(oPath, hk, variant) + return errHook.OverrideExists(oPath, hk, variant) } if info.Category == messages.CategoryCtxSpecific { @@ -49,7 +49,7 @@ func Run(cmd *cobra.Command, hk, variant string) error { data, readErr := hook.Message(hk, variant+file.ExtTxt) if readErr != nil { - return ctxErr.EmbeddedTemplateNotFound(hk, variant) + return errHook.EmbeddedTemplateNotFound(hk, variant) } dir := filepath.Dir(oPath) @@ -58,7 +58,7 @@ func Run(cmd *cobra.Command, hk, variant string) error { } if writeErr := os.WriteFile(oPath, data, 0o600); writeErr != nil { - return ctxErr.WriteOverride(oPath, writeErr) + return errHook.WriteOverride(oPath, writeErr) } writeMessage.OverrideCreated(cmd, oPath) diff --git a/internal/cli/system/cmd/message/cmd/list/doc.go b/internal/cli/system/cmd/message/cmd/list/doc.go new file mode 100644 index 000000000..df0ada529 --- /dev/null +++ b/internal/cli/system/cmd/message/cmd/list/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package list provides the ctx system message list subcommand for displaying available hook messages. +package list diff --git a/internal/cli/system/cmd/message/cmd/reset/doc.go b/internal/cli/system/cmd/message/cmd/reset/doc.go new file mode 100644 index 000000000..b461cdba0 --- /dev/null +++ b/internal/cli/system/cmd/message/cmd/reset/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package reset provides the ctx system message reset subcommand for restoring default hook templates. +package reset diff --git a/internal/cli/system/cmd/message/cmd/reset/run.go b/internal/cli/system/cmd/message/cmd/reset/run.go index d43b912a6..8604adbbc 100644 --- a/internal/cli/system/cmd/message/cmd/reset/run.go +++ b/internal/cli/system/cmd/message/cmd/reset/run.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/assets/hooks/messages" - ctxErr "github.com/ActiveMemory/ctx/internal/err/hook" + errHook "github.com/ActiveMemory/ctx/internal/err/hook" writeMessage "github.com/ActiveMemory/ctx/internal/write/message" ) @@ -30,7 +30,7 @@ import ( func Run(cmd *cobra.Command, hk, variant string) error { info := messages.Lookup(hk, variant) if info == nil { - return ctxErr.Validate(messages.Variants(hk) != nil, hk, variant) + return errHook.Validate(messages.Variants(hk) != nil, hk, variant) } oPath := message.OverridePath(hk, variant) @@ -40,7 +40,7 @@ func Run(cmd *cobra.Command, hk, variant string) error { writeMessage.NoOverride(cmd, hk, variant) return nil } - return ctxErr.RemoveOverride(oPath, removeErr) + return errHook.RemoveOverride(oPath, removeErr) } hookDir := filepath.Dir(oPath) diff --git a/internal/cli/system/cmd/message/cmd/show/doc.go b/internal/cli/system/cmd/message/cmd/show/doc.go new file mode 100644 index 000000000..63130135e --- /dev/null +++ b/internal/cli/system/cmd/message/cmd/show/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package show provides the ctx system message show subcommand for viewing hook template content. +package show diff --git a/internal/cli/system/cmd/message/cmd/show/run.go b/internal/cli/system/cmd/message/cmd/show/run.go index 341781f56..42747144c 100644 --- a/internal/cli/system/cmd/message/cmd/show/run.go +++ b/internal/cli/system/cmd/message/cmd/show/run.go @@ -11,9 +11,9 @@ import ( "github.com/spf13/cobra" "github.com/ActiveMemory/ctx/internal/assets/hooks/messages" - hook "github.com/ActiveMemory/ctx/internal/assets/read/hook" + readHook "github.com/ActiveMemory/ctx/internal/assets/read/hook" "github.com/ActiveMemory/ctx/internal/config/file" - ctxErr "github.com/ActiveMemory/ctx/internal/err/hook" + errHook "github.com/ActiveMemory/ctx/internal/err/hook" "github.com/ActiveMemory/ctx/internal/io" writeMessage "github.com/ActiveMemory/ctx/internal/write/message" ) @@ -30,7 +30,7 @@ import ( func Run(cmd *cobra.Command, hk, variant string) error { info := messages.Lookup(hk, variant) if info == nil { - return ctxErr.Validate(messages.Variants(hk) != nil, hk, variant) + return errHook.Validate(messages.Variants(hk) != nil, hk, variant) } oPath := message.OverridePath(hk, variant) @@ -41,9 +41,9 @@ func Run(cmd *cobra.Command, hk, variant string) error { return nil } - data, readErr := hook.Message(hk, variant+file.ExtTxt) + data, readErr := readHook.Message(hk, variant+file.ExtTxt) if readErr != nil { - return ctxErr.EmbeddedTemplateNotFound(hk, variant) + return errHook.EmbeddedTemplateNotFound(hk, variant) } writeMessage.SourceDefault(cmd) diff --git a/internal/cli/system/cmd/message/doc.go b/internal/cli/system/cmd/message/doc.go new file mode 100644 index 000000000..274f26cf8 --- /dev/null +++ b/internal/cli/system/cmd/message/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package message provides the parent command for ctx system message. +package message diff --git a/internal/cli/system/cmd/message/message.go b/internal/cli/system/cmd/message/message.go index 813758dda..2b625f9b5 100644 --- a/internal/cli/system/cmd/message/message.go +++ b/internal/cli/system/cmd/message/message.go @@ -4,7 +4,6 @@ // \ Copyright 2026-present Context contributors. // SPDX-License-Identifier: Apache-2.0 -// Package message provides the parent command for ctx system message. package message import ( diff --git a/internal/cli/system/cmd/post_commit/run.go b/internal/cli/system/cmd/post_commit/run.go index d83c473c4..540e44e67 100644 --- a/internal/cli/system/cmd/post_commit/run.go +++ b/internal/cli/system/cmd/post_commit/run.go @@ -11,7 +11,7 @@ import ( "os" "regexp" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/drift" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" @@ -48,7 +48,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { if !state.Initialized() { return nil } - input, sessionID, paused := hook2.Preamble(stdin) + input, sessionID, paused := coreCheck.Preamble(stdin) if paused { return nil } diff --git a/internal/cli/system/cmd/prune/run.go b/internal/cli/system/cmd/prune/run.go index c526b1858..f5eaf2e58 100644 --- a/internal/cli/system/cmd/prune/run.go +++ b/internal/cli/system/cmd/prune/run.go @@ -13,8 +13,8 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/system/core/health" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" - time2 "github.com/ActiveMemory/ctx/internal/config/time" - ctxerr "github.com/ActiveMemory/ctx/internal/err/state" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" + errState "github.com/ActiveMemory/ctx/internal/err/state" "github.com/ActiveMemory/ctx/internal/write/prune" "github.com/spf13/cobra" ) @@ -37,10 +37,10 @@ func Run(cmd *cobra.Command, days int, dryRun bool) error { entries, readErr := os.ReadDir(dir) if readErr != nil { - return ctxerr.ReadingDir(readErr) + return errState.ReadingDir(readErr) } - cutoff := time.Now().Add(-time.Duration(days) * time2.HoursPerDay * time.Hour) + cutoff := time.Now().Add(-time.Duration(days) * cfgTime.HoursPerDay * time.Hour) var pruned, skipped, preserved int for _, entry := range entries { diff --git a/internal/cli/system/cmd/qa_reminder/run.go b/internal/cli/system/cmd/qa_reminder/run.go index e739db6de..5e6eabf30 100644 --- a/internal/cli/system/cmd/qa_reminder/run.go +++ b/internal/cli/system/cmd/qa_reminder/run.go @@ -11,7 +11,7 @@ import ( "os" "strings" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" @@ -41,7 +41,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { if !state.Initialized() { return nil } - input, _, paused := hook2.Preamble(stdin) + input, _, paused := coreCheck.Preamble(stdin) if paused { return nil } diff --git a/internal/cli/system/cmd/resume/run.go b/internal/cli/system/cmd/resume/run.go index 045bd6d27..8a1553909 100644 --- a/internal/cli/system/cmd/resume/run.go +++ b/internal/cli/system/cmd/resume/run.go @@ -15,7 +15,7 @@ import ( coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/session" - session2 "github.com/ActiveMemory/ctx/internal/write/session" + writeSession "github.com/ActiveMemory/ctx/internal/write/session" ) // Run executes the resume logic. @@ -41,6 +41,6 @@ func Run(cmd *cobra.Command, stdin *os.File) error { path := nudge.PauseMarkerPath(sessionID) _ = os.Remove(path) - session2.SessionResumed(cmd, sessionID) + writeSession.SessionResumed(cmd, sessionID) return nil } diff --git a/internal/cli/system/cmd/specs_nudge/run.go b/internal/cli/system/cmd/specs_nudge/run.go index 93a900418..877c7245e 100644 --- a/internal/cli/system/cmd/specs_nudge/run.go +++ b/internal/cli/system/cmd/specs_nudge/run.go @@ -10,7 +10,7 @@ import ( "fmt" "os" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/check" + coreCheck "github.com/ActiveMemory/ctx/internal/cli/system/core/check" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" "github.com/ActiveMemory/ctx/internal/cli/system/core/nudge" coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" @@ -41,7 +41,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error { if !state.Initialized() { return nil } - input, _, paused := hook2.Preamble(stdin) + input, _, paused := coreCheck.Preamble(stdin) if paused { return nil } diff --git a/internal/cli/system/core/archive/backup.go b/internal/cli/system/core/archive/backup.go index 7533c2b39..a33cb3bdc 100644 --- a/internal/cli/system/core/archive/backup.go +++ b/internal/cli/system/core/archive/backup.go @@ -19,7 +19,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/archive" "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/embed/text" - configFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/entity" errBackup "github.com/ActiveMemory/ctx/internal/err/backup" internalIo "github.com/ActiveMemory/ctx/internal/io" @@ -37,7 +37,7 @@ import ( func CreateArchive( archivePath string, entries []entity.ArchiveEntry, w io.Writer, ) error { - outFile, createErr := internalIo.SafeCreateFile(archivePath, configFs.PermFile) + outFile, createErr := internalIo.SafeCreateFile(archivePath, cfgFs.PermFile) if createErr != nil { return errBackup.CreateArchive(createErr) } @@ -95,7 +95,7 @@ func BackupProject( // Touch marker file for check-backup-age hook. markerDir := filepath.Join(home, archive.BackupMarkerDir) - _ = os.MkdirAll(markerDir, configFs.PermExec) + _ = os.MkdirAll(markerDir, cfgFs.PermExec) markerPath := filepath.Join(markerDir, archive.BackupMarkerFile) internalIo.TouchFile(markerPath) diff --git a/internal/cli/system/core/archive/doc.go b/internal/cli/system/core/archive/doc.go new file mode 100644 index 000000000..53011e648 --- /dev/null +++ b/internal/cli/system/core/archive/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package archive creates compressed tar archives of context data with optional SMB backup. +package archive diff --git a/internal/cli/system/core/archive/smb.go b/internal/cli/system/core/archive/smb.go index 90474cde5..a2b0a8fa7 100644 --- a/internal/cli/system/core/archive/smb.go +++ b/internal/cli/system/core/archive/smb.go @@ -17,8 +17,8 @@ import ( "github.com/ActiveMemory/ctx/internal/config/archive" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/fs" - backupErr "github.com/ActiveMemory/ctx/internal/err/backup" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" + errBackup "github.com/ActiveMemory/ctx/internal/err/backup" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" "github.com/ActiveMemory/ctx/internal/io" ) @@ -44,7 +44,7 @@ type SMBConfig struct { func ParseSMBConfig(smbURL, subdir string) (*SMBConfig, error) { u, parseErr := url.Parse(smbURL) if parseErr != nil || u.Host == "" { - return nil, backupErr.InvalidSMBURL(smbURL) + return nil, errBackup.InvalidSMBURL(smbURL) } host := u.Host @@ -53,7 +53,7 @@ func ParseSMBConfig(smbURL, subdir string) (*SMBConfig, error) { share = share[1:] } if share == "" { - return nil, backupErr.SMBMissingShare(smbURL) + return nil, errBackup.SMBMissingShare(smbURL) } if subdir == "" { @@ -87,7 +87,7 @@ func EnsureSMBMount(cfg *SMBConfig) error { //nolint:gosec // G204: smbURL is from user env, not untrusted input mountCmd := exec.Command("gio", "mount", cfg.SourceURL) if mountErr := mountCmd.Run(); mountErr != nil { - return backupErr.MountFailed(cfg.SourceURL, mountErr) + return errBackup.MountFailed(cfg.SourceURL, mountErr) } return nil @@ -104,17 +104,17 @@ func EnsureSMBMount(cfg *SMBConfig) error { func CopyToSMB(cfg *SMBConfig, localPath string) error { dest := filepath.Join(cfg.GVFSPath, cfg.Subdir) if mkdirErr := os.MkdirAll(dest, fs.PermExec); mkdirErr != nil { - return fserr.CreateDir(dest, mkdirErr) + return errFs.CreateDir(dest, mkdirErr) } data, readErr := io.SafeReadUserFile(localPath) if readErr != nil { - return fserr.ReadFile(readErr) + return errFs.ReadFile(readErr) } destFile := filepath.Join(dest, filepath.Base(localPath)) if writeErr := os.WriteFile(destFile, data, fs.PermFile); writeErr != nil { - return backupErr.WriteSMB(writeErr) + return errBackup.WriteSMB(writeErr) } return nil diff --git a/internal/cli/system/core/bootstrap/doc.go b/internal/cli/system/core/bootstrap/doc.go new file mode 100644 index 000000000..db043ed92 --- /dev/null +++ b/internal/cli/system/core/bootstrap/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package bootstrap checks plugin installation status and emits warnings during context bootstrap. +package bootstrap diff --git a/internal/cli/system/core/ceremony/doc.go b/internal/cli/system/core/ceremony/doc.go new file mode 100644 index 000000000..74beec55e --- /dev/null +++ b/internal/cli/system/core/ceremony/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package ceremony assembles hook ceremony output from recent journal files and message templates. +package ceremony diff --git a/internal/cli/system/core/check/doc.go b/internal/cli/system/core/check/doc.go new file mode 100644 index 000000000..0bf896fcb --- /dev/null +++ b/internal/cli/system/core/check/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package check provides shared preamble logic and throttling for hook input processing. +package check diff --git a/internal/cli/system/core/counter/doc.go b/internal/cli/system/core/counter/doc.go new file mode 100644 index 000000000..bfd09f2a4 --- /dev/null +++ b/internal/cli/system/core/counter/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package counter reads and writes integer counters persisted in state files. +package counter diff --git a/internal/cli/system/core/drift/doc.go b/internal/cli/system/core/drift/doc.go new file mode 100644 index 000000000..84988cb6d --- /dev/null +++ b/internal/cli/system/core/drift/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package drift detects version drift across VERSION, plugin, and marketplace files and reports stale context entries. +package drift diff --git a/internal/cli/system/core/event/doc.go b/internal/cli/system/core/event/doc.go new file mode 100644 index 000000000..6212effea --- /dev/null +++ b/internal/cli/system/core/event/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package event formats and renders event log timestamps and entries. +package event diff --git a/internal/cli/system/core/health/doc.go b/internal/cli/system/core/health/doc.go new file mode 100644 index 000000000..426b104bb --- /dev/null +++ b/internal/cli/system/core/health/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package health monitors context health by detecting stale architecture maps and pruning expired state files. +package health diff --git a/internal/cli/system/core/health/prune.go b/internal/cli/system/core/health/prune.go index a911aa059..52c2493c8 100644 --- a/internal/cli/system/core/health/prune.go +++ b/internal/cli/system/core/health/prune.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" "github.com/ActiveMemory/ctx/internal/config/embed/text" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" ) // UUIDPattern matches a UUID (v4) anywhere in a filename. @@ -42,7 +42,7 @@ func AutoPrune(days int) int { return 0 } - cutoff := time.Now().Add(-time.Duration(days) * time2.HoursPerDay * time.Hour) + cutoff := time.Now().Add(-time.Duration(days) * cfgTime.HoursPerDay * time.Hour) var pruned int for _, entry := range entries { diff --git a/internal/cli/system/core/heartbeat/doc.go b/internal/cli/system/core/heartbeat/doc.go new file mode 100644 index 000000000..3011ba674 --- /dev/null +++ b/internal/cli/system/core/heartbeat/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package heartbeat reads and writes mtime values for session heartbeat tracking. +package heartbeat diff --git a/internal/cli/system/core/journal/doc.go b/internal/cli/system/core/journal/doc.go new file mode 100644 index 000000000..5ebeeefc9 --- /dev/null +++ b/internal/cli/system/core/journal/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package journal scans journal directories for the newest file modification time and manages journal state. +package journal diff --git a/internal/cli/system/core/knowledge/doc.go b/internal/cli/system/core/knowledge/doc.go new file mode 100644 index 000000000..6650199b9 --- /dev/null +++ b/internal/cli/system/core/knowledge/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package knowledge scans knowledge files against configured thresholds and emits warnings when limits are exceeded. +package knowledge diff --git a/internal/cli/system/core/load/doc.go b/internal/cli/system/core/load/doc.go new file mode 100644 index 000000000..2da08df09 --- /dev/null +++ b/internal/cli/system/core/load/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package load extracts index content from context files and handles load-gate logic during session startup. +package load diff --git a/internal/cli/system/core/log/doc.go b/internal/cli/system/core/log/doc.go new file mode 100644 index 000000000..962db87b5 --- /dev/null +++ b/internal/cli/system/core/log/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package log appends timestamped messages to hook log files with automatic rotation. +package log diff --git a/internal/cli/system/core/message/doc.go b/internal/cli/system/core/message/doc.go new file mode 100644 index 000000000..e260fd37d --- /dev/null +++ b/internal/cli/system/core/message/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package message loads and renders hook message templates with priority resolution between user overrides and defaults. +package message diff --git a/internal/cli/system/core/nudge/context_size.go b/internal/cli/system/core/nudge/context_size.go index 40b1d3a76..a362cc069 100644 --- a/internal/cli/system/core/nudge/context_size.go +++ b/internal/cli/system/core/nudge/context_size.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/cli/system/core/log" "github.com/ActiveMemory/ctx/internal/cli/system/core/message" - hook2 "github.com/ActiveMemory/ctx/internal/cli/system/core/session" + coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session" "github.com/ActiveMemory/ctx/internal/cli/system/core/state" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/hook" @@ -78,12 +78,12 @@ func EmitCheckpoint(logFile, sessionID string, count, tokens, pct, windowSize in func EmitWindowWarning(logFile, sessionID string, count, tokens, pct int) string { fallback := fmt.Sprintf( desc.Text(text.DescKeyCheckContextSizeWindowFallback), - pct, hook2.FormatTokenCount(tokens), + pct, coreSession.FormatTokenCount(tokens), ) content := message.LoadMessage(hook.CheckContextSize, hook.VariantWindow, map[string]any{ stats.VarPercentage: pct, - stats.VarTokenCount: hook2.FormatTokenCount(tokens), + stats.VarTokenCount: coreSession.FormatTokenCount(tokens), }, fallback) if content == "" { log.Message( @@ -107,7 +107,7 @@ func EmitWindowWarning(logFile, sessionID string, count, tokens, pct int) string ref := notify.NewTemplateRef(hook.CheckContextSize, hook.VariantWindow, map[string]any{ stats.VarPercentage: pct, - stats.VarTokenCount: hook2.FormatTokenCount(tokens), + stats.VarTokenCount: coreSession.FormatTokenCount(tokens), }, ) windowMsg := fmt.Sprintf(desc.Text(text.DescKeyRelayPrefixFormat), @@ -139,11 +139,11 @@ func EmitBillingWarning(logFile, sessionID string, count, tokens, threshold int) } fallback := fmt.Sprintf(desc.Text(text.DescKeyCheckContextSizeBillingFallback), - hook2.FormatTokenCount(tokens), hook2.FormatTokenCount(threshold)) + coreSession.FormatTokenCount(tokens), coreSession.FormatTokenCount(threshold)) content := message.LoadMessage(hook.CheckContextSize, hook.VariantBilling, map[string]any{ - stats.VarTokenCount: hook2.FormatTokenCount(tokens), - stats.VarThreshold: hook2.FormatTokenCount(threshold), + stats.VarTokenCount: coreSession.FormatTokenCount(tokens), + stats.VarThreshold: coreSession.FormatTokenCount(threshold), }, fallback) if content == "" { log.Message( @@ -171,15 +171,15 @@ func EmitBillingWarning(logFile, sessionID string, count, tokens, threshold int) ref := notify.NewTemplateRef( hook.CheckContextSize, hook.VariantBilling, map[string]any{ - stats.VarTokenCount: hook2.FormatTokenCount(tokens), - stats.VarThreshold: hook2.FormatTokenCount(threshold), + stats.VarTokenCount: coreSession.FormatTokenCount(tokens), + stats.VarThreshold: coreSession.FormatTokenCount(threshold), }, ) billingMsg := fmt.Sprintf(desc.Text(text.DescKeyRelayPrefixFormat), hook.CheckContextSize, fmt.Sprintf( desc.Text(text.DescKeyCheckContextSizeBillingRelayFormat), - hook2.FormatTokenCount(tokens), hook2.FormatTokenCount(threshold), + coreSession.FormatTokenCount(tokens), coreSession.FormatTokenCount(threshold), ), ) NudgeAndRelay(billingMsg, sessionID, ref) diff --git a/internal/cli/system/core/nudge/doc.go b/internal/cli/system/core/nudge/doc.go new file mode 100644 index 000000000..c589e0046 --- /dev/null +++ b/internal/cli/system/core/nudge/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package nudge emits checkpoint, pause, and oversize nudge notifications during sessions. +package nudge diff --git a/internal/cli/system/core/persistence/doc.go b/internal/cli/system/core/persistence/doc.go new file mode 100644 index 000000000..6aad217af --- /dev/null +++ b/internal/cli/system/core/persistence/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package persistence tracks edit counter state for persistence nudge timing. +package persistence diff --git a/internal/cli/system/core/session/doc.go b/internal/cli/system/core/session/doc.go new file mode 100644 index 000000000..e2bc2aa45 --- /dev/null +++ b/internal/cli/system/core/session/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package session builds hook JSON responses and manages session token tracking. +package session diff --git a/internal/cli/system/core/session/session_token.go b/internal/cli/system/core/session/session_token.go index eaca55397..ec391ec53 100644 --- a/internal/cli/system/core/session/session_token.go +++ b/internal/cli/system/core/session/session_token.go @@ -27,7 +27,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/stats" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" - io2 "github.com/ActiveMemory/ctx/internal/io" + internalIo "github.com/ActiveMemory/ctx/internal/io" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -74,7 +74,7 @@ func ReadSessionTokenInfo(sessionID string) (entity.TokenInfo, error) { func FindJSONLPath(sessionID string) (string, error) { // Check cache first cacheFile := filepath.Join(state.StateDir(), stats.JsonlPathCachePrefix+sessionID) - if data, readErr := io2.SafeReadUserFile(cacheFile); readErr == nil { + if data, readErr := internalIo.SafeReadUserFile(cacheFile); readErr == nil { cached := strings.TrimSpace(string(data)) if cached != "" { if _, statErr := os.Stat(cached); statErr == nil { @@ -113,7 +113,7 @@ func FindJSONLPath(sessionID string) (string, error) { // - SessionTokenInfo: Token count and model, or zero value if not found // - error: Non-nil only on I/O errors func ParseLastUsageAndModel(path string) (entity.TokenInfo, error) { - f, openErr := io2.SafeOpenUserFile(path) + f, openErr := internalIo.SafeOpenUserFile(path) if openErr != nil { return entity.TokenInfo{}, openErr } @@ -243,7 +243,7 @@ func ClaudeSettingsHas1M() bool { if homeErr != nil { return false } - data, readErr := io2.SafeReadUserFile(filepath.Join(home, dir.Claude, claude.GlobalSettings)) + data, readErr := internalIo.SafeReadUserFile(filepath.Join(home, dir.Claude, claude.GlobalSettings)) if readErr != nil { return false } diff --git a/internal/cli/system/core/state/doc.go b/internal/cli/system/core/state/doc.go new file mode 100644 index 000000000..84e428d5b --- /dev/null +++ b/internal/cli/system/core/state/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package state provides the project-scoped runtime state directory and initialization checks. +package state diff --git a/internal/cli/system/core/stats/stats.go b/internal/cli/system/core/stats/stats.go index ee415fe2e..6ad760870 100644 --- a/internal/cli/system/core/stats/stats.go +++ b/internal/cli/system/core/stats/stats.go @@ -22,11 +22,11 @@ import ( "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/config/journal" "github.com/ActiveMemory/ctx/internal/config/stats" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" - ctxerr "github.com/ActiveMemory/ctx/internal/err/recall" - io2 "github.com/ActiveMemory/ctx/internal/io" + errRecall "github.com/ActiveMemory/ctx/internal/err/recall" + internalIo "github.com/ActiveMemory/ctx/internal/io" ) // ReadStatsDir reads all stats JSONL files, optionally filtered by session prefix. @@ -42,7 +42,7 @@ func ReadStatsDir(dir, sessionFilter string) ([]Entry, error) { pattern := filepath.Join(dir, stats.FilePrefix+"*"+file.ExtJSONL) matches, globErr := filepath.Glob(pattern) if globErr != nil { - return nil, ctxerr.StatsGlob(globErr) + return nil, errRecall.StatsGlob(globErr) } var entries []Entry @@ -93,7 +93,7 @@ func ExtractStatsSessionID(basename string) string { // - []Entry: parsed entries // - error: non-nil on read failure func ParseStatsFile(path, sid string) ([]Entry, error) { - data, readErr := io2.SafeReadUserFile(path) + data, readErr := internalIo.SafeReadUserFile(path) if readErr != nil { return nil, readErr } @@ -212,7 +212,7 @@ func FormatStatsTimestamp(ts string) string { if parseErr != nil { return ts } - return t.Local().Format(time2.DateTimePreciseFormat) + return t.Local().Format(cfgTime.DateTimePreciseFormat) } // ReadNewLines reads bytes from offset to end and parses JSONL lines. @@ -225,7 +225,7 @@ func FormatStatsTimestamp(ts string) string { // Returns: // - []Entry: newly parsed entries func ReadNewLines(path string, offset int64, sid string) []Entry { - f, openErr := io2.SafeOpenUserFile(path) + f, openErr := internalIo.SafeOpenUserFile(path) if openErr != nil { return nil } diff --git a/internal/cli/system/core/time/doc.go b/internal/cli/system/core/time/doc.go new file mode 100644 index 000000000..c541f3abb --- /dev/null +++ b/internal/cli/system/core/time/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package time returns the most recent modification time across context files. +package time diff --git a/internal/cli/system/core/version/doc.go b/internal/cli/system/core/version/doc.go new file mode 100644 index 000000000..4ca4a5a1a --- /dev/null +++ b/internal/cli/system/core/version/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package version parses semantic versions and checks for update availability. +package version diff --git a/internal/cli/task/cmd/complete/run.go b/internal/cli/task/cmd/complete/run.go index ba440a1e4..6f87e854f 100644 --- a/internal/cli/task/cmd/complete/run.go +++ b/internal/cli/task/cmd/complete/run.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/regex" "github.com/ActiveMemory/ctx/internal/config/token" - ctxerr "github.com/ActiveMemory/ctx/internal/err/task" + errTask "github.com/ActiveMemory/ctx/internal/err/task" "github.com/ActiveMemory/ctx/internal/write/complete" "github.com/spf13/cobra" @@ -44,13 +44,13 @@ func CompleteTask(query, contextDir string) (string, error) { // Check if the file exists if _, statErr := os.Stat(filePath); os.IsNotExist(statErr) { - return "", ctxerr.FileNotFound() + return "", errTask.FileNotFound() } // Read existing content content, readErr := os.ReadFile(filepath.Clean(filePath)) if readErr != nil { - return "", ctxerr.FileRead(readErr) + return "", errTask.FileRead(readErr) } // Parse tasks and find matching one @@ -85,7 +85,7 @@ func CompleteTask(query, contextDir string) (string, error) { strings.ToLower(taskText), strings.ToLower(query), ) { if matchedLine != -1 { - return "", ctxerr.MultipleMatches(query) + return "", errTask.MultipleMatches(query) } matchedLine = i matchedTask = taskText @@ -94,7 +94,7 @@ func CompleteTask(query, contextDir string) (string, error) { } if matchedLine == -1 { - return "", ctxerr.NotFound(query) + return "", errTask.NotFound(query) } // Mark the task as complete @@ -105,7 +105,7 @@ func CompleteTask(query, contextDir string) (string, error) { // Write back newContent := strings.Join(lines, token.NewlineLF) if writeErr := os.WriteFile(filePath, []byte(newContent), fs.PermFile); writeErr != nil { - return "", ctxerr.FileWrite(writeErr) + return "", errTask.FileWrite(writeErr) } return matchedTask, nil diff --git a/internal/cli/task/core/count/doc.go b/internal/cli/task/core/count/doc.go new file mode 100644 index 000000000..83a071fb8 --- /dev/null +++ b/internal/cli/task/core/count/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package count counts pending top-level tasks in TASKS.md. +package count diff --git a/internal/cli/task/core/path/doc.go b/internal/cli/task/core/path/doc.go new file mode 100644 index 000000000..eb060cfaf --- /dev/null +++ b/internal/cli/task/core/path/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package path resolves file paths for TASKS.md and the task archive directory. +package path diff --git a/internal/cli/watch/core/apply/doc.go b/internal/cli/watch/core/apply/doc.go new file mode 100644 index 000000000..f59ed6137 --- /dev/null +++ b/internal/cli/watch/core/apply/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package apply routes context updates to the appropriate handler for writing entries or completing tasks. +package apply diff --git a/internal/cli/watch/core/stream/doc.go b/internal/cli/watch/core/stream/doc.go new file mode 100644 index 000000000..1c8aa58b5 --- /dev/null +++ b/internal/cli/watch/core/stream/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package stream parses stdin for XML context-update tags and dispatches them for processing. +package stream diff --git a/internal/cli/watch/core/stream/stream.go b/internal/cli/watch/core/stream/stream.go index d7feae53f..cd50146c3 100644 --- a/internal/cli/watch/core/stream/stream.go +++ b/internal/cli/watch/core/stream/stream.go @@ -17,8 +17,8 @@ import ( "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/regex" "github.com/ActiveMemory/ctx/internal/config/watch" - ctxerr "github.com/ActiveMemory/ctx/internal/err/fs" - watch2 "github.com/ActiveMemory/ctx/internal/write/watch" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" + writeWatch "github.com/ActiveMemory/ctx/internal/write/watch" "github.com/spf13/cobra" ) @@ -79,13 +79,13 @@ func ProcessStream(cmd *cobra.Command, reader io.Reader, dryRun bool) error { } if dryRun { - watch2.DryRunPreview(cmd, update.Type, update.Content) + writeWatch.DryRunPreview(cmd, update.Type, update.Content) } else { err := apply.ApplyUpdate(update) if err != nil { - watch2.ApplyFailed(cmd, update.Type, err) + writeWatch.ApplyFailed(cmd, update.Type, err) } else { - watch2.ApplySuccess(cmd, update.Type, update.Content) + writeWatch.ApplySuccess(cmd, update.Type, update.Content) updateCount++ } } @@ -94,7 +94,7 @@ func ProcessStream(cmd *cobra.Command, reader io.Reader, dryRun bool) error { } if err := scanner.Err(); err != nil { - return ctxerr.ReadInputStream(err) + return errFs.ReadInputStream(err) } return nil diff --git a/internal/cli/watch/watch.go b/internal/cli/watch/watch.go index 0b861ed8a..5562d5220 100644 --- a/internal/cli/watch/watch.go +++ b/internal/cli/watch/watch.go @@ -9,10 +9,10 @@ package watch import ( "github.com/spf13/cobra" - watchroot "github.com/ActiveMemory/ctx/internal/cli/watch/cmd/root" + watchRoot "github.com/ActiveMemory/ctx/internal/cli/watch/cmd/root" ) // Cmd returns the watch command. func Cmd() *cobra.Command { - return watchroot.Cmd() + return watchRoot.Cmd() } diff --git a/internal/cli/why/cmd/root/run.go b/internal/cli/why/cmd/root/run.go index 730a17466..d287d70bb 100644 --- a/internal/cli/why/cmd/root/run.go +++ b/internal/cli/why/cmd/root/run.go @@ -16,8 +16,8 @@ import ( "github.com/ActiveMemory/ctx/internal/write/why" "github.com/spf13/cobra" - errcli "github.com/ActiveMemory/ctx/internal/err/cli" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" + errCli "github.com/ActiveMemory/ctx/internal/err/cli" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" ) // Run dispatches to the interactive menu or direct document display. @@ -47,13 +47,13 @@ func showMenu(cmd *cobra.Command) error { reader := bufio.NewReader(os.Stdin) input, readErr := reader.ReadString('\n') if readErr != nil { - return fserr.ReadInput(readErr) + return errFs.ReadInput(readErr) } input = strings.TrimSpace(input) choice, parseErr := strconv.Atoi(input) if parseErr != nil || choice < 1 || choice > len(DocOrder) { - return errcli.InvalidSelection(input, len(DocOrder)) + return errCli.InvalidSelection(input, len(DocOrder)) } why.Separator(cmd) @@ -71,12 +71,12 @@ func showMenu(cmd *cobra.Command) error { func ShowDoc(cmd *cobra.Command, alias string) error { name, ok := DocAliases[alias] if !ok { - return errcli.UnknownDocument(alias) + return errCli.UnknownDocument(alias) } content, loadErr := philosophy.WhyDoc(name) if loadErr != nil { - return fserr.FileRead(name, loadErr) + return errFs.FileRead(name, loadErr) } cleaned := StripMkDocs(string(content)) diff --git a/internal/cli/why/strip_test.go b/internal/cli/why/strip_test.go index 143046d65..8d59f5fa9 100644 --- a/internal/cli/why/strip_test.go +++ b/internal/cli/why/strip_test.go @@ -11,12 +11,12 @@ import ( "testing" "github.com/ActiveMemory/ctx/internal/assets/read/philosophy" - whyroot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" + whyRoot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" ) func TestStripMkDocs_Frontmatter(t *testing.T) { input := "---\ntitle: Test\nicon: lucide/flame\n---\n\n# Hello\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if strings.Contains(got, "title: Test") { t.Error("frontmatter was not stripped") } @@ -27,7 +27,7 @@ func TestStripMkDocs_Frontmatter(t *testing.T) { func TestStripMkDocs_Images(t *testing.T) { input := "# Title\n\n![banner](images/banner.png)\n\nSome text.\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if strings.Contains(got, "![banner]") { t.Error("image line was not stripped") } @@ -43,7 +43,7 @@ func TestStripMkDocs_Admonitions(t *testing.T) { Normal text. ` - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if !strings.Contains(got, `> **Important Note**`) { t.Errorf("admonition title not converted, got:\n%s", got) } @@ -60,7 +60,7 @@ Normal text. func TestStripMkDocs_AdmonitionNoTitle(t *testing.T) { input := "!!! warning\n Body here.\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if strings.Contains(got, "!!!") { t.Error("admonition marker was not stripped") } @@ -81,7 +81,7 @@ func TestStripMkDocs_Tabs(t *testing.T) { Normal text. ` - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if !strings.Contains(got, "**Without ctx**") { t.Errorf("tab title not converted, got:\n%s", got) } @@ -101,7 +101,7 @@ Normal text. func TestStripMkDocs_RelativeLinks(t *testing.T) { input := "[Getting Started](getting-started.md) and [About](../home/about.md)\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if strings.Contains(got, ".md") { t.Errorf("relative .md link not stripped, got:\n%s", got) } @@ -115,7 +115,7 @@ func TestStripMkDocs_RelativeLinks(t *testing.T) { func TestStripMkDocs_PreservesExternalLinks(t *testing.T) { input := "[ctx site](https://ctx.ist) stays.\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if !strings.Contains(got, "[ctx site](https://ctx.ist)") { t.Errorf("external link was modified, got:\n%s", got) } @@ -123,7 +123,7 @@ func TestStripMkDocs_PreservesExternalLinks(t *testing.T) { func TestStripMkDocs_PreservesCodeBlocks(t *testing.T) { input := "```text\n{} --> what\nctx --> why\n```\n" - got := whyroot.StripMkDocs(input) + got := whyRoot.StripMkDocs(input) if !strings.Contains(got, "ctx --> why") { t.Errorf("code block content was lost, got:\n%s", got) } @@ -135,7 +135,7 @@ func TestStripMkDocs_EmbeddedManifesto(t *testing.T) { t.Fatalf("failed to load embedded manifesto: %v", loadErr) } - got := whyroot.StripMkDocs(string(content)) + got := whyRoot.StripMkDocs(string(content)) // Should not contain MkDocs artifacts. if strings.Contains(got, "---\ntitle:") { diff --git a/internal/cli/why/why.go b/internal/cli/why/why.go index c7f5a1f6d..6ca9af703 100644 --- a/internal/cli/why/why.go +++ b/internal/cli/why/why.go @@ -9,10 +9,10 @@ package why import ( "github.com/spf13/cobra" - whyroot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" + whyRoot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" ) // Cmd returns the "ctx why" cobra command. func Cmd() *cobra.Command { - return whyroot.Cmd() + return whyRoot.Cmd() } diff --git a/internal/cli/why/why_test.go b/internal/cli/why/why_test.go index 5db92df1d..fa3a33e89 100644 --- a/internal/cli/why/why_test.go +++ b/internal/cli/why/why_test.go @@ -11,7 +11,7 @@ import ( "strings" "testing" - whyroot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" + whyRoot "github.com/ActiveMemory/ctx/internal/cli/why/cmd/root" "github.com/spf13/cobra" ) @@ -27,7 +27,7 @@ func newTestCmd() (*cobra.Command, *bytes.Buffer) { func TestShowDoc_Manifesto(t *testing.T) { cmd, buf := newTestCmd() - showErr := whyroot.ShowDoc(cmd, "manifesto") + showErr := whyRoot.ShowDoc(cmd, "manifesto") if showErr != nil { t.Fatalf("ShowDoc(manifesto) error = %v", showErr) } @@ -44,7 +44,7 @@ func TestShowDoc_Manifesto(t *testing.T) { func TestShowDoc_UnknownAlias(t *testing.T) { cmd, _ := newTestCmd() - showErr := whyroot.ShowDoc(cmd, "nonexistent") + showErr := whyRoot.ShowDoc(cmd, "nonexistent") if showErr == nil { t.Fatal("expected error for unknown document alias") } @@ -106,7 +106,7 @@ func TestExtractAdmonitionTitle(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := whyroot.ExtractAdmonitionTitle(tc.input) + got := whyRoot.ExtractAdmonitionTitle(tc.input) if got != tc.want { t.Errorf("ExtractAdmonitionTitle(%q) = %q, want %q", tc.input, got, tc.want) } @@ -139,7 +139,7 @@ func TestExtractTabTitle(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := whyroot.ExtractTabTitle(tc.input) + got := whyRoot.ExtractTabTitle(tc.input) if got != tc.want { t.Errorf("ExtractTabTitle(%q) = %q, want %q", tc.input, got, tc.want) } diff --git a/internal/context/load/loader.go b/internal/context/load/loader.go index 640e9daf8..4b856bf61 100644 --- a/internal/context/load/loader.go +++ b/internal/context/load/loader.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/context/summary" "github.com/ActiveMemory/ctx/internal/context/token" "github.com/ActiveMemory/ctx/internal/entity" - errctx "github.com/ActiveMemory/ctx/internal/err/context" + errCtx "github.com/ActiveMemory/ctx/internal/err/context" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/validation" ) @@ -40,12 +40,12 @@ func Do(dir string) (*entity.Context, error) { info, statErr := os.Stat(dir) if statErr != nil { if os.IsNotExist(statErr) { - return nil, errctx.NotFound(dir) + return nil, errCtx.NotFound(dir) } return nil, statErr } if !info.IsDir() { - return nil, errctx.NotFound(dir) + return nil, errCtx.NotFound(dir) } // Reject context directories that contain symlinks (M-2 defense). diff --git a/internal/context/load/loader_test.go b/internal/context/load/loader_test.go index 6a07f912c..41fcc3f00 100644 --- a/internal/context/load/loader_test.go +++ b/internal/context/load/loader_test.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/context/sanitize" "github.com/ActiveMemory/ctx/internal/context/validate" - errctx "github.com/ActiveMemory/ctx/internal/err/context" + errCtx "github.com/ActiveMemory/ctx/internal/err/context" ) func TestExists(t *testing.T) { @@ -137,7 +137,7 @@ func TestLoadNonExistent(t *testing.T) { t.Error("Do() should return error for non-existent directory") } - var notFoundError *errctx.NotFoundError + var notFoundError *errCtx.NotFoundError ok := errors.As(err, ¬FoundError) if !ok { t.Errorf("error should be *NotFoundError, got %T", err) @@ -188,7 +188,7 @@ func TestIsEffectivelyEmpty(t *testing.T) { } func TestNotFoundError(t *testing.T) { - err := errctx.NotFound("/test/path") + err := errCtx.NotFound("/test/path") expected := "context directory not found: /test/path" if err.Error() != expected { t.Errorf("NotFoundError.Error() = %q, want %q", err.Error(), expected) diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 6fa68e410..e7fdd329e 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -20,8 +20,8 @@ import ( "github.com/ActiveMemory/ctx/internal/config/crypto" "github.com/ActiveMemory/ctx/internal/config/fs" - ctxerr "github.com/ActiveMemory/ctx/internal/err/crypto" - io2 "github.com/ActiveMemory/ctx/internal/io" + errCrypto "github.com/ActiveMemory/ctx/internal/err/crypto" + internalIo "github.com/ActiveMemory/ctx/internal/io" ) // GenerateKey returns a new 256-bit random key. @@ -32,7 +32,7 @@ import ( func GenerateKey() ([]byte, error) { key := make([]byte, crypto.KeySize) if _, err := io.ReadFull(rand.Reader, key); err != nil { - return nil, ctxerr.GenerateKey(err) + return nil, errCrypto.GenerateKey(err) } return key, nil } @@ -53,17 +53,17 @@ func GenerateKey() ([]byte, error) { func Encrypt(key, plaintext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - return nil, ctxerr.CreateCipher(err) + return nil, errCrypto.CreateCipher(err) } gcm, err := cipher.NewGCM(block) if err != nil { - return nil, ctxerr.CreateGCM(err) + return nil, errCrypto.CreateGCM(err) } nonce := make([]byte, crypto.NonceSize) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, ctxerr.GenerateNonce(err) + return nil, errCrypto.GenerateNonce(err) } ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) @@ -82,17 +82,17 @@ func Encrypt(key, plaintext []byte) ([]byte, error) { // authentication fails func Decrypt(key, ciphertext []byte) ([]byte, error) { if len(ciphertext) < crypto.NonceSize { - return nil, ctxerr.CiphertextTooShort() + return nil, errCrypto.CiphertextTooShort() } block, err := aes.NewCipher(key) if err != nil { - return nil, ctxerr.CreateCipher(err) + return nil, errCrypto.CreateCipher(err) } gcm, err := cipher.NewGCM(block) if err != nil { - return nil, ctxerr.CreateGCM(err) + return nil, errCrypto.CreateGCM(err) } nonce := ciphertext[:crypto.NonceSize] @@ -100,7 +100,7 @@ func Decrypt(key, ciphertext []byte) ([]byte, error) { plaintext, err := gcm.Open(nil, nonce, data, nil) if err != nil { - return nil, ctxerr.Decrypt(err) + return nil, errCrypto.Decrypt(err) } return plaintext, nil @@ -115,12 +115,12 @@ func Decrypt(key, ciphertext []byte) ([]byte, error) { // - []byte: The 32-byte key // - error: Non-nil if the file cannot be read or is not exactly 32 bytes func LoadKey(path string) ([]byte, error) { - key, err := io2.SafeReadUserFile(path) + key, err := internalIo.SafeReadUserFile(path) if err != nil { - return nil, ctxerr.ReadKey(err) + return nil, errCrypto.ReadKey(err) } if len(key) != crypto.KeySize { - return nil, ctxerr.InvalidKeySize(len(key), crypto.KeySize) + return nil, errCrypto.InvalidKeySize(len(key), crypto.KeySize) } return key, nil } @@ -135,7 +135,7 @@ func LoadKey(path string) ([]byte, error) { // - error: Non-nil if the file cannot be written func SaveKey(path string, key []byte) error { if err := os.WriteFile(path, key, fs.PermSecret); err != nil { - return ctxerr.WriteKey(err) + return errCrypto.WriteKey(err) } return nil } diff --git a/internal/crypto/keypath.go b/internal/crypto/keypath.go index 28c70ae28..942f1234c 100644 --- a/internal/crypto/keypath.go +++ b/internal/crypto/keypath.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" - cryptocfg "github.com/ActiveMemory/ctx/internal/config/crypto" + cfgCrypto "github.com/ActiveMemory/ctx/internal/config/crypto" "github.com/ActiveMemory/ctx/internal/config/dir" ) @@ -24,7 +24,7 @@ func GlobalKeyPath() string { if err != nil { return "" } - return filepath.Join(home, dir.CtxData, cryptocfg.ContextKey) + return filepath.Join(home, dir.CtxData, cfgCrypto.ContextKey) } // ExpandHome expands a leading ~/ prefix to the user's home directory. @@ -69,7 +69,7 @@ func ResolveKeyPath(contextDir, overridePath string) string { } // Tier 2: project-local key. - local := filepath.Join(contextDir, cryptocfg.ContextKey) + local := filepath.Join(contextDir, cfgCrypto.ContextKey) if _, err := os.Stat(local); err == nil { return local } diff --git a/internal/crypto/keypath_test.go b/internal/crypto/keypath_test.go index bfa24a94f..f2e91cc1d 100644 --- a/internal/crypto/keypath_test.go +++ b/internal/crypto/keypath_test.go @@ -11,7 +11,7 @@ import ( "path/filepath" "testing" - cryptocfg "github.com/ActiveMemory/ctx/internal/config/crypto" + cfgCrypto "github.com/ActiveMemory/ctx/internal/config/crypto" "github.com/ActiveMemory/ctx/internal/config/fs" ) @@ -20,7 +20,7 @@ func TestGlobalKeyPath(t *testing.T) { t.Setenv("HOME", dir) got := GlobalKeyPath() - want := filepath.Join(dir, ".ctx", cryptocfg.ContextKey) + want := filepath.Join(dir, ".ctx", cfgCrypto.ContextKey) if got != want { t.Errorf("GlobalKeyPath() = %q, want %q", got, want) } @@ -71,7 +71,7 @@ func TestResolveKeyPath_ProjectLocalBeforeGlobal(t *testing.T) { if err := os.MkdirAll(contextDir, 0750); err != nil { t.Fatal(err) } - localKey := filepath.Join(contextDir, cryptocfg.ContextKey) + localKey := filepath.Join(contextDir, cfgCrypto.ContextKey) if err := os.WriteFile(localKey, []byte("local-key"), fs.PermSecret); err != nil { t.Fatal(err) } @@ -80,7 +80,7 @@ func TestResolveKeyPath_ProjectLocalBeforeGlobal(t *testing.T) { if err := os.MkdirAll(globalDir, fs.PermKeyDir); err != nil { t.Fatal(err) } - globalKey := filepath.Join(globalDir, cryptocfg.ContextKey) + globalKey := filepath.Join(globalDir, cfgCrypto.ContextKey) if err := os.WriteFile(globalKey, []byte("global-key"), fs.PermSecret); err != nil { t.Fatal(err) } @@ -100,7 +100,7 @@ func TestResolveKeyPath_FallbackToGlobal(t *testing.T) { if err := os.MkdirAll(globalDir, fs.PermKeyDir); err != nil { t.Fatal(err) } - globalKey := filepath.Join(globalDir, cryptocfg.ContextKey) + globalKey := filepath.Join(globalDir, cfgCrypto.ContextKey) if err := os.WriteFile(globalKey, []byte("global-key"), fs.PermSecret); err != nil { t.Fatal(err) } diff --git a/internal/index/index.go b/internal/index/index.go index ce2943fba..c9f4232b4 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -19,8 +19,8 @@ import ( "github.com/ActiveMemory/ctx/internal/config/marker" "github.com/ActiveMemory/ctx/internal/config/regex" "github.com/ActiveMemory/ctx/internal/config/token" - ctxerr "github.com/ActiveMemory/ctx/internal/err/recall" - io2 "github.com/ActiveMemory/ctx/internal/io" + errRecall "github.com/ActiveMemory/ctx/internal/err/recall" + internalIo "github.com/ActiveMemory/ctx/internal/io" ) // ParseHeaders extracts all entries from file content. @@ -216,18 +216,18 @@ func ReindexFile( entryType string, ) error { if _, err := os.Stat(filePath); os.IsNotExist(err) { - return ctxerr.ReindexFileNotFound(fileName) + return errRecall.ReindexFileNotFound(fileName) } - content, err := io2.SafeReadUserFile(filePath) + content, err := internalIo.SafeReadUserFile(filePath) if err != nil { - return ctxerr.ReindexFileRead(filePath, err) + return errRecall.ReindexFileRead(filePath, err) } updated := updateFunc(string(content)) if err := os.WriteFile(filePath, []byte(updated), fs.PermFile); err != nil { - return ctxerr.ReindexFileWrite(filePath, err) + return errRecall.ReindexFileWrite(filePath, err) } entries := ParseHeaders(string(content)) diff --git a/internal/io/security.go b/internal/io/security.go index 7e3e154f7..b18de52e2 100644 --- a/internal/io/security.go +++ b/internal/io/security.go @@ -15,9 +15,9 @@ import ( "strings" "time" - configFs "github.com/ActiveMemory/ctx/internal/config/fs" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" - ctxerr "github.com/ActiveMemory/ctx/internal/err/http" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" + errHTTP "github.com/ActiveMemory/ctx/internal/err/http" ) // SafeReadFile resolves filename within baseDir, verifies the result @@ -37,13 +37,13 @@ import ( func SafeReadFile(baseDir, filename string) ([]byte, error) { absBase, absErr := filepath.Abs(baseDir) if absErr != nil { - return nil, fserr.ResolveBase(absErr) + return nil, errFs.ResolveBase(absErr) } safe := filepath.Join(absBase, filepath.Base(filename)) if !strings.HasPrefix(safe, absBase+string(os.PathSeparator)) { - return nil, fserr.PathEscapesBase(filename) + return nil, errFs.PathEscapesBase(filename) } data, readErr := os.ReadFile(safe) //nolint:gosec @@ -154,7 +154,7 @@ func SafeWriteFile(path string, data []byte, perm os.FileMode) error { // Parameters: // - path: absolute file path to touch func TouchFile(path string) { - _ = os.WriteFile(path, nil, configFs.PermSecret) //nolint:gosec // state marker, path from internal code + _ = os.WriteFile(path, nil, cfgFs.PermSecret) //nolint:gosec // state marker, path from internal code } // maxRedirects caps the number of HTTP redirects the client will follow. @@ -205,7 +205,7 @@ func SafePost( Timeout: timeout, CheckRedirect: func(_ *http.Request, via []*http.Request) error { if len(via) >= maxRedirects { - return ctxerr.TooManyRedirects() + return errHTTP.TooManyRedirects() } return nil }, @@ -221,11 +221,11 @@ func SafePost( func validateHTTPScheme(rawURL string) error { parsed, parseErr := url.Parse(rawURL) if parseErr != nil { - return ctxerr.ParseURL(parseErr) + return errHTTP.ParseURL(parseErr) } scheme := strings.ToLower(parsed.Scheme) if scheme != "http" && scheme != "https" { - return ctxerr.UnsafeURLScheme(parsed.Scheme) + return errHTTP.UnsafeURLScheme(parsed.Scheme) } return nil } diff --git a/internal/io/validate.go b/internal/io/validate.go index fcab7728d..fa97c3b37 100644 --- a/internal/io/validate.go +++ b/internal/io/validate.go @@ -10,18 +10,18 @@ import ( "path/filepath" "strings" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" ) // rejectDangerousPath returns an error if the resolved absolute path // falls under a system directory that ctx should never touch. func rejectDangerousPath(absPath string) error { if absPath == "/" { - return fserr.RefuseSystemPathRoot() + return errFs.RefuseSystemPathRoot() } for _, prefix := range dangerousPrefixes { if strings.HasPrefix(absPath, prefix) { - return fserr.RefuseSystemPath(absPath) + return errFs.RefuseSystemPath(absPath) } } return nil @@ -33,7 +33,7 @@ func cleanAndValidate(path string) (string, error) { clean := filepath.Clean(path) abs, absErr := filepath.Abs(clean) if absErr != nil { - return "", fserr.ResolvePath(absErr) + return "", errFs.ResolvePath(absErr) } if checkErr := rejectDangerousPath(abs); checkErr != nil { return "", checkErr diff --git a/internal/mcp/handler/task/doc.go b/internal/mcp/handler/task/doc.go new file mode 100644 index 000000000..45da8d231 --- /dev/null +++ b/internal/mcp/handler/task/doc.go @@ -0,0 +1,8 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package task iterates pending tasks from TASKS.md for MCP tool responses. +package task diff --git a/internal/mcp/handler/tool.go b/internal/mcp/handler/tool.go index 47bd2a462..774581f9f 100644 --- a/internal/mcp/handler/tool.go +++ b/internal/mcp/handler/tool.go @@ -13,22 +13,22 @@ import ( "time" "github.com/ActiveMemory/ctx/internal/assets/read/desc" - remindcore "github.com/ActiveMemory/ctx/internal/cli/remind/core" - taskcomplete "github.com/ActiveMemory/ctx/internal/cli/task/cmd/complete" - archiveCfg "github.com/ActiveMemory/ctx/internal/config/archive" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + remindCore "github.com/ActiveMemory/ctx/internal/cli/remind/core" + taskComplete "github.com/ActiveMemory/ctx/internal/cli/task/cmd/complete" + cfgArchive "github.com/ActiveMemory/ctx/internal/config/archive" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" - entryCfg "github.com/ActiveMemory/ctx/internal/config/entry" - configfs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgEntry "github.com/ActiveMemory/ctx/internal/config/entry" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/mcp/event" "github.com/ActiveMemory/ctx/internal/config/mcp/field" - timeCfg "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/context/load" "github.com/ActiveMemory/ctx/internal/drift" "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/entry" - mcpErr "github.com/ActiveMemory/ctx/internal/err/mcp" + errMcp "github.com/ActiveMemory/ctx/internal/err/mcp" "github.com/ActiveMemory/ctx/internal/mcp/handler/task" "github.com/ActiveMemory/ctx/internal/mcp/server/stat" "github.com/ActiveMemory/ctx/internal/mcp/session" @@ -131,7 +131,7 @@ func (h *Handler) Complete(query string) (string, error) { return "", boundaryErr } - completedTask, completeErr := taskcomplete.CompleteTask( + completedTask, completeErr := taskComplete.CompleteTask( query, h.ContextDir, ) if completeErr != nil { @@ -245,7 +245,7 @@ func (h *Handler) Recall(limit int, since time.Time) (string, error) { _, _ = fmt.Fprintf( &sb, desc.Text(text.DescKeyMCPRecallItemFormat), - i+1, sess.StartTime.Format(timeCfg.DateTimeFormat), + i+1, sess.StartTime.Format(cfgTime.DateTimeFormat), ) if sess.Project != "" { _, _ = fmt.Fprintf( @@ -289,8 +289,8 @@ func (h *Handler) WatchUpdate( } // Handle the "complete" type as a special case. - if entryType == entryCfg.Complete { - completedTask, completeErr := taskcomplete.CompleteTask( + if entryType == cfgEntry.Complete { + completedTask, completeErr := taskComplete.CompleteTask( content, h.ContextDir) if completeErr != nil { return "", completeErr @@ -366,7 +366,7 @@ func (h *Handler) Compact(archive bool) (string, error) { if writeErr := os.WriteFile( result.TasksFileUpdate.Path, result.TasksFileUpdate.Content, - configfs.PermFile, + cfgFs.PermFile, ); writeErr != nil { return "", writeErr } @@ -375,7 +375,7 @@ func (h *Handler) Compact(archive bool) (string, error) { // Write section-cleaned files. for _, fu := range result.SectionFileUpdates { if writeErr := os.WriteFile( - fu.Path, fu.Content, configfs.PermFile, + fu.Path, fu.Content, cfgFs.PermFile, ); writeErr != nil { return "", writeErr } @@ -390,7 +390,7 @@ func (h *Handler) Compact(archive bool) (string, error) { token.NewlineLF + token.NewlineLF } if _, archiveErr := tidy.WriteArchive( - archiveCfg.ArchiveScopeTasks, + cfgArchive.ArchiveScopeTasks, desc.Text(text.DescKeyHeadingArchivedTasks), archiveContent, ); archiveErr != nil { @@ -445,7 +445,7 @@ func (h *Handler) Next() (string, error) { return "", loadErr } - tasksFile := ctx.File(ctxCfg.Task) + tasksFile := ctx.File(cfgCtx.Task) if tasksFile == nil { return desc.Text(text.DescKeyMCPNoTasks), nil } @@ -483,7 +483,7 @@ func (h *Handler) CheckTaskCompletion(recentAction string) (string, error) { return "", loadErr } - tasksFile := ctx.File(ctxCfg.Task) + tasksFile := ctx.File(cfgCtx.Task) if tasksFile == nil { return "", nil } @@ -572,7 +572,7 @@ func (h *Handler) SessionEvent( return sb.String(), nil default: - return "", mcpErr.UnknownEventType(eventType) + return "", errMcp.UnknownEventType(eventType) } } @@ -582,7 +582,7 @@ func (h *Handler) SessionEvent( // - string: formatted reminder list or no-reminders message // - error: reminder read error func (h *Handler) Remind() (string, error) { - reminders, readErr := remindcore.ReadReminders() + reminders, readErr := remindCore.ReadReminders() if readErr != nil { return "", readErr } @@ -591,7 +591,7 @@ func (h *Handler) Remind() (string, error) { return desc.Text(text.DescKeyMCPNoReminders), nil } - today := time.Now().Format(timeCfg.DateFormat) + today := time.Now().Format(cfgTime.DateFormat) var sb strings.Builder _, _ = fmt.Fprintf( &sb, diff --git a/internal/mcp/server/catalog/data.go b/internal/mcp/server/catalog/data.go index 8068cec2a..40ae8f39f 100644 --- a/internal/mcp/server/catalog/data.go +++ b/internal/mcp/server/catalog/data.go @@ -8,7 +8,7 @@ package catalog import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/mcp/resource" ) @@ -23,14 +23,14 @@ type mapping struct { // table defines all individual context file resources. var table = []mapping{ - {ctxCfg.Constitution, resource.Constitution, desc.Text(text.DescKeyMCPResConstitution)}, - {ctxCfg.Task, resource.Tasks, desc.Text(text.DescKeyMCPResTasks)}, - {ctxCfg.Convention, resource.Conventions, desc.Text(text.DescKeyMCPResConventions)}, - {ctxCfg.Architecture, resource.Architecture, desc.Text(text.DescKeyMCPResArchitecture)}, - {ctxCfg.Decision, resource.Decisions, desc.Text(text.DescKeyMCPResDecisions)}, - {ctxCfg.Learning, resource.Learnings, desc.Text(text.DescKeyMCPResLearnings)}, - {ctxCfg.Glossary, resource.Glossary, desc.Text(text.DescKeyMCPResGlossary)}, - {ctxCfg.AgentPlaybook, resource.Playbook, desc.Text(text.DescKeyMCPResPlaybook)}, + {cfgCtx.Constitution, resource.Constitution, desc.Text(text.DescKeyMCPResConstitution)}, + {cfgCtx.Task, resource.Tasks, desc.Text(text.DescKeyMCPResTasks)}, + {cfgCtx.Convention, resource.Conventions, desc.Text(text.DescKeyMCPResConventions)}, + {cfgCtx.Architecture, resource.Architecture, desc.Text(text.DescKeyMCPResArchitecture)}, + {cfgCtx.Decision, resource.Decisions, desc.Text(text.DescKeyMCPResDecisions)}, + {cfgCtx.Learning, resource.Learnings, desc.Text(text.DescKeyMCPResLearnings)}, + {cfgCtx.Glossary, resource.Glossary, desc.Text(text.DescKeyMCPResGlossary)}, + {cfgCtx.AgentPlaybook, resource.Playbook, desc.Text(text.DescKeyMCPResPlaybook)}, } // uriLookup maps full resource URIs to context file names. Populated diff --git a/internal/mcp/server/def/prompt/prompt.go b/internal/mcp/server/def/prompt/prompt.go index 8efd0103b..3c071e503 100644 --- a/internal/mcp/server/def/prompt/prompt.go +++ b/internal/mcp/server/def/prompt/prompt.go @@ -11,19 +11,19 @@ import ( "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/mcp/field" - promptCfg "github.com/ActiveMemory/ctx/internal/config/mcp/prompt" + cfgMcpPrompt "github.com/ActiveMemory/ctx/internal/config/mcp/prompt" "github.com/ActiveMemory/ctx/internal/mcp/proto" ) // Defs defines all available MCP prompts. var Defs = []proto.Prompt{ { - Name: promptCfg.SessionStart, + Name: cfgMcpPrompt.SessionStart, Description: desc.Text( text.DescKeyMCPPromptSessionStartDesc), }, { - Name: promptCfg.AddDecision, + Name: cfgMcpPrompt.AddDecision, Description: desc.Text( text.DescKeyMCPPromptAddDecisionDesc), Arguments: []proto.PromptArgument{ @@ -50,7 +50,7 @@ var Defs = []proto.Prompt{ }, }, { - Name: promptCfg.AddLearning, + Name: cfgMcpPrompt.AddLearning, Description: desc.Text( text.DescKeyMCPPromptAddLearningDesc), Arguments: []proto.PromptArgument{ @@ -77,12 +77,12 @@ var Defs = []proto.Prompt{ }, }, { - Name: promptCfg.Reflect, + Name: cfgMcpPrompt.Reflect, Description: desc.Text( text.DescKeyMCPPromptReflectDesc), }, { - Name: promptCfg.Checkpoint, + Name: cfgMcpPrompt.Checkpoint, Description: desc.Text( text.DescKeyMCPPromptCheckpointDesc), }, diff --git a/internal/mcp/server/def/tool/tool.go b/internal/mcp/server/def/tool/tool.go index dd4c25665..8713f6e2d 100644 --- a/internal/mcp/server/def/tool/tool.go +++ b/internal/mcp/server/def/tool/tool.go @@ -13,21 +13,21 @@ import ( "github.com/ActiveMemory/ctx/internal/config/entry" "github.com/ActiveMemory/ctx/internal/config/mcp/field" "github.com/ActiveMemory/ctx/internal/config/mcp/schema" - toolCfg "github.com/ActiveMemory/ctx/internal/config/mcp/tool" + cfgMcpTool "github.com/ActiveMemory/ctx/internal/config/mcp/tool" "github.com/ActiveMemory/ctx/internal/mcp/proto" ) // Defs defines all available MCP tools. var Defs = []proto.Tool{ { - Name: toolCfg.Status, + Name: cfgMcpTool.Status, Description: desc.Text( text.DescKeyMCPToolStatusDesc), InputSchema: proto.InputSchema{Type: schema.Object}, Annotations: &proto.ToolAnnotations{ReadOnlyHint: true}, }, { - Name: toolCfg.Add, + Name: cfgMcpTool.Add, Description: desc.Text( text.DescKeyMCPToolAddDesc), InputSchema: proto.InputSchema{ @@ -60,7 +60,7 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{}, }, { - Name: toolCfg.Complete, + Name: cfgMcpTool.Complete, Description: desc.Text( text.DescKeyMCPToolCompleteDesc), InputSchema: proto.InputSchema{ @@ -77,14 +77,14 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{IdempotentHint: true}, }, { - Name: toolCfg.Drift, + Name: cfgMcpTool.Drift, Description: desc.Text( text.DescKeyMCPToolDriftDesc), InputSchema: proto.InputSchema{Type: schema.Object}, Annotations: &proto.ToolAnnotations{ReadOnlyHint: true}, }, { - Name: toolCfg.Recall, + Name: cfgMcpTool.Recall, Description: desc.Text( text.DescKeyMCPToolRecallDesc), InputSchema: proto.InputSchema{ @@ -105,7 +105,7 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{ReadOnlyHint: true}, }, { - Name: toolCfg.WatchUpdate, + Name: cfgMcpTool.WatchUpdate, Description: desc.Text( text.DescKeyMCPToolWatchUpdateDesc), InputSchema: proto.InputSchema{ @@ -128,7 +128,7 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{}, }, { - Name: toolCfg.Compact, + Name: cfgMcpTool.Compact, Description: desc.Text( text.DescKeyMCPToolCompactDesc), InputSchema: proto.InputSchema{ @@ -144,14 +144,14 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{}, }, { - Name: toolCfg.Next, + Name: cfgMcpTool.Next, Description: desc.Text( text.DescKeyMCPToolNextDesc), InputSchema: proto.InputSchema{Type: schema.Object}, Annotations: &proto.ToolAnnotations{ReadOnlyHint: true}, }, { - Name: toolCfg.CheckTaskCompletion, + Name: cfgMcpTool.CheckTaskCompletion, Description: desc.Text( text.DescKeyMCPToolCheckTaskDesc), InputSchema: proto.InputSchema{ @@ -167,7 +167,7 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{ReadOnlyHint: true}, }, { - Name: toolCfg.SessionEvent, + Name: cfgMcpTool.SessionEvent, Description: desc.Text( text.DescKeyMCPToolSessionDesc), InputSchema: proto.InputSchema{ @@ -189,7 +189,7 @@ var Defs = []proto.Tool{ Annotations: &proto.ToolAnnotations{}, }, { - Name: toolCfg.Remind, + Name: cfgMcpTool.Remind, Description: desc.Text( text.DescKeyMCPToolRemindDesc), InputSchema: proto.InputSchema{Type: schema.Object}, diff --git a/internal/mcp/server/extract/extract.go b/internal/mcp/server/extract/extract.go index 16ac7e6ad..441503b0f 100644 --- a/internal/mcp/server/extract/extract.go +++ b/internal/mcp/server/extract/extract.go @@ -9,7 +9,7 @@ package extract import ( "github.com/ActiveMemory/ctx/internal/config/cli" "github.com/ActiveMemory/ctx/internal/config/mcp/field" - mcperr "github.com/ActiveMemory/ctx/internal/err/mcp" + errMcp "github.com/ActiveMemory/ctx/internal/err/mcp" "github.com/ActiveMemory/ctx/internal/mcp/handler" ) @@ -29,7 +29,7 @@ func EntryArgs( content, _ := args[field.Content].(string) if entryType == "" || content == "" { - return "", "", mcperr.TypeContentRequired() + return "", "", errMcp.TypeContentRequired() } return entryType, content, nil diff --git a/internal/mcp/server/resource/resource.go b/internal/mcp/server/resource/resource.go index 96f3d4ed1..b6673c4d1 100644 --- a/internal/mcp/server/resource/resource.go +++ b/internal/mcp/server/resource/resource.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/ActiveMemory/ctx/internal/assets/read/desc" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/mcp/mime" "github.com/ActiveMemory/ctx/internal/config/token" @@ -78,7 +78,7 @@ func readAgentPacket( tokensUsed := ctxToken.EstimateTokensString(header) var skipped []string - for _, fileName := range ctxCfg.ReadOrder { + for _, fileName := range cfgCtx.ReadOrder { f := ctx.File(fileName) if f == nil || f.IsEmpty { continue diff --git a/internal/mcp/server/route/prompt/dispatch.go b/internal/mcp/server/route/prompt/dispatch.go index 11c169260..dcce69264 100644 --- a/internal/mcp/server/route/prompt/dispatch.go +++ b/internal/mcp/server/route/prompt/dispatch.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/mcp/prompt" "github.com/ActiveMemory/ctx/internal/mcp/handler" "github.com/ActiveMemory/ctx/internal/mcp/proto" - promptdef "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" + defPrompt "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" "github.com/ActiveMemory/ctx/internal/mcp/server/out" ) @@ -28,7 +28,7 @@ import ( // - *proto.Response: prompt list response func DispatchList(req proto.Request) *proto.Response { return out.OkResponse( - req.ID, proto.PromptListResult{Prompts: promptdef.Defs}, + req.ID, proto.PromptListResult{Prompts: defPrompt.Defs}, ) } diff --git a/internal/mcp/server/route/prompt/entry.go b/internal/mcp/server/route/prompt/entry.go index fabd290f0..891e6a4e1 100644 --- a/internal/mcp/server/route/prompt/entry.go +++ b/internal/mcp/server/route/prompt/entry.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/mcp/prompt" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/mcp/proto" - promptdef "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" + defPrompt "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" "github.com/ActiveMemory/ctx/internal/mcp/server/out" ) @@ -30,7 +30,7 @@ import ( // Returns: // - *proto.Response: formatted entry prompt func buildEntry( - id json.RawMessage, spec promptdef.EntryPromptSpec, + id json.RawMessage, spec defPrompt.EntryPromptSpec, ) *proto.Response { fieldFmt := desc.Text(spec.FieldFmtK) diff --git a/internal/mcp/server/route/prompt/prompt.go b/internal/mcp/server/route/prompt/prompt.go index a906612a8..65e545cb3 100644 --- a/internal/mcp/server/route/prompt/prompt.go +++ b/internal/mcp/server/route/prompt/prompt.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/config/cli" - ctxCfg "github.com/ActiveMemory/ctx/internal/config/ctx" + cfgCtx "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/mcp/field" "github.com/ActiveMemory/ctx/internal/config/mcp/mime" @@ -21,7 +21,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/context/load" "github.com/ActiveMemory/ctx/internal/mcp/proto" - promptdef "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" + defPrompt "github.com/ActiveMemory/ctx/internal/mcp/server/def/prompt" "github.com/ActiveMemory/ctx/internal/mcp/server/out" "github.com/ActiveMemory/ctx/internal/mcp/server/stat" ) @@ -51,7 +51,7 @@ func sessionStart( sb.WriteString(token.NewlineLF) sb.WriteString(token.NewlineLF) - for _, fileName := range ctxCfg.ReadOrder { + for _, fileName := range cfgCtx.ReadOrder { f := ctx.File(fileName) if f == nil || f.IsEmpty { continue @@ -146,12 +146,12 @@ func checkpoint( func addDecision( id json.RawMessage, args map[string]string, ) *proto.Response { - return buildEntry(id, promptdef.EntryPromptSpec{ + return buildEntry(id, defPrompt.EntryPromptSpec{ KeyHeader: text.DescKeyMCPPromptAddDecisionHeader, KeyFooter: text.DescKeyMCPPromptAddDecisionFooter, FieldFmtK: text.DescKeyMCPPromptAddDecisionFieldFmt, KeyResultD: text.DescKeyMCPPromptAddDecisionResultD, - Fields: []promptdef.EntryField{ + Fields: []defPrompt.EntryField{ {KeyLabel: text.DescKeyMCPPromptLabelDecision, Value: args[field.Content]}, {KeyLabel: text.DescKeyMCPPromptLabelContext, @@ -176,12 +176,12 @@ func addDecision( func addLearning( id json.RawMessage, args map[string]string, ) *proto.Response { - return buildEntry(id, promptdef.EntryPromptSpec{ + return buildEntry(id, defPrompt.EntryPromptSpec{ KeyHeader: text.DescKeyMCPPromptAddLearningHeader, KeyFooter: text.DescKeyMCPPromptAddLearningFooter, FieldFmtK: text.DescKeyMCPPromptAddLearningFieldFmt, KeyResultD: text.DescKeyMCPPromptAddLearningResultD, - Fields: []promptdef.EntryField{ + Fields: []defPrompt.EntryField{ {KeyLabel: text.DescKeyMCPPromptLabelLearning, Value: args[field.Content]}, {KeyLabel: text.DescKeyMCPPromptLabelContext, diff --git a/internal/mcp/server/route/tool/dispatch.go b/internal/mcp/server/route/tool/dispatch.go index 76fb5ea32..46bd0f774 100644 --- a/internal/mcp/server/route/tool/dispatch.go +++ b/internal/mcp/server/route/tool/dispatch.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/mcp/tool" "github.com/ActiveMemory/ctx/internal/mcp/handler" "github.com/ActiveMemory/ctx/internal/mcp/proto" - tooldef "github.com/ActiveMemory/ctx/internal/mcp/server/def/tool" + defTool "github.com/ActiveMemory/ctx/internal/mcp/server/def/tool" "github.com/ActiveMemory/ctx/internal/mcp/server/out" ) @@ -27,7 +27,7 @@ import ( // Returns: // - *proto.Response: tool list response func DispatchList(req proto.Request) *proto.Response { - return out.OkResponse(req.ID, proto.ToolListResult{Tools: tooldef.Defs}) + return out.OkResponse(req.ID, proto.ToolListResult{Tools: defTool.Defs}) } // DispatchCall unmarshals tool call params and dispatches to the diff --git a/internal/mcp/server/route/tool/tool.go b/internal/mcp/server/route/tool/tool.go index 12dfe2d7a..e5aa22f52 100644 --- a/internal/mcp/server/route/tool/tool.go +++ b/internal/mcp/server/route/tool/tool.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/mcp/cfg" "github.com/ActiveMemory/ctx/internal/config/mcp/field" - timeCfg "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/mcp/handler" "github.com/ActiveMemory/ctx/internal/mcp/proto" "github.com/ActiveMemory/ctx/internal/mcp/server/extract" @@ -88,7 +88,7 @@ func recall( var since time.Time if sinceStr, _ := args[field.Since].(string); sinceStr != "" { var parseErr error - since, parseErr = time.Parse(timeCfg.DateFormat, sinceStr) + since, parseErr = time.Parse(cfgTime.DateFormat, sinceStr) if parseErr != nil { return out.ToolError( id, fmt.Sprintf( diff --git a/internal/memory/discover.go b/internal/memory/discover.go index 4960abc15..ac96e2bc9 100644 --- a/internal/memory/discover.go +++ b/internal/memory/discover.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/memory" - ctxerr "github.com/ActiveMemory/ctx/internal/err/memory" + errMemory "github.com/ActiveMemory/ctx/internal/err/memory" ) // DiscoverMemoryPath locates Claude Code's auto memory file for the @@ -26,19 +26,19 @@ import ( func DiscoverMemoryPath(projectRoot string) (string, error) { abs, absErr := filepath.Abs(projectRoot) if absErr != nil { - return "", ctxerr.DiscoverResolveRoot(absErr) + return "", errMemory.DiscoverResolveRoot(absErr) } home, homeErr := os.UserHomeDir() if homeErr != nil { - return "", ctxerr.DiscoverResolveHome(homeErr) + return "", errMemory.DiscoverResolveHome(homeErr) } slug := ProjectSlug(abs) memPath := filepath.Join(home, dir.Claude, dir.Projects, slug, dir.Memory, memory.MemorySource) if _, statErr := os.Stat(memPath); statErr != nil { - return "", ctxerr.DiscoverNoMemory(memPath) + return "", errMemory.DiscoverNoMemory(memPath) } return memPath, nil } diff --git a/internal/memory/mirror.go b/internal/memory/mirror.go index 6ac03d3d5..c59d59b05 100644 --- a/internal/memory/mirror.go +++ b/internal/memory/mirror.go @@ -20,9 +20,9 @@ import ( "github.com/ActiveMemory/ctx/internal/config/file" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/memory" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" - ctxerr "github.com/ActiveMemory/ctx/internal/err/memory" + errMemory "github.com/ActiveMemory/ctx/internal/err/memory" "github.com/ActiveMemory/ctx/internal/io" ) @@ -34,7 +34,7 @@ func Sync(contextDir, sourcePath string) (SyncResult, error) { sourceData, readErr := io.SafeReadUserFile(sourcePath) if readErr != nil { - return SyncResult{}, ctxerr.ReadSource(readErr) + return SyncResult{}, errMemory.ReadSource(readErr) } result := SyncResult{ @@ -48,17 +48,17 @@ func Sync(contextDir, sourcePath string) (SyncResult, error) { result.MirrorLines = countLines(existingData) archivePath, archiveErr := Archive(contextDir) if archiveErr != nil { - return SyncResult{}, ctxerr.ArchivePrevious(archiveErr) + return SyncResult{}, errMemory.ArchivePrevious(archiveErr) } result.ArchivedTo = archivePath } if mkErr := os.MkdirAll(mirrorDir, fs.PermExec); mkErr != nil { - return SyncResult{}, ctxerr.CreateDir(mkErr) + return SyncResult{}, errMemory.CreateDir(mkErr) } if writeErr := os.WriteFile(mirrorPath, sourceData, fs.PermFile); writeErr != nil { - return SyncResult{}, ctxerr.WriteMirror(writeErr) + return SyncResult{}, errMemory.WriteMirror(writeErr) } return result, nil @@ -72,18 +72,18 @@ func Archive(contextDir string) (string, error) { data, readErr := io.SafeReadUserFile(mirrorPath) if readErr != nil { - return "", ctxerr.ReadMirrorArchive(readErr) + return "", errMemory.ReadMirrorArchive(readErr) } if mkErr := os.MkdirAll(archiveDir, fs.PermExec); mkErr != nil { - return "", ctxerr.CreateArchiveDir(mkErr) + return "", errMemory.CreateArchiveDir(mkErr) } - ts := time.Now().Format(time2.TimestampCompact) + ts := time.Now().Format(cfgTime.TimestampCompact) archivePath := filepath.Join(archiveDir, memory.PrefixMirror+ts+file.ExtMarkdown) if writeErr := os.WriteFile(archivePath, data, fs.PermFile); writeErr != nil { - return "", ctxerr.WriteArchive(writeErr) + return "", errMemory.WriteArchive(writeErr) } return archivePath, nil @@ -96,12 +96,12 @@ func Diff(contextDir, sourcePath string) (string, error) { mirrorData, mirrorErr := io.SafeReadUserFile(mirrorPath) if mirrorErr != nil { - return "", ctxerr.ReadMirror(mirrorErr) + return "", errMemory.ReadMirror(mirrorErr) } sourceData, sourceErr := io.SafeReadUserFile(sourcePath) if sourceErr != nil { - return "", ctxerr.ReadDiffSource(sourceErr) + return "", errMemory.ReadDiffSource(sourceErr) } if bytes.Equal(mirrorData, sourceData) { diff --git a/internal/memory/promote.go b/internal/memory/promote.go index dc1a6b1b5..58addf5b8 100644 --- a/internal/memory/promote.go +++ b/internal/memory/promote.go @@ -10,7 +10,7 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/read/desc" "github.com/ActiveMemory/ctx/internal/config/embed/text" "github.com/ActiveMemory/ctx/internal/config/entry" - ctxentry "github.com/ActiveMemory/ctx/internal/entry" + ctxEntry "github.com/ActiveMemory/ctx/internal/entry" ) // Promote writes a classified entry to the appropriate .context/ file. @@ -28,7 +28,7 @@ func Promote(e Entry, classification Classification) error { // (first line, trimmed of Markdown markers) title := extractTitle(e.Text) - params := ctxentry.Params{ + params := ctxEntry.Params{ Type: classification.Target, Content: title, } @@ -51,5 +51,5 @@ func Promote(e Entry, classification Classification) error { // Conventions just need content: FormatConvention handles the rest } - return ctxentry.Write(params) + return ctxEntry.Write(params) } diff --git a/internal/memory/publish.go b/internal/memory/publish.go index b25e10901..a75449b78 100644 --- a/internal/memory/publish.go +++ b/internal/memory/publish.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/marker" "github.com/ActiveMemory/ctx/internal/config/memory" "github.com/ActiveMemory/ctx/internal/config/token" - ctxerr "github.com/ActiveMemory/ctx/internal/err/memory" + errMemory "github.com/ActiveMemory/ctx/internal/err/memory" "github.com/ActiveMemory/ctx/internal/io" ) @@ -157,7 +157,7 @@ func RemovePublished(content string) (string, bool) { func Publish(contextDir, memoryPath string, budget int) (PublishResult, error) { result, selectErr := SelectContent(contextDir, budget) if selectErr != nil { - return PublishResult{}, ctxerr.SelectContent(selectErr) + return PublishResult{}, errMemory.SelectContent(selectErr) } formatted := result.Format() @@ -173,7 +173,7 @@ func Publish(contextDir, memoryPath string, budget int) (PublishResult, error) { if writeErr := os.WriteFile( memoryPath, []byte(merged), fs.PermFile, ); writeErr != nil { - return PublishResult{}, ctxerr.WriteMemory(writeErr) + return PublishResult{}, errMemory.WriteMemory(writeErr) } return result, nil diff --git a/internal/memory/publish_test.go b/internal/memory/publish_test.go index 8f5cda105..d0071ff25 100644 --- a/internal/memory/publish_test.go +++ b/internal/memory/publish_test.go @@ -17,8 +17,8 @@ import ( "github.com/ActiveMemory/ctx/internal/config/ctx" "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/marker" - cfgmem "github.com/ActiveMemory/ctx/internal/config/memory" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgMem "github.com/ActiveMemory/ctx/internal/config/memory" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -151,7 +151,7 @@ func TestSelectContent(t *testing.T) { } // Create DECISIONS.md with a recent entry - ts := time.Now().Format(time2.TimestampCompact) + ts := time.Now().Format(cfgTime.TimestampCompact) decisions := fmt.Sprintf("# Decisions\n\n## [%s] Use SQLite\n\nContext: testing\n", ts) if writeErr := os.WriteFile(filepath.Join(contextDir, ctx.Decision), []byte(decisions), 0o644); writeErr != nil { t.Fatal(writeErr) @@ -168,7 +168,7 @@ func TestSelectContent(t *testing.T) { t.Fatal(writeErr) } - result, selectErr := SelectContent(contextDir, cfgmem.DefaultPublishBudget) + result, selectErr := SelectContent(contextDir, cfgMem.DefaultPublishBudget) if selectErr != nil { t.Fatalf("SelectContent: %v", selectErr) } diff --git a/internal/memory/state.go b/internal/memory/state.go index 44acaed0d..fcd7a0cf8 100644 --- a/internal/memory/state.go +++ b/internal/memory/state.go @@ -20,7 +20,7 @@ import ( cfgFmt "github.com/ActiveMemory/ctx/internal/config/format" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/config/memory" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/io" ) @@ -90,7 +90,7 @@ func (s *State) Imported(hash string) bool { // MarkImported records an entry hash with its target and date. func (s *State) MarkImported(hash, target string) { - date := time.Now().Format(time2.DateFormat) + date := time.Now().Format(cfgTime.DateFormat) entry := strings.Join([]string{hash, target, date}, token.Colon) s.ImportedHashes = append(s.ImportedHashes, entry) } diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 69b43094a..faf1b950c 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -18,7 +18,7 @@ import ( "path/filepath" "time" - cryptoCfg "github.com/ActiveMemory/ctx/internal/config/crypto" + cfgCrypto "github.com/ActiveMemory/ctx/internal/config/crypto" "github.com/ActiveMemory/ctx/internal/config/fs" "github.com/ActiveMemory/ctx/internal/crypto" "github.com/ActiveMemory/ctx/internal/io" @@ -35,7 +35,7 @@ import ( // - error: non-nil only if decryption fails (missing files are silent) func LoadWebhook() (string, error) { kp := rc.KeyPath() - encPath := filepath.Join(rc.ContextDir(), cryptoCfg.NotifyEnc) + encPath := filepath.Join(rc.ContextDir(), cfgCrypto.NotifyEnc) key, err := crypto.LoadKey(kp) if err != nil { @@ -72,7 +72,7 @@ func LoadWebhook() (string, error) { // - error: non-nil if key generation, encryption, or file write fails func SaveWebhook(url string) error { kp := rc.KeyPath() - encPath := filepath.Join(rc.ContextDir(), cryptoCfg.NotifyEnc) + encPath := filepath.Join(rc.ContextDir(), cfgCrypto.NotifyEnc) key, err := crypto.LoadKey(kp) if err != nil { diff --git a/internal/parse/date.go b/internal/parse/date.go index b12ff67ec..b90a3733b 100644 --- a/internal/parse/date.go +++ b/internal/parse/date.go @@ -9,7 +9,7 @@ package parse import ( "time" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" ) // Date parses a YYYY-MM-DD string into a time.Time at midnight UTC. @@ -25,5 +25,5 @@ func Date(s string) (time.Time, error) { if s == "" { return time.Time{}, nil } - return time.Parse(time2.DateFormat, s) + return time.Parse(cfgTime.DateFormat, s) } diff --git a/internal/recall/parser/claude.go b/internal/recall/parser/claude.go index 2d8475516..8aa06ee5b 100644 --- a/internal/recall/parser/claude.go +++ b/internal/recall/parser/claude.go @@ -19,7 +19,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/parser" "github.com/ActiveMemory/ctx/internal/config/session" "github.com/ActiveMemory/ctx/internal/entity" - ctxerr "github.com/ActiveMemory/ctx/internal/err/parser" + errParser "github.com/ActiveMemory/ctx/internal/err/parser" ) // ClaudeCodeParser parses Claude Code JSONL session files. @@ -108,7 +108,7 @@ func (p *ClaudeCodeParser) Matches(path string) bool { func (p *ClaudeCodeParser) ParseFile(path string) ([]*entity.Session, error) { f, openErr := os.Open(filepath.Clean(path)) if openErr != nil { - return nil, ctxerr.OpenFile(openErr) + return nil, errParser.OpenFile(openErr) } defer func() { _ = f.Close() }() @@ -147,7 +147,7 @@ func (p *ClaudeCodeParser) ParseFile(path string) ([]*entity.Session, error) { } if scanErr := scanner.Err(); scanErr != nil { - return nil, ctxerr.ScanFile(scanErr) + return nil, errParser.ScanFile(scanErr) } // Convert to sessions @@ -187,7 +187,7 @@ func (p *ClaudeCodeParser) ParseLine(line []byte) (*entity.Message, string, erro var raw claudeRawMessage if unmarshalErr := json.Unmarshal(line, &raw); unmarshalErr != nil { - return nil, "", ctxerr.Unmarshal(unmarshalErr) + return nil, "", errParser.Unmarshal(unmarshalErr) } // Skip non-message lines diff --git a/internal/recall/parser/markdown.go b/internal/recall/parser/markdown.go index fbb55806c..68a5d3e86 100644 --- a/internal/recall/parser/markdown.go +++ b/internal/recall/parser/markdown.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/parser" "github.com/ActiveMemory/ctx/internal/config/session" "github.com/ActiveMemory/ctx/internal/entity" - ctxerr "github.com/ActiveMemory/ctx/internal/err/parser" + errParser "github.com/ActiveMemory/ctx/internal/err/parser" ) // MarkdownSessionParser parses Markdown session files written by AI agents. @@ -110,7 +110,7 @@ func (p *MarkdownSessionParser) Matches(path string) bool { func (p *MarkdownSessionParser) ParseFile(path string) ([]*entity.Session, error) { content, err := os.ReadFile(filepath.Clean(path)) if err != nil { - return nil, ctxerr.ReadFile(err) + return nil, errParser.ReadFile(err) } s := p.parseMarkdownSession(string(content), path) diff --git a/internal/recall/parser/parser.go b/internal/recall/parser/parser.go index 2c1015e1a..e9db58672 100644 --- a/internal/recall/parser/parser.go +++ b/internal/recall/parser/parser.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/parser" "github.com/ActiveMemory/ctx/internal/entity" - ctxerr "github.com/ActiveMemory/ctx/internal/err/parser" + errParser "github.com/ActiveMemory/ctx/internal/err/parser" ) // registeredParsers holds all available session parsers. @@ -40,7 +40,7 @@ func ParseFile(path string) ([]*entity.Session, error) { return parser.ParseFile(path) } } - return nil, ctxerr.NoMatch(path) + return nil, errParser.NoMatch(path) } // ScanDirectory recursively scans a directory for session files. @@ -103,7 +103,7 @@ func ScanDirectoryWithErrors(dir string) ([]*entity.Session, []error, error) { if parser.Matches(path) { sessions, err := parser.ParseFile(path) if err != nil { - parseErrors = append(parseErrors, ctxerr.FileError(path, err)) + parseErrors = append(parseErrors, errParser.FileError(path, err)) break } allSessions = append(allSessions, sessions...) @@ -115,7 +115,7 @@ func ScanDirectoryWithErrors(dir string) ([]*entity.Session, []error, error) { }) if err != nil { - return nil, nil, ctxerr.WalkDir(err) + return nil, nil, errParser.WalkDir(err) } // Sort by start time (newest first) diff --git a/internal/tidy/archive.go b/internal/tidy/archive.go index 6fd64cd55..3f2421897 100644 --- a/internal/tidy/archive.go +++ b/internal/tidy/archive.go @@ -15,9 +15,9 @@ import ( "github.com/ActiveMemory/ctx/internal/config/archive" "github.com/ActiveMemory/ctx/internal/config/dir" "github.com/ActiveMemory/ctx/internal/config/fs" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" - ctxerr "github.com/ActiveMemory/ctx/internal/err/backup" + errBackup "github.com/ActiveMemory/ctx/internal/err/backup" "github.com/ActiveMemory/ctx/internal/rc" ) @@ -35,11 +35,11 @@ import ( func WriteArchive(prefix, heading, content string) (string, error) { archiveDir := filepath.Join(rc.ContextDir(), dir.Archive) if mkErr := os.MkdirAll(archiveDir, fs.PermExec); mkErr != nil { - return "", ctxerr.CreateArchiveDir(mkErr) + return "", errBackup.CreateArchiveDir(mkErr) } now := time.Now() - dateStr := now.Format(time2.DateFormat) + dateStr := now.Format(cfgTime.DateFormat) archiveFile := filepath.Join( archiveDir, fmt.Sprintf(archive.TplArchiveFilename, prefix, dateStr), @@ -55,7 +55,7 @@ func WriteArchive(prefix, heading, content string) (string, error) { } if writeErr := os.WriteFile(archiveFile, []byte(finalContent), fs.PermFile); writeErr != nil { - return "", ctxerr.WriteArchive(writeErr) + return "", errBackup.WriteArchive(writeErr) } return archiveFile, nil diff --git a/internal/tidy/parse.go b/internal/tidy/parse.go index e0f8cc48e..82346a265 100644 --- a/internal/tidy/parse.go +++ b/internal/tidy/parse.go @@ -11,7 +11,7 @@ import ( "time" "github.com/ActiveMemory/ctx/internal/config/regex" - time2 "github.com/ActiveMemory/ctx/internal/config/time" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/task" @@ -111,7 +111,7 @@ func parseDoneTimestamp(line string) *time.Time { } // Parse YYYY-MM-DD-HHMMSS format - t, err := time.Parse(time2.TimestampCompact, match[1]) + t, err := time.Parse(cfgTime.TimestampCompact, match[1]) if err != nil { return nil } diff --git a/internal/validation/path.go b/internal/validation/path.go index bb9f12e9c..a507b0476 100644 --- a/internal/validation/path.go +++ b/internal/validation/path.go @@ -11,8 +11,8 @@ import ( "path/filepath" "strings" - errctx "github.com/ActiveMemory/ctx/internal/err/context" - fserr "github.com/ActiveMemory/ctx/internal/err/fs" + errCtx "github.com/ActiveMemory/ctx/internal/err/context" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" ) // ValidateBoundary checks that dir resolves to a path within the current @@ -21,19 +21,19 @@ import ( func ValidateBoundary(dir string) error { cwd, err := os.Getwd() if err != nil { - return fserr.BoundaryViolation(err) + return errFs.BoundaryViolation(err) } absDir, err := filepath.Abs(dir) if err != nil { - return fserr.BoundaryViolation(err) + return errFs.BoundaryViolation(err) } // Resolve symlinks in both paths so traversal via symlinked parents // is caught. resolvedCwd, err := filepath.EvalSymlinks(cwd) if err != nil { - return fserr.BoundaryViolation(err) + return errFs.BoundaryViolation(err) } resolvedDir, err := filepath.EvalSymlinks(absDir) @@ -47,7 +47,7 @@ func ValidateBoundary(dir string) error { // Append os.PathSeparator to avoid "/foo/bar" matching "/foo/b". root := resolvedCwd + string(os.PathSeparator) if resolvedDir != resolvedCwd && !strings.HasPrefix(resolvedDir, root) { - return errctx.OutsideRoot(dir, resolvedCwd) + return errCtx.OutsideRoot(dir, resolvedCwd) } return nil @@ -63,7 +63,7 @@ func CheckSymlinks(dir string) error { return nil } if info.Mode()&os.ModeSymlink != 0 { - return errctx.DirSymlink(dir) + return errCtx.DirSymlink(dir) } // Check immediate children. @@ -79,7 +79,7 @@ func CheckSymlinks(dir string) error { continue } if ci.Mode()&os.ModeSymlink != 0 { - return errctx.FileSymlink(child) + return errCtx.FileSymlink(child) } } diff --git a/site/404.html b/site/404.html index 8fa94ad68..585d61db2 100644 --- a/site/404.html +++ b/site/404.html @@ -3084,6 +3084,8 @@ + + @@ -3239,6 +3241,36 @@ +
  • + + + + + + + + + + Cutting a 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 444c3ab3f..044d6015b 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 @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/index.html b/site/blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/index.html index c7af0af92..831153e98 100644 --- a/site/blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/index.html +++ b/site/blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-01-refactoring-with-intent/index.html b/site/blog/2026-02-01-refactoring-with-intent/index.html index 0958ad429..01588ec56 100644 --- a/site/blog/2026-02-01-refactoring-with-intent/index.html +++ b/site/blog/2026-02-01-refactoring-with-intent/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-03-the-attention-budget/index.html b/site/blog/2026-02-03-the-attention-budget/index.html index a4db41a5e..65c9d243b 100644 --- a/site/blog/2026-02-03-the-attention-budget/index.html +++ b/site/blog/2026-02-03-the-attention-budget/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-04-skills-that-fight-the-platform/index.html b/site/blog/2026-02-04-skills-that-fight-the-platform/index.html index 40c8325c9..1d0b1ead1 100644 --- a/site/blog/2026-02-04-skills-that-fight-the-platform/index.html +++ b/site/blog/2026-02-04-skills-that-fight-the-platform/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-05-you-cant-import-expertise/index.html b/site/blog/2026-02-05-you-cant-import-expertise/index.html index 3b6c5584e..2f3673fda 100644 --- a/site/blog/2026-02-05-you-cant-import-expertise/index.html +++ b/site/blog/2026-02-05-you-cant-import-expertise/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-07-the-anatomy-of-a-skill-that-works/index.html b/site/blog/2026-02-07-the-anatomy-of-a-skill-that-works/index.html index e0a28e451..ed389faf2 100644 --- a/site/blog/2026-02-07-the-anatomy-of-a-skill-that-works/index.html +++ b/site/blog/2026-02-07-the-anatomy-of-a-skill-that-works/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-08-not-everything-is-a-skill/index.html b/site/blog/2026-02-08-not-everything-is-a-skill/index.html index e9150d940..79fc36818 100644 --- a/site/blog/2026-02-08-not-everything-is-a-skill/index.html +++ b/site/blog/2026-02-08-not-everything-is-a-skill/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-09-defense-in-depth-securing-ai-agents/index.html b/site/blog/2026-02-09-defense-in-depth-securing-ai-agents/index.html index 207dc93e1..22af96cd8 100644 --- a/site/blog/2026-02-09-defense-in-depth-securing-ai-agents/index.html +++ b/site/blog/2026-02-09-defense-in-depth-securing-ai-agents/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-12-how-deep-is-too-deep/index.html b/site/blog/2026-02-12-how-deep-is-too-deep/index.html index 311b8099e..3cdb2e365 100644 --- a/site/blog/2026-02-12-how-deep-is-too-deep/index.html +++ b/site/blog/2026-02-12-how-deep-is-too-deep/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-14-irc-as-context/index.html b/site/blog/2026-02-14-irc-as-context/index.html index b2d5387e8..bbc1e2c63 100644 --- a/site/blog/2026-02-14-irc-as-context/index.html +++ b/site/blog/2026-02-14-irc-as-context/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-14-parallel-agents-with-worktrees/index.html b/site/blog/2026-02-14-parallel-agents-with-worktrees/index.html index d6b015eb5..c0d87a49d 100644 --- a/site/blog/2026-02-14-parallel-agents-with-worktrees/index.html +++ b/site/blog/2026-02-14-parallel-agents-with-worktrees/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-15-ctx-v0.3.0-the-discipline-release/index.html b/site/blog/2026-02-15-ctx-v0.3.0-the-discipline-release/index.html index 342db2c17..715c1c4d5 100644 --- a/site/blog/2026-02-15-ctx-v0.3.0-the-discipline-release/index.html +++ b/site/blog/2026-02-15-ctx-v0.3.0-the-discipline-release/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-15-eight-ways-a-hook-can-talk/index.html b/site/blog/2026-02-15-eight-ways-a-hook-can-talk/index.html index 082d2985a..76959dcf1 100644 --- a/site/blog/2026-02-15-eight-ways-a-hook-can-talk/index.html +++ b/site/blog/2026-02-15-eight-ways-a-hook-can-talk/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-15-why-zensical/index.html b/site/blog/2026-02-15-why-zensical/index.html index 6bd8aab0c..4fd51636f 100644 --- a/site/blog/2026-02-15-why-zensical/index.html +++ b/site/blog/2026-02-15-why-zensical/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-16-ctx-v0.6.0-the-integration-release/index.html b/site/blog/2026-02-16-ctx-v0.6.0-the-integration-release/index.html index e483c7dd9..4e477df63 100644 --- a/site/blog/2026-02-16-ctx-v0.6.0-the-integration-release/index.html +++ b/site/blog/2026-02-16-ctx-v0.6.0-the-integration-release/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html b/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html index 008ed859c..42fdbb2f5 100644 --- a/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html +++ b/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-17-context-as-infrastructure/index.html b/site/blog/2026-02-17-context-as-infrastructure/index.html index 84f8aa358..f134d4693 100644 --- a/site/blog/2026-02-17-context-as-infrastructure/index.html +++ b/site/blog/2026-02-17-context-as-infrastructure/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html b/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html index 387742805..b690a6008 100644 --- a/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html +++ b/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-17-the-3-1-ratio/index.html b/site/blog/2026-02-17-the-3-1-ratio/index.html index e7fa36892..2e786d4ca 100644 --- a/site/blog/2026-02-17-the-3-1-ratio/index.html +++ b/site/blog/2026-02-17-the-3-1-ratio/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-17-when-a-system-starts-explaining-itself/index.html b/site/blog/2026-02-17-when-a-system-starts-explaining-itself/index.html index a5e05476f..680d96b7a 100644 --- a/site/blog/2026-02-17-when-a-system-starts-explaining-itself/index.html +++ b/site/blog/2026-02-17-when-a-system-starts-explaining-itself/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-25-the-homework-problem/index.html b/site/blog/2026-02-25-the-homework-problem/index.html index aea96d921..0dc93778d 100644 --- a/site/blog/2026-02-25-the-homework-problem/index.html +++ b/site/blog/2026-02-25-the-homework-problem/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-02-28-the-last-question/index.html b/site/blog/2026-02-28-the-last-question/index.html index 341831016..95e927378 100644 --- a/site/blog/2026-02-28-the-last-question/index.html +++ b/site/blog/2026-02-28-the-last-question/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/2026-03-04-agent-memory-is-infrastructure/index.html b/site/blog/2026-03-04-agent-memory-is-infrastructure/index.html index 3ed01c9ef..99923a24e 100644 --- a/site/blog/2026-03-04-agent-memory-is-infrastructure/index.html +++ b/site/blog/2026-03-04-agent-memory-is-infrastructure/index.html @@ -3091,6 +3091,8 @@ + + @@ -3246,6 +3248,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/blog/index.html b/site/blog/index.html index c66e2cd36..794c033f6 100644 --- a/site/blog/index.html +++ b/site/blog/index.html @@ -3102,6 +3102,8 @@ + + @@ -3257,6 +3259,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/config/index.html b/site/cli/config/index.html index 97fda08fa..fa3af6416 100644 --- a/site/cli/config/index.html +++ b/site/cli/config/index.html @@ -3187,6 +3187,8 @@ + + @@ -3342,6 +3344,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/context/index.html b/site/cli/context/index.html index 03d303e9f..ab3d85702 100644 --- a/site/cli/context/index.html +++ b/site/cli/context/index.html @@ -3348,6 +3348,8 @@ + + @@ -3503,6 +3505,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/doctor/index.html b/site/cli/doctor/index.html index c4bc72f3b..6a9ddd28e 100644 --- a/site/cli/doctor/index.html +++ b/site/cli/doctor/index.html @@ -3231,6 +3231,8 @@ + + @@ -3386,6 +3388,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/index.html b/site/cli/index.html index 03c036e52..d98063422 100644 --- a/site/cli/index.html +++ b/site/cli/index.html @@ -3102,6 +3102,8 @@ + + @@ -3257,6 +3259,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/init-status/index.html b/site/cli/init-status/index.html index 3d04f8369..aabb2e692 100644 --- a/site/cli/init-status/index.html +++ b/site/cli/init-status/index.html @@ -3192,6 +3192,8 @@ + + @@ -3347,6 +3349,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/mcp/index.html b/site/cli/mcp/index.html index 5b202b6e9..193218e55 100644 --- a/site/cli/mcp/index.html +++ b/site/cli/mcp/index.html @@ -3464,6 +3464,8 @@ + + @@ -3619,6 +3621,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/recall/index.html b/site/cli/recall/index.html index 1b034edd3..4729c45de 100644 --- a/site/cli/recall/index.html +++ b/site/cli/recall/index.html @@ -3281,6 +3281,8 @@ + + @@ -3436,6 +3438,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/system/index.html b/site/cli/system/index.html index e60fc959d..8f5cdd40b 100644 --- a/site/cli/system/index.html +++ b/site/cli/system/index.html @@ -3264,6 +3264,8 @@ + + @@ -3419,6 +3421,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/cli/tools/index.html b/site/cli/tools/index.html index 3ff4195b1..93ca89a50 100644 --- a/site/cli/tools/index.html +++ b/site/cli/tools/index.html @@ -3663,6 +3663,8 @@ + + @@ -3818,6 +3820,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • @@ -5538,7 +5570,7 @@

    ctx prompt rmPrompt Templates

    +

    See also: Prompt Templates


    ctx remind

    Session-scoped reminders that surface at session start. Reminders are diff --git a/site/home/about/index.html b/site/home/about/index.html index 0f4186fa4..3d26d7dca 100644 --- a/site/home/about/index.html +++ b/site/home/about/index.html @@ -3198,6 +3198,8 @@ + + @@ -3353,6 +3355,36 @@ +

  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • @@ -3796,7 +3828,7 @@

    How ctx Solves ThisJoin the Community →: ask questions, share workflows, and help shape what comes next

  • -
  • Read the Blog →: real-world patterns, ponderings, and lessons learned from building ctx using ctx
  • +
  • Read the Blog →: real-world patterns, ponderings, and lessons learned from building ctx using ctx

  • Ready to Get Started?

    diff --git a/site/home/common-workflows/index.html b/site/home/common-workflows/index.html index 2a2ce1f09..03157f591 100644 --- a/site/home/common-workflows/index.html +++ b/site/home/common-workflows/index.html @@ -3336,6 +3336,8 @@ + + @@ -3491,6 +3493,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/community/index.html b/site/home/community/index.html index e53149158..09f4c148d 100644 --- a/site/home/community/index.html +++ b/site/home/community/index.html @@ -3192,6 +3192,8 @@ + + @@ -3347,6 +3349,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/configuration/index.html b/site/home/configuration/index.html index 3ba5ffb03..5efd694b4 100644 --- a/site/home/configuration/index.html +++ b/site/home/configuration/index.html @@ -3392,6 +3392,8 @@ + + @@ -3547,6 +3549,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/context-files/index.html b/site/home/context-files/index.html index ba0a3aa0c..2016fe092 100644 --- a/site/home/context-files/index.html +++ b/site/home/context-files/index.html @@ -3587,6 +3587,8 @@ + + @@ -3742,6 +3744,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/contributing/index.html b/site/home/contributing/index.html index 1c003abcb..d0ba4ba14 100644 --- a/site/home/contributing/index.html +++ b/site/home/contributing/index.html @@ -3520,6 +3520,8 @@ + + @@ -3675,6 +3677,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • @@ -4410,7 +4442,7 @@

    Skills: Two Directories, One Rule internal/assets/claude/skills/ -The 29 ctx-* skills that ship with the plugin +The 39 ctx-* skills that ship with the plugin Yes @@ -4486,7 +4518,7 @@

    Adding a New CLI CommandAdd a section to the appropriate CLI doc page in docs/cli/.

  • Pattern to follow: internal/cli/pad/pad.go (parent with subcommands) or -internal/cli/complete/complete.go (single command).

    +internal/cli/drift/drift.go (single command).

    Adding a New Session Parser

    The recall system uses a SessionParser interface. To add support for a new AI tool (e.g. Aider, Cursor):

    diff --git a/site/home/faq/index.html b/site/home/faq/index.html index 81ee2b96a..97108e826 100644 --- a/site/home/faq/index.html +++ b/site/home/faq/index.html @@ -3247,6 +3247,8 @@ + + @@ -3402,6 +3404,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/first-session/index.html b/site/home/first-session/index.html index 6870e67b3..3903d495a 100644 --- a/site/home/first-session/index.html +++ b/site/home/first-session/index.html @@ -3225,6 +3225,8 @@ + + @@ -3380,6 +3382,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/getting-started/index.html b/site/home/getting-started/index.html index cacb12a0c..970df37d4 100644 --- a/site/home/getting-started/index.html +++ b/site/home/getting-started/index.html @@ -3276,6 +3276,8 @@ + + @@ -3431,6 +3433,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/index.html b/site/home/index.html index 81f66ad7d..a41ba91da 100644 --- a/site/home/index.html +++ b/site/home/index.html @@ -3102,6 +3102,8 @@ + + @@ -3257,6 +3259,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/is-ctx-right/index.html b/site/home/is-ctx-right/index.html index 18a4f48eb..359417fd2 100644 --- a/site/home/is-ctx-right/index.html +++ b/site/home/is-ctx-right/index.html @@ -3231,6 +3231,8 @@ + + @@ -3386,6 +3388,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/joining-a-project/index.html b/site/home/joining-a-project/index.html index 042a69c41..9aa091ef9 100644 --- a/site/home/joining-a-project/index.html +++ b/site/home/joining-a-project/index.html @@ -3214,6 +3214,8 @@ + + @@ -3369,6 +3371,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/keeping-ai-honest/index.html b/site/home/keeping-ai-honest/index.html index 5f50d54da..ac4923cf7 100644 --- a/site/home/keeping-ai-honest/index.html +++ b/site/home/keeping-ai-honest/index.html @@ -3236,6 +3236,8 @@ + + @@ -3391,6 +3393,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/prompting-guide/index.html b/site/home/prompting-guide/index.html index 27e30f75a..f49f9697f 100644 --- a/site/home/prompting-guide/index.html +++ b/site/home/prompting-guide/index.html @@ -3730,6 +3730,8 @@ + + @@ -3885,6 +3887,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/home/repeated-mistakes/index.html b/site/home/repeated-mistakes/index.html index dd39d0783..d1bfc6524 100644 --- a/site/home/repeated-mistakes/index.html +++ b/site/home/repeated-mistakes/index.html @@ -3242,6 +3242,8 @@ + + @@ -3397,6 +3399,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/index.html b/site/index.html index 624ef1152..607ea3785 100644 --- a/site/index.html +++ b/site/index.html @@ -3310,6 +3310,8 @@ + + @@ -3465,6 +3467,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • diff --git a/site/operations/autonomous-loop/index.html b/site/operations/autonomous-loop/index.html index 4c569a528..dcf3ca171 100644 --- a/site/operations/autonomous-loop/index.html +++ b/site/operations/autonomous-loop/index.html @@ -15,7 +15,7 @@ - + @@ -3099,6 +3099,8 @@ + + @@ -3255,6 +3257,36 @@ + + +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + @@ -4496,7 +4528,7 @@

    Resources - + diff --git a/site/operations/index.html b/site/operations/index.html index a05b25392..6adeaafb4 100644 --- a/site/operations/index.html +++ b/site/operations/index.html @@ -3099,6 +3099,8 @@ + + @@ -3257,6 +3259,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • @@ -3484,6 +3516,17 @@ +
  • + +
  • + + + + Cutting a Release + + + +
  • @@ -3581,6 +3624,10 @@

    AI ToolsConfigure ctx with Claude Code, Cursor, Aider, Copilot, Windsurf, and other AI coding tools.


    +

    Cutting a Release

    +

    Step-by-step runbook for maintainers: bump version, generate +release notes, run the release script, and verify the result.

    +

    Autonomous Loops

    Run an unattended AI agent that works through tasks overnight, with ctx providing persistent memory between iterations.

    diff --git a/site/operations/integrations/index.html b/site/operations/integrations/index.html index ac4c7a093..3f90cc248 100644 --- a/site/operations/integrations/index.html +++ b/site/operations/integrations/index.html @@ -18,7 +18,7 @@ - + @@ -3099,6 +3099,8 @@ + + @@ -3940,6 +3942,36 @@ +
  • + + + + + + + + + + Cutting a Release + + + + + + + + +
  • + + + + + + + + + +
  • @@ -5781,13 +5813,13 @@

    Further Reading +