Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 4 additions & 3 deletions .context/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,10 @@ Six defense layers (innermost to outermost):
### Layered Package Taxonomy

Every CLI package follows `cmd/root + core/` taxonomy (Decision
2026-03-06). `cmd/root/cmd.go` defines the Cobra command;
`cmd/root/run.go` implements the handler. Shared logic lives in
`core/`. Grouping commands use `internal/cli/parent.Cmd()` factory.
2026-03-06). Each feature's `cmd/root/cmd.go` defines the Cobra
command; `cmd/root/run.go` implements the handler. Shared logic
lives in `core/`. Grouping commands use `internal/cli/parent.Cmd()`
factory.

### Config Explosion

Expand Down
323 changes: 102 additions & 221 deletions .context/DECISIONS.md

Large diffs are not rendered by default.

402 changes: 105 additions & 297 deletions .context/LEARNINGS.md

Large diffs are not rendered by default.

71 changes: 9 additions & 62 deletions .context/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ TASK STATUS LABELS:
`#in-progress`: currently being worked on (add inline, don't move task)
-->

### Misc

- [ ] gitnexus analyze --embeddings --skill : we need a make target, but also this
butchers AGENTS.md and CLAUDE.md so those files need to be reverted and
GITNEXUS.md should be updated accordingly. The make target should remind
the user about it.

- [ ] SMB mount path support: add `CTX_BACKUP_SMB_MOUNT_PATH` env var so `ctx backup` can use fstab/systemd automounts instead of requiring GVFS. Spec: specs/smb-mount-path-support.md #priority:medium #added:2026-04-04-010000

### Architecture Docs

- [ ] Publish architecture docs to docs/: copy ARCHITECTURE.md, DETAILED_DESIGN domain files, and CHEAT-SHEETS.md to docs/reference/. Sanitize intervention points into docs/contributing/. Exclude DANGER-ZONES.md and ARCHITECTURE-PRINCIPAL.md (internal only). Spec: specs/publish-architecture-docs.md #priority:medium #added:2026-04-03-150000
Expand All @@ -33,7 +42,6 @@ TASK STATUS LABELS:

### Code Cleanup Findings

- [x] Add TestTypeFileConvention audit check: type definitions must live in types.go, not mixed into function files. Scan all non-test .go files for ast.TypeSpec declarations; flag any that appear in files not named types.go. Migrate violations. #priority:medium #added:2026-04-03-030033 #done:2026-04-03

- [ ] Extend flagbind helpers (IntFlag, DurationFlag, DurationFlagP, StringP, BoolP) and migrate ~50 call sites to unblock TestNoFlagBindOutsideFlagbind #added:2026-04-01-233250

Expand All @@ -45,21 +53,12 @@ TASK STATUS LABELS:
and types from cmd/ directories to core/. See grandfathered map in
compliance_test.go for the full list. #priority:medium #added:2026-03-31-005115

- [x] Collect all exec.Commands under internal/exec. See
Phase EXEC below for breakdown. — done, exec/{git,dep,gio,zensical}
exist, no exec.Command calls remain outside internal/exec
#done:2026-03-31

- [ ] PD.4.5: Update AGENT_PLAYBOOK.md — add generic "check available skills"
instruction #priority:medium #added:2026-03-25-203340

**PD.5 — Validate:**

- [x] PD.5.2: Run `ctx init` on a clean directory — verify no
`.context/prompts/` created. loop.md and skills checks are stale:
loop.md was never a ctx init artifact (ctx loop generates on demand),
skills deploy via plugin install, not ctx init.
#priority:high #added:2026-03-25-203340 #done:2026-03-31

### Phase -3: DevEx

Expand Down Expand Up @@ -92,15 +91,6 @@ TASK STATUS LABELS:
.ctxrc or settings.json. Related: consolidation nudge hook
spec. #added:2026-03-23-223500

- [x] Bug: check-version hook missing throttle touch on plugin
version read error (run.go:70). When claude.PluginVersion()
fails, the hook returns without touching the daily throttle
marker, causing repeated checks on days when plugin.json is
missing or corrupted. Fix: add
internalIo.TouchFile(markerFile) before the early return.
See docs/recipes/hook-sequence-diagrams.md check-version
diagram which documents the expected behavior.
#added:2026-03-23-162802 #done:2026-03-31

- [ ] Design UserPromptSubmit hook that runs go build and
surfaces compilation errors before the agent acts on stale
Expand Down Expand Up @@ -305,9 +295,6 @@ P0.4.10 task.
one file; types need to go to types.go per convention etc etc)
* Human: split err package into sub packages.

- [x] Add Use* constants for all system subcommands — all 30 system
subcommands already use cmd.UseSystem* constants
#added:2026-03-21-092550 #done:2026-03-31

- [ ] Refactor site/cmd/feed: extract helpers and types to core/, make Run
public #added:2026-03-21-074859
Expand Down Expand Up @@ -453,15 +440,6 @@ Many call sites use `_ =` or `_, _ =` to discard errors without
any feedback. Some are legitimate (best-effort cleanup), most are
lazy escapes that hide failures.

- [x] EH.0: Create central warning sink — `internal/log/warn/warn.go` with
`var sink io.Writer = os.Stderr` and `func Warn(format string,
args ...any)`.
All stderr warnings (`fmt.Fprintf(os.Stderr, ...)`) route through this
function. The `fmt.Fprintf` return error is handled once, centrally.
The sink is swappable (tests use `io.Discard`, future: syslog, file).
EH.2–EH.4 should use `log.Warn()` instead of raw `fmt.Fprintf`.
DoD: `grep -rn 'fmt.Fprintf(os.Stderr' internal/` returns zero hits
#priority:high #added:2026-03-15

- [ ] EH.1: Catalogue all silent error discards — recursive walk of `internal/`
for patterns: `_ = `, `_, _ = `, `//nolint:errcheck`, bare `return` after
Expand Down Expand Up @@ -596,34 +574,11 @@ Taxonomy (from prefix analysis):
state.go to types.go — file and type no longer exist, refactored away
#priority:low #added:2026-03-07-220825

- [x] Cleanup internal/cli/system/core/wrapup.go: line 18 constant should go to
config; make WrappedUpExpiry configurable via ctxrc — already done,
wrap.ExpiryHours and wrap.Marker exist in config/wrap
#priority:low #added:2026-03-07-220825 #done:2026-03-31

- [x] Cleanup internal/cli/system/core/version.go: line 81 newline should come
from config — already done, uses token.NewlineLF
#priority:low #added:2026-03-07-220819 #done:2026-03-31

- [x] Add taxonomy to internal/cli/system/core/ — currently an unstructured bag
of files; group by domain (backup, hooks, session, knowledge, etc.)
— already done, 20 domain subdirectories exist
#priority:medium #added:2026-03-07-220819 #done:2026-03-31

- [x] Cleanup internal/cli/system/core/version_drift.go: line 53 string
formatting should use assets — file moved to core/drift/, now uses
desc.Text and assets throughout
#priority:medium #added:2026-03-07-220819 #done:2026-03-31

- [x] Cleanup internal/cli/system/core/state.go: magic permissions (0o750),
magic strings ('Context: ' prefix, etc.) — file moved to core/state/,
magic values extracted to config
#priority:medium #added:2026-03-07-220819 #done:2026-03-31

- [x] Cleanup internal/cli/system/core/smb.go: errors should come from
internal/err; lines 101, 116, 111 need assets text — file moved to
core/archive/, errors routed through err package
#priority:medium #added:2026-03-07-220819 #done:2026-03-31

- [ ] Make AutoPruneStaleDays configurable via ctxrc. Currently hardcoded to 7
days in config.AutoPruneStaleDays; add a ctxrc key (e.g., auto_prune_days) and
Expand Down Expand Up @@ -714,19 +669,11 @@ Taxonomy (from prefix analysis):
tables) #added:2026-03-06-190225


- [x] Remove FlagNoColor and fatih/color dependency — replaced with plain
output, dependency removed from go.mod
#added:2026-03-06-182831 #done:2026-03-31

- [ ] Validate .ctxrc against ctxrc.schema.json at load time — schema is
embedded but never enforced, doctor does field-level checks without using
it #added:2026-03-06-174851

- [x] Fix 3 CI compliance issues from PR #27 after merge: missing copyright
header on internal/mcp/server_test.go, missing doc.go for internal/cli/mcp/,
literal newlines in internal/mcp/resources.go and
tools.go — all fixed, files moved to mcp/server/ with copyright
#added:2026-03-06-141508 #done:2026-03-31

- [ ] Add PostToolUse session event capture. Append lightweight event records
(tool name, files touched, timestamp) to .context/state/session-events.jsonl
Expand Down
229 changes: 229 additions & 0 deletions .context/archive/decisions-consolidated-2026-04-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# Archived Decisions (consolidated 2026-04-03)

Originals replaced by consolidated entries in DECISIONS.md.

## Group: Output/write location

## [2026-03-22-084509] No runtime pluralization — use singular/plural text key pairs

**Status**: Accepted

**Context**: Hardcoded English plural rules (+ s, y → ies) were scattered across format.Pluralize, padPluralize, and inline code — all i18n dead-ends

**Decision**: No runtime pluralization — use singular/plural text key pairs

**Rationale**: Different languages have vastly different plural rules. Complete sentence templates with embedded counts (time.minute-count '1 minute', time.minutes-count '%d minutes') let each locale define its own plural forms.

**Consequence**: format.Pluralize and format.PluralWord are deleted. All plural output uses paired text keys with the count embedded in the template.

---

## [2026-03-21-084020] Output functions belong in write/, logic and types in core/

**Status**: Accepted

**Context**: PrintFeedReport was initially placed in cli/site/core/ but it calls cmd.Println — that's output formatting, not business logic

**Decision**: Output functions belong in write/, logic and types in core/

**Rationale**: The project taxonomy separates concerns: core/ owns domain logic, types, and helpers; write/ owns CLI output formatting that takes *cobra.Command for Println. Mixing them blurs the boundary and makes testing harder.

**Consequence**: All functions that call cmd.Print/Println/Printf belong in the write/ package tree. core/ never imports cobra for output purposes.

---


## Group: YAML text externalization

## [2026-04-03-133236] YAML text externalization justification is agent legibility, not i18n

**Status**: Accepted

**Context**: Principal analysis initially framed 879-key YAML externalization as a bet on i18n. Blog post review (v0.8.0) revealed the real justification: agent legibility (named DescKey constants as traversable graphs), drift prevention (TestDescKeyYAMLLinkage catches orphans mechanically), and completing the archaeology of finding all 879 scattered strings.

**Decision**: YAML text externalization justification is agent legibility, not i18n

**Rationale**: The v0.8.0 blog makes it explicit: finding strings is the hard part, translation is mechanical. The externalization already pays for itself through agent legibility and mechanical verification. i18n is a free downstream consequence, not the justification.

**Consequence**: Future architecture analysis should frame externalization as already-justified investment. The 3-file ceremony (DescKey + YAML + write/err function) is the cost of agent-legible, drift-proof output — not speculative i18n prep.

---

## [2026-03-15-101336] TextDescKey exhaustive test verifies all 879 constants resolve to non-empty YAML values

**Status**: Accepted

**Context**: PR #42 merged with ~80 new MCP text keys but no test coverage for key-to-YAML mapping

**Decision**: TextDescKey exhaustive test verifies all 879 constants resolve to non-empty YAML values

**Rationale**: A single table-driven test parsing embed.go source catches typos and missing YAML entries at test time — no manual key list to maintain

**Consequence**: New TextDescKey constants are automatically covered; orphaned keys fail CI

---

## [2026-03-15-040638] Split text.yaml into 6 domain files loaded via loadYAMLDir

**Status**: Accepted

**Context**: text.yaml grew to 1812 lines covering write, errors, mcp, doctor, hooks, and ui domains

**Decision**: Split text.yaml into 6 domain files loaded via loadYAMLDir

**Rationale**: Matches existing split pattern (commands.yaml, flags.yaml, examples.yaml); loadYAMLDir merges all files in commands/text/ transparently so TextDesc() API stays unchanged

**Consequence**: New domain files must go into commands/text/; loadYAMLDir reads all .yaml in the directory at init time

---

## [2026-03-12-133007] Split commands.yaml into 4 domain files

**Status**: Accepted

**Context**: Single 2373-line YAML mixed commands, flags, text, and examples with inconsistent quoting

**Decision**: Split commands.yaml into 4 domain files

**Rationale**: Context is for humans — localization files should be human-readable block scalars. Separate files eliminate the underscore prefix namespace hack

**Consequence**: 4 files (commands.yaml, flags.yaml, text.yaml, examples.yaml) with dedicated loaders in embed.go

---

## [2026-03-06-200257] Externalize all command descriptions to embedded YAML for i18n readiness — commands.yaml holds Short/Long for 105 commands plus flag descriptions, loaded via assets.CommandDesc() and assets.FlagDesc()

**Status**: Accepted

**Context**: Command descriptions were inline strings scattered across 105 cobra.Command definitions

**Decision**: Externalize all command descriptions to embedded YAML for i18n readiness — commands.yaml holds Short/Long for 105 commands plus flag descriptions, loaded via assets.CommandDesc() and assets.FlagDesc()

**Rationale**: Centralizing user-facing text in a single translatable file prepares for i18n without runtime cost (embedded at compile time)

**Consequence**: System's 30 hidden hook subcommands excluded (not user-facing); flag descriptions use _flags.scope.name convention

---


## Group: Package taxonomy and code placement

## [2026-03-06-200247] cmd/root + core taxonomy for all CLI packages — single-command packages use cmd/root/{cmd.go,run.go}, multi-subcommand packages use cmd/<sub>/{cmd.go,run.go}, shared helpers in core/

**Status**: Accepted

**Context**: 35 CLI packages had inconsistent flat structures mixing Cmd(), run logic, helpers, and types in the same directory

**Decision**: cmd/root + core taxonomy for all CLI packages — single-command packages use cmd/root/{cmd.go,run.go}, multi-subcommand packages use cmd/<sub>/{cmd.go,run.go}, shared helpers in core/

**Rationale**: Taxonomical symmetry: every package has the same predictable shape, making navigation instant and agent-friendly

**Consequence**: cmd/ contains only cmd.go + run.go; helpers go to core/; 474 files changed in initial restructuring

---

## [2026-03-06-200227] Shared entry types and API live in internal/entry, not in CLI packages — domain types that multiple packages consume (mcp, watch, memory) belong in a domain package, not a CLI subpackage

**Status**: Accepted

**Context**: External consumers were importing cli/add for EntryParams/ValidateEntry/WriteEntry, creating a leaky abstraction

**Decision**: Shared entry types and API live in internal/entry, not in CLI packages — domain types that multiple packages consume (mcp, watch, memory) belong in a domain package, not a CLI subpackage

**Rationale**: Domain types in CLI packages force consumers to depend on CLI internals; internal/entry provides a clean boundary

**Consequence**: entry aliases Params from add/core to avoid import cycle (entry imports add/core for insert logic); future work may move insert logic to entry to eliminate the cycle

---

## [2026-03-13-151954] Templates and user-facing text live in assets, structural constants stay in config

**Status**: Accepted

**Context**: Ongoing refactoring session moving Tpl* constants out of config/

**Decision**: Templates and user-facing text live in assets, structural constants stay in config

**Rationale**: config/ is for structural constants (paths, limits, regexes); assets/ is for templates, labels, and text that would need i18n. Clean separation of concerns

**Consequence**: All tpl_entry.go, tpl_journal.go, tpl_loop.go, tpl_recall.go moved to assets/

---


## Group: Eager init over lazy loading

## [2026-03-18-193631] Eager Init() for static embedded data instead of per-accessor sync.Once

**Status**: Accepted

**Context**: 4 sync.Once guards + 4 exported maps + 4 Load functions + a wrapper package for YAML that never mutates.

**Decision**: Eager Init() for static embedded data instead of per-accessor sync.Once

**Rationale**: Data is static and required at startup. sync.Once per accessor is cargo cult. One Init() in main.go is sufficient. Tests call Init() in TestMain.

**Consequence**: Maps unexported, accessors are plain lookups, permissions and stopwords also loaded eagerly. Zero sync.Once remains in the lookup pipeline.

---

## [2026-03-16-104143] Explicit Init over package-level init() for resource lookup

**Status**: Accepted

**Context**: server/resource package used init() to silently build the URI lookup map

**Decision**: Explicit Init over package-level init() for resource lookup

**Rationale**: Implicit init hides startup dependencies, makes ordering unclear, and is harder to test. Explicit Init called from NewServer makes the dependency visible.

**Consequence**: res.Init() called explicitly from NewServer before ToList(); no package-level side effects

---


## Group: Pure logic separation of concerns

## [2026-03-15-230640] Pure-logic CompactContext with no I/O — callers own file writes and reporting

**Status**: Accepted

**Context**: MCP server and CLI compact command both implemented task compaction independently, with the MCP handler using a local WriteContextFile wrapper

**Decision**: Pure-logic CompactContext with no I/O — callers own file writes and reporting

**Rationale**: Separating pure logic from I/O lets both MCP (JSON-RPC responses) and CLI (cobra cmd.Println) callers control output and file writes. Eliminates duplication and the unnecessary mcp/server/fs package

**Consequence**: tidy.CompactContext returns a CompactResult struct; callers iterate FileUpdates and write them. Archive logic stays in callers since MCP and CLI have different archive policies

---

## [2026-03-16-122033] Server methods only handle dispatch and I/O, not struct construction

**Status**: Accepted

**Context**: MCP server had ok/error/writeError as methods plus prompt builders that didn't use Server state — they just constructed response structs

**Decision**: Server methods only handle dispatch and I/O, not struct construction

**Rationale**: Methods that don't access receiver state hide their true dependencies and inflate the Server interface. Free functions make the dependency graph explicit and are independently testable.

**Consequence**: New response helpers go in server/out, prompt builders in server/prompt. Server methods are limited to dispatch (handlePromptsGet) and I/O (writeJSON, emitNotification). Same principle applies to future tool/resource builders.

---

## [2026-03-23-003346] Pure-data param structs in entity — replace function pointers with text keys

**Status**: Accepted

**Context**: MergeParams had UpdateFn callback, DeployParams had ListErr/ReadErr function pointers — both smuggled side effects into data structs

**Decision**: Pure-data param structs in entity — replace function pointers with text keys

**Rationale**: Text keys are pure data, keep entity dependency-free, and the consuming function can do the dispatch itself

**Consequence**: All cross-cutting param structs in entity must be function-pointer-free; I/O functions passed as direct parameters

---


Loading
Loading