fix(hub): surface silently swallowed errors in hubsync hook and replication loop#114
fix(hub): surface silently swallowed errors in hubsync hook and replication loop#114omergk28 wants to merge 1267 commits into
Conversation
When --include-shared is passed, loads .context/shared/*.md and includes them as Tier 8 in the agent context packet. Shared entries are budget-aware and rendered in both markdown and JSON output formats. No-op when .context/shared/ is absent (opt-in). Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Background hub operation: --daemon forks the server and writes a PID file to <data-dir>/hub.pid. --stop sends SIGTERM to the running daemon and removes the PID file. Exec and error logic split to internal/exec/daemon/ and internal/err/serve/ per project conventions. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Raft-lite: uses hashicorp/raft ONLY for master election, not data consensus. No-op FSM since entries are replicated via sequence-based gRPC sync. New --peers flag on ctx serve --shared for cluster membership. Single-node mode auto-bootstraps. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Followers replicate from the master via sequence-based gRPC sync with automatic retry. Failover client tries peers in order and verifies connectivity with a Status call before returning. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Cluster management CLI: ctx hub status shows role and entry counts, ctx hub peer add/remove manages cluster membership, ctx hub stepdown transfers leadership gracefully. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Four integration tests: publish-and-sync across two clients, incremental sync with since_sequence, type-filtered sync, and full Client library round-trip (register, publish, sync, status). Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
New pages: connect.md (register, subscribe, sync, publish, listen, status), serve.md (shared hub, daemon, cluster modes), hub.md (status, peer, stepdown). Updated agent docs with --include-shared flag and Tier 6-8 budget tiers. Updated CLI index with new command entries. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
When --share is passed, ctx add writes the entry locally AND publishes it to the shared hub in one step. Best-effort: hub publish failure does not block the local write. Uses the existing encrypted connection config from ctx connect register. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Failover: first-peer, skip-bad-peer, all-bad-peers. Fan-out: subscribe-broadcast, unsubscribe, broadcast-to-none. Renderer: creates files with origin tags, appends to existing, filename generation. Total hub test count: 26. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
New check-hub-sync hook runs on UserPromptSubmit, daily throttled. If .connect.enc exists, silently syncs new entries from the hub to .context/shared/. No manual ctx connect sync needed after initial registration. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
1. Fix listen command: now streams via Listen RPC instead of blocking after initial sync 2. Add input validation on Publish: type, ID, origin, content size limit (1MB) 3. Warn on --share publish failure instead of silent suppression 4. Constant-time token comparison via crypto/subtle + O(1) map lookup 5. Wire Raft cluster to Server with SetCluster/Shutdown 6. Reject duplicate project registration in store 7. Disconnect slow fanout listeners instead of silently dropping 8. File locking on sync state to prevent concurrent race 9. Fail fast on auth errors in failover client Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Document --share flag behavior (best-effort, warns on failure), auto-sync hook, input validation rules (1MB content limit), and duplicate registration rejection. Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Rebase onto main brought strict audit tests (doc structure, magic strings/values, dead exports, flag YAML drift, fmt.Fprintf checks) that the hub code predates. Grandfather hub violations, add package exemptions, fix fmt.Fprintf return check, add AdminAuth flag constant, and fix AddConfig.Share field placement. Spec: specs/shared-hub-federation.md Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
…ntity
Closes TASKS.md:45 ("Move 6 grandfathered cross-package MCP types to
entity/") by eliminating the Handler struct entirely and reducing the
grandfathered cross-package types count from 6 to 0.
## Phase 1 — sub-task moves
- `def/prompt.EntrySpec`/`EntryField` -> `entity.PromptEntrySpec`/
`PromptEntryField` (pure data, natural entity fit)
- `internal/mcp/session/*` collapsed into `internal/mcp/handler/*`
(sole consumer; eliminates the sibling crossing)
- `internal/mcp/server/poll/` -> `internal/mcp/server/dispatch/poll/`
(ancestor->descendant exemption closes the last sibling crossing
before Phase 2)
## Phase 2 — flatten the Handler god-object
The Handler struct was a parameter bundle masquerading as domain
logic. 13 of its 17 methods only read ContextDir; 2 mutated session
state; the other 2 did not touch the receiver at all. TokenBudget
was a pass-through passenger consumed only by dispatch.go.
- New `entity.MCPDeps { ContextDir, TokenBudget, *MCPSession }`
parameter object, held by the server and threaded through dispatch
- New `entity.MCPSession` holding the former session.State fields
with pure Record*/Increment* mutation methods (no I/O)
- New `entity.PendingUpdate` (pure data)
- All 17 `(h *Handler) Foo(...)` methods converted to free functions
`handler.Foo(d *entity.MCPDeps, ...)`
- `handler.CheckGovernance` kept as a free function in handler/
because it still reads .context/state/violations.json (Phase 3
absorbed into Phase 2 naturally)
- `server.Server.handler *handler.Handler` replaced with
`server.Server.deps *entity.MCPDeps`; route/tool/* and
route/prompt/* updated to take `*entity.MCPDeps`
- `handler.Handler` struct deleted entirely; `handler/handler.go`
deleted; `handler/state.go` deleted
- `governance_test.go` rewritten to construct `*entity.MCPDeps`
## Grandfather purge (TestTypeFileConvention)
The grandfatheredTypes map was cleared to zero entries. All 35
underlying violations across 18 packages were fixed by moving type
declarations into per-package types.go files; methods remain in
their original files, which become type-decl-free and are skipped
by the audit.
- New types.go created in 13 packages (cli/dep/core/{golang,node,
python,rust}, cli/drift/core/{fix,out}, cli/guide/core/skill,
cli/remind/core/store, cli/system/core/nudge, cli/why/core/data,
config/memory, mcp/server/catalog, mcp/server/io)
- fm.go renamed to types.go (frontmatter) and atom.go renamed to
types.go (rss) since they were pure type definition files
- session.go regenerated with only the Session interface
- parser.{ClaudeCode,Copilot,CopilotCLI,MarkdownSession} and
lookup.commandEntry absorbed into their packages' existing types.go
Hardened the grandfatheredTypes comment with an explicit agent
directive: do not add entries here under any circumstance. Any
re-addition requires a dedicated pull request with per-entry
justification and maintainer approval. Drive-by additions by any
assistant are unauthorized.
## Interface segregation
Interfaces are behavioral contracts, not data blueprints, so they
live in their own <name>.go files rather than types.go.
- TestTypeFileConvention updated: interface type declarations are
now exempt from the "must have exported receiver" phase 4 check.
Interfaces cannot carry receivers; a file containing only an
interface is a valid pure type file.
- `parser.Session` interface moved to journal/parser/session.go
- `builder.GraphBuilder` interface moved to
cli/dep/core/builder/graph_builder.go
## Audit state
- TestCrossPackageTypes: grandfathered 6 -> 0
- TestTypeFileConvention: grandfathered map 34 -> 0
- TestDocCommentStructure: 0 (unchanged)
- make test: all pass
- make lint: 0 issues
## Documentation
Updated .context/DETAILED_DESIGN-mcp.md, .context/ARCHITECTURE.md,
.context/DANGER-ZONES.md, and .context/DETAILED_DESIGN.md to reflect
the new package layout (no more mcp/session, poll lives under
dispatch, Handler replaced by entity.MCPDeps).
Also sweeps in two pre-existing .context/ entries that were
uncommitted at session start (architecture skill pipeline decision,
pad index shifting learning).
Spec: specs/mcp-handler-flatten.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ext-hub feat: shared context hub for cross-project knowledge sharing
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Add parallel-slice batch functions to flagbind (BindStringFlagsP, BindStringFlags, BindBoolFlags, BindBoolFlagsP, BindStringFlagShorts, BindStringFlagsPDefault) replacing repetitive one-at-a-time flag registrations across 8 CLI command files. Convention sweep: - hub: rename files (entry_validate→validate_entry, errcheck→err_check), remove const aliases, move magic numbers to config/hub and config/entry, fix predicate naming (isEOF→eof, isAuthErr→authErr) - initialize: rename claudecheck→claude_check, details→detail - steering/types: align docstrings with conventions - compliance: fix TestNoSecretsInTemplates false positive on YAML keys - golangci: extend G101 exclusion to all embed/text/ DescKey files - sysinfo: add missing nolint:gosec for G204 on vm_stat Spec: specs/flagbind-batch-and-convention-sweep.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…arser rescue The dead exports test maintained a manual list of platform-specific symbols (linux-only, darwin-only) that appeared dead on the other OS. This list grew with every new platform constant and taught bad habits. Replace with a go/parser sweep that parses ALL .go files regardless of build tags, extracts selector names, and automatically rescues any export referenced from any platform file. No manual maintenance. Spec: specs/flagbind-batch-and-convention-sweep.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…next refactor: flagbind batch helpers and convention sweep
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](softprops/action-gh-release@v2...v3) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.43.0 to 0.44.0. - [Release notes](https://github.com/golang/tools/releases) - [Commits](golang/tools@v0.43.0...v0.44.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-version: 0.44.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Five command surface changes: - Move `ctx bootstrap` back to `ctx system bootstrap` (hidden, agent-only plumbing that was incorrectly promoted to top-level) - Rename `ctx stats` to `ctx usage` (clearer intent) - Rename `ctx resource` to `ctx sysinfo` (matches internal package, unambiguous) - Remove `ctx dep` entirely (marginal utility, go list/npm ls suffice) - Introduce `ctx hook` parent — consolidates message, notify, pause, resume, and event under a single namespace Includes full propagation: constants, YAML assets, flag/text DescKeys, group.go registration, docs, recipes, skills, CLI index, zensical.toml nav, and dead export cleanup of orphaned dep support packages. Also adds: - _ctx-command-audit skill for post-rename quality gates - Package taxonomy section in docs/home/contributing.md - hack/agents/ directory for autonomous agent prompts - Follow-up task for ctx explore command Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…olution When a non-ctx-initialized repo lives inside a ctx-initialized parent workspace, walkForContextDir walked upward and found the parent's .context, then the boundary check rejected it. Now the walk validates candidates against the git root: if a .context is found outside the git root, it belongs to a different project and is discarded. Git is a hint, not a requirement — when no .git exists, the walk falls back to CWD. Changes: - Add findGitRoot helper to walk.go - Update walkForContextDir to validate candidates against git root - Add DotDir constant to config/git, consolidate Diff into subcommands block - Add walk_test.go with 9 edge-case tests (nested repos, worktrees, no git) - Update existing UpwardWalkFromSubdir test to include .git directory Spec: specs/walk-boundary-git-anchor.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…sitives The 1-minute load average spikes during normal build/test runs, triggering false "critically low resources" alerts. The 5-minute average smooths transient spikes while still catching sustained resource pressure. Spec: specs/load-alert-5min-average.md Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
When ctx is installed as a global plugin, its hooks fire in every project Claude opens — including non-ctx projects. Two relay hooks were missing the state.Initialized() guard and nagged users with "Load Xx CPU count" and backup-age warnings in projects that don't use ctx at all. Add the guard, matching the pattern already present in 18 other hooks. Safety hooks (block_dangerous_command, block_non_path_ctx) intentionally run regardless of ctx state. Also records this session's decisions and learnings, and updates TASKS.md line 30 to reflect partial progress (boundary side effect resolved in e24941d; remaining hooks need per-hook audit). Spec: specs/hook-guard-uninitialized.md Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
Port all 40 Claude skills to Copilot CLI format with proper YAML frontmatter (tools array instead of allowed-tools string). Includes lifecycle hook scripts (bash + PowerShell), agent instructions, and hook configuration. Contents: - 40 skill SKILL.md files under integrations/copilot-cli/skills/ - 8 hook scripts (session-start/end, pre/post-tool-use × bash/ps1) - INSTRUCTIONS.md agent bootstrap instructions - ctx-hooks.json lifecycle hook configuration - Updated embed.go to include integrations assets - Parity spec document (specs/copilot-feature-parity-kit.md) Signed-off-by: ersan bilik <ersanbilik@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: ersan bilik <ersanbilik@gmail.com>
Combines two in-flight workstreams into one commit. ## Docstring namespace sweep (parallel agent) Follow-up to the CLI namespace cleanup in 78fbdf7. Updates doc comments, blog posts, recipes, and the regenerated site to reflect current command names (e.g. `ctx pause` → `ctx hook pause`, `ctx resume` → `ctx hook resume`, `ctx message` → `ctx hook message`). Also refreshes the Copilot CLI integration skills added in edaac81 (PR ActiveMemory#63) to match the new namespace. Scope: ~450 files across docs/, site/, internal/cli, internal/write, internal/config, .github/, integration skill templates, and .claude/ skill SKILL.md files. ## Git push regex hardening (this session) The `block-dangerous-command` hook's `MidGitPush` regex only matched `git push` mid-command after `;`, `&&`, or `||`. This session accidentally bypassed it with `git -C <path> push` — the permissions deny list `Bash(git push *)` only matches prefix `git push`, so `git -C <path> push` also slipped through. Replace `MidGitPush` with a broader `GitPush` that covers: - Bare `git push` at command start - All separator and subshell entry points (`;`, `&&`, `||`, `|`, `&`, `(`, `$(`, backtick, newline) - Env-var and command-wrapper prefixes (`GIT_DIR=/x git push`, `time git push`, `nice git push`) - Any flag shape between `git` and `push` (`-C path`, `-c key=val`, `--git-dir=/path`, `--no-pager`, `--bare`, `-p`, `-P`) - Tail anchor that distinguishes subcommand from ref names (`push-to-remote`, `push_branch`) via `[^a-zA-Z0-9._/-]|$` Documented trade-offs: accepted false positives on `git log push` and `git commit -m push` (push as literal arg); known blind spots for `eval` / `sh -c` quoting and shell aliases. Adds `internal/config/regex/cmd_test.go` with 42 table-driven cases covering all entry points, flag shapes, prefixes, negative cases (other subcommands, ref-name continuations), and the accepted false-positive classes. Renames the Go symbol `MidGitPush` → `GitPush` to accurately reflect scope; keeps legacy variant string `mid-git-push` and text key `block.mid-git-push` (user-facing message is already generic: "git push requires explicit user approval"). Spec: specs/git-push-regex-hardening.md Spec: specs/cli-namespace-cleanup.md Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
…-skill-parity-rebased Feat/copilot cli skill parity rebased
…dules/golang.org/x/tools-0.44.0 deps: Bump golang.org/x/tools from 0.43.0 to 0.44.0
Data-path marshal errors are now returned instead of writing empty output: the vscode tasks/extension/mcp config writers, the copilot vscode MCP writer, and the blocknonpathctx hook response. Two discards that lose results are now logged: journal querying skips an unreadable session dir via logWarn (cfgWarn.JournalScanDir) instead of dropping its sessions silently; drift's post-fix re-check warns on a failed context reload (cfgWarn.DriftReload) and keeps the prior context instead of detecting against an empty one. The genuinely-acceptable discards are annotated rather than changed: steering yaml.Marshal of plain frontmatter structs (cannot fail; formatters return []byte by contract), the trigger-test input echo, best-effort hook-stdin Unmarshal, the opencode path lookup whose error is handled by an empty-path fallback, and the sourcecoverage date parse that degrades to zero time on a ctx-generated column. Spec: specs/error-handling-audit.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Make the remaining `_ =` discards that EH.5's grep flags explicitly intentional, so an auditor reads them as deliberate rather than lazy: - cobra MarkFlagRequired / RegisterFlagCompletionFunc: only error on an unregistered flag name, bound immediately above (unreachable) - trace.Record / TruncatePending: best-effort provenance that must never fail the user's command - filepath.Glob in the stats watcher: nil-safe on a malformed pattern - mcp poller WriteJSON: best-effort notification push, no return path - hub cluster.Shutdown: best-effort graceful-stop teardown After this, `grep -rn '_ =' internal/` resolves to only category-(d) fmt.Fprint output and annotated/handled sites (EH.5 DoD). Spec: specs/error-handling-audit.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Mark the Phase EH fix/validate tasks done with per-task ship notes. The sweep surfaced or justified every error discard under internal/: data-path errors returned, best-effort discards routed to logWarn, write-handle closes merged into named returns, and the genuinely acceptable sites annotated. EH.5 grep clean, lint + test green. Spec: specs/error-handling-audit.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Capture the EH-sweep lesson: an auto-generated error-discard catalogue classified by callee name / regex is a worklist of candidates, not findings. Read the discarded value's type and the call's role before fixing — name-inference produced three false positives this pass (MergePublished bool, LoadState value, io/security already-correct closes). Spec: specs/error-handling-audit.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…d-index-data-loss Fix/learning add index data loss
…verable webhooks Two real defects behind the "notify fails in worktrees" report (P0.8.5): - crypto.ResolveKeyPath auto-detected a project-local <contextDir>/.ctx.key and preferred it over the global key. That file is gitignored, so a fresh worktree resolved to a different key and decryption silently failed. Remove the tier: resolution is now key_path override > global, with project-local kept only as a degenerate fallback when no home dir exists (never auto-detected). Also a documented security antipattern (key next to ciphertext). - notify.Send swallowed every fire-path failure as a silent no-op. It now treats .notify.enc existence as the sole "configured" signal and warns (non-fatal) when a configured webhook cannot be delivered — bad/absent key, decrypt, marshal, or POST — while keeping legitimate silences (not configured, event not subscribed). LoadWebhook detects file absence via os.Stat + errors.Is, not os.IsNotExist, which does not unwrap the text-registry-wrapped error. Spec: specs/notify-resolution-hardening.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
`gitnexus analyze` injects a full "# GitNexus — Code Intelligence" block between <!-- gitnexus:start/end --> markers. It first landed in 6afb50d (a recall/core deletion commit) as an analyze side effect, not a deliberate choice, and has churned on every reindex since. The project already has a curated home for this: GITNEXUS.md, added deliberately in bf42b1f with a CLAUDE.md cross-reference. The injected blocks were pure duplication on top of it. Realign to the pre-injection canonical state: - AGENTS.md: back to the redirect-to-CLAUDE.md stub (its form since fda3c82) - CLAUDE.md: keep the Companion Tools pointer to GITNEXUS.md, drop the block Re-injection guard lives outside this repo: run analyze with --skip-agents-md so the global gitnexus hook stops rewriting these two files. Spec: specs/meta/chores.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Follow-up to 8da165a: the marker-bounded removal of the GitNexus block from AGENTS.md/CLAUDE.md is mechanical, so capture a Phase CT task to automate it as `make strip-gitnexus`. Spec: specs/meta/chores.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Land the ctx-dream design that has been in flight: a scheduled, standalone "dream" that only ever proposes (Option B), human-gated via serendipity, with v1 scoped to disciplined triage of the gitignored ideas/ folder. - specs/ctx-dream.md: the v1 spec, plus a sketched-not-contracted v2 section capturing the end-to-end pipeline (raw episodes -> evidence store -> dream pass -> typed durable artifacts), a cautionary-sibling comparison against the Hermes "Dreaming" design (un-gated autonomous promotion is the failure mode to avoid), the lifted attention-scoring rubric, and the quiet_minutes trigger gate. - .context/briefs/...-ctx-dream-disciplined-consolidator.md: the debated brief the spec consumes. - .context/TASKS.md: ctx-dream v1 task breakdown (guards, ledger/state, triage, review CLI + serendipity skill, executor, tests). Spec: specs/ctx-dream.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Consolidation pass per the 3:1 discipline. LEARNINGS 157 -> 81 (18 merged entries) and DECISIONS 111 -> 56 (13 merged entries); every original is preserved verbatim under .context/archive/ and the indexes were rebuilt. No unique fact or rationale dropped — merges distill to bullets, originals are archived, not deleted. Includes the in-flight ctx-dream knowledge captured this cycle: the ctx-dream decision (kept standalone/legible) and the dream design principles (folded into a single "ctx-dream design principles" learning), so the dream knowledge rides with the consolidation that absorbed it. Also archives a handful of point-in-time/superseded entries (IMPLEMENTATION_PLAN refs, old key-migration specifics, Claude Code v2.1.69 internals, PR ActiveMemory#27 merge-readiness, blog-first ordering). Spec: specs/consolidation-nudge-hook.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
… fields Claude Code >= 2.1.158 emits attributionMcpServer / attributionMcpTool (MCP invocation provenance) and >= 2.1.161 emits promptSource (prompt provenance). These were unknown to the importer, so `ctx journal import` and `ctx journal schema check` reported schema drift on every recent session — recurring noise that erodes the drift signal. Add the three fields to OptionalFields (versions read from the actual JSONL, not guessed) and pin all three in TestKnownField_PostV1FieldDrift so a future refactor can't silently drop them and reopen the drift. Spec: specs/fix-journal-schema-drift.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ased) ctx drift's checkSyncStaleness hardcodes cursor/cline/kiro and treats a missing synced output as stale, so a single-tool project (e.g. Claude only) is nagged to sync steering for editors it never uses — pure noise. Spec replaces the hardcoded trio with a presence-based set: a syncable tool is checked iff its native output already exists on disk. This serves both audiences with one mechanism — a multi-tool repo that tracks its outputs gets them enforced in every clone; a single-tool user who never synced sees nothing. Repo-side freshness moves to a wired sync-steering / check-steering make target. No new .ctxrc surface; the pre-existing unused steering.default_tools (a content-scoping concern) is left out of scope. Spec: specs/steering-sync-drift-respects-configured-tools.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Generated by `ctx steering sync --all` from .context/steering/. This repo's dev team is multi-tool, so committing these outputs means a fresh clone has them present and the presence-based drift check enforces their freshness across all three tools (see the steering-sync-drift spec). Repo policy, not a feature requirement. Spec: specs/steering-sync-drift-respects-configured-tools.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…steering Implements specs/steering-sync-drift-respects-configured-tools.md. checkSyncStaleness no longer hardcodes cursor/cline/kiro and no longer treats a missing output as stale — so a single-tool project (e.g. Claude-only) is no longer nagged to sync steering for editors it never uses. A syncable tool is now checked iff it is "in play": at least one of its native outputs already exists on disk. - internal/steering: add SyncableTools() (the cursor/cline/kiro set) and Synced(steeringDir, projectRoot, tool) (file-level presence via SafeStat, skipping tombstoned/excluded files). - internal/drift: checkSyncStaleness iterates SyncableTools() and skips any tool that is not Synced(). - Makefile: standalone `sync-steering` + `check-steering` (the latter wired into `audit`, run by `check`), both via `go run`. sync-steering is intentionally not a build prereq — steering outputs are repo-root artifacts, not embedded assets, so they must not be regenerated on every build. - Tests: Synced/SyncableTools units; drift cases for "unsynced tools not checked" (the headline fix) and "only synced tools checked". - Spec: corrected the build-wiring section to match (check in audit, sync standalone). Spec: specs/steering-sync-drift-respects-configured-tools.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…r contract Resolve the ctx-dream v1 open questions ahead of implementation: - Executor is a documented contract, not a hardcoded assumption. cron `claude -p` is the reference executor (guards as PreToolUse hooks for free), but ctx owns an executor-agnostic Go core (dreams/ layout, state, ledger, proposal schema, guards as callable logic) so other harnesses can run the same dream. New Executor-contract section + decision record. - Dream is opt-in (not enabled by default); a `dream:` .ctxrc section gates it. Two user-facing docs required: a Claude Code enablement guide and an executor-contract reference for other harnesses (added to TASKS). - Serendipity review split into its own spec (specs/ctx-serendipity.md): the human garden-walk gate, accept/reject/amend, mechanical-vs- generative dispositions, ctx remind cadence, routing to archive / /ctx-spec / /ctx-blog. - Settled: merit defaults via the attention-scoring rubric (ranking only, never an auto-promote threshold); summary reuses the session model; Go package layout follows convention; proposals/ledger carry stable IDs so v2 supersession isn't foreclosed. Records the executor-contract architectural decision in DECISIONS.md. Spec: specs/ctx-dream.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ledger
The contracted Go core of ctx-dream (specs/ctx-dream.md), with no CLI,
skill, or executor yet — the safety substrate landed first, fully tested.
- internal/config/dream: proposal status/action enums, confidence,
source-status, ledger decision, notebook file names + validation
reason templates (config-housed, no magic strings).
- internal/dream: the data contract (Proposal, SourceState, LedgerEntry,
GuardDecision) plus the engine —
- guards: WriteScope (writes only under dreams//ideas/, or specs/ on
promote) and Leak (target must be git-ignored, except the promote
crossing) — executor-agnostic, so a Claude Code PreToolUse hook and a
raw-API tool executor enforce the same invariant.
- state: hash-based delta selection (the discipline clock) + JSON
persistence at dreams/state.json.
- ledger: append-only dreams/ledger.md + dedup-against-seen.
- validation referencing every enum value (the dead-export-safe fix).
- internal/err/dream + config/embed/text/err_dream.go + errors.yaml: the
error/text-registry ceremony for the above.
- internal/exec/git + config/git: CheckIgnore helper (maps git
check-ignore exit 0/1 to a bool, surfaces only real failures) — the
don't-leak primitive.
- config/dir: Dreams, Done directory constants.
Tests cover the guard allow/deny matrix (incl. tracked-path leak refusal
and the specs-promote crossing), state round-trip + delta selection, and
ledger append/readback + dedup.
Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ions The runnable Go surface on top of the v1 foundation (specs/ctx-dream.md). - internal/cli/dream: `ctx dream` (run a bounded pass) + review / accept / reject / amend subcommands, registered in the sessions command group. The pass resolves the root, ensures the gitignored dreams/ dir, runs the opt-in trigger gate (enabled + cadence + quiet-window, bypassable with --force), DeltaSelects ideas/**.md, takes an exclusive lock, invokes the configured executor (default `claude -p` with the ctx-dream skill + --max-turns/--model), FAILS LOUD with a failmark on a missing or failed executor, then validates proposals and persists state. - internal/dream: the disposition appliers — scan/delta, proposal read+pending (dedup-against-seen), and accept/reject/amend/backup. Every ideas/ mutation passes BOTH guards before any write; mechanical actions (archive→ideas/done/, mark-blog, keep) run in Go, generative ones (promote/merge) record intent and route to /ctx-serendipity. - internal/write/dream: all user-facing output. internal/exec/dream: executor lookup/run (keeps exec.* in internal/exec). internal/io: SafeTryLock/SafeUnlock (portable O_EXCL lock). - internal/rc: the `dream:` .ctxrc section (enabled/mode/max/cadence/ quiet_minutes/model/budget/executor) + schema; bootstrap registration; config/text/flag registry entries. Proposal on-disk contract: a single JSON array at dreams/<ts>/proposals.json. Spec: specs/ctx-dream.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…tignore The harness wiring + cognition that makes v1 runnable, plus the human gate. - internal/assets/claude/skills/ctx-dream: the disciplined ideas/-triage skill (classify + ground against code/specs, emit provenance-bearing proposals into dreams/<ts>/proposals.json) — scoped to the spec's v1, not the broader draft. Bundles guard.sh, the PreToolUse hook that restricts a headless pass to writing under dreams/ (mirrors the Go WriteScope/Leak guards for the Claude Code executor path). - internal/assets/claude/skills/ctx-serendipity: the garden-walk review skill — drives ctx dream review/accept/reject/amend; mechanical dispositions apply instantly, generative (merge/promote) are done from the full source with backup-before-mutate (specs/ctx-serendipity.md). - docs/recipes/run-the-dream.md: opt-in enablement guide for Claude Code users (.ctxrc dream.enabled, cron entry, guard-hook settings, review). - docs/reference/dream-executor-contract.md: the contract for non-Claude- Code harnesses (bounded pass, structural guard enforcement, fail-loud, proposals-only-into-dreams/). - .gitignore: dreams/ (inherits ideas/'s privacy class; the don't-leak guard double-checks at write time). - TASKS.md: mark the completed ctx-dream v1 items. Spec: specs/ctx-dream.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…d site Close the user-facing surface for ctx-dream per the CONVENTIONS "User-Facing Surface Completeness" checklist: - docs/cli/dream.md: per-command reference (the pass + review/accept/ reject/amend, flags, examples), linking the run-the-dream recipe and the executor-contract reference. - docs/cli/index.md: a `ctx dream` row in the Sessions table and the `dream:` block in the .ctxrc reference. - examples.yaml: example blocks for dream and its four subcommands (the desc.Example keys existed but had no entries, so --help showed none). - site/: `zensical build` rebuild so the published site includes the new dream CLI page, recipe, and executor-contract reference (built output is tracked; bundled with the docs change per convention). Recipe (docs/recipes/run-the-dream.md) and the executor-contract reference shipped with the feature commits. A Copilot-CLI skill mirror is intentionally not added: the sync maintains a curated subset and v1's reference executor is Claude Code (deferrable follow-up). Spec: specs/ctx-dream.md
…tifact tests Close the last ctx-dream v1 test task and harden the schema gate it exercises. - ProposalValid now also rejects a proposal with no target or empty evidence, not just unknown enums — enforcing the spec's "no evidence is not surfaced" provenance rule. New config reason/labels (FieldEvidence, FieldTargets, ReasonMissing). - TestCrashResume: a pass that crashes mid-way persists state only for completed sources; the next run reloads that committed state intact (atomic write — no torn file) and the discipline clock re-selects exactly the unprocessed + changed sources, skipping unchanged ones. - Corrupted-artifact regression corpus (testdata/corrupted-2605.12978.json, modeled on the arXiv 2605.12978 appendix): a well-formed proposal among corrupted ones (unknown status, stripped evidence, dropped target). TestCorruptedArtifactGate asserts only the provenance-bearing one survives; MalformedJSON asserts a structurally corrupt file errors (no panic / silent empty); Dedup asserts an already-decided artifact does not re-surface. Spec: specs/ctx-dream.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…lution-hardening Fix/notify resolution hardening
…cation loop The session-start hubsync hook returned "" on config-load, dial, sync-RPC, and write failures with no trace, and conflated a real sync error with the genuine "nothing new" result. The follower replication loop returned silently on dial, stream-open, send, and close-send failures and on every receive error. Operators could not distinguish a healthy quiet sync from one that never reached the network. Wire every silent return through the warn sink with format constants in config/warn. Behavior is otherwise unchanged: both functions keep their signatures and never-block contracts. - Sync now warns per failure site and stays silent only for a genuine zero-entry result (the un-conflation from ActiveMemory#100). - replicateOnce warns per failure site; the receive path stays silent for the eof() end-of-stream (the one deliberate deviation from the issue's proposed code, which would have warned once per replication cycle) and for caller shutdown. - Two ActiveMemory#100 sub-items (close-defer warn, append warn + keep consuming) had already landed; this covers the rest and adds the regression tests the issue asked for, including pinning keep-consuming-after-append-error and clean-EOF-no-warn. Closes ActiveMemory#100. Spec: specs/fix-hub-silent-error-suppression.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Omer Kocaoglu <omergk28@gmail.com>
|
The fix is correct, and the key insight is right Some nits below: The real bug was the conflation of "error" with "nothing new": // internal/cli/system/core/hubsync/sync.go — before
entries, syncErr := client.Sync(context.Background(), cfg.Types, 0)
if syncErr != nil || len(entries) == 0 {
return ""
}The PR un-conflates them so a real error warns while a genuine empty result stays silent: // after
if syncErr != nil {
logWarn.Warn(cfgWarn.HubSyncPull, cfg.HubAddr, syncErr)
return ""
}
if len(entries) == 0 {
// Genuine empty result: not an error, no warning.
return ""
}Every other silent // internal/hub/replicate.go
if recvErr := stream.RecvMsg(msg); recvErr != nil {
if !eof(recvErr) && ctx.Err() == nil {
logWarn.Warn(cfgWarn.HubReplicateRecv, masterAddr, recvErr)
}
return
}…and the spec documents this as a deliberate deviation from issue #100's proposed code (which warned on every recv error and would have made clean cycles noisy). Things I checked that hold up
Findings (all minor / adjacent — none are blockers)1. (adjacent, pre-existing) The "must not block the session" contract isn't actually guaranteedThe doc update reaffirms the contract: // internal/cli/system/core/hubsync/doc.go
// Every error is surfaced as a stderr warning via the warn sink,
// but never propagates: the hook must not block the session start.But the underlying RPC has no deadline: // sync.go — context.Background(), no timeout
entries, syncErr := client.Sync(context.Background(), cfg.Types, 0)
// internal/hub/client.go — lazy NewClient, fail-fast (not WaitForReady), no dial timeout
conn, dialErr := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), ...)A hub that accepts the TCP connection but never responds (hung server / black-hole proxy) makes 2. (forward-looking) Replication warning cadence when wired
3. (trivial)
|
…ory#114 review) Review of ActiveMemory#100 surfaced that the re-documented "must not block the session start" contract was not actually guaranteed: the hubsync pull RPC ran under context.Background() with no deadline, so a hub that accepts the connection but never responds (hung server, black-hole proxy) would hang the session-start hook indefinitely. - Bound the pull with context.WithTimeout(HubSyncTimeout) — new 10s constant in internal/config/hub. An exceeded deadline surfaces via the existing HubSyncPull warning and the hook returns "" like any other failure; the daily throttle plus next-session retry covers a cut-off pull. A test-overridable syncTimeout var (export_test.go seam) lets TestSync_WarnsOnHungHub drive a black-hole listener in milliseconds instead of the full interval. - Harden internal/hub/eof.go to errors.Is(err, io.EOF) so the replication receive-suppression branch stays correct if any layer ever wraps EOF (gRPC delivers it unwrapped today). - Record the forward-looking finding (dedup/backoff for the per-cycle HubReplicateRecv warning once startReplication is wired, ActiveMemory#96) as a task rather than guessing now; replicateOnce is dead code until then. Spec: specs/fix-hub-silent-error-suppression.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Omer Kocaoglu <omergk28@gmail.com>
|
Thanks for the thorough review — addressed in
The deadline (F1) is the only behavior change; the never-block contract the PR re-documents is now actually true. |
|
Thanks @omergk28 F1 and F3 landed exactly as discussed, and the code fix is right; one more small push and this merges. Here are some fixes before we can merge this: M1 — M2 — the "next session retries" comments are false. Two new comments justify the 10s deadline with a retry story the code doesn't implement: // sync.go — "An exceeded deadline surfaces as a warning like any
// other sync failure; the next session retries."
// config/hub/hub.go — "the daily throttle plus next-session retry
// covers a cut-off pull"But msg := hubsync.Sync(sessionID)
if msg != "" { writeSetup.Nudge(cmd, msg) }
_ = internalIo.TouchFile(markerPath) // stamps failed pulls tooRetry is next day, not next session. To be clear: I think the unconditional stamp is the right behavior — it's exactly what caps a black-holing hub at one 10s stall per day instead of one per session. Don't change the code; change the two comments ("the next day's first session retries"). While you're in there: N1 — keep N2 — hung-hub test failure path. Cleanup runs LIFO, so if the 5s guard ever fires, the warn-sink restore (registered last) runs before the black-hole conns close — which is what unblocks the leaked Things I checked that hold up
AdditionalThis is not direclty part of your PR; but since you are touching relevant files, I'd be glad if you can check them out too.
Thanks again for all your help 🙏 🌮 . |
Summary
Two hub code paths swallowed errors with no logging surface — the session-start hubsync hook (
internal/cli/system/core/hubsync) and the cluster replication loop (internal/hub/replicate.go). Operators couldn't tell a healthy quiet sync from one that never reached the network.This PR wires every silent return through the established
warnsink with format constants inconfig/warn, and un-conflates the sync-error check from the empty-result check (only the error warns now). Both functions keep their signatures and never-block contracts — logging is the only behavior change.Closes #100.
Already landed
Two of #100's sub-items were already in main before this PR (the
conn.Closedefer warn and thestore.Appendwarn + keep-consuming, viaCloseHubClient/HubReplicateAppend). This PR covers the rest and adds the regression tests the issue asked for — including pinning the already-landed keep-consuming behavior, which previously had no coverage.One deliberate deviation from the issue's proposed code
The issue's sketch warns on every
RecvMsgerror — butio.EOFis the normal end of every sync stream, so that would emit a warning once perReplicateIntervalon perfectly healthy replication. The receive path here stays silent foreof()(the hub package's own end-of-stream helper, same as the other two RecvMsg sites) and for caller shutdown (ctx.Err() != nil), and warns on everything else.TestReplicateOnce_CleanReplicationDoesNotWarnpins this.Tests (mapped to the issue's Tests Required)
TestSync_WarnsOnLoadErrorTestSync_WarnsOnDialError— empirically, the only eagergrpc.NewClientfailure is a control character failing URL parse; every other malformed target (://invalid…) defers to first use.TestSync_WarnsOnPullErrorcovers the lazy path (closed port → Sync RPC fails)TestSync_NoWarnOnEmptyResult— real in-process hub, zero entries, asserts an empty warn bufferTestReplicateOnce_WarnsOnDialError+TestReplicateOnce_WarnsOnTransportErrorTestReplicateOnce_KeepsConsumingAfterAppendError— read-only follower dir, asserts exactly 2 append warnings (root/Windows skip guards per therc/require_test.goprecedent)All use the existing
warn.SetSinkseam; the hubsync package previously had zero tests.Verification
go test -race -count=2on both packages — clean.syncErrwith the empty check failsTestSync_NoWarnOnEmptyResult; (2) removing the EOF guard failsTestReplicateOnce_CleanReplicationDoesNotWarn; (3) dropping the dial warn failsTestSync_WarnsOnDialError.make audit— all checks passed.🤖 Generated with Claude Code