user-preferences

User preferences — language and settings from preferences.json.

SKILL.md

GitHub

User Preferences

Read one file; never merge

.dev-team-agents/resolved/preferences.json      ← read THIS

It is the resolved view of three layers, written by devteam bind and devteam sync. Read it and stop: no agent merges layers, and no agent reads a source layer to answer "what is the language?".

Fallback, one level only: if that file does not exist, the project is not bound (or predates v3) — read .dev-team-agents/user-data/preferences.json, the v2 location. Nothing else.

The resolved file carries a _generated_by key naming the command that writes it. Editing it by hand works until the next sync silently discards the edit, which is why writes go elsewhere.

Write through a layer, never the projection

shipped defaults  ->  global user  ->  this project      (later layers win)
Layer Where Set it with
Defaults <core version>/scripts/lib/preferences-defaults.json Not writable — it ships
Global user <data store>/preferences.json devteam prefs set <key> <value>
This project <data store>/projects/<project_id>/preferences.json devteam prefs set <key> <value> --scope project

devteam prefs list shows every key with the layer it came from; devteam prefs unset <key> drops it from a layer so the one below applies again. All three layers are personal and none is committed — see project.json § context_paths in skills/shared/project-context/SKILL.md for the topology that is committed, and why it is not a preference.

Consent keys — telemetry and auto_update — resolve to false when no layer sets them. The shipped default is true because the installer prompts there; an absent value anywhere else means the user never saw a prompt, and that reads as "no". devteam prefs list reports those as consent-withheld rather than defaults, so the distinction is visible.


Schema and Defaults

Canonical source: scripts/lib/preferences-defaults.json is the single source of truth for the default schema. It is read by install.sh (on install/update) and by the session-start.sh health-check backfill, which fills any missing key on every session. The block below mirrors it — keep them in sync.

{
  "language": "pt-BR",
  "context_window_percent_warning": 55,
  "context_window_percent_limit": 60,
  "suppress_notifications": false,
  "session_summary_max_days": 30,
  "session_summary_max_entries": 30,
  "docs_stale_after_days": 30,
  "auto_update": true,
  "update_check_interval_hours": 24,
  "model_max_tokens": 200000,
  "session_no_commit_turns": 8,
  "telemetry": true,
  "worktree_active": true,
  "worktree_base_branch": null,
  "worktree_commit_action": "ask",
  "auto_learn_before_commit": true,
  "worktree_path": ".worktrees",
  "worktree_docker_isolate": true,
  "qa_browser": null,
  "ci_cd_detected": null
}

These defaults apply only to a preferences.json that does not exist yet. An existing file is never rewritten: install.sh merges with existing values winning, and the session-start.sh backfill only adds absent keys.

Consent keys — telemetry and auto_update. Both default to true in a fresh file, where the installer prompts for them. They are never backfilled as true into a preferences.json that already exists: that file's owner never saw the prompt for a field added after they installed, so an absent key is read as "no". Both install and the session-start backfill write false in that case.

Field Type Default Purpose
language string "pt-BR" Conversation language (BCP 47 tag). Docs remain English.
context_window_percent_warning number 55 % at which agents emit a warning notification
context_window_percent_limit number 60 % at which agents emit a critical notification
suppress_notifications bool or array false false = none suppressed; true = all suppressed; ["info"] = suppress by type
session_summary_max_days number 30 Days before session-summary entries are trimmed
session_summary_max_entries number 30 Maximum number of entries in session-summary.md
docs_stale_after_days number 30 Days before project.md, session-summary.md, and the last health check are considered stale
auto_update bool true Auto-update dev-team-agents when a new version is detected (consent key)
update_check_interval_hours number 24 Hours between update checks
model_max_tokens number 200000 Context window for the active model
session_no_commit_turns number 8 Turns of work with a dirty tree and no commit before a one-time-per-session warning
telemetry bool true Anonymous usage telemetry (opt out with false) (consent key)
worktree_active bool true Default to a git worktree per task without asking
worktree_base_branch string or null null Base branch for worktrees (null = auto-detect)
worktree_commit_action string "ask" Default post-commit action in an active worktree: ask, finalize, rebase, or commit-only
auto_learn_before_commit bool true Silently run /devteam:learn before every commit, its docs committed with the work (false = skip by default)
worktree_path string ".worktrees" Directory where worktrees are created
worktree_docker_isolate bool true Isolated Docker Compose stack per worktree (when Docker present)
qa_browser string or null null Browser used when the in-app browser is unavailable — qa-specialist and the Visual Check Gate (null = ask on first use)
ci_cd_detected bool or null null Cached GitHub Actions detection (null = unchecked)

For where each field is actually read and applied, see the "Consumed at" column in CLAUDE-md/preferences.md — the canonical consumer table, kept current whenever a field's wiring changes.

Proposing a new value mid-session. When the user repeatedly corrects the same behavior, CLAUDE.md § Correction Pattern Rule governs whether to propose persisting it here. That rule — not this file — owns the trigger (2+ corrections) and the confirmation step; this file only owns the schema the proposal must map to. Never write a field here without the AskUserQuestion confirmation that rule requires.


Language Policy

Two distinct rules govern language in dev-team-agents:

Output type Language rule
Documents written to disk (ADRs, session-summary, plan files, changelogs, commits, code comments) Always English — regardless of preferences.json
Conversation with the user (responses, questions, explanations, notifications, plans presented for approval) Use language from preferences.json — fallback to English if absent or unreadable

The conversation rule holds for every message, including the closing summary of a task written right after an English commit or session summary — that is exactly where it used to drift. The UserPromptSubmit hook user-prompt-submit/04-language.sh restates it on every prompt as [DEVTEAM:LANGUAGE]; follow that line.

How agents read the user's language:

PREFS=".dev-team-agents/resolved/preferences.json"
[ -f "$PREFS" ] || PREFS=".dev-team-agents/user-data/preferences.json"
USER_LANG="en"
if [ -f "$PREFS" ] && command -v python3 >/dev/null 2>&1; then
    USER_LANG=$(python3 -c \
        "import json; d=json.load(open('$PREFS')); print(d.get('language','en'))" \
        2>/dev/null || echo "en")
fi

For agents: read the file named in § Read one file; never merge at the start of each session. Use the language value when writing any response directed at the user. If neither file exists, use English.


suppress_notifications Handling

Value Behavior
false No suppression — all notifications shown
true All notifications suppressed
["info"] Only info type suppressed
["warning", "critical"] warning and critical suppressed

Any unrecognized value → treat as false (no suppression). Never fail — graceful degradation only.


Missing File Behavior

If preferences.json does not exist:

  • Use English as the conversation language
  • Use all schema defaults for thresholds and intervals
  • Emit a warning notification at session start prompting the user to run setup-assistant

If a specific field is missing from the file (partial schema), use its default value.


Reading Individual Fields (shell)

_pref() {
    local field="$1" default="$2" prefs=".dev-team-agents/resolved/preferences.json"
    [ -f "$prefs" ] || prefs=".dev-team-agents/user-data/preferences.json"
    python3 -c \
        "import json,sys; d=json.load(open(sys.argv[1])); \
         print(d.get('$field', $default))" "$prefs" 2>/dev/null || echo "$default"
}

LANG=$(_pref language '"en"')
WARN_PCT=$(_pref context_window_percent_warning 55)
LIMIT_PCT=$(_pref context_window_percent_limit 60)
SUPPRESS=$(_pref suppress_notifications false)
STALE_DAYS=$(_pref docs_stale_after_days 30)
AUTO_UPDATE=$(_pref auto_update false)
CHECK_HOURS=$(_pref update_check_interval_hours 24)