qwen-code/docs
qqqys 673454839e
feat(memory): add a git-shared team memory tier (#5886)
* feat(memory): add a git-shared team memory tier

Add an opt-in third auto-memory tier — TEAM — stored in the repository at
`.qwen/team-memory/` and shared with collaborators through git. The private
USER and PROJECT tiers are unchanged.

- Enabled via the `memory.enableTeamMemory` setting (default off) or the
  `QWEN_CODE_MEMORY_TEAM` env override; inactive otherwise.
- The model is taught a three-tier prompt and when to route saves to the
  shared tier (project-wide conventions, team-wide references).
- Writes to the team directory are scanned for secrets (a curated
  gitleaks-style ruleset) and hard-blocked, since the directory is committed
  to the repo and visible to everyone with repo access.
- Team writes default to 'ask' permission — not auto-allowed like the private
  tiers — so they are proposed for confirmation in the default approval mode
  and surface in the git diff for review.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(vscode): update settings schema for team memory

* fix(memory): harden team memory review gaps

* fix(memory): address team-memory PR review feedback

- secret-scanner: AWS key suffix is base62 ([A-Z0-9]), not base32
  ([A-Z2-7]) — old class missed IDs with digits 0/1/8/9; add a
  positive test for such a key.
- paths: document that isAnyAutoMemPath deliberately excludes team
  memory (security-load-bearing — team writes must stay 'ask').
- extractionAgentPlanner: name the team-memory dir in the scoped deny
  message so a denied team write is debuggable.
- prompt: spell the tier count via a number-word lookup so a future
  4th tier never silently reads "two".
- team-memory-secret-guard: debug-log matched rule IDs only (never the
  secret content) when a write is blocked.
- edit: when a blocked team write's secret already exists in the
  on-disk file, tell the user to delete the committed secret.
- docs: caveat that a directory-form gitignore (.qwen/) makes the
  !-reinclude a no-op; use the file-glob form.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory secret guard on blocked writes and notebooks

- write-file: blocked team-memory secret write now returns an `error`
  field (INVALID_TOOL_PARAMS) so the framework treats it as a failure
  instead of a silent success, mirroring edit.ts.
- notebook-edit: scan the serialized .ipynb that hits disk through the
  team-memory secret guard before writing, blocking with the same
  `error` field; behavior unchanged for non-team-memory paths.
- store: log non-ENOENT errors in readTeamAutoMemoryIndex before
  re-throwing, so EACCES/EIO/ELOOP no longer vanish via config.ts's
  best-effort `.catch(() => null)`.
- config: emit a debug diagnostic when team memory is enabled but
  suppressed by the untrusted-workspace trust gate.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): auto-generate the team memory index

Generate the team `MEMORY.md` index by scanning the saved memory files
instead of having the model hand-maintain it. The index is derived and
ordered by path, so the committed file is deterministic across machines.
This removes the git merge-conflict surface a hand-edited shared index would
have, keeps the index consistent with the files, and frees the model from the
two-step save for the team tier.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* feat(memory): opt-in git sync for team memory

Add an opt-in, best-effort git sync of team memory at session start so a
daemon can share team memory through the remote without manual git. Off by
default; enabled with `QWEN_CODE_MEMORY_TEAM_SYNC=1`.

It commits the team directory (only that path), pulls fast-forward-only, then
pushes. `--ff-only` is deliberate: it never creates a merge commit or a
conflict — a diverged branch is skipped rather than touching the working tree.
All git is best-effort and never throws, so a failure cannot break a session.
Commits can be attributed to the acting user via an optional author, for
cooperative per-user attribution on a shared daemon.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): close team-memory secret-guard regex + test gaps

- secret-scanner: add `_` to the two T3BlbkFJ-marker char classes so legacy
  base64url OpenAI keys with underscores in the body no longer bypass the
  scanner; extend the OpenAI positive samples with an underscore key body.
- edit: cover the pre-existing-secret branch — seed the on-disk file with a
  full detectable token, assert the distinct "already exists" message and that
  the blocked edit leaves the file untouched.
- team-paths: cover the first-ever write before .qwen/team-memory exists,
  exercising realpathNearestExisting walking up to an existing ancestor.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(core): add Google OAuth client secret and SendGrid secret-scanner rules

Extend the team-memory secret scanner with two more high-confidence,
distinctive-prefix credential patterns (both gitleaks rules):

- gcp-oauth-client-secret: GOCSPX- prefix + 24 base64url chars,
  placed next to the existing GCP AIza API-key rule.
- sendgrid-api-token: SG. + 22-char id + . + 43-char secret.

Both have a near-zero false-positive bar, matching the curated set's
existing criteria. Add a load-bearing positive sample per rule.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): warn when team memory is enabled but not git-shareable

When the team tier is active, emit a one-time startup warning if its
directory is git-ignored (e.g. a directory-form `.qwen/` ignore that a
`!`-reinclude cannot escape) or there is no git root — so the tier never
silently shares nothing. Gated on the active (trusted + enabled) tier;
probes a representative file path with `git check-ignore`, and is
latched so refreshHierarchicalMemory re-runs do not re-emit it.

Closes the last open review thread (wenshao + qwen-code-ci-bot both
flagged the silent-inert case); the docs caveat already shipped.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): dedup team index by description; fix empty-frontmatter parse

The team index now collapses entries that share a (normalized) description
into one line — listing the other files via "(also: …)" — so two people
saving the same shared fact don't surface as duplicates. Nothing is dropped;
every file path is preserved and the grouping is deterministic (input is
path-sorted).

Also fix a pre-existing frontmatter bug: parseFrontmatterValue used `\s*`,
which crosses newlines, so an empty value (`description:`) captured the next
frontmatter line. Bound it to horizontal whitespace.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* feat(memory): add memory.enableTeamMemorySync setting

Team-memory git sync was env-only (QWEN_CODE_MEMORY_TEAM_SYNC). Add a
first-class `memory.enableTeamMemorySync` setting mirroring the
enableTeamMemory plumbing (settingsSchema, cli/core config, acpAgent
type/schema/default/keys/normalizer, desktop mirror), with the env var kept
as a 0/1 override. Off by default. Docs updated.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): audit hardening — resilient scan, deterministic team index

From a multi-round adversarial audit of the team-memory tier:

- scan: a single unreadable memory file (EACCES, or a TOCTOU delete during
  `git pull`) no longer rejects the whole scan and wipe every memory from the
  index — per-file failures are caught and skipped (debug-logged).
- indexer: order the team index by code unit, not `localeCompare()`, so the
  committed MEMORY.md is byte-identical across machines/locales and the
  ff-only sync can't wedge on index churn.
- scan: escape the frontmatter key before building its RegExp (latent hardening).
- config: surface a skipped/failed team-memory sync at debug level instead of
  silently swallowing it.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): deterministic team index cap + surface diverged sync

Round-2 audit follow-ups:

- scan: the >200-file cap selected its subset by mtime + locale basename, which
  differs per machine/locale — so a large team's committed index churned and
  wedged the ff-only sync. Team scans now cap by code-unit path (deterministic);
  private tiers keep mtime-recency (never shared, so it's fine).
- sync: a diverged branch made `pull --ff-only` (and the push) fail with no
  `skippedReason` — a silent no-op for a user who opted into sync. Now reported
  as `pull-failed` / `push-failed` and logged. Docs note pull/push are
  whole-branch, not path-scoped.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): regenerate settings schema for enableTeamMemorySync

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory git sync (reconcile-first, scoped push, safe failure)

The opt-in team-memory git sync could publish unrelated local commits, wedge a
two-writer branch, leave team files staged on commit failure, and hang session
start on a credential prompt. Harden all four maintainer-flagged criticals:

- Reconcile first: run `git pull --ff-only` BEFORE committing so the local team
  commit lands on top of upstream instead of diverging; a diverged branch is
  skipped cleanly as `pull-failed` rather than committing then wedging `--ff-only`.
- Scoped push: push only when THIS sync created the commit and the branch was
  not already ahead of `@{u}` (pre-sync), via an explicit single-branch refspec
  (`HEAD:<merge-ref>`) — never an unqualified `git push`. New `local-ahead` skip.
- Safe failure: unstage the team path when the commit fails (hook/GPG/identity)
  so a user's next manual `git commit` cannot sweep team files in.
- Non-interactive git: force `GIT_TERMINAL_PROMPT=0` + batch-mode SSH, SIGKILL on
  timeout, and a shorter timeout so sync cannot block session start on a prompt.

config refresh: rebuild the team index BEFORE syncing (so a fresh MEMORY.md is
committed/pushed, with a re-build after a pull that brought new files) and key
the shareability latch by projectRoot so a newly entered repo is re-checked.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory index/scan/path security

Address the security/correctness criticals from PR #5886 review. Team
memory is committed to the repo and loaded into every collaborator's
system prompt, so its write/scan/classification paths are trust
boundaries.

- indexer: pass noFollow on the team MEMORY.md write and reject a
  symlinked team root, so a committed `.qwen/team-memory` (or index)
  symlink can't redirect the generated index outside the repo.
- indexer: sanitize attacker-controlled frontmatter title/description
  (strip control/zero-width/bidi chars, collapse newlines, defang
  code/link markdown, cap length) before embedding into MEMORY.md, to
  prevent prompt injection into the shared context.
- indexer: skip the rewrite when the generated index is byte-identical,
  avoiding mtime churn and no-op commit cycles.
- scan: normalize CRLF before parsing so a Windows checkout's team files
  don't silently drop out of the shared index.
- paths: resolve a dangling final-component symlink via lstat/readlink in
  realpathNearestExisting, so a `decoy -> team-memory/leak.md` (missing
  target) is classified as team and the secret scanner can't be bypassed.
- secret-scanner: bound both quantifiers in the openai-api-key T3BlbkFJ
  rule ({20,512}) to remove O(n^2) ReDoS backtracking; real keys still
  match.
- team-memory-git-status: also probe a representative topic path so a
  repo that re-includes the index but ignores topic files is flagged as
  non-shareable.
- notebook-edit: run the team-memory secret scan in validateToolParamValues
  too, for parity with edit/write-file.

Adds load-bearing tests for each fix.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): sanitize team index paths and verify whole-path containment

Refines the earlier team-memory security hardening with two fixes the
first pass left open:

- sanitizeIndexPath now defangs the attacker-controlled relativePath
  rendered into the committed MEMORY.md — at BOTH the `](path)` link
  target and the "(also: ...)" dedup suffix. A git filename may legally
  contain newlines and markdown delimiters, so a raw path could inject a
  second physical line (e.g. "- SYSTEM:") and reopen the prompt-injection
  structure the patch was closing for title/description.
- rebuildTeamAutoMemoryIndex now realpath-resolves the whole team root
  and requires it to equal repoRoot/.qwen/team-memory. The prior lstat
  only inspected the leaf, so a symlinked PARENT component (e.g.
  `.qwen -> /tmp/out`) let scan/write escape the repo undetected while
  the guard believed it had rejected symlinks.

Also adds a regression test pinning the security-load-bearing invariant
that isAnyAutoMemPath excludes team paths (so team writes stay ask-gated).

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): keep team index link targets addressable, not just safe

The earlier injection fix rewrote legal filename chars (`(` `)` `[` `]` and
backticks) to `_` and used that destroyed value as the ACTUAL MEMORY.md link
target. A real file `feedback/a(b).md` was emitted as `](feedback/a_b_.md)`,
which does not exist (or, worse, collides with a different real file) — the
link was injection-safe but no longer addressable.

Replace `sanitizeIndexPath` with `encodeIndexPathTarget`, a reversible
percent-encoder: every char outside an addressable allowlist (alphanumerics
and `/ . - _ ~`) is percent-encoded, so breakout chars become inert ASCII
(newline->%0A, `(`->%28, `)`->%29, space->%20, backtick->%60, ...). The target
is a single line with no `](`/`)` breakout, yet `decodeURIComponent` recovers
the exact original path so the link resolves to the real file. `/` is kept
literal so the path still works as a relative reference. Applied at both index
sites (the main `](path)` link and the `(also: ...)` suffix). The display label
(title) stays non-reversibly sanitized as before — it is not addressable.

Update the path tests to assert the emitted target percent-decodes back to the
real relative path, and add a `feedback/a(b).md` case. The newline+`]`/`)`
injection assertions remain (mutation-verified: they fail if the encoding is
dropped).

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): gate team-memory sync on the index safety check

rebuildTeamAutoMemoryIndex throws when the team root is a symlink that
could redirect the committed index outside the repo, but the surrounding
.catch swallowed the error and syncTeamMemory ran independently — so a
refused symlink/escape still got git add/commit/push'd on the out-of-repo
dir, defeating the indexer's refusal. Capture whether the rebuild
succeeded and only sync when it did, so a failed safety check skips sync
entirely.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): harden team-memory git sync and cover execute-time secret scan

Several robustness fixes to the team-memory git sync, plus a missing test:

- Respect a user's GIT_SSH_COMMAND: append our non-interactive batch guards
  (-oBatchMode=yes -oConnectTimeout=5) onto it instead of clobbering it, so a
  custom identity (-i), proxy jump (-J), or non-standard port is preserved.
- Skip cleanly on a detached HEAD (new `detached-head` skippedReason) instead
  of creating an orphaned commit that can never be pushed. A branch with no
  upstream still commits locally as before.
- Use SIGTERM (not SIGKILL) as the git kill signal so a timeout mid-commit/-pull
  lets git release index.lock and clean up; the non-interactive ssh guards
  already bound the network-hang case.
- docs(memory): correct the sync description to match the implementation
  (rebuild index, ff-pull BEFORE commit, push only the scoped sync commit,
  skip on divergence/no-upstream).
- test(notebook-edit): cover the execute-time team-memory secret scan (the
  full-notebook backstop), distinct from the existing validate-time test.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(memory): split security vs operational team-sync failures; per-op git kill signal

Refine the recent team-memory sync changes per review:

- indexer: throw a typed TeamMemoryRootSecurityError for the symlink/escape
  rejection so the sync gate can tell a SECURITY refusal apart from an
  OPERATIONAL IO failure. config now blocks sync ONLY on the security class;
  an operational rebuild failure (EACCES/ENOSPC/EPERM) is logged but
  self-corrects on the next rebuild and no longer permanently gates legitimate
  team sync. The security invariant (never add/commit/push an escaping root)
  is unchanged.
- team-memory-sync: pass a per-op kill signal to tryGit — SIGKILL for the
  hang-prone network/ref ops (pull/push/rev-parse/symbolic-ref; no index.lock
  to corrupt), SIGTERM for mutating ops (add/commit) so git can release locks
  and clean up. Node's execFile timeout does not escalate to SIGKILL on its
  own, so a child trapping SIGTERM could hang past the timeout.
- team-memory-sync: thread the resolved branch into resolvePushTarget so it no
  longer re-spawns `git symbolic-ref` after the detached-HEAD check.
- docs: fix a broken sentence in the team-memory git-sync section.
- tests: cover both rebuild failure classes (security -> sync skipped;
  operational -> sync still runs) and a positive gate (rebuild ok + sync
  enabled -> sync runs); assert the typed error on the indexer symlink tests.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-27 12:29:48 +00:00
..
design fix(core): stop repeated truncated write_file/edit retries from looping (#5934) 2026-06-27 12:17:12 +00:00
developers refactor(cli): Split serve server routes (#5809) 2026-06-26 12:47:47 +00:00
e2e-tests feat(worktree): Phase D — startup --worktree flag + symlinkDirectories + PR refs (#4381) 2026-05-27 17:04:51 +08:00
plans feat(core)!: redesign auto-compaction thresholds with three-tier ladder (#4345) 2026-05-25 21:11:08 +08:00
superpowers fix(core): add streaming inactivity timeout to the OpenAI pipeline (#5827) 2026-06-24 23:33:55 +00:00
users feat(memory): add a git-shared team memory tier (#5886) 2026-06-27 12:29:48 +00:00
verification/abort-controller-refactor fix(core): stop AbortSignal listener leak in long sessions (MaxListenersExceededWarning) (#4366) 2026-05-26 14:21:49 +08:00
_meta.ts feat: refactor docs 2025-12-05 10:51:57 +08:00
declarative-agents-port.md feat(core): port declarative-agent mcpServers + hooks (CC 2.1.168 parity follow-up) (#4996) 2026-06-12 14:15:51 +08:00
index.md fix: lint issues 2025-12-19 15:52:11 +08:00
yaml-parser-replacement.md feat(core): port declarative-agent mcpServers + hooks (CC 2.1.168 parity follow-up) (#4996) 2026-06-12 14:15:51 +08:00