teamai-cli 0.20.0-beta.6 → 0.20.0

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/dist/index.js CHANGED
@@ -444,8 +444,8 @@ var init_types = __esm({
444
444
  tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
445
445
  cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
446
446
  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" },
447
- openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules" },
448
- hermes: { skills: ".hermes/skills" },
447
+ openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules", claudemd: ".openclaw/workspace/AGENTS.md" },
448
+ hermes: { skills: ".hermes/skills", claudemd: "AGENTS.md" },
449
449
  workbuddy: { skills: ".workbuddy/skills", rules: ".workbuddy/rules", settings: ".workbuddy/settings.json", claudemd: "AGENTS.md", mcp: ".workbuddy/mcp.json", mcpProject: ".workbuddy/mcp.json" }
450
450
  })
451
451
  });
@@ -1188,6 +1188,13 @@ teamai recall "<\u5173\u952E\u8BCD1> <\u5173\u952E\u8BCD2> ..."
1188
1188
 
1189
1189
  \u4ECE\u4EFB\u52A1\u63CF\u8FF0\u4E2D\u63D0\u53D6 3-6 \u4E2A\u9AD8\u4FE1\u53F7\u5173\u952E\u8BCD\u8FDB\u884C\u68C0\u7D22\u3002
1190
1190
 
1191
+ **\u52A1\u5FC5\u4E2D\u82F1\u53CC\u8BED\u68C0\u7D22\uFF08\u8DE8\u8BED\u8A00\u53EC\u56DE\uFF09**\uFF1A\u77E5\u8BC6\u5E93\u4E2D\u82F1\u6587\u6DF7\u6742\uFF0C\u68C0\u7D22\u662F\u7EAF\u8BCD\u6CD5\u5339\u914D\uFF0C
1192
+ \u4E2D\u6587 query \u65E0\u6CD5\u547D\u4E2D\u7EAF\u82F1\u6587\u6587\u6863\uFF0C\u53CD\u4E4B\u4EA6\u7136\u3002\u56E0\u6B64\u6BCF\u4E2A\u9886\u57DF\u672F\u8BED\u90FD\u5E94**\u540C\u65F6\u7ED9\u51FA\u4E2D\u82F1\u4E24\u79CD\u5199\u6CD5**
1193
+ \u4E00\u8D77\u68C0\u7D22\uFF0C\u4F8B\u5982\u300C\u91CD\u542F\u63A8\u7406\u96C6\u7FA4\u300D\u5E94\u6269\u5C55\u4E3A \`\u91CD\u542F \u63A8\u7406 \u96C6\u7FA4 restart inference cluster\`\uFF1B
1194
+ \u53EA\u7FFB\u8BD1\u4E2D\u82F1\u786E\u5B9E\u4E0D\u540C\u7684\u672F\u8BED\uFF1B\u4EFB\u4F55**\u8BED\u8A00\u65E0\u5173\u7684 token**\uFF08\u4E2D\u82F1\u5199\u6CD5\u76F8\u540C\u3001\u6216\u65E0\u5B9E\u9645\u8BD1\u6CD5\u8005\uFF09\u4E00\u5F8B\u4FDD\u6301\u539F\u6837\uFF0C
1195
+ \u4F8B\u5982\u4E13\u6709\u540D\u8BCD\u3001\u54C1\u724C\u4E0E\u4EA7\u54C1\u540D\u3001\u7F29\u5199\u3001\u4EE3\u7801\u6807\u8BC6\u7B26\u4E0E\u7B26\u53F7\u3001\u9519\u8BEF\u7801\u3001\u7248\u672C\u53F7\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u547D\u4EE4\u4E0E API \u540D
1196
+ \uFF08\`AppID\`\u3001\`Kubernetes\`\u3001\`GPU\`\u3001\`RuntimeError\`\u3001\`v2.1.3\`\u3001\`oauth\`\uFF09\u2014\u2014\u7FFB\u8BD1\u5B83\u4EEC\u53EA\u4F1A\u635F\u5BB3\u5339\u914D\u3002
1197
+
1191
1198
  ### \u68C0\u7D22\u540E\u8981\u6C42\uFF08\u7528\u4E8E\u8BC4\u4F30\u77E5\u8BC6\u88AB\u91C7\u7EB3\u60C5\u51B5\uFF0C\u8BF7\u52A1\u5FC5\u6267\u884C\uFF09
1192
1199
 
1193
1200
  teamai-recall subagent \u7684\u8FD4\u56DE\u91CC\u5DF2\u5217\u51FA\u672C\u6B21\u68C0\u7D22\u5230\u7684\u5019\u9009 doc-id\uFF08\u89C1\u5176 recalled-doc-ids \u5217\u8868\uFF09\u3002
@@ -1349,15 +1356,12 @@ function getTGitToken() {
1349
1356
  "No TGit credentials found. Set the TGIT_TOKEN environment variable (a git.woa.com Personal Access Token) or run `gf auth login`."
1350
1357
  );
1351
1358
  }
1352
- function tgitGitUser(scheme) {
1353
- return scheme === "private-token" ? "private" : "oauth2";
1354
- }
1355
- function tryGetTGitToken() {
1356
- try {
1357
- return getTGitToken();
1358
- } catch {
1359
+ function tgitGitCloneUrl(httpsUrl) {
1360
+ const oauthToken = gfGetOAuthToken();
1361
+ if (!oauthToken) {
1359
1362
  return null;
1360
1363
  }
1364
+ return httpsUrl.replace(/^https:\/\//, `https://oauth2:${oauthToken}@`);
1361
1365
  }
1362
1366
  function tgitAuthHeaders(token, scheme) {
1363
1367
  if (scheme === "bearer") {
@@ -1579,10 +1583,8 @@ function gitOutputSaysRepoMissing(output) {
1579
1583
  return output.includes("not found") || output.includes("does not exist") || output.includes("Repository not found");
1580
1584
  }
1581
1585
  function gfRepoClone(repo, localPath) {
1582
- const creds = tryGetTGitToken();
1583
- if (creds) {
1584
- const user = tgitGitUser(creds.scheme);
1585
- const cloneUrl = `https://${user}:${creds.token}@git.woa.com/${repo}.git`;
1586
+ const cloneUrl = tgitGitCloneUrl(`https://git.woa.com/${repo}.git`);
1587
+ if (cloneUrl) {
1586
1588
  const result2 = spawnSync("git", ["clone", cloneUrl, localPath], {
1587
1589
  encoding: "utf-8",
1588
1590
  stdio: ["pipe", "pipe", "pipe"],
@@ -1593,7 +1595,7 @@ function gfRepoClone(repo, localPath) {
1593
1595
  throw new RepoNotFoundError2(repo);
1594
1596
  }
1595
1597
  if (result2.status !== 0) {
1596
- const sanitized = allOutput2.replace(/(oauth2|private):[^@]+@/g, "$1:***@");
1598
+ const sanitized = allOutput2.replace(/oauth2:[^@]+@/g, "oauth2:***@");
1597
1599
  throw new Error(`git clone failed: ${sanitized.trim()}`);
1598
1600
  }
1599
1601
  return;
@@ -2856,9 +2858,191 @@ var init_builtin_skills = __esm({
2856
2858
  }
2857
2859
  });
2858
2860
 
2861
+ // src/openclaw-hooks.ts
2862
+ var openclaw_hooks_exports = {};
2863
+ __export(openclaw_hooks_exports, {
2864
+ OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
2865
+ applyOpenClawAgentHook: () => applyOpenClawAgentHook,
2866
+ injectOpenClawHooks: () => injectOpenClawHooks,
2867
+ removeOpenClawAgentHook: () => removeOpenClawAgentHook,
2868
+ removeOpenClawHooks: () => removeOpenClawHooks,
2869
+ resolveOpenClawHooksDir: () => resolveOpenClawHooksDir,
2870
+ resolveOpenclawWorkspaceDir: () => resolveOpenclawWorkspaceDir
2871
+ });
2872
+ import path10 from "path";
2873
+ function resolveOpenClawHooksDir(tool) {
2874
+ if (tool === "openclaw" && process.env.OPENCLAW_STATE_DIR) {
2875
+ return path10.join(process.env.OPENCLAW_STATE_DIR, "hooks");
2876
+ }
2877
+ const home = process.env.HOME ?? "";
2878
+ return path10.join(home, `.${tool}`, "hooks");
2879
+ }
2880
+ function buildHookMd(tool) {
2881
+ const events = Object.keys(EVENT_MAP);
2882
+ const metadata = JSON.stringify({ openclaw: { events } });
2883
+ return [
2884
+ "---",
2885
+ `name: ${TEAMAI_MARKER} status-report`,
2886
+ `metadata:`,
2887
+ ` ${metadata}`,
2888
+ `handler: ./handler.ts`,
2889
+ "---",
2890
+ "",
2891
+ `${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
2892
+ "Managed by teamai \u2014 do not edit by hand.",
2893
+ ""
2894
+ ].join("\n");
2895
+ }
2896
+ function buildHandlerTs(tool) {
2897
+ const mapLiteral = JSON.stringify(EVENT_MAP);
2898
+ return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
2899
+ import { spawn } from 'node:child_process';
2900
+
2901
+ const EVENT_MAP: Record<string, string> = ${mapLiteral};
2902
+ const TOOL = ${JSON.stringify(tool)};
2903
+
2904
+ export default async function handler(ctx: { event?: string } = {}): Promise<void> {
2905
+ const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
2906
+ if (!dispatchEvent) return;
2907
+ try {
2908
+ const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
2909
+ stdio: ['inherit', 'ignore', 'ignore'],
2910
+ });
2911
+ child.on('error', () => {});
2912
+ } catch {
2913
+ // never block the agent
2914
+ }
2915
+ }
2916
+ `;
2917
+ }
2918
+ async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
2919
+ const effectiveHooksDir = resolveOpenClawHooksDir(tool);
2920
+ const dir = path10.join(effectiveHooksDir, OPENCLAW_HOOK_DIR);
2921
+ await ensureDir(dir);
2922
+ await writeFile(path10.join(dir, "HOOK.md"), buildHookMd(tool));
2923
+ await writeFile(path10.join(dir, "handler.ts"), buildHandlerTs(tool));
2924
+ log.success(`Injected teamai OpenClaw hook into ${dir}`);
2925
+ }
2926
+ async function removeOpenClawHooks(hooksDir) {
2927
+ const dir = path10.join(hooksDir, OPENCLAW_HOOK_DIR);
2928
+ if (await pathExists(dir)) {
2929
+ await remove(dir);
2930
+ log.success(`Removed teamai OpenClaw hook from ${dir}`);
2931
+ }
2932
+ if (process.env.OPENCLAW_STATE_DIR) {
2933
+ const altDir = path10.join(process.env.OPENCLAW_STATE_DIR, "hooks", OPENCLAW_HOOK_DIR);
2934
+ if (altDir !== dir && await pathExists(altDir)) {
2935
+ await remove(altDir);
2936
+ log.success(`Removed teamai OpenClaw hook from ${altDir}`);
2937
+ }
2938
+ }
2939
+ }
2940
+ function buildAgentHookMd(slug, openclawEvent) {
2941
+ const metadata = JSON.stringify({ openclaw: { events: [openclawEvent] } });
2942
+ return [
2943
+ "---",
2944
+ `name: ${TEAMAI_MARKER} ${slug}`,
2945
+ `metadata:`,
2946
+ ` ${metadata}`,
2947
+ `handler: ./handler.ts`,
2948
+ "---",
2949
+ "",
2950
+ `${TEAMAI_MARKER} Agent hook [${slug}] \u2014 managed by teamai, do not edit by hand.`,
2951
+ ""
2952
+ ].join("\n");
2953
+ }
2954
+ function buildAgentHandlerTs(command, timeout) {
2955
+ const timeoutMs = timeout * 1e3;
2956
+ return [
2957
+ `// ${TEAMAI_MARKER} agent hook handler \u2014 generated by teamai, do not edit.`,
2958
+ `import { spawn } from 'node:child_process';`,
2959
+ "",
2960
+ `export default async function handler(): Promise<void> {`,
2961
+ " try {",
2962
+ ` const child = spawn('sh', ['-c', ${JSON.stringify(command)}], {`,
2963
+ ` stdio: ['inherit', 'ignore', 'ignore'],`,
2964
+ ` timeout: ${timeoutMs},`,
2965
+ " });",
2966
+ " child.on('error', () => {});",
2967
+ " } catch {",
2968
+ " // never block the agent",
2969
+ " }",
2970
+ "}",
2971
+ ""
2972
+ ].join("\n");
2973
+ }
2974
+ async function applyOpenClawAgentHook(def) {
2975
+ const openclawEvent = CLAUDE_TO_OPENCLAW_EVENTS[def.event];
2976
+ if (!openclawEvent) {
2977
+ log.warn(`OpenClaw does not support event "${def.event}" \u2014 skipping hook [${def.slug}]`);
2978
+ return;
2979
+ }
2980
+ const tool = def.tool ?? "openclaw";
2981
+ const hooksDir = resolveOpenClawHooksDir(tool);
2982
+ const dir = path10.join(hooksDir, def.slug);
2983
+ await ensureDir(dir);
2984
+ await writeFile(path10.join(dir, "HOOK.md"), buildAgentHookMd(def.slug, openclawEvent));
2985
+ await writeFile(path10.join(dir, "handler.ts"), buildAgentHandlerTs(def.command, def.timeout ?? 10));
2986
+ log.success(`Installed OpenClaw agent hook [${def.slug}] in ${dir}`);
2987
+ }
2988
+ async function removeOpenClawAgentHook(opts) {
2989
+ const tool = opts.tool ?? "openclaw";
2990
+ const hooksDir = resolveOpenClawHooksDir(tool);
2991
+ const dir = path10.join(hooksDir, opts.slug);
2992
+ if (await pathExists(dir)) {
2993
+ await remove(dir);
2994
+ log.success(`Removed OpenClaw agent hook [${opts.slug}] from ${dir}`);
2995
+ }
2996
+ }
2997
+ async function resolveOpenclawWorkspaceDir(workspacePath) {
2998
+ const candidates = [];
2999
+ if (workspacePath) candidates.push(workspacePath);
3000
+ const stateDir = process.env.OPENCLAW_STATE_DIR;
3001
+ if (stateDir && path10.isAbsolute(stateDir)) {
3002
+ const cfgRaw = await readFileSafe(path10.join(stateDir, "openclaw.json"));
3003
+ if (cfgRaw) {
3004
+ try {
3005
+ const cfg = JSON.parse(cfgRaw);
3006
+ const ws = cfg?.agents?.defaults?.workspace;
3007
+ if (typeof ws === "string" && ws) candidates.push(ws);
3008
+ } catch (e) {
3009
+ log.debug(`openclaw: failed to parse openclaw.json: ${e.message}`);
3010
+ }
3011
+ }
3012
+ }
3013
+ const home = process.env.HOME;
3014
+ if (home) candidates.push(path10.join(home, ".openclaw", "workspace"));
3015
+ for (const candidate of candidates) {
3016
+ if (await pathExists(candidate)) {
3017
+ log.debug(`openclaw: resolved workspace dir to ${candidate}`);
3018
+ return candidate;
3019
+ }
3020
+ }
3021
+ log.warn(`openclaw: no workspace dir found (tried: ${candidates.join(", ") || "none"})`);
3022
+ return null;
3023
+ }
3024
+ var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP, CLAUDE_TO_OPENCLAW_EVENTS;
3025
+ var init_openclaw_hooks = __esm({
3026
+ "src/openclaw-hooks.ts"() {
3027
+ "use strict";
3028
+ init_fs();
3029
+ init_logger();
3030
+ OPENCLAW_HOOK_DIR = "teamai-status-report";
3031
+ TEAMAI_MARKER = "[teamai]";
3032
+ EVENT_MAP = {
3033
+ "session:start": "session-start",
3034
+ "command:new": "prompt-submit"
3035
+ };
3036
+ CLAUDE_TO_OPENCLAW_EVENTS = {
3037
+ SessionStart: "session:start",
3038
+ UserPromptSubmit: "command:new"
3039
+ };
3040
+ }
3041
+ });
3042
+
2859
3043
  // src/builtin-hooks.ts
2860
3044
  import fs5 from "fs";
2861
- import path10 from "path";
3045
+ import path11 from "path";
2862
3046
  import { fileURLToPath as fileURLToPath2 } from "url";
2863
3047
  function hasShell() {
2864
3048
  if (_hasShellCache === void 0) {
@@ -2886,12 +3070,12 @@ function pickLatestVersion(versions) {
2886
3070
  }
2887
3071
  function resolveWorkbuddyNode() {
2888
3072
  const home = process.env.HOME ?? "";
2889
- const versionsDir = path10.join(home, WORKBUDDY_BUNDLED_NODE_DIR);
3073
+ const versionsDir = path11.join(home, WORKBUDDY_BUNDLED_NODE_DIR);
2890
3074
  try {
2891
3075
  const versions = fs5.readdirSync(versionsDir).filter((d) => !d.startsWith("."));
2892
3076
  const latest = pickLatestVersion(versions);
2893
3077
  if (!latest) return null;
2894
- const nodeBin = path10.join(versionsDir, latest, "bin", "node");
3078
+ const nodeBin = path11.join(versionsDir, latest, "bin", "node");
2895
3079
  if (fs5.existsSync(nodeBin)) return nodeBin;
2896
3080
  } catch {
2897
3081
  }
@@ -2904,10 +3088,10 @@ function resolveCodebuddyNode() {
2904
3088
  for (const entry of entries) {
2905
3089
  if (!entry.startsWith(".codebuddy-server")) continue;
2906
3090
  try {
2907
- const binDir = path10.join(home, entry, "bin");
3091
+ const binDir = path11.join(home, entry, "bin");
2908
3092
  const stableDirs = fs5.readdirSync(binDir).filter((d) => d.startsWith("stable-"));
2909
3093
  for (const stable of stableDirs) {
2910
- const nodeBin = path10.join(binDir, stable, "node");
3094
+ const nodeBin = path11.join(binDir, stable, "node");
2911
3095
  if (fs5.existsSync(nodeBin)) return nodeBin;
2912
3096
  }
2913
3097
  } catch {
@@ -2920,8 +3104,8 @@ function resolveCodebuddyNode() {
2920
3104
  function resolveTeamaiEntryScript() {
2921
3105
  try {
2922
3106
  const thisFile = fileURLToPath2(import.meta.url);
2923
- const distDir = path10.dirname(thisFile);
2924
- const candidate = path10.join(distDir, "index.js");
3107
+ const distDir = path11.dirname(thisFile);
3108
+ const candidate = path11.join(distDir, "index.js");
2925
3109
  if (fs5.existsSync(candidate)) return candidate;
2926
3110
  } catch {
2927
3111
  }
@@ -2932,8 +3116,8 @@ function ensureTeamaiWrapper() {
2932
3116
  if (!entryScript) return null;
2933
3117
  const nodeBin = resolveWorkbuddyNode() ?? resolveCodebuddyNode() ?? process.argv[0];
2934
3118
  const home = process.env.HOME ?? "";
2935
- const binDir = path10.join(home, TEAMAI_BIN_DIR);
2936
- const wrapperPath = path10.join(binDir, WRAPPER_NAME);
3119
+ const binDir = path11.join(home, TEAMAI_BIN_DIR);
3120
+ const wrapperPath = path11.join(binDir, WRAPPER_NAME);
2937
3121
  const script = [
2938
3122
  "#!/bin/sh",
2939
3123
  `# Auto-generated by teamai \u2014 do not edit.`,
@@ -3007,11 +3191,11 @@ var init_builtin_hooks = __esm({
3007
3191
  });
3008
3192
 
3009
3193
  // src/resources/hooks.ts
3010
- import path11 from "path";
3194
+ import path12 from "path";
3011
3195
  import { z as z3 } from "zod";
3012
3196
  import YAML2 from "yaml";
3013
3197
  function teamHooksYamlPath(repoPath) {
3014
- return path11.join(repoPath, "hooks", "hooks.yaml");
3198
+ return path12.join(repoPath, "hooks", "hooks.yaml");
3015
3199
  }
3016
3200
  async function parseHooksYaml(repoPath) {
3017
3201
  const content = await readFileSafe(teamHooksYamlPath(repoPath));
@@ -3134,80 +3318,6 @@ var init_hooks = __esm({
3134
3318
  }
3135
3319
  });
3136
3320
 
3137
- // src/openclaw-hooks.ts
3138
- var openclaw_hooks_exports = {};
3139
- __export(openclaw_hooks_exports, {
3140
- OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
3141
- injectOpenClawHooks: () => injectOpenClawHooks,
3142
- removeOpenClawHooks: () => removeOpenClawHooks
3143
- });
3144
- import path12 from "path";
3145
- function buildHookMd(tool) {
3146
- const events = Object.keys(EVENT_MAP);
3147
- return [
3148
- "---",
3149
- `name: ${TEAMAI_MARKER} status-report`,
3150
- "events:",
3151
- ...events.map((e) => ` - ${e}`),
3152
- `handler: ./handler.ts`,
3153
- "---",
3154
- "",
3155
- `${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
3156
- "Managed by teamai \u2014 do not edit by hand.",
3157
- ""
3158
- ].join("\n");
3159
- }
3160
- function buildHandlerTs(tool) {
3161
- const mapLiteral = JSON.stringify(EVENT_MAP);
3162
- return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
3163
- import { spawn } from 'node:child_process';
3164
-
3165
- const EVENT_MAP: Record<string, string> = ${mapLiteral};
3166
- const TOOL = ${JSON.stringify(tool)};
3167
-
3168
- export default async function handler(ctx: { event?: string } = {}): Promise<void> {
3169
- const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
3170
- if (!dispatchEvent) return;
3171
- try {
3172
- const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
3173
- stdio: ['inherit', 'ignore', 'ignore'],
3174
- });
3175
- child.on('error', () => {});
3176
- } catch {
3177
- // never block the agent
3178
- }
3179
- }
3180
- `;
3181
- }
3182
- async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
3183
- const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
3184
- await ensureDir(dir);
3185
- await writeFile(path12.join(dir, "HOOK.md"), buildHookMd(tool));
3186
- await writeFile(path12.join(dir, "handler.ts"), buildHandlerTs(tool));
3187
- log.success(`Injected teamai OpenClaw hook into ${dir}`);
3188
- }
3189
- async function removeOpenClawHooks(hooksDir) {
3190
- const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
3191
- if (await pathExists(dir)) {
3192
- await remove(dir);
3193
- log.success(`Removed teamai OpenClaw hook from ${dir}`);
3194
- }
3195
- }
3196
- var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP;
3197
- var init_openclaw_hooks = __esm({
3198
- "src/openclaw-hooks.ts"() {
3199
- "use strict";
3200
- init_fs();
3201
- init_logger();
3202
- OPENCLAW_HOOK_DIR = "teamai-status-report";
3203
- TEAMAI_MARKER = "[teamai]";
3204
- EVENT_MAP = {
3205
- "session:start": "session-start",
3206
- "command:new": "prompt-submit"
3207
- };
3208
- }
3209
- });
3210
-
3211
3321
  // src/hermes-home.ts
3212
3322
  var hermes_home_exports = {};
3213
3323
  __export(hermes_home_exports, {
@@ -3477,6 +3587,7 @@ var hooks_exports = {};
3477
3587
  __export(hooks_exports, {
3478
3588
  AGENT_HOOK_EVENTS: () => AGENT_HOOK_EVENTS,
3479
3589
  CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
3590
+ OPENCLAW_TOOLS: () => OPENCLAW_TOOLS,
3480
3591
  TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
3481
3592
  TEAMAI_LEGACY_HOOK_SUBCOMMANDS: () => TEAMAI_LEGACY_HOOK_SUBCOMMANDS,
3482
3593
  agentHookDescription: () => agentHookDescription,
@@ -3684,7 +3795,7 @@ async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCo
3684
3795
  }
3685
3796
  }
3686
3797
  function isAgentHookSupportedTool(tool) {
3687
- return !CURSOR_TOOLS.has(tool) && !OPENCLAW_TOOLS.has(tool);
3798
+ return !CURSOR_TOOLS.has(tool);
3688
3799
  }
3689
3800
  function isAgentHookEvent(event) {
3690
3801
  return AGENT_HOOK_EVENTS.has(event);
@@ -4333,9 +4444,9 @@ async function scanTranscriptStop(transcriptPath) {
4333
4444
  }
4334
4445
  if (entry.type === "assistant") {
4335
4446
  const usage = entry.message?.usage;
4336
- const dedupKey = typeof entry.message?.id === "string" ? entry.message.id : typeof entry.requestId === "string" ? entry.requestId : void 0;
4337
- if (usage && dedupKey && !countedUsageKeys.has(dedupKey)) {
4338
- countedUsageKeys.add(dedupKey);
4447
+ const dedupKey2 = typeof entry.message?.id === "string" ? entry.message.id : typeof entry.requestId === "string" ? entry.requestId : void 0;
4448
+ if (usage && dedupKey2 && !countedUsageKeys.has(dedupKey2)) {
4449
+ countedUsageKeys.add(dedupKey2);
4339
4450
  tokens.input += toNum(usage.input_tokens);
4340
4451
  tokens.output += toNum(usage.output_tokens);
4341
4452
  tokens.cacheRead += toNum(usage.cache_read_input_tokens);
@@ -4793,6 +4904,16 @@ async function detectWorkbuddyVersion() {
4793
4904
  }
4794
4905
  return "";
4795
4906
  }
4907
+ async function detectHermesVersion() {
4908
+ const raw = await execVersion("hermes");
4909
+ const match = raw.match(/^\(?(\d+(?:\.\d+)*)\)?/);
4910
+ return match?.[1] ?? "";
4911
+ }
4912
+ async function detectOpenclawVersion() {
4913
+ const raw = await execVersion("openclaw");
4914
+ const match = raw.match(/^([\d.]+)/);
4915
+ return match?.[1] ?? "";
4916
+ }
4796
4917
  async function getAgentVersion(agentType) {
4797
4918
  if (VERSION_CACHE.has(agentType)) return VERSION_CACHE.get(agentType);
4798
4919
  const detector = DETECTORS[agentType];
@@ -4825,7 +4946,9 @@ var init_agent_version = __esm({
4825
4946
  cursor: detectCursorVersion,
4826
4947
  codebuddy: detectCodebuddyCliVersion,
4827
4948
  "codebuddy-ide": detectCodebuddyIdeVersion,
4828
- workbuddy: detectWorkbuddyVersion
4949
+ workbuddy: detectWorkbuddyVersion,
4950
+ hermes: detectHermesVersion,
4951
+ openclaw: detectOpenclawVersion
4829
4952
  };
4830
4953
  }
4831
4954
  });
@@ -4834,24 +4957,29 @@ var init_agent_version = __esm({
4834
4957
  import crypto2 from "crypto";
4835
4958
  import fs8 from "fs";
4836
4959
  import { execFileSync } from "child_process";
4960
+ import os2 from "os";
4837
4961
  function getMachineId() {
4838
4962
  if (cachedMachineId !== null) return cachedMachineId;
4839
4963
  cachedMachineId = detectMachineId();
4840
4964
  return cachedMachineId;
4841
4965
  }
4842
4966
  function detectMachineId(platform = process.platform) {
4967
+ let id = "";
4843
4968
  try {
4844
4969
  switch (platform) {
4845
4970
  case "darwin":
4846
- return readDarwinMachineId();
4971
+ id = readDarwinMachineId();
4972
+ break;
4847
4973
  case "win32":
4848
- return readWindowsMachineId();
4974
+ id = readWindowsMachineId();
4975
+ break;
4849
4976
  default:
4850
- return readLinuxMachineId();
4977
+ id = readLinuxMachineId();
4978
+ break;
4851
4979
  }
4852
4980
  } catch {
4853
- return "";
4854
4981
  }
4982
+ return id || os2.hostname() || "";
4855
4983
  }
4856
4984
  function readDarwinMachineId() {
4857
4985
  const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
@@ -4896,7 +5024,7 @@ var init_machine_id = __esm({
4896
5024
  });
4897
5025
 
4898
5026
  // src/utils/path-safety.ts
4899
- import os2 from "os";
5027
+ import os3 from "os";
4900
5028
  import path19 from "path";
4901
5029
  import fs9 from "fs";
4902
5030
  function assertSafePath(target, allowedRoots) {
@@ -4912,7 +5040,7 @@ function assertSafePath(target, allowedRoots) {
4912
5040
  );
4913
5041
  }
4914
5042
  function resolveReal(p) {
4915
- const expanded = p.startsWith("~") ? path19.join(os2.homedir(), p.slice(1)) : p;
5043
+ const expanded = p.startsWith("~") ? path19.join(os3.homedir(), p.slice(1)) : p;
4916
5044
  const abs = path19.resolve(expanded);
4917
5045
  try {
4918
5046
  return fs9.realpathSync(abs);
@@ -4921,7 +5049,7 @@ function resolveReal(p) {
4921
5049
  }
4922
5050
  }
4923
5051
  function defaultAllowedRoots() {
4924
- return [process.cwd(), os2.homedir()];
5052
+ return [process.cwd(), os3.homedir()];
4925
5053
  }
4926
5054
  function assertSafeResourceName(name) {
4927
5055
  if (name.includes("\0")) {
@@ -5300,7 +5428,7 @@ __export(local_agent_exports, {
5300
5428
  writeTokenFile: () => writeTokenFile
5301
5429
  });
5302
5430
  import fs10 from "fs";
5303
- import os3 from "os";
5431
+ import os4 from "os";
5304
5432
  import path20 from "path";
5305
5433
  import readline3 from "readline";
5306
5434
  import { execFile as execFile2 } from "child_process";
@@ -5525,7 +5653,7 @@ function createResourceLocalConfig(config, scope, repoPath, workspacePath) {
5525
5653
  const projectScope = scope === "project";
5526
5654
  return {
5527
5655
  repo: { localPath: repoPath, remote: config.endpoint },
5528
- username: os3.userInfo().username,
5656
+ username: os4.userInfo().username,
5529
5657
  scope: projectScope ? "project" : "user",
5530
5658
  projectRoot: projectScope ? workspacePath : void 0,
5531
5659
  additionalRoles: []
@@ -5835,7 +5963,7 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
5835
5963
  async function ensureWorkspaceBinding(config, workspacePath, sessionId) {
5836
5964
  if (config.workspaceBindings[workspacePath]) return;
5837
5965
  const markerKey = sessionId || `ppid-${process.ppid}`;
5838
- const hintMarker = path20.join(os3.tmpdir(), `teamai-bind-session-${markerKey}`);
5966
+ const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-session-${markerKey}`);
5839
5967
  if (fs10.existsSync(hintMarker)) return;
5840
5968
  try {
5841
5969
  fs10.writeFileSync(hintMarker, "");
@@ -5883,7 +6011,7 @@ function isBindPromptEnabled() {
5883
6011
  async function emitBindingHint(config, workspacePath, sessionId) {
5884
6012
  if (config.workspaceBindings[workspacePath]) return;
5885
6013
  const markerKey = sessionId || `ppid-${process.ppid}`;
5886
- const hintMarker = path20.join(os3.tmpdir(), `teamai-bind-hint-${markerKey}`);
6014
+ const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-hint-${markerKey}`);
5887
6015
  if (fs10.existsSync(hintMarker)) return;
5888
6016
  try {
5889
6017
  fs10.writeFileSync(hintMarker, "");
@@ -6044,8 +6172,8 @@ async function buildReportPayload(config, context) {
6044
6172
  agent_type: normalizeAgentType(tool),
6045
6173
  agent_version: await getAgentVersion(tool),
6046
6174
  local_agent_id: resolveLocalAgentId(context),
6047
- host_name: os3.hostname(),
6048
- os: os3.platform(),
6175
+ host_name: os4.hostname(),
6176
+ os: os4.platform(),
6049
6177
  started_at: config.createdAt,
6050
6178
  last_status: context.status ?? "running",
6051
6179
  // Instance-level skills/rules are a phase-1 legacy concept. They are
@@ -6156,7 +6284,7 @@ function assertHttpUrl(rawUrl) {
6156
6284
  return parsed;
6157
6285
  }
6158
6286
  async function downloadResource(downloadUrl) {
6159
- const tmpDir = await fs10.promises.mkdtemp(path20.join(os3.tmpdir(), "teamai-local-agent-"));
6287
+ const tmpDir = await fs10.promises.mkdtemp(path20.join(os4.tmpdir(), "teamai-local-agent-"));
6160
6288
  const filePath = path20.join(tmpDir, "resource");
6161
6289
  let current = assertHttpUrl(downloadUrl);
6162
6290
  let response;
@@ -6326,7 +6454,7 @@ async function installDownloadedResource(input) {
6326
6454
  const dest = path20.join(repoPath, "claudemd", `${input.slug}.md`);
6327
6455
  await fse3.ensureDir(path20.dirname(dest));
6328
6456
  await fse3.copyFile(mdFile, dest);
6329
- await syncClaudemd(teamConfig, localConfig, repoPath);
6457
+ await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
6330
6458
  }
6331
6459
  const version2 = commandVersion(input.command, input.kind);
6332
6460
  const manifest = await loadManifest();
@@ -6364,12 +6492,28 @@ async function uninstallResource(input) {
6364
6492
  await new RulesHandler().removeItem(input.slug, teamConfig, localConfig);
6365
6493
  } else {
6366
6494
  await remove(path20.join(repoPath, "claudemd", `${input.slug}.md`));
6367
- await syncClaudemd(teamConfig, localConfig, repoPath);
6495
+ await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
6368
6496
  }
6369
6497
  delete scopeManifest[manifestKind(input.kind)][input.slug];
6370
6498
  await saveManifest(manifest);
6371
6499
  }
6372
- async function syncClaudemd(teamConfig, localConfig, repoPath) {
6500
+ async function resolveHermesUserBaseDir() {
6501
+ try {
6502
+ const envWs = process.env.TEAMAI_HERMES_WORKSPACE;
6503
+ if (envWs && path20.isAbsolute(envWs)) return envWs;
6504
+ const cfg = await readJson(getConfigPath2());
6505
+ const bindings = cfg?.workspaceBindings;
6506
+ if (bindings && typeof bindings === "object") {
6507
+ const entries = Object.entries(bindings).filter(([p, v]) => path20.isAbsolute(p) && v?.ideType === "hermes").sort((a, b) => (b[1].boundAt ?? "").localeCompare(a[1].boundAt ?? ""));
6508
+ for (const [p] of entries) {
6509
+ if (await pathExists(path20.join(p, ".hermes"))) return p;
6510
+ }
6511
+ }
6512
+ } catch {
6513
+ }
6514
+ return void 0;
6515
+ }
6516
+ async function syncClaudemd(teamConfig, localConfig, repoPath, workspacePath) {
6373
6517
  const claudemdDir = path20.join(repoPath, "claudemd");
6374
6518
  const files = await pathExists(claudemdDir) ? (await fse3.readdir(claudemdDir)).filter((file) => file.endsWith(".md")).sort() : [];
6375
6519
  const contents = [];
@@ -6378,24 +6522,48 @@ async function syncClaudemd(teamConfig, localConfig, repoPath) {
6378
6522
  if (content) contents.push(content);
6379
6523
  }
6380
6524
  const block = compileClaudemdBlock(contents);
6381
- const baseDir = localConfig.scope === "project" && localConfig.projectRoot ? localConfig.projectRoot : process.env.HOME ?? "";
6525
+ let syncedAny = false;
6526
+ const defaultBaseDir = localConfig.scope === "project" && localConfig.projectRoot ? localConfig.projectRoot : process.env.HOME ?? "";
6382
6527
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
6383
6528
  if (!toolPath.claudemd) continue;
6384
- if (!await ResourceHandler.isToolInstalled(toolPath.claudemd, baseDir)) continue;
6385
- const claudeMdPath = path20.join(baseDir, toolPath.claudemd);
6529
+ let baseDir = defaultBaseDir;
6530
+ let resolvedAbsPath = null;
6531
+ if (tool === "openclaw" && localConfig.scope !== "project") {
6532
+ const openclawWs = await resolveOpenclawWorkspaceDir(workspacePath);
6533
+ if (openclawWs) {
6534
+ resolvedAbsPath = path20.join(openclawWs, path20.basename(toolPath.claudemd));
6535
+ }
6536
+ } else if (tool === "hermes" && localConfig.scope !== "project") {
6537
+ const hermesBase = workspacePath ?? await resolveHermesUserBaseDir();
6538
+ if (hermesBase) {
6539
+ baseDir = hermesBase;
6540
+ log.debug(`local-agent: hermes user-scope baseDir resolved to ${baseDir}`);
6541
+ }
6542
+ }
6543
+ const toolInstalled = resolvedAbsPath ? await pathExists(resolvedAbsPath) : toolPath.claudemd.includes("/") ? await ResourceHandler.isToolInstalled(toolPath.claudemd, baseDir) : await pathExists(path20.join(baseDir, `.${tool}`));
6544
+ if (!toolInstalled) {
6545
+ log.debug(`Skipped CLAUDE.md sync for ${tool}: target not found`);
6546
+ continue;
6547
+ }
6548
+ const claudeMdPath = resolvedAbsPath ?? path20.join(baseDir, toolPath.claudemd);
6386
6549
  try {
6387
6550
  const { injectClaudeMdSection: injectClaudeMdSection2 } = await Promise.resolve().then(() => (init_claudemd(), claudemd_exports));
6388
6551
  if (block) {
6389
6552
  await injectClaudeMdSection2(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, block);
6390
6553
  log.debug(`local-agent: synced CLAUDE.md instructions to ${tool}`);
6554
+ syncedAny = true;
6391
6555
  } else {
6392
6556
  await removeClaudeMdSection(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END);
6393
6557
  log.debug(`local-agent: removed CLAUDE.md instructions from ${tool}`);
6558
+ syncedAny = true;
6394
6559
  }
6395
6560
  } catch (e) {
6396
6561
  log.warn(`Failed to sync CLAUDE.md instructions to ${tool}: ${e.message}`);
6397
6562
  }
6398
6563
  }
6564
+ if (files.length > 0 && !syncedAny) {
6565
+ throw new Error("CLAUDE.md sync landed on no tool: every configured target was skipped");
6566
+ }
6399
6567
  }
6400
6568
  async function removeClaudeMdSection(filePath, startMarker, endMarker) {
6401
6569
  const existing = await readFileSafe(filePath);
@@ -6521,6 +6689,9 @@ async function runHookRuleCommand(config, command, context) {
6521
6689
  if (rec.tool === "hermes") {
6522
6690
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6523
6691
  await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
6692
+ } else if (OPENCLAW_TOOLS.has(rec.tool)) {
6693
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6694
+ await removeOpenClawAgentHook2({ slug, tool: rec.tool });
6524
6695
  } else {
6525
6696
  const settingsPath = resolveToolSettingsPath(config, rec.tool);
6526
6697
  await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
@@ -6546,6 +6717,9 @@ async function runHookRuleCommand(config, command, context) {
6546
6717
  if (prior.tool === "hermes") {
6547
6718
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6548
6719
  await removeHermesAgentHook2({ slug, event: prior.event, command: prior.command });
6720
+ } else if (OPENCLAW_TOOLS.has(prior.tool)) {
6721
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6722
+ await removeOpenClawAgentHook2({ slug, tool: prior.tool });
6549
6723
  } else {
6550
6724
  const priorPath = resolveToolSettingsPath(config, prior.tool);
6551
6725
  await removeAgentHook(priorPath, prior.tool, { slug, command: prior.command });
@@ -6557,6 +6731,9 @@ async function runHookRuleCommand(config, command, context) {
6557
6731
  if (tool === "hermes") {
6558
6732
  const { applyHermesAgentHook: applyHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6559
6733
  await applyHermesAgentHook2({ slug, event, command: cmd, matcher, timeout });
6734
+ } else if (OPENCLAW_TOOLS.has(tool)) {
6735
+ const { applyOpenClawAgentHook: applyOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6736
+ await applyOpenClawAgentHook2({ slug, event, command: cmd, tool, matcher, timeout });
6560
6737
  } else {
6561
6738
  const settingsPath = resolveToolSettingsPath(config, tool);
6562
6739
  await applyAgentHook(settingsPath, tool, { slug, event, command: cmd, matcher, timeout });
@@ -6781,6 +6958,9 @@ async function removeAllAgentHooks() {
6781
6958
  if (rec.tool === "hermes") {
6782
6959
  const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
6783
6960
  await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
6961
+ } else if (OPENCLAW_TOOLS.has(rec.tool)) {
6962
+ const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
6963
+ await removeOpenClawAgentHook2({ slug, tool: rec.tool });
6784
6964
  } else {
6785
6965
  const settingsPath = resolveToolSettingsPath(config, rec.tool);
6786
6966
  await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
@@ -6854,6 +7034,7 @@ var init_local_agent = __esm({
6854
7034
  init_machine_id();
6855
7035
  init_builtin_rules();
6856
7036
  init_builtin_hooks();
7037
+ init_openclaw_hooks();
6857
7038
  init_path_safety();
6858
7039
  init_tool_names();
6859
7040
  init_http_log();
@@ -7442,6 +7623,7 @@ var init_skills = __esm({
7442
7623
  init_fs();
7443
7624
  init_logger();
7444
7625
  init_builtin_skills();
7626
+ init_openclaw_hooks();
7445
7627
  init_roles();
7446
7628
  CONTRIBUTORS_FILE = "CONTRIBUTORS";
7447
7629
  SKILL_MD = "SKILL.md";
@@ -7627,11 +7809,21 @@ var init_skills = __esm({
7627
7809
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
7628
7810
  if (isAgentDisabled(localConfig, tool)) continue;
7629
7811
  if (!toolPath.skills) continue;
7630
- if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
7631
- log.debug(`Skipping skill sync for ${tool}: tool not installed`);
7632
- continue;
7812
+ let dest;
7813
+ if (tool === "openclaw") {
7814
+ const wsDir = await resolveOpenclawWorkspaceDir();
7815
+ if (!wsDir) {
7816
+ log.debug(`Skipping skill sync for openclaw: workspace dir not found`);
7817
+ continue;
7818
+ }
7819
+ dest = path22.join(wsDir, "skills", item.name);
7820
+ } else {
7821
+ if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
7822
+ log.debug(`Skipping skill sync for ${tool}: tool not installed`);
7823
+ continue;
7824
+ }
7825
+ dest = path22.join(baseDir, toolPath.skills, item.name);
7633
7826
  }
7634
- const dest = path22.join(baseDir, toolPath.skills, item.name);
7635
7827
  try {
7636
7828
  await copyDir(item.sourcePath, dest);
7637
7829
  await ensureSkillFrontmatter(dest, item.name);
@@ -7666,7 +7858,14 @@ var init_skills = __esm({
7666
7858
  await this.addTombstone(name, localConfig);
7667
7859
  for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
7668
7860
  if (!toolPath.skills) continue;
7669
- const skillDir = path22.join(baseDir, toolPath.skills, name);
7861
+ let skillDir;
7862
+ if (tool === "openclaw") {
7863
+ const wsDir = await resolveOpenclawWorkspaceDir();
7864
+ if (!wsDir) continue;
7865
+ skillDir = path22.join(wsDir, "skills", name);
7866
+ } else {
7867
+ skillDir = path22.join(baseDir, toolPath.skills, name);
7868
+ }
7670
7869
  if (await pathExists(skillDir)) {
7671
7870
  await remove(skillDir);
7672
7871
  removed.push(skillDir);
@@ -9703,6 +9902,10 @@ async function pushCore(localConfig, teamConfig, options) {
9703
9902
  try {
9704
9903
  const repoPath = localConfig.repo.localPath;
9705
9904
  const git = createGit2(repoPath);
9905
+ if (!await isDedicatedRepoRoot(repoPath)) {
9906
+ pullSpin.fail("Cannot push: team repo path is not a dedicated git root. Run `teamai init` to re-clone the team repo before pushing.");
9907
+ return;
9908
+ }
9706
9909
  await resetToCleanMaster(git, repoPath);
9707
9910
  await pullRepo(repoPath);
9708
9911
  pullSpin.succeed("Up to date");
@@ -9743,8 +9946,8 @@ async function pushCore(localConfig, teamConfig, options) {
9743
9946
  process.exitCode = 2;
9744
9947
  return;
9745
9948
  }
9746
- const os7 = await import("os");
9747
- const skillPath = options.skill.startsWith("~") ? path32.join(os7.homedir(), options.skill.slice(1)) : path32.resolve(options.skill);
9949
+ const os8 = await import("os");
9950
+ const skillPath = options.skill.startsWith("~") ? path32.join(os8.homedir(), options.skill.slice(1)) : path32.resolve(options.skill);
9748
9951
  let matchedItem;
9749
9952
  for (const item of allItems) {
9750
9953
  if (item.type !== "skills") continue;
@@ -9756,7 +9959,7 @@ async function pushCore(localConfig, teamConfig, options) {
9756
9959
  matchedItem = item;
9757
9960
  break;
9758
9961
  }
9759
- const skillInput = options.skill.replace(/^~/, os7.homedir());
9962
+ const skillInput = options.skill.replace(/^~/, os8.homedir());
9760
9963
  if (item.sourcePath.endsWith(skillInput) || item.sourcePath.includes(path32.sep + skillInput)) {
9761
9964
  matchedItem = item;
9762
9965
  break;
@@ -10039,6 +10242,7 @@ __export(git_exports, {
10039
10242
  getRepoStatus: () => getRepoStatus,
10040
10243
  hasCommits: () => hasCommits,
10041
10244
  initRepo: () => initRepo,
10245
+ isDedicatedRepoRoot: () => isDedicatedRepoRoot,
10042
10246
  isGitRepo: () => isGitRepo,
10043
10247
  isMetadataOnlyDiff: () => isMetadataOnlyDiff,
10044
10248
  pullRepo: () => pullRepo,
@@ -10047,6 +10251,7 @@ __export(git_exports, {
10047
10251
  resetToCleanMaster: () => resetToCleanMaster
10048
10252
  });
10049
10253
  import fs12 from "fs";
10254
+ import { realpath } from "fs/promises";
10050
10255
  import path33 from "path";
10051
10256
  import fse8 from "fs-extra";
10052
10257
  import simpleGit from "simple-git";
@@ -10276,6 +10481,21 @@ function generateBranchName(username) {
10276
10481
  const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
10277
10482
  return `teamai/push/${username}/${timestamp}`;
10278
10483
  }
10484
+ async function isDedicatedRepoRoot(repoPath) {
10485
+ const git = createGit2(repoPath);
10486
+ let toplevel;
10487
+ try {
10488
+ toplevel = (await git.revparse(["--show-toplevel"])).trim();
10489
+ } catch {
10490
+ return true;
10491
+ }
10492
+ try {
10493
+ const [realTop, realRepo] = await Promise.all([realpath(toplevel), realpath(repoPath)]);
10494
+ return realTop === realRepo;
10495
+ } catch {
10496
+ return false;
10497
+ }
10498
+ }
10279
10499
  async function resetToCleanMaster(git, localPath) {
10280
10500
  const status2 = await git.status();
10281
10501
  const hasConflicts = status2.conflicted.length > 0;
@@ -11909,13 +12129,10 @@ function tokenize(text) {
11909
12129
  if (t !== word) tokens.push(t);
11910
12130
  }
11911
12131
  }
11912
- const chars = [...word];
11913
- for (const ch of chars) {
11914
- if (/[一-鿿]/.test(ch)) {
11915
- cjkRun.push(ch);
11916
- } else {
11917
- flushCjkRun();
11918
- }
12132
+ if (word.length === 1 && /[一-鿿]/.test(word)) {
12133
+ cjkRun.push(word);
12134
+ } else {
12135
+ flushCjkRun();
11919
12136
  }
11920
12137
  }
11921
12138
  flushCjkRun();
@@ -11930,6 +12147,40 @@ function tokenCount(text) {
11930
12147
  }
11931
12148
  return count;
11932
12149
  }
12150
+ function wordSegments(text) {
12151
+ if (!text) return [];
12152
+ const input = text.length > MAX_TOKENIZE_CHARS ? text.slice(0, MAX_TOKENIZE_CHARS) : text;
12153
+ const words = [];
12154
+ let run = [];
12155
+ let lastWasCjkWord = false;
12156
+ const flushRun = () => {
12157
+ if (run.length > 0 && lastWasCjkWord) {
12158
+ words[words.length - 1] += run.shift();
12159
+ lastWasCjkWord = false;
12160
+ }
12161
+ if (run.length > 0) {
12162
+ words.push(run.join(""));
12163
+ lastWasCjkWord = false;
12164
+ }
12165
+ run = [];
12166
+ };
12167
+ for (const seg of sharedSegmenter.segment(input)) {
12168
+ if (!seg.isWordLike) {
12169
+ flushRun();
12170
+ lastWasCjkWord = false;
12171
+ continue;
12172
+ }
12173
+ if (seg.segment.length === 1 && /[一-鿿]/.test(seg.segment)) {
12174
+ run.push(seg.segment);
12175
+ continue;
12176
+ }
12177
+ flushRun();
12178
+ words.push(seg.segment);
12179
+ lastWasCjkWord = /^[一-鿿]+$/.test(seg.segment);
12180
+ }
12181
+ flushRun();
12182
+ return words;
12183
+ }
11933
12184
  var MAX_TOKENIZE_CHARS, sharedSegmenter;
11934
12185
  var init_tokenizer = __esm({
11935
12186
  "src/utils/tokenizer.ts"() {
@@ -12244,7 +12495,8 @@ __export(search_index_exports, {
12244
12495
  parseLearningDoc: () => parseLearningDoc,
12245
12496
  search: () => search,
12246
12497
  titleFromFilename: () => titleFromFilename,
12247
- tokenize: () => tokenize
12498
+ tokenize: () => tokenize,
12499
+ wordSegments: () => wordSegments
12248
12500
  });
12249
12501
  import path42 from "path";
12250
12502
  import matter3 from "gray-matter";
@@ -12308,13 +12560,13 @@ function parseLearningDoc(content, filename) {
12308
12560
  date: typeof data.date === "string" ? data.date : data.date instanceof Date ? data.date.toISOString().slice(0, 10) : void 0,
12309
12561
  tags: Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string") : typeof data.Tags === "string" ? data.Tags.split(/[,,]\s*/).map((t) => t.trim()).filter(Boolean) : void 0
12310
12562
  };
12311
- const bodyExcerpt = body.slice(0, MAX_BODY_CHARS);
12563
+ const bodyExcerpt = body;
12312
12564
  return { meta, bodyExcerpt };
12313
12565
  } catch {
12314
12566
  log.error(`Failed to parse frontmatter for ${filename}, using fallback`);
12315
12567
  return {
12316
12568
  meta: {},
12317
- bodyExcerpt: content.slice(0, MAX_BODY_CHARS)
12569
+ bodyExcerpt: content
12318
12570
  };
12319
12571
  }
12320
12572
  }
@@ -12535,6 +12787,16 @@ async function loadIndex(indexPath) {
12535
12787
  }
12536
12788
  return raw;
12537
12789
  }
12790
+ function dedupKey(r) {
12791
+ return JSON.stringify([
12792
+ r.entry.type,
12793
+ r.entry.domain,
12794
+ r.entry.title,
12795
+ r.entry.date,
12796
+ r.entry.author,
12797
+ [...r.entry.tokens].sort()
12798
+ ]);
12799
+ }
12538
12800
  function search(query, index, limit = 5) {
12539
12801
  if (!query.trim()) return [];
12540
12802
  const queryTokens = tokenize(query);
@@ -12548,6 +12810,8 @@ function search(query, index, limit = 5) {
12548
12810
  const docFreq = df[token] ?? 0;
12549
12811
  return Math.log((N + 1) / (docFreq + 1)) + 1;
12550
12812
  };
12813
+ const lengthNorm = Math.sqrt(queryTokens.length);
12814
+ const wordTokens = wordSegments(query).map((w) => ({ word: w, tokens: tokenize(w) }));
12551
12815
  const results = [];
12552
12816
  for (const entry of index.entries) {
12553
12817
  let score = 0;
@@ -12568,8 +12832,9 @@ function search(query, index, limit = 5) {
12568
12832
  score += 1 * idf(qt);
12569
12833
  }
12570
12834
  }
12571
- const isCodebaseDoc = entry.type === "docs" && (entry.path ?? entry.filename ?? "").includes("team-codebase");
12572
- if (score > 0 && (hasTitleOrTagMatch || isCodebaseDoc)) {
12835
+ const isDocsEntry = entry.type === "docs";
12836
+ if (score > 0 && (hasTitleOrTagMatch || isDocsEntry)) {
12837
+ score /= lengthNorm;
12573
12838
  score += Math.min(entry.votes * 0.5, 5);
12574
12839
  const domainMultiplier = domainWeightRow[entry.domain ?? "neutral"];
12575
12840
  const typeMultiplier = TYPE_BONUS[entry.type];
@@ -12580,16 +12845,35 @@ function search(query, index, limit = 5) {
12580
12845
  if (entry.hotness !== void 0 && entry.hotness < 1) {
12581
12846
  score *= entry.hotness;
12582
12847
  }
12583
- results.push({ entry, score });
12848
+ if (!hasTitleOrTagMatch) {
12849
+ results.push({ entry, score });
12850
+ continue;
12851
+ }
12852
+ const matchedTerms = [];
12853
+ const missingTerms = [];
12854
+ for (const { word, tokens: wt } of wordTokens) {
12855
+ const hit = wt.some((t) => entryTokens.has(`title:${t}`) || entryTokens.has(`tag:${t}`));
12856
+ (hit ? matchedTerms : missingTerms).push(word);
12857
+ }
12858
+ results.push({ entry, score, matchedTerms, missingTerms });
12584
12859
  }
12585
12860
  }
12586
12861
  results.sort((a, b) => {
12587
12862
  if (b.score !== a.score) return b.score - a.score;
12588
12863
  return (b.entry.date || "").localeCompare(a.entry.date || "");
12589
12864
  });
12590
- return results.slice(0, limit);
12865
+ const seen = /* @__PURE__ */ new Set();
12866
+ const deduped = [];
12867
+ for (const r of results) {
12868
+ const key = dedupKey(r);
12869
+ if (seen.has(key)) continue;
12870
+ seen.add(key);
12871
+ deduped.push(r);
12872
+ if (deduped.length === limit) break;
12873
+ }
12874
+ return deduped;
12591
12875
  }
12592
- var MAX_BODY_CHARS, MAX_DOC_BYTES, TECHNICAL_TAGS, OPS_TAGS, SUPPORT_TAGS, TECHNICAL_PATH_PATTERNS, OPS_PATH_PATTERNS, SUPPORT_PATH_PATTERNS, DOMAIN_WEIGHT, TYPE_BONUS, CODEBASE_INDEX_FILENAME, CODEBASE_FULL_FILENAME, CODEBASE_INDEX_WEIGHT_BOOST;
12876
+ var MAX_DOC_BYTES, TECHNICAL_TAGS, OPS_TAGS, SUPPORT_TAGS, TECHNICAL_PATH_PATTERNS, OPS_PATH_PATTERNS, SUPPORT_PATH_PATTERNS, DOMAIN_WEIGHT, TYPE_BONUS, CODEBASE_INDEX_FILENAME, CODEBASE_FULL_FILENAME, CODEBASE_INDEX_WEIGHT_BOOST;
12593
12877
  var init_search_index = __esm({
12594
12878
  "src/utils/search-index.ts"() {
12595
12879
  "use strict";
@@ -12597,7 +12881,6 @@ var init_search_index = __esm({
12597
12881
  init_tokenizer();
12598
12882
  init_logger();
12599
12883
  init_types();
12600
- MAX_BODY_CHARS = 2e3;
12601
12884
  MAX_DOC_BYTES = 50 * 1024;
12602
12885
  TECHNICAL_TAGS = /* @__PURE__ */ new Set([
12603
12886
  "api",
@@ -13349,6 +13632,7 @@ var init_skill_health = __esm({
13349
13632
  // src/digest.ts
13350
13633
  var digest_exports = {};
13351
13634
  __export(digest_exports, {
13635
+ buildSkillChangePathspec: () => buildSkillChangePathspec,
13352
13636
  formatTokenCount: () => formatTokenCount,
13353
13637
  generateDigest: () => generateDigest,
13354
13638
  summarizeConversation: () => summarizeConversation,
@@ -13399,9 +13683,17 @@ function parseGitLogOutput(output) {
13399
13683
  if (current) commits.push(current);
13400
13684
  return commits;
13401
13685
  }
13402
- async function getRecentSkillChanges(repoPath) {
13686
+ function buildSkillChangePathspec(subdir = "") {
13687
+ const prefix = subdir ? `${subdir.replace(/\/+$/, "")}/` : "";
13688
+ const pathspec = `${prefix}skills/*/SKILL.md`;
13689
+ const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13690
+ const skillRe = new RegExp(`^${escaped}skills/([^/]+)/SKILL\\.md$`);
13691
+ return { pathspec, skillRe };
13692
+ }
13693
+ async function getRecentSkillChanges(repoPath, subdir = "") {
13403
13694
  const seen = /* @__PURE__ */ new Set();
13404
13695
  const changes = [];
13696
+ const { pathspec, skillRe } = buildSkillChangePathspec(subdir);
13405
13697
  try {
13406
13698
  const git = createGit2(repoPath);
13407
13699
  const rawOutput = await git.raw([
@@ -13411,13 +13703,13 @@ async function getRecentSkillChanges(repoPath) {
13411
13703
  "--name-only",
13412
13704
  "--pretty=format:%H|%an|%s",
13413
13705
  "--",
13414
- "skills/*/SKILL.md"
13706
+ pathspec
13415
13707
  ]);
13416
13708
  if (!rawOutput.trim()) return changes;
13417
13709
  const commits = parseGitLogOutput(rawOutput);
13418
13710
  for (const commit of commits) {
13419
13711
  for (const file of commit.files) {
13420
- const match = file.match(/^skills\/([^/]+)\/SKILL\.md$/);
13712
+ const match = file.match(skillRe);
13421
13713
  if (!match) continue;
13422
13714
  const skillName = match[1];
13423
13715
  if (seen.has(skillName)) continue;
@@ -13435,14 +13727,14 @@ async function getRecentSkillChanges(repoPath) {
13435
13727
  "--name-only",
13436
13728
  "--pretty=format:%H|%an|%s",
13437
13729
  "--",
13438
- "skills/*/SKILL.md"
13730
+ pathspec
13439
13731
  ]);
13440
13732
  const newSkills = /* @__PURE__ */ new Set();
13441
13733
  if (addedOutput.trim()) {
13442
13734
  const addedCommits = parseGitLogOutput(addedOutput);
13443
13735
  for (const commit of addedCommits) {
13444
13736
  for (const file of commit.files) {
13445
- const match = file.match(/^skills\/([^/]+)\/SKILL\.md$/);
13737
+ const match = file.match(skillRe);
13446
13738
  if (match) {
13447
13739
  newSkills.add(match[1]);
13448
13740
  }
@@ -13623,7 +13915,10 @@ async function generateDigest(options) {
13623
13915
  console.log(`\u{1F4CA} Knowledge base total: ${totalLearnings} learnings`);
13624
13916
  console.log("");
13625
13917
  }
13626
- const skillChanges = await getRecentSkillChanges(repoPath);
13918
+ const skillChanges = localConfig.repo.kind === "self" ? await getRecentSkillChanges(
13919
+ localConfig.repo.businessRepoRoot ?? path44.dirname(repoPath),
13920
+ ".teamai"
13921
+ ) : await getRecentSkillChanges(repoPath);
13627
13922
  const newSkills = skillChanges.filter((c) => c.type === "new");
13628
13923
  const updatedSkills = skillChanges.filter((c) => c.type === "updated");
13629
13924
  if (newSkills.length > 0) {
@@ -14099,6 +14394,10 @@ async function reportUsageToTeam(repoPath, username, options) {
14099
14394
  writeRoot = await ensureReportsWorktree2(selfConfig);
14100
14395
  } else {
14101
14396
  const git = createGit2(repoPath);
14397
+ if (!await isDedicatedRepoRoot(repoPath)) {
14398
+ log.debug(`Skipping report: ${repoPath} is not a dedicated team-repo root (safety guard)`);
14399
+ return;
14400
+ }
14102
14401
  await resetToCleanMaster(git, repoPath);
14103
14402
  await pullRepo(repoPath);
14104
14403
  }
@@ -15092,7 +15391,15 @@ async function pullForScope(localConfig, options, policy = {}) {
15092
15391
  const docsRepoDir = path48.join(localConfig.repo.localPath, "docs");
15093
15392
  const rulesRepoDir = path48.join(localConfig.repo.localPath, "rules");
15094
15393
  const skillsRepoDir = path48.join(localConfig.repo.localPath, "skills");
15095
- const votesDir = path48.join(localConfig.repo.localPath, "votes");
15394
+ let votesDir = path48.join(localConfig.repo.localPath, "votes");
15395
+ if (localConfig.repo.kind === "self") {
15396
+ try {
15397
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
15398
+ votesDir = path48.join(await ensureReportsWorktree2(localConfig), "votes");
15399
+ } catch (e) {
15400
+ log.debug(`[self] reports worktree for votes unavailable: ${e.message}`);
15401
+ }
15402
+ }
15096
15403
  let learningsCount = 0;
15097
15404
  let effectiveLearningsDir;
15098
15405
  if (localConfig.scope === "user") {
@@ -15258,7 +15565,15 @@ async function pullForScope(localConfig, options, policy = {}) {
15258
15565
  const YAML20 = (await import("yaml")).default;
15259
15566
  const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15260
15567
  const { getRecommendations: getRecommendations2, displayRecommendations: displayRecommendations2 } = await Promise.resolve().then(() => (init_skill_recommend(), skill_recommend_exports));
15261
- const statsDir = path48.join(localConfig.repo.localPath, "stats");
15568
+ let statsDir = path48.join(localConfig.repo.localPath, "stats");
15569
+ if (localConfig.repo.kind === "self") {
15570
+ try {
15571
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
15572
+ statsDir = path48.join(await ensureReportsWorktree2(localConfig), "stats");
15573
+ } catch (e) {
15574
+ log.debug(`[self] reports worktree for stats unavailable: ${e.message}`);
15575
+ }
15576
+ }
15262
15577
  const files = await listFiles2(statsDir);
15263
15578
  const teamStats = [];
15264
15579
  for (const file of files) {
@@ -15532,7 +15847,7 @@ async function pull(options) {
15532
15847
  const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
15533
15848
  const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
15534
15849
  const targets = [];
15535
- if (projectConfig) {
15850
+ if (projectConfig && projectConfig.repo.kind !== "http") {
15536
15851
  targets.push({
15537
15852
  repoPath: projectConfig.repo.localPath,
15538
15853
  username: projectConfig.username,
@@ -15548,7 +15863,13 @@ async function pull(options) {
15548
15863
  targets.push({
15549
15864
  repoPath: activeUserConfig.repo.localPath,
15550
15865
  username: activeUserConfig.username,
15551
- opts: { skipTruncate: true, excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [] }
15866
+ opts: {
15867
+ skipTruncate: true,
15868
+ excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [],
15869
+ // Self mode routes stats/votes to the teamai-reports orphan branch —
15870
+ // never reset/pull the business repo working tree.
15871
+ ...activeUserConfig.repo.kind === "self" ? { selfConfig: activeUserConfig } : {}
15872
+ }
15552
15873
  });
15553
15874
  }
15554
15875
  const eventCount = (await readUsageEvents2()).length;
@@ -17280,9 +17601,13 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
17280
17601
  res.hookFiles.push({ path: settingsPath, tool });
17281
17602
  }
17282
17603
  } else {
17283
- const hooksDir = path56.join(baseDir, `.${tool}`, "hooks");
17284
- if (await pathExists(path56.join(hooksDir, OPENCLAW_HOOK_DIR))) {
17285
- res.openclawHookDirs.push({ hooksDir, tool });
17604
+ const defaultHooksDir = path56.join(baseDir, `.${tool}`, "hooks");
17605
+ const resolvedHooksDir = resolveOpenClawHooksDir(tool);
17606
+ const dirsToCheck = /* @__PURE__ */ new Set([defaultHooksDir, resolvedHooksDir]);
17607
+ for (const hooksDir of dirsToCheck) {
17608
+ if (await pathExists(path56.join(hooksDir, OPENCLAW_HOOK_DIR))) {
17609
+ res.openclawHookDirs.push({ hooksDir, tool });
17610
+ }
17286
17611
  }
17287
17612
  }
17288
17613
  if (toolPath.claudemd) {
@@ -20784,15 +21109,31 @@ async function contributeSelf(localConfig, content, options) {
20784
21109
  await ensureDir(path69.join(wtRepo, "learnings"));
20785
21110
  await fs24.promises.writeFile(path69.join(wtRepo, relPath), content, "utf-8");
20786
21111
  try {
21112
+ const { pathExists: pathExists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
20787
21113
  const wtLearnings = path69.join(wtRepo, "learnings");
20788
21114
  await fse11.copy(wtLearnings, LEARNINGS_LOCAL_DIR, {
20789
21115
  overwrite: true,
20790
21116
  filter: (src) => !path69.basename(src).startsWith(".")
20791
21117
  });
21118
+ const repoPath = localConfig.repo.localPath;
21119
+ const docsDir = path69.join(repoPath, "docs");
21120
+ const rulesDir = path69.join(repoPath, "rules");
21121
+ const skillsDir = path69.join(repoPath, "skills");
21122
+ let votesDir;
21123
+ try {
21124
+ const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
21125
+ const candidate = path69.join(await ensureReportsWorktree2(localConfig), "votes");
21126
+ if (await pathExists3(candidate)) votesDir = candidate;
21127
+ } catch {
21128
+ }
20792
21129
  const teamaiHome = getTeamaiHome(localConfig.scope, localConfig.projectRoot);
20793
21130
  const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
20794
21131
  await buildIndex2({
20795
- learningsDir: LEARNINGS_LOCAL_DIR,
21132
+ learningsDir: await pathExists3(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0,
21133
+ docsDir: await pathExists3(docsDir) ? docsDir : void 0,
21134
+ rulesDir: await pathExists3(rulesDir) ? rulesDir : void 0,
21135
+ skillsDir: await pathExists3(skillsDir) ? skillsDir : void 0,
21136
+ votesDir,
20796
21137
  indexPath: path69.join(teamaiHome, "search-index.json")
20797
21138
  });
20798
21139
  } catch (e) {
@@ -21568,7 +21909,7 @@ function formatResults(results) {
21568
21909
  lines.push(`--- [teamai:recall:start] --- (${results.length} result${results.length !== 1 ? "s" : ""})`);
21569
21910
  lines.push("");
21570
21911
  for (let i = 0; i < results.length; i++) {
21571
- const { entry, score, scope, learningsBase, sources } = results[i];
21912
+ const { entry, score, scope, learningsBase, sources, matchedTerms, missingTerms } = results[i];
21572
21913
  const voteStr = entry.votes > 0 ? ` \u2605${entry.votes}` : "";
21573
21914
  const scopeStr = scope ? ` [${scope}]` : "";
21574
21915
  const typeTag = entry.type ? `[${entry.type}] ` : "";
@@ -21577,6 +21918,10 @@ function formatResults(results) {
21577
21918
  if (entry.tags.length > 0) {
21578
21919
  lines.push(`Tags: ${entry.tags.join(", ")}`);
21579
21920
  }
21921
+ if (missingTerms && missingTerms.length > 0) {
21922
+ const matchedStr = matchedTerms && matchedTerms.length > 0 ? matchedTerms.join(", ") : "none";
21923
+ lines.push(`Matched: ${matchedStr} | Missing: ${missingTerms.join(", ")}`);
21924
+ }
21580
21925
  const filePath = entry.path ? entry.path : learningsBase ? `${learningsBase}/${entry.filename}` : `~/.teamai/learnings/${entry.filename}`;
21581
21926
  lines.push(`File: ${filePath}`);
21582
21927
  if (sources && sources.length > 0) {
@@ -21671,8 +22016,16 @@ async function recall(query, options) {
21671
22016
  const rounded = Math.round(score * 10) / 10;
21672
22017
  const verdict = isRelevantScore(score, isCodebaseHit, baseline) ? "RELEVANT" : "NOT_RELEVANT";
21673
22018
  let line = `${verdict} score=${rounded.toFixed(1)}`;
22019
+ const cutoff = isCodebaseHit ? CODEBASE_RELEVANCE_THRESHOLD : Math.max((baseline > 0 ? baseline : 1) * LEARNINGS_RELEVANCE_RATIO, LEARNINGS_ABSOLUTE_FLOOR);
22020
+ line += ` threshold=${(Math.round(cutoff * 10) / 10).toFixed(1)}`;
21674
22021
  if (verdict === "RELEVANT" && topResult) {
21675
22022
  line += ` title="${topResult.entry.title}"`;
22023
+ if (topResult.matchedTerms && topResult.matchedTerms.length > 0) {
22024
+ line += ` matched=${topResult.matchedTerms.join(",")}`;
22025
+ }
22026
+ if (topResult.missingTerms && topResult.missingTerms.length > 0) {
22027
+ line += ` missing=${topResult.missingTerms.join(",")}`;
22028
+ }
21676
22029
  if (topResult.sources && topResult.sources.length > 0) {
21677
22030
  const srcStr = topResult.sources.map((s) => s.desc ? `${s.path}(${s.desc})` : s.path).join(",");
21678
22031
  line += ` sources=${srcStr}`;
@@ -21964,10 +22317,10 @@ var init_recall_toggle = __esm({
21964
22317
 
21965
22318
  // src/utils/cache-index.ts
21966
22319
  import path75 from "path";
21967
- import os4 from "os";
22320
+ import os5 from "os";
21968
22321
  import fs25 from "fs-extra";
21969
22322
  function getCacheRoot() {
21970
- return process.env.TEAMAI_CACHE_DIR ?? path75.join(os4.homedir(), ".teamai", "cache", "repos");
22323
+ return process.env.TEAMAI_CACHE_DIR ?? path75.join(os5.homedir(), ".teamai", "cache", "repos");
21971
22324
  }
21972
22325
  function buildKey(provider, owner, repo) {
21973
22326
  return `${provider}/${owner}/${repo}`;
@@ -25913,6 +26266,23 @@ function parseJSON(raw) {
25913
26266
  return null;
25914
26267
  }
25915
26268
  }
26269
+ function resolveImportToModule(importerFile, importPath) {
26270
+ if (importPath.startsWith(".")) {
26271
+ const importerDir = path86.dirname(importerFile);
26272
+ const resolved = path86.normalize(path86.join(importerDir, importPath));
26273
+ const topLevel = resolved.split("/")[0];
26274
+ if (!topLevel || topLevel === ".." || topLevel === ".") return void 0;
26275
+ return topLevel;
26276
+ }
26277
+ if (importPath.includes(".") && !importPath.includes("/")) {
26278
+ return importPath.split(".")[0];
26279
+ }
26280
+ const parts = importPath.split("/");
26281
+ const first = parts[0];
26282
+ if (!first) return void 0;
26283
+ if (first.startsWith("@")) return void 0;
26284
+ return first;
26285
+ }
25916
26286
  async function enrichWithAI(ctx) {
25917
26287
  const moduleEntries = [...ctx.modules.entries()].filter(([, facts]) => facts.length >= 5);
25918
26288
  if (moduleEntries.length === 0) {
@@ -25976,9 +26346,9 @@ async function enrichWithAI(ctx) {
25976
26346
  const moduleImports = ctx.facts.filter((f) => f.kind === "relation" && f.file.startsWith(name + "/"));
25977
26347
  const targetModules = /* @__PURE__ */ new Set();
25978
26348
  for (const imp of moduleImports) {
25979
- const targetParts = imp.name.split("/");
25980
- if (targetParts[0] && targetParts[0] !== name) {
25981
- targetModules.add(targetParts[0]);
26349
+ const resolved = resolveImportToModule(imp.file, imp.name);
26350
+ if (resolved && resolved !== name) {
26351
+ targetModules.add(resolved);
25982
26352
  }
25983
26353
  }
25984
26354
  for (const target of targetModules) {
@@ -26465,7 +26835,19 @@ async function extractCodebase(opts) {
26465
26835
  let callChains;
26466
26836
  const depPathsFile = path87.join(evidenceDir, "dependency-paths.md");
26467
26837
  if (changedFiles) {
26468
- callChains = [];
26838
+ let reused = false;
26839
+ try {
26840
+ const existing = await readFile8(depPathsFile, "utf-8");
26841
+ if (existing.trim()) {
26842
+ reused = true;
26843
+ }
26844
+ } catch {
26845
+ }
26846
+ if (reused) {
26847
+ callChains = [];
26848
+ } else {
26849
+ callChains = traceCallChains(facts, files);
26850
+ }
26469
26851
  } else {
26470
26852
  callChains = traceCallChains(facts, files);
26471
26853
  }
@@ -26750,15 +27132,16 @@ async function shallowClone(url, localPath, provider, opts) {
26750
27132
  log.debug(`shallowClone: \u4F7F\u7528\u533F\u540D HTTPS \u514B\u9686 github \u4ED3\u5E93`);
26751
27133
  }
26752
27134
  } else if (provider === "tgit") {
26753
- const creds = tryGetTGitToken();
26754
- cloneUrl = url.replace(/^http:\/\//, "https://");
26755
- if (creds) {
26756
- cloneUrl = cloneUrl.replace("https://", `https://${tgitGitUser(creds.scheme)}:${creds.token}@`);
27135
+ const httpsUrl = url.replace(/^http:\/\//, "https://");
27136
+ const authed = tgitGitCloneUrl(httpsUrl);
27137
+ if (authed) {
27138
+ cloneUrl = authed;
26757
27139
  cloneMethod = "https-token";
26758
- log.debug(`shallowClone: \u4F7F\u7528 HTTPS+token \u514B\u9686 tgit \u4ED3\u5E93`);
27140
+ log.debug(`shallowClone: \u4F7F\u7528 HTTPS+OAuth token \u514B\u9686 tgit \u4ED3\u5E93`);
26759
27141
  } else {
27142
+ cloneUrl = httpsUrl;
26760
27143
  cloneMethod = "https-anonymous";
26761
- log.debug(`shallowClone: \u65E0 TGit token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
27144
+ log.debug(`shallowClone: \u65E0 TGit OAuth token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
26762
27145
  }
26763
27146
  } else {
26764
27147
  cloneUrl = url.replace(/^http:\/\//, "https://");
@@ -26826,10 +27209,10 @@ __export(repo_cache_exports, {
26826
27209
  writeLastSync: () => writeLastSync
26827
27210
  });
26828
27211
  import path88 from "path";
26829
- import os5 from "os";
27212
+ import os6 from "os";
26830
27213
  import fs31 from "fs-extra";
26831
27214
  function getCacheRoot2() {
26832
- return process.env.TEAMAI_CACHE_DIR ?? path88.join(os5.homedir(), ".teamai", "cache", "repos");
27215
+ return process.env.TEAMAI_CACHE_DIR ?? path88.join(os6.homedir(), ".teamai", "cache", "repos");
26833
27216
  }
26834
27217
  function getRepoCacheDir(provider, owner, repo) {
26835
27218
  return path88.join(getCacheRoot2(), provider, owner, repo);
@@ -28884,7 +29267,7 @@ __export(import_exports, {
28884
29267
  importCmd: () => importCmd
28885
29268
  });
28886
29269
  import path98 from "path";
28887
- import os6 from "os";
29270
+ import os7 from "os";
28888
29271
  import fs38 from "fs-extra";
28889
29272
  import { Listr, PRESET_TIMER } from "listr2";
28890
29273
  async function importCmd(opts) {
@@ -29078,7 +29461,7 @@ async function importCmd(opts) {
29078
29461
  log.success(`Local directory ${slug} import complete (dry-run)`);
29079
29462
  return;
29080
29463
  }
29081
- const tmpExtractDir = await fs38.mkdtemp(path98.join(os6.tmpdir(), "teamai-extract-"));
29464
+ const tmpExtractDir = await fs38.mkdtemp(path98.join(os7.tmpdir(), "teamai-extract-"));
29082
29465
  try {
29083
29466
  const { extractCodebase: extractCodebase2 } = await Promise.resolve().then(() => (init_codebase_extract(), codebase_extract_exports));
29084
29467
  await extractCodebase2({