teamai-cli 0.24.0-beta.8 → 0.24.0-beta.9

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
@@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file. See [standa
11
11
 
12
12
  ### 🐛 Bug Fixes
13
13
 
14
+ - The GitHub and CNB providers resolve their CLI to a launchable absolute path and start it through cross-spawn, so on Windows they no longer answer "installed" while every call fails silently ([#520](https://github.com/Tencent/teamai-cli/pull/520)).
15
+ - `enabledAgents` now also gates CLI builtin deploy, CLAUDE.md-class injects, and last-pull skip-sync targets, so an already-installed tool outside the whitelist is not written to ([#510](https://github.com/Tencent/teamai-cli/issues/510)).
14
16
  - `teamai status` counts rule files in subdirectories recursively ([#437](https://github.com/Tencent/teamai-cli/pull/437)).
15
17
  - Codex Stop-phase contribution hints are deferred to the next prompt, so the host no longer rejects `additionalContext` ([#441](https://github.com/Tencent/teamai-cli/pull/441)).
16
18
 
package/README.md CHANGED
@@ -199,6 +199,8 @@ teamai codebase --reconcile --output /path/to/repo # map product docs to code pa
199
199
  teamai codebase --lint --output /path/to/repo # check the locally extracted graph
200
200
  ```
201
201
 
202
+ Extract writes `teamwiki/evidence/code/<project>/_manifest.json` even when AI enrichment is skipped or produces nothing, so `--deep-enrich` can start.
203
+
202
204
  The graph stores components, interfaces, configs, and cross-repo import edges. `teamai recall` uses it for graph-boosted re-ranking.
203
205
  When a recall hit comes from a codebase page, the result includes a `Sources:` line listing the relevant source file paths — giving agents a direct starting point for code changes instead of re-exploring the repo.
204
206
 
package/README.zh-CN.md CHANGED
@@ -199,6 +199,8 @@ teamai codebase --reconcile --output /path/to/repo # 将产品文档映射到代
199
199
  teamai codebase --lint --output /path/to/repo # 检查本地提取的图谱
200
200
  ```
201
201
 
202
+ 只要 extract 发现了组件,就会写入 `teamwiki/evidence/code/<project>/_manifest.json`(包括跳过 AI 增强或增强没有产出的情况),因此 `--deep-enrich` 可以接着跑。
203
+
202
204
  图谱存储组件、接口、配置和跨仓库依赖边。`teamai recall` 利用图谱进行增强排名。
203
205
  当召回命中 codebase 页面时,结果会附带一行 `Sources:`,列出相关源文件路径,供 agent 直接作为代码改动的入口,无需重新探索代码库。
204
206
 
package/dist/index.js CHANGED
@@ -171,6 +171,7 @@ __export(fs_exports, {
171
171
  listFiles: () => listFiles,
172
172
  listFilesRecursive: () => listFilesRecursive,
173
173
  pathExists: () => pathExists,
174
+ pruneEmptyDirs: () => pruneEmptyDirs,
174
175
  readFileSafe: () => readFileSafe,
175
176
  readJson: () => readJson,
176
177
  remove: () => remove,
@@ -255,6 +256,32 @@ async function copyDir(src, dest) {
255
256
  filter: (srcPath) => !isIgnored(path2.basename(srcPath))
256
257
  });
257
258
  }
259
+ async function pruneEmptyDirs(target) {
260
+ const expanded = expandHome(target);
261
+ let entries;
262
+ try {
263
+ const stat8 = await fse.lstat(expanded);
264
+ if (!stat8.isDirectory() || stat8.isSymbolicLink()) return false;
265
+ entries = await fse.readdir(expanded, { withFileTypes: true });
266
+ } catch {
267
+ return false;
268
+ }
269
+ let empty = true;
270
+ for (const entry of entries) {
271
+ if (entry.isDirectory()) {
272
+ if (!await pruneEmptyDirs(path2.join(expanded, entry.name))) empty = false;
273
+ } else {
274
+ empty = false;
275
+ }
276
+ }
277
+ if (!empty) return false;
278
+ try {
279
+ await fse.rmdir(expanded);
280
+ return true;
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
258
285
  async function copyFile(src, dest) {
259
286
  const destExpanded = expandHome(dest);
260
287
  await fse.ensureDir(path2.dirname(destExpanded));
@@ -3215,7 +3242,7 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
3215
3242
  log.debug(`Skipping built-in rules for ${tool}: tool not installed`);
3216
3243
  continue;
3217
3244
  }
3218
- if (localConfig && isAgentDisabled(localConfig, tool)) continue;
3245
+ if (localConfig && isAgentExcluded(localConfig, tool)) continue;
3219
3246
  const rulesDir = path14.join(baseDir, toolPath.rules);
3220
3247
  if (!await pathExists(rulesDir)) continue;
3221
3248
  try {
@@ -3454,7 +3481,7 @@ var init_pre_push_sync = __esm({
3454
3481
  });
3455
3482
 
3456
3483
  // src/providers/types.ts
3457
- var RepoNotFoundError;
3484
+ var RepoNotFoundError, OrganizationNotFoundError, RepoCreatePermissionError;
3458
3485
  var init_types2 = __esm({
3459
3486
  "src/providers/types.ts"() {
3460
3487
  "use strict";
@@ -3464,6 +3491,26 @@ var init_types2 = __esm({
3464
3491
  this.name = "RepoNotFoundError";
3465
3492
  }
3466
3493
  };
3494
+ OrganizationNotFoundError = class extends Error {
3495
+ org;
3496
+ createUrl;
3497
+ constructor(org, createUrl) {
3498
+ super(`Organization "${org}" not found.`);
3499
+ this.name = "OrganizationNotFoundError";
3500
+ this.org = org;
3501
+ this.createUrl = createUrl;
3502
+ }
3503
+ };
3504
+ RepoCreatePermissionError = class extends Error {
3505
+ repo;
3506
+ createUrl;
3507
+ constructor(repo, createUrl) {
3508
+ super(`No permission to create repo "${repo}".`);
3509
+ this.name = "RepoCreatePermissionError";
3510
+ this.repo = repo;
3511
+ this.createUrl = createUrl;
3512
+ }
3513
+ };
3467
3514
  }
3468
3515
  });
3469
3516
 
@@ -4026,20 +4073,82 @@ var init_tgit = __esm({
4026
4073
  }
4027
4074
  });
4028
4075
 
4029
- // src/providers/github/gh-cli.ts
4030
- import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
4031
- function getGhPath() {
4076
+ // src/utils/cli-path.ts
4077
+ import { execFileSync } from "child_process";
4078
+ import { existsSync } from "fs";
4079
+ function pickWindowsCommand(whereOutput) {
4080
+ const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
4081
+ for (const ext of WIN_EXEC_EXTENSIONS) {
4082
+ const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
4083
+ if (hit !== void 0) return hit;
4084
+ }
4085
+ return null;
4086
+ }
4087
+ function whereOnWindows(cmd) {
4032
4088
  try {
4033
- const which = execSync2("which gh", {
4034
- encoding: "utf-8",
4035
- stdio: ["pipe", "pipe", "pipe"]
4089
+ const out = execFileSync("where", [cmd], {
4090
+ encoding: "utf8",
4091
+ stdio: ["ignore", "pipe", "ignore"],
4092
+ shell: false,
4093
+ timeout: CLI_DETECT_TIMEOUT_MS
4036
4094
  });
4037
- const trimmed = which.trim();
4038
- return trimmed || null;
4095
+ const p = pickWindowsCommand(out);
4096
+ return p !== null && existsSync(p) ? p : null;
4039
4097
  } catch {
4040
4098
  return null;
4041
4099
  }
4042
4100
  }
4101
+ function whichOnPosix(cmd) {
4102
+ try {
4103
+ const p = execFileSync("bash", ["-lc", `command -v ${cmd}`], {
4104
+ encoding: "utf8",
4105
+ stdio: ["ignore", "pipe", "ignore"],
4106
+ shell: false,
4107
+ timeout: CLI_DETECT_TIMEOUT_MS
4108
+ }).trim();
4109
+ if (p && existsSync(p)) return p;
4110
+ } catch {
4111
+ }
4112
+ try {
4113
+ const p = execFileSync("zsh", ["-lc", `command -v ${cmd}`], {
4114
+ encoding: "utf8",
4115
+ stdio: ["ignore", "pipe", "ignore"],
4116
+ shell: false,
4117
+ timeout: CLI_DETECT_TIMEOUT_MS
4118
+ }).trim();
4119
+ if (p && existsSync(p)) return p;
4120
+ } catch {
4121
+ }
4122
+ try {
4123
+ const p = execFileSync("which", [cmd], {
4124
+ encoding: "utf8",
4125
+ stdio: ["ignore", "pipe", "ignore"],
4126
+ shell: false,
4127
+ timeout: CLI_DETECT_TIMEOUT_MS
4128
+ }).trim();
4129
+ if (p && existsSync(p)) return p;
4130
+ } catch {
4131
+ }
4132
+ return null;
4133
+ }
4134
+ function resolveCliPath(cmd, platform = process.platform) {
4135
+ return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
4136
+ }
4137
+ var CLI_DETECT_TIMEOUT_MS, WIN_EXEC_EXTENSIONS;
4138
+ var init_cli_path = __esm({
4139
+ "src/utils/cli-path.ts"() {
4140
+ "use strict";
4141
+ CLI_DETECT_TIMEOUT_MS = 5e3;
4142
+ WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
4143
+ }
4144
+ });
4145
+
4146
+ // src/providers/github/gh-cli.ts
4147
+ import { spawnSync as spawnSync2 } from "child_process";
4148
+ import crossSpawn from "cross-spawn";
4149
+ function getGhPath() {
4150
+ return resolveCliPath("gh");
4151
+ }
4043
4152
  function isGhInstalled() {
4044
4153
  return getGhPath() !== null;
4045
4154
  }
@@ -4052,14 +4161,14 @@ function ghExec(args, options) {
4052
4161
  }
4053
4162
  log.debug(`gh exec: ${ghPath} ${args.join(" ")}`);
4054
4163
  if (options?.inheritStdio) {
4055
- const result2 = spawnSync2(ghPath, args, {
4164
+ const result2 = crossSpawn.sync(ghPath, args, {
4056
4165
  stdio: "inherit",
4057
4166
  env: { ...process.env, ...options.env ?? {} },
4058
4167
  cwd: options.cwd
4059
4168
  });
4060
4169
  return { stdout: "", stderr: "", status: result2.status ?? 1 };
4061
4170
  }
4062
- const result = spawnSync2(ghPath, args, {
4171
+ const result = crossSpawn.sync(ghPath, args, {
4063
4172
  env: { ...process.env, ...options?.env ?? {} },
4064
4173
  encoding: "utf-8",
4065
4174
  maxBuffer: 10 * 1024 * 1024,
@@ -4307,6 +4416,7 @@ var init_gh_cli = __esm({
4307
4416
  "src/providers/github/gh-cli.ts"() {
4308
4417
  "use strict";
4309
4418
  init_logger();
4419
+ init_cli_path();
4310
4420
  GITHUB_API = "https://api.github.com";
4311
4421
  RepoNotFoundError3 = class extends Error {
4312
4422
  constructor(repo) {
@@ -4569,14 +4679,20 @@ var init_github = __esm({
4569
4679
  });
4570
4680
 
4571
4681
  // src/providers/cnb/cnb-cli.ts
4572
- import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
4682
+ import { execSync as execSync2, spawnSync as spawnSync3 } from "child_process";
4683
+ import crossSpawn2 from "cross-spawn";
4573
4684
  function cnbExec(args, options) {
4574
- log.debug(`cnb exec: cnb ${args.join(" ")}`);
4685
+ const cnbPath = resolveCliPath("cnb");
4686
+ if (!cnbPath) {
4687
+ log.debug("cnb CLI not found on PATH");
4688
+ return { stdout: "", stderr: "cnb CLI not found on PATH", status: 127 };
4689
+ }
4690
+ log.debug(`cnb exec: ${cnbPath} ${args.join(" ")}`);
4575
4691
  if (options?.inheritStdio) {
4576
- const r2 = spawnSync3("cnb", args, { stdio: "inherit", env: { ...process.env }, cwd: options.cwd });
4692
+ const r2 = crossSpawn2.sync(cnbPath, args, { stdio: "inherit", env: { ...process.env }, cwd: options.cwd });
4577
4693
  return { stdout: "", stderr: "", status: r2.status ?? 1 };
4578
4694
  }
4579
- const r = spawnSync3("cnb", args, {
4695
+ const r = crossSpawn2.sync(cnbPath, args, {
4580
4696
  env: { ...process.env },
4581
4697
  encoding: "utf-8",
4582
4698
  maxBuffer: 10 * 1024 * 1024,
@@ -4598,12 +4714,7 @@ function assertCnbApiOk(out, action) {
4598
4714
  }
4599
4715
  }
4600
4716
  function isCnbInstalled() {
4601
- try {
4602
- execSync3("which cnb", { stdio: ["pipe", "pipe", "pipe"] });
4603
- return true;
4604
- } catch {
4605
- return false;
4606
- }
4717
+ return resolveCliPath("cnb") !== null;
4607
4718
  }
4608
4719
  async function ensureCnbInstalled() {
4609
4720
  if (isCnbInstalled()) {
@@ -4612,7 +4723,7 @@ async function ensureCnbInstalled() {
4612
4723
  }
4613
4724
  const spin = spinner("Installing cnb CLI (@cnbcool/cnb-cli)...").start();
4614
4725
  try {
4615
- execSync3("npm install -g @cnbcool/cnb-cli", { stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
4726
+ execSync2("npm install -g @cnbcool/cnb-cli", { stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
4616
4727
  if (!isCnbInstalled()) throw new Error("cnb not found on PATH after install");
4617
4728
  spin.succeed("cnb CLI installed");
4618
4729
  } catch (e) {
@@ -4646,7 +4757,7 @@ function cnbWhoami() {
4646
4757
  }
4647
4758
  function cnbLogin() {
4648
4759
  log.info("Starting cnb authentication (OAuth2 device flow)...");
4649
- const r = cnbExec(["login"], { inheritStdio: true });
4760
+ const r = cnbExec(["login", "--host", CNB_HOST], { inheritStdio: true });
4650
4761
  if (r.status !== 0) throw new Error("cnb login failed. Please try again.");
4651
4762
  }
4652
4763
  function ensureCnbAuthenticated() {
@@ -4693,13 +4804,47 @@ function cnbRepoClone(repo, localPath) {
4693
4804
  const sanitized = out.replace(/cnb:[^@]+@/g, "cnb:***@").trim();
4694
4805
  throw new Error(`git clone failed: ${sanitized}`);
4695
4806
  }
4807
+ if (!token) {
4808
+ const cfg = spawnSync3("git", ["config", "--local", "credential.helper", "!cnb git-credential"], {
4809
+ encoding: "utf-8",
4810
+ stdio: ["pipe", "pipe", "pipe"],
4811
+ cwd: localPath
4812
+ });
4813
+ if (cfg.status !== 0) {
4814
+ log.warn(`Could not persist CNB credential helper: ${(cfg.stderr ?? "").trim()}. Push/pull may prompt for credentials.`);
4815
+ }
4816
+ }
4817
+ }
4818
+ function cnbOrganizationCreateUrl() {
4819
+ return `https://${CNB_HOST}/new/groups`;
4820
+ }
4821
+ function cnbRepoCreateUrl() {
4822
+ return `https://${CNB_HOST}/new/repos`;
4823
+ }
4824
+ function cnbOrganizationExists(org) {
4825
+ const r = cnbExec(["organizations", "get-group", "--group", org]);
4826
+ const out = r.stdout || r.stderr;
4827
+ if (/(?:^|["\s])status["\s:]+\s*200\b/.test(out)) return true;
4828
+ if (/(?:^|["\s])status["\s:]+\s*404\b/.test(out) || /not found|不存在/i.test(out)) return false;
4829
+ throw new Error(`cnb get-group failed for "${org}": ${out || `exit ${r.status}`}`);
4696
4830
  }
4697
4831
  async function cnbCreateRepo(owner, repo) {
4698
- const r = cnbExec(["repositories", "create-repo", "--slug", owner, "--name", repo]);
4699
- if (r.status !== 0) {
4700
- throw new Error(`cnb create-repo failed: ${r.stderr || r.stdout}`);
4832
+ try {
4833
+ const r = cnbExec(["repositories", "create-repo", "--slug", owner, "--name", repo]);
4834
+ if (r.status !== 0) {
4835
+ throw new Error(`cnb create-repo failed: ${r.stderr || r.stdout}`);
4836
+ }
4837
+ assertCnbApiOk(r.stdout, "create-repo");
4838
+ } catch (e) {
4839
+ const msg = e.message;
4840
+ if (/HTTP 404|not found|不存在/i.test(msg)) {
4841
+ throw new OrganizationNotFoundError(owner, cnbOrganizationCreateUrl());
4842
+ }
4843
+ if (/HTTP 403|scope|permission|forbidden|权限/i.test(msg)) {
4844
+ throw new RepoCreatePermissionError(`${owner}/${repo}`, cnbRepoCreateUrl());
4845
+ }
4846
+ throw e;
4701
4847
  }
4702
- assertCnbApiOk(r.stdout, "create-repo");
4703
4848
  }
4704
4849
  function cnbPullCreate(opts) {
4705
4850
  const args = [
@@ -4732,6 +4877,8 @@ var init_cnb_cli = __esm({
4732
4877
  "src/providers/cnb/cnb-cli.ts"() {
4733
4878
  "use strict";
4734
4879
  init_logger();
4880
+ init_cli_path();
4881
+ init_types2();
4735
4882
  CNB_HOST = process.env.TEAMAI_CNB_HOST?.trim() || "cnb.cool";
4736
4883
  CnbRepoNotFoundError = class extends Error {
4737
4884
  constructor(repo) {
@@ -4781,6 +4928,12 @@ var init_cnb = __esm({
4781
4928
  async createRepo(owner, repo) {
4782
4929
  await cnbCreateRepo(owner, repo);
4783
4930
  }
4931
+ organizationExists(org) {
4932
+ return cnbOrganizationExists(org);
4933
+ }
4934
+ getOrganizationCreateUrl() {
4935
+ return cnbOrganizationCreateUrl();
4936
+ }
4784
4937
  async createPullRequest(opts) {
4785
4938
  return cnbPullCreate({
4786
4939
  repo: opts.repo,
@@ -6320,6 +6473,8 @@ __export(providers_exports, {
6320
6473
  GitCodeProvider: () => GitCodeProvider,
6321
6474
  GitHubProvider: () => GitHubProvider,
6322
6475
  GitLabProvider: () => GitLabProvider,
6476
+ OrganizationNotFoundError: () => OrganizationNotFoundError,
6477
+ RepoCreatePermissionError: () => RepoCreatePermissionError,
6323
6478
  RepoNotFoundError: () => RepoNotFoundError,
6324
6479
  TGitProvider: () => TGitProvider,
6325
6480
  detectProvider: () => detectProvider,
@@ -6394,7 +6549,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
6394
6549
  log.debug(`Skipping built-in skill deployment for ${tool}: tool not installed`);
6395
6550
  continue;
6396
6551
  }
6397
- if (localConfig && isAgentDisabled(localConfig, tool)) continue;
6552
+ if (localConfig && isAgentExcluded(localConfig, tool)) continue;
6398
6553
  for (const skillName of skillNames) {
6399
6554
  const srcDir = path18.join(builtinDir, skillName);
6400
6555
  const destDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, skillName, srcDir);
@@ -6740,7 +6895,7 @@ var init_session_id = __esm({
6740
6895
 
6741
6896
  // src/pid-monitor.ts
6742
6897
  import fs9 from "fs";
6743
- import { execSync as execSync4 } from "child_process";
6898
+ import { execSync as execSync3 } from "child_process";
6744
6899
  function getParentPid(pid) {
6745
6900
  try {
6746
6901
  const stat8 = fs9.readFileSync(`/proc/${pid}/stat`, "utf-8");
@@ -6752,7 +6907,7 @@ function getParentPid(pid) {
6752
6907
  } catch {
6753
6908
  }
6754
6909
  try {
6755
- const out = execSync4(`ps -o ppid= -p ${pid}`, {
6910
+ const out = execSync3(`ps -o ppid= -p ${pid}`, {
6756
6911
  encoding: "utf-8",
6757
6912
  timeout: 2e3,
6758
6913
  stdio: ["pipe", "pipe", "pipe"]
@@ -6769,7 +6924,7 @@ function getProcessComm(pid) {
6769
6924
  } catch {
6770
6925
  }
6771
6926
  try {
6772
- const out = execSync4(`ps -o comm= -p ${pid}`, {
6927
+ const out = execSync3(`ps -o comm= -p ${pid}`, {
6773
6928
  encoding: "utf-8",
6774
6929
  timeout: 2e3,
6775
6930
  stdio: ["pipe", "pipe", "pipe"]
@@ -7878,7 +8033,7 @@ var init_agent_version = __esm({
7878
8033
  // src/machine-id.ts
7879
8034
  import crypto2 from "crypto";
7880
8035
  import fs11 from "fs";
7881
- import { execFileSync } from "child_process";
8036
+ import { execFileSync as execFileSync2 } from "child_process";
7882
8037
  import os6 from "os";
7883
8038
  function getMachineId() {
7884
8039
  if (cachedMachineId !== null) return cachedMachineId;
@@ -7904,7 +8059,7 @@ function detectMachineId(platform = process.platform) {
7904
8059
  return id || os6.hostname() || "";
7905
8060
  }
7906
8061
  function readDarwinMachineId() {
7907
- const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
8062
+ const out = execFileSync2("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
7908
8063
  encoding: "utf-8",
7909
8064
  timeout: 3e3
7910
8065
  });
@@ -7912,7 +8067,7 @@ function readDarwinMachineId() {
7912
8067
  return match ? match[1].trim() : "";
7913
8068
  }
7914
8069
  function readWindowsMachineId() {
7915
- const out = execFileSync(
8070
+ const out = execFileSync2(
7916
8071
  "reg",
7917
8072
  ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
7918
8073
  { encoding: "utf-8", timeout: 3e3 }
@@ -11765,9 +11920,16 @@ async function scanTeamRepoNamespaces(repoPath) {
11765
11920
  for (const dir of topDirs) {
11766
11921
  const dirPath = path29.join(teamSkillsDir, dir);
11767
11922
  const hasSkillMd = await pathExists(path29.join(dirPath, "SKILL.md"));
11768
- if (!hasSkillMd) {
11769
- namespaces.push(dir);
11923
+ if (hasSkillMd) continue;
11924
+ const subDirs = await listDirs(dirPath);
11925
+ let holdsSkill = false;
11926
+ for (const subDir of subDirs) {
11927
+ if (await pathExists(path29.join(dirPath, subDir, "SKILL.md"))) {
11928
+ holdsSkill = true;
11929
+ break;
11930
+ }
11770
11931
  }
11932
+ if (holdsSkill) namespaces.push(dir);
11771
11933
  }
11772
11934
  return namespaces;
11773
11935
  }
@@ -13182,7 +13344,7 @@ async function deployBuiltinAgents(teamConfig, localConfig, options) {
13182
13344
  log.debug(`Skipping built-in agent deployment for ${tool}: tool not installed`);
13183
13345
  continue;
13184
13346
  }
13185
- if (localConfig && isAgentDisabled(localConfig, tool)) continue;
13347
+ if (localConfig && isAgentExcluded(localConfig, tool)) continue;
13186
13348
  if (!ALL_SUPPORTED_TOOLS.includes(tool)) {
13187
13349
  log.warn(
13188
13350
  `Skipping built-in agent deployment for ${tool}: unsupported agent format; disable this target or add a native renderer`
@@ -15700,6 +15862,15 @@ async function initSelfRepo(options) {
15700
15862
  log.info(" 4. Push your business repo (e.g. `git push -u origin HEAD`) so teammates get the .teamai/ knowledge and are auto-initialized on clone.");
15701
15863
  closePrompt();
15702
15864
  }
15865
+ function guideWebCreation(kind, name, url) {
15866
+ log.info(`${kind} "${name}" can't be created from the CLI (insufficient token permission).`);
15867
+ if (url) {
15868
+ log.info(`Create it here, then re-run this command:`);
15869
+ log.info(` ${url}`);
15870
+ } else {
15871
+ log.info(`Create the ${kind} on the platform, then re-run this command.`);
15872
+ }
15873
+ }
15703
15874
  async function init(options) {
15704
15875
  if (options.http) {
15705
15876
  return initHttp(options.http, options);
@@ -15851,6 +16022,23 @@ async function init(options) {
15851
16022
  } catch (e) {
15852
16023
  if (e instanceof RepoNotFoundError) {
15853
16024
  cloneSpin.info(`Repo ${repoInfo.owner}/${repoInfo.repo} does not exist`);
16025
+ if (typeof provider.organizationExists === "function") {
16026
+ let orgMissing = false;
16027
+ try {
16028
+ orgMissing = !provider.organizationExists(repoInfo.owner);
16029
+ } catch (checkErr) {
16030
+ log.debug(`Organization check failed: ${checkErr.message}`);
16031
+ }
16032
+ if (orgMissing) {
16033
+ cloneSpin.info(`Organization "${repoInfo.owner}" does not exist`);
16034
+ guideWebCreation(
16035
+ "organization",
16036
+ repoInfo.owner,
16037
+ provider.getOrganizationCreateUrl?.() ?? null
16038
+ );
16039
+ process.exit(1);
16040
+ }
16041
+ }
15854
16042
  const confirmed = await askConfirmation(
15855
16043
  `Create repo ${repoInfo.owner}/${repoInfo.repo}? [Y/n] `,
15856
16044
  true
@@ -15864,6 +16052,16 @@ async function init(options) {
15864
16052
  await provider.createRepo(repoInfo.owner, repoInfo.repo);
15865
16053
  createSpin.succeed(`Repo ${repoInfo.owner}/${repoInfo.repo} created`);
15866
16054
  } catch (ce) {
16055
+ if (ce instanceof OrganizationNotFoundError) {
16056
+ createSpin.fail(`Organization "${ce.org}" does not exist`);
16057
+ guideWebCreation("organization", ce.org, ce.createUrl ?? null);
16058
+ process.exit(1);
16059
+ }
16060
+ if (ce instanceof RepoCreatePermissionError) {
16061
+ createSpin.fail(`No permission to create repo "${ce.repo}" from the CLI`);
16062
+ guideWebCreation("repo", ce.repo, ce.createUrl ?? null);
16063
+ process.exit(1);
16064
+ }
15867
16065
  const msg = ce.message;
15868
16066
  if (/already been taken|already exists/i.test(msg)) {
15869
16067
  createSpin.info(`Repo ${repoInfo.owner}/${repoInfo.repo} already exists, retrying clone`);
@@ -16244,6 +16442,9 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
16244
16442
  items: toPendingItems(items)
16245
16443
  });
16246
16444
  await checkoutMaster(localConfig.repo.localPath);
16445
+ for (const rel of pushedFiles) {
16446
+ await pruneEmptyDirs(path45.resolve(localConfig.repo.localPath, rel));
16447
+ }
16247
16448
  return true;
16248
16449
  } catch (e) {
16249
16450
  pushSpin.fail(`Push failed: ${e.message}`);
@@ -22024,7 +22225,7 @@ async function refreshTeamRepo(localConfig) {
22024
22225
  if (!apiKey) {
22025
22226
  throw new Error("No API key configured. Re-run `teamai init --http <url> --token <key>` or set TEAMAI_API_TOKEN.");
22026
22227
  }
22027
- return { label: "HTTP (report/sync delivery)", version: null, reportingOnly: true, submodulesFailed: false };
22228
+ return { label: "HTTP (report/sync delivery)", version: null, reportingOnly: true, submodulesFailed: false, submodulesChanged: false };
22028
22229
  }
22029
22230
  if (localConfig.repo.kind === "self") {
22030
22231
  try {
@@ -22038,7 +22239,7 @@ async function refreshTeamRepo(localConfig) {
22038
22239
  } catch {
22039
22240
  version3 = null;
22040
22241
  }
22041
- return { label: "single-repo (knowledge on main)", version: version3, reportingOnly: false, submodulesFailed: false };
22242
+ return { label: "single-repo (knowledge on main)", version: version3, reportingOnly: false, submodulesFailed: false, submodulesChanged: false };
22042
22243
  }
22043
22244
  const result = await pullRepo(localConfig.repo.localPath);
22044
22245
  try {
@@ -22054,17 +22255,34 @@ async function refreshTeamRepo(localConfig) {
22054
22255
  version2 = null;
22055
22256
  }
22056
22257
  let submodulesFailed = false;
22258
+ let submodulesChanged = false;
22057
22259
  try {
22058
22260
  const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
22059
22261
  if (teamConfig?.submodules) {
22060
- await createGit2(localConfig.repo.localPath).submoduleUpdate(["--init"]);
22061
- log.debug("Submodules updated");
22262
+ const git = createGit2(localConfig.repo.localPath);
22263
+ let before = null;
22264
+ try {
22265
+ before = await git.subModule(["status"]);
22266
+ } catch {
22267
+ before = null;
22268
+ }
22269
+ await git.submoduleUpdate(["--init"]);
22270
+ let after = null;
22271
+ try {
22272
+ after = await git.subModule(["status"]);
22273
+ } catch {
22274
+ after = null;
22275
+ }
22276
+ submodulesChanged = before === null || before !== after;
22277
+ log.debug(
22278
+ submodulesChanged ? "Submodules updated (tree changed \u2014 full sync this pull)" : "Submodules updated (no change)"
22279
+ );
22062
22280
  }
22063
22281
  } catch (e) {
22064
22282
  submodulesFailed = true;
22065
22283
  log.warn(`Submodule update failed for ${localConfig.repo.localPath}: ${e.message}`);
22066
22284
  }
22067
- return { label: result, version: version2, reportingOnly: false, submodulesFailed };
22285
+ return { label: result, version: version2, reportingOnly: false, submodulesFailed, submodulesChanged };
22068
22286
  }
22069
22287
  async function usageReportDisabled(repoPath) {
22070
22288
  return (await loadTeamConfig(repoPath))?.usageReport === false;
@@ -22183,7 +22401,7 @@ async function skillSafeToRemove(deployedDir, source) {
22183
22401
  async function cleanupInactiveNamespaceSkills(teamConfig, localConfig, retainedSkillNames, inactiveSkillNames, inactiveSkillSources) {
22184
22402
  const baseDir = resolveBaseDir(localConfig);
22185
22403
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
22186
- if (isAgentDisabled(localConfig, tool)) continue;
22404
+ if (isAgentExcluded(localConfig, tool)) continue;
22187
22405
  if (!toolPath.skills) continue;
22188
22406
  if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
22189
22407
  if (!await pathExists(path65.join(baseDir, toolPath.skills))) continue;
@@ -22244,7 +22462,7 @@ async function getInstalledResourceTargets(teamConfig, localConfig) {
22244
22462
  const baseDir = resolveBaseDir(localConfig);
22245
22463
  const targets = [];
22246
22464
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
22247
- if (isAgentDisabled(localConfig, tool)) continue;
22465
+ if (isAgentExcluded(localConfig, tool)) continue;
22248
22466
  const resourcePaths = [toolPath.skills, toolPath.rules, toolPath.agents].filter((resourcePath) => !!resourcePath);
22249
22467
  for (const resourcePath of resourcePaths) {
22250
22468
  if (await ResourceHandler.isToolInstalled(resourcePath, baseDir)) {
@@ -22268,18 +22486,20 @@ async function pullForScope(localConfig, options, policy = {}) {
22268
22486
  let currentRev = null;
22269
22487
  let reportingOnly = false;
22270
22488
  let submodulesFailed = false;
22489
+ let submodulesChanged = false;
22271
22490
  try {
22272
22491
  const refresh = await refreshTeamRepo(localConfig);
22273
22492
  currentRev = refresh.version;
22274
22493
  reportingOnly = refresh.reportingOnly;
22275
22494
  submodulesFailed = refresh.submodulesFailed;
22495
+ submodulesChanged = refresh.submodulesChanged;
22276
22496
  pullSpin.succeed(`[${scopeLabel}] Team repo: ${refresh.label}`);
22277
22497
  } catch (e) {
22278
22498
  pullSpin.fail(`[${scopeLabel}] Pull failed: ${e.message}`);
22279
22499
  return;
22280
22500
  }
22281
22501
  let currentTargets = null;
22282
- if (!options.force && !options.dryRun) {
22502
+ if (!options.force && !options.dryRun && !submodulesChanged) {
22283
22503
  try {
22284
22504
  const state = await loadStateForScope(localConfig);
22285
22505
  if (currentRev && state[revisionField] && state[revisionField] === currentRev) {
@@ -22460,7 +22680,7 @@ async function pullForScope(localConfig, options, policy = {}) {
22460
22680
  const dir = toolPath[toolPathField];
22461
22681
  if (!dir) continue;
22462
22682
  if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
22463
- if (isAgentDisabled(localConfig, tool)) continue;
22683
+ if (isAgentExcluded(localConfig, tool)) continue;
22464
22684
  const extensions = type === "rules" ? [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])] : [ext];
22465
22685
  for (const name of tombstones) {
22466
22686
  for (const extension of extensions) {
@@ -22490,7 +22710,7 @@ async function pullForScope(localConfig, options, policy = {}) {
22490
22710
  if (!options.dryRun && desiredSkillNames && knownRepoSkillNames) {
22491
22711
  const baseDir = resolveBaseDir(localConfig);
22492
22712
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
22493
- if (isAgentDisabled(localConfig, tool)) continue;
22713
+ if (isAgentExcluded(localConfig, tool)) continue;
22494
22714
  if (!toolPath.skills) continue;
22495
22715
  if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
22496
22716
  const skillsDir = path65.join(baseDir, toolPath.skills);
@@ -22609,7 +22829,7 @@ async function pullForScope(localConfig, options, policy = {}) {
22609
22829
  if (compiled) {
22610
22830
  const baseDir = resolveBaseDir(localConfig);
22611
22831
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
22612
- if (isAgentDisabled(localConfig, tool)) continue;
22832
+ if (isAgentExcluded(localConfig, tool)) continue;
22613
22833
  if (!toolPath.claudemd) continue;
22614
22834
  if (toolPath.rules && !await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
22615
22835
  const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
@@ -22639,7 +22859,7 @@ async function pullForScope(localConfig, options, policy = {}) {
22639
22859
  if (compiled) {
22640
22860
  const baseDir = resolveBaseDir(localConfig);
22641
22861
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
22642
- if (isAgentDisabled(localConfig, tool)) continue;
22862
+ if (isAgentExcluded(localConfig, tool)) continue;
22643
22863
  if (!toolPath.claudemd) continue;
22644
22864
  if (toolPath.rules && !await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
22645
22865
  const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
@@ -22818,7 +23038,7 @@ async function injectRecallBlockIntoTools(config, localConfig, scopeLabel) {
22818
23038
  const recallBlock = compileRecallRulesBlock();
22819
23039
  let injected = 0;
22820
23040
  for (const [tool, toolPath] of Object.entries(scopedToolPaths(config, localConfig))) {
22821
- if (isAgentDisabled(localConfig, tool)) continue;
23041
+ if (isAgentExcluded(localConfig, tool)) continue;
22822
23042
  if (!toolPath.claudemd || !toolPath.agents) continue;
22823
23043
  if (!await ResourceHandler.isToolInstalled(toolPath.agents, baseDir)) continue;
22824
23044
  const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
@@ -27057,67 +27277,7 @@ __export(ai_client_exports, {
27057
27277
  pickWindowsCommand: () => pickWindowsCommand,
27058
27278
  resolveCliPath: () => resolveCliPath
27059
27279
  });
27060
- import { execFileSync as execFileSync2 } from "child_process";
27061
27280
  import spawn2 from "cross-spawn";
27062
- import { existsSync } from "fs";
27063
- function pickWindowsCommand(whereOutput) {
27064
- const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
27065
- for (const ext of WIN_EXEC_EXTENSIONS) {
27066
- const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
27067
- if (hit !== void 0) return hit;
27068
- }
27069
- return null;
27070
- }
27071
- function whereOnWindows(cmd) {
27072
- try {
27073
- const out = execFileSync2("where", [cmd], {
27074
- encoding: "utf8",
27075
- stdio: ["ignore", "pipe", "ignore"],
27076
- shell: false,
27077
- timeout: CLI_DETECT_TIMEOUT_MS
27078
- });
27079
- const p = pickWindowsCommand(out);
27080
- return p !== null && existsSync(p) ? p : null;
27081
- } catch {
27082
- return null;
27083
- }
27084
- }
27085
- function whichOnPosix(cmd) {
27086
- try {
27087
- const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
27088
- encoding: "utf8",
27089
- stdio: ["ignore", "pipe", "ignore"],
27090
- shell: false,
27091
- timeout: CLI_DETECT_TIMEOUT_MS
27092
- }).trim();
27093
- if (p && existsSync(p)) return p;
27094
- } catch {
27095
- }
27096
- try {
27097
- const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
27098
- encoding: "utf8",
27099
- stdio: ["ignore", "pipe", "ignore"],
27100
- shell: false,
27101
- timeout: CLI_DETECT_TIMEOUT_MS
27102
- }).trim();
27103
- if (p && existsSync(p)) return p;
27104
- } catch {
27105
- }
27106
- try {
27107
- const p = execFileSync2("which", [cmd], {
27108
- encoding: "utf8",
27109
- stdio: ["ignore", "pipe", "ignore"],
27110
- shell: false,
27111
- timeout: CLI_DETECT_TIMEOUT_MS
27112
- }).trim();
27113
- if (p && existsSync(p)) return p;
27114
- } catch {
27115
- }
27116
- return null;
27117
- }
27118
- function resolveCliPath(cmd, platform = process.platform) {
27119
- return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
27120
- }
27121
27281
  function detectClaudeCli() {
27122
27282
  for (const cmd of ALLOWED_CLI_CANDIDATES) {
27123
27283
  const absPath = resolveCliPath(cmd);
@@ -27232,11 +27392,13 @@ async function runWithConcurrency(tasks, concurrency) {
27232
27392
  await Promise.all(promises);
27233
27393
  return results;
27234
27394
  }
27235
- var ALLOWED_CLI_CANDIDATES, CLI_DETECT_TIMEOUT_MS, WIN_EXEC_EXTENSIONS, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27395
+ var ALLOWED_CLI_CANDIDATES, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27236
27396
  var init_ai_client = __esm({
27237
27397
  "src/utils/ai-client.ts"() {
27238
27398
  "use strict";
27239
27399
  init_logger();
27400
+ init_cli_path();
27401
+ init_cli_path();
27240
27402
  ALLOWED_CLI_CANDIDATES = [
27241
27403
  "claude",
27242
27404
  "claude-internal",
@@ -27248,8 +27410,6 @@ var init_ai_client = __esm({
27248
27410
  "workbuddy",
27249
27411
  "openclaw"
27250
27412
  ];
27251
- CLI_DETECT_TIMEOUT_MS = 5e3;
27252
- WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
27253
27413
  DEFAULT_TIMEOUT_MS = 6e5;
27254
27414
  DEFAULT_CONCURRENCY = 3;
27255
27415
  }
@@ -33182,7 +33342,7 @@ var init_import_iwiki = __esm({
33182
33342
  });
33183
33343
 
33184
33344
  // src/providers/github/mr-fetch.ts
33185
- import { execSync as execSync5 } from "child_process";
33345
+ import { execSync as execSync4 } from "child_process";
33186
33346
  import https2 from "https";
33187
33347
  function parseGitHubPRUrl(url) {
33188
33348
  const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
@@ -33269,12 +33429,12 @@ async function fetchGitHubPR(url) {
33269
33429
  const repoArg = `${owner}/${repo}`;
33270
33430
  log.debug(`fetchGitHubPR: ${repoArg}#${number}`);
33271
33431
  try {
33272
- const viewOutput = execSync5(
33432
+ const viewOutput = execSync4(
33273
33433
  `gh pr view ${number} --repo ${repoArg} --json title,body,author,mergedAt,commits`,
33274
33434
  { maxBuffer: 10 * 1024 * 1024, encoding: "utf8" }
33275
33435
  );
33276
33436
  const prView = JSON.parse(viewOutput);
33277
- const rawDiff = execSync5(
33437
+ const rawDiff = execSync4(
33278
33438
  `gh pr diff ${number} --repo ${repoArg}`,
33279
33439
  { maxBuffer: 50 * 1024 * 1024, encoding: "utf8" }
33280
33440
  );
@@ -33688,7 +33848,7 @@ var init_import_mr = __esm({
33688
33848
  });
33689
33849
 
33690
33850
  // src/codebase.ts
33691
- import { execSync as execSync6 } from "child_process";
33851
+ import { execSync as execSync5 } from "child_process";
33692
33852
  import fs36 from "fs";
33693
33853
  import path103 from "path";
33694
33854
  import matter12 from "gray-matter";
@@ -33704,7 +33864,7 @@ ${commitMessages}`);
33704
33864
  log.debug(`gatherRepoContext: git log \u5931\u8D25 \u2014 ${String(err)}`);
33705
33865
  }
33706
33866
  try {
33707
- const rawTree = execSync6(
33867
+ const rawTree = execSync5(
33708
33868
  'find . -maxdepth 4 -not -path "*/.git/*" -not -path "*/node_modules/*" -not -path "*/__pycache__/*" -not -path "*/dist/*" -not -path "*/.claude/worktrees/*" -not -name "*.js.map"',
33709
33869
  { cwd: repoPath, encoding: "utf-8" }
33710
33870
  );
@@ -36853,9 +37013,12 @@ var init_templates = __esm({
36853
37013
  // src/enrich-with-ai.ts
36854
37014
  var enrich_with_ai_exports = {};
36855
37015
  __export(enrich_with_ai_exports, {
37016
+ buildFallbackManifest: () => buildFallbackManifest,
37017
+ describeEvidenceManifest: () => describeEvidenceManifest,
36856
37018
  edgeProvenanceRank: () => edgeProvenanceRank,
36857
37019
  edgeReason: () => edgeReason,
36858
37020
  enrichWithAI: () => enrichWithAI,
37021
+ groupFactsByModule: () => groupFactsByModule,
36859
37022
  parseEdgeProvenance: () => parseEdgeProvenance,
36860
37023
  writeManifest: () => writeManifest
36861
37024
  });
@@ -36925,6 +37088,62 @@ function resolveImportToModule(importerFile, importPath) {
36925
37088
  if (first.startsWith("@")) return void 0;
36926
37089
  return first;
36927
37090
  }
37091
+ function groupFactsByModule(facts) {
37092
+ const modules = /* @__PURE__ */ new Map();
37093
+ for (const fact of facts) {
37094
+ if (fact.kind === "relation") continue;
37095
+ const mod = fact.file.split("/")[0] || "_root";
37096
+ const existing = modules.get(mod) ?? [];
37097
+ existing.push(fact);
37098
+ modules.set(mod, existing);
37099
+ }
37100
+ return modules;
37101
+ }
37102
+ function isSafeSlug(name) {
37103
+ try {
37104
+ assertSafeResourceName(name);
37105
+ return true;
37106
+ } catch {
37107
+ return false;
37108
+ }
37109
+ }
37110
+ function buildFallbackManifest(ctx) {
37111
+ const moduleNames = [...ctx.modules.keys()].filter(isSafeSlug);
37112
+ const componentNames = [...new Set(
37113
+ ctx.facts.filter((f) => f.kind === "component").map((f) => f.name)
37114
+ )].filter(isSafeSlug);
37115
+ const slugs = moduleNames.length > 0 ? moduleNames : componentNames;
37116
+ if (slugs.length === 0) return null;
37117
+ const components = slugs.map((name) => {
37118
+ const moduleFacts = ctx.modules.get(name) ?? ctx.facts.filter((f) => f.name === name);
37119
+ const entrypoints = moduleFacts.filter((f) => f.kind === "component").filter((f) => /handler|route|controller|endpoint|main|server|app/i.test(f.name)).slice(0, 5).map((f) => `${f.name} (${f.file}:${f.lineStart})`);
37120
+ return {
37121
+ slug: name,
37122
+ docPath: `evidence/code/${ctx.project}/${name}.md`,
37123
+ title: name,
37124
+ category: "component",
37125
+ confidence: "EXTRACTED",
37126
+ ...entrypoints.length > 0 ? { entrypoints } : {}
37127
+ };
37128
+ });
37129
+ return {
37130
+ schemaVersion: "team-wiki.codebase-output-manifest.v2",
37131
+ project: ctx.project,
37132
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
37133
+ components,
37134
+ edges: []
37135
+ };
37136
+ }
37137
+ function describeEvidenceManifest(source, componentCount) {
37138
+ if (source === "fallback") {
37139
+ const noun = componentCount === 1 ? "component" : "components";
37140
+ return `Wrote fallback _manifest.json (${componentCount} ${noun}, no AI enrich)`;
37141
+ }
37142
+ if (source === "none") {
37143
+ return "AI enrich produced no manifest; deep-enrich will have no components";
37144
+ }
37145
+ return void 0;
37146
+ }
36928
37147
  async function enrichWithAI(ctx) {
36929
37148
  const moduleEntries = [...ctx.modules.entries()].filter(([, facts]) => facts.length >= 5);
36930
37149
  if (moduleEntries.length === 0) {
@@ -37063,6 +37282,7 @@ var init_enrich_with_ai = __esm({
37063
37282
  "use strict";
37064
37283
  init_ai_client();
37065
37284
  init_logger();
37285
+ init_path_safety();
37066
37286
  }
37067
37287
  });
37068
37288
 
@@ -37584,39 +37804,53 @@ async function extractCodebase(opts) {
37584
37804
  const repoGraph = mergeGraphs(graph, overlay);
37585
37805
  await saveGraphIndex(wikiRoot, repoGraph);
37586
37806
  let aiDomains = [];
37807
+ let manifestSource = "none";
37808
+ let manifestComponentCount = 0;
37809
+ const {
37810
+ enrichWithAI: enrichWithAI2,
37811
+ writeManifest: writeManifest2,
37812
+ buildFallbackManifest: buildFallbackManifest2,
37813
+ groupFactsByModule: groupFactsByModule2,
37814
+ describeEvidenceManifest: describeEvidenceManifest2
37815
+ } = await Promise.resolve().then(() => (init_enrich_with_ai(), enrich_with_ai_exports));
37816
+ const modules = groupFactsByModule2(facts);
37587
37817
  if (opts.skipEnrich) {
37588
37818
  if (!opts.json) console.log(chalk3.dim(" [AI enrich: skipped (--skip-enrich)]"));
37589
- } else try {
37590
- const { enrichWithAI: enrichWithAI2, writeManifest: writeManifest2 } = await Promise.resolve().then(() => (init_enrich_with_ai(), enrich_with_ai_exports));
37591
- const modules = /* @__PURE__ */ new Map();
37592
- for (const fact of facts) {
37593
- if (fact.kind === "relation") continue;
37594
- const mod = fact.file.split("/")[0] || "_root";
37595
- const existing = modules.get(mod) ?? [];
37596
- existing.push(fact);
37597
- modules.set(mod, existing);
37598
- }
37599
- const enrichResult = await enrichWithAI2({ project, facts, interfaceInventory, modules });
37600
- if (enrichResult) {
37601
- await writeManifest2(enrichResult.manifest, evidenceDir);
37602
- aiDomains = enrichResult.domains;
37603
- const domainMeta = {
37604
- domain: enrichResult.repoDomain || (enrichResult.domains[0]?.name ?? ""),
37605
- description: enrichResult.repoDescription || "",
37606
- keywords: enrichResult.repoKeywords || [],
37607
- components: enrichResult.domains[0]?.components ?? []
37608
- };
37609
- await writeFile10(path112.join(evidenceDir, "_domains.json"), JSON.stringify(domainMeta, null, 2), "utf-8");
37819
+ } else {
37820
+ try {
37821
+ const enrichResult = await enrichWithAI2({ project, facts, interfaceInventory, modules });
37822
+ if (enrichResult) {
37823
+ await writeManifest2(enrichResult.manifest, evidenceDir);
37824
+ manifestSource = "ai";
37825
+ manifestComponentCount = enrichResult.manifest.components.length;
37826
+ aiDomains = enrichResult.domains;
37827
+ const domainMeta = {
37828
+ domain: enrichResult.repoDomain || (enrichResult.domains[0]?.name ?? ""),
37829
+ description: enrichResult.repoDescription || "",
37830
+ keywords: enrichResult.repoKeywords || [],
37831
+ components: enrichResult.domains[0]?.components ?? []
37832
+ };
37833
+ await writeFile10(path112.join(evidenceDir, "_domains.json"), JSON.stringify(domainMeta, null, 2), "utf-8");
37834
+ if (!opts.json) {
37835
+ const domainLabel = domainMeta.domain || "uncategorized";
37836
+ console.log(` AI enrich: ${enrichResult.manifest.components.length} modules, domain=${domainLabel}`);
37837
+ }
37838
+ }
37839
+ } catch (e) {
37610
37840
  if (!opts.json) {
37611
- const domainLabel = domainMeta.domain || "uncategorized";
37612
- console.log(` AI enrich: ${enrichResult.manifest.components.length} modules, domain=${domainLabel}`);
37841
+ console.log(chalk3.dim(` [AI enrich skipped: ${e.message}]`));
37613
37842
  }
37614
37843
  }
37615
- } catch (e) {
37616
- if (!opts.json) {
37617
- console.log(chalk3.dim(` [AI enrich skipped: ${e.message}]`));
37844
+ }
37845
+ if (manifestSource === "none") {
37846
+ const fallback = buildFallbackManifest2({ project, facts, modules });
37847
+ if (fallback && fallback.components.length > 0) {
37848
+ await writeManifest2(fallback, evidenceDir);
37849
+ manifestSource = "fallback";
37850
+ manifestComponentCount = fallback.components.length;
37618
37851
  }
37619
37852
  }
37853
+ const manifestNote = describeEvidenceManifest2(manifestSource, manifestComponentCount);
37620
37854
  const moduleSummaries = buildModuleSummaries(facts, graph, project);
37621
37855
  if (moduleSummaries.size > 0) {
37622
37856
  const modulesDir = path112.join(evidenceDir, "modules");
@@ -37727,7 +37961,13 @@ async function extractCodebase(opts) {
37727
37961
  facts: { total: facts.length, byKind },
37728
37962
  graph: { nodes: repoGraph.nodes.length, edges: repoGraph.edges.length },
37729
37963
  incremental: !!opts.incremental && !!changedFiles,
37730
- outputDir: wikiRoot
37964
+ outputDir: wikiRoot,
37965
+ manifest: {
37966
+ written: manifestSource !== "none",
37967
+ source: manifestSource,
37968
+ components: manifestComponentCount,
37969
+ ...manifestNote ? { note: manifestNote } : {}
37970
+ }
37731
37971
  };
37732
37972
  if (opts.json) {
37733
37973
  console.log(JSON.stringify(result, null, 2));
@@ -37745,6 +37985,9 @@ async function extractCodebase(opts) {
37745
37985
  console.log(` Call chains: ${callChains.length} chains (max depth ${Math.max(...callChains.map((c) => c.depth))})`);
37746
37986
  }
37747
37987
  console.log(` Output: ${wikiRoot}`);
37988
+ if (manifestNote) {
37989
+ console.log(` ${manifestNote}`);
37990
+ }
37748
37991
  }
37749
37992
  }
37750
37993
  var init_codebase_extract = __esm({
@@ -38013,7 +38256,8 @@ var init_repo_cache = __esm({
38013
38256
  // src/deep-enrich.ts
38014
38257
  var deep_enrich_exports = {};
38015
38258
  __export(deep_enrich_exports, {
38016
- deepEnrich: () => deepEnrich
38259
+ deepEnrich: () => deepEnrich,
38260
+ runHiddenDeepEnrich: () => runHiddenDeepEnrich
38017
38261
  });
38018
38262
  import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
38019
38263
  import path114 from "path";
@@ -38595,6 +38839,18 @@ async function invalidateStaleAiDocs(docsDir, slugs) {
38595
38839
  await removeIfExists(architectureDocPath(docsDir));
38596
38840
  await removeIfExists(path114.join(docsDir, "graph-g5-scenarios.md"));
38597
38841
  }
38842
+ async function runHiddenDeepEnrich(opts) {
38843
+ const wikiRoot = opts.wikiRoot ?? path114.join(process.cwd(), ".teamai", "team-repo", "teamwiki");
38844
+ const evidenceDir = path114.join(wikiRoot, "evidence", "code", opts.project);
38845
+ const result = await deepEnrich({
38846
+ project: opts.project,
38847
+ evidenceDir,
38848
+ wikiRoot,
38849
+ maxModules: opts.maxModules
38850
+ });
38851
+ if (!result.complete) process.exitCode = 1;
38852
+ return result;
38853
+ }
38598
38854
  async function deepEnrich(opts) {
38599
38855
  const { project, evidenceDir } = opts;
38600
38856
  const docsDir = path114.join(evidenceDir, "docs");
@@ -40807,7 +41063,7 @@ async function codebaseCmd(opts) {
40807
41063
  componentCount = 0;
40808
41064
  }
40809
41065
  if (componentCount === 0) {
40810
- console.log(`No components in evidence for project "${project}". Run \`teamai codebase --extract\` first.`);
41066
+ console.log(`No components in _manifest.json for project "${project}".`);
40811
41067
  process.exitCode = 1;
40812
41068
  return;
40813
41069
  }
@@ -42451,11 +42707,12 @@ ciCmd.command("extract-mr").description("Extract knowledge from MR/PR and post a
42451
42707
  await ciExtractMr2({ ...globalOpts, ...cmdOpts });
42452
42708
  });
42453
42709
  program.command("deep-enrich", { hidden: true }).description("Run deep AI knowledge generation for an imported repo").requiredOption("--project <slug>", "Project slug (directory name in evidence/code/)").option("--wiki-root <path>", "Teamwiki root path").option("--max-modules <n>", "Max modules to process (cost control)", parseInt).action(async (cmdOpts) => {
42454
- const p = await import("path");
42455
- const wikiRoot = cmdOpts.wikiRoot ?? p.join(process.cwd(), ".teamai", "team-repo", "teamwiki");
42456
- const evidenceDir = p.join(wikiRoot, "evidence", "code", cmdOpts.project);
42457
- const { deepEnrich: deepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
42458
- await deepEnrich2({ project: cmdOpts.project, evidenceDir, wikiRoot, maxModules: cmdOpts.maxModules });
42710
+ const { runHiddenDeepEnrich: runHiddenDeepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
42711
+ await runHiddenDeepEnrich2({
42712
+ project: cmdOpts.project,
42713
+ wikiRoot: cmdOpts.wikiRoot,
42714
+ maxModules: cmdOpts.maxModules
42715
+ });
42459
42716
  });
42460
42717
  recallCmd.command("feedback").description("Record manual feedback for a recalled document").option("--positive <docId>", "Upvote a document (marks as actually useful)").option("--negative <docId>", "Record negative signal for a document").action(async (cmdOpts) => {
42461
42718
  const { recallFeedback: recallFeedback2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.24.0-beta.8",
3
+ "version": "0.24.0-beta.9",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {