125 Commits
Author SHA1 Message Date
Saul Moro f19f9de23a ci(lint): enable type-aware linting with no-floating-promises and no-misused-promises (#836) (#863)
* ci(lint): enable type-aware linting with no-floating-promises and no-misused-promises

tsc does not report a promise nobody awaits or an async callback handed to an
API that ignores its result. In a CLI that is an error nobody sees. oxlint's
--type-aware mode checks both through oxlint-tsgolint.

- package.json: add oxlint-tsgolint 7.0.2003 (oxlint 1.85.0 needs
  >=7.0.2001) and pass --type-aware to `npm run lint`. The package ships
  prebuilt binaries as optional dependencies with no install script, so CI's
  `npm ci --ignore-scripts` needs no change.
- .oxlintrc.json: turn on typescript/no-floating-promises and
  typescript/no-misused-promises. Under src/__tests__/**, no-misused-promises
  skips checksVoidReturn.arguments: vi.spyOn(fse, ...) types the spy from
  fs-extra's last overload, the callback one that returns void, so every
  correct async mockImplementation is flagged.
- --type-aware also turns on oxlint's default type-aware rules. The ones
  with hits on main stay off, since each is its own evaluation under #836.
  The rest of the defaults have no hits and stay on.

Fixes, all behavior-neutral:
- dashboard.ts: the SSE debounce and the PID check become named async
  functions called with `void`; both already catch everything they throw.
  The HTTP handler becomes handleRequest, and createServer catches its
  rejection, logs it and answers 500.
- import-repo-list.ts: track in-flight imports in a Set, so the unused array
  splice returned is gone.
- scripts/mock-teamai-server.mjs: same handler wrapper as the dashboard, so a
  malformed JSON body answers 500 instead of an unhandled rejection.
- ai-client.test.ts: schedule the mock process events with queueMicrotask.

Refs #836.

* ci(lint): turn on typescript/no-useless-default-assignment

1 prod hit, type-only: syncTeamUpdatesToLocal's last parameter was
`placedRules: Record<string, string> | undefined = undefined`; it is now
`placedRules?: Record<string, string>`.

Refs #836.

* ci(lint): list only the type-aware rules that would otherwise run as off

consistent-return, no-unnecessary-boolean-literal-compare,
no-unnecessary-type-arguments, no-unnecessary-type-assertion,
no-unnecessary-type-conversion, no-unnecessary-type-parameters and
no-unsafe-type-assertion are not default rules, so listing them as off
changed nothing. The config keeps off only the nine default type-aware rules that
--type-aware would turn on.
`oxlint --type-aware --print-config` gives the same set of active rules
before and after.

Refs #836.
2026-09-29 11:07:58 +08:00
Saul Moro f7da1bb8b6 ci(lint): add oxlint and fail CI on any warning (#828) (#839)
* fix(tags,roles): honor --dry-run in tags subscribe, tags unsubscribe and roles set (#836)

The three commands saved the local config and reset lastPullRev even
under the global --dry-run, which is documented as "Preview mode, no
changes made". Their siblings (tags add/remove, roles init/add/remove/
update) already return early with a [dry-run] message.

The roles set preview names the additional roles it would save,
including none, because a real run replaces the existing list.

oxlint reported the unused options parameter in tagsSubscribe and
tagsUnsubscribe; rolesSet has the same bug but reads options.add.

* chore(lint): add oxlint with its default rules

Pinned to an exact version so a new default rule arrives in its own PR,
not as a CI failure on an unrelated one. The no-unused-vars options
keep oxlint's _ ignore patterns and add ignoreRestSiblings, which the
rest-omit in dashboard.ts relies on to keep config and roots out of
/api/workspaces.

* style(lint): apply oxlint safe fixes

Drop redundant escapes in regex character classes and template literals,
empty-object fallbacks in object spreads (spreading undefined adds
nothing), and anchored regexes that are plain startsWith/endsWith
checks. No behavior change.

* refactor(lint): remove unused imports and an unused catch binding

Applied with oxlint --fix-suggestions and reviewed by hand. Every
removed whole import is a library module with no import-time side
effects.

* refactor(lint): remove dead code reported by no-unused-vars

Each hit was checked against its callers and git history; none is
missing wiring (the two that were, tags subscribe/unsubscribe, are fixed
in the preceding commit). Removed: unused locals and functions, the
options parameter of tagsList, rolesList and generateDigest (read-only
commands), the never-read interactive option of importFromRepo, the
empty test/e2e.mjs left over from the E2E migration, and a try/catch
that only rethrew. new Array(n) becomes Array.from. No behavior change.

* test(lint): fix lint hits in tests

- contribute dry-run test asserted nothing; it now checks that the run
  leaves the repo/HOME tree unchanged (verified to fail when the dry-run
  early return is removed).
- Drop a no-op expect(result).not.toThrow on a string.
- Keep undefined in two optional-chain casts so a regression fails the
  assertion instead of throwing a TypeError.
- Remove unused locals, helpers and imports; new Array(n) becomes
  Array.from.

* refactor(lint): write control-character classes as \p{Cc}

no-control-regex flags literal control ranges. \p{Cc} names the same
set (C0, DEL, C1) and reads as what it means. Checked against the old
classes on every code point from U+0000 to U+10FFFF: manifest-schema and
agent-format match exactly, and contribute-check's normalization
pipeline produces the same output. The test assertion is now stricter
and checks every control character the sanitizer removes.

* refactor(lint): remove disable directives for rules that are not enabled

Four eslint-disable comments named rules this repo never ran
(no-await-in-loop, @typescript-eslint/no-explicit-any), so they
suppressed nothing.

* ci(lint): fail CI on any oxlint warning (#828)

npm run lint runs oxlint --deny-warnings and runs before the type check
in both GitHub Actions and Coding CI. The repo is at zero warnings, so
new code must stay clean. --report-unused-disable-directives also fails
on a disable comment that suppresses nothing, so a suppression cannot
outlive the code it was written for. CLAUDE.md, AGENTS.md,
CONTRIBUTING.md and the PR template list the command so contributors
and agents run it before opening a PR.

Closes #828

* chore(lint): pin oxlint 1.16.0, the newest release that accepts Node 20.0

oxlint 1.17.0 and later declare engines.node ^20.19.0 || >=22.12.0,
while the repo supports Node >=20. 1.16.0 declares >=8, supports
--deny-warnings and --report-unused-disable-directives, and reports 0
warnings on this branch.

* Revert "fix(tags,roles): honor --dry-run in tags subscribe, tags unsubscribe and roles set (#836)"

This reverts commit 224d459c99.

* refactor(lint): mark the unused options parameter of tags subscribe and unsubscribe

With the #837 dry-run fix reverted out of this PR, both functions no
longer read options. The underscore prefix keeps the signature and call
sites unchanged, so #837 can rebase onto it by renaming the parameter
back.

* chore(lint): restore oxlint 1.85.0

This reverts commit e2347efa. oxlint is a devDependency, so its Node
requirement (^20.19.0 || >=22.12.0) never reaches users installing
teamai-cli, and CI's node-version 20 resolves to the latest 20.x.
Staying on 1.85.0 keeps the #836 warning counts and the planned
type-aware follow-up on the same version.

* docs(contributing): note the Node version npm run lint needs

* docs(agents): note the Node version npm run lint needs
2026-09-26 19:56:03 +08:00
Saul Moro 21cb76aa49 feat: one namespace model for every resource type (#707) (#816)
* chore: start one namespace model for every resource type (#707)

* refactor(pull): check agent and skill namespace collisions with one resolver (#707)

Add src/namespace-resolver.ts, the pure rule tickets 02-05 build on: an
active namespace item replaces the root item of the same name, and a name
twice in one place or in two active namespaces is a tagged conflict naming
both sources. The result depends only on the active order, not read order.

Agents and skills now run their duplicate checks through it and throw the
same messages. Agents still treat root + namespace as an error.

Add fast-check for the resolver's property tests.

* feat(env,hooks,mcp): scope env, hooks and MCP servers by namespace (#707)

env/<ns>/env.yaml, hooks/<ns>/hooks.yaml and mcp/<ns>/mcp.yaml are read where
<ns> is active in resources.env/hooks/mcp; a namespace entry replaces the root
entry of the same key, hook id or server name. A broken active file, a name
twice in one file or in two active namespaces stops that type for the run and
keeps what is installed. Per-entry projects: (and roles: on env) reach nobody;
roles: on hooks and MCP keeps filtering with a deprecation warning. Unknown
resources: keys warn instead of failing the manifest.

* feat(pull): let an active namespace item replace the root item for skills, agents, rules and claudemd (#707)

With a role or project configured, an item in an active namespace now
replaces the root item of the same name, whole:

- agents by stem: root + namespace is no longer a duplicate error, and a
  recorded (placed) agent replaces the root agent too
- skills by name, including a root skill received through a tag; an
  install removes the files of the version it replaces
- rules by first-level file name, in tool dirs and Hermes' SOUL.md block
- claudemd files by name in the managed block

Two active namespaces with one rule or claudemd name stop that type for
the run and keep what is installed. Push writes an edited overridden
skill, agent or rule back to its namespace, and the skills push scan
covers role and project namespaces. The placement record is withdrawn
by a same-name shared-root file only in legacy mode. Recall indexes the
skills pull delivers. doctor lists overrides, and in legacy mode repeated
names, as notes. Legacy mode is otherwise unchanged.

* fix(pull): deliver both namespace rules and claudemd files of one name (#707)

Rules and claudemd have no namespace-vs-namespace conflict: each
namespace rule keeps its own local path and each claudemd file its own
place in the block, so two active namespaces with one first-level name
are both delivered, as before. Only root suppression applies.

doctor override and legacy repeated-name notes now use the same wording
as the env, hooks and MCP ones. The usage guide and admin reference say
to keep overridable shared content at the root, with an example.

* fix(pull): stop only skills or agents on a namespace collision (#707)

Two active namespaces with one skill name or agent stem used to throw
and abort the whole scope, so rules, env, docs, cleanup and the search
index were skipped too. resolveDesiredSkills, resolveDesiredAgents,
scanRoleAwareSkills and filterAgentsByNamespaces now return a tagged
conflict. pull warns, leaves that type as installed (no install, no
inactive-namespace sweep) and syncs the rest. doctor reports the
collision as before; recall indexes no skills while it stands.

* feat(env,hooks,mcp): namespace flags, origins in status and doctor, docs (#707)

env add/remove take --role/--project; remove mcp searches every file and asks
for --role/--project when several define the name; push picks up
env/<ns>/env.yaml. status, list and doctor show where each entry comes from;
doctor lists overrides as notes and per-entry roles:/projects: as one
informational check. Usage guides and the admin skill reference describe the
namespace files; the #668 e2e moves onto them.

* test(env): show a broken env file stops env only (#707)

* fix(remove): remove an MCP server from the root file by default (#707)

remove mcp <name> follows push's convention: mcp/mcp.yaml when it defines the
name, else the one namespace file that does; --role/--project pick a namespace.
Only a name several namespace files (and not the root) define is refused.

* test(remove): expect namespace files in sorted order (#707)

* feat(docs): deliver a declared docs namespace only where it is active (#707)

A docs/<ns>/ that any role or project lists under resources.docs now
reaches only members with that namespace active; an undeclared
docs/<dir>/ stays shared. Leaving a namespace removes its local docs that
are byte-equal to the team copy and keeps edited ones with a line.
team-codebase is rejected as a docs namespace. The search index (pull,
recall, contribute) and doctor's "Team docs delivered" use the same set.

* feat(models): scope team model profiles by namespace and bind keys to their gateway (#707)

models/<ns>/models.yaml, declared under resources.models, replaces the root
profile with the same id while <ns> is active. A team API key is stored per
profile id and base_url origin, so pull never writes a key next to a gateway
on another origin; it prints the models switch line instead. Conflicts and
broken files stop model updates for the run. models list and doctor show
where each profile comes from; push validates every models file.

* docs(models): describe model profiles by namespace and key binding (#707)

* docs: list docs among the axes declared by hand (#707)

* docs: describe one namespace model for every resource type (#707)

Rewrite the multi-project design doc's precedence section for
namespace-over-root, add the per-type conflict, failure and legacy-mode
rules, and replace the per-entry key rows in the product overview. The
JSON doctor notes now also carry namespace notes.

* docs(changelog): replace per-entry scoping with namespace files (#707)

Drop the beta-only per-entry projects: entry, add the namespace axes,
the override, the per-type failure policy, the roles: deprecation on
hooks and MCP, and the upgrade-every-member-first note.

* docs(changelog): say the model key binding re-keys once and affects only betas (#707)

* refactor(namespaces): one warn-once registry instead of the quiet flag (#707)

Namespace fallback warnings, entry notices and unknown resources: keys
now go through utils/warn-once, reset once per pull, so the quiet option
threaded through eleven signatures is gone. The one-line wrappers
resolveTeamEnv, resolveTeamMcpServers and resolveTeamProfiles are removed;
every caller uses resolveEntries/resolveEntriesFor with the type's reader.

* refactor(namespaces): shared entry-file helpers, no unsafe casts in new code (#707)

- listEntryFiles/entryFileAbsolutePath replace the per-type file listers
  in env, mcp and models and the repeated path joins.
- gatewaySuffix replaces three spellings of the gateway suffix.
- LATER_RESOURCE_TYPES and friends are named for what they are:
  HAND_DECLARED_RESOURCE_TYPES, HandDeclaredNamespacesShape.
- Error messages use instanceof Error; manifest role/project ids are
  narrowed instead of cast; mapResources builds a typed object; pull
  writes env through an EnvHandler instance instead of a cast.
- status keys counts by entry type; rules localNameFor reuses
  deliversEveryNamespace, whose false answer is now documented.

* refactor(desired): move the desired-set resolvers out of pull.ts (#707)

Commands must stay thin, and recall, contribute and doctor imported
pull.js only to learn what a member receives. The skills, agents, rules
and claudemd resolvers, RolePullContext and the index sources now live in
src/resources/desired.ts; root suppression, the override note and the
repeated-name grouping live in namespace-resolver.

- A skill or agent conflict is a tagged DeliveryConflict carrying the
  resolver's NamespaceConflict, rendered once by describeDeliveryConflict
  (wording unchanged); DesiredItems names the result union.
- DesiredItems keeps each override, so doctor no longer rebuilds skill
  and agent overrides by hand.
- recall and contribute share deliveredIndexSources; pull indexes through
  the same indexedSkills instead of a second copy.
- doctor: one unresolvableCheck for skills, agents and docs; the docs
  check reports an unreadable manifest instead of returning nothing; the
  namespace notes catch only the team-repo reads.
- docs withdrawal reuses utils pruneEmptyDirs.

* fix(pull): name both files in a skill or agent conflict (#707)

Story 8 asks for a message naming both files. A skill or agent conflict
named only the namespaces, and an agent defined twice inside one
namespace (agents/a/x.md next to agents/a/x.yaml) read as 'found in
active namespaces "a" and "a"'. The duplicate case now names its one
place, and both cases list the two files.

* fix(rules): only a delivered namespace rule replaces the root rule, in every tool (#707)

- The rules override ran before the tag filter, so a namespace rule the
  member's tag subscription excludes still suppressed the root rule and
  the member received neither. The tag filter now runs first.
- JoyCode, OMP, Pi and Copilot share their rule directory with the
  member's own rules, so the stale sweep deletes nothing there unless a
  tombstone names it: the root rule a namespace rule replaced stayed
  installed and both versions loaded (story 6). pullAllRules now removes
  such a copy while it is byte-equal to its render, as agents do.

* fix(recall): keep the indexed skills while a skills conflict holds them (#707)

On a skills conflict pull keeps the installed skills, but the index was
rebuilt with none, so recall returned none of the skills the member still
has. The index now keeps the skills entries it already held, and pull
does the same when resolving the skills fails.

* fix(hooks,mcp): fail hooks inject and mcp inject when team entries do not resolve (#707)

reconcileTeamHooksForConfig returned [] when the team hooks could not be
resolved, the same value as a team without hooks, so hooks inject printed
'Hooks injected into all AI tool settings' and exited 0 over a broken
hooks/<ns>/hooks.yaml. It now returns { ok: false }, and hooks inject
exits 1 after the warning that names the file. mcp inject said 'Already
up to date.' in the same case; the MCP reconcile now marks the result
unresolved and mcp inject exits 1.

* fix(models): bind a beta API key to the gateway it was sent to, once (#707)

- A key a 0.26.0 beta stored under team:<id> counted for whatever origin
  the root profile had now, so a root profile moved to another host got
  the old key written next to it. The first pull or models command that
  reads such a key now binds it to the origin TeamAI last wrote into the
  agents switched to that profile (the root's current origin when it is
  among them), else to the root's current origin, and never re-reads the
  unbound key. Pull then leaves a moved agent alone and asks for the
  switch.
- The 'switch to set a key' and 'no longer active' lines are written to
  debug.log too: SessionStart pulls run silent.
- Legacy mode, which reads no namespace, says a profile 'was removed'.
- A models command whose profiles do not resolve reports it with
  log.error and exit code 1, as env list and mcp list do, instead of
  throwing.

* fix(entries): keep 0.25 files that repeat a name under different roles: working (#707)

0.25.0 let hooks.yaml and mcp.yaml repeat a hook or server name under
different roles:, delivering every copy that passed the role filter (MCP
kept the last). The namespace resolver treated that as a duplicate, so a
member holding both roles, or a role-less member in a team with
projects.yaml, stopped receiving hooks or MCP entirely. During the
roles: deprecation window such a repeat is delivered as in 0.25; a name
repeated without roles: on every copy is still a duplicate.

Also restores the test that the role filter runs before
requireTeamScripts, so the transparency print lists only what will run.

* feat(doctor): say where each entry type's entries come from (#707)

The spec asks doctor, like status and the list commands, to show each
entry's namespace; doctor listed overrides only. For env, hooks, MCP and
models, a namespace contributing any entry now adds a note counting the
entries by origin, 'env: 3 received here (2 root, 1 checkout)', from the
describeOrigins that status uses.

* test(pull): env and hooks conflicts between two namespaces, and builtin: in a namespace file (#707)

Seam 1 asks every type to show, through pull, that two active namespaces
defining one name keep the installed state and name both files. Env and
hooks were covered only through doctor and the handler; so was the
warning for builtin: in a namespace hooks file.

* docs(skill-data): hooks and MCP edits are published with git, not teamai push (#707)

manage-admin.md told admins to publish hooks/MCP file edits with
teamai push, which sweeps only rules/, env/ and .codebuddy-plugin/, so
an agent following it would push nothing. It now says to commit and
push the file with git, as the usage guide does.

* refactor(models): read switched agents without a cast (#707)

* docs(changelog): doctor counts entries per namespace; inject fails on unresolved entries (#707)

* refactor(pull): drop imports the resolver move left unused (#707)

* fix(hooks): install the built-in hooks when the team hooks do not resolve (#707)

A first init or bootstrap whose team hooks did not resolve (a broken
namespace file, a clash, a duplicate id) installed no built-in hook, so
the session-start pull that heals the member never ran. Installed team
hooks are still kept; the built-in hooks are now installed where missing,
with the root file's builtin: overrides whenever hooks/hooks.yaml parses,
and with their defaults only in a tool with no teamai hook when it does
not. init and bootstrap say that the team hooks were not installed.

* fix(manifest): keep an unknown resources: key when roles and projects save (#707)

zod stripped the key this CLI only warns about, so a projects or roles
command run on this version deleted a newer CLI's type from the team
repo for everyone.

* fix(docs): withdraw a copy the team edited after delivery, not only an unchanged one (#707)

Withdrawing an inactive docs namespace compared the local copy with the
current team file only. A doc the team changed after the member received
it was then kept forever with a false 'you edited it' line. A copy equal
to an earlier team commit is what the mirror delivered, so it goes too.

* fix(rules): withdraw a replaced root rule edited in the same push, name a kept copy (#707)

In the JoyCode, OMP, Pi and Copilot rule dirs, a replaced root rule's
copy was removed only while it matched the current root rule. When the
admin edited the root rule and added its namespace override in one push,
the member's unedited copy stayed loaded beside the override, silently.
It is now also compared with the render at the last pull, and a copy
that is kept is named with the fix.

* fix(doctor): fail a check when team hooks or model profiles do not resolve (#707)

teamai status counts such a type as 0 and says to run doctor, but doctor
had failing checks only for env and MCP, so a duplicate hook id or a
two-namespace clash showed nothing there.

* fix(env): warn when --role names a namespace nothing declares (#707)

env add/remove --role <ns> wrote env/<ns>/env.yaml for a namespace no
role or project lists under resources.env, so the variable reached
nobody and nothing said so. The same applies to remove mcp --role.

* fix(env): find a changed namespace env file whose name is not ASCII on push (#707)

git ls-files quotes such a path by default, so it never matched the name
on disk and push skipped the change.

* fix(entries): an active env, hooks, MCP or models file that cannot be read stops the type (#707)

The readers folded every read error into 'file does not exist', so an
unreadable namespace file silently delivered the root entry in place of
its override. Only ENOENT is absence now; any other error is a broken
file, like one that does not parse.

* fix(entries): match env, hooks, MCP and models namespace dirs case-folded, as docs does (#707)

A declared namespace was joined onto the path as written, so with
env: [checkout] and a directory env/Checkout/, macOS and Windows members
got the override and Linux members the root value.

* fix(doctor): split the legacy claudemd paths on '/', not path.sep (#707)

listFilesRecursive always joins with '/', so on Windows every path was
one segment and a claudemd/<ns>/x.md beside claudemd/x.md was never
reported.

* fix(recall): index the rules pull delivers, not the whole rules/ tree (#707)

A namespace rule replaces the root rule of its name, but recall, contribute
and pull indexed every file under rules/: the replaced root rule and the rules
of inactive namespaces came back from recall. Index the resolved rule set, as
docs and skills already do.

* fix(entries): write a namespace file into the directory pull reads it from (#707)

Pull matches a declared namespace to its directory case-folded, but --role and
--project returned the spelling typed. On a case-sensitive filesystem
`env add --project checkout` created env/checkout/, which shadowed
env/Checkout/ and dropped its variables from delivery.

* fix(remove): remove no MCP server by a bare name while an MCP file does not parse (#707)

The team scan skips a file that does not parse. With mcp/mcp.yaml broken,
`remove mcp db` took the one readable checkout/db as the target and removed
it. Refuse and name the file unless the readable root defines the name.

* docs: rules in recall, namespace writes and remove mcp on a broken file (#707)

* fix(remove): say the MCP file --role or --project names does not parse, not that the name is missing (#707)

The team scan skips a file that does not parse, so `remove mcp db --project
checkout` with a broken mcp/checkout/mcp.yaml reported "Not found". Name the
file and remove nothing.

* fix(skills): remove a leftover of another skill version only when it matches that version (#707)

Install removed any installed file at a path another team version of the skill
has, by path alone. A file a member added under that name, e.g. README.md
beside a namespace they never had, was deleted on every pull. Remove it only
when it is byte for byte that version's file; keep any other and name it.

* fix(env): edit no env file that does not parse, and no --project target after a failed refresh (#707)

env add and env remove read the target through parseEnvYaml, which answers an
empty list for a file that does not parse, then wrote that back: every
variable the file had was replaced. They now refuse and name the file.

--project resolves through manifest/projects.yaml. After a failed pull that
copy may be stale and name a namespace the project no longer uses, whose file
push would publish, so --project now changes nothing then. The root file and
--role do not depend on the manifest and still only warn.

* fix(pull): let no unusable namespace item replace the root one (#707)

A skill directory without SKILL.md replaced the root skill of its name:
install overlaid it and removed the installed SKILL.md as the other version's
leftover, while pull still counted the skill as synced. Such a directory is
no longer a skill; pull names it and keeps delivering the root one.

An agent file that does not parse delivers nothing, yet it still replaced the
root agent, and cleanup removed the unchanged root copy because no active
destination held that stem. The root agent now stays while its replacement
cannot be read or parsed.

* fix(push): take no namespace directory without SKILL.md for a member's skill (#707)

Pull stopped delivering such a directory in 6c7df8e3, but the push scan still
mapped the skill name to it. An unedited root skill then showed as modified,
and push wrote it into skills/<ns>/<name>/, deleting what was there and making
it a namespace skill that replaces the root one.

Also keep only a replaced root agent while its replacement does not parse, so
an unchanged copy from an inactive namespace is still removed, and put
renderedForTool's doc comment back on it.
2026-09-25 17:59:19 +08:00
Saul Moro ca6e51251f feat(skill): serve builtin skill content from the CLI, deploy a discovery stub (#699)
* feat(skill): serve packaged skill content from the CLI

Add `teamai skill get <names...> [--full] [--all]` and `teamai skill path
[name]`, so an agent can read built-in skill content that always matches the
installed CLI version instead of a copy deployed into its skills directory.

`get` prints SKILL.md byte for byte, frontmatter included, with {SKILL_DIR}
resolved to the absolute packaged directory so documented script invocations
run as-is. `--full` appends references/ and templates/, walked recursively and
sorted by relative path, because our references nest one level deeper than the
flat layout agent-browser assumes.

Content goes to stdout and every diagnostic to stderr, so the output stays
byte-exact when piped. An unknown flag warns and continues; an unknown name is
fatal, since acting on the wrong skill is worse than a retry.

`skill list` gains the served catalog and `--json`; `skill show` resolves
packaged skills before the installed-agent fallback, which is what keeps it
working once the deployed unit becomes a stub. Legacy directory names resolve
as aliases.

Refs #678

* refactor(skills): move content to skill-data and deploy a single stub

Agents now receive one file: `skills/teamai/SKILL.md`, a discovery stub of
about 2 KB whose description carries the triggers of every workflow and whose
body holds the commands that load them. The workflow content moves to
skill-data/{core,share,wiki}, which is never deployed and is printed by
`teamai skill get`.

Before this, `deployBuiltinSkills` copied three whole trees — 176 KB — into
every installed agent on every pull, so the text an agent read could disagree
with the CLI it documented until the member ran a pull, and a machine with ten
agents held ten copies. skills/ keeps its meaning ("everything here is
deployed"), which is what lets BUILTIN_SKILL_NAMES collapse to one name.

The stub is copied verbatim: no ensureSkillFrontmatter on the way out, so a
deployed copy that differs from the packaged one is a bug rather than a
variant. Recall no longer gates deployment, since the stub routes to every
workflow; the run-time gate for share lands with the pruning pass.

Uninstall learns the legacy directory names, which it would otherwise leave
behind on every machine that upgraded.

"skill-data" is added to package.json files, with a test that asserts it
through `npm pack`: without that entry every test still passes against the
repo and `skill get` serves nothing once installed from the registry.

Refs #678

* fix(skills): repair stale commands, broken refs and frontmatter

An audit of the three builtin skills found 60 defects. This fixes the ones that
survive the move to skill-data, and splits the two skills that were carrying
more than one job.

Stale CLI surface. The wiki skill advertised `teamai extract graph`, a command
that has never existed. The hand-written "ground truth" cheat sheet in the
teamai skill omitted 19 real commands while telling the agent that anything
missing from it could be checked with `--help` — which fails for the flags
`--help` hides. The cheat sheet is replaced by
`skill-data/core/references/commands.md`, rendered from the CLI's own command
table, with hidden flags marked as such. Two tests guard it: one regenerates
the file and diffs, the other resolves every `teamai …` string written anywhere
in skill-data against the command table and fails on an unknown command or
flag. That second test is the one that would have caught e151d43, 1ca43ac,
8bb0548 and 2ddb546 before they shipped; it carries a case proving it catches
`teamai extract graph`.

Paths. Everything the skills told an agent to read or execute assumed the skill
sat in the agent's own directory: `python3 scripts/scan_repo.py` from a cwd that
is the target repo, references cited by bare filename in two different
conventions, methodology paths handed to sub-agents inside input packets. All of
them now go through {SKILL_DIR}, which `skill get` resolves. The README template
nobody referenced is wired into the step that writes the knowledge-base README.

Frontmatter. None of the three skills declared allowed-tools, so the first
command of every flow hit a permission prompt. The wiki skill kept its trigger
words and prerequisites inside the description text; both move into the body.

Splits. `core` keeps what a daily user needs and `setup` takes day 0 and the
repo lifecycle, so the common path no longer carries ~500 lines of repo
creation. The wiki skill's phase procedures move into references/phases/, taking
its SKILL.md from 38.7 KB — larger than agent-browser's entire core — to 17 KB
with an index that says when to load each phase.

One contradiction is resolved in the author's text: the share skill mandated
that every generated document be written in Chinese, against global rule 1
("reply in the user's language") and this repo's own English rule. It now
follows rule 1.

Refs #678

* feat(pull): prune legacy builtin skill directories, gate recall at run time

Upgrading the CLI used to leave the pre-stub trees in place: cleanup skips
builtin names, and nothing else knew about them, so `team-wiki-codebase` and
`teamai-share-learnings` would sit in every agent directory on the machine
forever. Deployment now removes them first, in both the configured skills path
and Codex's shared `.agents/skills`. Unconditional, because those trees were
overwritten on every pull, so no local edit ever survived in them.

Recall moves from deploy time to run time. Before, `skipRecall` decided whether
the share skill reached the agent at all; with one stub routing to everything,
there is no directory to withhold, so `teamai skill get share` checks instead
and says what to enable. `--all` is exempt: an inventory dump is not an attempt
to run the workflow. With no team config to consult the gate fails open — a
fresh machine reading the docs gets the content rather than a refusal it cannot
act on.

deployBuiltinSkills drops its `skipRecall` option rather than keeping one that
no longer decides anything, and recall-toggle stops deleting a skill directory
it no longer owns.

Refs #678

* docs: align the nudge and the guides with CLI-served skills

`/teamai-share-learnings` was never a slash command of its own — it existed
because the directory was installed. The Stop-hook nudge now names `/teamai` and
carries `teamai skill get share` literally, so an agent can act on it without
having to infer the intent from the conversation. The five READMEs and both
usage guides follow.

Both guides gain the `skill get` / `skill path` commands and a short section on
why built-in skills are served rather than copied. `docs/designs/skill-serving.md`
records the contracts that are easy to break later: byte-for-byte output,
{SKILL_DIR} substitution, stdout/stderr discipline, recursive `--full`, the
run-time recall gate, the three drift guards, and when to retire
LEGACY_BUILTIN_SKILL_NAMES and the long-name aliases.

AGENTS.md and CLAUDE.md gain the rule that keeps this from rotting: skill-data
is treated like documentation, a behaviour change updates the affected skill,
commands.md is regenerated rather than edited, and new workflows go under
skill-data instead of into the stub.

Refs #678

* fix(skills): apply standards review findings

`teamai init` printed "Built-in skills (e.g. team-wiki-codebase) are ready to
use in your IDE now" seconds after deployment deleted that very directory. The
message now names the teamai skill and how it loads its workflows. Two comments
carrying the same stale name follow.

The five READMEs said different things: only the English one named the share
workflow and its command. All five now do.

`collectSupplementaryFiles` hand-rolled a recursive walk that
`listFilesRecursive` already does, including the ignore list that skips `.pyc`
and `__pycache__` next to the wiki's Python scripts. It calls the helper
instead. `listServableSkills` drops its fallback to `skills/`: a package without
`skill-data/` is broken, and serving the stub as if it were the content hides
that from the one error message built to report it.

Tests drop six non-null assertions for a helper that throws, per the repo's rule
against moving a compile-time error to run time.

AGENTS.md and CLAUDE.md record the exemption the branch created: skill content
printed by `skill get` keeps the language its author wrote it in, while the
command's own prompts, errors and listings stay English.

Refs #678

* fix(skills): apply spec review findings

Upgrading left the old references in place. Releases before the stub deployed
`skills/teamai/` with six reference files beside SKILL.md, and `teamai` is not a
legacy name to prune, so copying one file over that directory kept ~39 KB of
pre-stub instructions next to the new stub for good. Deployment now clears
everything the deployed unit does not contain before writing it, and a test
seeds the old layout to prove it.

Pruning reached neither reporting-only teams nor the Codex shared directory in
any test. The prune now runs before the reporting-only return, so a team that
switched to reporting-only still loses the stale trees, and the Codex
`.agents/skills` path is covered by a test. Excluded agents stay untouched, as
the enabledAgents whitelist documents.

The served content still routed to skills that no longer exist: five mentions of
`teamai-share-learnings` and one `/team-wiki-codebase --update`, which is the
rule this branch itself added being broken on arrival. One second-hop path
inside a sub-agent input packet was still relative.

The share skill's document template, frontmatter table and tag taxonomy move to
`references/doc-template.md`, taking the always-read body from 3 701 to 2 471
bytes. Two tests now assert what nothing guarded: every served skill's
frontmatter name matches its directory and declares allowed-tools.

A new e2e file runs the built CLI the way an agent does: every listed skill is
servable and byte-identical bar the resolved placeholder, the wiki scripts run
from the directory `skill path` prints, an unknown name exits 1 with empty
stdout, a hallucinated flag warns and still serves, and `--full` appends the
nested references in sorted order.

Refs #678

* fix(skills): prune only directories the CLI owned, gate every content path on recall

- LEGACY_BUILTIN_SKILL_NAMES drops teamai-workflow and teamai-import: they were
  reserved in the old guard set but never packaged, so a directory by either
  name is the user's own skill. Test: user-created skills with those names
  survive pull.
- The recall gate now covers skill get --all (blocked skill skipped, named on
  stderr), skill path (refused) and skill list --json (blockedByRecall, path
  null). skill list reads the flag from the catalog instead of re-checking.
- skill get [names...]: the positional is optional so --all is reachable from
  the real CLI; Commander used to fail with 'missing required argument'.
  Covered by the skill-serving e2e.
- commands-reference renders Commander's variadic marker (<names...>); the
  snapshot is regenerated.

* test(skills): drive the recall gate through the real CLI, guard the stub description budget

- skill-serving e2e: a HOME with a team whose recall is off; skill get share,
  --all, skill path share and skill list --json each withhold share, and all
  serve it after recall enable. The earlier HOME has no team config and fails
  open, so the gate was never exercised through dist/index.js.
- skill-content test: the stub description stays within 1024 characters.
- skill-commands-exist also scans the deployed stub.
- Docs and PR lead with content versioned with the CLI; the size numbers are
  measured (stub description 0.8 KB, body 1.3 KB; --full 32/36/115 KB).
- Content audit against origin/main: every file has a counterpart. Fixes:
  {SKILL_DIR} defined in core/setup/share where the references are listed, the
  wiki overview draws the served layout, team-wiki-codebase kept as a trigger
  word in the stub description.

* fix(skills): gate skill show on recall, prune Codex's shared dir only from Codex

Review follow-up on #699.

- `skill show <served skill>` refuses a recall-blocked skill with the same
  message and exit code as `skill get` / `skill path`; it printed the
  directory those two withhold.
- A skill resolved from skill-data/ is classified `[builtin]` directly.
  BUILTIN_SKILL_NAMES only knows the deployed stub, so `skill show core`
  reported `[local-only]` beside a package path.
- pruneLegacyBuiltinSkills reaches `.agents/skills` only on Codex's own
  pass. Another enabled tool's pass deleted Codex's legacy copies while
  Codex was excluded, against the enabledAgents guarantee.
- The share skill and its references are written in English; the generated
  document still follows the session's language. The AGENTS.md exception
  for Chinese skill-data output is dropped.

* fix(skills): one resolver for served skills, legacy names kept out of push, wiki in English

Review follow-up on #699.

- resolveServableSkill is the only way to obtain a PackagedSkill outside
  skill-content.ts; it returns `blocked` instead of the skill, so `get`,
  `path`, `list` and `show` inherit the recall gate by construction.
- push never offers `team-wiki-codebase` / `teamai-share-learnings` as new
  user skills: between the upgrade and the first pull they are still on
  disk (isCliOwnedSkillName).
- `recall disable` removes the legacy `teamai-share-learnings` directory
  again (LEGACY_RECALL_SKILL_NAMES), skipping excluded agents.
- `skill list` prints the packaged catalog before `teamai init`, with a
  hint for the team half, instead of failing on the team listing.
- skill-data/wiki (SKILL.md, 14 references, 2 scripts) translated to
  English. Generated document names follow one glossary; validate_kb.py
  still recognises headings of knowledge bases built by the previous
  release, matched by code point so the source stays ASCII.

* fix(skills): prune only files the CLI packaged, let local skills win by name

Review follow-up on #699.

- PACKAGED_SKILL_FILES lists every file a release ever wrote under skills/,
  as the union of `git ls-tree -r <tag> -- skills/` over all 91 tags. The
  prune removes those paths and the directories they leave empty; a file a
  member added is kept, its directory with it, and pull says which and why.
  The stub directory loses its six known references by name instead of
  "everything that is not SKILL.md". Python bytecode of a script we shipped
  counts as ours, so a __pycache__ does not strand the tree.
- locateSkill searches the team repo, then installed agents, then the
  package. A directory a member created under `codebase`, `default`,
  `learning` or `share` is the skill they asked about, and the recall gate
  does not apply to it.
- A guard test fails when a file ships under skills/ without being recorded
  in the manifest, which a later migration would otherwise leave behind.

* fix(skills): close the last recall bypass, uninstall Codex's shared stub

Review follow-up on #699.

- `skill path` takes a name, always. The argument-less form printed the
  `skill-data/` root, and `<root>/share/SKILL.md` is readable from there —
  the content the gate withholds one command over.
- uninstall discovers skills in Codex's shared `.agents/skills` root, where
  resolveSkillDestination puts the stub whenever the skill already lives
  there. Without it, uninstall reported success and left it behind. Codex
  only, as the legacy prune already does.
- core/SKILL.md said team sharing is enabled by default; getRecallSharing
  defaults it to false. It now says recall is off by default and names
  `teamai recall enable`.

* fix(skills): uninstall by the same ownership rule as pull, quote {SKILL_DIR}

Review follow-up on #699.

- uninstall removed a CLI-owned skill directory whole, undoing one command
  over the guarantee pull makes. It now removes the PACKAGED_SKILL_FILES
  paths through the same removeOwnedFiles, keeps a directory holding a file
  the member added, says which one, and tells the confirmation prompt so it
  no longer promises a directory it will keep. A team-repo skill is synced
  whole and still goes whole.
- Served shell commands quote the placeholder: `python3 "{SKILL_DIR}/..."`.
  Unquoted, an install path with a space ("Program Files", "Application
  Support", a Windows path through Bash) splits into two arguments and the
  documented invocation fails. A test fails on an unquoted occurrence after
  any command word, in SKILL.md or any reference.
- wiki/references/overview.md said the methodology, scripts and agent specs
  are deployed into agent directories. They are not: only the stub is, and
  the rest is served from the installed CLI.

* fix(skills): deploy the stub in reporting-only mode, drop the stale list alias

Review follow-up on #699.

- Reporting-only HTTP pull pruned the legacy trees and deployed nothing, so
  a member on an HTTP team came out of the upgrade with no built-in entry
  point at all. The skip predates CLI-served content: it existed because the
  only deployable unit then needed a team repo. The stub does not — its
  workflows are printed by the installed binary, and `skill get wiki` is a
  local knowledge-base generator that never touches a repo. The stub now
  deploys in every mode, and `reportingOnly` goes with the branch it gated:
  nothing else read it.
- `teamai skill list` called itself an alias for `teamai list skills
  --source all`. It has not been one since it started printing the CLI-served
  catalog underneath. Both descriptions, the generated command reference and
  both usage guides now say what it does.

Refs #678

* fix(skills): carry the TGit provider guide into the served setup skill

#724 landed `skills/teamai/references/provider-tgit.md` and repointed
setup-admin.md and join-member.md at it. Rebasing onto that left the new
file in a tree this branch no longer deploys, and the pointers in bare
`provider-tgit.md` form the served skills do not use.

- move it to `skill-data/setup/references/`, beside the two files that
  cite it, so `teamai skill get setup --full` serves it
- rewrite every pointer to it as `{SKILL_DIR}/references/provider-tgit.md`
- list it in the setup skill's reference table
- add `references/provider-tgit.md` to PACKAGED_SKILL_FILES, so the prune
  removes it from members who pulled a release that shipped it

* fix(skills): make the blocked catalog entry unrepresentable, drop unsafe casts

Review findings from the standards axis, plus the doc half of the prune
count.

- `SkillCatalogEntry` allowed `{blockedByRecall: true, path: '/…'}`, an
  invariant `skillCatalog` then upheld by hand. Split it on
  `blockedByRecall`, so the withheld directory is a type error rather than
  a review catch. Both variants keep the `path` key, so the
  `skill list --json` shape is unchanged.
- `command.commands as Command[]` stripped commander's `readonly` in three
  places. `for…of` and `.find` need no cast.
- `docs/designs/skill-serving.md` still said the prune removes six
  `teamai/references/*.md`; provider-tgit.md makes it seven.

* fix(skills): keep publishing a skill reachable when recall is off

Publishing a skill is `teamai push --skill`, which never consulted recall
(`src/push.ts` names it nowhere). On main the flow shipped in the teamai
skill, ungated. Moving `contribute-member.md` under `share` put it behind
the recall gate, so with recall off — a new team's default — the core
routing table sent the agent to `teamai skill get share`, which exits 1
and tells it to enable recall. Wrong advice for a flow recall does not
touch, and no other path to the instructions.

Move the file to `core`, the skill that already owns `push`, and split the
routing row so publishing and session learnings stop sharing one
destination. The gate itself is right and stays: learnings do need recall.

`share/SKILL.md` already called this "a different flow"; now it points at
`teamai skill get core --full` instead of at its own references.

PACKAGED_SKILL_FILES is unchanged: the legacy path a pre-stub release
wrote is still `teamai/references/contribute-member.md`.

* fix(skills): back up what the prune removes, so no edit is a one-way door

Review finding: `removeOwnedFiles` proves ownership by pathname and
deletes without reading the file, so a member's edit goes with it.

For a path the current package still ships that changes nothing: the old
deployment overwrote it with `overwrite: true` on the same three triggers,
so the edit died either way, at the same moment. The case the objection
gets right is a path a retired release shipped and the package no longer
does — the overwrite never reached it, so the edit did survive, and the
prune is the first thing to remove it.

Copy every pruned file to `~/.teamai/removed-skills/<date>/<tool>/<skill>/`
before removing it. Outside every agent directory, so nothing reads it back
as a skill.

Verifying contents against a hash of each released version was the other
way out, and it is worse: anything not byte-identical is then kept, so one
CRLF checkout on Windows — a platform this project supports — leaves the
whole 176 KB in place and reports success. Backing up gives the same
guarantee without betting the migration on byte equality.

Uninstall keeps deleting outright: there the member asked for the files to
go.

* fix(skills): let no backup failure authorise a delete, give each root its own

Two holes in the backup the previous commit added, both reported in review.

The copy's failure was swallowed at debug level and the delete went ahead
regardless, so a full disk or a read-only home turned the migration back
into the data loss the backup exists to prevent — and the log still named
a backup directory that held nothing. A file whose copy fails is now kept,
counted, and named at warn level; `removeOwnedFiles` returns what happened
instead of a bare boolean, and only a run that copied something names the
directory.

The backup path was `<date>/<tool>/<skill>` with `overwrite: true`, so the
second copy of a name silently replaced the first. Codex prunes the same
skill from `.codex/skills` and the shared `.agents/skills`, and two pulls
share a date. The path now carries a per-run id and the skill root, and the
copy refuses to overwrite rather than clobbering a copy it cannot replace.

Tests cover both: a file where the backup tree must start makes every copy
fail, and the two Codex roots land in separate directories. Each fails
against the previous commit.

* fix(skills): stop at a symlinked root, archive only what is retired

Three review findings, all in the prune.

A symlinked skill directory was walked through. `readdir` follows the link,
every path under it matches a packaged name, and the delete lands in
someone else's checkout. Ownership now stops at the link: the root is
lstat'd, a symlink is refused, and link and target are left alone.

The stub directory was pruned against the full historical file list, which
includes the SKILL.md written one line later. Deployment runs on every
session start, unchanged revision included, so that archived an identical
copy per session forever. Only paths this release no longer ships are
archived now.

Backups were written under the tool's base directory, which under project
scope is the repo root, so they landed in the working tree outside the
generated .teamai/.gitignore. They go to the machine's home.

Also: `skill show <packaged>` resolved the team before the package, so it
failed on a machine that never ran `teamai init` for content that needs no
team. Packaged names resolve first and print without the team-dependent
fields.

* fix(skills): stop at the first link above a skill dir, report a half prune

Findings from a self-review run before pushing, plus the two from the last
review round.

The symlink guard was one level too low. It lstat'd the skill directory, so
the common shape — `~/.claude/skills` itself linked at a dotfiles checkout —
walked straight through: every directory under the link is real. The guard
now walks each component below the tool's base directory and stops at the
first link, which covers the prune and the stub write with one check.
Components at or above the base are not checked: a home directory under a
link is ordinary, and refusing there would disable deployment on those
machines.

The symlink branch borrowed the foreign-files message, so a member was told
"delete the rest yourself" about a directory nothing had touched. Following
that destroys what the guard just protected. It has its own sentence now, in
pull and in uninstall.

`remove()` was not fail-closed the way the backup is: a read-only parent
left the tree half-pruned under a debug line, and a `walkFiles` that threw
returned success. Both are recorded in `notRemoved` and reported.

The backup path gained the base directory: `inheritUserScope` deploys the
user base and then the project base in one process, same tool, same root,
same skill name, and `errorOnExist` turned that collision into files the
second pass could neither archive nor prune.

Docs corrected against the code: the archive path, the tag count (98, not
91, and `teamai-wiki` is excluded), the version line, and the size table.

* fix(skills): route the nudge and skill publishing where they land, classify legacy names as ours

The Stop-hook hint said "run /teamai", but bare /teamai prints the menu and
stops, so following the primary suggestion never reached the share workflow.
It now names an invocation the core skill routes to share, with the
`teamai skill get share` fallback kept. The four docs that quote the hint follow.

The setup skill sent "publish one skill" to `teamai skill get share`, which
handles session learnings and is refused when recall is off (the default);
reusable-skill publishing lives in core's contribute-member reference and needs
no recall. The routing row and the two references that repeated it now point
there.

classifySkill checked BUILTIN_SKILL_NAMES alone, so until the first pull pruned
them, team-wiki-codebase and teamai-share-learnings showed as [local-only]. It
now uses isCliOwnedSkillName, the rule push and uninstall already apply.

* fix(skills): pre-push review — gate the nudge on recall, keep bytecode out of the tarball, report a failed uninstall delete

A review of the whole branch against #678, #730 and the design doc, run before
pushing. What it found and what changed:

- The Stop-hook share reminder was gated on the hint switch alone; recall is off
  by default and `teamai skill get share` refuses then, so the reminder pointed
  at a command that said no. It is withheld while recall is off, the same gate
  the workflow has; the served text about when the prompt appears now matches.
- `npm pack` swept `skill-data/wiki/scripts/__pycache__` into the tarball once
  the e2e suite had run the scripts. Excluded in package.json "files", asserted
  absent in the tarball test, and the e2e run sets PYTHONDONTWRITEBYTECODE.
- The `share` description still offered to publish reusable skills, the flow its
  own body sends to `core`; the sentence is gone.
- `skill show <unknown>` before `teamai init` threw the init error as a stack
  trace; it prints the not-found line and exits 1.
- The stub pre-approved every `teamai` command from the always-loaded unit;
  narrowed to `Bash(teamai skill:*)`, which is all it asks for (#678).
- Six routing lines loaded `core --full` to reach one reference; they name the
  file under `$(teamai skill path core)/references/` instead.
- `uninstall` reported a failed delete as "holds files TeamAI did not put there;
  the packaged files were removed", both false and the error unprinted. It names
  the file and the error; a test makes the stub directory read-only.
- The stub directory archived under `<tool>/.claude-skills-teamai/teamai/` while
  the legacy trees used `<tool>/.claude-skills/<skill>/`; one layout now.
- CHANGELOG entry; dead `isRecallEnabled` import; wiki heading still naming
  `team-wiki-codebase`; JSDoc on the wrong declaration; stale byte counts; the
  usage guides gain the recall refusal and the archive location; the design doc
  records the `--json` deviation, the legacy-name classification rule, the
  uninstall symlink scope and the fail-open wording.

* fix(skills): English-only served content, one link guard for every caller, withhold share from read-only sources

The reviewer flagged Chinese in skills/ and skill-data/ a third time. Both
reach the agent as CLI output, so the stub's trigger keywords, the paired
sample invocations and the Chinese name for TGit go; the agent translates for the user. A test
fails on CJK anywhere under either root.

Pre-push review of the whole branch, and what changed:

- uninstall walked through a linked ~/.claude/skills and deleted the
  packaged files inside the member's dotfiles checkout; pull refused the same
  layout. removeOwnedFiles now owns the guard, so pull, deploy and uninstall
  apply one check: the skills root and the skill directory. A linked
  ~/.claude (stow, chezmoi) is no longer refused, since every other resource
  writes through it and refusing left those machines on the pre-stub trees.
- share was served to read-only HTTP teams, where its last step
  (teamai contribute) always fails; reportingOnly used to skip it. The
  serving gate carries a reason (recall | read-only) with its own message,
  and `skill list --json` reports it as `blockedBy`.
- The bytecode rule claimed any file under any __pycache__; it now claims
  only the .pyc of a shipped script.
- recall disable pruned the shared .agents/skills root for an uninstalled
  Codex; it has deployment's install gate now.
- The source-team guard lost the legacy names when BUILTIN_SKILL_NAMES
  narrowed, so a source removal could delete a legacy tree wholesale.
- Routing: the admin wrap-up and the stub still sent "share what I learned"
  to share without saying it needs recall, and the stub filed "share this
  with my team" (the publish-a-skill phrase) under share. recall enable is
  described as the per-machine override it is, next to the team key.
- {SKILL_DIR} definitions now say how a reference file opened on its own
  spells the directory, since serving resolves the definition too.
- skill show: packaged resolve only when init fails, aligned label, a
  served skill is "served by the CLI, not installed".
- Docs: uninstall removes the archive with ~/.teamai; zh said the whole
  directory is kept; the product overview lacked the recall gate; the design
  doc's release, tag and byte figures were stale; CHANGELOG notes the
  language change of generated documents.

* fix(skills): check every path component below the base for a link, in uninstall too

The previous commit narrowed the guard to the skills root and the skill
directory, so a link at ~/.config or ~/.config/opencode was walked through:
the prune could delete, and deploy write, inside a dotfiles checkout. The
full walk from the tool's base directory is back, and removeOwnedFiles now
requires the base, so uninstall applies it too; each skill directory in the
uninstall plan carries the base its skills root hangs off.

A member whose whole ~/.claude is a link keeps the pre-stub trees and gets
the warning naming the path, as before the previous commit. Deleting through
a link is the one thing the prune must never do.

* fix(skills): withhold the share hint on read-only sources, route legacy names through the gate

contributeHintAllowed checked recall only. The dispatcher already drops this
gitOnly handler for HTTP teams, but the gate now says so itself, so the
reminder never points at a `share` that refuses as read-only wherever it runs.

`skill show teamai-share-learnings` searched the agent directories before
the package, so a legacy tree a pull had not pruned yet was shown with its
path while the gate refused `share`. A legacy built-in name now skips the
agent search and goes to the packaged skill and its gate; ordinary names and
aliases such as `share` keep a member's own directory first.

* fix(skills): deploy and prune built-ins where the tool keeps its skills

deployBuiltinSkills joined baseDir with the configured skills path, while
team-skill sync resolves the directory through skillsDirForTool: OpenClaw's
workspace, and HERMES_HOME for Hermes. Those agents got the stub in a
directory they never read and had their legacy trees pruned from the wrong
place. Deploy, the legacy prune, recall disable and uninstall now resolve
the same directory; the link guard starts at the tool's base directory when
the skills directory sits under it, else at that directory's parent.

* fix(skills): check an external skills root for a link, keep config-load logs off stdout, drop inert allowed-tools

- A skills directory outside the tool's base (HERMES_HOME, an OpenClaw
  workspace) had the guard start at the root itself, so a linked root was
  never checked. It starts one level above now, and a linked HERMES_HOME is
  refused like a linked ~/.claude.
- The share gate loads the config, which can migrate it and report that with
  log.info on stdout: an upgrading machine got that line in `skill get`
  output and in `skill list --json`. Config loading reports on stderr for
  that call; setStderrOnly returns the previous mode so it can be restored.
- `allowed-tools` in the served skills was printed as command output and
  never processed as skill metadata, so it granted nothing. Removed, and the
  test now fails if one comes back. Only the stub's line pre-approves.

* fix(skills): walk the link guard from the scope root, so a linked COPILOT_HOME is refused

skillsGuardBase started at the tool's base directory, which for Copilot in
user scope is COPILOT_HOME, so the walk never checked whether COPILOT_HOME
itself was a link, and pull and uninstall wrote and pruned through it. The
guard now starts at the scope root (home, or the project root), where a link
at or above is ordinary, in deploy, the legacy prune and uninstall alike; a
root configured outside it still has the walk start just above that root.

* fix(skills): keep generated documents in Simplified Chinese, fail on a broken config, quote skill paths

- share and wiki had moved generated learnings and knowledge-base documents
  from always Chinese to the session language. Serving the instructions from
  the CLI does not need that, so both say "Simplified Chinese" again, in an
  English instruction; the CHANGELOG entry follows.
- skill show and skill list treated every autoDetectInit failure as "not
  initialized" and pointed at `teamai init`. requireInit now throws a tagged
  NotInitializedError; only that falls back to the packaged catalog, and a
  malformed or unreadable config propagates.
- `$(teamai skill path …)/…` is word-split in a shell command like an
  unquoted {SKILL_DIR}; all ten occurrences are double-quoted and the quoting
  test covers the form.

* fix(config): raise NotInitializedError only when the config file is missing

loadLocalConfig returns null both for a missing file and for one that fails
to parse, validate or migrate (it logs the reason). requireInit turned every
null into NotInitializedError, so skill show and skill list still fell back
to the packaged catalog and a `teamai init` hint on a broken config. Only an
absent file is NotInitializedError now; an existing one that could not be
used is an error naming its path, in requireInit and the user branch of
requireInitForScope. Covered through the real loader and the built binary.

* fix(skills): prove ownership by content, not path alone; retire the second Codex copy

- The legacy prune and uninstall removed any file at a path a release had
  packaged, so a member's edit, a skill of their own under an old name, or a
  root TeamAI never managed (toolPaths or HERMES_HOME moved) lost its files.
  A file is ours now only at a packaged path and with content a release
  shipped there: PACKAGED_SKILL_DIGESTS records the sha256 of every blob over
  all 99 tags through v0.25.0 and main before the stub, 37 versions across 21
  paths. A skill-root SKILL.md is compared by its body, since releases before
  0.17 shipped no frontmatter and the deploy of the day repaired it on disk.
  The current stub is ours by the packaged copy. Anything else stays.
- Codex reads .codex/skills and the shared .agents/skills, and the stub goes
  to the shared one when a copy lives there; the copy an earlier release left
  in the other root kept its old SKILL.md and references. It is retired by
  the same ownership rule, archived first, and named when kept.
- Tests mock the digest table with a stand-in for shipped content, and a test
  keeps the stand-in on the same paths as the real table.

* chore(skills): carry main's skill edits into the served copies after the rebase

#713 and #736 edited skills/teamai/references/*.md, which this branch moved to
skill-data/setup/references/. Two hunks did not follow the move:
- join-member.md: TGIT_TOKEN is REST-API-only and cannot clone (#713).
- setup-admin.md: the /teamai share entry publishes a reusable skill; a
  session's learnings are automatic (#736), in English as the served text is.
#739's partial config mock is restored in skip-uninstalled-tools.test.ts.

* fix(skills): deploy before pruning, block share on an unloadable config, drop hidden commands from the reference

- Legacy trees were pruned before the stub was written, so a refused or
  failed stub (a link, a read-only directory) left the agent with nothing
  to discover. They go only once the stub deployed for that agent.
- The share gate failed open on any config error. Only a machine with no
  config (NotInitializedError) is served; a config that exists but cannot be
  loaded blocks with its own reason, `blockedBy: "config"`.
- The KB template told agents to run `code-to-knowledge --update`, which
  does not exist; it names `teamai codebase --extract … --incremental`.
- The generated command reference listed hidden hook plumbing (`track`,
  `contribute-check`, `todowrite-hint`, …). It renders what `--help` lists.
- removeEmptyDirs swallowed every rmdir error, so a directory that stayed
  could be reported removed. Only "still holds something" is expected; any
  other failure is reported.

* fix(skills): whole-file ownership, stub before its references, no side effects before the link guard

Review of 327f9cd:
- SKILL.md was compared by its body, so a member who changed only its
  frontmatter lost the file. Every release from 0.16.1 (the first whose
  deploy repaired frontmatter) shipped complete frontmatter, so what is on
  disk is what was shipped: digests are whole files now (42 versions over
  100 tags and main). A link is never ours; bytecode is ours only beside a
  script proven ours by content, decided before anything is removed.
- The stub dir's retired references were pruned before SKILL.md was copied;
  a failed copy left the old skill pointing at files that were gone. The
  stub is written first.
- The Codex destination was resolved with the reconciliation that deletes a
  duplicate, before the link guard ran. It is resolved side-effect free; the
  other copy is handled under the guard by retireOtherCodexCopy, whose
  report now names a failed backup or delete as such.
- A broken project config was skipped by detection, so the share gate
  answered with the user config. findUnreadableProjectConfig reports it via
  an optional sink on detection (no caller changes), and the gate blocks.
  The Stop-hook reminder is withheld on an unloadable config too.
- init announced the stub as ready when nothing was deployed; hook-dispatch
  is hidden (hook plumbing), and the reference says it lists public commands;
  the design doc no longer says teamai-workflow/teamai-import are removed.

* fix(config): report a broken higher-priority project config even when a fallback loads

findUnreadableProjectConfig dropped a recorded error whenever detection
went on to find a later candidate: a broken partition config followed by a
valid legacy .teamai/ config returned null, and the share gate answered with
the fallback's team. It now reports the first unreadable file regardless.
An existing config file that is empty or cannot be read is reported to the
sink too, instead of returning without a word.
2026-09-23 16:15:38 +08:00
jeff e7994db3ca chore(deps): upgrade high/critical severity dependencies (#514)
Resolve all 10 Critical+High Dependabot alerts:
- smol-toml ^1.3.1 -> ^1.7.1 (runtime, CVE-2026-85730)
- js-yaml override ^3.15.1 -> ^3.15.2 (runtime, CVE-2026-84375)
- vitest / @vitest/coverage-v8 2.x -> 3.2.7 (clears CVE-2026-47429; stays on 3.x)
- override brace-expansion 1.1.18/2.1.4/5.0.9, nanoid 3.3.19, postcss 8.5.28, picomatch 4.0.7

npm audit: critical/high now 0. build + tsc + full vitest suite (3006 tests) pass.
2026-09-11 17:51:29 +08:00
jeffyxuandCursor 851d5f2600 docs: align README and usage guide with TeamAI product architecture
Reframe public docs around Team Execution × Team Context × Team Improvement, and add vision.md as the longer product write-up.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 14:29:37 +08:00
chengjieyu 40837dc7f3 fix: harden team package orchestration 2026-09-03 11:51:35 +08:00
chengjieyu cce6e81a2c fix(packages): address install review findings
Keep package acknowledgement and version updates accurate, isolate declaration validation, and harden package setup across hooks, scopes, and Windows.
2026-09-02 20:05:50 +08:00
jeffyxuandCursor fd9f3f8dca chore(release): 0.22.0
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 18:24:11 +08:00
jeffyxuandCursor d92fb4d237 chore(release): 0.22.0-beta.5
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 16:25:46 +08:00
jeffyxuandCursor 78063e7f83 chore(release): 0.22.0-beta.4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 14:39:59 +08:00
jeffyxu 633f67213b chore(release): 0.22.0-beta.3 2026-09-01 21:54:17 +08:00
jeff 915fec98f0 chore(release): 0.22.0-beta.2 (#382) 2026-09-01 20:37:56 +08:00
jeff db3aa2c841 chore(release): 0.22.0-beta.1 (#379) 2026-09-01 20:01:23 +08:00
Jiahe GengandClaude Opus 4.8 6e29f84b29 feat(wiki-engine): add WASM tree-sitter AST track for code knowledge graph (#304)
* feat(wiki-engine): add WASM tree-sitter AST track for code knowledge graph

The code knowledge graph extractors were purely regex/line-based, which
produced false-positive dependency edges (path-substring matching) and had
no notion of call relationships. This adds a real AST track using
web-tree-sitter (pure-WASM, no native toolchain) for TypeScript/JavaScript,
Python, and Go, ported from the team-wiki reference implementation.

- New ast/ module: WASM parser registry (async one-time init), tree-sitter
  queries, symbol/import/call-site walk, import & call resolvers, and
  fact/edge adapters. Emits precise file-to-file DEPENDS_ON / REFERENCES
  edges tagged source:"code-ast" with confidence weights.
- Dual-track: runs alongside the regex heuristic track (which still covers
  Java/Rust/config); AST facts win on merge. Falls back to heuristic-only
  and records an AST_UNAVAILABLE gap when the runtime is unavailable or
  TEAMAI_SKIP_AST=1.
- code-graph: AST relation facts build precise edges instead of being
  re-fuzzed through the path-substring matcher.
- enrich: manifest edges now preserve real AST relation/source provenance
  (deterministic rank-based merge) instead of hardcoding DEPENDS_ON /
  code-heuristic.
- Pinned web-tree-sitter@0.25.10 + tree-sitter-wasms@0.1.13 (only ABI-14
  compatible pair; 0.26 rejects these grammars).
- Docs (README + usage-guide, EN/zh-CN) and unit tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(wiki-engine): address AST track code-review findings

- walk.ts: free the parsed tree-sitter Tree via try/finally { tree.delete() }
  to release WASM off-heap memory; JS GC does not reclaim it, so large repos
  would otherwise grow the emscripten heap unbounded.
- call-resolver.ts: memoize import bindings per source file in resolveCallSites
  (was rebuilt for every call site — O(callSites × imports)).
- import-bindings.ts: detect Python exports via module-level class/function
  definitions instead of the non-existent "__export__" node type, so Python
  symbol-level call resolution works (was always exported=false).
- merge-edges.ts / import-resolver.ts: drop unused findConflictingEdges,
  EdgeConflict, and clearTsconfigCache (speculative dead code).
- walk.ts: simplify a no-op ternary on the call receiver.
- tests: add a Python module-level-export regression case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(wiki-engine): resolve Python dotted imports and TS implements edges

Two AST-resolver precision improvements from PR review follow-up:

- Python multi-segment imports: `from a.b.c import x` now maps the dotted
  module to a nested path (a/b/c.py or a/b/c/__init__.py) instead of only
  resolving single-segment sibling imports.
- TS/TSX IMPLEMENTS edges: a class's `implements` clause now produces
  IMPLEMENTS edges (source:"code-ast") to the interface's defining file,
  resolved via same-file interface symbols or imported bindings. A separate
  query pattern avoids the capture-map collision when a class implements
  multiple interfaces; names that resolve to neither (ambient/global types)
  are skipped rather than emitting a spurious edge.

Adds tests for both; Go cross-package module resolution remains a known
follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: mention IMPLEMENTS edges in AST track description

Keep README and usage-guide (EN/zh-CN) in sync with the new TS implements
edge support.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(import): serialize team-repo write phase to stop batch races dropping AST edges

Batch imports (--from-repo-list and --from-org, which run importFromRepo
concurrently at concurrency>=2) were losing AST edges: the final per-repo
graph-index.json committed to the team repo contained only code-heuristic
edges, while single --from-repo and concurrency=1 produced the correct
code-ast edges.

Root cause: each concurrent importFromRepo writes into the shared
teamwikiRoot (per-repo graph copy, facts/interfaces caches, source-manifest,
router/index, and reconcileKnowledge's global graph). Those are
read-modify-write operations on shared files, so parallel repos clobbered
each other's artifacts.

Fix: a module-level promise-chain mutex. The clone + extractCodebase phase
(the expensive part, which writes only to the per-repo cache dir) stays
parallel; only the team-repo write phase is serialized. A repo acquires the
lock after extract and releases it in the existing finally, so it is
exception-safe and never deadlocks. Verified: with the fix, concurrency=3
batch import produces the same code-ast edge counts as concurrency=1.

Adds unit tests asserting the mutex's mutual-exclusion, re-acquire, and FIFO
contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Revert "fix(import): serialize team-repo write phase to stop batch races dropping AST edges"

This reverts commit f798f21300.

* feat(import): add cross-process lock to protect uncommitted artifacts

During import, teamwiki artifacts sit uncommitted in the team-repo
working tree until the final aggregate+push. An ambient teamai hook
(SessionStart -> reportUsageToTeam) runs `git reset --hard HEAD` on that
same repo, wiping those artifacts before they are committed.

Add a cross-process file lock (import-lock.ts): the lock file lives in
the team-repo's PARENT dir so it is never reset away. It carries pid +
host + startedAt, with a 2h TTL, same-host PID liveness probe, and
dead-lock self-cleanup. Reference-counted for reentrant single/batch use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(import): hold import lock and skip hook reset during import

Wire the cross-process lock into the import paths and honor it in the
reset path:

- reportUsageToTeam: before resetToCleanMaster, check isImportInProgress
  and skip reset+pull when an import holds the lock.
- importFromRepo: acquire the lock around the whole artifact-write phase
  (steps 4-7), release in finally.
- importFromRepoList: hold one outer batch lock across the whole run so
  per-repo skipAutoPush artifacts survive until the final push.

Add deep-enrich-graph-preserve regression test proving deepEnrich never
mutates the per-repo graph-index.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(wiki-engine): resolve Python absolute package imports in AST track

The AST import resolver only handled relative and sibling-module imports (based on the importing file directory). Python code importing via the top-level package name rooted at the repo root (e.g. from hai_flow.conf import config) failed to resolve, leaving the import-binding table empty. Since cross-file call edges are built from those bindings, a large pure-Python repo produced zero code-ast edges despite hundreds of resolved calls. Add resolveAbsolutePackageImport: map a dotted specifier onto a repo-root-relative path and probe for a module file or package __init__.py. On a real 669-file Python repo this took the graph from 0 code-ast edges to 948.
2026-08-28 20:00:08 +08:00
jeffyxu 0e0051fdd3 0.21.0 2026-08-27 14:55:22 +08:00
baiabai frankandbaijinrong c3782a3e21 fix(opencode): Fix invalid agent configuration generated when recall is enabled (#344)
fix(opencode): Fix invalid agent configuration generated when recall is enabled
fix(recall): Clean up Codex native proxy files

---------

Co-authored-by: baijinrong <baijinrong@kuaishou.com>
2026-08-27 14:46:27 +08:00
jeffyxuandCursor 6bb1991277 0.21.0-beta.10
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 14:14:57 +08:00
jeffyxuandCursor a08308f487 0.21.0-beta.9
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 19:30:42 +08:00
jeffyxuandCursor 003bb712ec 0.21.0-beta.8
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:18:47 +08:00
jeffyxu 4197c4f0c4 0.21.0-beta.3 2026-08-26 14:32:25 +08:00
jeffyxu 3c9339bdea 0.21.0-beta.2 2026-08-25 17:48:27 +08:00
jeffyxu 8650d58d1c 0.21.0-beta.1 2026-08-25 14:59:33 +08:00
jeffyxu 02f251b7bf chore(release): 0.21.0-beta.0 2026-08-24 19:42:06 +08:00
jeff c89e4718c2 fix(deps): patch runtime security advisories in simple-git and js-yaml (#316)
Bump the two production dependencies flagged by Dependabot as
runtime-scope vulnerabilities:

- simple-git ^3.27.0 -> ^3.36.0 (installed 3.35.2 -> 3.36.0)
  fixes RCE advisory GHSA (Dependabot #6, high)
- js-yaml -> ^3.15.1 via overrides (pulled transitively by
  gray-matter, was 3.14.2) fixes quadratic-CPU DoS via merge-key
  chains (Dependabot #16, high)

`npm audit --omit=dev` now reports 0 vulnerabilities. Remaining open
alerts are all devDependency-scoped (build/test toolchain) and do not
ship in the published package.
2026-08-24 19:16:26 +08:00
jeffyxu 6abfc69f45 0.20.0 2026-08-20 18:51:13 +08:00
jeffyxu 6d44a47a6a 0.19.0 2026-08-06 15:47:04 +08:00
jeffyxu 9bebdf3272 0.18.0 2026-07-30 13:24:59 +08:00
jeffyxu 8eb79354c7 0.17.7 2026-07-27 11:06:00 +08:00
jeffyxu 3d0a666f6c 0.17.6 2026-07-22 20:01:50 +08:00
jeffyxu 653281213b 0.17.4 2026-07-20 11:05:57 +08:00
jeffyxu 7d323f0ade chore(release): 0.17.3 2026-07-14 11:04:37 +08:00
jeffandCursor 95c77ed051 chore(release): 0.17.2 (#159)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 11:27:53 +08:00
jeffandCursor 04eef5a947 chore(release): 0.17.1 (#157)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 19:47:12 +08:00
e922992f85 chore(release): 0.17.0 (#148)
Co-authored-by: jeffyxu <jeffyxu@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 16:57:18 +08:00
Jiahe Gengandjaelgeng 44e0bcb2ef feat(import): add listr2 progress bar and English CLI output (#117)
- Add listr2 dependency for structured multi-step progress display
- Wrap --from-repo, --from-repo-list, --from-mr, --from-org with
  listr2 task lists showing step-by-step progress with timers
- Translate ~80 Chinese log/spinner/error strings to English across
  import-repo.ts, import.ts, import-mr.ts, import-iwiki.ts,
  import-org.ts, import-repo-list.ts, codebase-extract.ts,
  deep-enrich.ts, import-local.ts
- Preserve Chinese in AI prompts and generated document content
- Preserve Chinese in code comments (only user-facing output translated)

Co-authored-by: jaelgeng <jaelgeng@tencent.com>
2026-07-03 10:44:50 +08:00
8825589d0f feat: git-free HTTP team repo + hooks-driven agent status reporting (#68)
* feat: HTTP team repo + hooks-based agent status reporting (issue #1)

实现 issue #1 评审通过的两套方案,共享同一套 skill 分发原语。

方案一(git-free HTTP 团队仓库 + skill 只读拉取):
- source-http.ts: GET /repo 物化(files[] 内联 + commands[] 走共享执行器),
  路径穿越防护,version 作为增量缓存 key
- init --http <url>: 只读消费者 onboarding(只需 API key,跳过 git/clone/member/reviewer)
- pull.ts: 抽象 refreshTeamRepo() 收口 git/http 两种刷新,其余管线原样复用
- read-only.ts: http kind 下 push/contribute/remove 明确拒绝

方案二(hooks 驱动的 agent 状态上报):
- machine-id.ts: 跨平台 machine_id(macOS ioreg / Windows reg / Linux machine-id)
  + local_agent_id 派生(install_path 仅本地哈希,不上报)
- status-report.ts: report/sync/ack 三接口 + 离线队列 + clawpro/local 来源区分;
  接口路径走可覆盖的内部映射(默认 iWiki 契约,TEAMAI_REPORT_PATHS 可覆盖)
- 挂到既有 hook dispatch: session-start→report+sync,prompt-submit→sync
- openclaw-hooks.ts: WorkBuddy(龙虾系)HOOK.md + handler.ts 注入适配器

共享地基:
- skill-command.ts: executeSkillCommand(fflate 解压 zip,含 SKILL.md 校验、
  路径穿越防护、SMH 直连下载),push/pull 两条路径共用
- api-key.ts: 统一 Bearer 凭证解析/保存(0600,不入 config/不上报)
- teamai login <key> 命令

测试: 本地实现三接口 mock HTTP 服务(进程内 helper + scripts/mock-teamai-server.mjs
独立可运行版),单测 + 集成端到端共 49 个新用例全部通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit e8e50532024bfccf83a1b968cfc7d5a0de4472e3)

* fix: align local-agent reporter with updated backend contract

后端契约调整(对齐 clawpro https://5hborhrw.cvmopenclaw.site):
1. 接口名去掉 v1:/api/v1/local-agent/* → /api/local-agent/*
2. ack 的 command id 从 path 移到 request body,类型 int:
   POST /api/local-agent/commands/ack,body { id: <int>, status, error }

- status-report.ts: EndpointMap.ack 由路径构造函数改为固定路径字符串;
  默认路径去 v1;ack body 带 int id。TEAMAI_REPORT_PATHS 覆盖保留。
- skill-command.ts: SkillCommand.id string → number。
- mock-server.ts / mock-teamai-server.mjs / 单测同步更新。

端到端验证(真实后端):report/sync → 200,ack 路由按 int body id 校验;
完整 install→ack 闭环(mock)通过。tsc 通过,vitest 1454 passed 无回归。

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit fe5d649f4f29a0e70996a011ee670bb32ec7ca27)

* fix: wire WorkBuddy via settings.json Claude hooks (not OpenClaw)

Real-device verification (WorkBuddy 5.2.0) shows WorkBuddy embeds the CodeBuddy
CLI engine and reads Claude-format hooks from ~/.workbuddy/settings.json:
SessionStart / UserPromptSubmit / PostToolUse all fire with PascalCase event
names and CLI-style tool names (tool_name="Bash"), identical to codebuddy.

MR 191 originally (wrongly) assumed WorkBuddy used the OpenClaw HOOK.md engine,
so teamai never wired it (0 workbuddy events on a real machine). Fix:

- types.ts: toolPaths.workbuddy now carries settings: '.workbuddy/settings.json'
  → routes through the Claude-format injection path like codebuddy.
- hooks.ts: drop workbuddy from OPENCLAW_TOOLS (kept for the still-unverified
  openclaw/qclaw/easyclaw/autoclaw variants).
- openclaw-hooks.ts: clarify it's no longer for WorkBuddy; default tool=openclaw.
- tests updated to assert workbuddy → settings.json hooks (--tool workbuddy),
  OpenClaw HOOK.md path only for openclaw.

Verified end-to-end on the real machine: `teamai hooks inject` writes the
workbuddy hook block (preserving claw/enabledPlugins/sandbox); real WorkBuddy
fires the hooks; `hook-dispatch --tool workbuddy` records tool=workbuddy in the
dashboard. tsc OK, vitest 110 files / 1466 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit d1ec907433177e3ac76ed81867fec928be647293)

* fix: teamai uninstall also removes OpenClaw HOOK.md hooks

MR 191 added injectOpenClawHooks but uninstall only removed hooks from tools
with a `settings` path, so OpenClaw-style HOOK.md/handler.ts dirs
(~/.<tool>/hooks/teamai-status-report) leaked on uninstall.

- uninstall.ts: discover + remove OpenClaw hook dirs for settings-less tools
  (mirrors the inject path); listed in the removal summary.
- Added regression test asserting the OpenClaw HOOK.md dir is removed.

Verified end-to-end: with workbuddy now settings-based, `uninstall` strips its
teamai hooks while preserving claw/enabledPlugins/sandbox; OpenClaw HOOK.md dirs
are removed (previously leaked); real-machine dry-run lists ~/.workbuddy/
settings.json. tsc OK, vitest 110 files / 1467 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 4a9a2361c55292547dd033ade98825ac3083811a)

* feat: tolerate missing /repo in HTTP init (reporting-only mode)

A clawpro-style backend may ship the status-reporter endpoints before the
team-repo /repo endpoint exists. Previously `teamai init --http <url>` hard-
failed on such endpoints (/repo → 404 or 200 HTML), so there was no first-class
way to configure endpoint+key for reporting.

- source-http.ts: fetchRepoSnapshot now distinguishes "/repo not live yet"
  (404 or non-JSON 200 body) by throwing RepoNotAvailableError; auth (401/403)
  and other errors stay hard failures.
- init.ts: on RepoNotAvailableError, init --http enters reporting-only mode —
  writes a minimal local teamai.yaml (default toolPaths), saves the http config
  (endpoint+key), injects hooks, and prints a clear notice. When /repo later
  comes online, a normal pull materializes skills with no re-init.
- pull.ts: refreshTeamRepo swallows RepoNotAvailableError (reporting-only) so
  every session doesn't error while /repo is absent.
- tests: RepoNotAvailableError classification (404 / non-JSON / 500).

Verified e2e against the real reporter-only backend: `teamai login <key>` +
`teamai init --http <url>` now succeeds (reporting-only), reporter report/sync
hit the backend (200, no offline queue), and pull no longer hard-fails.
tsc OK, vitest 110 files / 1470 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 75b03c633a731f302375c764df970a5b84dba00d)

* refactor: fold `teamai login` into `init --http --token` (remove login command)

Reduces command surface and puts endpoint + key in one place (addresses the
"why are login and endpoint separate" awkwardness). The `login` command was
only introduced in this unreleased HTTP/reporter feature set, so removing it is
not a breaking change for released users.

- index.ts: remove `login` command; add `--token <key>` to `init`.
- init.ts: initHttp persists --token via saveApiKey (0600) before resolving;
  still falls back to TEAMAI_API_TOKEN / existing apikey file.
- update "run `teamai login`" hints in source-http.ts / pull.ts / api-key.ts.

One-command setup now:
  teamai init --http <url> --token <key>

Verified e2e against the real reporter-only backend: single command saves the
key, configures the http endpoint (reporting-only), injects hooks; reporter
report/sync hit the backend (200). `login` no longer appears in --help.
tsc OK, vitest 110 files / 1470 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit e98b48f7fc63eddc755fa9ef04260ad9055c49d3)

* fix: skip git usage auto-report for HTTP consumers (no .git → noisy ERROR)

In HTTP team-repo mode the local team-repo path is not a git checkout, so
pull's Step 5 usage auto-report (reportUsageToTeam, git-based) failed every
session with `[ERROR] Auto-report skipped: fatal: not a git repository`. HTTP
consumers are read-only and have no git remote to report to, so skip the step
entirely when repo.kind === 'http'.

Verified: `teamai pull` in HTTP reporting-only mode now produces no Auto-report
error. tsc OK, vitest 110 files / 1470 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 388078d0c587c03602b93a5635e193961e84e592)

* feat: log skill-command execution in the reporter (observability)

Skill install/uninstall driven by `sync` commands previously ran silently —
on failure the reporter only ack'd `failed` with no local log, making it
impossible to see why a dispatched skill (e.g. fd-find) didn't install. Now:

- log.debug the number of commands returned by sync
- log.debug each command success
- log.error each command FAILURE with the underlying error message

No behavior change beyond logging. tsc OK, vitest 110 files / 1470 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 9bbabfcc0ef467b1addf605a29a2754632c455f4)

* chore: drop accidentally committed .teamai/domains.yaml (test artifact)

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 882698389411271ca359725b17e61907143c12ad)

* feat: surface skill download URL + server error body in reporter logs

A failed install previously logged only "download failed: HTTP 409",
giving no clue what was being fetched. Now downloadZip logs the signed
download_url before fetching and includes the server response body + URL
in the thrown error (which lands in debug.log and the ack error field).
The reporter also logs each sync command (incl. download_url) and renders
empty skill_version as "?" instead of a bare "@".

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 2a58ede7abf0e411ad61474adc0b65d21887f713)

* feat: log every reporter run + report/sync/ack outcome

Previously a successful report or sync produced no log line, so there was
no way to tell whether a SessionStart/UserPromptSubmit hook actually fired
or whether sync ran ("seems sync never triggered"). Now each invocation
logs run (agent/phase/id/endpoint) and the OK/FAILED outcome of report,
sync (with command count), and each ack.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 49ac63953c9d6e33e8f9998a2be86ea91da2e00e)

* feat: skip team-repo built-in skills in HTTP reporting-only mode

teamai-share-learnings and teamai-wiki both write to the team repo, so in
reporting-only HTTP mode (no /repo) they are non-functional. refreshTeamRepo
now reports reportingOnly, which pull threads into deployBuiltinSkills to
skip injecting them.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit d5c40475ba99078174ba553719b707e6a0d88ccc)

* fix: accept flat skill zips (SKILL.md at root), not just <slug>/SKILL.md

The clawpro/skillhub backend packages skills as a flat zip (SKILL.md +
_meta.json at the root, e.g. find-skills-skill), but installSkillZip required
a top-level <slug>/ directory and failed with "skill package missing
<slug>/SKILL.md". installSkillZip now resolves the SKILL.md location across
three layouts (nested-by-slug, flat root, nested-other-name) and installs the
contents into <skillsDir>/<slug>/.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit fbdf643c79c42613eb52fb7c383fddeb893b9b83)

* fix: use reconcileTeamHooksForConfig for HTTP init hook injection

Reconcile the issue-#1 HTTP init path onto main's unified-hooks (#65)
architecture: the old `injectHooksToAllTools(toolPaths, baseDir)` entry no
longer exists, so the HTTP consumer now injects hooks via the same
authoritative `reconcileTeamHooksForConfig` path the git init uses. Fixes the
tsc TS2304 (injectHooksToAllTools / resolveBaseDir not found) seen in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: document git-free HTTP team repo + agent status reporting

Add a "Read-only consumers (HTTP team repo, no git)" quick-start subsection
to both README.md and README.zh-CN.md, covering `init --http --token`,
reporting-only fallback, hooks-driven status reporting, and the local-only
hashing of install path / machine id.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: document the HTTP contract (endpoints, /repo schema, env knobs)

Add a collapsible "HTTP contract" block to both READMEs spelling out what a
--http backend must serve: the fixed GET /repo snapshot shape, the three
(overridable) local-agent reporter endpoints, the signed download_url +
accepted zip layouts, and the env vars that make paths/hosts/agents
configurable. Clarifies what is fixed vs configurable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(reporter): drop per-skill `source` tag and clawpro bookkeeping

The report payload no longer tags each skill `source: clawpro|local`. With
`source` gone, the entire clawpro-skills.json bookkeeping (getClawproSlugs /
recordClawproSlug / clawproRecordPath) had no remaining consumer, so it is
removed. `scanReportableSkills(skillsDir)` now just lists every skill found in
the agent's own skills dir. Tests updated accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 15:54:38 +08:00
jeffyxu ba149c8447 0.16.8 2026-06-18 17:28:45 +08:00
jeffyxuandClaude Opus 4.8 583943b671 Merge origin/master (0.16.7) into GitHub line: reconcile hook architectures
Reconciles the TGit lineage (published as npm 0.16.7) with the GitHub
lineage (#28-#30: subagent push, knowledge-base import, contribute-check
Phase 2) by adopting the unified hook-dispatch architecture and porting the
GitHub-only hooks onto it:

- todowrite-hint: registered as a post-tool-use/TodoWrite dispatch handler;
  hooks.ts now emits a `hook-dispatch post-tool-use --matcher TodoWrite` entry.
- mr-hint: registered as a session-start dispatch handler; mr-hint.ts gains a
  stdin-free computeMrHintOutput() core (the dispatcher consumes stdin once).
- contribute-check handler now forwards cwd for #30's knowledge-gap logic.
- Legacy per-command hooks (incl. todowrite-hint, mr-hint) added to the
  migration cleanup list.

Tests updated for the merged dispatch counts (PostToolUse 6->7, total 9->10).
All unit + e2e tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:28:38 +08:00
jeffyxu aff668899e 0.16.7 2026-06-12 17:40:45 +08:00
Jiahe Gengandjaelgeng 7fd9f436a4 Phase 0+Phase 1+P4.4: 实现了subagent推送,检索subagent,初始化知识库导入以及MR自动learnings&codebase更新功能(团队级别codebase文档功能有待完善) (#28)
* docs:知识库飞轮系统设计roadmap文档

* feat: phase 1 - agents support, multi-index search, recall subagent & todowrite hint

- Add agents resource type (pull/push/remove support)
- Add builtin-agents with teamai-recall agent
- Add multi-source search index (rules + skills + agents)
- Add todowrite hint injection for recall subagent
- Add phase1 e2e tests and unit tests for new features
- Update README (zh-CN + en) with agents documentation

* docs: update roadmap文档

* feat(search): P1.4 domain inference + search weighting

Introduce KnowledgeDomain (technical/ops/support/neutral) inferred from
frontmatter > tags > path > type fallback. Apply per-domain score
multipliers at search time (technical ×1.0, neutral ×0.85, ops ×0.5,
support ×0.3) plus a skills/rules type bonus (×1.1).

Bump SEARCH_INDEX_VERSION 2→3; legacy v2 indexes auto-rebuild on next pull.

--other=P1.4 domain inference and search weighting

* test(validation): add Phase 1 E2E suite and acceptance report

Move phase1-e2e.test.ts to validation/ alongside the acceptance report,
update import paths and vitest.e2e.config.ts to pick up the new location.

Fix pre-existing E2E isolation bug: loadStateForScope mock used
mockResolvedValue (shared object reference), causing test-1 to mutate
state.lastPullRev and trigger the rev-based early-exit in test-2/3.
Switch to mockImplementation(() => ({ lastPull: null })) so each call
gets a fresh object. All 5 E2E tests now pass.

--other=Phase 1 validation

* docs: update roadmap — add P4.6 learning promotion mechanism

Learning entries that accumulate confidence ≥ 0.90 (5+ upvotes, 2+
contributors, 14+ days old) are prompted for promotion to docs/skills/rules
based on content type, regardless of origin or domain. Add P4.6 step to
Phase 4, dependency graph, implementation detail, work-estimate table,
and architecture overview diagram.

--other=roadmap update

* docs(validation): update phase1 report to reflect E2E bug fix

All 5 E2E tests now pass. Update P1.2 status to ✅, remove E2E
failure notes and known issues, set pass rate to 100%.

--other=phase1 report update

* docs(validation): add runtime evidence appendix to phase1 report

Append Appendix A1-A4 with captured outputs from demo-phase1.test.ts:
agents sync paths, CLAUDE.md full content after injection,
search-index.json entry listing (4 types covered), and recall("api")
full STDOUT including envelope markers and domain-weighted scores.

Knowledge content in A3/A4 is anonymised (titles, authors, file paths
replaced with generic placeholders).

Also commit demo-phase1.test.ts as the reproducible evidence runner.

--other=phase1 report runtime evidence

* feat(search): query-aware domain weights + IDF scoring (v4 index)

改动 A — 查询感知 domain 权重:
将静态一维 DOMAIN_WEIGHT 改为二维查询-文档权重矩阵,新增
inferQueryDomain() 从查询 token 推断查询域。搜索 k8s/deploy 等
ops 相关问题时,ops 条目不再被打五折,technical 查询行为与原来一致。

改动 B — IDF 降权:
buildIndex() 末尾计算 df(文档频率)map 并写入索引;search() 中
为每个 token 匹配乘以 log((N+1)/(df+1))+1 的 IDF 权重,高频通用词
(api、deploy、error)自动降权,低频专有词(deepgemm、mooncake)
权重保持不变。

索引版本 3 → 4;isLegacyIndex() 新增 !index.df 判断触发重建。
旧 v3 索引在下次 teamai pull 时自动重建,search() 对无 df 字段的
旧索引降级为 idf=1.0,不报错。

--other=search quality improvements

* feat(import): add teamai import command — Phase 0 cold-start + P4.4 MR pipeline

## 新增命令:teamai import

支持五种知识来源:
- --dir <path>:扫描本地目录,AI 分类为 rule/doc/learning
- --from-claude:迁移 ~/.claude/rules 等 AI 工具规则目录
- --workspace:基于当前 git 仓库生成 codebase.md
- --from-mr <url>:从已合并 MR 提炼 learning + codebase 更新建议(P4.4)
- --from-iwiki <id/url>:从 iWiki Space 批量导入文档

## 新增核心模块

- src/utils/ai-client.ts:claude -p 子进程封装(并发 ≤ 3,60s 超时)
- src/utils/dedup.ts:Jaccard 相似度重复检测(14 天窗口,≥ 60% 标记 superseded)
- src/utils/iwiki-client.ts:iWiki MCP HTTP 客户端(JSON-RPC 2.0,零外部依赖)
- src/import-local.ts:本地文件扫描/AI 分类/交互确认/推送
- src/import-mr.ts:MR 三层解析/双路 AI 提炼/dedup/推送
- src/import-iwiki.ts:iWiki 导入(复用 import-local.ts 基础设施)
- src/codebase.ts:codebase.md 生成/增量更新

## 扩展现有接口

- providers/types.ts:GitProvider 新增可选 fetchMergeRequest() 方法
- providers/github/mr-fetch.ts:gh pr view 实现
- providers/tgit/mr-fetch.ts:gf mr 实现
- types.ts:新增 MRData/ClassifiedItem/LearningDraft/CodebaseSuggestion/ImportSession

## 测试 & 文档

- ai-client.test.ts:5 tests(spawn mock + 并发控制)
- dedup.test.ts:11 tests(关键词提取 + Jaccard + 文件扫描)
- validation/phase0-p44-acceptance-report-public.md:Phase 0 + P4.4 验收报告

--story=132854480 【产品需求】teamai-cli Phase 0 冷启动 + P4.4 MR 提炼流水线

* fix(import): support claude-internal CLI + gh REST API fallback + real PR demo

## ai-client.ts
- detectClaudeCli():按 claude → claude-internal 优先级探测,结果缓存,
  进程内只探测一次,两者均不可用时给出清晰错误提示
- DEFAULT_TIMEOUT_MS:60s → 180s,适应大 diff 下 AI 提炼耗时

## providers/github/mr-fetch.ts
- gh CLI 不可用时自动降级到 GitHub REST API(内置 https,零依赖)
- 支持公开仓库无 token 访问;有 GITHUB_TOKEN 环境变量时自动携带

## import-mr.ts
- codebase 建议 JSON 解析:先提取 {…} 块再解析,
  兼容 AI 在 JSON 前附加说明文字的输出格式

## index.ts
- import 子命令新增 --all 选项,跳过交互确认

## validation
- phase0-p44-acceptance-report-public.md A4:替换为基于真实 PR #2
  的端到端操作记录(真实终端输出 + AI 真实生成的 learning.md
  和 codebase-suggestions.json 完整原文)

--story=132854480 【产品需求】teamai-cli Phase 0 冷启动 + P4.4 MR 提炼流水线

* fix(codebase): require file-path prefix in module descriptions for agent guidance

codebase.ts 和 import-mr.ts 的提示词中"主要模块"格式要求均已更新:

- 之前:**模块名** — 功能说明(AI 可能只写中文名,无路径索引)
- 之后:**文件或目录路径** — 功能说明(明确要求带路径,便于 agent 定位)

codebase.ts: 全量生成 prompt 示例改为 **src/utils/git.ts** — 功能说明
import-mr.ts: codebase 建议 prompt 新增正确/错误示例,强制路径前缀

同步更新验收报告 A4:
- codebase-suggestions.json 从 8 条无路径条目 → 11 条带路径条目(真实重跑输出)
- 更新后 codebase.md 主要模块列表格式与更新前一致

--story=132854480 【产品需求】teamai-cli Phase 0 冷启动 + P4.4 MR 提炼流水线

* feat(codebase): upgrade workspace + MR prompts to A1-level documentation quality

codebase.ts — gatherRepoContext 扩展:
- 增加 package.json(依赖和 scripts)
- 增加入口文件(src/index.ts)命令注册全文
- 增加类型定义文件(src/types.ts)关键接口
- 文件树深度 maxdepth 3→4,过滤 dist/ worktrees/
- 截断上限:FILE_TREE 3000→5000 字符,DOC 1000→2000 字符
- 新增 META_MAX_CHARS 常量(2500 字符)

codebase.ts — 全量生成 prompt 重写:
- 提供完整 8+ 章节格式骨架(项目概述/技术栈/目录结构/数据配置/
  核心数据流/关键接口/配置系统/性能可靠性/测试覆盖/备注)
- 目录结构要求带分组框树形图(┌─ 功能分组 ──┐ 风格)
- 技术栈要求表格含版本信息
- 项目概述要求带 emoji 核心能力 bullet list
- 核心数据流要求带缩进 → 的流程图格式

import-mr.ts — codebase 建议 prompt 升级:
- 新增 existingCodebaseMd 参数,注入现有文档全文作为格式样本
- AI 参考现有文档的分组和粒度生成风格一致的增量条目

import.ts — --from-mr 分支:
- 调用前读取 repoPath/docs/codebase.md 传入 existingCodebaseMd
- 确保 MR 增量更新与初始生成风格一致
- 复用已导入的顶层 fs 模块,删除内联 dynamic import

--story=132854480 【产品需求】teamai-cli Phase 0 冷启动 + P4.4 MR 提炼流水线

* docs(validation): replace A1+A4 codebase docs with real teamai-cli generated output

A1 附录:替换为 teamai import --workspace 对 upstream/main 真实生成的
codebase.md(含分组框目录树、表格技术栈、emoji 核心能力、流程图数据流)

A4 附录:
- Step 1:更新 codebase-before.md 为同一真实生成版本
- Step 2/3:终端输出更新为 3 条建议(主要模块 7 条/关键路径 4 条/架构决策)
- Step 3:learning.md 更新为最新真实 AI 输出
- Step 4:更新前后对比基于真实文档
- Step 5:飞轮闭环统计数字更新(3 条建议)

--story=132854480 【产品需求】teamai-cli Phase 0 冷启动 + P4.4 MR 提炼流水线

* feat(mr-hint): add SessionStart hook to hint AI about unimported merged MRs

P4.4 优化:在每次 Session 开始时检测当前 git 仓库的 origin remote,
查询近 7 天内已合入但尚未通过 teamai import 处理的 MR,并通过
additionalContext 提示 AI 在任务完成后建议用户运行 teamai import --from-mr。

核心实现:
- src/mr-hint.ts:新增 mrHint() 入口,支持 TGit REST API 和 GitHub gh CLI
  双路查询;per-repo 磁盘缓存(30 天 TTL)避免重复提示相同 MR
- src/hooks.ts:注册 SessionStart hook,更新 TEAMAI_COMMAND_MARKERS
  和 TEAMAI_HOOK_SUBCOMMANDS,同步 buildCursorHooks
- src/index.ts:注册 mr-hint 子命令
- 同步修复 hooks.test.ts、usage-tracking.test.ts、doctor.test.ts
  中 todowrite-hint 加入后遗留的计数断言

--other=P4.4 MR 合入统一处理流水线(SessionStart 触发提示)

* feat(mr-hint): add GitHub REST API fallback when gh CLI unavailable

gh CLI 不在环境中时自动回退到 GitHub REST API(/repos/.../pulls),
逻辑与 providers/github/mr-fetch.ts 保持一致;
支持公开仓库无 token,有 GITHUB_TOKEN 时自动携带以提升限速上限。

--other=P4.4 MR 合入统一处理流水线(mr-hint GitHub REST fallback)

* docs(validation): update public acceptance report for P4.4 mr-hint trigger mechanism

新增 P4.4 触发机制优化章节,更新附录 A1(基于含 mr-hint 模块的代码库
真实生成),追加附录 A4.2(SessionStart hook 自动感知 merged PR 的真实
运行场景,含 GitHub REST API fallback 演示与幂等性验证)。

--other=P4.4 MR 合入统一处理流水线验收报告更新

* feat(ai-client): support login shell PATH + extend CLI candidates

- 用 bash -lc 包裹探测和调用,解决 ~/.nvm 路径下 CLI 不在 PATH 的问题
- 探测顺序扩展为 claude / claude-internal / codex / codex-internal /
  codebuddy / workbuddy / openclaw
- 新增 shellEscape() 避免 prompt 中单引号破坏 shell 命令
- 超时从 180s 降至 120s
- 测试补充 execFileSync mock,修复预存 5 个失败用例

feat(import-mr): interactive codebase review loop + apply suggestions

- 新增 reviewCodebaseSuggestions():AI 实时修订循环,用户输入意见
  → AI 修订 → 再展示,直到 y 确认或 n 跳过
- --output 模式:apply 后写 codebase-after.md(完整 Markdown)
- repoPath 模式:apply 后写回 docs/codebase.md
- 修复 codebase.ts apply prompt,确保输出完整文档而非摘要

feat(import): add --existing-codebase option

allow 用户显式指定 before codebase.md 路径,不依赖团队仓库;
优先级高于从 repoPath/docs/codebase.md 自动读取

--other=P4.4 MR 合入统一处理流水线优化

* docs(validation): update A4 with real before/after codebase demo

用真实运行产物替换 A4 中的 codebase before/after 内容:
- Step 1 before:由 teamai import --workspace 在 PR #2 合入前的代码库生成
- Step 3 suggestions:teamai import --from-mr PR #2 的真实 AI 输出
- Step 4(新增):apply suggestions 后的 codebase-after.md,含 diff 展示
三阶段格式统一,均为同一版本 prompt 的真实运行产物

--other=P4.4 验收报告 A4 codebase before/after 更新

* fix(providers): add TGit REST API fallback + multi-shell CLI detection

- mr-fetch.ts: 新增 fetchTGitMRViaApi(),gf CLI 不可用时自动 fallback
  到 git.woa.com REST API(使用 ~/.netrc OAuth token);
  diff 获取失败时降级为空字符串而非中断流程
- ai-client.ts: detectClaudeCli() 对每个候选依次尝试
  bash -lc → zsh -lc → which,覆盖 fish/CI 容器等非标准 shell 环境

--other=fix-risk-items

* docs: 验收文档typo更正

* fix(ai-client): multi-CLI compat + shell injection hardening

- ai-client.ts: detectClaudeCli 解析 CLI 绝对路径并校验存在性,
  spawn 改为直调 absPath + 参数数组,删除 shellEscape 去 shell;
  新增 buildCliArgs 区分 codex/codex-internal 用 'exec' 子命令、
  其余 CLI 用 '-p',修复 codex 系 CLI 调用失败问题
- providers/tgit/mr-fetch.ts: execSync → execFileSync 数组参数,
  消除 mrIid/repoArg 命令注入风险
- mr-hint.ts: TEAMAI_MR_HINT_CWD 增加 path.resolve + statSync
  校验,非法路径静默跳过
- ai-client.test.ts: mock 适配新探测语义(command -v 返回路径
  + existsSync=true)

--other=phase0-p44-cli-compat-and-security

* feat(codebase): align with llm-wiki — frontmatter / index / lint / multi-source

参照 docs/llm-wiki.md 的持久化知识库理念,对 codebase 文档生成做四项优化:

- frontmatter:generateCodebaseMd 输出顶部注入 YAML frontmatter
  (title / lastUpdated / source / generator / schemaVersion),
  支持去重旧 frontmatter,便于跨会话溯源
- 索引体系:新增 generateCodebaseIndex 导出,从 codebase.md 抽取
  二级章节 + 一句摘要 + 关键词,输出 codebase-index.md,加速 LLM 跨
  会话定位
- 健康检查:新增 lintCodebaseMd 导出,AI 检测矛盾/过时/孤儿/缺失
  四类问题,返回 LintReport(含 severity 分级),不修改文档
- 多源聚合:generateCodebaseMd 入参新增 learningsSuggestions 与
  learningsDir,gatherLearningsContext 内部函数读取 learnings/*.md
  frontmatter tags 做高频统计,融合 P4.4 MR 建议进 prompt
- prompt 模板新增"架构决策与权衡""已知限制与演进方向"两章节
- import.ts workspace 流程串入索引生成 + lint 报告打印
- types.ts 新增 LintIssue / LintReport 接口
- 新增 codebase.test.ts 11 个单元测试,全部通过

--other=phase4-codebase-llm-wiki-alignment

* feat(search): codebase-index.md high-weight + skip codebase.md

- 新增常量 CODEBASE_INDEX_FILENAME / CODEBASE_FULL_FILENAME /
  CODEBASE_INDEX_WEIGHT_BOOST(×1.5)
- entryFromMdFile:同目录存在 codebase-index.md 时自动跳过
  codebase.md,避免全量文档与索引文件重复命中
- search():codebase-index.md 命中时额外乘以 1.5 权重 boost,
  recall 时章节摘要优先返回
- 兼容 subagent 与 fallback recall 两条路径,boost 在本地索引
  阶段生效,无额外 AI 调用开销
- 新增 3 个测试用例(跳过逻辑 / 权重 boost / fallback 路径),
  search-index.test.ts 共 26 tests 全通过

--other=phase4-codebase-index-search-boost

* docs(validation): refresh A1/A4 with llm-wiki-optimized codebase output

用新版 CLI(llm-wiki 优化后)重新执行 A1/A4,更新公开版验收报告产物:
- codebase-before.md:含 YAML frontmatter、架构决策与权衡、
  已知限制与演进方向两新章节
- codebase-index.md:新增章节索引文件(11 行索引表)
- codebase-after.md:PR #2 应用建议后的更新版
- learning.md / codebase-suggestions.json:最新 AI 提炼产物
- 记录实际使用模型:claude-internal v1.1.9(DeepSeek-V3.1-Terminus)
- 保持 tgit → [internal] 等脱敏风格

--other=phase0-p44-acceptance-report-refresh

* docs(validation): replace codebase-after full content with before/after diff

A4 Step 4 中 codebase-after.md 展示方式由全文改为 unified diff,
更直观反映 MR 建议应用后的变更:新增"主要模块"章节(+8 行),
包含 import-local/import-mr/import-iwiki/codebase 四个关键模块说明

--other=phase0-p44-acceptance-report-refresh

* docs(roadmap): add Phase 6 — Phase 5 hardening

Phase 5 shipped the team-level codebase aggregation pipeline; in
shipping it we deliberately deferred several reliability concerns to
keep each step deliverable. Phase 6 captures those deferrals as a
focused hardening pass — no new capability surface, just turning the
Phase 5 deliverables into something safe to run in production
indefinitely.

Six sub-steps, three independent (P6.0 / P6.1 / P6.5) and three
chained (P6.2 → P6.3 → P6.4):

- P6.0  Real TGit listOrgRepos (replace stub)
- P6.1  Cache lifecycle (LRU + size cap + GC command)
- P6.2  Section-level diff with HTML-anchor in-place updates
- P6.3  pending-review CLI (review / apply / reject)
- P6.4  Domain-drift auto-apply workflow
- P6.5  Global codebase doc lint (cross-file consistency)

Appendix C dependency table updated with all six rows.

Phase 5 leftovers explicitly out of scope here (二级业务域 / 跨仓重复
检测 / search-index 联动 / agent 检索效果量化) are listed under the
new "遗留至 Phase 7" block.

--other=phase6-roadmap

* feat(import): Phase 5 — team-level codebase aggregation

Lift teamai-cli's codebase knowledge base from a single-repo, local view
into a team-wide, multi-repo aggregation that can be initialized in one
command, kept in sync incrementally, and audited end-to-end. The work
ships in five sub-steps but is a single coherent feature; merging as one
commit per the project's MR rules.

What's new
==========

P5.0  Business-domain dictionary (src/domains/*)
    Zod schema + YAML store + AI batch clustering + single-repo
    recommendation + interactive review CLI + jsonl audit log. Library
    only; no CLI wiring at this step.

P5.1  Single remote repo import
    `teamai import --from-repo <url>` shallow-clones into
    ~/.teamai/cache/repos/<provider>/<owner>/<repo>, reuses the existing
    generateCodebaseMd scanner, and writes a per-repo summary at
    docs/team-codebase/repos/<slug>.md. Three-tier auth (HTTPS+token /
    HTTPS-anonymous / SSH); tokens are always redacted in error output.

P5.2  Batch import + domain aggregation
    `--from-repo-list <yaml>` drives a whitelist with per-entry
    { url, domain, auth, priority } plus org entries (deferred). Failures
    on one repo don't block siblings. Pure-template aggregator emits
    docs/team-codebase/domains/domain-<name>.md and a top-level
    docs/team-codebase/index.md from the per-repo files. Default output
    root moved to docs/team-codebase to avoid colliding with the
    existing teamai-cli self-codebase at docs/codebase.md.

P5.3  Incremental sync + domain drift
    `--incremental` skips the full clone when the cache is hit and
    LAST_SYNC is present, falling back to a fresh shallowClone if fetch
    fails. After scan, a fresh recommendDomain pass is compared against
    the existing assignment; divergent recommendations (different
    domain + confidence > 0.5 + delta > 0.4) land in
    domains.history.jsonl as a drift event without auto-reassigning.
    AI failures never block the main flow. CI scheduling examples
    shipped under examples/ci/ for GitHub Actions and Coding CI.

P5.4  Org bootstrap + iWiki dual output
    `--from-org <org> --bootstrap` lists repos via gh api (paged with
    /orgs/<o>/repos -> /users/<o>/repos fallback), AI-clusters them,
    and walks the user through reviewDomains to produce both
    domains.yaml and repo-whitelist.yaml before chaining into
    importFromRepoList for the first full sync. TGit listOrgRepos is a
    stub (Phase 6).
    `--from-iwiki --iwiki-dual` extracts business APIs / external
    knowledge / glossary into docs/team-codebase/external-knowledge.md
    guarded by HTML comment anchors so future syncs replace bodies in
    place. `--require-review` defers section writes to
    .teamai/pending-review.jsonl. A small source-conflict helper flags
    multi-source updates within a 24h window.

Filesystem layout introduced
============================

  docs/team-codebase/
    index.md                  # business-domain map + repo index
    domains/domain-*.md       # per-domain aggregate
    repos/<slug>.md           # per-repo detail (from --from-repo)
    external-knowledge.md     # iwiki-extracted sections
  .teamai/
    domains.yaml              # business-domain dictionary
    domains.draft.yaml        # AI cluster draft
    domains.history.jsonl     # decision audit
    repo-whitelist.yaml       # repo allowlist
    source-marks.jsonl        # multi-source conflict tracking
    pending-review.jsonl      # deferred high-risk changes
  ~/.teamai/cache/repos/      # shallow-clone cache + LAST_SYNC

Surface area
============

CLI flags added to `teamai import`:
  --from-repo / --from-repo-list / --from-org / --bootstrap
  --depth / --ssh / --domain / --concurrency / --skip-aggregate
  --incremental / --max-repos / --exclude-archived
  --include-pattern / --exclude-pattern / --skip-import
  --iwiki-dual / --require-review

Tests
=====

~95 new unit tests across 14 new test files. Full suite passes
1165/1170 (the one remaining failure in types.test.ts pre-dates this
change). tsc clean (only the pre-existing recall.test.ts error is left).

Out of scope (tracked in Phase 6)
=================================

- TGit listOrgRepos real implementation (currently a stub)
- Cache LRU + 5GB cap + GC command
- Section-level diff with in-place anchor updates
- pending-review CLI to consume the deferred changes
- Domain-drift auto-apply workflow
- Global codebase doc lint

--other=phase5-team-codebase

* feat(agents): multi-CLI subagent sync + security hardening

- introduce YAML intermediate spec for team agents with renderers
  for claude / claude-internal / codebuddy / codex / codex-internal / cursor
- add agents path for codex / codex-internal / cursor in toolPaths
- pull renders per target tool format (.md / .toml); push reverses
  native files back to YAML, warns and skips on conflicts
- legacy agents/*.md still synced to claude-family for back-compat

Security fixes:
- clone.ts: drop token-in-URL, use http.extraHeader + sanitizeGitUrl
- ai-client.ts: execFileSync with shell:false, timeout, CLI whitelist
- path-safety.ts: assertSafePath + assertSafeResourceName
- import-local.ts: path traversal guard on --dir / output
- push.ts --skill / status.ts --agent: assertSafeResourceName
- env-commands.ts: env list masked by default, add --reveal flag

CSIG fixes:
- parseAgentYaml returns ParseResult instead of throwing
- fileContentEqual catch logs the error instead of swallowing

* feat: Phase 6 — Phase 5 hardening pass

Phase 5 shipped the team-codebase aggregation pipeline; in shipping
it we deliberately deferred a handful of reliability concerns to keep
each step deliverable. Phase 6 closes those gaps -- no new capability
surface, just turning the Phase 5 outputs into something safe to run
in production indefinitely. Six sub-steps, three independent and
three chained, merged here as one commit per project MR rules.

What's in the box
=================

P6.5  Global codebase doc lint  (src/codebase-{lint,cmd}.ts)
    A deterministic, AI-free cross-file lint over docs/team-codebase
    and the .teamai/ controls. 12 categories spanning anchor
    integrity, repo/whitelist consistency, sync staleness, frontmatter
    completeness, multi-source conflict, etc. `--fix` is intentionally
    narrow: only mechanical low-risk actions (orphan-md → archived,
    schemaVersion backfill, index counts refresh). High issues that
    aren't fixable end up in the skipped list. Exit 1 when any high
    remains so CI can gate merges. `--json` for downstream tooling.

P6.0  TGit listOrgRepos real implementation
    Replace the Phase 5.4 stub. Uses TGit's GitLab-style OpenAPI:
    GET https://git.woa.com/api/v3/groups/<encoded-path>/projects
    with token from the existing gfGetOAuthToken() helper. Multi-level
    group paths are URL-encoded whole. Pagination loops to maxRepos
    (200 default). Field mapping matches the GitHub side; primaryLanguage
    is left blank because the list endpoint doesn't return it. Errors
    are clean: 404 → "group not found or no access", other HTTP →
    "TGit API HTTP <code>: <body>", missing token → explicit hint
    about ~/.netrc / TAI_PAT_TOKEN. Token never reaches a log line or
    Error message.

P6.1  Cache lifecycle (LRU + size cap + GC command)
    The Phase 5 shallow-clone cache only grew. P6.1 adds an explicit
    metadata file at ~/.teamai/cache/repos/.cache-index.json; every
    successful clone/fetch in importFromRepo now refreshes its row
    via touchCacheEntry() (wrapped in try/catch + log.debug so cache
    bookkeeping never blocks the import). GC algorithm: stale-evict
    > 30d, then if over cap (5GB default, override via
    TEAMAI_CACHE_MAX_BYTES or --max-bytes) evict by ascending
    last_used until totalBytes ≤ cap*0.8. Eviction order is safe:
    fs.remove first, only then splice from index; failures land in
    skipped[]. getCacheStatus auto-heals index entries whose physical
    directory is gone. CLI: `teamai cache --status | --gc [--dry-run]
    [--max-bytes N] [--stale-days N] [--json]`.

P6.2  Section-level diff + in-place anchor updates
    The Phase 5 --incremental flag skipped clone but still rewrote
    docs/team-codebase/repos/<slug>.md whole, producing churn even
    when the source repo had no real change. P6.2 turns those
    summaries into anchored sections so unchanged content survives
    byte-equal across sync runs. Every `## title` block is wrapped in
        <!-- managed-by: import --from-repo, section: <slug>,
             source: ..., syncedAt: ... -->
        ## <title>
        <body>
        <!-- /managed-by: <slug> -->
    Section slugs derive mechanically from the title; duplicate slugs
    in one file get -2 / -3 suffixes so split / parse stay aligned.
    generateCodebaseMd is intentionally NOT touched -- the AI still
    emits one whole markdown blob, and the --workspace path that
    maintains the teamai-cli self codebase (docs/codebase.md) is
    untouched. Anchors only apply to per-repo team-codebase outputs;
    importFromRepo runs the AI output through mergeWithAnchors() per
    slug:
      - same body hash         → kept (old syncedAt + source preserved)
      - body changed           → rewritten with fresh body + new meta
      - present in fresh only  → added (appended)
      - present in old only    → removed (dropped)
    The frontmatter rule that actually delivers byte-equal: if all
    sections are kept, the prelude is also taken from old, otherwise
    from fresh. Without this tie-break the fresh `lastUpdated: <ISO>`
    in frontmatter would mtime-bump the file every run.

P6.3  pending-review CLI
    Phase 5.4 wrote .teamai/pending-review.jsonl when --require-review
    fired but provided no consumer. P6.3 adds the consumer:
        teamai review                # list (sorted by risk desc)
        teamai review <id>           # show details
        teamai review <id> --apply   # apply + drop + audit
        teamai review <id> --reject [--reason ...]
        teamai review --all-apply [--max-risk medium|low]
    Schema upgraded to {id, ts, kind, target, payload, source, risk}
    with backward-compat: loadPendingReview() normalises old rows
    on read, computing id from sha1(file|section|ts).slice(0,12) and
    inferring risk from a small hardcoded set of high-risk sections.
    iwiki-dual.ts now writes through appendPendingReview() so new rows
    always land in canonical shape. --apply only handles
    kind=codebase-section -- it calls patchManagedSection (P6.2),
    writes a fresh syncedAt, drops the row, appends an audit event.
    Other kinds gracefully degrade to "not auto-applicable". Atomic
    write through .tmp+rename so partial writes can't corrupt the
    jsonl.

P6.4  Domain-drift auto-apply workflow
    P5.3 detected drift and wrote history.jsonl, but gave the user
    no way to act on it. P6.4 turns drift into an actionable backlog:
    detectDomainDrift now dual-writes -- besides history.jsonl, every
    new event lands in pending-review.jsonl as kind=domain-drift,
    deduped 24h per url so a re-drifting repo stays one open item
    instead of growing. CLI:
        teamai domains drift                  # list
        teamai domains drift <repoUrl> --apply
        teamai domains drift <repoUrl> --lock
        teamai domains drift --apply-all [--threshold 0.8]
    Apply does the actual reassignment (splice old → push new,
    auto-create the new domain after a TTY confirmation; non-TTY
    refuses), updates confidence/signal from the recommendation,
    audits via appendHistory(reassign), drops the row, then calls
    regenerateAggregate so domain-*.md and index.md catch up. Lock
    sets RepoEntry.locked=true and clears stale drift items for the
    url. apply-all walks confidence-desc, applies above threshold,
    failures don't abort the batch.

CLI surface added
=================

  teamai cache --status | --gc
  teamai codebase --lint [--fix]
  teamai review [id] [--apply | --reject | --all-apply]
  teamai domains drift [url] [--apply | --lock | --apply-all]

Tests
=====

~150 new unit tests across 14 new test files. Full suite passes
1344 / 0 failing on this branch (Phase 5's pre-existing
recall.test.ts / types.test.ts failures were fixed in main between
Phase 5 and now and stay green here too). tsc clean.

Out of scope (tracked as Phase 7 in roadmap_jael.md)
====================================================

- Two-level domain hierarchy (e.g. AI/inference, platform/CI)
- Active cross-repo duplicate detection
- codebase.md ↔ search-index/recall integration
- agent retrieval effectiveness metrics

--other=phase6-team-codebase-hardening

* chore: stop tracking local drafts (roadmap, validation, .codebuddy)

These files exist in the working tree to support local iteration on
the team-codebase pipeline (personal roadmap notes, internal phase
acceptance reports, and the .codebuddy plan tree), but they should
not land in the upstream open-source repository. Add them to
.gitignore and untrack them via `git rm --cached` so they:

- stay on disk for local use
- stop showing up in `git status` for daily work
- disappear from the diff against upstream when sending PRs

This is a tracking change only -- no code or test behaviour is
affected.

* docs(readme): trim subagent phase notes and add Phase 5/6 commands

Three adjustments based on mentor feedback:

1. Remove the "Recall via subagent (Phase 1)" subsection from both
   README.md and README.zh-CN.md. The phase-numbered design note
   was useful during development but reads as roadmap detail on
   the public README. The downstream paragraph that explains what
   teamai recall actually returns -- the [<type>] tags plus the
   four-category index table -- is kept; that one is product
   behaviour, not phase trivia.

2. Tighten the public-facing tool list to the openly distributed
   editors (Claude Code, Codex, Cursor, CodeBuddy IDE, OpenClaw,
   WorkBuddy) and the matching ~/.claude/skills, ~/.codex/skills,
   ~/.cursor/skills, ~/.codebuddy/skills paths. No code changes:
   the underlying tool registry, sync paths, usage tracker, agent
   format dispatch, and AI-client probing are all left untouched,
   so existing setups keep working as before -- only the public
   README is shorter.

3. Add the recent commands that landed in PR #6 / #8 to the table:
     teamai import --from-repo / --from-repo-list / --from-org /
                   --from-iwiki [--iwiki-dual]
     teamai cache --status | --gc
     teamai codebase --lint [--fix]
     teamai review [id] [--apply | --reject | --all-apply]
     teamai domains drift [url] [--apply | --lock | --apply-all]

Documentation only; no code or test changes.

* fix(p5-p6): address audit findings — 2 blockers, 1 major, 5 medium

Independent review of the Phase 5 / Phase 6 codebase surfaced eight
issues; this commit fixes all of them. No new dependencies.

Blockers
========

1. iwiki anchor prefix mismatch broke `teamai review --apply`.
   iwiki-dual writes `<!-- managed-by: import --from-iwiki, ... -->`
   but the parser in section-patcher locked the prefix to
   `--from-repo`, so any pending-review item produced via
   --iwiki-dual --require-review threw `section not found` on apply.
   Fix: relax the parsing regex on both sides (parseSections and
   patchManagedSection) to accept `--from-(?:repo|iwiki)`. Writers
   stay as-is so the source-of-truth is still recoverable from the
   anchor metadata.

2. `--from-org` silently dropped private repos.
   `&type=public` was hardcoded into the GitHub list-org-repos URL,
   so on enterprise / internal orgs (mostly private) bootstrap
   produced a near-empty draft without error. Removed the query
   parameter -- relying on the caller's auth visibility (gh CLI or
   GITHUB_TOKEN) is the right default and matches GitHub's `type=all`.

Major
=====

3. tryEndpointPrefix returned success when the first page was empty.
   Combined with the bug above, an internal org with all-private
   repos returned [] from `/orgs/<x>` and never tried `/users/<x>`.
   Fix: when items.length === 0 && page === 1, return false so the
   outer code falls back to the user endpoint. Applied to both the
   gh CLI branch and the fetch branch.

Medium
======

4. ReDoS hardening on section-patcher anchor regexes.
   `[^>]*?` could be coaxed into exponential backtracking by hostile
   input. Replaced with `[^>\n]{0,256}?` -- bounded character class
   plus length cap. Applied to all four open/close anchor regexes
   (parseSections + patchManagedSection, both directions).

5. 10 MB hard cap on YAML / JSON config reads.
   loadDomains, loadCacheIndex, loadRepoList, and loadPendingReview
   now stat() before readFile and reject anything over 10 MB. Stops
   a malformed or hostile config file from blowing up memory.

6. Final path-safety check before per-repo writeFile.
   importFromRepo now calls assertSafePath() (an existing helper from
   PR #7) on the resolved repos/<slug>.md path. Defence-in-depth on
   top of the existing slug sanitisation; refuses to write outside
   the configured reposDir even if a future code path generates a
   weird slug.

7. SSRF guard + 50 MB response cap on outbound HTTP.
   gh-org and gf-org's fetch path now sets `redirect: 'manual'` and
   throws on any 3xx, and reads the body as a stream that cancels
   the reader and throws once total bytes exceed 50 MB. The gh CLI
   branch is unaffected -- gh handles redirects itself.

8. Backup before mergeWithAnchors fallback.
   When the existing repo file has corrupt / unclosed anchors,
   parseSections used to silently throw and importFromRepo fell
   back to a full-rewrite, losing every prior syncedAt timestamp.
   It now writes the old file to <repoMdPath>.bak (single overwrite,
   no accumulation) before doing the fallback wrap, so the prior
   state is recoverable.

Tests
=====

- iwiki-review-apply.test.ts (new): end-to-end -- iwiki-dual writes
  to pending-review.jsonl with --require-review, then `teamai review
  <id> --apply` is asserted to actually mutate external-knowledge.md
  (string contains the new body, anchor still says `--from-iwiki`).
  This was the regression that the parsing-regex fix unblocks.
- gh-org.test.ts (new): three cases -- private repos visible without
  type=public; first-page empty on /orgs/ falls back to /users/;
  /orgs/ 404 also falls back to /users/.
- section-patcher.test.ts: added cases for splitToSections /
  parseSections / patchManagedSection on iwiki-flavoured anchors.
- domains-store / cache-index / repo-list / review-store: each
  gained a test that writes an actual 11 MB file to a tmpdir and
  asserts the loader rejects it (not mocked).
- import-repo-merge.test.ts: corrupted-anchor case asserts the
  .bak file appears with the original content.

96 test files / 1356 tests pass / 0 failures (12 added by this
commit). tsc has only the pre-existing recall.test.ts error (carried
over from main, unrelated). Line length still ≤ 120 across all
touched files.

--other=fix-p5-p6-audit

* fix(test): add missing `type` field to recall.test.ts SearchIndexEntry mock

upstream CI's `Type check` step (npx tsc --noEmit) blocked PR #28 with:

  src/__tests__/recall.test.ts(62,7): error TS2741: Property 'type'
    is missing in type '{ filename, title, author, date, tags,
    tokens, votes }' but required in type 'SearchIndexEntry'.

SearchIndexEntry was extended in Phase 1 with a required `type:
KnowledgeType` field for the multi-bucket index, but the mock
factory in the recall vote test didn't follow. Local vitest doesn't
type-check the source so the bug never surfaced; upstream CI does
run tsc strictly and the typecheck step gates everything else
(unit tests, build, e2e), which is why PR #28 failed at the very
first step.

The test only exercises recallVote's counter path -- the `type`
field is never read -- so 'learnings' is just a representative
default; behaviour is unchanged.

Verified locally:
  - npx tsc --noEmit          → 0 errors (was 1)
  - npx vitest run recall     → 9/9 passing (unchanged)
  - npx vitest run            → 1360/1360 passing (unchanged)
  - npm run build             → success

* fix(test): resolve cross-platform path assertion failures in review-cmd.test.ts

--story=fix-github-actions-test-failures

Replace absolute path assertions with expect.stringContaining() to handle different tmp directory paths on macOS (/private/var) vs Linux (/var)

---------

Co-authored-by: jaelgeng <jaelgeng@tencent.com>
2026-06-12 17:28:43 +08:00
jeffyxu 5f1c4e81e1 0.16.6 2026-05-27 11:00:32 +08:00
jeffyxu 5015eb95b2 feat: wiki toggle via env var + wiki path fixes + README i18n
- feat: add wiki feature toggle via environment variable (closes #21)
- fix: align wiki skill path resolution with teamai push scope logic
- fix(push): exclude .git from copyDir to prevent submodule gitlink
- refactor: wiki 存储路径从各工具目录统一到 ~/.teamai/wiki/
- fix: filter rules by role knowledge namespaces during pull
- docs: split README into English (default) and Chinese versions
2026-05-27 11:00:12 +08:00
jeffyxu 585ce9715b 0.16.6 2026-05-22 15:41:47 +08:00
jeffyxu 1a18e72c74 fix: sync with GitHub - wiki skill path + package.json format (merge request !188)
Squash merge branch 'fix/sync-wiki-path-from-github' into 'master'
Sync from GitHub main (PR #15 merged):

1. skills/teamai-wiki/SKILL.md - wiki path scope-aware
2. package.json - bin path and repo url format
2026-05-19 12:38:08 +00:00
jeffyxu cbd54795be 0.16.5 2026-05-19 20:37:27 +08:00
jeffyxu 883d0aea0a 0.16.4 2026-05-19 10:17:04 +08:00
jeffyxu a7298b5823 0.16.3 2026-05-09 14:26:52 +08:00
jeffyxu 1b5a71e8da docs: update project tagline to The team harness for AI agents (merge request !180)
Squash merge branch 'worktree-update-tagline' into 'master'
## Summary

- 更新项目 tagline 为 "The team harness for AI agents"
- 副标题明确两大核心能力:(1) 跨 AI Agent 同步 skills/rules/docs;(2) 构建团队共享知识库
- 涉及文件:README.md, README.en.md, package.json, src/index.ts

## Test plan

- [x] grep team harness 命中 4 个文件
- [x] 旧 tagline 不再出现
- [x] npx tsc --noEmit 通过
2026-05-06 08:55:20 +00:00
jeffyxuandCursor 0bf2a94e18 chore: normalize package.json bin path and repository url
Auto-applied by `npm pkg fix` during 0.16.2 first publish to public npm:
- bin.teamai: "./dist/index.js" -> "dist/index.js"
- repository.url: "https://..." -> "git+https://..."

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-01 22:57:53 +08:00