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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. See [standa
17
17
  ### 📝 Documentation
18
18
 
19
19
  - Align the public usage guides, drop internal-only details, and cover the missing commands and configuration ([#442](https://github.com/Tencent/teamai-cli/pull/442)).
20
+ - Document `teamai projects` in the bilingual READMEs as the lead distribution control, including learnings isolation ([#490](https://github.com/Tencent/teamai-cli/pull/490), for [#487](https://github.com/Tencent/teamai-cli/issues/487)).
20
21
 
21
22
  ## [0.23.0](https://github.com/Tencent/teamai-cli/compare/v0.22.0...v0.23.0) (2026-09-08)
22
23
 
package/README.md CHANGED
@@ -103,10 +103,13 @@ Team-wide settings an admin configures once and delivers to every member on `tea
103
103
 
104
104
  | Capability | Command | What it does |
105
105
  |------------|---------|--------------|
106
+ | **Projects** | `teamai projects` | Bind a working directory to one or more logical projects so it syncs that project's skills, knowledge, and isolated learnings. Orthogonal to roles. |
106
107
  | **Roles** | `teamai roles` | Define role → namespace mappings so each member syncs only the skills for their role. |
107
108
  | **Tags** | `teamai tags` | Tag skills / rules so members subscribe to just the tags they need. |
108
109
  | **Sources** | `teamai source` | Subscribe to additional skill repos — other teams' public repos, or shared/public repos within your own org; subscribed skills sync automatically on pull. |
109
110
 
111
+ Learnings isolation: `learnings/` at the repo root is shared with everyone; `learnings/<project-id>/` is project-private. See the [usage guide](docs/usage-guide.md#multi-project-project-as-a-dimension-orthogonal-to-role).
112
+
110
113
  ## Team Execution
111
114
 
112
115
  > One Team. One Harness. Every Agent.
@@ -237,7 +240,7 @@ Insight into how the team actually uses its AI tools, and a starting point for t
237
240
  | `teamai pull` | Pull team resources and inject into local AI tools |
238
241
  | `teamai push` | Push local resources to a branch and open a Merge Request |
239
242
  | `teamai packages [install] [target]` | Install declared npm packages and Claude plugins; with a target, also update `teamai.yaml`. Bare `teamai packages` installs everything; `teamai packages install <target>` adds one |
240
- | `teamai status` | Show local vs team repo diff |
243
+ | `teamai status` | Show local vs team repo diff and resource counts, including namespaced skills and nested docs |
241
244
  | `teamai contribute` | Share session experience to team repo |
242
245
  | `teamai recall <query>` | Search the team knowledge base (BM25 + graph-boost) |
243
246
  | `teamai recall enable/disable/status` | Toggle or check recall state |
@@ -250,6 +253,7 @@ Insight into how the team actually uses its AI tools, and a starting point for t
250
253
  | `teamai codebase --lint` | Knowledge graph health check |
251
254
  | `teamai ci extract-mr --url <url>` | CI: extract knowledge from MR, post comments, write after merge |
252
255
  | `teamai members` | List team members |
256
+ | `teamai projects` | Bind a working directory to one or more logical projects |
253
257
  | `teamai roles` | Manage team roles and namespaces |
254
258
  | `teamai tags` | Manage tag-based skill/rule filtering |
255
259
  | `teamai skill exclude add/remove/list` | Manage skills excluded from local sync ([usage guide](docs/usage-guide.md#excluding-skills-you-dont-need)) |
package/README.zh-CN.md CHANGED
@@ -103,10 +103,13 @@ teamai init https://github.com/yourorg/yourrepo --scope user
103
103
 
104
104
  | 能力 | 命令 | 作用 |
105
105
  |------|------|------|
106
+ | **项目(Projects)** | `teamai projects` | 将工作目录绑定到一个或多个逻辑项目,使其同步该项目的 skills、knowledge 以及隔离的 learnings。与角色正交。 |
106
107
  | **角色(Roles)** | `teamai roles` | 定义「角色 → 命名空间」映射,让每位成员只同步与自身角色匹配的 skills。 |
107
108
  | **标签(Tags)** | `teamai tags` | 给 skills / rules 打标签,成员只订阅自己需要的标签。 |
108
109
  | **订阅源(Sources)** | `teamai source` | 订阅额外的 skill 仓库——其他团队的公开仓库,或本团队内的公共/共享仓库;已订阅的 skills 会在 pull 时自动同步。 |
109
110
 
111
+ learnings 隔离:仓库 `learnings/` 根目录对所有人共享;`learnings/<project-id>/` 为项目私有。详见[使用指南](docs/usage-guide.zh-CN.md#多项目project-作为与-role-正交的维度)。
112
+
110
113
  ## Team Execution
111
114
 
112
115
  > One Team. One Harness. Every Agent.
@@ -237,7 +240,7 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
237
240
  | `teamai pull` | 拉取团队资源并注入到本地 AI 工具 |
238
241
  | `teamai push` | 推送本地资源到分支并创建合并请求 |
239
242
  | `teamai packages [install] [target]` | 安装团队 npm 包和 Claude 插件。裸 `teamai packages` 安装全部;`teamai packages install <target>` 添加单个并更新声明 |
240
- | `teamai status` | 显示本地与团队仓库的差异 |
243
+ | `teamai status` | 显示本地与团队仓库的差异及资源数量,包含 namespace 下的技能和子目录中的文档 |
241
244
  | `teamai contribute` | 将 session 经验分享到团队仓库 |
242
245
  | `teamai recall <query>` | 搜索团队知识库(BM25 + 图谱增强) |
243
246
  | `teamai recall enable/disable/status` | 开关或查看 recall 状态 |
@@ -250,6 +253,7 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
250
253
  | `teamai codebase --lint` | 知识图谱健康检查 |
251
254
  | `teamai ci extract-mr --url <url>` | CI:从 MR 提取知识、发评论、合并后写入 |
252
255
  | `teamai members` | 查看团队成员 |
256
+ | `teamai projects` | 将工作目录绑定到一个或多个逻辑项目 |
253
257
  | `teamai roles` | 管理团队角色和命名空间 |
254
258
  | `teamai tags` | 管理基于标签的 skill/rule 过滤 |
255
259
  | `teamai skill exclude add/remove/list` | 管理不参与本地同步的 skills([使用指南](docs/usage-guide.zh-CN.md#排除个人不需要的-skill)) |
package/dist/index.js CHANGED
@@ -11974,6 +11974,10 @@ var init_skills = __esm({
11974
11974
  }
11975
11975
  dest = path28.join(wsDir, "skills", item.name);
11976
11976
  } else if (tool === "hermes") {
11977
+ if (!await pathExists(getHermesHome())) {
11978
+ log.debug(`Skipping skill sync for ${tool}: tool not installed`);
11979
+ continue;
11980
+ }
11977
11981
  dest = path28.join(getHermesHome(), "skills", item.name);
11978
11982
  } else {
11979
11983
  if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
@@ -12421,10 +12425,7 @@ var init_docs = __esm({
12421
12425
  }
12422
12426
  async scanTeamForPull(_teamConfig, localConfig) {
12423
12427
  const docsDir = path31.join(localConfig.repo.localPath, "docs");
12424
- if (!await pathExists(docsDir)) return [];
12425
- const files = await listFiles(docsDir);
12426
- const realFiles = files.filter((f) => !f.startsWith("."));
12427
- if (realFiles.length === 0) return [];
12428
+ if (await this.countDocFiles(docsDir) === 0) return [];
12428
12429
  return [{
12429
12430
  name: "docs",
12430
12431
  type: "docs",
@@ -12433,8 +12434,8 @@ var init_docs = __esm({
12433
12434
  }];
12434
12435
  }
12435
12436
  async countDocFiles(sourcePath) {
12436
- const files = await listFiles(sourcePath);
12437
- return files.filter((f) => !f.startsWith(".")).length;
12437
+ const files = await listFilesRecursive(sourcePath);
12438
+ return files.filter((f) => f.split("/").every((segment) => !segment.startsWith("."))).length;
12438
12439
  }
12439
12440
  async pushItem(_item, _teamConfig, _localConfig) {
12440
12441
  }
@@ -23270,13 +23271,10 @@ async function status(options) {
23270
23271
  log.info("Team resources:");
23271
23272
  const repoPath = localConfig.repo.localPath;
23272
23273
  const counts = {};
23273
- const skillsDirs = await listDirs(path66.join(repoPath, "skills"));
23274
- counts.skills = skillsDirs.length;
23274
+ counts.skills = (await new SkillsHandler().scanTeamForPull(teamConfig, localConfig)).length;
23275
23275
  const rulesFiles = (await listFilesRecursive(path66.join(repoPath, "rules"))).filter((f) => f.endsWith(".md"));
23276
23276
  counts.rules = rulesFiles.length;
23277
- const docsExists = await pathExists(path66.join(repoPath, "docs"));
23278
- const docFiles = docsExists ? (await listFiles(path66.join(repoPath, "docs"))).filter((f) => !f.startsWith(".")) : [];
23279
- counts.docs = docFiles.length;
23277
+ counts.docs = await new DocsHandler().countDocFiles(path66.join(repoPath, "docs"));
23280
23278
  const envYamlPath = path66.join(repoPath, "env", "env.yaml");
23281
23279
  let envCount = 0;
23282
23280
  if (await pathExists(envYamlPath)) {
@@ -23561,6 +23559,7 @@ var init_status = __esm({
23561
23559
  init_resources();
23562
23560
  init_fs();
23563
23561
  init_skills();
23562
+ init_docs();
23564
23563
  init_known_agents();
23565
23564
  init_agent_skills();
23566
23565
  init_types();
@@ -26985,46 +26984,78 @@ var ai_client_exports = {};
26985
26984
  __export(ai_client_exports, {
26986
26985
  callClaude: () => callClaude,
26987
26986
  callClaudeParallel: () => callClaudeParallel,
26988
- getAICliName: () => getAICliName
26987
+ getAICliName: () => getAICliName,
26988
+ pickWindowsCommand: () => pickWindowsCommand,
26989
+ resolveCliPath: () => resolveCliPath
26989
26990
  });
26990
- import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
26991
+ import { execFileSync as execFileSync2 } from "child_process";
26992
+ import spawn2 from "cross-spawn";
26991
26993
  import { existsSync } from "fs";
26994
+ function pickWindowsCommand(whereOutput) {
26995
+ const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
26996
+ for (const ext of WIN_EXEC_EXTENSIONS) {
26997
+ const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
26998
+ if (hit !== void 0) return hit;
26999
+ }
27000
+ return null;
27001
+ }
27002
+ function whereOnWindows(cmd) {
27003
+ try {
27004
+ const out = execFileSync2("where", [cmd], {
27005
+ encoding: "utf8",
27006
+ stdio: ["ignore", "pipe", "ignore"],
27007
+ shell: false,
27008
+ timeout: CLI_DETECT_TIMEOUT_MS
27009
+ });
27010
+ const p = pickWindowsCommand(out);
27011
+ return p !== null && existsSync(p) ? p : null;
27012
+ } catch {
27013
+ return null;
27014
+ }
27015
+ }
27016
+ function whichOnPosix(cmd) {
27017
+ try {
27018
+ const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
27019
+ encoding: "utf8",
27020
+ stdio: ["ignore", "pipe", "ignore"],
27021
+ shell: false,
27022
+ timeout: CLI_DETECT_TIMEOUT_MS
27023
+ }).trim();
27024
+ if (p && existsSync(p)) return p;
27025
+ } catch {
27026
+ }
27027
+ try {
27028
+ const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
27029
+ encoding: "utf8",
27030
+ stdio: ["ignore", "pipe", "ignore"],
27031
+ shell: false,
27032
+ timeout: CLI_DETECT_TIMEOUT_MS
27033
+ }).trim();
27034
+ if (p && existsSync(p)) return p;
27035
+ } catch {
27036
+ }
27037
+ try {
27038
+ const p = execFileSync2("which", [cmd], {
27039
+ encoding: "utf8",
27040
+ stdio: ["ignore", "pipe", "ignore"],
27041
+ shell: false,
27042
+ timeout: CLI_DETECT_TIMEOUT_MS
27043
+ }).trim();
27044
+ if (p && existsSync(p)) return p;
27045
+ } catch {
27046
+ }
27047
+ return null;
27048
+ }
27049
+ function resolveCliPath(cmd, platform = process.platform) {
27050
+ return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
27051
+ }
26992
27052
  function detectClaudeCli() {
26993
- const candidates = ALLOWED_CLI_CANDIDATES;
26994
- for (const cmd of candidates) {
26995
- try {
26996
- const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
26997
- encoding: "utf8",
26998
- stdio: ["ignore", "pipe", "ignore"],
26999
- shell: false,
27000
- timeout: CLI_DETECT_TIMEOUT_MS
27001
- }).trim();
27002
- if (p && existsSync(p)) return { cmd, absPath: p };
27003
- } catch {
27004
- }
27005
- try {
27006
- const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
27007
- encoding: "utf8",
27008
- stdio: ["ignore", "pipe", "ignore"],
27009
- shell: false,
27010
- timeout: CLI_DETECT_TIMEOUT_MS
27011
- }).trim();
27012
- if (p && existsSync(p)) return { cmd, absPath: p };
27013
- } catch {
27014
- }
27015
- try {
27016
- const p = execFileSync2("which", [cmd], {
27017
- encoding: "utf8",
27018
- stdio: ["ignore", "pipe", "ignore"],
27019
- shell: false,
27020
- timeout: CLI_DETECT_TIMEOUT_MS
27021
- }).trim();
27022
- if (p && existsSync(p)) return { cmd, absPath: p };
27023
- } catch {
27024
- }
27053
+ for (const cmd of ALLOWED_CLI_CANDIDATES) {
27054
+ const absPath = resolveCliPath(cmd);
27055
+ if (absPath !== null) return { cmd, absPath };
27025
27056
  }
27026
27057
  throw new Error(
27027
- "AI CLI \u4E0D\u53EF\u7528\uFF1A\u8BF7\u5B89\u88C5\u4EE5\u4E0B\u4EFB\u610F\u4E00\u4E2A CLI \u5DE5\u5177\uFF1Aclaude / claude-internal / codex / codex-internal / codebuddy / workbuddy / openclaw"
27058
+ "AI CLI unavailable: install one of claude / claude-internal / codex / codex-internal / codebuddy / workbuddy / openclaw"
27028
27059
  );
27029
27060
  }
27030
27061
  function buildCliArgs(cmd, prompt) {
@@ -27050,8 +27081,8 @@ async function callClaude(prompt, opts) {
27050
27081
  }
27051
27082
  log.debug(`[ai-client] calling ${_cliInfo.cmd}, timeout=${Math.round(timeoutMs / 1e3)}s, prompt=${prompt.slice(0, 60).replace(/\n/g, " ")}...`);
27052
27083
  const child = spawn2(_cliInfo.absPath, buildCliArgs(_cliInfo.cmd, prompt), { stdio: ["ignore", "pipe", "pipe"] });
27053
- child.stdout.on("data", (chunk) => chunks.push(chunk));
27054
- child.stderr.on("data", (chunk) => errChunks.push(chunk));
27084
+ child.stdout?.on("data", (chunk) => chunks.push(chunk));
27085
+ child.stderr?.on("data", (chunk) => errChunks.push(chunk));
27055
27086
  const timer = setTimeout(() => {
27056
27087
  child.kill();
27057
27088
  const seconds = Math.round(timeoutMs / 1e3);
@@ -27132,7 +27163,7 @@ async function runWithConcurrency(tasks, concurrency) {
27132
27163
  await Promise.all(promises);
27133
27164
  return results;
27134
27165
  }
27135
- var ALLOWED_CLI_CANDIDATES, CLI_DETECT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27166
+ var ALLOWED_CLI_CANDIDATES, CLI_DETECT_TIMEOUT_MS, WIN_EXEC_EXTENSIONS, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
27136
27167
  var init_ai_client = __esm({
27137
27168
  "src/utils/ai-client.ts"() {
27138
27169
  "use strict";
@@ -27149,6 +27180,7 @@ var init_ai_client = __esm({
27149
27180
  "openclaw"
27150
27181
  ];
27151
27182
  CLI_DETECT_TIMEOUT_MS = 5e3;
27183
+ WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
27152
27184
  DEFAULT_TIMEOUT_MS = 6e5;
27153
27185
  DEFAULT_CONCURRENCY = 3;
27154
27186
  }
@@ -37913,7 +37945,7 @@ var deep_enrich_exports = {};
37913
37945
  __export(deep_enrich_exports, {
37914
37946
  deepEnrich: () => deepEnrich
37915
37947
  });
37916
- import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8 } from "fs/promises";
37948
+ import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
37917
37949
  import path113 from "path";
37918
37950
  async function readFileSafe4(filePath) {
37919
37951
  try {
@@ -38370,6 +38402,10 @@ async function runPhaseAiGraph(opts, ctx, docsDir) {
38370
38402
  const g6 = buildG6Content(project, ctx.manifest);
38371
38403
  await writeFile11(path113.join(docsDir, "graph-g6-multihop.md"), g6, "utf-8");
38372
38404
  log.debug(`deep-enrich[${project}]: G6 multi-hop analysis written`);
38405
+ const g5Path = path113.join(docsDir, "graph-g5-scenarios.md");
38406
+ if (await pathExists(g5Path)) {
38407
+ return { g5Generated: true, g6Generated: g6HasEdges };
38408
+ }
38373
38409
  let g5Generated = false;
38374
38410
  if (ctx.moduleDocs.size < 2) {
38375
38411
  log.warn(`deep-enrich[${project}]: Insufficient modules (${ctx.moduleDocs.size} < 2), skipping G5`);
@@ -38448,6 +38484,47 @@ async function runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags) {
38448
38484
  }
38449
38485
  log.debug(`deep-enrich[${project}]: Index incremental update complete`);
38450
38486
  }
38487
+ function architectureDocPath(docsDir) {
38488
+ return path113.join(docsDir, "architecture.md");
38489
+ }
38490
+ async function existingComponentSlugs(docsDir, slugs) {
38491
+ const found = [];
38492
+ for (const slug of slugs) {
38493
+ try {
38494
+ assertSafeResourceName(slug);
38495
+ } catch {
38496
+ continue;
38497
+ }
38498
+ if (await pathExists(path113.join(docsDir, `${slug}.md`))) found.push(slug);
38499
+ }
38500
+ return found;
38501
+ }
38502
+ async function collectMissingAiDocs(docsDir, slugs) {
38503
+ const existing = new Set(await existingComponentSlugs(docsDir, slugs));
38504
+ return {
38505
+ missingComponents: slugs.filter((slug) => !existing.has(slug)),
38506
+ missingArchitecture: !await pathExists(architectureDocPath(docsDir))
38507
+ };
38508
+ }
38509
+ async function removeIfExists(filePath) {
38510
+ try {
38511
+ await unlink2(filePath);
38512
+ } catch (err) {
38513
+ if (err.code !== "ENOENT") throw err;
38514
+ }
38515
+ }
38516
+ async function invalidateStaleAiDocs(docsDir, slugs) {
38517
+ for (const slug of slugs) {
38518
+ try {
38519
+ assertSafeResourceName(slug);
38520
+ } catch {
38521
+ continue;
38522
+ }
38523
+ await removeIfExists(path113.join(docsDir, `${slug}.md`));
38524
+ }
38525
+ await removeIfExists(architectureDocPath(docsDir));
38526
+ await removeIfExists(path113.join(docsDir, "graph-g5-scenarios.md"));
38527
+ }
38451
38528
  async function deepEnrich(opts) {
38452
38529
  const { project, evidenceDir } = opts;
38453
38530
  const docsDir = path113.join(evidenceDir, "docs");
@@ -38456,7 +38533,7 @@ async function deepEnrich(opts) {
38456
38533
  let components = ctx.manifest.components ?? [];
38457
38534
  if (components.length === 0) {
38458
38535
  log.warn(`deep-enrich[${project}]: No components in _manifest.json, aborting`);
38459
- return;
38536
+ return { project, complete: false, missingComponents: [], missingArchitecture: true };
38460
38537
  }
38461
38538
  if (opts.maxModules && components.length > opts.maxModules) {
38462
38539
  log.info(`deep-enrich[${project}]: Limited to first ${opts.maxModules} components (${components.length} total)`);
@@ -38465,52 +38542,55 @@ async function deepEnrich(opts) {
38465
38542
  }
38466
38543
  const allSlugs = components.map((c) => c.slug);
38467
38544
  const progress = await loadProgress(evidenceDir, project, allSlugs);
38468
- if (progress.phase === "done") {
38469
- const manifestTime = ctx.manifest.generatedAt;
38470
- const progressStale = manifestTime && progress.startedAt < manifestTime;
38471
- let docsEmpty = true;
38472
- if (await pathExists(docsDir)) {
38473
- const entries = await readdir4(docsDir).catch(() => []);
38474
- docsEmpty = entries.filter((e) => e.endsWith(".md")).length === 0;
38475
- }
38476
- if (docsEmpty || progressStale) {
38477
- log.info(`deep-enrich[${project}]: stale progress detected (phase=done, docs_empty=${docsEmpty}, progress_stale=${Boolean(progressStale)}), resetting`);
38478
- progress.phase = "pending";
38479
- progress.componentsDone = [];
38480
- progress.componentsPending = [...allSlugs];
38481
- progress.startedAt = (/* @__PURE__ */ new Date()).toISOString();
38482
- await saveProgress(evidenceDir, progress);
38483
- }
38545
+ const manifestTime = ctx.manifest.generatedAt;
38546
+ if (manifestTime && progress.startedAt < manifestTime) {
38547
+ log.info(
38548
+ `deep-enrich[${project}]: stale progress detected (manifest newer than progress.startedAt), invalidating AI docs`
38549
+ );
38550
+ await invalidateStaleAiDocs(docsDir, allSlugs);
38551
+ progress.phase = "pending";
38552
+ progress.componentsDone = [];
38553
+ progress.componentsPending = [...allSlugs];
38554
+ progress.startedAt = (/* @__PURE__ */ new Date()).toISOString();
38555
+ await saveProgress(evidenceDir, progress);
38484
38556
  }
38485
- if (progress.phase === "pending" || progress.phase === "components") {
38557
+ const existing = await existingComponentSlugs(docsDir, allSlugs);
38558
+ progress.componentsDone = existing;
38559
+ progress.componentsPending = allSlugs.filter((slug) => !existing.includes(slug));
38560
+ if (progress.componentsPending.length > 0) {
38486
38561
  progress.phase = "components";
38487
38562
  await saveProgress(evidenceDir, progress);
38488
38563
  await runPhaseComponents(opts, ctx, progress, docsDir);
38489
38564
  }
38490
- if (progress.phase === "components" || progress.phase === "architecture") {
38565
+ if (!await pathExists(architectureDocPath(docsDir))) {
38491
38566
  progress.phase = "architecture";
38492
38567
  await saveProgress(evidenceDir, progress);
38493
38568
  await runPhaseArchitecture(opts, ctx, docsDir);
38494
38569
  }
38495
- if (progress.phase === "architecture" || progress.phase === "graph") {
38496
- progress.phase = "graph";
38497
- await saveProgress(evidenceDir, progress);
38498
- await runPhaseGraph(opts, ctx, docsDir);
38499
- }
38500
- let graphFlags = { g5Generated: false, g6Generated: true };
38501
- if (progress.phase === "graph" || progress.phase === "ai-graph") {
38502
- progress.phase = "ai-graph";
38503
- await saveProgress(evidenceDir, progress);
38504
- graphFlags = await runPhaseAiGraph(opts, ctx, docsDir);
38505
- }
38506
- if (progress.phase === "ai-graph" || progress.phase === "index-enhance") {
38507
- progress.phase = "index-enhance";
38508
- await saveProgress(evidenceDir, progress);
38509
- await runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags);
38510
- }
38511
- progress.phase = "done";
38570
+ progress.phase = "graph";
38512
38571
  await saveProgress(evidenceDir, progress);
38513
- log.success(`deep-enrich[${project}]: Deep knowledge generation complete`);
38572
+ await runPhaseGraph(opts, ctx, docsDir);
38573
+ progress.phase = "ai-graph";
38574
+ await saveProgress(evidenceDir, progress);
38575
+ const graphFlags = await runPhaseAiGraph(opts, ctx, docsDir);
38576
+ progress.phase = "index-enhance";
38577
+ await saveProgress(evidenceDir, progress);
38578
+ await runPhaseIndexEnhance(opts, ctx, docsDir, graphFlags);
38579
+ const missing = await collectMissingAiDocs(docsDir, allSlugs);
38580
+ const complete = missing.missingComponents.length === 0 && !missing.missingArchitecture;
38581
+ progress.componentsDone = allSlugs.filter((slug) => !missing.missingComponents.includes(slug));
38582
+ progress.componentsPending = missing.missingComponents;
38583
+ progress.phase = complete ? "done" : missing.missingComponents.length > 0 ? "components" : "architecture";
38584
+ await saveProgress(evidenceDir, progress);
38585
+ if (complete) {
38586
+ log.success(`deep-enrich[${project}]: Deep knowledge generation complete`);
38587
+ } else {
38588
+ const missingComps = missing.missingComponents.join(", ") || "(none)";
38589
+ log.warn(
38590
+ `deep-enrich[${project}]: incomplete \u2014 missing component docs: ${missingComps}, architecture: ${missing.missingArchitecture}`
38591
+ );
38592
+ }
38593
+ return { project, complete, ...missing };
38514
38594
  }
38515
38595
  var PROGRESS_PATH_SUBDIR, PROGRESS_FILENAME, VALID_PHASES;
38516
38596
  var init_deep_enrich = __esm({
@@ -40670,12 +40750,26 @@ async function codebaseCmd(opts) {
40670
40750
  return;
40671
40751
  }
40672
40752
  const { deepEnrich: deepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
40673
- await deepEnrich2({ project, evidenceDir, wikiRoot: teamwikiDir });
40753
+ const result = await deepEnrich2({ project, evidenceDir, wikiRoot: teamwikiDir });
40674
40754
  if (opts.json) {
40675
- console.log(JSON.stringify({ project, evidenceDir, complete: true }, null, 2));
40676
- } else {
40755
+ console.log(JSON.stringify({
40756
+ project,
40757
+ evidenceDir,
40758
+ complete: result.complete,
40759
+ missingComponents: result.missingComponents,
40760
+ missingArchitecture: result.missingArchitecture
40761
+ }, null, 2));
40762
+ } else if (result.complete) {
40677
40763
  console.log(`Deep enrichment complete: project=${project}`);
40764
+ } else {
40765
+ const parts = [];
40766
+ if (result.missingComponents.length > 0) {
40767
+ parts.push(`missing component docs: ${result.missingComponents.join(", ")}`);
40768
+ }
40769
+ if (result.missingArchitecture) parts.push("missing architecture.md");
40770
+ console.log(`Deep enrichment incomplete: project=${project}, ${parts.join(", ")}`);
40678
40771
  }
40772
+ if (!result.complete) process.exitCode = 1;
40679
40773
  return;
40680
40774
  }
40681
40775
  if (opts.reconcile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.24.0-beta.5",
3
+ "version": "0.24.0-beta.6",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {