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

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/README.md CHANGED
@@ -191,6 +191,7 @@ Matched: conflict | Missing: port
191
191
  teamai import --from-repo https://github.com/org/repo
192
192
  teamai import --from-org myorg # batch import all repos
193
193
  teamai codebase --extract /path/to/repo # local extract into teamwiki/
194
+ teamai codebase --deep-enrich --project my-service --output /path/to/repo # generate deep knowledge docs
194
195
  teamai codebase --reconcile --output /path/to/repo # map product docs to code pages
195
196
  teamai codebase --lint --output /path/to/repo # check the locally extracted graph
196
197
  ```
@@ -244,6 +245,7 @@ Insight into how the team actually uses its AI tools, and a starting point for t
244
245
  | `teamai recall maintenance` | Maintain knowledge base health: prune low-confidence learnings, writeback confidence scores, flag stale entries |
245
246
  | `teamai import` | Import knowledge (`--dir`, `--from-repo`, `--from-org`, `--from-repo-list`, `--from-mr`) |
246
247
  | `teamai codebase --extract [path]` | Extract code facts and build the local graph under `teamwiki/` |
248
+ | `teamai codebase --deep-enrich` | Generate deep knowledge docs from extracted evidence |
247
249
  | `teamai codebase --reconcile` | Reconcile product documentation with extracted code knowledge |
248
250
  | `teamai codebase --lint` | Knowledge graph health check |
249
251
  | `teamai ci extract-mr --url <url>` | CI: extract knowledge from MR, post comments, write after merge |
package/README.zh-CN.md CHANGED
@@ -191,6 +191,7 @@ Matched: conflict | Missing: port
191
191
  teamai import --from-repo https://github.com/org/repo
192
192
  teamai import --from-org myorg # 批量导入所有仓库
193
193
  teamai codebase --extract /path/to/repo # 本地提取到 teamwiki/
194
+ teamai codebase --deep-enrich --project my-service --output /path/to/repo # 从提取结果生成深度知识文档
194
195
  teamai codebase --reconcile --output /path/to/repo # 将产品文档映射到代码页面
195
196
  teamai codebase --lint --output /path/to/repo # 检查本地提取的图谱
196
197
  ```
@@ -244,6 +245,7 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
244
245
  | `teamai recall maintenance` | 维护知识库健康:清理低置信度 learnings、回写置信度、标记过时条目 |
245
246
  | `teamai import` | 导入知识(`--dir`、`--from-repo`、`--from-org`、`--from-repo-list`、`--from-mr`) |
246
247
  | `teamai codebase --extract [path]` | 提取代码事实并在 `teamwiki/` 下构建本地图谱 |
248
+ | `teamai codebase --deep-enrich` | 从已提取的 evidence 生成深度知识文档 |
247
249
  | `teamai codebase --reconcile` | 将产品文档与提取的代码知识进行对账 |
248
250
  | `teamai codebase --lint` | 知识图谱健康检查 |
249
251
  | `teamai ci extract-mr --url <url>` | CI:从 MR 提取知识、发评论、合并后写入 |
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);
@@ -13768,7 +13803,7 @@ async function acquireReclaimSentinel(sentinel, owner) {
13768
13803
  return exclusiveCreate(sentinel, payload);
13769
13804
  }
13770
13805
  async function acquireLock(lockPath) {
13771
- const resolved = lockPath ?? expandHome(TEAMAI_UPDATE_LOCK_PATH);
13806
+ const resolved = lockPath ?? expandHome(getUpdateLockPath());
13772
13807
  const owner = randomUUID2();
13773
13808
  const payload = JSON.stringify({
13774
13809
  pid: process.pid,
@@ -13807,7 +13842,7 @@ async function acquireLock(lockPath) {
13807
13842
  }
13808
13843
  }
13809
13844
  async function releaseLock(lockPath) {
13810
- const resolved = lockPath ?? expandHome(TEAMAI_UPDATE_LOCK_PATH);
13845
+ const resolved = lockPath ?? expandHome(getUpdateLockPath());
13811
13846
  const ourOwner = heldLockOwners.get(resolved);
13812
13847
  if (!ourOwner) return;
13813
13848
  try {
@@ -15211,7 +15246,7 @@ async function initHttp(url, options) {
15211
15246
  if (scope === "project") {
15212
15247
  await saveLocalConfigForScope(localConfig, scope, projectRoot);
15213
15248
  } else {
15214
- await ensureDir(TEAMAI_HOME);
15249
+ await ensureDir(getTeamaiHomeDir());
15215
15250
  await saveLocalConfig(localConfig);
15216
15251
  }
15217
15252
  log.success(`Local config saved to ${teamaiHome}/config.yaml`);
@@ -15950,9 +15985,9 @@ async function init(options) {
15950
15985
  log.debug("Generated .teamai/.gitignore for project scope");
15951
15986
  }
15952
15987
  } else {
15953
- await ensureDir(TEAMAI_HOME);
15988
+ await ensureDir(getTeamaiHomeDir());
15954
15989
  await saveLocalConfig(localConfig);
15955
- log.success(`Local config saved to ${TEAMAI_HOME}/config.yaml`);
15990
+ log.success(`Local config saved to ${getTeamaiHomeDir()}/config.yaml`);
15956
15991
  }
15957
15992
  try {
15958
15993
  const state = await loadStateForScope(localConfig);
@@ -17203,6 +17238,23 @@ function projectSlug(anchor) {
17203
17238
  function projectDataHome(anchor) {
17204
17239
  return path46.join(getUserHome(), ".teamai", "projects", projectSlug(anchor));
17205
17240
  }
17241
+ function projectsRootDir() {
17242
+ return path46.join(getUserHome(), ".teamai", "projects");
17243
+ }
17244
+ async function writeAnchorFile(partitionDir, anchor) {
17245
+ await fs17.promises.mkdir(partitionDir, { recursive: true });
17246
+ await fs17.promises.writeFile(path46.join(partitionDir, "anchor"), `${anchor}
17247
+ `, "utf-8");
17248
+ }
17249
+ async function readAnchorFile(partitionDir) {
17250
+ try {
17251
+ const raw = await fs17.promises.readFile(path46.join(partitionDir, "anchor"), "utf-8");
17252
+ const trimmed = raw.trim();
17253
+ return trimmed || null;
17254
+ } catch {
17255
+ return null;
17256
+ }
17257
+ }
17206
17258
  var caseInsensitiveCache;
17207
17259
  var init_partition = __esm({
17208
17260
  "src/utils/partition.ts"() {
@@ -17430,7 +17482,7 @@ async function loadTeamConfig(repoPath) {
17430
17482
  }
17431
17483
  }
17432
17484
  async function loadLocalConfig() {
17433
- const configPath = expandHome(TEAMAI_CONFIG_PATH);
17485
+ const configPath = expandHome(getUserConfigPath());
17434
17486
  const content = await readFileSafe(configPath);
17435
17487
  if (!content) return null;
17436
17488
  try {
@@ -17447,15 +17499,15 @@ function serializeLocalConfig(config) {
17447
17499
  return YAML14.stringify(persisted);
17448
17500
  }
17449
17501
  async function saveLocalConfig(config) {
17450
- await writeFile(expandHome(TEAMAI_CONFIG_PATH), serializeLocalConfig(config));
17502
+ await writeFile(expandHome(getUserConfigPath()), serializeLocalConfig(config));
17451
17503
  }
17452
17504
  async function loadState() {
17453
- const raw = await readJson(expandHome(TEAMAI_STATE_PATH));
17505
+ const raw = await readJson(expandHome(getUserStatePath()));
17454
17506
  if (!raw) return StateSchema.parse({});
17455
17507
  return StateSchema.parse(raw);
17456
17508
  }
17457
17509
  async function saveState(state) {
17458
- await writeJson(expandHome(TEAMAI_STATE_PATH), state);
17510
+ await writeJson(expandHome(getUserStatePath()), state);
17459
17511
  }
17460
17512
  async function requireInit() {
17461
17513
  const localConfig = await loadLocalConfig();
@@ -17488,8 +17540,19 @@ async function loadLocalConfigForScope(scope, projectRoot) {
17488
17540
  }
17489
17541
  }
17490
17542
  async function saveLocalConfigForScope(config, _scope, _projectRoot) {
17491
- const configPath = path48.join(getDataHome(config), "config.yaml");
17543
+ const dataHome = getDataHome(config);
17544
+ const configPath = path48.join(dataHome, "config.yaml");
17492
17545
  await writeFile(expandHome(configPath), serializeLocalConfig(config));
17546
+ try {
17547
+ if (config.scope === "project" && config.projectRoot) {
17548
+ const partition = await resolveProjectDataHome(config.projectRoot);
17549
+ if (path48.resolve(expandHome(dataHome)) === path48.resolve(partition)) {
17550
+ const anchors = await resolveAnchors(config.projectRoot);
17551
+ if (anchors) await writeAnchorFile(partition, anchors.projectAnchor);
17552
+ }
17553
+ }
17554
+ } catch {
17555
+ }
17493
17556
  }
17494
17557
  async function loadStateForScope(localConfig) {
17495
17558
  const statePath = getStatePath(localConfig);
@@ -18524,8 +18587,7 @@ async function runMigration(plan, opts = {}) {
18524
18587
  await rebaseConfigPaths(path51.join(staging, "config.yaml"), legacyDir, partitionDir);
18525
18588
  await remove(partitionDir);
18526
18589
  await fse10.rename(staging, partitionDir);
18527
- await writeFile(path51.join(partitionDir, "anchor"), `${anchor}
18528
- `);
18590
+ await writeAnchorFile(partitionDir, anchor);
18529
18591
  await releaseLock(lockPath);
18530
18592
  lockReleased = true;
18531
18593
  const backup = await retireLegacy(legacyDir);
@@ -18956,6 +19018,9 @@ async function mirrorLearnings(sourceDir, destinationDir, activeNamespaces) {
18956
19018
  }
18957
19019
  });
18958
19020
  }
19021
+ async function addLearningToCache(sourceFile, destinationDir, relativePath) {
19022
+ await fse11.copy(sourceFile, path54.join(destinationDir, relativePath), { overwrite: true });
19023
+ }
18959
19024
  var init_learnings_mirror = __esm({
18960
19025
  "src/utils/learnings-mirror.ts"() {
18961
19026
  "use strict";
@@ -19180,8 +19245,8 @@ async function recallFeedback(opts) {
19180
19245
  const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
19181
19246
  const { localConfig } = await autoDetectInit2();
19182
19247
  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`);
19248
+ const { getUserVotesDir: getUserVotesDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
19249
+ const votePath = path55.join(getUserVotesDir2(), `${username}.yaml`);
19185
19250
  if (opts.positive) {
19186
19251
  await incrementUpvoted(votePath, [opts.positive]);
19187
19252
  log.success(`Upvoted: ${opts.positive}`);
@@ -20579,8 +20644,8 @@ function computeDailyStatsDelta(current, reported) {
20579
20644
  const bucket = delta[date] ?? emptyDaily();
20580
20645
  if (!previous) {
20581
20646
  bucket.sessionsEnded += 1;
20582
- bucket.sessionsSucceeded += snapshot.succeeded;
20583
20647
  }
20648
+ bucket.sessionsSucceeded += snapshot.succeeded - (previous?.succeeded ?? 0);
20584
20649
  bucket.promptTurns += positiveDelta(snapshot.prompts, previous?.prompts);
20585
20650
  bucket.durationMs += positiveDelta(snapshot.durationMs, previous?.durationMs);
20586
20651
  bucket.sessionsCorrected += positiveDelta(snapshot.corrected, previous?.corrected);
@@ -21539,7 +21604,12 @@ async function writeReportedDailySessions(data) {
21539
21604
  }
21540
21605
  function hasDailyDelta(delta) {
21541
21606
  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
21607
+ (bucket) => (
21608
+ // sessionsSucceeded can be negative (a resumed session that later failed
21609
+ // claws back an earlier increment), so it must not be checked with the
21610
+ // same "> 0" as the other, purely monotonic counters (#473).
21611
+ 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
21612
+ )
21543
21613
  );
21544
21614
  }
21545
21615
  function filterEventsByScope(events, opts) {
@@ -21640,9 +21710,9 @@ async function reportUsageToTeam(repoPath, username, options) {
21640
21710
  filesToPush.push(`stats/${username}.yaml`);
21641
21711
  }
21642
21712
  try {
21643
- if (await pathExists(VOTES_LOCAL_DIR)) {
21713
+ if (await pathExists(getUserVotesDir())) {
21644
21714
  const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
21645
- const synced = await syncVotesToTeam2(writeRoot, username, VOTES_LOCAL_DIR);
21715
+ const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
21646
21716
  if (synced) {
21647
21717
  filesToPush.push(`votes/${username}.yaml`);
21648
21718
  }
@@ -22413,13 +22483,13 @@ async function pullForScope(localConfig, options, policy = {}) {
22413
22483
  if (localConfig.scope === "user") {
22414
22484
  await mirrorLearnings(
22415
22485
  learningsRepoDir,
22416
- LEARNINGS_LOCAL_DIR,
22486
+ getUserLearningsDir(),
22417
22487
  activeLearningsNamespaces
22418
22488
  );
22419
22489
  if (await pathExists(learningsRepoDir)) {
22420
22490
  learningsCount = await countLearnings(learningsRepoDir);
22421
22491
  }
22422
- effectiveLearningsDir = await pathExists(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0;
22492
+ effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
22423
22493
  } else {
22424
22494
  effectiveLearningsDir = await pathExists(learningsRepoDir) ? learningsRepoDir : void 0;
22425
22495
  if (effectiveLearningsDir) {
@@ -23165,6 +23235,10 @@ __export(status_exports, {
23165
23235
  import path66 from "path";
23166
23236
  import YAML21 from "yaml";
23167
23237
  async function status(options) {
23238
+ if (options.all) {
23239
+ await statusAll();
23240
+ return;
23241
+ }
23168
23242
  const { localConfig, teamConfig } = await autoDetectInit();
23169
23243
  const scopeLabel = localConfig.scope;
23170
23244
  console.log("");
@@ -23244,6 +23318,56 @@ async function status(options) {
23244
23318
  }
23245
23319
  console.log("");
23246
23320
  }
23321
+ async function statusAll() {
23322
+ const root = projectsRootDir();
23323
+ const slugs = await listDirs(root);
23324
+ console.log("");
23325
+ log.info(`Project data partitions (${root}):`);
23326
+ if (slugs.length === 0) {
23327
+ log.info(" (none \u2014 no project has been initialized or migrated on this machine)");
23328
+ console.log("");
23329
+ return;
23330
+ }
23331
+ let orphanCount = 0;
23332
+ for (const slug of slugs.sort()) {
23333
+ const partitionDir = path66.join(root, slug);
23334
+ const anchor = await readAnchorFile(partitionDir);
23335
+ let projectPath = anchor;
23336
+ let scope;
23337
+ let kind;
23338
+ const cfgRaw = await readFileSafe(path66.join(partitionDir, "config.yaml"));
23339
+ if (cfgRaw) {
23340
+ try {
23341
+ const parsed = LocalConfigSchema.parse(YAML21.parse(cfgRaw));
23342
+ scope = parsed.scope;
23343
+ kind = parsed.repo.kind;
23344
+ if (!projectPath) projectPath = parsed.repo.businessRepoRoot ?? parsed.projectRoot ?? null;
23345
+ } catch {
23346
+ }
23347
+ }
23348
+ let state;
23349
+ if (!anchor) {
23350
+ state = projectPath ? "unknown \u2014 no anchor; cannot confirm orphan (partition may still be active)" : "unknown (no anchor / project path)";
23351
+ } else if (!await pathExists(anchor)) {
23352
+ state = "ORPHAN \u2014 project path is gone, safe to delete";
23353
+ orphanCount++;
23354
+ } else if (projectSlug(anchor) !== slug) {
23355
+ state = "corrupt \u2014 dir name does not match anchor";
23356
+ } else {
23357
+ state = "active";
23358
+ }
23359
+ const kindLabel = kind ? ` ${kind}` : "";
23360
+ log.info(` ${slug} [${state}]`);
23361
+ log.info(` project: ${projectPath ?? "(unresolved)"}${scope ? ` (${scope}${kindLabel})` : ""}`);
23362
+ }
23363
+ console.log("");
23364
+ if (orphanCount > 0) {
23365
+ log.warn(
23366
+ `${orphanCount} orphan partition(s) found. teamai never deletes them automatically; remove one with: rm -rf "${root}/<slug>"`
23367
+ );
23368
+ console.log("");
23369
+ }
23370
+ }
23247
23371
  async function list(type, options) {
23248
23372
  const { localConfig, teamConfig } = await autoDetectInit();
23249
23373
  const repoPath = localConfig.repo.localPath;
@@ -23440,6 +23564,7 @@ var init_status = __esm({
23440
23564
  init_known_agents();
23441
23565
  init_agent_skills();
23442
23566
  init_types();
23567
+ init_partition();
23443
23568
  init_env();
23444
23569
  init_mcp();
23445
23570
  init_hooks();
@@ -25855,16 +25980,16 @@ async function saveSession(options) {
25855
25980
  }
25856
25981
  if (options.dryRun) {
25857
25982
  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`
25983
+ `[dry-run] Would record session ${sessionId.slice(0, 8)} (${summary.toolTotal} tools, ${summary.interventionCount} interventions, valuable=${summary.valuable}) to ${getSessionLogsDir()}/${monthKey(summary)}.md`
25859
25984
  );
25860
25985
  } else {
25861
- const written = await appendMonthlyLog(SESSION_LOGS_LOCAL_DIR, summary, { includePrompt: true });
25986
+ const written = await appendMonthlyLog(getSessionLogsDir(), summary, { includePrompt: true });
25862
25987
  if (written) {
25863
25988
  log.info(`Recorded session to ${written}`);
25864
25989
  } else {
25865
25990
  log.info(`Session ${sessionId.slice(0, 8)} already recorded this month.`);
25866
25991
  }
25867
- await pruneMonthlyLogs(SESSION_LOGS_LOCAL_DIR, /* @__PURE__ */ new Date()).catch(() => []);
25992
+ await pruneMonthlyLogs(getSessionLogsDir(), /* @__PURE__ */ new Date()).catch(() => []);
25868
25993
  }
25869
25994
  if (!options.push) return;
25870
25995
  if (!summary.valuable && !options.force) {
@@ -28070,7 +28195,7 @@ async function resolveVizRoot(opts) {
28070
28195
  const reportsRoot = getReportsDir(config);
28071
28196
  const useProjectScope = config.scope === "project" && Boolean(config.projectRoot);
28072
28197
  const teamaiHome2 = useProjectScope ? getDataHome(config) : getTeamaiHome("user");
28073
- const learningsDir = useProjectScope ? path81.join(knowledgeRoot, "learnings") : LEARNINGS_LOCAL_DIR;
28198
+ const learningsDir = useProjectScope ? path81.join(knowledgeRoot, "learnings") : getUserLearningsDir();
28074
28199
  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
28200
  return {
28076
28201
  root: knowledgeRoot,
@@ -28082,14 +28207,14 @@ async function resolveVizRoot(opts) {
28082
28207
  source
28083
28208
  };
28084
28209
  }
28085
- const teamaiHome = path81.dirname(VOTES_LOCAL_DIR);
28210
+ const teamaiHome = getTeamaiHomeDir();
28086
28211
  return {
28087
28212
  root: teamaiHome,
28088
28213
  knowledgeRoot: teamaiHome,
28089
- votesDir: VOTES_LOCAL_DIR,
28090
- learningsDir: LEARNINGS_LOCAL_DIR,
28214
+ votesDir: getUserVotesDir(),
28215
+ learningsDir: getUserLearningsDir(),
28091
28216
  statsDir: path81.join(teamaiHome, "stats"),
28092
- indexPath: SEARCH_INDEX_PATH,
28217
+ indexPath: getUserSearchIndexPath(),
28093
28218
  source: { scope: "local", label: "Local ~/.teamai \xB7 your recalls only" }
28094
28219
  };
28095
28220
  }
@@ -30048,8 +30173,8 @@ var init_hook_handlers = __esm({
30048
30173
  const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
30049
30174
  const voteData = await parseTranscriptForVotes2(transcriptPath);
30050
30175
  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;
30176
+ const { getUserVotesDir: getUserVotesDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
30177
+ const votesDir = getUserVotesDir2();
30053
30178
  const votePath = path90.join(votesDir, `${localConfig.username}.yaml`);
30054
30179
  const recalledSet = new Set(voteData.recalledDocIds);
30055
30180
  const verifiedDocIds = voteData.referencedDocIds.filter((id) => recalledSet.has(id));
@@ -30323,8 +30448,8 @@ async function rebuildIndexAfterContribute(localConfig) {
30323
30448
  localConfig.projects ?? []
30324
30449
  );
30325
30450
  if (localConfig.scope === "user") {
30326
- await mirrorLearnings(learningsRepoDir, LEARNINGS_LOCAL_DIR, activeLearningsNamespaces);
30327
- effectiveLearningsDir = await pathExists(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0;
30451
+ await mirrorLearnings(learningsRepoDir, getUserLearningsDir(), activeLearningsNamespaces);
30452
+ effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
30328
30453
  } else {
30329
30454
  effectiveLearningsDir = await pathExists(learningsRepoDir) ? learningsRepoDir : void 0;
30330
30455
  }
@@ -30454,12 +30579,11 @@ async function contributeSelf(localConfig, content, options) {
30454
30579
  await fs30.promises.writeFile(destAbs, content, "utf-8");
30455
30580
  try {
30456
30581
  const { pathExists: pathExists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
30457
- const wtLearnings = path91.join(wtRepo, "learnings");
30458
30582
  const activeLearningsNamespaces = await resolveActiveLearningsNamespaces(
30459
30583
  localConfig.repo.localPath,
30460
30584
  localConfig.projects ?? []
30461
30585
  );
30462
- await mirrorLearnings(wtLearnings, LEARNINGS_LOCAL_DIR, activeLearningsNamespaces);
30586
+ await addLearningToCache(destAbs, getUserLearningsDir(), relPath.slice("learnings/".length));
30463
30587
  const repoPath = localConfig.repo.localPath;
30464
30588
  const docsDir = path91.join(repoPath, "docs");
30465
30589
  const rulesDir = path91.join(repoPath, "rules");
@@ -30474,7 +30598,7 @@ async function contributeSelf(localConfig, content, options) {
30474
30598
  const teamaiHome = getDataHome(localConfig);
30475
30599
  const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
30476
30600
  await buildIndex2({
30477
- learningsDir: await pathExists3(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0,
30601
+ learningsDir: await pathExists3(getUserLearningsDir()) ? getUserLearningsDir() : void 0,
30478
30602
  learningsNamespaces: activeLearningsNamespaces,
30479
30603
  docsDir: await pathExists3(docsDir) ? docsDir : void 0,
30480
30604
  rulesDir: await pathExists3(rulesDir) ? rulesDir : void 0,
@@ -36050,6 +36174,9 @@ var init_reconciler_v2_types = __esm({
36050
36174
  // src/wiki-engine/knowledge-reconciler.ts
36051
36175
  import { readFile as readFile9, readdir as readdir3, stat as stat5 } from "fs/promises";
36052
36176
  import path109 from "path";
36177
+ function isExpectedSelfLoop(edge) {
36178
+ return edge.from === edge.to && edge.relation === "IMPLEMENTS" && !!edge.source && REPAIRABLE_CODE_EDGE_SOURCES.has(edge.source);
36179
+ }
36053
36180
  async function loadReconciliationBase(wikiRoot) {
36054
36181
  const graphPath = path109.join(wikiRoot, ".indices", "graph-index.json");
36055
36182
  const graph = await loadGraphIndex(wikiRoot);
@@ -36061,7 +36188,10 @@ async function loadReconciliationBase(wikiRoot) {
36061
36188
  graph.nodes,
36062
36189
  graph.edges.filter((edge) => edge.source !== BRIDGE_EDGE_SOURCE)
36063
36190
  );
36064
- if (validateGraph(base).issues.some((issue) => issue.code !== REPAIRABLE_VALIDATION_ISSUE_CODE)) {
36191
+ const hasUnexpectedSelfLoop = base.edges.some((edge) => edge.from === edge.to && !isExpectedSelfLoop(edge));
36192
+ if (hasUnexpectedSelfLoop || validateGraph(base).issues.some(
36193
+ (issue) => issue.code !== REPAIRABLE_VALIDATION_ISSUE_CODE && issue.code !== SELF_LOOP_VALIDATION_ISSUE_CODE
36194
+ )) {
36065
36195
  throw new Error(`Cannot reconcile invalid graph index at ${graphPath}`);
36066
36196
  }
36067
36197
  const nodeSlugs = new Set(base.nodes.map((node) => node.slug));
@@ -36082,7 +36212,7 @@ async function loadReconciliationBase(wikiRoot) {
36082
36212
  }
36083
36213
  }
36084
36214
  const repaired = mergeGraphs(base, createGraphIndex(endpointNodes));
36085
- if (!validateGraph(repaired).valid) {
36215
+ if (validateGraph(repaired).issues.some((issue) => issue.code !== SELF_LOOP_VALIDATION_ISSUE_CODE)) {
36086
36216
  throw new Error(`Cannot reconcile invalid graph index at ${graphPath}`);
36087
36217
  }
36088
36218
  return repaired;
@@ -36406,7 +36536,7 @@ async function reconcileKnowledge(options) {
36406
36536
  stats
36407
36537
  };
36408
36538
  }
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;
36539
+ 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
36540
  var init_knowledge_reconciler = __esm({
36411
36541
  "src/wiki-engine/knowledge-reconciler.ts"() {
36412
36542
  "use strict";
@@ -36421,6 +36551,7 @@ var init_knowledge_reconciler = __esm({
36421
36551
  REPAIRABLE_CODE_EDGE_SOURCES = /* @__PURE__ */ new Set(["code-ast", "code-heuristic"]);
36422
36552
  MISSING_PATH_ERROR_CODE = "ENOENT";
36423
36553
  REPAIRABLE_VALIDATION_ISSUE_CODE = "edge.missing_node";
36554
+ SELF_LOOP_VALIDATION_ISSUE_CODE = "edge.self_loop";
36424
36555
  }
36425
36556
  });
36426
36557
 
@@ -40464,7 +40595,7 @@ async function codebaseCmd(opts) {
40464
40595
  await printCodebaseStatus(opts);
40465
40596
  return;
40466
40597
  }
40467
- if (!opts.lint && !opts.reconcile) {
40598
+ if (!opts.lint && !opts.reconcile && !opts.deepEnrich) {
40468
40599
  console.log("teamai codebase \u2014 team codebase knowledge management");
40469
40600
  console.log("");
40470
40601
  console.log("Usage:");
@@ -40474,6 +40605,7 @@ async function codebaseCmd(opts) {
40474
40605
  console.log(" teamai codebase --lint --json Output JSON report (for CI)");
40475
40606
  console.log(" teamai codebase --lint --severity high Only report high-severity issues");
40476
40607
  console.log(" teamai codebase --reconcile Reconcile product and code knowledge");
40608
+ console.log(" teamai codebase --deep-enrich Generate deep knowledge from extracted evidence");
40477
40609
  console.log(" teamai codebase --status Show knowledge-base git baseline");
40478
40610
  return;
40479
40611
  }
@@ -40492,7 +40624,58 @@ async function codebaseCmd(opts) {
40492
40624
  }
40493
40625
  if (!await pathExists3(teamwikiDir)) {
40494
40626
  console.log("No teamwiki found. Run `teamai import` first.");
40495
- if (opts.reconcile) process.exitCode = 1;
40627
+ if (opts.reconcile || opts.deepEnrich) process.exitCode = 1;
40628
+ return;
40629
+ }
40630
+ if (opts.deepEnrich) {
40631
+ const project = opts.project?.trim() ?? "";
40632
+ if (!project) {
40633
+ console.log("Deep enrichment requires --project <slug>.");
40634
+ process.exitCode = 1;
40635
+ return;
40636
+ }
40637
+ const { assertWithinRoot: assertWithinRoot2 } = await Promise.resolve().then(() => (init_path_safety(), path_safety_exports));
40638
+ const codeRoot = path125.join(teamwikiDir, "evidence", "code");
40639
+ const evidenceDir = path125.join(codeRoot, project);
40640
+ try {
40641
+ assertWithinRoot2(codeRoot, evidenceDir);
40642
+ } catch (e) {
40643
+ console.log(e.message);
40644
+ process.exitCode = 1;
40645
+ return;
40646
+ }
40647
+ if (!await pathExists3(evidenceDir)) {
40648
+ console.log(`No extracted evidence found for project "${project}". Run \`teamai codebase --extract\` first.`);
40649
+ process.exitCode = 1;
40650
+ return;
40651
+ }
40652
+ let componentCount = 0;
40653
+ try {
40654
+ const manifest = JSON.parse(await readFile15(path125.join(evidenceDir, "_manifest.json"), "utf-8"));
40655
+ componentCount = Array.isArray(manifest.components) ? manifest.components.length : 0;
40656
+ } catch {
40657
+ componentCount = 0;
40658
+ }
40659
+ if (componentCount === 0) {
40660
+ console.log(`No components in evidence for project "${project}". Run \`teamai codebase --extract\` first.`);
40661
+ process.exitCode = 1;
40662
+ return;
40663
+ }
40664
+ if (opts.dryRun) {
40665
+ if (opts.json) {
40666
+ console.log(JSON.stringify({ project, evidenceDir, dryRun: true }, null, 2));
40667
+ } else {
40668
+ console.log(`Deep enrichment preview: project=${project} (no writes)`);
40669
+ }
40670
+ return;
40671
+ }
40672
+ const { deepEnrich: deepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
40673
+ await deepEnrich2({ project, evidenceDir, wikiRoot: teamwikiDir });
40674
+ if (opts.json) {
40675
+ console.log(JSON.stringify({ project, evidenceDir, complete: true }, null, 2));
40676
+ } else {
40677
+ console.log(`Deep enrichment complete: project=${project}`);
40678
+ }
40496
40679
  return;
40497
40680
  }
40498
40681
  if (opts.reconcile) {
@@ -41699,10 +41882,10 @@ program.command("pull").description("Pull team resources and inject into local A
41699
41882
  const { pull: pull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
41700
41883
  await pull2({ ...globalOpts, ...cmdOpts });
41701
41884
  });
41702
- program.command("status").description("Show local vs team repo diff").action(async () => {
41885
+ 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
41886
  const globalOpts = program.opts();
41704
41887
  const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
41705
- await status2(globalOpts);
41888
+ await status2({ ...globalOpts, ...cmdOpts });
41706
41889
  });
41707
41890
  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
41891
  const globalOpts = program.opts();
@@ -42087,7 +42270,7 @@ program.command("mr-hint", { hidden: true }).description("Hint AI about recently
42087
42270
  await mrHint2();
42088
42271
  }
42089
42272
  });
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) => {
42273
+ 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
42274
  const globalOpts = program.opts();
42092
42275
  const { codebaseCmd: codebaseCmd2 } = await Promise.resolve().then(() => (init_codebase_cmd(), codebase_cmd_exports));
42093
42276
  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.5",
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. |