teamai-cli 0.24.0-beta.4 → 0.24.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. See [standa
17
17
  ### 📝 Documentation
18
18
 
19
19
  - Align the public usage guides, drop internal-only details, and cover the missing commands and configuration ([#442](https://github.com/Tencent/teamai-cli/pull/442)).
20
+ - Document `teamai projects` in the bilingual READMEs as the lead distribution control, including learnings isolation ([#490](https://github.com/Tencent/teamai-cli/pull/490), for [#487](https://github.com/Tencent/teamai-cli/issues/487)).
20
21
 
21
22
  ## [0.23.0](https://github.com/Tencent/teamai-cli/compare/v0.22.0...v0.23.0) (2026-09-08)
22
23
 
package/README.md CHANGED
@@ -103,10 +103,13 @@ Team-wide settings an admin configures once and delivers to every member on `tea
103
103
 
104
104
  | Capability | Command | What it does |
105
105
  |------------|---------|--------------|
106
+ | **Projects** | `teamai projects` | Bind a working directory to one or more logical projects so it syncs that project's skills, knowledge, and isolated learnings. Orthogonal to roles. |
106
107
  | **Roles** | `teamai roles` | Define role → namespace mappings so each member syncs only the skills for their role. |
107
108
  | **Tags** | `teamai tags` | Tag skills / rules so members subscribe to just the tags they need. |
108
109
  | **Sources** | `teamai source` | Subscribe to additional skill repos — other teams' public repos, or shared/public repos within your own org; subscribed skills sync automatically on pull. |
109
110
 
111
+ Learnings isolation: `learnings/` at the repo root is shared with everyone; `learnings/<project-id>/` is project-private. See the [usage guide](docs/usage-guide.md#multi-project-project-as-a-dimension-orthogonal-to-role).
112
+
110
113
  ## Team Execution
111
114
 
112
115
  > One Team. One Harness. Every Agent.
@@ -191,6 +194,7 @@ Matched: conflict | Missing: port
191
194
  teamai import --from-repo https://github.com/org/repo
192
195
  teamai import --from-org myorg # batch import all repos
193
196
  teamai codebase --extract /path/to/repo # local extract into teamwiki/
197
+ teamai codebase --deep-enrich --project my-service --output /path/to/repo # generate deep knowledge docs
194
198
  teamai codebase --reconcile --output /path/to/repo # map product docs to code pages
195
199
  teamai codebase --lint --output /path/to/repo # check the locally extracted graph
196
200
  ```
@@ -236,7 +240,7 @@ Insight into how the team actually uses its AI tools, and a starting point for t
236
240
  | `teamai pull` | Pull team resources and inject into local AI tools |
237
241
  | `teamai push` | Push local resources to a branch and open a Merge Request |
238
242
  | `teamai packages [install] [target]` | Install declared npm packages and Claude plugins; with a target, also update `teamai.yaml`. Bare `teamai packages` installs everything; `teamai packages install <target>` adds one |
239
- | `teamai status` | Show local vs team repo diff |
243
+ | `teamai status` | Show local vs team repo diff and resource counts, including namespaced skills and nested docs |
240
244
  | `teamai contribute` | Share session experience to team repo |
241
245
  | `teamai recall <query>` | Search the team knowledge base (BM25 + graph-boost) |
242
246
  | `teamai recall enable/disable/status` | Toggle or check recall state |
@@ -244,10 +248,12 @@ Insight into how the team actually uses its AI tools, and a starting point for t
244
248
  | `teamai recall maintenance` | Maintain knowledge base health: prune low-confidence learnings, writeback confidence scores, flag stale entries |
245
249
  | `teamai import` | Import knowledge (`--dir`, `--from-repo`, `--from-org`, `--from-repo-list`, `--from-mr`) |
246
250
  | `teamai codebase --extract [path]` | Extract code facts and build the local graph under `teamwiki/` |
251
+ | `teamai codebase --deep-enrich` | Generate deep knowledge docs from extracted evidence |
247
252
  | `teamai codebase --reconcile` | Reconcile product documentation with extracted code knowledge |
248
253
  | `teamai codebase --lint` | Knowledge graph health check |
249
254
  | `teamai ci extract-mr --url <url>` | CI: extract knowledge from MR, post comments, write after merge |
250
255
  | `teamai members` | List team members |
256
+ | `teamai projects` | Bind a working directory to one or more logical projects |
251
257
  | `teamai roles` | Manage team roles and namespaces |
252
258
  | `teamai tags` | Manage tag-based skill/rule filtering |
253
259
  | `teamai skill exclude add/remove/list` | Manage skills excluded from local sync ([usage guide](docs/usage-guide.md#excluding-skills-you-dont-need)) |
package/README.zh-CN.md CHANGED
@@ -103,10 +103,13 @@ teamai init https://github.com/yourorg/yourrepo --scope user
103
103
 
104
104
  | 能力 | 命令 | 作用 |
105
105
  |------|------|------|
106
+ | **项目(Projects)** | `teamai projects` | 将工作目录绑定到一个或多个逻辑项目,使其同步该项目的 skills、knowledge 以及隔离的 learnings。与角色正交。 |
106
107
  | **角色(Roles)** | `teamai roles` | 定义「角色 → 命名空间」映射,让每位成员只同步与自身角色匹配的 skills。 |
107
108
  | **标签(Tags)** | `teamai tags` | 给 skills / rules 打标签,成员只订阅自己需要的标签。 |
108
109
  | **订阅源(Sources)** | `teamai source` | 订阅额外的 skill 仓库——其他团队的公开仓库,或本团队内的公共/共享仓库;已订阅的 skills 会在 pull 时自动同步。 |
109
110
 
111
+ learnings 隔离:仓库 `learnings/` 根目录对所有人共享;`learnings/<project-id>/` 为项目私有。详见[使用指南](docs/usage-guide.zh-CN.md#多项目project-作为与-role-正交的维度)。
112
+
110
113
  ## Team Execution
111
114
 
112
115
  > One Team. One Harness. Every Agent.
@@ -191,6 +194,7 @@ Matched: conflict | Missing: port
191
194
  teamai import --from-repo https://github.com/org/repo
192
195
  teamai import --from-org myorg # 批量导入所有仓库
193
196
  teamai codebase --extract /path/to/repo # 本地提取到 teamwiki/
197
+ teamai codebase --deep-enrich --project my-service --output /path/to/repo # 从提取结果生成深度知识文档
194
198
  teamai codebase --reconcile --output /path/to/repo # 将产品文档映射到代码页面
195
199
  teamai codebase --lint --output /path/to/repo # 检查本地提取的图谱
196
200
  ```
@@ -236,7 +240,7 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
236
240
  | `teamai pull` | 拉取团队资源并注入到本地 AI 工具 |
237
241
  | `teamai push` | 推送本地资源到分支并创建合并请求 |
238
242
  | `teamai packages [install] [target]` | 安装团队 npm 包和 Claude 插件。裸 `teamai packages` 安装全部;`teamai packages install <target>` 添加单个并更新声明 |
239
- | `teamai status` | 显示本地与团队仓库的差异 |
243
+ | `teamai status` | 显示本地与团队仓库的差异及资源数量,包含 namespace 下的技能和子目录中的文档 |
240
244
  | `teamai contribute` | 将 session 经验分享到团队仓库 |
241
245
  | `teamai recall <query>` | 搜索团队知识库(BM25 + 图谱增强) |
242
246
  | `teamai recall enable/disable/status` | 开关或查看 recall 状态 |
@@ -244,10 +248,12 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
244
248
  | `teamai recall maintenance` | 维护知识库健康:清理低置信度 learnings、回写置信度、标记过时条目 |
245
249
  | `teamai import` | 导入知识(`--dir`、`--from-repo`、`--from-org`、`--from-repo-list`、`--from-mr`) |
246
250
  | `teamai codebase --extract [path]` | 提取代码事实并在 `teamwiki/` 下构建本地图谱 |
251
+ | `teamai codebase --deep-enrich` | 从已提取的 evidence 生成深度知识文档 |
247
252
  | `teamai codebase --reconcile` | 将产品文档与提取的代码知识进行对账 |
248
253
  | `teamai codebase --lint` | 知识图谱健康检查 |
249
254
  | `teamai ci extract-mr --url <url>` | CI:从 MR 提取知识、发评论、合并后写入 |
250
255
  | `teamai members` | 查看团队成员 |
256
+ | `teamai projects` | 将工作目录绑定到一个或多个逻辑项目 |
251
257
  | `teamai roles` | 管理团队角色和命名空间 |
252
258
  | `teamai tags` | 管理基于标签的 skill/rule 过滤 |
253
259
  | `teamai skill exclude add/remove/list` | 管理不参与本地同步的 skills([使用指南](docs/usage-guide.zh-CN.md#排除个人不需要的-skill)) |
package/dist/index.js CHANGED
@@ -453,7 +453,6 @@ __export(types_exports, {
453
453
  CONTRIBUTE_LOW_QUALITY_BONUS: () => CONTRIBUTE_LOW_QUALITY_BONUS,
454
454
  CONTRIBUTE_LOW_QUALITY_THRESHOLD: () => CONTRIBUTE_LOW_QUALITY_THRESHOLD,
455
455
  CONTRIBUTE_REJECT_WEIGHT: () => CONTRIBUTE_REJECT_WEIGHT,
456
- CONTRIBUTE_SESSIONS_DIR: () => CONTRIBUTE_SESSIONS_DIR,
457
456
  CONTRIBUTE_SKILL_BONUS: () => CONTRIBUTE_SKILL_BONUS,
458
457
  CONTRIBUTE_SMART_THRESHOLD: () => CONTRIBUTE_SMART_THRESHOLD,
459
458
  CONTRIBUTE_TOOLERROR_TIERS: () => CONTRIBUTE_TOOLERROR_TIERS,
@@ -464,15 +463,12 @@ __export(types_exports, {
464
463
  CultureTeamSchema: () => CultureTeamSchema,
465
464
  DASHBOARD_COMPACTION_THRESHOLD: () => DASHBOARD_COMPACTION_THRESHOLD,
466
465
  DASHBOARD_DEFAULT_PORT: () => DASHBOARD_DEFAULT_PORT,
467
- DASHBOARD_EVENTS_DIR: () => DASHBOARD_EVENTS_DIR,
468
- DASHBOARD_EVENTS_PATH: () => DASHBOARD_EVENTS_PATH,
469
466
  DASHBOARD_IDLE_TIMEOUT_MS: () => DASHBOARD_IDLE_TIMEOUT_MS,
470
467
  DASHBOARD_PID_CHECK_INTERVAL_MS: () => DASHBOARD_PID_CHECK_INTERVAL_MS,
471
468
  DASHBOARD_STALE_TIMEOUT_MS: () => DASHBOARD_STALE_TIMEOUT_MS,
472
469
  DASHBOARD_STOPPED_DISPLAY_MS: () => DASHBOARD_STOPPED_DISPLAY_MS,
473
470
  INTERVENTION_SCAN_MAX_BYTES: () => INTERVENTION_SCAN_MAX_BYTES,
474
471
  KNOWLEDGE_WORKTREE_DIRNAME: () => KNOWLEDGE_WORKTREE_DIRNAME,
475
- LEARNINGS_LOCAL_DIR: () => LEARNINGS_LOCAL_DIR,
476
472
  LocalConfigSchema: () => LocalConfigSchema,
477
473
  MemberConfigSchema: () => MemberConfigSchema,
478
474
  PendingPushItemSchema: () => PendingPushItemSchema,
@@ -481,9 +477,7 @@ __export(types_exports, {
481
477
  REPORTS_LOCK_FILENAME: () => REPORTS_LOCK_FILENAME,
482
478
  REPORTS_WORKTREE_DIRNAME: () => REPORTS_WORKTREE_DIRNAME,
483
479
  RESOURCE_TYPES: () => RESOURCE_TYPES,
484
- SEARCH_INDEX_PATH: () => SEARCH_INDEX_PATH,
485
480
  SEARCH_INDEX_VERSION: () => SEARCH_INDEX_VERSION,
486
- SESSION_LOGS_LOCAL_DIR: () => SESSION_LOGS_LOCAL_DIR,
487
481
  SKILL_NAME_REGEX: () => SKILL_NAME_REGEX,
488
482
  SOURCE_PULL_TTL_MS: () => SOURCE_PULL_TTL_MS,
489
483
  SYNC_LOCK_FILENAME: () => SYNC_LOCK_FILENAME,
@@ -494,32 +488,23 @@ __export(types_exports, {
494
488
  TEAMAI_AGENT_HOOK_PREFIX: () => TEAMAI_AGENT_HOOK_PREFIX,
495
489
  TEAMAI_CLAUDEMD_END: () => TEAMAI_CLAUDEMD_END,
496
490
  TEAMAI_CLAUDEMD_START: () => TEAMAI_CLAUDEMD_START,
497
- TEAMAI_CONFIG_PATH: () => TEAMAI_CONFIG_PATH,
498
491
  TEAMAI_CULTURE_END: () => TEAMAI_CULTURE_END,
499
492
  TEAMAI_CULTURE_START: () => TEAMAI_CULTURE_START,
500
493
  TEAMAI_CUSTOM_HOOK_PREFIX: () => TEAMAI_CUSTOM_HOOK_PREFIX,
501
494
  TEAMAI_ENV_END: () => TEAMAI_ENV_END,
502
495
  TEAMAI_ENV_START: () => TEAMAI_ENV_START,
503
- TEAMAI_HOME: () => TEAMAI_HOME,
504
496
  TEAMAI_HOOK_DESCRIPTION_PREFIX: () => TEAMAI_HOOK_DESCRIPTION_PREFIX,
505
- TEAMAI_KNOWN_SKILLS_PATH: () => TEAMAI_KNOWN_SKILLS_PATH,
506
- TEAMAI_PUSHIGNORE_PATH: () => TEAMAI_PUSHIGNORE_PATH,
507
497
  TEAMAI_RECALL_RULES_END: () => TEAMAI_RECALL_RULES_END,
508
498
  TEAMAI_RECALL_RULES_START: () => TEAMAI_RECALL_RULES_START,
509
499
  TEAMAI_RULES_END: () => TEAMAI_RULES_END,
510
500
  TEAMAI_RULES_START: () => TEAMAI_RULES_START,
511
501
  TEAMAI_SOURCES_DIR: () => TEAMAI_SOURCES_DIR,
512
- TEAMAI_STATE_PATH: () => TEAMAI_STATE_PATH,
513
- TEAMAI_TOKEN_PATH: () => TEAMAI_TOKEN_PATH,
514
- TEAMAI_UPDATE_LOCK_PATH: () => TEAMAI_UPDATE_LOCK_PATH,
515
- TEAMAI_USAGE_PATH: () => TEAMAI_USAGE_PATH,
516
502
  TRANSCRIPT_INTERRUPT_PREFIX: () => TRANSCRIPT_INTERRUPT_PREFIX,
517
503
  TRANSCRIPT_REJECT_MARKERS: () => TRANSCRIPT_REJECT_MARKERS,
518
504
  TRANSCRIPT_SYSTEM_PREFIXES: () => TRANSCRIPT_SYSTEM_PREFIXES,
519
505
  TeamaiConfigSchema: () => TeamaiConfigSchema,
520
506
  ToolPathsSchema: () => ToolPathsSchema,
521
507
  UsageEventSchema: () => UsageEventSchema,
522
- VOTES_LOCAL_DIR: () => VOTES_LOCAL_DIR,
523
508
  addTokenUsage: () => addTokenUsage,
524
509
  areTeamHooksDisabled: () => areTeamHooksDisabled,
525
510
  emptyTokenUsage: () => emptyTokenUsage,
@@ -533,8 +518,17 @@ __export(types_exports, {
533
518
  getPushignorePath: () => getPushignorePath,
534
519
  getRecallSharing: () => getRecallSharing,
535
520
  getReportsDir: () => getReportsDir,
521
+ getSessionLogsDir: () => getSessionLogsDir,
536
522
  getStatePath: () => getStatePath,
537
523
  getTeamaiHome: () => getTeamaiHome,
524
+ getTeamaiHomeDir: () => getTeamaiHomeDir,
525
+ getTokenPath: () => getTokenPath,
526
+ getUpdateLockPath: () => getUpdateLockPath,
527
+ getUserConfigPath: () => getUserConfigPath,
528
+ getUserLearningsDir: () => getUserLearningsDir,
529
+ getUserSearchIndexPath: () => getUserSearchIndexPath,
530
+ getUserStatePath: () => getUserStatePath,
531
+ getUserVotesDir: () => getUserVotesDir,
538
532
  isAgentDisabled: () => isAgentDisabled,
539
533
  isContributeHintEnabled: () => isContributeHintEnabled,
540
534
  isRecallEnabled: () => isRecallEnabled,
@@ -599,6 +593,24 @@ function managedMcpManifestPath(dataHome, workspaceRoot) {
599
593
  function legacyManagedMcpManifestPath(dataHome) {
600
594
  return path3.join(dataHome, "managed-mcp.json");
601
595
  }
596
+ function getTeamaiHomeDir() {
597
+ return path3.join(getUserHome(), ".teamai");
598
+ }
599
+ function getUserConfigPath() {
600
+ return path3.join(getTeamaiHomeDir(), "config.yaml");
601
+ }
602
+ function getUserStatePath() {
603
+ return path3.join(getTeamaiHomeDir(), "state.json");
604
+ }
605
+ function getTokenPath() {
606
+ return path3.join(getTeamaiHomeDir(), "token");
607
+ }
608
+ function getUpdateLockPath() {
609
+ return path3.join(getTeamaiHomeDir(), ".update-lock");
610
+ }
611
+ function getSessionLogsDir() {
612
+ return path3.join(getTeamaiHomeDir(), "session-logs");
613
+ }
602
614
  function emptyTokenUsage() {
603
615
  return { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
604
616
  }
@@ -614,6 +626,15 @@ function addTokenUsage(a, b) {
614
626
  cacheCreation: (a?.cacheCreation ?? 0) + (b?.cacheCreation ?? 0)
615
627
  };
616
628
  }
629
+ function getUserLearningsDir() {
630
+ return path3.join(getTeamaiHomeDir(), "learnings");
631
+ }
632
+ function getUserSearchIndexPath() {
633
+ return path3.join(getTeamaiHomeDir(), "search-index.json");
634
+ }
635
+ function getUserVotesDir() {
636
+ return path3.join(getTeamaiHomeDir(), "votes");
637
+ }
617
638
  function resolveBaseDir(localConfig) {
618
639
  if (localConfig.scope === "project") {
619
640
  if (!localConfig.projectRoot) {
@@ -711,7 +732,7 @@ function getPushignorePath() {
711
732
  function areTeamHooksDisabled() {
712
733
  return process.env.TEAMAI_HOOKS_DISABLED === "1" || process.env.TEAMAI_HOOKS_DISABLED === "true";
713
734
  }
714
- var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, TEAMAI_HOME, TEAMAI_CONFIG_PATH, TEAMAI_STATE_PATH, TEAMAI_TOKEN_PATH, TEAMAI_UPDATE_LOCK_PATH, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, TEAMAI_USAGE_PATH, TEAMAI_KNOWN_SKILLS_PATH, TEAMAI_PUSHIGNORE_PATH, SESSION_LOGS_LOCAL_DIR, UsageEventSchema, DASHBOARD_EVENTS_DIR, DASHBOARD_EVENTS_PATH, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, CONTRIBUTE_SESSIONS_DIR, SEARCH_INDEX_VERSION, LEARNINGS_LOCAL_DIR, SEARCH_INDEX_PATH, VOTES_LOCAL_DIR, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
735
+ var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, UsageEventSchema, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, SEARCH_INDEX_VERSION, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
715
736
  var init_types = __esm({
716
737
  "src/types.ts"() {
717
738
  "use strict";
@@ -879,7 +900,7 @@ var init_types = __esm({
879
900
  // .zcode/config.json (a different key), which the Claude writer cannot
880
901
  // emit — so no mcpProject. ZCode has no user-level rules dir convention.
881
902
  zcode: { skills: ".zcode/skills", agents: ".zcode/agents", settings: ".zcode/cli/config.json", mcp: ".agents/mcp.json" },
882
- codebuddy: { skills: ".codebuddy/skills", rules: ".codebuddy/rules", settings: ".codebuddy/settings.json", claudemd: ".codebuddy/CODEBUDDY.md", agents: ".codebuddy/agents", mcp: ".codebuddy/mcp.json", mcpProject: ".codebuddy/mcp.json" },
903
+ codebuddy: { skills: ".codebuddy/skills", rules: ".codebuddy/rules", settings: ".codebuddy/settings.json", claudemd: ".codebuddy/CODEBUDDY.md", agents: ".codebuddy/agents", mcp: ".codebuddy/mcp.json", mcpProject: ".mcp.json" },
883
904
  openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules", claudemd: ".openclaw/workspace/AGENTS.md" },
884
905
  hermes: { skills: ".hermes/skills", claudemd: "AGENTS.md" },
885
906
  // DeepSeek Harness: skills synced to ~/.dsh/skills, which its skill-filesystem
@@ -1022,11 +1043,6 @@ var init_types = __esm({
1022
1043
  lastUpdateCheck: z.string().nullable().default(null),
1023
1044
  availableUpdate: z.string().nullable().default(null)
1024
1045
  });
1025
- TEAMAI_HOME = path3.join(getUserHome(), ".teamai");
1026
- TEAMAI_CONFIG_PATH = path3.join(TEAMAI_HOME, "config.yaml");
1027
- TEAMAI_STATE_PATH = path3.join(TEAMAI_HOME, "state.json");
1028
- TEAMAI_TOKEN_PATH = path3.join(TEAMAI_HOME, "token");
1029
- TEAMAI_UPDATE_LOCK_PATH = path3.join(TEAMAI_HOME, ".update-lock");
1030
1046
  RESOURCE_TYPES = ["skills", "rules", "docs", "env", "agents", "hooks", "mcp"];
1031
1047
  TEAMAI_RULES_START = "<!-- [teamai:rules:start] -->";
1032
1048
  TEAMAI_RULES_END = "<!-- [teamai:rules:end] -->";
@@ -1042,17 +1058,11 @@ var init_types = __esm({
1042
1058
  TEAMAI_RECALL_RULES_START = "<!-- [teamai:recall-rules:start] -->";
1043
1059
  TEAMAI_RECALL_RULES_END = "<!-- [teamai:recall-rules:end] -->";
1044
1060
  SKILL_NAME_REGEX = /^[a-zA-Z0-9_\-:.]{1,200}$/;
1045
- TEAMAI_USAGE_PATH = `${TEAMAI_HOME}/usage.jsonl`;
1046
- TEAMAI_KNOWN_SKILLS_PATH = `${TEAMAI_HOME}/known-skills.json`;
1047
- TEAMAI_PUSHIGNORE_PATH = `${TEAMAI_HOME}/pushignore`;
1048
- SESSION_LOGS_LOCAL_DIR = `${TEAMAI_HOME}/session-logs`;
1049
1061
  UsageEventSchema = z.object({
1050
1062
  skill: z.string().regex(SKILL_NAME_REGEX),
1051
1063
  timestamp: z.string(),
1052
1064
  tool: z.string()
1053
1065
  });
1054
- DASHBOARD_EVENTS_DIR = `${TEAMAI_HOME}/dashboard`;
1055
- DASHBOARD_EVENTS_PATH = `${DASHBOARD_EVENTS_DIR}/events.jsonl`;
1056
1066
  DASHBOARD_DEFAULT_PORT = 3721;
1057
1067
  DASHBOARD_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
1058
1068
  DASHBOARD_STALE_TIMEOUT_MS = 30 * 60 * 1e3;
@@ -1118,11 +1128,7 @@ var init_types = __esm({
1118
1128
  CONTRIBUTE_LOW_QUALITY_BONUS = 10;
1119
1129
  CONTRIBUTE_LOW_QUALITY_THRESHOLD = 5;
1120
1130
  CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT = 0;
1121
- CONTRIBUTE_SESSIONS_DIR = `${TEAMAI_HOME}/sessions`;
1122
1131
  SEARCH_INDEX_VERSION = 6;
1123
- LEARNINGS_LOCAL_DIR = `${TEAMAI_HOME}/learnings`;
1124
- SEARCH_INDEX_PATH = `${TEAMAI_HOME}/search-index.json`;
1125
- VOTES_LOCAL_DIR = `${TEAMAI_HOME}/votes`;
1126
1132
  CultureCompanySchema = z.object({
1127
1133
  name: z.string(),
1128
1134
  mission: z.string().optional(),
@@ -3471,6 +3477,12 @@ import { execSync, spawnSync } from "child_process";
3471
3477
  import fs4 from "fs";
3472
3478
  import os2 from "os";
3473
3479
  import path15 from "path";
3480
+ function gfInstallDir() {
3481
+ return path15.join(getTeamaiHomeDir(), "gf");
3482
+ }
3483
+ function gfBinPath() {
3484
+ return path15.join(gfInstallDir(), "gf", "bin", "gf");
3485
+ }
3474
3486
  function shellQuote2(s) {
3475
3487
  return "'" + s.replace(/'/g, "'\\''") + "'";
3476
3488
  }
@@ -3500,11 +3512,11 @@ function gfExec(args, options) {
3500
3512
  }
3501
3513
  function getGfPath() {
3502
3514
  try {
3503
- const stat8 = execSync(`test -x "${GF_BIN_PATH}" && echo ok`, {
3515
+ const stat8 = execSync(`test -x "${gfBinPath()}" && echo ok`, {
3504
3516
  encoding: "utf-8",
3505
3517
  stdio: ["pipe", "pipe", "pipe"]
3506
3518
  });
3507
- if (stat8.trim() === "ok") return GF_BIN_PATH;
3519
+ if (stat8.trim() === "ok") return gfBinPath();
3508
3520
  } catch {
3509
3521
  }
3510
3522
  try {
@@ -3553,17 +3565,17 @@ async function ensureGfInstalled() {
3553
3565
  const url = getGfDownloadUrl();
3554
3566
  const spin = spinner("Installing gf CLI (\u5DE5\u8702\u547D\u4EE4\u884C\u5DE5\u5177)...").start();
3555
3567
  try {
3556
- await ensureDir(GF_INSTALL_DIR);
3568
+ await ensureDir(gfInstallDir());
3557
3569
  execSync(
3558
- `curl -fsSL "${url}" | tar xz -C "${GF_INSTALL_DIR}"`,
3570
+ `curl -fsSL "${url}" | tar xz -C "${gfInstallDir()}"`,
3559
3571
  { stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 }
3560
3572
  );
3561
- execSync(`test -x "${GF_BIN_PATH}"`, { stdio: ["pipe", "pipe", "pipe"] });
3562
- spin.succeed(`gf CLI installed to ${GF_INSTALL_DIR}`);
3573
+ execSync(`test -x "${gfBinPath()}"`, { stdio: ["pipe", "pipe", "pipe"] });
3574
+ spin.succeed(`gf CLI installed to ${gfInstallDir()}`);
3563
3575
  } catch (e) {
3564
3576
  spin.fail(`Failed to install gf CLI: ${e.message}`);
3565
3577
  log.info(`You can install it manually from: ${url}`);
3566
- log.info(`Extract to: ${GF_INSTALL_DIR}`);
3578
+ log.info(`Extract to: ${gfInstallDir()}`);
3567
3579
  throw e;
3568
3580
  }
3569
3581
  }
@@ -3706,7 +3718,7 @@ function gfMrCreate(opts) {
3706
3718
  }
3707
3719
  throw new Error(`gf mr create succeeded but returned unexpected output: ${output}`);
3708
3720
  }
3709
- var GF_INSTALL_DIR, GF_BIN_PATH, GF_DOWNLOAD_BASE, AUTH_CWD, RepoNotFoundError2;
3721
+ var GF_DOWNLOAD_BASE, AUTH_CWD, RepoNotFoundError2;
3710
3722
  var init_gf_cli = __esm({
3711
3723
  "src/providers/tgit/gf-cli.ts"() {
3712
3724
  "use strict";
@@ -3714,8 +3726,6 @@ var init_gf_cli = __esm({
3714
3726
  init_logger();
3715
3727
  init_types();
3716
3728
  init_rest_auth();
3717
- GF_INSTALL_DIR = path15.join(TEAMAI_HOME, "gf");
3718
- GF_BIN_PATH = path15.join(GF_INSTALL_DIR, "gf", "bin", "gf");
3719
3729
  GF_DOWNLOAD_BASE = "http://mirrors.tencent.com/repository/generic/gongfeng-cli/files/channels/stable";
3720
3730
  AUTH_CWD = os2.tmpdir();
3721
3731
  RepoNotFoundError2 = class extends Error {
@@ -6494,6 +6504,13 @@ var init_roles = __esm({
6494
6504
  });
6495
6505
 
6496
6506
  // src/utils/path-safety.ts
6507
+ var path_safety_exports = {};
6508
+ __export(path_safety_exports, {
6509
+ assertSafePath: () => assertSafePath,
6510
+ assertSafeResourceName: () => assertSafeResourceName,
6511
+ assertWithinRoot: () => assertWithinRoot,
6512
+ defaultAllowedRoots: () => defaultAllowedRoots
6513
+ });
6497
6514
  import os5 from "os";
6498
6515
  import path19 from "path";
6499
6516
  import fs7 from "fs";
@@ -10385,8 +10402,26 @@ async function reconcileClaudeModels(models, manifest) {
10385
10402
  await writeModelJson(profilePath, { env: desired });
10386
10403
  const conflictKeys = /* @__PURE__ */ new Set([
10387
10404
  ...Object.keys(desired),
10388
- "ANTHROPIC_API_KEY"
10405
+ "ANTHROPIC_API_KEY",
10406
+ "ANTHROPIC_CUSTOM_HEADERS",
10407
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
10408
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
10409
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL"
10389
10410
  ]);
10411
+ const isManagedValue = (key, value) => previousHashes[key] !== void 0 && entryHash(value) === previousHashes[key] || env[key] !== void 0 && entryHash(value) === entryHash(env[key]);
10412
+ const userOwnsInShell = (key) => {
10413
+ const value = process.env[key];
10414
+ if (typeof value !== "string" || value.trim() === "") return false;
10415
+ return !isManagedValue(key, value);
10416
+ };
10417
+ const shellConflicts = [...conflictKeys].filter(userOwnsInShell);
10418
+ if (shellConflicts.length > 0) {
10419
+ await appendErrorLog({
10420
+ apply_model_config: "skipped claude gateway: user owns conflicting shell env",
10421
+ conflicts: shellConflicts
10422
+ });
10423
+ return;
10424
+ }
10390
10425
  const canManage = [...conflictKeys].every((key) => env[key] === void 0 || previousHashes[key] !== void 0 && entryHash(env[key]) === previousHashes[key]);
10391
10426
  if (!canManage) {
10392
10427
  manifest.claudeEnv = {};
@@ -10950,7 +10985,7 @@ async function initLocalAgentHttp(options) {
10950
10985
  await ensureDir(getLocalAgentHome());
10951
10986
  await saveLocalAgentConfig(config);
10952
10987
  if (options.token) {
10953
- await writeTokenFile(TEAMAI_TOKEN_PATH, options.token);
10988
+ await writeTokenFile(getTokenPath(), options.token);
10954
10989
  }
10955
10990
  const teamConfig = createLocalAgentTeamConfig(endpoint);
10956
10991
  await injectHooksToAllTools(teamConfig.toolPaths, getUserHome(), options.filterAgents);
@@ -11939,6 +11974,10 @@ var init_skills = __esm({
11939
11974
  }
11940
11975
  dest = path28.join(wsDir, "skills", item.name);
11941
11976
  } else if (tool === "hermes") {
11977
+ if (!await pathExists(getHermesHome())) {
11978
+ log.debug(`Skipping skill sync for ${tool}: tool not installed`);
11979
+ continue;
11980
+ }
11942
11981
  dest = path28.join(getHermesHome(), "skills", item.name);
11943
11982
  } else {
11944
11983
  if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
@@ -12386,10 +12425,7 @@ var init_docs = __esm({
12386
12425
  }
12387
12426
  async scanTeamForPull(_teamConfig, localConfig) {
12388
12427
  const docsDir = path31.join(localConfig.repo.localPath, "docs");
12389
- if (!await pathExists(docsDir)) return [];
12390
- const files = await listFiles(docsDir);
12391
- const realFiles = files.filter((f) => !f.startsWith("."));
12392
- if (realFiles.length === 0) return [];
12428
+ if (await this.countDocFiles(docsDir) === 0) return [];
12393
12429
  return [{
12394
12430
  name: "docs",
12395
12431
  type: "docs",
@@ -12398,8 +12434,8 @@ var init_docs = __esm({
12398
12434
  }];
12399
12435
  }
12400
12436
  async countDocFiles(sourcePath) {
12401
- const files = await listFiles(sourcePath);
12402
- return files.filter((f) => !f.startsWith(".")).length;
12437
+ const files = await listFilesRecursive(sourcePath);
12438
+ return files.filter((f) => f.split("/").every((segment) => !segment.startsWith("."))).length;
12403
12439
  }
12404
12440
  async pushItem(_item, _teamConfig, _localConfig) {
12405
12441
  }
@@ -13768,7 +13804,7 @@ async function acquireReclaimSentinel(sentinel, owner) {
13768
13804
  return exclusiveCreate(sentinel, payload);
13769
13805
  }
13770
13806
  async function acquireLock(lockPath) {
13771
- const resolved = lockPath ?? expandHome(TEAMAI_UPDATE_LOCK_PATH);
13807
+ const resolved = lockPath ?? expandHome(getUpdateLockPath());
13772
13808
  const owner = randomUUID2();
13773
13809
  const payload = JSON.stringify({
13774
13810
  pid: process.pid,
@@ -13807,7 +13843,7 @@ async function acquireLock(lockPath) {
13807
13843
  }
13808
13844
  }
13809
13845
  async function releaseLock(lockPath) {
13810
- const resolved = lockPath ?? expandHome(TEAMAI_UPDATE_LOCK_PATH);
13846
+ const resolved = lockPath ?? expandHome(getUpdateLockPath());
13811
13847
  const ourOwner = heldLockOwners.get(resolved);
13812
13848
  if (!ourOwner) return;
13813
13849
  try {
@@ -15211,7 +15247,7 @@ async function initHttp(url, options) {
15211
15247
  if (scope === "project") {
15212
15248
  await saveLocalConfigForScope(localConfig, scope, projectRoot);
15213
15249
  } else {
15214
- await ensureDir(TEAMAI_HOME);
15250
+ await ensureDir(getTeamaiHomeDir());
15215
15251
  await saveLocalConfig(localConfig);
15216
15252
  }
15217
15253
  log.success(`Local config saved to ${teamaiHome}/config.yaml`);
@@ -15950,9 +15986,9 @@ async function init(options) {
15950
15986
  log.debug("Generated .teamai/.gitignore for project scope");
15951
15987
  }
15952
15988
  } else {
15953
- await ensureDir(TEAMAI_HOME);
15989
+ await ensureDir(getTeamaiHomeDir());
15954
15990
  await saveLocalConfig(localConfig);
15955
- log.success(`Local config saved to ${TEAMAI_HOME}/config.yaml`);
15991
+ log.success(`Local config saved to ${getTeamaiHomeDir()}/config.yaml`);
15956
15992
  }
15957
15993
  try {
15958
15994
  const state = await loadStateForScope(localConfig);
@@ -17203,6 +17239,23 @@ function projectSlug(anchor) {
17203
17239
  function projectDataHome(anchor) {
17204
17240
  return path46.join(getUserHome(), ".teamai", "projects", projectSlug(anchor));
17205
17241
  }
17242
+ function projectsRootDir() {
17243
+ return path46.join(getUserHome(), ".teamai", "projects");
17244
+ }
17245
+ async function writeAnchorFile(partitionDir, anchor) {
17246
+ await fs17.promises.mkdir(partitionDir, { recursive: true });
17247
+ await fs17.promises.writeFile(path46.join(partitionDir, "anchor"), `${anchor}
17248
+ `, "utf-8");
17249
+ }
17250
+ async function readAnchorFile(partitionDir) {
17251
+ try {
17252
+ const raw = await fs17.promises.readFile(path46.join(partitionDir, "anchor"), "utf-8");
17253
+ const trimmed = raw.trim();
17254
+ return trimmed || null;
17255
+ } catch {
17256
+ return null;
17257
+ }
17258
+ }
17206
17259
  var caseInsensitiveCache;
17207
17260
  var init_partition = __esm({
17208
17261
  "src/utils/partition.ts"() {
@@ -17430,7 +17483,7 @@ async function loadTeamConfig(repoPath) {
17430
17483
  }
17431
17484
  }
17432
17485
  async function loadLocalConfig() {
17433
- const configPath = expandHome(TEAMAI_CONFIG_PATH);
17486
+ const configPath = expandHome(getUserConfigPath());
17434
17487
  const content = await readFileSafe(configPath);
17435
17488
  if (!content) return null;
17436
17489
  try {
@@ -17447,15 +17500,15 @@ function serializeLocalConfig(config) {
17447
17500
  return YAML14.stringify(persisted);
17448
17501
  }
17449
17502
  async function saveLocalConfig(config) {
17450
- await writeFile(expandHome(TEAMAI_CONFIG_PATH), serializeLocalConfig(config));
17503
+ await writeFile(expandHome(getUserConfigPath()), serializeLocalConfig(config));
17451
17504
  }
17452
17505
  async function loadState() {
17453
- const raw = await readJson(expandHome(TEAMAI_STATE_PATH));
17506
+ const raw = await readJson(expandHome(getUserStatePath()));
17454
17507
  if (!raw) return StateSchema.parse({});
17455
17508
  return StateSchema.parse(raw);
17456
17509
  }
17457
17510
  async function saveState(state) {
17458
- await writeJson(expandHome(TEAMAI_STATE_PATH), state);
17511
+ await writeJson(expandHome(getUserStatePath()), state);
17459
17512
  }
17460
17513
  async function requireInit() {
17461
17514
  const localConfig = await loadLocalConfig();
@@ -17488,8 +17541,19 @@ async function loadLocalConfigForScope(scope, projectRoot) {
17488
17541
  }
17489
17542
  }
17490
17543
  async function saveLocalConfigForScope(config, _scope, _projectRoot) {
17491
- const configPath = path48.join(getDataHome(config), "config.yaml");
17544
+ const dataHome = getDataHome(config);
17545
+ const configPath = path48.join(dataHome, "config.yaml");
17492
17546
  await writeFile(expandHome(configPath), serializeLocalConfig(config));
17547
+ try {
17548
+ if (config.scope === "project" && config.projectRoot) {
17549
+ const partition = await resolveProjectDataHome(config.projectRoot);
17550
+ if (path48.resolve(expandHome(dataHome)) === path48.resolve(partition)) {
17551
+ const anchors = await resolveAnchors(config.projectRoot);
17552
+ if (anchors) await writeAnchorFile(partition, anchors.projectAnchor);
17553
+ }
17554
+ }
17555
+ } catch {
17556
+ }
17493
17557
  }
17494
17558
  async function loadStateForScope(localConfig) {
17495
17559
  const statePath = getStatePath(localConfig);
@@ -18524,8 +18588,7 @@ async function runMigration(plan, opts = {}) {
18524
18588
  await rebaseConfigPaths(path51.join(staging, "config.yaml"), legacyDir, partitionDir);
18525
18589
  await remove(partitionDir);
18526
18590
  await fse10.rename(staging, partitionDir);
18527
- await writeFile(path51.join(partitionDir, "anchor"), `${anchor}
18528
- `);
18591
+ await writeAnchorFile(partitionDir, anchor);
18529
18592
  await releaseLock(lockPath);
18530
18593
  lockReleased = true;
18531
18594
  const backup = await retireLegacy(legacyDir);
@@ -18956,6 +19019,9 @@ async function mirrorLearnings(sourceDir, destinationDir, activeNamespaces) {
18956
19019
  }
18957
19020
  });
18958
19021
  }
19022
+ async function addLearningToCache(sourceFile, destinationDir, relativePath) {
19023
+ await fse11.copy(sourceFile, path54.join(destinationDir, relativePath), { overwrite: true });
19024
+ }
18959
19025
  var init_learnings_mirror = __esm({
18960
19026
  "src/utils/learnings-mirror.ts"() {
18961
19027
  "use strict";
@@ -19180,8 +19246,8 @@ async function recallFeedback(opts) {
19180
19246
  const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
19181
19247
  const { localConfig } = await autoDetectInit2();
19182
19248
  const { username } = localConfig;
19183
- const { VOTES_LOCAL_DIR: VOTES_LOCAL_DIR2 } = await Promise.resolve().then(() => (init_types(), types_exports));
19184
- const votePath = path55.join(VOTES_LOCAL_DIR2, `${username}.yaml`);
19249
+ const { getUserVotesDir: getUserVotesDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
19250
+ const votePath = path55.join(getUserVotesDir2(), `${username}.yaml`);
19185
19251
  if (opts.positive) {
19186
19252
  await incrementUpvoted(votePath, [opts.positive]);
19187
19253
  log.success(`Upvoted: ${opts.positive}`);
@@ -20579,8 +20645,8 @@ function computeDailyStatsDelta(current, reported) {
20579
20645
  const bucket = delta[date] ?? emptyDaily();
20580
20646
  if (!previous) {
20581
20647
  bucket.sessionsEnded += 1;
20582
- bucket.sessionsSucceeded += snapshot.succeeded;
20583
20648
  }
20649
+ bucket.sessionsSucceeded += snapshot.succeeded - (previous?.succeeded ?? 0);
20584
20650
  bucket.promptTurns += positiveDelta(snapshot.prompts, previous?.prompts);
20585
20651
  bucket.durationMs += positiveDelta(snapshot.durationMs, previous?.durationMs);
20586
20652
  bucket.sessionsCorrected += positiveDelta(snapshot.corrected, previous?.corrected);
@@ -21539,7 +21605,12 @@ async function writeReportedDailySessions(data) {
21539
21605
  }
21540
21606
  function hasDailyDelta(delta) {
21541
21607
  return Object.values(delta).some(
21542
- (bucket) => bucket.sessionsEnded > 0 || bucket.sessionsSucceeded > 0 || bucket.promptTurns > 0 || bucket.durationMs > 0 || bucket.sessionsCorrected > 0 || bucket.pricedRequests > 0 || bucket.costMicros > 0 || bucket.cacheReadTokens > 0 || bucket.cacheEligibleInputTokens > 0
21608
+ (bucket) => (
21609
+ // sessionsSucceeded can be negative (a resumed session that later failed
21610
+ // claws back an earlier increment), so it must not be checked with the
21611
+ // same "> 0" as the other, purely monotonic counters (#473).
21612
+ bucket.sessionsEnded > 0 || bucket.sessionsSucceeded !== 0 || bucket.promptTurns > 0 || bucket.durationMs > 0 || bucket.sessionsCorrected > 0 || bucket.pricedRequests > 0 || bucket.costMicros > 0 || bucket.cacheReadTokens > 0 || bucket.cacheEligibleInputTokens > 0
21613
+ )
21543
21614
  );
21544
21615
  }
21545
21616
  function filterEventsByScope(events, opts) {
@@ -21640,9 +21711,9 @@ async function reportUsageToTeam(repoPath, username, options) {
21640
21711
  filesToPush.push(`stats/${username}.yaml`);
21641
21712
  }
21642
21713
  try {
21643
- if (await pathExists(VOTES_LOCAL_DIR)) {
21714
+ if (await pathExists(getUserVotesDir())) {
21644
21715
  const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
21645
- const synced = await syncVotesToTeam2(writeRoot, username, VOTES_LOCAL_DIR);
21716
+ const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
21646
21717
  if (synced) {
21647
21718
  filesToPush.push(`votes/${username}.yaml`);
21648
21719
  }
@@ -22413,13 +22484,13 @@ async function pullForScope(localConfig, options, policy = {}) {
22413
22484
  if (localConfig.scope === "user") {
22414
22485
  await mirrorLearnings(
22415
22486
  learningsRepoDir,
22416
- LEARNINGS_LOCAL_DIR,
22487
+ getUserLearningsDir(),
22417
22488
  activeLearningsNamespaces
22418
22489
  );
22419
22490
  if (await pathExists(learningsRepoDir)) {
22420
22491
  learningsCount = await countLearnings(learningsRepoDir);
22421
22492
  }
22422
- effectiveLearningsDir = await pathExists(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0;
22493
+ effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
22423
22494
  } else {
22424
22495
  effectiveLearningsDir = await pathExists(learningsRepoDir) ? learningsRepoDir : void 0;
22425
22496
  if (effectiveLearningsDir) {
@@ -23165,6 +23236,10 @@ __export(status_exports, {
23165
23236
  import path66 from "path";
23166
23237
  import YAML21 from "yaml";
23167
23238
  async function status(options) {
23239
+ if (options.all) {
23240
+ await statusAll();
23241
+ return;
23242
+ }
23168
23243
  const { localConfig, teamConfig } = await autoDetectInit();
23169
23244
  const scopeLabel = localConfig.scope;
23170
23245
  console.log("");
@@ -23196,13 +23271,10 @@ async function status(options) {
23196
23271
  log.info("Team resources:");
23197
23272
  const repoPath = localConfig.repo.localPath;
23198
23273
  const counts = {};
23199
- const skillsDirs = await listDirs(path66.join(repoPath, "skills"));
23200
- counts.skills = skillsDirs.length;
23274
+ counts.skills = (await new SkillsHandler().scanTeamForPull(teamConfig, localConfig)).length;
23201
23275
  const rulesFiles = (await listFilesRecursive(path66.join(repoPath, "rules"))).filter((f) => f.endsWith(".md"));
23202
23276
  counts.rules = rulesFiles.length;
23203
- const docsExists = await pathExists(path66.join(repoPath, "docs"));
23204
- const docFiles = docsExists ? (await listFiles(path66.join(repoPath, "docs"))).filter((f) => !f.startsWith(".")) : [];
23205
- counts.docs = docFiles.length;
23277
+ counts.docs = await new DocsHandler().countDocFiles(path66.join(repoPath, "docs"));
23206
23278
  const envYamlPath = path66.join(repoPath, "env", "env.yaml");
23207
23279
  let envCount = 0;
23208
23280
  if (await pathExists(envYamlPath)) {
@@ -23244,6 +23316,56 @@ async function status(options) {
23244
23316
  }
23245
23317
  console.log("");
23246
23318
  }
23319
+ async function statusAll() {
23320
+ const root = projectsRootDir();
23321
+ const slugs = await listDirs(root);
23322
+ console.log("");
23323
+ log.info(`Project data partitions (${root}):`);
23324
+ if (slugs.length === 0) {
23325
+ log.info(" (none \u2014 no project has been initialized or migrated on this machine)");
23326
+ console.log("");
23327
+ return;
23328
+ }
23329
+ let orphanCount = 0;
23330
+ for (const slug of slugs.sort()) {
23331
+ const partitionDir = path66.join(root, slug);
23332
+ const anchor = await readAnchorFile(partitionDir);
23333
+ let projectPath = anchor;
23334
+ let scope;
23335
+ let kind;
23336
+ const cfgRaw = await readFileSafe(path66.join(partitionDir, "config.yaml"));
23337
+ if (cfgRaw) {
23338
+ try {
23339
+ const parsed = LocalConfigSchema.parse(YAML21.parse(cfgRaw));
23340
+ scope = parsed.scope;
23341
+ kind = parsed.repo.kind;
23342
+ if (!projectPath) projectPath = parsed.repo.businessRepoRoot ?? parsed.projectRoot ?? null;
23343
+ } catch {
23344
+ }
23345
+ }
23346
+ let state;
23347
+ if (!anchor) {
23348
+ state = projectPath ? "unknown \u2014 no anchor; cannot confirm orphan (partition may still be active)" : "unknown (no anchor / project path)";
23349
+ } else if (!await pathExists(anchor)) {
23350
+ state = "ORPHAN \u2014 project path is gone, safe to delete";
23351
+ orphanCount++;
23352
+ } else if (projectSlug(anchor) !== slug) {
23353
+ state = "corrupt \u2014 dir name does not match anchor";
23354
+ } else {
23355
+ state = "active";
23356
+ }
23357
+ const kindLabel = kind ? ` ${kind}` : "";
23358
+ log.info(` ${slug} [${state}]`);
23359
+ log.info(` project: ${projectPath ?? "(unresolved)"}${scope ? ` (${scope}${kindLabel})` : ""}`);
23360
+ }
23361
+ console.log("");
23362
+ if (orphanCount > 0) {
23363
+ log.warn(
23364
+ `${orphanCount} orphan partition(s) found. teamai never deletes them automatically; remove one with: rm -rf "${root}/<slug>"`
23365
+ );
23366
+ console.log("");
23367
+ }
23368
+ }
23247
23369
  async function list(type, options) {
23248
23370
  const { localConfig, teamConfig } = await autoDetectInit();
23249
23371
  const repoPath = localConfig.repo.localPath;
@@ -23437,9 +23559,11 @@ var init_status = __esm({
23437
23559
  init_resources();
23438
23560
  init_fs();
23439
23561
  init_skills();
23562
+ init_docs();
23440
23563
  init_known_agents();
23441
23564
  init_agent_skills();
23442
23565
  init_types();
23566
+ init_partition();
23443
23567
  init_env();
23444
23568
  init_mcp();
23445
23569
  init_hooks();
@@ -25855,16 +25979,16 @@ async function saveSession(options) {
25855
25979
  }
25856
25980
  if (options.dryRun) {
25857
25981
  log.info(
25858
- `[dry-run] Would record session ${sessionId.slice(0, 8)} (${summary.toolTotal} tools, ${summary.interventionCount} interventions, valuable=${summary.valuable}) to ${SESSION_LOGS_LOCAL_DIR}/${monthKey(summary)}.md`
25982
+ `[dry-run] Would record session ${sessionId.slice(0, 8)} (${summary.toolTotal} tools, ${summary.interventionCount} interventions, valuable=${summary.valuable}) to ${getSessionLogsDir()}/${monthKey(summary)}.md`
25859
25983
  );
25860
25984
  } else {
25861
- const written = await appendMonthlyLog(SESSION_LOGS_LOCAL_DIR, summary, { includePrompt: true });
25985
+ const written = await appendMonthlyLog(getSessionLogsDir(), summary, { includePrompt: true });
25862
25986
  if (written) {
25863
25987
  log.info(`Recorded session to ${written}`);
25864
25988
  } else {
25865
25989
  log.info(`Session ${sessionId.slice(0, 8)} already recorded this month.`);
25866
25990
  }
25867
- await pruneMonthlyLogs(SESSION_LOGS_LOCAL_DIR, /* @__PURE__ */ new Date()).catch(() => []);
25991
+ await pruneMonthlyLogs(getSessionLogsDir(), /* @__PURE__ */ new Date()).catch(() => []);
25868
25992
  }
25869
25993
  if (!options.push) return;
25870
25994
  if (!summary.valuable && !options.force) {
@@ -26860,46 +26984,78 @@ var ai_client_exports = {};
26860
26984
  __export(ai_client_exports, {
26861
26985
  callClaude: () => callClaude,
26862
26986
  callClaudeParallel: () => callClaudeParallel,
26863
- getAICliName: () => getAICliName
26987
+ getAICliName: () => getAICliName,
26988
+ pickWindowsCommand: () => pickWindowsCommand,
26989
+ resolveCliPath: () => resolveCliPath
26864
26990
  });
26865
- import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
26991
+ import { execFileSync as execFileSync2 } from "child_process";
26992
+ import spawn2 from "cross-spawn";
26866
26993
  import { existsSync } from "fs";
26994
+ function pickWindowsCommand(whereOutput) {
26995
+ const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
26996
+ for (const ext of WIN_EXEC_EXTENSIONS) {
26997
+ const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
26998
+ if (hit !== void 0) return hit;
26999
+ }
27000
+ return null;
27001
+ }
27002
+ function whereOnWindows(cmd) {
27003
+ try {
27004
+ const out = execFileSync2("where", [cmd], {
27005
+ encoding: "utf8",
27006
+ stdio: ["ignore", "pipe", "ignore"],
27007
+ shell: false,
27008
+ timeout: CLI_DETECT_TIMEOUT_MS
27009
+ });
27010
+ const p = pickWindowsCommand(out);
27011
+ return p !== null && existsSync(p) ? p : null;
27012
+ } catch {
27013
+ return null;
27014
+ }
27015
+ }
27016
+ function whichOnPosix(cmd) {
27017
+ try {
27018
+ const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
27019
+ encoding: "utf8",
27020
+ stdio: ["ignore", "pipe", "ignore"],
27021
+ shell: false,
27022
+ timeout: CLI_DETECT_TIMEOUT_MS
27023
+ }).trim();
27024
+ if (p && existsSync(p)) return p;
27025
+ } catch {
27026
+ }
27027
+ try {
27028
+ const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
27029
+ encoding: "utf8",
27030
+ stdio: ["ignore", "pipe", "ignore"],
27031
+ shell: false,
27032
+ timeout: CLI_DETECT_TIMEOUT_MS
27033
+ }).trim();
27034
+ if (p && existsSync(p)) return p;
27035
+ } catch {
27036
+ }
27037
+ try {
27038
+ const p = execFileSync2("which", [cmd], {
27039
+ encoding: "utf8",
27040
+ stdio: ["ignore", "pipe", "ignore"],
27041
+ shell: false,
27042
+ timeout: CLI_DETECT_TIMEOUT_MS
27043
+ }).trim();
27044
+ if (p && existsSync(p)) return p;
27045
+ } catch {
27046
+ }
27047
+ return null;
27048
+ }
27049
+ function resolveCliPath(cmd, platform = process.platform) {
27050
+ return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
27051
+ }
26867
27052
  function detectClaudeCli() {
26868
- const candidates = ALLOWED_CLI_CANDIDATES;
26869
- for (const cmd of candidates) {
26870
- try {
26871
- const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
26872
- encoding: "utf8",
26873
- stdio: ["ignore", "pipe", "ignore"],
26874
- shell: false,
26875
- timeout: CLI_DETECT_TIMEOUT_MS
26876
- }).trim();
26877
- if (p && existsSync(p)) return { cmd, absPath: p };
26878
- } catch {
26879
- }
26880
- try {
26881
- const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
26882
- encoding: "utf8",
26883
- stdio: ["ignore", "pipe", "ignore"],
26884
- shell: false,
26885
- timeout: CLI_DETECT_TIMEOUT_MS
26886
- }).trim();
26887
- if (p && existsSync(p)) return { cmd, absPath: p };
26888
- } catch {
26889
- }
26890
- try {
26891
- const p = execFileSync2("which", [cmd], {
26892
- encoding: "utf8",
26893
- stdio: ["ignore", "pipe", "ignore"],
26894
- shell: false,
26895
- timeout: CLI_DETECT_TIMEOUT_MS
26896
- }).trim();
26897
- if (p && existsSync(p)) return { cmd, absPath: p };
26898
- } catch {
26899
- }
27053
+ for (const cmd of ALLOWED_CLI_CANDIDATES) {
27054
+ const absPath = resolveCliPath(cmd);
27055
+ if (absPath !== null) return { cmd, absPath };
26900
27056
  }
26901
27057
  throw new Error(
26902
- "AI CLI \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5B89\u88C5\u4EE5\u4E0B\u4EFB\u610F\u4E00\u4E2A CLI \u5DE5\u5177\uFF1Aclaude / claude-internal / codex / codex-internal / codebuddy / workbuddy / openclaw"
27058
+ "AI CLI unavailable: install one of claude / claude-internal / codex / codex-internal / codebuddy / workbuddy / openclaw"
26903
27059
  );
26904
27060
  }
26905
27061
  function buildCliArgs(cmd, prompt) {
@@ -26925,8 +27081,8 @@ async function callClaude(prompt, opts) {
26925
27081
  }
26926
27082
  log.debug(`[ai-client] calling ${_cliInfo.cmd}, timeout=${Math.round(timeoutMs / 1e3)}s, prompt=${prompt.slice(0, 60).replace(/\n/g, " ")}...`);
26927
27083
  const child = spawn2(_cliInfo.absPath, buildCliArgs(_cliInfo.cmd, prompt), { stdio: ["ignore", "pipe", "pipe"] });
26928
- child.stdout.on("data", (chunk) => chunks.push(chunk));
26929
- child.stderr.on("data", (chunk) => errChunks.push(chunk));
27084
+ child.stdout?.on("data", (chunk) => chunks.push(chunk));
27085
+ child.stderr?.on("data", (chunk) => errChunks.push(chunk));
26930
27086
  const timer = setTimeout(() => {
26931
27087
  child.kill();
26932
27088
  const seconds = Math.round(timeoutMs / 1e3);
@@ -27007,7 +27163,7 @@ async function runWithConcurrency(tasks, concurrency) {
27007
27163
  await Promise.all(promises);
27008
27164
  return results;
27009
27165
  }
27010
- var ALLOWED_CLI_CANDIDATES, CLI_DETECT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27166
+ var ALLOWED_CLI_CANDIDATES, CLI_DETECT_TIMEOUT_MS, WIN_EXEC_EXTENSIONS, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27011
27167
  var init_ai_client = __esm({
27012
27168
  "src/utils/ai-client.ts"() {
27013
27169
  "use strict";
@@ -27024,6 +27180,7 @@ var init_ai_client = __esm({
27024
27180
  "openclaw"
27025
27181
  ];
27026
27182
  CLI_DETECT_TIMEOUT_MS = 5e3;
27183
+ WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
27027
27184
  DEFAULT_TIMEOUT_MS = 6e5;
27028
27185
  DEFAULT_CONCURRENCY = 3;
27029
27186
  }
@@ -28070,7 +28227,7 @@ async function resolveVizRoot(opts) {
28070
28227
  const reportsRoot = getReportsDir(config);
28071
28228
  const useProjectScope = config.scope === "project" && Boolean(config.projectRoot);
28072
28229
  const teamaiHome2 = useProjectScope ? getDataHome(config) : getTeamaiHome("user");
28073
- const learningsDir = useProjectScope ? path81.join(knowledgeRoot, "learnings") : LEARNINGS_LOCAL_DIR;
28230
+ const learningsDir = useProjectScope ? path81.join(knowledgeRoot, "learnings") : getUserLearningsDir();
28074
28231
  const source = config.repo.kind === "self" ? { scope: "local", label: "Personal repo \xB7 your recalls only" } : { scope: "team", label: "Team repo \xB7 aggregated across the team" };
28075
28232
  return {
28076
28233
  root: knowledgeRoot,
@@ -28082,14 +28239,14 @@ async function resolveVizRoot(opts) {
28082
28239
  source
28083
28240
  };
28084
28241
  }
28085
- const teamaiHome = path81.dirname(VOTES_LOCAL_DIR);
28242
+ const teamaiHome = getTeamaiHomeDir();
28086
28243
  return {
28087
28244
  root: teamaiHome,
28088
28245
  knowledgeRoot: teamaiHome,
28089
- votesDir: VOTES_LOCAL_DIR,
28090
- learningsDir: LEARNINGS_LOCAL_DIR,
28246
+ votesDir: getUserVotesDir(),
28247
+ learningsDir: getUserLearningsDir(),
28091
28248
  statsDir: path81.join(teamaiHome, "stats"),
28092
- indexPath: SEARCH_INDEX_PATH,
28249
+ indexPath: getUserSearchIndexPath(),
28093
28250
  source: { scope: "local", label: "Local ~/.teamai \xB7 your recalls only" }
28094
28251
  };
28095
28252
  }
@@ -30048,8 +30205,8 @@ var init_hook_handlers = __esm({
30048
30205
  const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
30049
30206
  const voteData = await parseTranscriptForVotes2(transcriptPath);
30050
30207
  const { localConfig } = await autoDetectInit2();
30051
- const { VOTES_LOCAL_DIR: VOTES_LOCAL_DIR2 } = await Promise.resolve().then(() => (init_types(), types_exports));
30052
- const votesDir = VOTES_LOCAL_DIR2;
30208
+ const { getUserVotesDir: getUserVotesDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
30209
+ const votesDir = getUserVotesDir2();
30053
30210
  const votePath = path90.join(votesDir, `${localConfig.username}.yaml`);
30054
30211
  const recalledSet = new Set(voteData.recalledDocIds);
30055
30212
  const verifiedDocIds = voteData.referencedDocIds.filter((id) => recalledSet.has(id));
@@ -30323,8 +30480,8 @@ async function rebuildIndexAfterContribute(localConfig) {
30323
30480
  localConfig.projects ?? []
30324
30481
  );
30325
30482
  if (localConfig.scope === "user") {
30326
- await mirrorLearnings(learningsRepoDir, LEARNINGS_LOCAL_DIR, activeLearningsNamespaces);
30327
- effectiveLearningsDir = await pathExists(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0;
30483
+ await mirrorLearnings(learningsRepoDir, getUserLearningsDir(), activeLearningsNamespaces);
30484
+ effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
30328
30485
  } else {
30329
30486
  effectiveLearningsDir = await pathExists(learningsRepoDir) ? learningsRepoDir : void 0;
30330
30487
  }
@@ -30454,12 +30611,11 @@ async function contributeSelf(localConfig, content, options) {
30454
30611
  await fs30.promises.writeFile(destAbs, content, "utf-8");
30455
30612
  try {
30456
30613
  const { pathExists: pathExists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
30457
- const wtLearnings = path91.join(wtRepo, "learnings");
30458
30614
  const activeLearningsNamespaces = await resolveActiveLearningsNamespaces(
30459
30615
  localConfig.repo.localPath,
30460
30616
  localConfig.projects ?? []
30461
30617
  );
30462
- await mirrorLearnings(wtLearnings, LEARNINGS_LOCAL_DIR, activeLearningsNamespaces);
30618
+ await addLearningToCache(destAbs, getUserLearningsDir(), relPath.slice("learnings/".length));
30463
30619
  const repoPath = localConfig.repo.localPath;
30464
30620
  const docsDir = path91.join(repoPath, "docs");
30465
30621
  const rulesDir = path91.join(repoPath, "rules");
@@ -30474,7 +30630,7 @@ async function contributeSelf(localConfig, content, options) {
30474
30630
  const teamaiHome = getDataHome(localConfig);
30475
30631
  const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
30476
30632
  await buildIndex2({
30477
- learningsDir: await pathExists3(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0,
30633
+ learningsDir: await pathExists3(getUserLearningsDir()) ? getUserLearningsDir() : void 0,
30478
30634
  learningsNamespaces: activeLearningsNamespaces,
30479
30635
  docsDir: await pathExists3(docsDir) ? docsDir : void 0,
30480
30636
  rulesDir: await pathExists3(rulesDir) ? rulesDir : void 0,
@@ -36050,6 +36206,9 @@ var init_reconciler_v2_types = __esm({
36050
36206
  // src/wiki-engine/knowledge-reconciler.ts
36051
36207
  import { readFile as readFile9, readdir as readdir3, stat as stat5 } from "fs/promises";
36052
36208
  import path109 from "path";
36209
+ function isExpectedSelfLoop(edge) {
36210
+ return edge.from === edge.to && edge.relation === "IMPLEMENTS" && !!edge.source && REPAIRABLE_CODE_EDGE_SOURCES.has(edge.source);
36211
+ }
36053
36212
  async function loadReconciliationBase(wikiRoot) {
36054
36213
  const graphPath = path109.join(wikiRoot, ".indices", "graph-index.json");
36055
36214
  const graph = await loadGraphIndex(wikiRoot);
@@ -36061,7 +36220,10 @@ async function loadReconciliationBase(wikiRoot) {
36061
36220
  graph.nodes,
36062
36221
  graph.edges.filter((edge) => edge.source !== BRIDGE_EDGE_SOURCE)
36063
36222
  );
36064
- if (validateGraph(base).issues.some((issue) => issue.code !== REPAIRABLE_VALIDATION_ISSUE_CODE)) {
36223
+ const hasUnexpectedSelfLoop = base.edges.some((edge) => edge.from === edge.to && !isExpectedSelfLoop(edge));
36224
+ if (hasUnexpectedSelfLoop || validateGraph(base).issues.some(
36225
+ (issue) => issue.code !== REPAIRABLE_VALIDATION_ISSUE_CODE && issue.code !== SELF_LOOP_VALIDATION_ISSUE_CODE
36226
+ )) {
36065
36227
  throw new Error(`Cannot reconcile invalid graph index at ${graphPath}`);
36066
36228
  }
36067
36229
  const nodeSlugs = new Set(base.nodes.map((node) => node.slug));
@@ -36082,7 +36244,7 @@ async function loadReconciliationBase(wikiRoot) {
36082
36244
  }
36083
36245
  }
36084
36246
  const repaired = mergeGraphs(base, createGraphIndex(endpointNodes));
36085
- if (!validateGraph(repaired).valid) {
36247
+ if (validateGraph(repaired).issues.some((issue) => issue.code !== SELF_LOOP_VALIDATION_ISSUE_CODE)) {
36086
36248
  throw new Error(`Cannot reconcile invalid graph index at ${graphPath}`);
36087
36249
  }
36088
36250
  return repaired;
@@ -36406,7 +36568,7 @@ async function reconcileKnowledge(options) {
36406
36568
  stats
36407
36569
  };
36408
36570
  }
36409
- var BRIDGE_EDGE_SOURCE, PRODUCT_PAGE_TYPE, CODE_PAGE_TYPE, PAGE_CONFIDENCE, PRODUCT_PAGE_DOMAIN, CODE_PAGE_DOMAIN, REPAIRABLE_CODE_EDGE_SOURCES, MISSING_PATH_ERROR_CODE, REPAIRABLE_VALIDATION_ISSUE_CODE;
36571
+ var BRIDGE_EDGE_SOURCE, PRODUCT_PAGE_TYPE, CODE_PAGE_TYPE, PAGE_CONFIDENCE, PRODUCT_PAGE_DOMAIN, CODE_PAGE_DOMAIN, REPAIRABLE_CODE_EDGE_SOURCES, MISSING_PATH_ERROR_CODE, REPAIRABLE_VALIDATION_ISSUE_CODE, SELF_LOOP_VALIDATION_ISSUE_CODE;
36410
36572
  var init_knowledge_reconciler = __esm({
36411
36573
  "src/wiki-engine/knowledge-reconciler.ts"() {
36412
36574
  "use strict";
@@ -36421,6 +36583,7 @@ var init_knowledge_reconciler = __esm({
36421
36583
  REPAIRABLE_CODE_EDGE_SOURCES = /* @__PURE__ */ new Set(["code-ast", "code-heuristic"]);
36422
36584
  MISSING_PATH_ERROR_CODE = "ENOENT";
36423
36585
  REPAIRABLE_VALIDATION_ISSUE_CODE = "edge.missing_node";
36586
+ SELF_LOOP_VALIDATION_ISSUE_CODE = "edge.self_loop";
36424
36587
  }
36425
36588
  });
36426
36589
 
@@ -37782,7 +37945,7 @@ var deep_enrich_exports = {};
37782
37945
  __export(deep_enrich_exports, {
37783
37946
  deepEnrich: () => deepEnrich
37784
37947
  });
37785
- import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8 } from "fs/promises";
37948
+ import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
37786
37949
  import path113 from "path";
37787
37950
  async function readFileSafe4(filePath) {
37788
37951
  try {
@@ -38239,6 +38402,10 @@ async function runPhaseAiGraph(opts, ctx, docsDir) {
38239
38402
  const g6 = buildG6Content(project, ctx.manifest);
38240
38403
  await writeFile11(path113.join(docsDir, "graph-g6-multihop.md"), g6, "utf-8");
38241
38404
  log.debug(`deep-enrich[${project}]: G6 multi-hop analysis written`);
38405
+ const g5Path = path113.join(docsDir, "graph-g5-scenarios.md");
38406
+ if (await pathExists(g5Path)) {
38407
+ return { g5Generated: true, g6Generated: g6HasEdges };
38408
+ }
38242
38409
  let g5Generated = false;
38243
38410
  if (ctx.moduleDocs.size < 2) {
38244
38411
  log.warn(`deep-enrich[${project}]: Insufficient modules (${ctx.moduleDocs.size} < 2), skipping G5`);
@@ -38317,6 +38484,47 @@ async function runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags) {
38317
38484
  }
38318
38485
  log.debug(`deep-enrich[${project}]: Index incremental update complete`);
38319
38486
  }
38487
+ function architectureDocPath(docsDir) {
38488
+ return path113.join(docsDir, "architecture.md");
38489
+ }
38490
+ async function existingComponentSlugs(docsDir, slugs) {
38491
+ const found = [];
38492
+ for (const slug of slugs) {
38493
+ try {
38494
+ assertSafeResourceName(slug);
38495
+ } catch {
38496
+ continue;
38497
+ }
38498
+ if (await pathExists(path113.join(docsDir, `${slug}.md`))) found.push(slug);
38499
+ }
38500
+ return found;
38501
+ }
38502
+ async function collectMissingAiDocs(docsDir, slugs) {
38503
+ const existing = new Set(await existingComponentSlugs(docsDir, slugs));
38504
+ return {
38505
+ missingComponents: slugs.filter((slug) => !existing.has(slug)),
38506
+ missingArchitecture: !await pathExists(architectureDocPath(docsDir))
38507
+ };
38508
+ }
38509
+ async function removeIfExists(filePath) {
38510
+ try {
38511
+ await unlink2(filePath);
38512
+ } catch (err) {
38513
+ if (err.code !== "ENOENT") throw err;
38514
+ }
38515
+ }
38516
+ async function invalidateStaleAiDocs(docsDir, slugs) {
38517
+ for (const slug of slugs) {
38518
+ try {
38519
+ assertSafeResourceName(slug);
38520
+ } catch {
38521
+ continue;
38522
+ }
38523
+ await removeIfExists(path113.join(docsDir, `${slug}.md`));
38524
+ }
38525
+ await removeIfExists(architectureDocPath(docsDir));
38526
+ await removeIfExists(path113.join(docsDir, "graph-g5-scenarios.md"));
38527
+ }
38320
38528
  async function deepEnrich(opts) {
38321
38529
  const { project, evidenceDir } = opts;
38322
38530
  const docsDir = path113.join(evidenceDir, "docs");
@@ -38325,7 +38533,7 @@ async function deepEnrich(opts) {
38325
38533
  let components = ctx.manifest.components ?? [];
38326
38534
  if (components.length === 0) {
38327
38535
  log.warn(`deep-enrich[${project}]: No components in _manifest.json, aborting`);
38328
- return;
38536
+ return { project, complete: false, missingComponents: [], missingArchitecture: true };
38329
38537
  }
38330
38538
  if (opts.maxModules && components.length > opts.maxModules) {
38331
38539
  log.info(`deep-enrich[${project}]: Limited to first ${opts.maxModules} components (${components.length} total)`);
@@ -38334,52 +38542,55 @@ async function deepEnrich(opts) {
38334
38542
  }
38335
38543
  const allSlugs = components.map((c) => c.slug);
38336
38544
  const progress = await loadProgress(evidenceDir, project, allSlugs);
38337
- if (progress.phase === "done") {
38338
- const manifestTime = ctx.manifest.generatedAt;
38339
- const progressStale = manifestTime && progress.startedAt < manifestTime;
38340
- let docsEmpty = true;
38341
- if (await pathExists(docsDir)) {
38342
- const entries = await readdir4(docsDir).catch(() => []);
38343
- docsEmpty = entries.filter((e) => e.endsWith(".md")).length === 0;
38344
- }
38345
- if (docsEmpty || progressStale) {
38346
- log.info(`deep-enrich[${project}]: stale progress detected (phase=done, docs_empty=${docsEmpty}, progress_stale=${Boolean(progressStale)}), resetting`);
38347
- progress.phase = "pending";
38348
- progress.componentsDone = [];
38349
- progress.componentsPending = [...allSlugs];
38350
- progress.startedAt = (/* @__PURE__ */ new Date()).toISOString();
38351
- await saveProgress(evidenceDir, progress);
38352
- }
38545
+ const manifestTime = ctx.manifest.generatedAt;
38546
+ if (manifestTime && progress.startedAt < manifestTime) {
38547
+ log.info(
38548
+ `deep-enrich[${project}]: stale progress detected (manifest newer than progress.startedAt), invalidating AI docs`
38549
+ );
38550
+ await invalidateStaleAiDocs(docsDir, allSlugs);
38551
+ progress.phase = "pending";
38552
+ progress.componentsDone = [];
38553
+ progress.componentsPending = [...allSlugs];
38554
+ progress.startedAt = (/* @__PURE__ */ new Date()).toISOString();
38555
+ await saveProgress(evidenceDir, progress);
38353
38556
  }
38354
- if (progress.phase === "pending" || progress.phase === "components") {
38557
+ const existing = await existingComponentSlugs(docsDir, allSlugs);
38558
+ progress.componentsDone = existing;
38559
+ progress.componentsPending = allSlugs.filter((slug) => !existing.includes(slug));
38560
+ if (progress.componentsPending.length > 0) {
38355
38561
  progress.phase = "components";
38356
38562
  await saveProgress(evidenceDir, progress);
38357
38563
  await runPhaseComponents(opts, ctx, progress, docsDir);
38358
38564
  }
38359
- if (progress.phase === "components" || progress.phase === "architecture") {
38565
+ if (!await pathExists(architectureDocPath(docsDir))) {
38360
38566
  progress.phase = "architecture";
38361
38567
  await saveProgress(evidenceDir, progress);
38362
38568
  await runPhaseArchitecture(opts, ctx, docsDir);
38363
38569
  }
38364
- if (progress.phase === "architecture" || progress.phase === "graph") {
38365
- progress.phase = "graph";
38366
- await saveProgress(evidenceDir, progress);
38367
- await runPhaseGraph(opts, ctx, docsDir);
38368
- }
38369
- let graphFlags = { g5Generated: false, g6Generated: true };
38370
- if (progress.phase === "graph" || progress.phase === "ai-graph") {
38371
- progress.phase = "ai-graph";
38372
- await saveProgress(evidenceDir, progress);
38373
- graphFlags = await runPhaseAiGraph(opts, ctx, docsDir);
38374
- }
38375
- if (progress.phase === "ai-graph" || progress.phase === "index-enhance") {
38376
- progress.phase = "index-enhance";
38377
- await saveProgress(evidenceDir, progress);
38378
- await runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags);
38379
- }
38380
- progress.phase = "done";
38570
+ progress.phase = "graph";
38381
38571
  await saveProgress(evidenceDir, progress);
38382
- log.success(`deep-enrich[${project}]: Deep knowledge generation complete`);
38572
+ await runPhaseGraph(opts, ctx, docsDir);
38573
+ progress.phase = "ai-graph";
38574
+ await saveProgress(evidenceDir, progress);
38575
+ const graphFlags = await runPhaseAiGraph(opts, ctx, docsDir);
38576
+ progress.phase = "index-enhance";
38577
+ await saveProgress(evidenceDir, progress);
38578
+ await runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags);
38579
+ const missing = await collectMissingAiDocs(docsDir, allSlugs);
38580
+ const complete = missing.missingComponents.length === 0 && !missing.missingArchitecture;
38581
+ progress.componentsDone = allSlugs.filter((slug) => !missing.missingComponents.includes(slug));
38582
+ progress.componentsPending = missing.missingComponents;
38583
+ progress.phase = complete ? "done" : missing.missingComponents.length > 0 ? "components" : "architecture";
38584
+ await saveProgress(evidenceDir, progress);
38585
+ if (complete) {
38586
+ log.success(`deep-enrich[${project}]: Deep knowledge generation complete`);
38587
+ } else {
38588
+ const missingComps = missing.missingComponents.join(", ") || "(none)";
38589
+ log.warn(
38590
+ `deep-enrich[${project}]: incomplete \u2014 missing component docs: ${missingComps}, architecture: ${missing.missingArchitecture}`
38591
+ );
38592
+ }
38593
+ return { project, complete, ...missing };
38383
38594
  }
38384
38595
  var PROGRESS_PATH_SUBDIR, PROGRESS_FILENAME, VALID_PHASES;
38385
38596
  var init_deep_enrich = __esm({
@@ -40464,7 +40675,7 @@ async function codebaseCmd(opts) {
40464
40675
  await printCodebaseStatus(opts);
40465
40676
  return;
40466
40677
  }
40467
- if (!opts.lint && !opts.reconcile) {
40678
+ if (!opts.lint && !opts.reconcile && !opts.deepEnrich) {
40468
40679
  console.log("teamai codebase \u2014 team codebase knowledge management");
40469
40680
  console.log("");
40470
40681
  console.log("Usage:");
@@ -40474,6 +40685,7 @@ async function codebaseCmd(opts) {
40474
40685
  console.log(" teamai codebase --lint --json Output JSON report (for CI)");
40475
40686
  console.log(" teamai codebase --lint --severity high Only report high-severity issues");
40476
40687
  console.log(" teamai codebase --reconcile Reconcile product and code knowledge");
40688
+ console.log(" teamai codebase --deep-enrich Generate deep knowledge from extracted evidence");
40477
40689
  console.log(" teamai codebase --status Show knowledge-base git baseline");
40478
40690
  return;
40479
40691
  }
@@ -40492,7 +40704,72 @@ async function codebaseCmd(opts) {
40492
40704
  }
40493
40705
  if (!await pathExists3(teamwikiDir)) {
40494
40706
  console.log("No teamwiki found. Run `teamai import` first.");
40495
- if (opts.reconcile) process.exitCode = 1;
40707
+ if (opts.reconcile || opts.deepEnrich) process.exitCode = 1;
40708
+ return;
40709
+ }
40710
+ if (opts.deepEnrich) {
40711
+ const project = opts.project?.trim() ?? "";
40712
+ if (!project) {
40713
+ console.log("Deep enrichment requires --project <slug>.");
40714
+ process.exitCode = 1;
40715
+ return;
40716
+ }
40717
+ const { assertWithinRoot: assertWithinRoot2 } = await Promise.resolve().then(() => (init_path_safety(), path_safety_exports));
40718
+ const codeRoot = path125.join(teamwikiDir, "evidence", "code");
40719
+ const evidenceDir = path125.join(codeRoot, project);
40720
+ try {
40721
+ assertWithinRoot2(codeRoot, evidenceDir);
40722
+ } catch (e) {
40723
+ console.log(e.message);
40724
+ process.exitCode = 1;
40725
+ return;
40726
+ }
40727
+ if (!await pathExists3(evidenceDir)) {
40728
+ console.log(`No extracted evidence found for project "${project}". Run \`teamai codebase --extract\` first.`);
40729
+ process.exitCode = 1;
40730
+ return;
40731
+ }
40732
+ let componentCount = 0;
40733
+ try {
40734
+ const manifest = JSON.parse(await readFile15(path125.join(evidenceDir, "_manifest.json"), "utf-8"));
40735
+ componentCount = Array.isArray(manifest.components) ? manifest.components.length : 0;
40736
+ } catch {
40737
+ componentCount = 0;
40738
+ }
40739
+ if (componentCount === 0) {
40740
+ console.log(`No components in evidence for project "${project}". Run \`teamai codebase --extract\` first.`);
40741
+ process.exitCode = 1;
40742
+ return;
40743
+ }
40744
+ if (opts.dryRun) {
40745
+ if (opts.json) {
40746
+ console.log(JSON.stringify({ project, evidenceDir, dryRun: true }, null, 2));
40747
+ } else {
40748
+ console.log(`Deep enrichment preview: project=${project} (no writes)`);
40749
+ }
40750
+ return;
40751
+ }
40752
+ const { deepEnrich: deepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
40753
+ const result = await deepEnrich2({ project, evidenceDir, wikiRoot: teamwikiDir });
40754
+ if (opts.json) {
40755
+ console.log(JSON.stringify({
40756
+ project,
40757
+ evidenceDir,
40758
+ complete: result.complete,
40759
+ missingComponents: result.missingComponents,
40760
+ missingArchitecture: result.missingArchitecture
40761
+ }, null, 2));
40762
+ } else if (result.complete) {
40763
+ console.log(`Deep enrichment complete: project=${project}`);
40764
+ } else {
40765
+ const parts = [];
40766
+ if (result.missingComponents.length > 0) {
40767
+ parts.push(`missing component docs: ${result.missingComponents.join(", ")}`);
40768
+ }
40769
+ if (result.missingArchitecture) parts.push("missing architecture.md");
40770
+ console.log(`Deep enrichment incomplete: project=${project}, ${parts.join(", ")}`);
40771
+ }
40772
+ if (!result.complete) process.exitCode = 1;
40496
40773
  return;
40497
40774
  }
40498
40775
  if (opts.reconcile) {
@@ -41699,10 +41976,10 @@ program.command("pull").description("Pull team resources and inject into local A
41699
41976
  const { pull: pull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
41700
41977
  await pull2({ ...globalOpts, ...cmdOpts });
41701
41978
  });
41702
- program.command("status").description("Show local vs team repo diff").action(async () => {
41979
+ program.command("status").description("Show local vs team repo diff").option("--all", "List every project data partition under ~/.teamai/projects (flags stale/orphan ones)").action(async (cmdOpts) => {
41703
41980
  const globalOpts = program.opts();
41704
41981
  const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
41705
- await status2(globalOpts);
41982
+ await status2({ ...globalOpts, ...cmdOpts });
41706
41983
  });
41707
41984
  program.command("list [type]").description("List resources (skills|rules|docs|env|agents|hooks|mcp). For skills, --source local/all also scans installed AI agent skill directories.").option("--source <src>", "Where to look for skills: repo | local | all", "all").option("--agent <name>", "Filter local agents by id (only applies to skills)").option("--reveal", "Show env values in plaintext (default: masked)").action(async (type, cmdOpts) => {
41708
41985
  const globalOpts = program.opts();
@@ -42087,7 +42364,7 @@ program.command("mr-hint", { hidden: true }).description("Hint AI about recently
42087
42364
  await mrHint2();
42088
42365
  }
42089
42366
  });
42090
- program.command("codebase").description("Inspect and maintain team-codebase outputs").option("--extract [path]", "Extract code knowledge and build graph from source").addOption(new Option("--incremental", "Only re-extract changed files (requires prior manifest)").hideHelp()).addOption(new Option("--project <name>", "Project slug for extract output (default: directory name)").hideHelp()).addOption(new Option("--max-files <n>", "Max source files to scan (default: 200)").hideHelp()).addOption(new Option("--upgrade-wiki", "Migrate docs/team-codebase/ to teamwiki/ graph format").hideHelp()).option("--lint", "Run global consistency lint over the teamwiki knowledge graph").option("--reconcile", "Reconcile product and code knowledge in teamwiki").addOption(new Option("--fix", "Deprecated: teamwiki lint has no autofix; runs lint in report-only mode").hideHelp()).option("--status", "Show knowledge-base git baseline (headSha / repoUrl / branch)").addOption(new Option("--severity <level>", "Minimum severity to report: high|medium|low|info").default("info").hideHelp()).option("--json", "Output report as JSON (suitable for CI)").addOption(new Option("--output <path>", "Custom teamwiki output root directory").hideHelp()).action(async (cmdOpts) => {
42367
+ program.command("codebase").description("Inspect and maintain team-codebase outputs").option("--extract [path]", "Extract code knowledge and build graph from source").addOption(new Option("--incremental", "Only re-extract changed files (requires prior manifest)").hideHelp()).addOption(new Option("--project <name>", "Project slug for --extract (defaults to directory name) and required for --deep-enrich").hideHelp()).addOption(new Option("--max-files <n>", "Max source files to scan (default: 200)").hideHelp()).addOption(new Option("--upgrade-wiki", "Migrate docs/team-codebase/ to teamwiki/ graph format").hideHelp()).option("--lint", "Run global consistency lint over the teamwiki knowledge graph").option("--reconcile", "Reconcile product and code knowledge in teamwiki").option("--deep-enrich", "Generate deep knowledge docs from extracted evidence").addOption(new Option("--fix", "Deprecated: teamwiki lint has no autofix; runs lint in report-only mode").hideHelp()).option("--status", "Show knowledge-base git baseline (headSha / repoUrl / branch)").addOption(new Option("--severity <level>", "Minimum severity to report: high|medium|low|info").default("info").hideHelp()).option("--json", "Output report as JSON (suitable for CI)").addOption(new Option("--output <path>", "Custom teamwiki output root directory").hideHelp()).action(async (cmdOpts) => {
42091
42368
  const globalOpts = program.opts();
42092
42369
  const { codebaseCmd: codebaseCmd2 } = await Promise.resolve().then(() => (init_codebase_cmd(), codebase_cmd_exports));
42093
42370
  await codebaseCmd2({ ...globalOpts, ...cmdOpts });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.24.0-beta.4",
3
+ "version": "0.24.0-beta.6",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,6 +23,7 @@
23
23
  - 每次生成后有准确性统计,超标自动警告
24
24
  - AI 读知识库而非读源码,**约 1/50 的 token 消耗**获得全局架构认知
25
25
  - Phase 0 可用 `teamai codebase --extract` 生成可证据化的结构边(TS/JS/Python/Go AST + 多语言 heuristic)
26
+ - 提取后可用 `teamai codebase --deep-enrich --project <slug> --output <repo>` 生成确定性图谱文档(G1/G2/G3)与深度知识;无需单独的 team-wiki CLI
26
27
 
27
28
  ---
28
29
 
@@ -889,6 +889,7 @@ _review/ ← 过程文件(不入知识库)
889
889
  | 阶段 | 命令 / 路径 |
890
890
  |------|-------------|
891
891
  | Phase 0 结构基线 | `teamai codebase --extract <repo> --project <slug>`(writes `<repo>/teamwiki/`) |
892
+ | Deep knowledge | Use `teamai codebase --deep-enrich --project <slug> --output <repo>` after extract has written `teamwiki/evidence/code/<slug>/`. `--output` is the repository root, not the `teamwiki/` directory. Prefix with `teamai --dry-run` to preview without writing. TeamAI does not ship a separate team-wiki CLI. No extra plugin is required. |
892
893
  | K3 后编译进 wiki | Skip. TeamAI does not ship a separate team-wiki CLI. Continue with this skill using `teamai` and the files under this skill directory. No extra plugin is required. |
893
894
  | 产品文档入图 | Skip. Same English note as above. |
894
895
  | 产品↔代码桥接 | Use `teamai codebase --reconcile --output <repo>` after product pages and extracted code pages are under `<repo>/teamwiki/`. Prefix with `teamai --dry-run` to preview without updating the graph. |