Skip to content

feat(cli): send rich context with shelltime q - #315

Merged
AnnatarHe merged 3 commits into
mainfrom
claude/tender-keller-qkfiy2
Oct 6, 2026
Merged

AnnatarHe merged 3 commits into
mainfrom
claude/tender-keller-qkfiy2

Conversation

@AnnatarHe

Copy link
Copy Markdown
Contributor

Summary

shelltime q sent only the shell, OS, working directory and hostname, so the model had to guess about the repository, project tooling and machine. With ai.shareContext on (the default), it now also sends a bounded context object that makes suggestions fit the user's real environment.

Server side: https://github.com/shelltime/server/pull/505. Today the server drops everything except shell, OS and query, so deploy that PR first. This CLI change is harmless against an old server, which ignores unknown fields.

Context collected

Five collectors run concurrently under a 500 ms limit (git gets 400 ms). Anything late is dropped. A dry run takes about 30 ms in a 1,600-file repo.

Area Details
system OS version, kernel, arch, CPU count, uptime, load average, root, SSH, container/WSL, tmux/zellij/screen, TERM_PROGRAM, timezone, local time. Read from /proc and /etc/os-release on Linux and from sysctl on macOS, with no forks.
git Path in repo, branch (short hash when detached), upstream, ahead/behind, staged/unstaged/untracked/conflicted counts, in-progress merge/rebase/am/cherry-pick/revert/bisect, remotes as name=host only, last 3 commit subjects. If git status is too slow, the branch is read from HEAD and statusIncomplete is set.
project Types and package managers from manifest and lock files, walking up to the repo root so monorepo workspaces find the root lockfile. The packageManager field wins. Also package.json script names, Makefile targets and justfile recipes (names only, common ones first, max 20).
tools Non-standard CLIs found on PATH (rg, fd, jq, docker, pnpm, brew, …), plus GNU variants such as gsed on macOS.
dir Up to 40 entry names from the current directory, sorted, with / on directories. Skipped in $HOME.

Also:

  • Accurate shell: the shell is taken from the parent process when it's a known shell, otherwise $SHELL. A fish session started from a zsh login shell now reports fish.
  • shelltime q --show-context "…": prints the request as JSON on stdout (pipeable to jq) without calling the AI or spending credits. It works without a token.
  • Opt-out: ai.shareContext: false sends only shell, OS and query.
  • Dependencies: golang.org/x/sys is promoted from indirect to direct (no new module). sysstat_{linux,darwin,other}.go keep the Windows build working.

Auto-run hardening (separate commit)

The prompt now carries text the repository author controls (file names, commit subjects, script names). Before this PR, ClassifyCommand looked only at the first word, so with ai.agent.view on, curl … | sh, ls | xargs rm -rf or cat a; rm -rf ~ would auto-run as "view". Now:

  • Pipelines, lists and substitutions are split (quote and escape aware), and the most severe segment decides.
  • Interpreters and wrappers (sh, python, xargs, sudo, eval, env cmd, find -exec, …) and multi-line scripts are classed as "other", which never auto-runs.
  • View commands that redirect into a file, curl -o and wget are classed as "edit".
  • git, docker/podman, kubectl and systemctl are classified by subcommand. For example, git reset --hard, force push, branch or stash deletion and kubectl delete are delete; docker exec and systemctl reboot are other.

Docs

docs/CONFIG.md documents shareContext, the table of what is sent, --show-context, the server-side AI Context, a privacy note and the compound-command auto-run rules. README.md links to it.

Testing

  • model: parser tables for os-release, /proc and darwin vm.loadavg; Makefile, justfile and package.json fixtures under fixtures/query_context/; monorepo walk-up, including that manifests above the repo root are ignored; directory listing and truncation; sanitizer (ANSI, control characters, rune-safe truncation); 60+ new classifier cases.
  • daemon: real temp repos for a clean repo, file counts, ahead of a bare upstream, merge conflict, detached HEAD and subdirectories; a porcelain v2 fixture; remote credential stripping.
  • commands: context reaches the AI mock; shareContext: false skips collection and sends no context; --show-context is a dry run, including without an AI service; shell detection; a real end-to-end gatherQueryContext in a temp pnpm repo.
  • go vet ./... passes on linux and darwin; darwin and windows (amd64/arm64) builds pass.
  • The CI command (go test -timeout 3m -coverprofile=coverage.txt -covermode=atomic ./...) passes.
  • go test -race passes for commands and model and for the new daemon tests. The full daemon package has intermittent -race failures in existing tests (TestSocketTopicProcessor_TrackPreAndPostRouted, TestCCInfoTimerTestSuite, TestFetchRateLimit_…) that also reproduce on main without this change. CI doesn't run -race.

🤖 Generated with Claude Code

https://claude.ai/code/session_012gybkwT4DrTkNF4wKq54JT


Generated by Claude Code

claude added 2 commits October 6, 2026 08:34
ClassifyCommand only looked at the first word, so with ai.agent.view
enabled `curl ... | sh`, `ls | xargs rm -rf` or `cat a; rm -rf ~` were
auto-run as "view". `shelltime q` is about to send repository-controlled
text (file names, commit subjects, script names) to the model, so the
classifier has to be safe against a manipulated suggestion.

- Split pipelines, lists and substitutions (quote and escape aware) and
  return the most severe segment.
- Treat interpreters and wrappers (sh, python, xargs, sudo, eval, env
  with a command, find -exec, ...) and multi-line scripts as "other",
  which never auto-runs.
- Upgrade view commands that redirect output to a file, and downloads
  (curl -o, wget), to "edit".
- Classify git, docker/podman, kubectl and systemctl by subcommand:
  read-only subcommands stay "view", `git reset --hard`, forced pushes,
  branch/stash deletion and `kubectl delete` are "delete", and exec,
  reboot and global git options are "other".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gybkwT4DrTkNF4wKq54JT
`shelltime q` only sent the shell, OS, working directory and hostname,
so suggestions guessed at the repository, project tooling and machine.
When ai.shareContext is enabled (the default) it now also sends a
`context` object, collected concurrently within 500ms:

- system: OS version, kernel, arch, CPU count, uptime, load average,
  root, SSH, container, multiplexer, terminal, timezone and local time
  (read from /proc and sysctl, no forks)
- git: path in repo, branch, upstream, ahead/behind, staged, unstaged,
  untracked and conflicted counts, in-progress operation, remote hosts
  and the last 3 commit subjects; falls back to HEAD if `git status` is
  slow
- project: types and package managers from manifest and lock files,
  walking up to the repo root for monorepos, plus package.json script,
  Makefile target and justfile recipe names
- tools found on PATH, and up to 40 names from the current directory

The shell is now taken from the parent process when it is a known
shell, so a fish session started from a zsh login shell is reported as
fish. `shelltime q --show-context "..."` prints the request without
calling the AI. With ai.shareContext: false only shell, OS and query
are sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gybkwT4DrTkNF4wKq54JT
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.74074% with 65 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
model/query_context.go 91.36% 19 Missing ⚠️
commands/query_context.go 85.14% 15 Missing ⚠️
model/command_classifier.go 91.62% 15 Missing ⚠️
daemon/git_context.go 89.83% 12 Missing ⚠️
model/sysstat_linux.go 84.21% 3 Missing ⚠️
commands/query.go 96.42% 1 Missing ⚠️
Flag Coverage Δ
unittests 85.79% <90.74%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
model/ai_service.go 90.24% <ø> (ø)
model/sysstat.go 100.00% <100.00%> (ø)
commands/query.go 97.36% <96.42%> (-0.68%) ⬇️
model/sysstat_linux.go 84.21% <84.21%> (ø)
daemon/git_context.go 89.83% <89.83%> (ø)
commands/query_context.go 85.14% <85.14%> (ø)
model/command_classifier.go 91.79% <91.62%> (-4.21%) ⬇️
model/query_context.go 91.36% <91.36%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread model/command_classifier.go
Comment thread model/query_context.go
- classifyDocker, classifyKubectl and classifySystemctl read the first
  argument as the subcommand, so `kubectl -n prod delete pod web`,
  `docker --context x rm -f web` or `systemctl --force poweroff` fell
  through to "edit" and auto-ran with ai.agent.edit. A leading global
  option now classifies as "other" (never auto-run), as git already did.
- ParseJustRecipes indexed fields[0] on lines made only of non-ASCII
  whitespace (\v, \f, NBSP), panicking inside a collector goroutine and
  crashing `shelltime q`. Skip such lines.
- Recover panics in query context collectors so a collector bug can
  only drop that piece of context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gybkwT4DrTkNF4wKq54JT
@claude

claude Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@AnnatarHe
AnnatarHe merged commit d9498f8 into main Oct 6, 2026
8 checks passed
@AnnatarHe
AnnatarHe deleted the claude/tender-keller-qkfiy2 branch October 6, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants