teamai-cli 0.26.0-beta.5 → 0.26.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
@@ -66,8 +66,8 @@ function ensureLogDir() {
66
66
  }
67
67
  function maybeRotate() {
68
68
  try {
69
- const stat9 = fs.statSync(getLogFilePath());
70
- if (stat9.size >= MAX_LOG_BYTES) {
69
+ const stat10 = fs.statSync(getLogFilePath());
70
+ if (stat10.size >= MAX_LOG_BYTES) {
71
71
  fs.renameSync(getLogFilePath(), getLogFilePath() + ".1");
72
72
  }
73
73
  } catch {
@@ -683,8 +683,8 @@ async function writeJsonAtomic(filePath, data, options) {
683
683
  async function copyDir(src, dest) {
684
684
  const destExpanded = expandHome(dest);
685
685
  try {
686
- const stat9 = await fse.lstat(destExpanded);
687
- if (stat9.isSymbolicLink()) {
686
+ const stat10 = await fse.lstat(destExpanded);
687
+ if (stat10.isSymbolicLink()) {
688
688
  await fse.remove(destExpanded);
689
689
  }
690
690
  } catch {
@@ -698,8 +698,8 @@ async function pruneEmptyDirs(target) {
698
698
  const expanded = expandHome(target);
699
699
  let entries;
700
700
  try {
701
- const stat9 = await fse.lstat(expanded);
702
- if (!stat9.isDirectory() || stat9.isSymbolicLink()) return false;
701
+ const stat10 = await fse.lstat(expanded);
702
+ if (!stat10.isDirectory() || stat10.isSymbolicLink()) return false;
703
703
  entries = await fse.readdir(expanded, { withFileTypes: true });
704
704
  } catch {
705
705
  return false;
@@ -781,8 +781,8 @@ async function remove(p) {
781
781
  }
782
782
  async function getFileMtime(filePath) {
783
783
  try {
784
- const stat9 = await fse.stat(expandHome(filePath));
785
- return stat9.mtimeMs;
784
+ const stat10 = await fse.stat(expandHome(filePath));
785
+ return stat10.mtimeMs;
786
786
  } catch {
787
787
  return 0;
788
788
  }
@@ -796,8 +796,8 @@ async function getDirLatestMtime(dirPath) {
796
796
  if (isIgnored(entry.name)) continue;
797
797
  const fullPath = path5.join(expanded, entry.name);
798
798
  if (entry.isFile()) {
799
- const stat9 = await fse.stat(fullPath);
800
- if (stat9.mtimeMs > latest) latest = stat9.mtimeMs;
799
+ const stat10 = await fse.stat(fullPath);
800
+ if (stat10.mtimeMs > latest) latest = stat10.mtimeMs;
801
801
  } else if (entry.isDirectory()) {
802
802
  const sub = await getDirLatestMtime(fullPath);
803
803
  if (sub > latest) latest = sub;
@@ -3765,11 +3765,12 @@ export const TeamaiHooks = async ({ directory, worktree }) => {
3765
3765
  `;
3766
3766
  }
3767
3767
  async function injectOpencodeHooks(baseDir, scope) {
3768
- const dir = resolveOpencodePluginDir(baseDir, scope);
3769
- await ensureDir(dir);
3770
- const file = path14.join(dir, OPENCODE_HOOK_FILE);
3771
- await writeFile(file, buildPluginSource());
3772
- log.success(`Injected teamai OpenCode hook into ${file}`);
3768
+ const file = path14.join(resolveOpencodePluginDir(baseDir, scope), OPENCODE_HOOK_FILE);
3769
+ if (await writeIfChanged(file, buildPluginSource())) {
3770
+ log.success(`Injected teamai OpenCode hook into ${file}`);
3771
+ } else {
3772
+ log.debug(`teamai OpenCode hook already up-to-date in ${file}`);
3773
+ }
3773
3774
  }
3774
3775
  async function removeOpencodeHooks(baseDir, scope) {
3775
3776
  const dir = resolveOpencodePluginDir(baseDir, scope);
@@ -3935,11 +3936,12 @@ export default function teamaiHooks(pi) {
3935
3936
  `;
3936
3937
  }
3937
3938
  async function injectOmpHooks() {
3938
- const dir = resolveOmpExtensionsDir();
3939
- await ensureDir(dir);
3940
- const file = path15.join(dir, OMP_HOOK_FILE);
3941
- await writeFile(file, buildOmpExtensionSource());
3942
- log.success(`Injected teamai OMP hook into ${file}`);
3939
+ const file = path15.join(resolveOmpExtensionsDir(), OMP_HOOK_FILE);
3940
+ if (await writeIfChanged(file, buildOmpExtensionSource())) {
3941
+ log.success(`Injected teamai OMP hook into ${file}`);
3942
+ } else {
3943
+ log.debug(`teamai OMP hook already up-to-date in ${file}`);
3944
+ }
3943
3945
  }
3944
3946
  async function removeOmpHooks() {
3945
3947
  const file = path15.join(resolveOmpExtensionsDir(), OMP_HOOK_FILE);
@@ -4062,9 +4064,11 @@ async function injectPiHooks() {
4062
4064
  log.warn(`Skipping Pi hook injection: ${file} exists without the TeamAI marker`);
4063
4065
  return;
4064
4066
  }
4065
- await ensureDir(dir);
4066
- await writeFile(file, buildPiExtensionSource());
4067
- log.success(`Injected teamai Pi hook into ${file}`);
4067
+ if (await writeIfChanged(file, buildPiExtensionSource())) {
4068
+ log.success(`Injected teamai Pi hook into ${file}`);
4069
+ } else {
4070
+ log.debug(`teamai Pi hook already up-to-date in ${file}`);
4071
+ }
4068
4072
  }
4069
4073
  async function removePiHooks() {
4070
4074
  const file = path16.join(resolvePiExtensionsDir(), PI_HOOK_FILE);
@@ -4236,10 +4240,13 @@ async function injectOpenClawHooks(workspacePath, tool = "openclaw") {
4236
4240
  return;
4237
4241
  }
4238
4242
  const dir = path17.join(wsDir, "hooks", OPENCLAW_HOOK_DIR);
4239
- await ensureDir(dir);
4240
- await writeFile(path17.join(dir, "HOOK.md"), buildHookMd(tool));
4241
- await writeFile(path17.join(dir, "handler.ts"), buildHandlerTs(tool));
4242
- log.success(`Injected teamai OpenClaw hook into ${dir}`);
4243
+ const hookMdChanged = await writeIfChanged(path17.join(dir, "HOOK.md"), buildHookMd(tool));
4244
+ const handlerChanged = await writeIfChanged(path17.join(dir, "handler.ts"), buildHandlerTs(tool));
4245
+ if (hookMdChanged || handlerChanged) {
4246
+ log.success(`Injected teamai OpenClaw hook into ${dir}`);
4247
+ } else {
4248
+ log.debug(`teamai OpenClaw hook already up-to-date in ${dir}`);
4249
+ }
4243
4250
  await enableOpenClawInternalHooks(tool);
4244
4251
  }
4245
4252
  async function enableOpenClawInternalHooks(tool = "openclaw") {
@@ -4526,9 +4533,10 @@ async function upsertHermesHook(event, entry) {
4526
4533
  if (entry.matcher !== void 0) cleanEntry.matcher = entry.matcher;
4527
4534
  if (entry.timeout !== void 0) cleanEntry.timeout = entry.timeout;
4528
4535
  const newArr = [...untouched, cleanEntry];
4529
- if (JSON.stringify(arr) === JSON.stringify(newArr)) return;
4536
+ if (JSON.stringify(arr) === JSON.stringify(newArr)) return false;
4530
4537
  doc.setIn(["hooks", event], newArr);
4531
4538
  await writeConfigDoc(doc);
4539
+ return true;
4532
4540
  }
4533
4541
  async function removeHermesHookByCommand(command) {
4534
4542
  const doc = await readConfigDoc();
@@ -4561,9 +4569,10 @@ async function addHermesAllowlist(event, command) {
4561
4569
  const raw = await readJson(filePath);
4562
4570
  const data = raw && Array.isArray(raw.approvals) ? raw : { approvals: [] };
4563
4571
  const exists3 = data.approvals.some((a) => a.event === event && a.command === command);
4564
- if (exists3) return;
4572
+ if (exists3) return false;
4565
4573
  data.approvals.push({ event, command });
4566
4574
  await writeJson(filePath, data);
4575
+ return true;
4567
4576
  }
4568
4577
  async function removeHermesAllowlist(event, command) {
4569
4578
  const filePath = getHermesAllowlistPath();
@@ -4611,16 +4620,19 @@ function buildReportScript() {
4611
4620
  }
4612
4621
  async function injectHermesHooks() {
4613
4622
  const scriptPath = getReportScriptPath();
4614
- await ensureDir(path20.dirname(scriptPath));
4615
- await writeFile(scriptPath, buildReportScript());
4623
+ const scriptChanged = await writeIfChanged(scriptPath, buildReportScript());
4616
4624
  try {
4617
4625
  await chmod(scriptPath, 493);
4618
4626
  } catch (e) {
4619
4627
  log.warn(`Failed to chmod Hermes hook script: ${e.message}`);
4620
4628
  }
4621
- await upsertHermesHook(REPORT_EVENT, { command: scriptPath, timeout: 60 });
4622
- await addHermesAllowlist(REPORT_EVENT, scriptPath);
4623
- log.success("Injected teamai Hermes hook into " + scriptPath);
4629
+ const hookChanged = await upsertHermesHook(REPORT_EVENT, { command: scriptPath, timeout: 60 });
4630
+ const allowlistChanged = await addHermesAllowlist(REPORT_EVENT, scriptPath);
4631
+ if (scriptChanged || hookChanged || allowlistChanged) {
4632
+ log.success("Injected teamai Hermes hook into " + scriptPath);
4633
+ } else {
4634
+ log.debug(`teamai Hermes hook already up-to-date in ${scriptPath}`);
4635
+ }
4624
4636
  }
4625
4637
  async function removeHermesHooks() {
4626
4638
  const scriptPath = getReportScriptPath();
@@ -6491,7 +6503,7 @@ searched: relay that warning rather than conclude the team has no knowledge ther
6491
6503
  import crypto2 from "crypto";
6492
6504
  import path25 from "path";
6493
6505
  import fse2 from "fs-extra";
6494
- async function syncTeamUpdatesToLocal(teamConfig, localConfig, baseRevs, placedRules = void 0) {
6506
+ async function syncTeamUpdatesToLocal(teamConfig, localConfig, baseRevs, placedRules) {
6495
6507
  const bases = (typeof baseRevs === "string" ? [baseRevs] : baseRevs ?? []).filter((rev) => rev !== "");
6496
6508
  if (bases.length === 0) {
6497
6509
  log.debug("No base revision \u2014 skipping pre-push sync");
@@ -6640,12 +6652,12 @@ async function replaceSkillDir(teamSkillDir, localSkillDir) {
6640
6652
  }
6641
6653
  async function removeLeftover(dir) {
6642
6654
  try {
6643
- const stat9 = await fse2.lstat(dir).catch((error) => {
6655
+ const stat10 = await fse2.lstat(dir).catch((error) => {
6644
6656
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
6645
6657
  throw error;
6646
6658
  });
6647
- if (stat9 === null) return;
6648
- if (stat9.isDirectory()) await fse2.chmod(dir, 448);
6659
+ if (stat10 === null) return;
6660
+ if (stat10.isDirectory()) await fse2.chmod(dir, 448);
6649
6661
  await fse2.remove(dir);
6650
6662
  } catch (error) {
6651
6663
  log.warn(`Could not remove ${dir} (${error instanceof Error ? error.message : String(error)}). Delete it by hand.`);
@@ -6827,11 +6839,11 @@ function gfExec(args, options) {
6827
6839
  }
6828
6840
  function getGfPath() {
6829
6841
  try {
6830
- const stat9 = execSync(`test -x "${gfBinPath()}" && echo ok`, {
6842
+ const stat10 = execSync(`test -x "${gfBinPath()}" && echo ok`, {
6831
6843
  encoding: "utf-8",
6832
6844
  stdio: ["pipe", "pipe", "pipe"]
6833
6845
  });
6834
- if (stat9.trim() === "ok") return gfBinPath();
6846
+ if (stat10.trim() === "ok") return gfBinPath();
6835
6847
  } catch {
6836
6848
  }
6837
6849
  try {
@@ -10827,6 +10839,27 @@ var init_hook_cwd = __esm({
10827
10839
  });
10828
10840
 
10829
10841
  // src/utils/session-id.ts
10842
+ async function agentSessionIdFromEnv() {
10843
+ if (BRIDGE_AGENT_ENV.some((name) => process.env[name])) return void 0;
10844
+ const ids = [...new Set(AGENT_SESSION_ENV.map((name) => process.env[name]).filter((v) => !!v))];
10845
+ if (ids.length <= 1) return ids[0];
10846
+ const { readEvents: readEvents2 } = await Promise.resolve().then(() => (init_dashboard_collector(), dashboard_collector_exports));
10847
+ const firstEvent = /* @__PURE__ */ new Map();
10848
+ const lastStart = /* @__PURE__ */ new Map();
10849
+ for (const { sessionId, timestamp, type } of await readEvents2()) {
10850
+ if (!ids.includes(sessionId)) continue;
10851
+ const first = firstEvent.get(sessionId);
10852
+ if (!first || timestamp < first) firstEvent.set(sessionId, timestamp);
10853
+ const start = lastStart.get(sessionId);
10854
+ if (type === "session_start" && (!start || timestamp > start)) lastStart.set(sessionId, timestamp);
10855
+ }
10856
+ let latest;
10857
+ for (const [id, first] of firstEvent) {
10858
+ const runStart = lastStart.get(id) ?? first;
10859
+ if (!latest || runStart > latest.runStart) latest = { id, runStart };
10860
+ }
10861
+ return latest?.id ?? ids[0];
10862
+ }
10830
10863
  function deriveSessionId(data, options = {}) {
10831
10864
  if (typeof data.session_id === "string" && data.session_id) {
10832
10865
  return data.session_id;
@@ -10847,11 +10880,27 @@ function deriveSessionId(data, options = {}) {
10847
10880
  function deriveDispatchSessionId(data, tool) {
10848
10881
  return deriveSessionId(data, { includeCwd: tool.toLowerCase() !== COPILOT_TOOL_ID });
10849
10882
  }
10883
+ var AGENT_SESSION_ENV, BRIDGE_AGENT_ENV;
10850
10884
  var init_session_id = __esm({
10851
10885
  "src/utils/session-id.ts"() {
10852
10886
  "use strict";
10853
10887
  init_hook_cwd();
10854
10888
  init_types();
10889
+ AGENT_SESSION_ENV = [
10890
+ "CLAUDE_CODE_SESSION_ID",
10891
+ // Claude Code
10892
+ "CODEX_SESSION_ID",
10893
+ // Codex >= 0.148
10894
+ "CODEBUDDY_SESSION_ID",
10895
+ // CodeBuddy
10896
+ "COPILOT_AGENT_SESSION_ID",
10897
+ // Copilot CLI >= 1.0.29
10898
+ "CURSOR_CONVERSATION_ID",
10899
+ // Cursor
10900
+ "CLAUDE_SESSION_ID"
10901
+ // CodeBuddy's alias and older setups
10902
+ ];
10903
+ BRIDGE_AGENT_ENV = ["PI_SESSION_ID", "OPENCODE"];
10855
10904
  }
10856
10905
  });
10857
10906
 
@@ -11011,10 +11060,10 @@ function getParentPid(pid) {
11011
11060
  return ppid !== void 0 && ppid > 0 ? ppid : void 0;
11012
11061
  }
11013
11062
  try {
11014
- const stat9 = fs13.readFileSync(`/proc/${pid}/stat`, "utf-8");
11015
- const closeParen = stat9.lastIndexOf(") ");
11063
+ const stat10 = fs13.readFileSync(`/proc/${pid}/stat`, "utf-8");
11064
+ const closeParen = stat10.lastIndexOf(") ");
11016
11065
  if (closeParen === -1) return void 0;
11017
- const fields = stat9.slice(closeParen + 2).split(" ");
11066
+ const fields = stat10.slice(closeParen + 2).split(" ");
11018
11067
  const ppid = parseInt(fields[1], 10);
11019
11068
  return ppid > 0 ? ppid : void 0;
11020
11069
  } catch {
@@ -11323,8 +11372,9 @@ async function acquireReclaimSentinel(sentinel, owner) {
11323
11372
  });
11324
11373
  return exclusiveCreate(sentinel, payload);
11325
11374
  }
11326
- async function acquireLock(lockPath) {
11375
+ async function acquireLock(lockPath, options = {}) {
11327
11376
  const resolved = lockPath ?? expandHome(getUpdateLockPath());
11377
+ if (options.dryRun) return await lockState(resolved) !== "live";
11328
11378
  const owner = randomUUID();
11329
11379
  const payload = JSON.stringify({
11330
11380
  pid: process.pid,
@@ -11416,6 +11466,22 @@ async function doUpdate() {
11416
11466
  log.debug(`Auto-update skipped: ${reason}`);
11417
11467
  return;
11418
11468
  }
11469
+ const pkgName = getCurrentPackageName();
11470
+ const registry = resolveRegistryForPackage(pkgName);
11471
+ const target = resolveInstallPrefix();
11472
+ if (!target) {
11473
+ const root = path35.dirname(path35.dirname(fileURLToPath3(import.meta.url)));
11474
+ const message = `Self-update skipped: teamai is running from ${root}, which is not an npm install it can update in place (for example an \`npm link\` checkout). For a checkout, pull and rebuild it; to switch to the published package, run "npm install -g ${pkgName} --registry=${registry}".`;
11475
+ log.warn(message);
11476
+ log.persist(message);
11477
+ return;
11478
+ }
11479
+ if (!target.global) {
11480
+ const message = `Self-update is not supported for the vendored install at ${target.prefix} (npm would relocate or prune the runtime tree) \u2014 update manually.`;
11481
+ log.warn(message);
11482
+ log.persist(message);
11483
+ return;
11484
+ }
11419
11485
  if (policy === "prompt") {
11420
11486
  if (!isInteractive()) {
11421
11487
  log.info(`Update available: v${result.current} \u2192 v${result.latest}. Run "teamai update" to upgrade.`);
@@ -11435,16 +11501,7 @@ async function doUpdate() {
11435
11501
  return;
11436
11502
  }
11437
11503
  try {
11438
- const pkgName = getCurrentPackageName();
11439
- const registry = resolveRegistryForPackage(pkgName);
11440
11504
  const npm = resolveNpmCommand();
11441
- const target = resolveInstallPrefix();
11442
- if (target && !target.global) {
11443
- log.warn(
11444
- `Self-update is not supported for the vendored install at ${target.prefix} (npm would relocate or prune the runtime tree) \u2014 update manually.`
11445
- );
11446
- return;
11447
- }
11448
11505
  await execFileAsync(
11449
11506
  npm.cmd,
11450
11507
  [
@@ -11452,7 +11509,7 @@ async function doUpdate() {
11452
11509
  "install",
11453
11510
  "-g",
11454
11511
  pkgName,
11455
- ...target ? [`--prefix=${target.prefix}`] : [],
11512
+ `--prefix=${target.prefix}`,
11456
11513
  `--registry=${registry}`
11457
11514
  ],
11458
11515
  { timeout: INSTALL_TIMEOUT, windowsHide: true }
@@ -11603,8 +11660,8 @@ async function readStdin() {
11603
11660
  }
11604
11661
  async function readLastAssistantOutput(transcriptPath) {
11605
11662
  try {
11606
- const stat9 = await fs15.promises.stat(transcriptPath);
11607
- const fileSize = stat9.size;
11663
+ const stat10 = await fs15.promises.stat(transcriptPath);
11664
+ const fileSize = stat10.size;
11608
11665
  if (fileSize === 0) return "";
11609
11666
  const readSize = Math.min(fileSize, TRANSCRIPT_TAIL_BYTES);
11610
11667
  const offset = Math.max(0, fileSize - readSize);
@@ -11676,12 +11733,12 @@ async function scanJsonlTranscriptOnce(transcriptPath, modelAliases) {
11676
11733
  const countedUsageKeys = /* @__PURE__ */ new Set();
11677
11734
  const historicalRequests = await readLocalRequestRecords();
11678
11735
  try {
11679
- const stat9 = await fs15.promises.stat(transcriptPath);
11680
- if (stat9.size === 0) {
11736
+ const stat10 = await fs15.promises.stat(transcriptPath);
11737
+ if (stat10.size === 0) {
11681
11738
  return { result: { interrupt, toolReject, toolError, tokens, prompts }, codexSnapshot: null };
11682
11739
  }
11683
- if (stat9.size > INTERVENTION_SCAN_MAX_BYTES) {
11684
- log.warn(`dashboard: transcript too large to scan (${stat9.size} bytes)`);
11740
+ if (stat10.size > INTERVENTION_SCAN_MAX_BYTES) {
11741
+ log.warn(`dashboard: transcript too large to scan (${stat10.size} bytes)`);
11685
11742
  return { result: { interrupt, toolReject, toolError, tokens, prompts }, codexSnapshot: null };
11686
11743
  }
11687
11744
  const rl = readline2.createInterface({
@@ -11823,10 +11880,10 @@ function preferCodexSnapshot(current, observed) {
11823
11880
  }
11824
11881
  async function readLatestCodexUsageFromTail(transcriptPath) {
11825
11882
  try {
11826
- const stat9 = await fs15.promises.stat(transcriptPath);
11827
- if (stat9.size === 0) return null;
11828
- const readSize = Math.min(stat9.size, CODEX_USAGE_TAIL_BYTES);
11829
- const offset = stat9.size - readSize;
11883
+ const stat10 = await fs15.promises.stat(transcriptPath);
11884
+ if (stat10.size === 0) return null;
11885
+ const readSize = Math.min(stat10.size, CODEX_USAGE_TAIL_BYTES);
11886
+ const offset = stat10.size - readSize;
11830
11887
  const fh = await fs15.promises.open(transcriptPath, "r");
11831
11888
  try {
11832
11889
  const buffer = Buffer.alloc(readSize);
@@ -11899,10 +11956,10 @@ function parseCopilotShutdown(entry) {
11899
11956
  }
11900
11957
  async function readLatestCopilotUsageFromTail(transcriptPath) {
11901
11958
  try {
11902
- const stat9 = await fs15.promises.stat(transcriptPath);
11903
- if (stat9.size === 0) return { shutdownObserved: false };
11904
- const readSize = Math.min(stat9.size, COPILOT_USAGE_TAIL_BYTES);
11905
- const offset = stat9.size - readSize;
11959
+ const stat10 = await fs15.promises.stat(transcriptPath);
11960
+ if (stat10.size === 0) return { shutdownObserved: false };
11961
+ const readSize = Math.min(stat10.size, COPILOT_USAGE_TAIL_BYTES);
11962
+ const offset = stat10.size - readSize;
11906
11963
  const fh = await fs15.promises.open(transcriptPath, "r");
11907
11964
  try {
11908
11965
  const buffer = Buffer.alloc(readSize);
@@ -11954,11 +12011,11 @@ async function waitForCopilotShutdownUsage(transcriptPath, initial) {
11954
12011
  }
11955
12012
  async function findCopilotRunMarkerAfter(transcriptPath, startOffset, startedAt, endedAt, useProviderTime) {
11956
12013
  try {
11957
- const stat9 = await fs15.promises.stat(transcriptPath);
11958
- if (startOffset > stat9.size || stat9.size - startOffset > COPILOT_RUN_SCAN_BYTES) {
12014
+ const stat10 = await fs15.promises.stat(transcriptPath);
12015
+ if (startOffset > stat10.size || stat10.size - startOffset > COPILOT_RUN_SCAN_BYTES) {
11959
12016
  return void 0;
11960
12017
  }
11961
- const readSize = stat9.size - startOffset;
12018
+ const readSize = stat10.size - startOffset;
11962
12019
  const offset = startOffset;
11963
12020
  const fh = await fs15.promises.open(transcriptPath, "r");
11964
12021
  try {
@@ -11997,8 +12054,8 @@ async function readCopilotUsageForRun(transcriptPath, marker) {
11997
12054
  const descendants = /* @__PURE__ */ new Set([marker.id]);
11998
12055
  let latest = { shutdownObserved: false };
11999
12056
  try {
12000
- const stat9 = await fs15.promises.stat(transcriptPath);
12001
- if (stat9.size - marker.offset > COPILOT_RUN_SCAN_BYTES) return latest;
12057
+ const stat10 = await fs15.promises.stat(transcriptPath);
12058
+ if (stat10.size - marker.offset > COPILOT_RUN_SCAN_BYTES) return latest;
12002
12059
  const stream = fs15.createReadStream(transcriptPath, {
12003
12060
  start: marker.offset,
12004
12061
  encoding: "utf-8"
@@ -12051,8 +12108,8 @@ function resolveCopilotUsageTranscript(sessionId) {
12051
12108
  }
12052
12109
  async function readCodebuddyIndexOnce(transcriptPath) {
12053
12110
  try {
12054
- const stat9 = await fs15.promises.stat(transcriptPath);
12055
- if (stat9.size === 0 || stat9.size > INTERVENTION_SCAN_MAX_BYTES) return null;
12111
+ const stat10 = await fs15.promises.stat(transcriptPath);
12112
+ if (stat10.size === 0 || stat10.size > INTERVENTION_SCAN_MAX_BYTES) return null;
12056
12113
  const content = await fs15.promises.readFile(transcriptPath, "utf-8");
12057
12114
  const data = JSON.parse(content);
12058
12115
  if (!data || !Array.isArray(data.requests)) return null;
@@ -12085,8 +12142,8 @@ async function scanCodebuddyBlobs(messagesDir) {
12085
12142
  const erroredCallIds = /* @__PURE__ */ new Set();
12086
12143
  for (const blobPath of blobPaths) {
12087
12144
  try {
12088
- const stat9 = await fs15.promises.stat(blobPath);
12089
- if (stat9.size === 0 || stat9.size > INTERVENTION_SCAN_MAX_BYTES) continue;
12145
+ const stat10 = await fs15.promises.stat(blobPath);
12146
+ if (stat10.size === 0 || stat10.size > INTERVENTION_SCAN_MAX_BYTES) continue;
12090
12147
  const raw = await fs15.promises.readFile(blobPath, "utf-8");
12091
12148
  const blob = JSON.parse(raw);
12092
12149
  if (blob.role === "assistant") {
@@ -13660,11 +13717,24 @@ function sameFile(a, b, platform = process.platform) {
13660
13717
  function shellQuoteValue(value) {
13661
13718
  return `'${value.replace(/'/g, "'\\''")}'`;
13662
13719
  }
13663
- function extractEnvBlock(profileContent) {
13664
- const start = profileContent.indexOf(TEAMAI_ENV_START);
13665
- if (start === -1) return null;
13666
- const end = profileContent.indexOf(TEAMAI_ENV_END, start);
13667
- return end === -1 ? profileContent.slice(start) : profileContent.slice(start, end);
13720
+ function findEnvBlocks(profileContent) {
13721
+ const blocks = [];
13722
+ let from = 0;
13723
+ for (; ; ) {
13724
+ const start = profileContent.indexOf(TEAMAI_ENV_START, from);
13725
+ if (start === -1) return blocks;
13726
+ const endMarker = profileContent.indexOf(TEAMAI_ENV_END, start);
13727
+ if (endMarker === -1) {
13728
+ blocks.push({ text: profileContent.slice(start), start, end: null });
13729
+ return blocks;
13730
+ }
13731
+ const end = endMarker + TEAMAI_ENV_END.length;
13732
+ blocks.push({ text: profileContent.slice(start, endMarker), start, end });
13733
+ from = end;
13734
+ }
13735
+ }
13736
+ function findEnvBlockFor(profileContent, envShPath) {
13737
+ return findEnvBlocks(profileContent).find((block) => envBlockReferencesDataHome(block.text, envShPath)) ?? null;
13668
13738
  }
13669
13739
  function envBlockSourcesPath(block, envShPath) {
13670
13740
  const posixPath = toGeneratedForm(envShPath);
@@ -13681,9 +13751,16 @@ function candidateSpellings(envShPath) {
13681
13751
  }
13682
13752
  return [...spellings];
13683
13753
  }
13754
+ function includesAsPath(text, spelling) {
13755
+ for (let at = text.indexOf(spelling); at !== -1; at = text.indexOf(spelling, at + 1)) {
13756
+ const after = at + spelling.length;
13757
+ if ((at === 0 || /[\s'"]/.test(text[at - 1])) && (after === text.length || /[\s'";]/.test(text[after]))) return true;
13758
+ }
13759
+ return false;
13760
+ }
13684
13761
  function envBlockReferencesDataHome(block, envShPath) {
13685
13762
  for (const spelling of candidateSpellings(envShPath)) {
13686
- if (block.includes(spelling) || block.includes(shellQuoteValue(spelling)) || block.includes(`"${spelling}"`)) {
13763
+ if (includesAsPath(block, spelling) || block.includes(shellQuoteValue(spelling)) || block.includes(`"${spelling}"`)) {
13687
13764
  return true;
13688
13765
  }
13689
13766
  }
@@ -13763,14 +13840,15 @@ async function resolveActiveShellProfile(envShPath, platform = process.platform)
13763
13840
  const activePick = await detectShellProfile(platform);
13764
13841
  const visited = /* @__PURE__ */ new Set();
13765
13842
  const queue = [activePick];
13843
+ let firstWithBlock = null;
13766
13844
  while (queue.length > 0) {
13767
13845
  const current = queue.shift();
13768
13846
  if (visited.has(current)) continue;
13769
13847
  visited.add(current);
13770
13848
  const content = await readFileSafe(current);
13771
- const block = content ? extractEnvBlock(content) : null;
13772
- if (block && envBlockReferencesDataHome(block, envShPath)) return current;
13773
13849
  if (!content) continue;
13850
+ if (findEnvBlockFor(content, envShPath)) return current;
13851
+ if (firstWithBlock === null && findEnvBlocks(content).length > 0) firstWithBlock = current;
13774
13852
  for (const name of SHELL_PROFILE_CANDIDATE_NAMES) {
13775
13853
  const candidate = path38.join(home, name);
13776
13854
  if (candidate !== current && !visited.has(candidate) && await referencesCandidate(content, name)) {
@@ -13778,7 +13856,7 @@ async function resolveActiveShellProfile(envShPath, platform = process.platform)
13778
13856
  }
13779
13857
  }
13780
13858
  }
13781
- return activePick;
13859
+ return firstWithBlock ?? activePick;
13782
13860
  }
13783
13861
  var SHELL_PROFILE_CANDIDATE_NAMES;
13784
13862
  var init_shell_profile = __esm({
@@ -13878,12 +13956,18 @@ function parseEnvYamlDocument(raw, label) {
13878
13956
  function envPushItem(relativePath, sourcePath) {
13879
13957
  return { name: relativePath.slice("env/".length), type: "env", sourcePath, relativePath };
13880
13958
  }
13881
- var EnvVariableSchema, EnvYamlSchema, envEntryReader, ENV_KEY_RE, EnvHandler;
13959
+ async function userScopeEnvShPath() {
13960
+ const userConfig = await loadLocalConfigForScope("user");
13961
+ if (userConfig) return path39.join(getDataHome(userConfig), "env.sh");
13962
+ return await pathExists(getUserConfigPath()) ? path39.join(getTeamaiHome("user"), "env.sh") : null;
13963
+ }
13964
+ var EnvVariableSchema, EnvYamlSchema, envEntryReader, ENV_KEY_RE, SOURCES_ENV_SH, EnvHandler;
13882
13965
  var init_env = __esm({
13883
13966
  "src/resources/env.ts"() {
13884
13967
  "use strict";
13885
13968
  init_base();
13886
13969
  init_types();
13970
+ init_config();
13887
13971
  init_fs();
13888
13972
  init_logger();
13889
13973
  init_namespaced_entries();
@@ -13925,6 +14009,7 @@ var init_env = __esm({
13925
14009
  scopeOf: (variable) => variable
13926
14010
  };
13927
14011
  ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
14012
+ SOURCES_ENV_SH = /(?:^|[\s;&|])(?:source|\.)\s[^\n;&|]*env\.sh/m;
13928
14013
  EnvHandler = class extends ResourceHandler {
13929
14014
  type = "env";
13930
14015
  /**
@@ -14014,7 +14099,7 @@ var init_env = __esm({
14014
14099
  if (inject) {
14015
14100
  const profilePath = teamConfig.sharing.env.shellProfilePath ? teamConfig.sharing.env.shellProfilePath : await this.detectShellProfile(envShPath);
14016
14101
  const shellBlock = this.generateShellBlock(teamaiHome);
14017
- await this.injectShellProfile(profilePath, shellBlock);
14102
+ await this.injectShellProfile(profilePath, shellBlock, envShPath, localConfig.scope);
14018
14103
  }
14019
14104
  return true;
14020
14105
  }
@@ -14130,17 +14215,29 @@ var init_env = __esm({
14130
14215
  return resolveActiveShellProfile(envShPath, platform);
14131
14216
  }
14132
14217
  /**
14133
- * Inject the shell block into the profile file (idempotent).
14218
+ * Inject this scope's shell block into the profile file (idempotent).
14219
+ *
14220
+ * The profile keeps the user scope's block plus at most one project block,
14221
+ * the user's first so a project value wins on a key both define (#876). A
14222
+ * scope replaces its own block. Otherwise a project takes over another
14223
+ * project's block, never the user scope's, which keeps the project block
14224
+ * last-wins; a user scope goes in right before the project block. Any other
14225
+ * blocks (a hand-edited profile) are left alone.
14134
14226
  */
14135
- async injectShellProfile(profilePath, block) {
14227
+ async injectShellProfile(profilePath, block, envShPath, scope) {
14136
14228
  const original = await readFileSafe(profilePath) ?? "";
14137
14229
  let content = original;
14138
- const startIdx = content.indexOf(TEAMAI_ENV_START);
14139
- const endIdx = content.indexOf(TEAMAI_ENV_END);
14140
- if (startIdx !== -1 && endIdx !== -1) {
14141
- const before = content.substring(0, startIdx);
14142
- const after = content.substring(endIdx + TEAMAI_ENV_END.length);
14143
- content = before + block + after;
14230
+ const blocks = findEnvBlocks(content).filter((b) => b.end !== null);
14231
+ let target = blocks.find((b) => envBlockReferencesDataHome(b.text, envShPath));
14232
+ if (!target) {
14233
+ const userEnvShPath = scope === "user" ? envShPath : await userScopeEnvShPath();
14234
+ const isUserBlock = (b) => !SOURCES_ENV_SH.test(b.text) || userEnvShPath !== null && envBlockReferencesDataHome(b.text, userEnvShPath);
14235
+ target = blocks.find((b) => scope === "user" ? isUserBlock(b) : !isUserBlock(b));
14236
+ }
14237
+ if (target) {
14238
+ content = content.substring(0, target.start) + block + content.substring(target.end);
14239
+ } else if (scope === "user" && blocks.length > 0) {
14240
+ content = content.substring(0, blocks[0].start) + block + "\n\n" + content.substring(blocks[0].start);
14144
14241
  } else {
14145
14242
  if (content.length > 0 && !content.endsWith("\n")) {
14146
14243
  content += "\n";
@@ -22572,8 +22669,8 @@ async function cleanupStaleSessions(dir, currentSessionId) {
22572
22669
  if (name === currentBasename) continue;
22573
22670
  const filePath = path57.join(dir, entry);
22574
22671
  try {
22575
- const stat9 = await fs25.promises.stat(filePath);
22576
- if (now - stat9.mtimeMs > STALE_SESSION_MS) {
22672
+ const stat10 = await fs25.promises.stat(filePath);
22673
+ if (now - stat10.mtimeMs > STALE_SESSION_MS) {
22577
22674
  await fs25.promises.unlink(filePath);
22578
22675
  }
22579
22676
  } catch {
@@ -25974,7 +26071,7 @@ async function contribute(options) {
25974
26071
  log.debug(`contribute: post-publish index rebuild skipped: ${e.message}`);
25975
26072
  }
25976
26073
  }
25977
- const sessionId = options.sessionId || process.env.CLAUDE_SESSION_ID || "";
26074
+ const sessionId = options.sessionId || await agentSessionIdFromEnv() || "";
25978
26075
  if (sessionId) {
25979
26076
  await markContributed(sessionId);
25980
26077
  }
@@ -26000,6 +26097,7 @@ var init_contribute = __esm({
26000
26097
  init_fs();
26001
26098
  init_logger();
26002
26099
  init_contribute_check();
26100
+ init_session_id();
26003
26101
  init_pending_learnings();
26004
26102
  init_learnings_publish();
26005
26103
  init_learnings_roots();
@@ -27047,7 +27145,8 @@ async function capUsageEvents(config) {
27047
27145
  }
27048
27146
  async function withUsageLock(usagePath, wait, fn) {
27049
27147
  const lockPath = `${usagePath}.lock`;
27050
- for (let i = 0; i < wait.attempts; i++) {
27148
+ const deadline = Date.now() + wait.budgetMs;
27149
+ for (; ; ) {
27051
27150
  if (await acquireLock(lockPath)) {
27052
27151
  try {
27053
27152
  await foldPendingEvents2(usagePath);
@@ -27057,9 +27156,10 @@ async function withUsageLock(usagePath, wait, fn) {
27057
27156
  }
27058
27157
  return true;
27059
27158
  }
27060
- await new Promise((r) => setTimeout(r, wait.delayMs));
27159
+ const remaining = deadline - Date.now();
27160
+ if (remaining <= 0) return false;
27161
+ await new Promise((r) => setTimeout(r, Math.min(wait.delayMs, remaining)));
27061
27162
  }
27062
- return false;
27063
27163
  }
27064
27164
  function pendingPrefix(usagePath) {
27065
27165
  return `${path70.basename(usagePath, ".jsonl")}.pending-`;
@@ -27337,8 +27437,8 @@ var init_usage_tracker = __esm({
27337
27437
  ];
27338
27438
  PROJECT_SKILL_DIRS = [...SKILL_DIRS, ".github/skills"];
27339
27439
  USAGE_EVENT_CAP = 5e3;
27340
- APPEND_LOCK_WAIT = { attempts: 10, delayMs: 25 };
27341
- REWRITE_LOCK_WAIT = { attempts: 100, delayMs: 50 };
27440
+ APPEND_LOCK_WAIT = { budgetMs: 250, delayMs: 25 };
27441
+ REWRITE_LOCK_WAIT = { budgetMs: 5e3, delayMs: 50 };
27342
27442
  USAGE_SIDE_FILE_PATTERNS = ["usage.jsonl.*", "usage.pending-*.jsonl"];
27343
27443
  }
27344
27444
  });
@@ -28916,15 +29016,15 @@ function mergeLocalAndReported(localStats, reported) {
28916
29016
  });
28917
29017
  }
28918
29018
  }
28919
- for (const stat9 of localStats) {
28920
- const existing = map.get(stat9.name);
29019
+ for (const stat10 of localStats) {
29020
+ const existing = map.get(stat10.name);
28921
29021
  if (existing) {
28922
- existing.count += stat9.count;
28923
- if (stat9.lastUsed > existing.lastUsed) {
28924
- existing.lastUsed = stat9.lastUsed;
29022
+ existing.count += stat10.count;
29023
+ if (stat10.lastUsed > existing.lastUsed) {
29024
+ existing.lastUsed = stat10.lastUsed;
28925
29025
  }
28926
29026
  } else {
28927
- map.set(stat9.name, { ...stat9 });
29027
+ map.set(stat10.name, { ...stat10 });
28928
29028
  }
28929
29029
  }
28930
29030
  return Array.from(map.values()).sort((a, b) => b.count - a.count);
@@ -29019,10 +29119,10 @@ async function showStats(options = {}) {
29019
29119
  console.log("");
29020
29120
  const maxNameLen = Math.max(...stats.map((s) => s.name.length), 4);
29021
29121
  const maxCountLen = Math.max(...stats.map((s) => String(s.count).length), 4);
29022
- for (const stat9 of stats) {
29023
- const name = stat9.name.padEnd(maxNameLen);
29024
- const count = String(stat9.count).padStart(maxCountLen);
29025
- const recency = formatRelativeTime(stat9.lastUsed);
29122
+ for (const stat10 of stats) {
29123
+ const name = stat10.name.padEnd(maxNameLen);
29124
+ const count = String(stat10.count).padStart(maxCountLen);
29125
+ const recency = formatRelativeTime(stat10.lastUsed);
29026
29126
  console.log(` ${name} ${count} uses last: ${recency}`);
29027
29127
  }
29028
29128
  const totalEvents = stats.reduce((sum, s) => sum + s.count, 0);
@@ -29259,16 +29359,16 @@ function mergeStats(existing, username, newEvents) {
29259
29359
  skills[name] = { count: data.count, lastUsed: data.lastUsed };
29260
29360
  }
29261
29361
  }
29262
- for (const stat9 of newEvents) {
29263
- const prev = skills[stat9.name];
29264
- const newLastUsed = stat9.lastUsed.toISOString();
29362
+ for (const stat10 of newEvents) {
29363
+ const prev = skills[stat10.name];
29364
+ const newLastUsed = stat10.lastUsed.toISOString();
29265
29365
  if (prev) {
29266
- prev.count += stat9.count;
29366
+ prev.count += stat10.count;
29267
29367
  if (newLastUsed > prev.lastUsed) {
29268
29368
  prev.lastUsed = newLastUsed;
29269
29369
  }
29270
29370
  } else {
29271
- skills[stat9.name] = { count: stat9.count, lastUsed: newLastUsed };
29371
+ skills[stat10.name] = { count: stat10.count, lastUsed: newLastUsed };
29272
29372
  }
29273
29373
  }
29274
29374
  return {
@@ -29304,7 +29404,7 @@ async function readSnapshot(name, config, split, persist = true) {
29304
29404
  if (!persist) return seed;
29305
29405
  try {
29306
29406
  await writeJson(own, seed);
29307
- const sharedTime = await fs35.promises.stat(sharedSnapshotPath(name)).then((stat9) => stat9.mtime, () => void 0);
29407
+ const sharedTime = await fs35.promises.stat(sharedSnapshotPath(name)).then((stat10) => stat10.mtime, () => void 0);
29308
29408
  if (sharedTime) await fs35.promises.utimes(own, sharedTime, sharedTime);
29309
29409
  } catch (e) {
29310
29410
  log.debug(`Could not seed ${own}: ${e.message}`);
@@ -29696,7 +29796,7 @@ function metricsAsOf(events, at) {
29696
29796
  return at === void 0 ? void 0 : aggregateSessionMetrics(events.filter((e) => Date.parse(e.timestamp) <= at));
29697
29797
  }
29698
29798
  async function snapshotWrittenAt(config) {
29699
- const mtime = (file) => fs35.promises.stat(file).then((stat9) => stat9.mtimeMs, () => void 0);
29799
+ const mtime = (file) => fs35.promises.stat(file).then((stat10) => stat10.mtimeMs, () => void 0);
29700
29800
  const own = await mtime(scopeSnapshotPath("prompt-tokens", config));
29701
29801
  if (own === void 0) return mtime(sharedSnapshotPath("prompt-tokens"));
29702
29802
  const stats = config ? await mtime(path77.join(getReportsDir(config), "stats", `${config.username}.yaml`)) : void 0;
@@ -30357,10 +30457,10 @@ async function envDeliveryProblems(ctx) {
30357
30457
  teamConfig?.sharing?.env?.shellProfilePath ?? await envHandler2.detectShellProfile(envShPath)
30358
30458
  );
30359
30459
  const profile = await readFileSafe(profilePath);
30360
- const block = profile === null ? null : extractEnvBlock(profile);
30460
+ const block = profile === null ? null : findEnvBlockFor(profile, envShPath);
30361
30461
  if (block === null) {
30362
- problems.push(`${profilePath} carries no TeamAI env block`);
30363
- } else if (envSh !== null && !envBlockSourcesPath(block, envShPath)) {
30462
+ problems.push(`${profilePath} carries no TeamAI env block for ${envShPath}`);
30463
+ } else if (envSh !== null && !envBlockSourcesPath(block.text, envShPath)) {
30364
30464
  problems.push(
30365
30465
  `the block in ${profilePath} does not load ${envShPath}: a POSIX shell reads an unquoted backslash as an escape, so the \`[ -f ... ]\` test fails and \`source\` never runs`
30366
30466
  );
@@ -30371,10 +30471,7 @@ async function envDeliveryProblems(ctx) {
30371
30471
  const candidate = path78.join(home, name);
30372
30472
  if (sameFile(candidate, profilePath)) continue;
30373
30473
  const content = await readFileSafe(candidate);
30374
- const strayBlock = content ? extractEnvBlock(content) : null;
30375
- if (strayBlock && envBlockReferencesDataHome(strayBlock, envShPath)) {
30376
- staleProfiles.push(candidate);
30377
- }
30474
+ if (content && findEnvBlockFor(content, envShPath)) staleProfiles.push(candidate);
30378
30475
  }
30379
30476
  return { problems, staleProfiles };
30380
30477
  }
@@ -32025,8 +32122,8 @@ async function pull(options, result) {
32025
32122
  const lockScope = async (config) => {
32026
32123
  if (config.repo.kind === "http") return true;
32027
32124
  const lock = path81.join(getDataHome(config), SYNC_LOCK_FILENAME);
32028
- if (await acquireLock(lock)) {
32029
- heldLocks.set(config, lock);
32125
+ if (await acquireLock(lock, { dryRun: options.dryRun })) {
32126
+ if (!options.dryRun) heldLocks.set(config, lock);
32030
32127
  return true;
32031
32128
  }
32032
32129
  log.info(`[${config.scope}] sync in progress elsewhere \u2014 skipped (another pull/push holds the lock)`);
@@ -32037,9 +32134,13 @@ async function pull(options, result) {
32037
32134
  let projectConfig = null;
32038
32135
  const unreadable = [];
32039
32136
  try {
32040
- projectConfig = await detectProjectConfig(void 0, (configPath, error) => {
32041
- unreadable.push(`${configPath}: ${error}`);
32042
- });
32137
+ projectConfig = await detectProjectConfig(
32138
+ void 0,
32139
+ (configPath, error) => {
32140
+ unreadable.push(`${configPath}: ${error}`);
32141
+ },
32142
+ { dryRun: options.dryRun }
32143
+ );
32043
32144
  } catch (e) {
32044
32145
  log.warn(`Project-scope detection error: ${e.message}`);
32045
32146
  }
@@ -32059,7 +32160,7 @@ async function pull(options, result) {
32059
32160
  log.info("project scope detected, skipped user scope");
32060
32161
  } else {
32061
32162
  try {
32062
- const loadedUserConfig = await loadLocalConfigForScope("user");
32163
+ const loadedUserConfig = await loadLocalConfigForScope("user", void 0, { dryRun: options.dryRun });
32063
32164
  if (loadedUserConfig) {
32064
32165
  if (inheritUserScope) {
32065
32166
  inheritedUserConfig = loadedUserConfig;
@@ -32737,7 +32838,10 @@ var init_rules = __esm({
32737
32838
  }
32738
32839
  }
32739
32840
  await this.activateOpencodeInstructions(teamConfig, localConfig, rules.length > 0);
32740
- if (rules.length === 0) return;
32841
+ if (rules.length === 0) {
32842
+ await this.reclaimUnselectedTeamRules(teamConfig, localConfig);
32843
+ return;
32844
+ }
32741
32845
  for (const rule of rules) {
32742
32846
  await this.pullItem(rule, teamConfig, localConfig);
32743
32847
  }
@@ -32865,6 +32969,33 @@ var init_rules = __esm({
32865
32969
  /**
32866
32970
  * Recursively remove empty subdirectories under a given directory.
32867
32971
  */
32972
+ /**
32973
+ * Remove the copies of team rules that no longer reach this directory when none
32974
+ * does — e.g. the last rule of a project the directory dropped, or of one an
32975
+ * admin removed. Only a file TeamAI provably wrote goes: it sits at a team rule's
32976
+ * delivery path and holds exactly what pull rendered for that tool, from the
32977
+ * rule as it is now or as it was at a revision this checkout last pulled (the
32978
+ * admin may have edited the rule before removing its project). That proof holds
32979
+ * in rule directories shared with user-authored rules too, so JoyCode, OMP, Pi
32980
+ * and Copilot are reclaimed like the rest. A personal rule, a locally edited
32981
+ * copy and the author's own copy of a rule they published stay.
32982
+ */
32983
+ async reclaimUnselectedTeamRules(teamConfig, localConfig) {
32984
+ const teamRules = await this.scanTeamForPull(teamConfig, localConfig);
32985
+ if (teamRules.length === 0) return;
32986
+ const deliveredRevs = (await (await Promise.resolve().then(() => (init_pull(), pull_exports))).resolveCheckoutBases(localConfig, await loadStateForScope(localConfig))).revs;
32987
+ const touchedDirs = /* @__PURE__ */ new Set();
32988
+ for (const item of teamRules) {
32989
+ for (const { tool, dest, supersedes } of await this.deliveryTargets(teamConfig, localConfig, item)) {
32990
+ if (supersedes) continue;
32991
+ if (!await isDeliveredRender(tool, dest, item, localConfig.repo.localPath, deliveredRevs)) continue;
32992
+ await remove(dest);
32993
+ touchedDirs.add(path83.join(resolveToolBaseDir(tool, localConfig), scopedToolPaths(teamConfig, localConfig)[tool].rules));
32994
+ log.debug(`Removed unselected team rule ${item.name} from ${tool}`);
32995
+ }
32996
+ }
32997
+ for (const dir of touchedDirs) await this.removeEmptyDirs(dir);
32998
+ }
32868
32999
  async removeEmptyDirs(dir) {
32869
33000
  if (!await pathExists(dir)) return;
32870
33001
  const subdirs = await listDirs(dir);
@@ -33273,6 +33404,7 @@ __export(push_exports, {
33273
33404
  filterExistingTopLevelPaths: () => filterExistingTopLevelPaths,
33274
33405
  push: () => push
33275
33406
  });
33407
+ import { chmod as chmod2, readFile as readFile4, stat as stat3 } from "fs/promises";
33276
33408
  import path86 from "path";
33277
33409
  import YAML20 from "yaml";
33278
33410
  async function filterExistingTopLevelPaths(repoPath, candidates) {
@@ -33453,13 +33585,22 @@ async function hasGitModeChange(git, filePath) {
33453
33585
  return true;
33454
33586
  }
33455
33587
  }
33456
- function isTeamaiOwnedDirtyPath(filePath, pendingTeamConfig, modeChangedPaths) {
33588
+ async function readFileSnapshot(filePath) {
33589
+ try {
33590
+ const [content, stats] = await Promise.all([readFile4(filePath), stat3(filePath)]);
33591
+ return { content, mode: stats.mode & 511 };
33592
+ } catch {
33593
+ return null;
33594
+ }
33595
+ }
33596
+ function isTeamaiOwnedDirtyPath(filePath, pendingTeamConfig, modeChangedPaths, pendingEnvPaths) {
33457
33597
  const normalized = filePath.replaceAll("\\", "/");
33458
33598
  if (normalized === ".teamai/.sync-lock") return true;
33599
+ if (pendingEnvPaths.has(normalized)) return true;
33459
33600
  return normalized === "teamai.yaml" && pendingTeamConfig !== null && !modeChangedPaths.has(normalized);
33460
33601
  }
33461
- function collectUnsafeDirtyPaths(status2, pendingTeamConfig, modeChangedPaths = /* @__PURE__ */ new Set()) {
33462
- return collectDirtyPaths(status2).filter((filePath) => !isTeamaiOwnedDirtyPath(filePath, pendingTeamConfig, modeChangedPaths));
33602
+ function collectUnsafeDirtyPaths(status2, pendingTeamConfig, modeChangedPaths = /* @__PURE__ */ new Set(), pendingEnvPaths = /* @__PURE__ */ new Set()) {
33603
+ return collectDirtyPaths(status2).filter((filePath) => !isTeamaiOwnedDirtyPath(filePath, pendingTeamConfig, modeChangedPaths, pendingEnvPaths));
33463
33604
  }
33464
33605
  function collisionPaths(type, placedAt) {
33465
33606
  if (type !== "agents") return [placedAt];
@@ -33579,7 +33720,7 @@ async function pushGroup(args) {
33579
33720
  log.debug(`Marketplace refresh skipped: ${e.message}`);
33580
33721
  }
33581
33722
  }
33582
- const sweeperCandidates = ["rules/", "env/", ".codebuddy-plugin/"];
33723
+ const sweeperCandidates = ["rules/", ".codebuddy-plugin/"];
33583
33724
  const existingSweepers = await filterExistingTopLevelPaths(
33584
33725
  localConfig.repo.localPath,
33585
33726
  sweeperCandidates
@@ -33655,7 +33796,7 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
33655
33796
  }
33656
33797
  }
33657
33798
  async function push(options, result) {
33658
- const { localConfig, teamConfig } = await autoDetectInit();
33799
+ const { localConfig, teamConfig } = await autoDetectInit(void 0, { dryRun: options.dryRun });
33659
33800
  assertNotReadOnly(localConfig, "teamai push");
33660
33801
  if (options.project && options.role) {
33661
33802
  log.error("Use either --role or --project, not both.");
@@ -33676,30 +33817,22 @@ async function push(options, result) {
33676
33817
  }
33677
33818
  if (localConfig.repo.kind === "self") {
33678
33819
  const selfSyncLock = path86.join(getDataHome(localConfig), SYNC_LOCK_FILENAME);
33679
- if (!await acquireLock(selfSyncLock)) {
33820
+ if (!await acquireLock(selfSyncLock, { dryRun: options.dryRun })) {
33680
33821
  log.error("Another teamai pull/push/migration is in progress for this project. Re-run once it finishes.");
33681
33822
  process.exitCode = 1;
33682
33823
  return;
33683
33824
  }
33684
33825
  try {
33685
- try {
33686
- const { migrateSelfModeGitignore: migrateSelfModeGitignore2 } = await Promise.resolve().then(() => (init_init(), init_exports));
33687
- await migrateSelfModeGitignore2(localConfig);
33688
- } catch {
33826
+ if (!options.dryRun) {
33827
+ try {
33828
+ const { migrateSelfModeGitignore: migrateSelfModeGitignore2 } = await Promise.resolve().then(() => (init_init(), init_exports));
33829
+ await migrateSelfModeGitignore2(localConfig);
33830
+ } catch {
33831
+ }
33689
33832
  }
33690
33833
  const { withKnowledgeWorktree: withKnowledgeWorktree2, EmptyRepoError: EmptyRepoError2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
33691
33834
  try {
33692
- const activeConfigPath = path86.join(localConfig.repo.localPath, "teamai.yaml");
33693
- const activeConfig = await readFileSafe(activeConfigPath);
33694
- const businessRoot = localConfig.repo.businessRepoRoot ?? localConfig.projectRoot;
33695
- let pendingTeamConfig = null;
33696
- if (activeConfig !== null && businessRoot) {
33697
- const relativeConfigPath = path86.relative(businessRoot, activeConfigPath).split(path86.sep).join("/");
33698
- const committed = await getFileContentAtRev(businessRoot, "HEAD", relativeConfigPath);
33699
- if (committed === null || committed.toString() !== activeConfig) {
33700
- pendingTeamConfig = activeConfig;
33701
- }
33702
- }
33835
+ const pendingTeamConfig = await pendingSelfTeamConfig(localConfig);
33703
33836
  await withKnowledgeWorktree2(localConfig, async (wtConfig) => {
33704
33837
  if (pendingTeamConfig !== null) {
33705
33838
  await writeFile(path86.join(wtConfig.repo.localPath, "teamai.yaml"), pendingTeamConfig);
@@ -33720,7 +33853,7 @@ async function push(options, result) {
33720
33853
  return;
33721
33854
  }
33722
33855
  const syncLock = path86.join(getDataHome(localConfig), SYNC_LOCK_FILENAME);
33723
- const locked = await acquireLock(syncLock);
33856
+ const locked = await acquireLock(syncLock, { dryRun: options.dryRun });
33724
33857
  if (!locked) {
33725
33858
  log.error("Another teamai pull/push is in progress for this project. Re-run once it finishes.");
33726
33859
  process.exitCode = 1;
@@ -33732,9 +33865,39 @@ async function push(options, result) {
33732
33865
  await releaseLock(syncLock);
33733
33866
  }
33734
33867
  }
33868
+ async function pendingSelfTeamConfig(localConfig) {
33869
+ const activeConfigPath = path86.join(localConfig.repo.localPath, "teamai.yaml");
33870
+ const activeConfig = await readFileSafe(activeConfigPath);
33871
+ const businessRoot = localConfig.repo.businessRepoRoot ?? localConfig.projectRoot;
33872
+ if (activeConfig === null || !businessRoot) return null;
33873
+ const relativeConfigPath = path86.relative(businessRoot, activeConfigPath).split(path86.sep).join("/");
33874
+ const committed = await getFileContentAtRev(businessRoot, "HEAD", relativeConfigPath);
33875
+ return committed === null || committed.toString() !== activeConfig ? activeConfig : null;
33876
+ }
33735
33877
  async function pushCore(localConfig, teamConfig, options, initialPendingTeamConfig = null, result) {
33736
33878
  const selfMode = localConfig.repo.kind === "self";
33737
33879
  let pendingTeamConfig = initialPendingTeamConfig;
33880
+ const pendingEnvFiles = /* @__PURE__ */ new Map();
33881
+ const restorePendingEnvFiles = async () => {
33882
+ const lost = [];
33883
+ for (const [relativePath, { content, mode }] of pendingEnvFiles) {
33884
+ const target = path86.join(localConfig.repo.localPath, ...relativePath.split("/"));
33885
+ try {
33886
+ await writeFile(target, content);
33887
+ await chmod2(target, mode);
33888
+ } catch (e) {
33889
+ lost.push(`${relativePath} (${e.message})`);
33890
+ }
33891
+ }
33892
+ return lost;
33893
+ };
33894
+ const reportLostEnvFiles = (lost) => {
33895
+ log.error(
33896
+ `Could not put back ${lost.join(", ")} after resetting the team repo, so that env edit is no longer in the clone. Nothing more was pushed. Run \`teamai env add\` again for the variables it held, then push.`
33897
+ );
33898
+ process.exitCode = 1;
33899
+ };
33900
+ let lostEnvFiles = [];
33738
33901
  let teamRepoStale = false;
33739
33902
  if (!selfMode) {
33740
33903
  const pullSpin = spinner("Pulling latest changes...").start();
@@ -33758,10 +33921,26 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
33758
33921
  if (pendingTeamConfig !== null && await hasGitModeChange(git, "teamai.yaml")) {
33759
33922
  modeChangedPaths.add("teamai.yaml");
33760
33923
  }
33924
+ const status2 = await git.status();
33925
+ const indexedPaths = new Set(collectDirtyPaths({
33926
+ staged: status2.staged,
33927
+ created: status2.created,
33928
+ conflicted: status2.conflicted,
33929
+ deleted: status2.deleted,
33930
+ renamed: status2.renamed
33931
+ }));
33932
+ for (const item of await getHandler("env").scanLocalForPush(teamConfig, localConfig)) {
33933
+ if (indexedPaths.has(item.relativePath)) continue;
33934
+ const snapshot = await readFileSnapshot(item.sourcePath);
33935
+ if (snapshot !== null && !await hasGitModeChange(git, item.relativePath)) {
33936
+ pendingEnvFiles.set(item.relativePath, snapshot);
33937
+ }
33938
+ }
33761
33939
  const unsafeDirtyPaths = collectUnsafeDirtyPaths(
33762
- await git.status(),
33940
+ status2,
33763
33941
  pendingTeamConfig,
33764
- modeChangedPaths
33942
+ modeChangedPaths,
33943
+ new Set(pendingEnvFiles.keys())
33765
33944
  );
33766
33945
  if (unsafeDirtyPaths.length > 0) {
33767
33946
  pullSpin.fail(
@@ -33770,8 +33949,12 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
33770
33949
  process.exitCode = 1;
33771
33950
  return;
33772
33951
  }
33773
- await resetToCleanMaster(git, repoPath);
33774
- await pullRepo(repoPath);
33952
+ try {
33953
+ await resetToCleanMaster(git, repoPath);
33954
+ await pullRepo(repoPath);
33955
+ } finally {
33956
+ lostEnvFiles = await restorePendingEnvFiles();
33957
+ }
33775
33958
  if (pendingTeamConfig !== null) {
33776
33959
  await writeFile(yamlPath, pendingTeamConfig);
33777
33960
  }
@@ -33780,6 +33963,10 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
33780
33963
  teamRepoStale = true;
33781
33964
  pullSpin.warn(`Pull failed: ${e.message}`);
33782
33965
  }
33966
+ if (lostEnvFiles.length > 0) {
33967
+ reportLostEnvFiles(lostEnvFiles);
33968
+ return;
33969
+ }
33783
33970
  }
33784
33971
  const brokenCatalogs = await brokenTeamProfileFiles(localConfig.repo.localPath);
33785
33972
  if (brokenCatalogs.length > 0) {
@@ -34206,11 +34393,20 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
34206
34393
  includeTeamConfig: groupIndex === configGroupIndex,
34207
34394
  branch: options.branch
34208
34395
  });
34209
- if (pendingTeamConfig !== null && groupIndex < configGroupIndex) {
34396
+ const configPending = pendingTeamConfig !== null && groupIndex <= configGroupIndex;
34397
+ if (outcome === "failed" && (configPending || pendingEnvFiles.size > 0)) {
34398
+ await checkoutMaster(localConfig.repo.localPath);
34399
+ }
34400
+ if (pendingTeamConfig !== null && configPending && (groupIndex < configGroupIndex || outcome === "failed")) {
34210
34401
  await writeFile(path86.join(localConfig.repo.localPath, "teamai.yaml"), pendingTeamConfig);
34211
34402
  }
34212
- if (outcome === "failed") {
34403
+ if (outcome === "pushed" || outcome === "pr-failed") {
34404
+ for (const item of group.items) pendingEnvFiles.delete(item.relativePath);
34405
+ }
34406
+ const lost = await restorePendingEnvFiles();
34407
+ if (outcome === "failed" || lost.length > 0) {
34213
34408
  await saveStateForScope(pushState, localConfig);
34409
+ if (lost.length > 0) reportLostEnvFiles(lost);
34214
34410
  process.exitCode = 1;
34215
34411
  return;
34216
34412
  }
@@ -34238,6 +34434,8 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
34238
34434
  options,
34239
34435
  anyPrFailed ? void 0 : result
34240
34436
  );
34437
+ const lost = await restorePendingEnvFiles();
34438
+ if (lost.length > 0) reportLostEnvFiles(lost);
34241
34439
  return;
34242
34440
  }
34243
34441
  if (result && anyPushed && !anyPrFailed) result.completed = true;
@@ -34334,6 +34532,7 @@ __export(git_exports, {
34334
34532
  getHeadRev: () => getHeadRev,
34335
34533
  getRemoteUrl: () => getRemoteUrl,
34336
34534
  getRepoStatus: () => getRepoStatus,
34535
+ gitBinary: () => gitBinary,
34337
34536
  hasCommits: () => hasCommits,
34338
34537
  hashObject: () => hashObject,
34339
34538
  initRepo: () => initRepo,
@@ -34356,16 +34555,53 @@ __export(git_exports, {
34356
34555
  resetToCleanMaster: () => resetToCleanMaster,
34357
34556
  resolveAnchors: () => resolveAnchors
34358
34557
  });
34558
+ import { spawnSync as spawnSync7 } from "child_process";
34359
34559
  import fs38 from "fs";
34360
34560
  import { realpath } from "fs/promises";
34361
34561
  import path87 from "path";
34362
34562
  import fse13 from "fs-extra";
34363
34563
  import simpleGit from "simple-git";
34564
+ function gitBinary(options = {}) {
34565
+ if ((options.platform ?? process.platform) === "win32") return "git";
34566
+ const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
34567
+ if (resolvedGit?.pathEnv !== pathEnv || !stillExecutable(resolvedGit.binary)) {
34568
+ resolvedGit = { pathEnv, binary: lookUpGit(pathEnv) };
34569
+ }
34570
+ return resolvedGit.binary;
34571
+ }
34572
+ function stillExecutable(binary) {
34573
+ if (binary === "git") return true;
34574
+ try {
34575
+ fs38.accessSync(binary, fs38.constants.X_OK);
34576
+ return true;
34577
+ } catch {
34578
+ return false;
34579
+ }
34580
+ }
34581
+ function lookUpGit(pathEnv) {
34582
+ for (const dir of pathEnv.split(path87.delimiter)) {
34583
+ if (!path87.isAbsolute(dir)) return "git";
34584
+ const candidate = path87.join(dir, "git");
34585
+ let stat10;
34586
+ try {
34587
+ stat10 = fs38.statSync(candidate);
34588
+ } catch (e) {
34589
+ if (typeof e === "object" && e !== null && "code" in e && (e.code === "ENOENT" || e.code === "ENOTDIR")) continue;
34590
+ return "git";
34591
+ }
34592
+ if (!stat10.isFile() || !SIMPLE_GIT_SAFE_BINARY.test(candidate)) return "git";
34593
+ return spawnsByPath(candidate) ? candidate : "git";
34594
+ }
34595
+ return "git";
34596
+ }
34597
+ function spawnsByPath(candidate) {
34598
+ return spawnSync7(candidate, ["--version"], { stdio: "ignore", timeout: 1e4 }).error === void 0;
34599
+ }
34364
34600
  function createGit(basePath) {
34365
34601
  if (basePath) {
34366
- return simpleGit({ baseDir: basePath });
34602
+ return simpleGit({ baseDir: basePath, binary: gitBinary() });
34367
34603
  }
34368
- return simpleGit();
34604
+ return simpleGit({ binary: gitBinary() });
34369
34605
  }
34370
34606
  function commitSkippingHooks(git, message, paths) {
34371
34607
  if (paths === void 0) return git.commit(message, { "--no-verify": null });
@@ -34379,7 +34615,7 @@ async function isGitRepo(localPath) {
34379
34615
  }
34380
34616
  async function initRepo(remote, localPath) {
34381
34617
  await fse13.ensureDir(localPath);
34382
- const git = simpleGit({ baseDir: localPath });
34618
+ const git = createGit(localPath);
34383
34619
  await git.init();
34384
34620
  await git.addRemote("origin", remote);
34385
34621
  }
@@ -34861,11 +35097,12 @@ async function getRepoStatus(localPath) {
34861
35097
  modified: [...status2.modified, ...status2.not_added, ...status2.created]
34862
35098
  };
34863
35099
  }
34864
- var defaultBranchCache, anchorsByDir;
35100
+ var SIMPLE_GIT_SAFE_BINARY, resolvedGit, defaultBranchCache, anchorsByDir;
34865
35101
  var init_git2 = __esm({
34866
35102
  "src/utils/git.ts"() {
34867
35103
  "use strict";
34868
35104
  init_logger();
35105
+ SIMPLE_GIT_SAFE_BINARY = /^([a-z]:)?([a-z0-9/.\\_~-]+)$/i;
34869
35106
  defaultBranchCache = /* @__PURE__ */ new Map();
34870
35107
  anchorsByDir = /* @__PURE__ */ new Map();
34871
35108
  }
@@ -37027,7 +37264,7 @@ async function status(options) {
37027
37264
  await statusAll();
37028
37265
  return;
37029
37266
  }
37030
- const { localConfig, teamConfig } = await autoDetectInit();
37267
+ const { localConfig, teamConfig } = await autoDetectInit(void 0, { dryRun: true });
37031
37268
  const scopeLabel = localConfig.scope;
37032
37269
  console.log("");
37033
37270
  log.info(`Scope: ${scopeLabel}${scopeLabel === "project" && localConfig.projectRoot ? ` (${localConfig.projectRoot})` : ""}`);
@@ -37157,7 +37394,7 @@ async function statusAll() {
37157
37394
  }
37158
37395
  }
37159
37396
  async function list(type, options) {
37160
- const { localConfig, teamConfig } = await autoDetectInit();
37397
+ const { localConfig, teamConfig } = await autoDetectInit(void 0, { dryRun: true });
37161
37398
  const repoPath = localConfig.repo.localPath;
37162
37399
  const source = options.source ?? "all";
37163
37400
  if (!["repo", "local", "all"].includes(source)) {
@@ -37707,13 +37944,21 @@ async function removeCore(type, names, options, localConfig, teamConfig) {
37707
37944
  }
37708
37945
  if (type === "mcp" && !name.includes("/")) {
37709
37946
  const target = await mcpRemovalTarget(name, teamItems.filter((item) => item.name === name).map(qualified), localConfig, options);
37710
- if (target === "ambiguous") {
37711
- ambiguous = true;
37712
- } else if (target === null) {
37713
- notFound2.push(name);
37714
- } else {
37715
- if (target !== name) log.info(`${name} is ${target}`);
37716
- found.push(target);
37947
+ switch (target.kind) {
37948
+ case "ambiguous":
37949
+ ambiguous = true;
37950
+ break;
37951
+ case "not-found":
37952
+ notFound2.push(name);
37953
+ break;
37954
+ case "target":
37955
+ if (target.name !== name) log.info(`${name} is ${target.name}`);
37956
+ found.push(target.name);
37957
+ break;
37958
+ default: {
37959
+ const unhandled = target;
37960
+ throw new Error(`Unhandled MCP removal target: ${JSON.stringify(unhandled)}`);
37961
+ }
37717
37962
  }
37718
37963
  continue;
37719
37964
  }
@@ -37842,33 +38087,34 @@ async function mcpRemovalTarget(name, candidates, localConfig, options) {
37842
38087
  const target = await entryNamespaceFromFlags2(localConfig.repo.localPath, "mcp", options);
37843
38088
  if (!target.ok) {
37844
38089
  log.error(target.message);
37845
- return "ambiguous";
38090
+ return { kind: "ambiguous" };
37846
38091
  }
37847
38092
  const wanted = target.namespace === null ? name : `${target.namespace}/${name}`;
37848
- if (candidates.includes(wanted)) return wanted;
38093
+ if (candidates.includes(wanted)) return { kind: "target", name: wanted };
37849
38094
  const file = entryFilePath2("mcp", target.namespace);
37850
38095
  if ((await unreadableMcpFiles(localConfig.repo.localPath)).includes(file)) {
37851
38096
  log.error(`${file} does not parse, so "${name}" cannot be found in it. Fix it in the team repo, then retry.`);
37852
- return "ambiguous";
38097
+ return { kind: "ambiguous" };
37853
38098
  }
37854
- return null;
38099
+ return { kind: "not-found" };
37855
38100
  }
37856
- if (candidates.includes(name)) return name;
38101
+ if (candidates.includes(name)) return { kind: "target", name };
37857
38102
  const unreadable = await unreadableMcpFiles(localConfig.repo.localPath);
37858
38103
  if (unreadable.length > 0) {
37859
38104
  log.error(
37860
38105
  `Cannot tell which MCP file defines "${name}": ${unreadable.join(", ")} does not parse. Fix it in the team repo, or pass --role <ns> or --project <id> to name the file.`
37861
38106
  );
37862
- return "ambiguous";
38107
+ return { kind: "ambiguous" };
37863
38108
  }
37864
38109
  if (candidates.length > 1) {
37865
38110
  const files = candidates.map((candidate) => `mcp/${candidate.slice(0, candidate.lastIndexOf("/"))}/mcp.yaml`);
37866
38111
  log.error(
37867
38112
  `MCP server "${name}" is not in mcp/mcp.yaml but is defined in several namespace files (${files.join(", ")}), and each reaches different members. Pass --role <ns> or --project <id> to remove it from one of them.`
37868
38113
  );
37869
- return "ambiguous";
38114
+ return { kind: "ambiguous" };
37870
38115
  }
37871
- return candidates[0] ?? null;
38116
+ const only = candidates[0];
38117
+ return only === void 0 ? { kind: "not-found" } : { kind: "target", name: only };
37872
38118
  }
37873
38119
  async function unreadableMcpFiles(repoPath) {
37874
38120
  const { listEntryFiles: listEntryFiles2 } = await Promise.resolve().then(() => (init_namespaced_entries(), namespaced_entries_exports));
@@ -39174,8 +39420,7 @@ async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
39174
39420
  ]));
39175
39421
  for (const candidate of candidateProfilePaths) {
39176
39422
  const profileContent = await readFileSafe(candidate);
39177
- const block = profileContent ? extractEnvBlock(profileContent) : null;
39178
- if (block && envBlockReferencesDataHome(block, envShPath)) {
39423
+ if (profileContent && findEnvBlockFor(profileContent, envShPath)) {
39179
39424
  plan.shellProfiles.push(candidate);
39180
39425
  }
39181
39426
  }
@@ -39437,15 +39682,15 @@ async function executeRemoval(plan) {
39437
39682
  if (plan.agentFiles.length > 0) {
39438
39683
  log.success(`Removed ${plan.agentFiles.length} agent files`);
39439
39684
  }
39685
+ const envShPath = path97.join(plan.teamaiHome, "env.sh");
39440
39686
  for (const profilePath of plan.shellProfiles) {
39441
39687
  try {
39442
39688
  const content = await readFileSafe(profilePath);
39443
39689
  if (content) {
39444
- const startIdx = content.indexOf(TEAMAI_ENV_START);
39445
- const endIdx = content.indexOf(TEAMAI_ENV_END);
39446
- if (startIdx !== -1 && endIdx !== -1) {
39447
- const before = content.substring(0, startIdx).replace(/\n+$/, "\n");
39448
- const after = content.substring(endIdx + TEAMAI_ENV_END.length).replace(/^\n+/, "\n");
39690
+ const block = findEnvBlockFor(content, envShPath);
39691
+ if (block && block.end !== null) {
39692
+ const before = content.substring(0, block.start).replace(/\n+$/, "\n");
39693
+ const after = content.substring(block.end).replace(/^\n+/, "\n");
39449
39694
  await writeFile(profilePath, before + after);
39450
39695
  log.success(`Cleaned shell profile: ${profilePath}`);
39451
39696
  }
@@ -40307,7 +40552,7 @@ async function saveSession(options) {
40307
40552
  log.info("No local session data yet \u2014 nothing to save.");
40308
40553
  return;
40309
40554
  }
40310
- const sessionId = options.sessionId || process.env.CLAUDE_SESSION_ID || mostRecentSessionId(events);
40555
+ const sessionId = options.sessionId || await agentSessionIdFromEnv() || mostRecentSessionId(events);
40311
40556
  if (!sessionId) {
40312
40557
  log.error("Could not determine a session id. Pass --session-id <id>.");
40313
40558
  return;
@@ -40431,6 +40676,7 @@ var init_save_session = __esm({
40431
40676
  init_session_collector();
40432
40677
  init_logger();
40433
40678
  init_async();
40679
+ init_session_id();
40434
40680
  init_types();
40435
40681
  }
40436
40682
  });
@@ -40463,7 +40709,7 @@ async function dashboardWorkspaces(events) {
40463
40709
  const seen = /* @__PURE__ */ new Set();
40464
40710
  async function add(root, dataHome) {
40465
40711
  if (seen.has(root)) return;
40466
- if (!await fs41.stat(root).then((stat9) => stat9.isDirectory()).catch(() => false)) return;
40712
+ if (!await fs41.stat(root).then((stat10) => stat10.isDirectory()).catch(() => false)) return;
40467
40713
  const config = await readConfigFrom(dataHome, root);
40468
40714
  if (!config) return;
40469
40715
  seen.add(root);
@@ -41748,16 +41994,16 @@ function renderOverviewCards(data) {
41748
41994
  return `<section id="overview"><h2 data-i18n>Overview</h2><div class="cards">${cardHtml}</div></section>`;
41749
41995
  }
41750
41996
  function renderCoverageSection(coverage) {
41751
- const rows = coverage.map((stat9) => {
41752
- const label = escapeHtml(stat9.type);
41753
- const pct = stat9.coveragePct;
41997
+ const rows = coverage.map((stat10) => {
41998
+ const label = escapeHtml(stat10.type);
41999
+ const pct = stat10.coveragePct;
41754
42000
  return [
41755
42001
  `<div class="cov-row">`,
41756
42002
  ` <div class="cov-label">${label}</div>`,
41757
42003
  ` <div class="cov-bar-wrap">`,
41758
42004
  ` <div class="cov-bar" style="width:${pct}%"></div>`,
41759
42005
  ` </div>`,
41760
- ` <div class="cov-stat">${stat9.covered}/${stat9.total} (${pct}%)</div>`,
42006
+ ` <div class="cov-stat">${stat10.covered}/${stat10.total} (${pct}%)</div>`,
41761
42007
  `</div>`
41762
42008
  ].join("");
41763
42009
  }).join("");
@@ -42513,7 +42759,7 @@ async function getVizSummary(opts = {}) {
42513
42759
  return {
42514
42760
  totalEntries: data.totalEntries,
42515
42761
  overallCoveragePct: data.overallCoveragePct,
42516
- coverage: (data.coverage ?? []).map((stat9) => ({ type: stat9.type, coveragePct: stat9.coveragePct })),
42762
+ coverage: (data.coverage ?? []).map((stat10) => ({ type: stat10.type, coveragePct: stat10.coveragePct })),
42517
42763
  source: data.source
42518
42764
  };
42519
42765
  }
@@ -42582,27 +42828,30 @@ async function startDashboard(port) {
42582
42828
  const contextRequests = /* @__PURE__ */ new Map();
42583
42829
  const contextCaches = /* @__PURE__ */ new Map();
42584
42830
  const clients = /* @__PURE__ */ new Set();
42585
- let watchDebounce = null;
42586
- const watcher = fs43.watch(eventsPath, () => {
42587
- if (watchDebounce) clearTimeout(watchDebounce);
42588
- watchDebounce = setTimeout(async () => {
42589
- try {
42590
- const events = await readEvents(eventsPath);
42591
- const workspaces = await getWorkspaces(events);
42592
- const sessions = rebuildSessions(events);
42593
- const data = JSON.stringify(sessions);
42594
- for (const client of clients) {
42595
- const workspace = workspaces.find((w) => w.id === clientScopes.get(client));
42596
- client.write(`data: ${workspace ? JSON.stringify(rebuildSessions(workspaceEvents(events, workspace, workspaces))) : data}
42831
+ const pushSessions = async () => {
42832
+ try {
42833
+ const events = await readEvents(eventsPath);
42834
+ const workspaces = await getWorkspaces(events);
42835
+ const sessions = rebuildSessions(events);
42836
+ const data = JSON.stringify(sessions);
42837
+ for (const client of clients) {
42838
+ const workspace = workspaces.find((w) => w.id === clientScopes.get(client));
42839
+ client.write(`data: ${workspace ? JSON.stringify(rebuildSessions(workspaceEvents(events, workspace, workspaces))) : data}
42597
42840
 
42598
42841
  `);
42599
- }
42600
- } catch (e) {
42601
- log.debug(`dashboard: SSE push error: ${e.message}`);
42602
42842
  }
42603
- }, 200);
42843
+ } catch (e) {
42844
+ log.debug(`dashboard: SSE push error: ${e.message}`);
42845
+ }
42846
+ };
42847
+ let watchDebounce = null;
42848
+ const eventsFileName = path107.basename(eventsPath);
42849
+ const watcher = fs43.watch(path107.dirname(eventsPath), (_eventType, filename) => {
42850
+ if (filename && filename.toString() !== eventsFileName) return;
42851
+ if (watchDebounce) clearTimeout(watchDebounce);
42852
+ watchDebounce = setTimeout(() => void pushSessions(), 200);
42604
42853
  });
42605
- const pidCheckInterval = setInterval(async () => {
42854
+ const checkPids = async () => {
42606
42855
  try {
42607
42856
  const events = await readEvents(eventsPath);
42608
42857
  const sessions = rebuildSessions(events);
@@ -42631,8 +42880,9 @@ async function startDashboard(port) {
42631
42880
  } catch (e) {
42632
42881
  log.debug(`dashboard: PID check error: ${e.message}`);
42633
42882
  }
42634
- }, DASHBOARD_PID_CHECK_INTERVAL_MS);
42635
- const server = http.createServer(async (req, res) => {
42883
+ };
42884
+ const pidCheckInterval = setInterval(() => void checkPids(), DASHBOARD_PID_CHECK_INTERVAL_MS);
42885
+ const handleRequest = async (req, res) => {
42636
42886
  const url = new URL(req.url ?? "/", `http://localhost:${serverPort}`);
42637
42887
  res.setHeader("Access-Control-Allow-Origin", "*");
42638
42888
  if (url.pathname === "/" || url.pathname === "/index.html") {
@@ -42761,6 +43011,15 @@ async function startDashboard(port) {
42761
43011
  }
42762
43012
  res.writeHead(404, { "Content-Type": "text/plain" });
42763
43013
  res.end("Not Found");
43014
+ };
43015
+ const server = http.createServer((req, res) => {
43016
+ handleRequest(req, res).catch((e) => {
43017
+ log.debug(`dashboard: request error: ${e instanceof Error ? e.message : String(e)}`);
43018
+ if (!res.headersSent) {
43019
+ res.writeHead(500, { "Content-Type": "application/json" });
43020
+ res.end(JSON.stringify({ error: "Internal error" }));
43021
+ }
43022
+ });
42764
43023
  });
42765
43024
  server.on("error", (err) => {
42766
43025
  if (err.code === "EADDRINUSE") {
@@ -43144,8 +43403,8 @@ async function parseTranscriptForVotes(transcriptPath) {
43144
43403
  let finalAssistantText = "";
43145
43404
  let finalAssistantId;
43146
43405
  try {
43147
- const stat9 = await fs45.promises.stat(transcriptPath);
43148
- if (stat9.size === 0) return emptyResult();
43406
+ const stat10 = await fs45.promises.stat(transcriptPath);
43407
+ if (stat10.size === 0) return emptyResult();
43149
43408
  } catch {
43150
43409
  return emptyResult();
43151
43410
  }
@@ -43837,7 +44096,7 @@ __export(mr_hint_exports, {
43837
44096
  mrHint: () => mrHint,
43838
44097
  parseRemoteToRepo: () => parseRemoteToRepo
43839
44098
  });
43840
- import { spawnSync as spawnSync7 } from "child_process";
44099
+ import { spawnSync as spawnSync8 } from "child_process";
43841
44100
  import fs48 from "fs";
43842
44101
  import path113 from "path";
43843
44102
  function repoSlug(owner, repo) {
@@ -43875,7 +44134,7 @@ function saveCache(owner, repo, cache) {
43875
44134
  }
43876
44135
  function getGitRemote(cwd) {
43877
44136
  try {
43878
- const result = spawnSync7("git", ["remote", "get-url", "origin"], {
44137
+ const result = spawnSync8("git", ["remote", "get-url", "origin"], {
43879
44138
  cwd,
43880
44139
  encoding: "utf-8",
43881
44140
  stdio: ["pipe", "pipe", "pipe"]
@@ -43962,7 +44221,7 @@ async function listGitHubMergedMRsViaREST(owner, repo, since) {
43962
44221
  }
43963
44222
  async function listGitHubMergedMRs(owner, repo, since) {
43964
44223
  try {
43965
- const result = spawnSync7(
44224
+ const result = spawnSync8(
43966
44225
  "gh",
43967
44226
  [
43968
44227
  "pr",
@@ -44558,6 +44817,7 @@ __export(hook_dispatch_cli_exports, {
44558
44817
  deriveDispatchSessionId: () => deriveDispatchSessionId,
44559
44818
  hookDispatchCli: () => hookDispatchCli,
44560
44819
  parseStdin: () => parseStdin,
44820
+ readStdin: () => readStdin4,
44561
44821
  salvageStdinFields: () => salvageStdinFields,
44562
44822
  trySpawnDetachedViaWmi: () => trySpawnDetachedViaWmi
44563
44823
  });
@@ -44565,17 +44825,23 @@ import { spawn as spawn4 } from "child_process";
44565
44825
  import fs49 from "fs";
44566
44826
  import os10 from "os";
44567
44827
  import path115 from "path";
44568
- async function readStdin4() {
44569
- if (process.stdin.isTTY) return "";
44828
+ async function readStdin4(stream = process.stdin, timeoutMs = STDIN_READ_TIMEOUT_MS) {
44829
+ if (stream.isTTY) return "";
44570
44830
  const chunks = [];
44571
44831
  const readAll = (async () => {
44572
- for await (const chunk of process.stdin) {
44832
+ for await (const chunk of stream) {
44573
44833
  chunks.push(chunk);
44574
44834
  }
44575
44835
  })();
44836
+ void readAll.catch(() => {
44837
+ });
44576
44838
  let timer;
44839
+ let timedOut = false;
44577
44840
  const deadline = new Promise((resolve) => {
44578
- timer = setTimeout(resolve, STDIN_READ_TIMEOUT_MS);
44841
+ timer = setTimeout(() => {
44842
+ timedOut = true;
44843
+ resolve();
44844
+ }, timeoutMs);
44579
44845
  timer.unref();
44580
44846
  });
44581
44847
  try {
@@ -44583,8 +44849,7 @@ async function readStdin4() {
44583
44849
  } finally {
44584
44850
  if (timer) clearTimeout(timer);
44585
44851
  }
44586
- readAll.catch(() => {
44587
- });
44852
+ if (timedOut && !stream.destroyed) stream.destroy();
44588
44853
  return Buffer.concat(chunks).toString("utf-8");
44589
44854
  }
44590
44855
  async function spawnBackground(event, tool, matcher, raw, cwd) {
@@ -44888,7 +45153,7 @@ __export(graph_index_schema_exports, {
44888
45153
  toPageSlug: () => toPageSlug,
44889
45154
  validateGraph: () => validateGraph
44890
45155
  });
44891
- import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
45156
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
44892
45157
  import path117 from "path";
44893
45158
  import { z as z11 } from "zod";
44894
45159
  function toPageSlug(relativePath) {
@@ -45076,7 +45341,7 @@ function computeGraphHealth(graph) {
45076
45341
  async function loadGraphIndex(wikiRoot) {
45077
45342
  const graphPath = path117.join(wikiRoot, ".indices", "graph-index.json");
45078
45343
  try {
45079
- const raw = await readFile4(graphPath, "utf8");
45344
+ const raw = await readFile5(graphPath, "utf8");
45080
45345
  const parsed = JSON.parse(raw);
45081
45346
  return parseGraphIndex(parsed);
45082
45347
  } catch {
@@ -45227,7 +45492,7 @@ var init_graph_index_schema = __esm({
45227
45492
  });
45228
45493
 
45229
45494
  // src/code-knowledge-recall.ts
45230
- import { readFile as readFile5, readdir as readdir2 } from "fs/promises";
45495
+ import { readFile as readFile6, readdir as readdir2 } from "fs/promises";
45231
45496
  import path118 from "path";
45232
45497
  import matter9 from "gray-matter";
45233
45498
  function countOccurrences(text, token) {
@@ -45365,7 +45630,7 @@ async function loadWikiPages(wikiRoot, depth) {
45365
45630
  if (depth === "route") {
45366
45631
  const routerPath = path118.join(wikiRoot, "router.md");
45367
45632
  try {
45368
- const content = await readFile5(routerPath, "utf-8");
45633
+ const content = await readFile6(routerPath, "utf-8");
45369
45634
  const titleMatch = content.match(/^title:\s*(.+)$/m);
45370
45635
  const title = titleMatch ? titleMatch[1].trim() : "Team Wiki Router";
45371
45636
  pages.push({
@@ -45477,7 +45742,7 @@ async function loadPagesRecursive(dir, relativePath, pages, depth, currentDepth
45477
45742
  if (depthFromProject <= 1 && NAVIGATION_FILES.has(entry.name)) continue;
45478
45743
  }
45479
45744
  try {
45480
- const content = await readFile5(fullPath, "utf-8");
45745
+ const content = await readFile6(fullPath, "utf-8");
45481
45746
  let title;
45482
45747
  let sources;
45483
45748
  try {
@@ -45632,6 +45897,7 @@ __export(recall_exports, {
45632
45897
  computeIdfBaseline: () => computeIdfBaseline,
45633
45898
  formatResults: () => formatResults,
45634
45899
  isRelevantScore: () => isRelevantScore,
45900
+ normalizeLearningsScoreForRanking: () => normalizeLearningsScoreForRanking,
45635
45901
  recall: () => recall
45636
45902
  });
45637
45903
  import path119 from "path";
@@ -45650,6 +45916,12 @@ function computeIdfBaseline(indexes) {
45650
45916
  if (maxEntries === 0) return 1;
45651
45917
  return Math.log((maxEntries + 1) / 2) + 1;
45652
45918
  }
45919
+ function normalizeLearningsScoreForRanking(score, idfBaseline) {
45920
+ if (score <= 0) return 0;
45921
+ const baseline = idfBaseline > 0 ? idfBaseline : 1;
45922
+ const threshold = Math.max(baseline * LEARNINGS_RELEVANCE_RATIO, LEARNINGS_ABSOLUTE_FLOOR);
45923
+ return Math.min(10, Math.max(0, 4 + 2 * Math.log2(score / threshold)));
45924
+ }
45653
45925
  function resolveReadablePath(indexedPath, filename, learningsBase) {
45654
45926
  if (indexedPath && existsSync3(indexedPath)) return indexedPath;
45655
45927
  const underBase = learningsBase ? path119.join(learningsBase, filename) : null;
@@ -45919,13 +46191,14 @@ async function recall(query, options) {
45919
46191
  );
45920
46192
  const idfBaseline = computeIdfBaseline(scopeIndexes.map((s) => s.index));
45921
46193
  for (const { index, scope, learningsBase } of scopeIndexes) {
46194
+ const scopeIdfBaseline = computeIdfBaseline([index]);
45922
46195
  const results = search(query, index);
45923
46196
  for (const r of results) {
45924
46197
  const entryKey = `${r.entry.type}:${r.entry.filename}`;
45925
46198
  if (scope === "user" && projectEntryKeys.has(entryKey)) continue;
45926
46199
  if (!seenEntries.has(entryKey)) {
45927
46200
  seenEntries.add(entryKey);
45928
- allResults.push({ ...r, scope, learningsBase });
46201
+ allResults.push({ ...r, scope, learningsBase, idfBaseline: scopeIdfBaseline });
45929
46202
  }
45930
46203
  }
45931
46204
  }
@@ -45957,18 +46230,20 @@ async function recall(query, options) {
45957
46230
  } catch {
45958
46231
  log.warn("recall: code graph retrieval unavailable, run teamai codebase --lint to diagnose");
45959
46232
  }
46233
+ const rankingScore = (result) => result.fromCodebase ? result.score : normalizeLearningsScoreForRanking(result.score, result.idfBaseline ?? idfBaseline);
45960
46234
  allResults.sort((a, b) => {
45961
- if (b.score !== a.score) return b.score - a.score;
46235
+ const scoreDelta = rankingScore(b) - rankingScore(a);
46236
+ if (scoreDelta !== 0) return scoreDelta;
45962
46237
  return (b.entry.date || "").localeCompare(a.entry.date || "");
45963
46238
  });
45964
46239
  if (options.check) {
45965
46240
  const top = allResults.length > 0 ? allResults[0] : void 0;
45966
- emitCheckVerdict(top?.score ?? 0, top?.fromCodebase ?? false, idfBaseline, top);
46241
+ emitCheckVerdict(top?.score ?? 0, top?.fromCodebase ?? false, top?.idfBaseline ?? idfBaseline, top);
45967
46242
  return;
45968
46243
  }
45969
46244
  const topResults = allResults.slice(0, 5);
45970
46245
  if (process.env.TEAMAI_RECALL_DISABLED !== "1") {
45971
- recordRecallQuality(deriveSessionId({}), topResults);
46246
+ recordRecallQuality(await agentSessionIdFromEnv() ?? deriveSessionId({}), topResults);
45972
46247
  }
45973
46248
  if (topResults.length === 0) {
45974
46249
  log.info(`No matching learnings found for "${query}".`);
@@ -46158,8 +46433,8 @@ function keyToAbsPath(key) {
46158
46433
  async function loadCacheIndex() {
46159
46434
  const indexPath = path121.join(getCacheRoot(), INDEX_FILENAME);
46160
46435
  try {
46161
- const stat9 = await fs50.stat(indexPath);
46162
- if (stat9.size > MAX_CONFIG_FILE_BYTES) {
46436
+ const stat10 = await fs50.stat(indexPath);
46437
+ if (stat10.size > MAX_CONFIG_FILE_BYTES) {
46163
46438
  throw new Error(`${indexPath} exceeds max allowed size 10MB`);
46164
46439
  }
46165
46440
  const raw = await fs50.readFile(indexPath, "utf8");
@@ -46188,20 +46463,20 @@ function emptyIndex() {
46188
46463
  }
46189
46464
  async function statDirSize(absPath) {
46190
46465
  let total = 0;
46191
- let stat9;
46466
+ let stat10;
46192
46467
  try {
46193
- stat9 = await fs50.lstat(absPath);
46468
+ stat10 = await fs50.lstat(absPath);
46194
46469
  } catch (err) {
46195
46470
  log.debug(`[cache-index] statDirSize: lstat failed, skipping ${absPath}: ${String(err)}`);
46196
46471
  return 0;
46197
46472
  }
46198
- if (stat9.isSymbolicLink()) {
46473
+ if (stat10.isSymbolicLink()) {
46199
46474
  return 0;
46200
46475
  }
46201
- if (stat9.isFile()) {
46202
- return stat9.size;
46476
+ if (stat10.isFile()) {
46477
+ return stat10.size;
46203
46478
  }
46204
- if (!stat9.isDirectory()) {
46479
+ if (!stat10.isDirectory()) {
46205
46480
  return 0;
46206
46481
  }
46207
46482
  let entries;
@@ -46623,8 +46898,8 @@ async function scanCandidates(opts) {
46623
46898
  if (ext !== ".md" && ext !== ".txt") continue;
46624
46899
  const absPath = path122.join(expandedDir, relPath);
46625
46900
  try {
46626
- const stat9 = fs51.statSync(absPath);
46627
- if (stat9.size > MAX_FILE_SIZE_BYTES) continue;
46901
+ const stat10 = fs51.statSync(absPath);
46902
+ if (stat10.size > MAX_FILE_SIZE_BYTES) continue;
46628
46903
  } catch (statErr) {
46629
46904
  log.warn(`cannot stat file, skipped: ${absPath} (${String(statErr)})`);
46630
46905
  continue;
@@ -46649,8 +46924,8 @@ async function scanCandidates(opts) {
46649
46924
  if (path122.extname(relPath).toLowerCase() !== ".md") continue;
46650
46925
  const absPath = path122.join(baseDir, relPath);
46651
46926
  try {
46652
- const stat9 = fs51.statSync(absPath);
46653
- if (stat9.size > MAX_FILE_SIZE_BYTES) continue;
46927
+ const stat10 = fs51.statSync(absPath);
46928
+ if (stat10.size > MAX_FILE_SIZE_BYTES) continue;
46654
46929
  } catch (statErr) {
46655
46930
  log.warn(`cannot stat file, skipped: ${absPath} (${String(statErr)})`);
46656
46931
  continue;
@@ -47095,7 +47370,7 @@ var init_iwiki_client = __esm({
47095
47370
 
47096
47371
  // src/import-iwiki.ts
47097
47372
  import path123 from "path";
47098
- import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
47373
+ import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
47099
47374
  function parseIWikiInput(input) {
47100
47375
  const trimmed = input.trim();
47101
47376
  if (/^\d+$/.test(trimmed)) {
@@ -47204,7 +47479,7 @@ async function importFromIWiki(opts) {
47204
47479
  }
47205
47480
  async function reconcileIwikiWithCodebase(documents, teamwikiRoot) {
47206
47481
  const graphPath = path123.join(teamwikiRoot, ".indices", "graph-index.json");
47207
- const graphRaw = await readFile6(graphPath, "utf-8");
47482
+ const graphRaw = await readFile7(graphPath, "utf-8");
47208
47483
  const graph = JSON.parse(graphRaw);
47209
47484
  const codeLabels = /* @__PURE__ */ new Map();
47210
47485
  for (const node of graph.nodes) {
@@ -47222,7 +47497,7 @@ async function reconcileIwikiWithCodebase(documents, teamwikiRoot) {
47222
47497
  const files = await readdir10(projectDir).catch(() => []);
47223
47498
  for (const file of files) {
47224
47499
  if (!file.endsWith(".md")) continue;
47225
- const content = await readFile6(path123.join(projectDir, file), "utf-8").catch(() => "");
47500
+ const content = await readFile7(path123.join(projectDir, file), "utf-8").catch(() => "");
47226
47501
  codePageContents.set(`evidence/code/${project}/${file}`, content);
47227
47502
  }
47228
47503
  }
@@ -47519,8 +47794,8 @@ async function resolveDocDate(filePath, filename) {
47519
47794
  return parsed;
47520
47795
  }
47521
47796
  }
47522
- const stat9 = await fs52.stat(filePath);
47523
- return stat9.mtime;
47797
+ const stat10 = await fs52.stat(filePath);
47798
+ return stat10.mtime;
47524
47799
  }
47525
47800
  async function listComparableLearnings(learningsDirs, namespaces) {
47526
47801
  const out = await listLearningFiles(learningsDirs);
@@ -48127,7 +48402,7 @@ var init_codebase = __esm({
48127
48402
  // src/wiki-engine/code-knowledge/code-collector.ts
48128
48403
  import { createHash as createHash11 } from "crypto";
48129
48404
  import { execFile as execFile3 } from "child_process";
48130
- import { readFile as readFile7, readdir as readdir3, stat as stat3 } from "fs/promises";
48405
+ import { readFile as readFile8, readdir as readdir3, stat as stat4 } from "fs/promises";
48131
48406
  import path127 from "path";
48132
48407
  import { promisify as promisify3 } from "util";
48133
48408
  function isKeyFile(relativePath, language) {
@@ -48162,7 +48437,7 @@ async function collectCode(options) {
48162
48437
  const limited = filtered.slice(0, options.maxFiles ?? 200);
48163
48438
  const files = [];
48164
48439
  for (const filePath of limited) {
48165
- const content = await readFile7(filePath, "utf8");
48440
+ const content = await readFile8(filePath, "utf8");
48166
48441
  const relativePath = toPosix(path127.relative(root, filePath));
48167
48442
  const language = languageFor(filePath);
48168
48443
  files.push({
@@ -48196,7 +48471,7 @@ async function walk(directory, results, includeTests) {
48196
48471
  }
48197
48472
  if (entry.isDirectory()) {
48198
48473
  await walk(fullPath, results, includeTests);
48199
- } else if (entry.isFile() && isCodeFile(fullPath) && (await stat3(fullPath)).size < 256e3) {
48474
+ } else if (entry.isFile() && isCodeFile(fullPath) && (await stat4(fullPath)).size < 256e3) {
48200
48475
  results.push(fullPath);
48201
48476
  }
48202
48477
  }
@@ -49182,7 +49457,7 @@ var init_call_resolver = __esm({
49182
49457
  });
49183
49458
 
49184
49459
  // src/wiki-engine/code-knowledge/ast/import-resolver.ts
49185
- import { readFile as readFile8, stat as stat4 } from "fs/promises";
49460
+ import { readFile as readFile9, stat as stat5 } from "fs/promises";
49186
49461
  import path129 from "path";
49187
49462
  async function loadTsconfigPaths(repoRoot, fromRelativeFile) {
49188
49463
  const absDir = path129.dirname(path129.resolve(repoRoot, fromRelativeFile));
@@ -49194,7 +49469,7 @@ async function loadTsconfigPaths(repoRoot, fromRelativeFile) {
49194
49469
  }
49195
49470
  const configPath = path129.join(dir, "tsconfig.json");
49196
49471
  try {
49197
- const raw = JSON.parse(await readFile8(configPath, "utf8"));
49472
+ const raw = JSON.parse(await readFile9(configPath, "utf8"));
49198
49473
  const configDir = path129.dirname(configPath);
49199
49474
  const baseUrl = raw.compilerOptions?.baseUrl ?? ".";
49200
49475
  const pathsMap = raw.compilerOptions?.paths ?? {};
@@ -49345,7 +49620,7 @@ async function buildFileExistenceChecker(repoRoot, knownFiles) {
49345
49620
  if (knownFiles.has(relativePath)) return true;
49346
49621
  try {
49347
49622
  const abs = path129.resolve(repoRoot, relativePath);
49348
- const s = await stat4(abs);
49623
+ const s = await stat5(abs);
49349
49624
  return s.isFile();
49350
49625
  } catch {
49351
49626
  return false;
@@ -50107,10 +50382,10 @@ var init_ast = __esm({
50107
50382
  });
50108
50383
 
50109
50384
  // src/wiki-engine/code-knowledge/code-incremental.ts
50110
- import { readFile as readFile9, writeFile as writeFile5, stat as stat5, mkdir as mkdir3 } from "fs/promises";
50385
+ import { readFile as readFile10, writeFile as writeFile5, stat as stat6, mkdir as mkdir3 } from "fs/promises";
50111
50386
  import path131 from "path";
50112
50387
  async function detectCodeIncrementalChanges(root, manifestPath2, project) {
50113
- const previous = await exists(manifestPath2) ? JSON.parse(await readFile9(manifestPath2, "utf8")) : { files: [] };
50388
+ const previous = await exists(manifestPath2) ? JSON.parse(await readFile10(manifestPath2, "utf8")) : { files: [] };
50114
50389
  const oldSha = previous.headSha;
50115
50390
  const newSha = await gitCommit(root);
50116
50391
  if (oldSha && newSha && await isWorkingTreeClean(root)) {
@@ -50148,7 +50423,7 @@ function affectedPages(project, files) {
50148
50423
  }
50149
50424
  async function exists(filePath) {
50150
50425
  try {
50151
- await stat5(path131.resolve(filePath));
50426
+ await stat6(path131.resolve(filePath));
50152
50427
  return true;
50153
50428
  } catch {
50154
50429
  return false;
@@ -50157,7 +50432,7 @@ async function exists(filePath) {
50157
50432
  async function loadFactsCache(indicesDir) {
50158
50433
  const cachePath = path131.join(indicesDir, FACTS_CACHE_FILENAME);
50159
50434
  try {
50160
- const raw = await readFile9(cachePath, "utf-8");
50435
+ const raw = await readFile10(cachePath, "utf-8");
50161
50436
  const parsed = JSON.parse(raw);
50162
50437
  return Array.isArray(parsed) ? parsed : [];
50163
50438
  } catch {
@@ -50171,7 +50446,7 @@ async function saveFactsCache(indicesDir, facts) {
50171
50446
  async function loadInterfacesCache(indicesDir) {
50172
50447
  const cachePath = path131.join(indicesDir, INTERFACES_CACHE_FILENAME);
50173
50448
  try {
50174
- const raw = await readFile9(cachePath, "utf-8");
50449
+ const raw = await readFile10(cachePath, "utf-8");
50175
50450
  const parsed = JSON.parse(raw);
50176
50451
  return parsed?.entries ? parsed : { entries: [], scannedAt: "" };
50177
50452
  } catch {
@@ -50560,7 +50835,7 @@ var init_reconciler_v2_types = __esm({
50560
50835
  });
50561
50836
 
50562
50837
  // src/wiki-engine/knowledge-reconciler.ts
50563
- import { readFile as readFile10, readdir as readdir4, stat as stat6 } from "fs/promises";
50838
+ import { readFile as readFile11, readdir as readdir4, stat as stat7 } from "fs/promises";
50564
50839
  import path133 from "path";
50565
50840
  function isExpectedSelfLoop(edge) {
50566
50841
  return edge.from === edge.to && edge.relation === "IMPLEMENTS" && !!edge.source && REPAIRABLE_CODE_EDGE_SOURCES.has(edge.source);
@@ -50607,7 +50882,7 @@ async function loadReconciliationBase(wikiRoot) {
50607
50882
  }
50608
50883
  async function exists2(p) {
50609
50884
  try {
50610
- await stat6(p);
50885
+ await stat7(p);
50611
50886
  return true;
50612
50887
  } catch (error) {
50613
50888
  if (error.code === MISSING_PATH_ERROR_CODE) return false;
@@ -50623,7 +50898,7 @@ async function readPages(dirPath) {
50623
50898
  if (entry.isDirectory()) {
50624
50899
  pages.push(...await readPages(full));
50625
50900
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
50626
- const text = await readFile10(full, "utf8");
50901
+ const text = await readFile11(full, "utf8");
50627
50902
  const headingMatch = text.match(/^#\s+(.+)/m);
50628
50903
  const title = headingMatch ? headingMatch[1].trim() : entry.name.replace(/\.md$/, "");
50629
50904
  const updatedMatch = text.match(/updated[:\s]+(\d{4}-\d{2}-\d{2})/i);
@@ -51418,7 +51693,7 @@ __export(codebase_extract_exports, {
51418
51693
  extractCodebase: () => extractCodebase
51419
51694
  });
51420
51695
  import { statSync } from "fs";
51421
- import { mkdir as mkdir5, writeFile as writeFile7, readFile as readFile11, realpath as realpath3 } from "fs/promises";
51696
+ import { mkdir as mkdir5, writeFile as writeFile7, readFile as readFile12, realpath as realpath3 } from "fs/promises";
51422
51697
  import path135 from "path";
51423
51698
  import chalk4 from "chalk";
51424
51699
  function detectKnowledgeGaps(facts, graph, files) {
@@ -51907,7 +52182,7 @@ async function extractCodebase(opts) {
51907
52182
  if (changedFiles) {
51908
52183
  let reused = false;
51909
52184
  try {
51910
- const existing = await readFile11(depPathsFile, "utf-8");
52185
+ const existing = await readFile12(depPathsFile, "utf-8");
51911
52186
  if (existing.trim()) {
51912
52187
  reused = true;
51913
52188
  }
@@ -51925,7 +52200,7 @@ async function extractCodebase(opts) {
51925
52200
  await mkdir5(evidenceDir, { recursive: true });
51926
52201
  if (changedFiles && !pages.has("dependency-paths.md")) {
51927
52202
  try {
51928
- const existing = await readFile11(depPathsFile, "utf-8");
52203
+ const existing = await readFile12(depPathsFile, "utf-8");
51929
52204
  pages.set("dependency-paths.md", existing);
51930
52205
  } catch {
51931
52206
  }
@@ -52044,7 +52319,7 @@ async function extractCodebase(opts) {
52044
52319
  }));
52045
52320
  if (changedFiles !== void 0 && changedFiles.length > 0) {
52046
52321
  try {
52047
- const oldManifestRaw = await readFile11(manifestPath2, "utf-8");
52322
+ const oldManifestRaw = await readFile12(manifestPath2, "utf-8");
52048
52323
  const oldManifest = JSON.parse(oldManifestRaw);
52049
52324
  const changedSet = /* @__PURE__ */ new Set([...changedFiles, ...deletedFiles]);
52050
52325
  const unchanged = (oldManifest.files ?? []).filter((f) => !changedSet.has(f.relativePath)).map((f) => ({ relativePath: f.relativePath, sha256: f.sha256, language: f.language ?? "" }));
@@ -52059,7 +52334,7 @@ async function extractCodebase(opts) {
52059
52334
  let prevBranch;
52060
52335
  let prevIngestedMrs = [];
52061
52336
  try {
52062
- const prev = JSON.parse(await readFile11(manifestPath2, "utf-8"));
52337
+ const prev = JSON.parse(await readFile12(manifestPath2, "utf-8"));
52063
52338
  prevRepoUrl = prev.repoUrl;
52064
52339
  prevBranch = prev.branch;
52065
52340
  prevIngestedMrs = prev.ingestedMrs ?? [];
@@ -52396,11 +52671,11 @@ __export(deep_enrich_exports, {
52396
52671
  deepEnrich: () => deepEnrich,
52397
52672
  runHiddenDeepEnrich: () => runHiddenDeepEnrich
52398
52673
  });
52399
- import { readFile as readFile12, writeFile as writeFile8, readdir as readdir5, mkdir as mkdir6, unlink as unlink2 } from "fs/promises";
52674
+ import { readFile as readFile13, writeFile as writeFile8, readdir as readdir5, mkdir as mkdir6, unlink as unlink2 } from "fs/promises";
52400
52675
  import path137 from "path";
52401
52676
  async function readFileSafe2(filePath) {
52402
52677
  try {
52403
- return await readFile12(filePath, "utf-8");
52678
+ return await readFile13(filePath, "utf-8");
52404
52679
  } catch {
52405
52680
  return "";
52406
52681
  }
@@ -52446,7 +52721,7 @@ function isValidProgressState(v, project) {
52446
52721
  async function loadProgress(evidenceDir, project, allComponents) {
52447
52722
  const p = progressPath(evidenceDir);
52448
52723
  try {
52449
- const raw = await readFile12(p, "utf-8");
52724
+ const raw = await readFile13(p, "utf-8");
52450
52725
  const parsed = JSON.parse(raw);
52451
52726
  if (isValidProgressState(parsed, project)) return parsed;
52452
52727
  } catch {
@@ -53132,7 +53407,7 @@ var rebuild_wiki_index_exports = {};
53132
53407
  __export(rebuild_wiki_index_exports, {
53133
53408
  rebuildWikiIndex: () => rebuildWikiIndex
53134
53409
  });
53135
- import { readFile as readFile13, readdir as readdir7, stat as stat7, writeFile as writeFile9 } from "fs/promises";
53410
+ import { readFile as readFile14, readdir as readdir7, stat as stat8, writeFile as writeFile9 } from "fs/promises";
53136
53411
  import path139 from "path";
53137
53412
  async function rebuildWikiIndex(teamwikiRoot) {
53138
53413
  const evidenceCodeDir = path139.join(teamwikiRoot, "evidence", "code");
@@ -53144,7 +53419,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53144
53419
  const dirs = await readdir7(evidenceCodeDir);
53145
53420
  for (const dir of dirs) {
53146
53421
  const dirPath = path139.join(evidenceCodeDir, dir);
53147
- const dirStat = await stat7(dirPath).catch(() => null);
53422
+ const dirStat = await stat8(dirPath).catch(() => null);
53148
53423
  if (!dirStat?.isDirectory()) continue;
53149
53424
  const info = {
53150
53425
  slug: dir,
@@ -53158,7 +53433,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53158
53433
  };
53159
53434
  const overviewPath = path139.join(dirPath, "overview.md");
53160
53435
  if (await pathExists(overviewPath)) {
53161
- const content = await readFile13(overviewPath, "utf-8");
53436
+ const content = await readFile14(overviewPath, "utf-8");
53162
53437
  const bodyStart = content.indexOf("\n\n", content.indexOf("---", 3));
53163
53438
  if (bodyStart > 0) {
53164
53439
  const body = content.slice(bodyStart).trim();
@@ -53171,7 +53446,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53171
53446
  }
53172
53447
  const projectIndex = path139.join(dirPath, "index.md");
53173
53448
  if (await pathExists(projectIndex)) {
53174
- const content = await readFile13(projectIndex, "utf-8");
53449
+ const content = await readFile14(projectIndex, "utf-8");
53175
53450
  const factsMatch = content.match(/Facts:\s*(\d+)/);
53176
53451
  if (factsMatch) info.facts = parseInt(factsMatch[1], 10);
53177
53452
  const ifMatches = content.matchAll(/\|\s*(HTTP|MQ|RPC)\s*\|\s*(\d+)\s*\|/g);
@@ -53182,7 +53457,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53182
53457
  const manifestPath2 = path139.join(dirPath, "_manifest.json");
53183
53458
  if (await pathExists(manifestPath2)) {
53184
53459
  try {
53185
- const raw = await readFile13(manifestPath2, "utf-8");
53460
+ const raw = await readFile14(manifestPath2, "utf-8");
53186
53461
  const manifest = JSON.parse(raw);
53187
53462
  for (const comp of manifest.components) {
53188
53463
  if (comp.responsibilities) info.responsibilities.push(...comp.responsibilities);
@@ -53194,7 +53469,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53194
53469
  const domainsPath = path139.join(dirPath, "_domains.json");
53195
53470
  if (await pathExists(domainsPath)) {
53196
53471
  try {
53197
- const raw = await readFile13(domainsPath, "utf-8");
53472
+ const raw = await readFile14(domainsPath, "utf-8");
53198
53473
  const domainMeta = JSON.parse(raw);
53199
53474
  if (domainMeta.domain) {
53200
53475
  info.domain = domainMeta.domain;
@@ -53210,7 +53485,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53210
53485
  }
53211
53486
  const chainsPath = path139.join(dirPath, "dependency-paths.md");
53212
53487
  if (await pathExists(chainsPath)) {
53213
- const content = await readFile13(chainsPath, "utf-8");
53488
+ const content = await readFile14(chainsPath, "utf-8");
53214
53489
  const chainMatch = content.match(/(\d+)\s*call chain/);
53215
53490
  if (chainMatch) info.callChains = parseInt(chainMatch[1], 10);
53216
53491
  }
@@ -53227,7 +53502,7 @@ async function rebuildWikiIndex(teamwikiRoot) {
53227
53502
  const graphPath = path139.join(teamwikiRoot, ".indices", "graph-index.json");
53228
53503
  if (await pathExists(graphPath)) {
53229
53504
  try {
53230
- const raw = await readFile13(graphPath, "utf-8");
53505
+ const raw = await readFile14(graphPath, "utf-8");
53231
53506
  const graph = JSON.parse(raw);
53232
53507
  totalNodes = Array.isArray(graph.nodes) ? graph.nodes.length : 0;
53233
53508
  totalEdges = Array.isArray(graph.edges) ? graph.edges.length : 0;
@@ -53777,8 +54052,8 @@ async function loadRepoList(filePath) {
53777
54052
  if (!exists3) {
53778
54053
  throw new Error(`Repo list not found: ${filePath}`);
53779
54054
  }
53780
- const stat9 = await fs59.stat(filePath);
53781
- if (stat9.size > MAX_CONFIG_FILE_BYTES2) {
54055
+ const stat10 = await fs59.stat(filePath);
54056
+ if (stat10.size > MAX_CONFIG_FILE_BYTES2) {
53782
54057
  throw new Error(`${filePath} exceeds max allowed size 10MB`);
53783
54058
  }
53784
54059
  const raw = await fs59.readFile(filePath, "utf8");
@@ -53866,7 +54141,7 @@ async function importFromRepoList(opts) {
53866
54141
  failed.push({ url: entry.url, error: message });
53867
54142
  }
53868
54143
  }
53869
- const inFlight = [];
54144
+ const inFlight = /* @__PURE__ */ new Set();
53870
54145
  for (const entry of queue) {
53871
54146
  while (semaphore.running >= concurrency) {
53872
54147
  await Promise.race(inFlight);
@@ -53874,10 +54149,9 @@ async function importFromRepoList(opts) {
53874
54149
  semaphore.running++;
53875
54150
  const task = processEntry(entry).finally(() => {
53876
54151
  semaphore.running--;
53877
- const idx = inFlight.indexOf(task);
53878
- if (idx !== -1) inFlight.splice(idx, 1);
54152
+ inFlight.delete(task);
53879
54153
  });
53880
- inFlight.push(task);
54154
+ inFlight.add(task);
53881
54155
  }
53882
54156
  await Promise.all(inFlight);
53883
54157
  if (!dryRun && succeeded.length > 0) {
@@ -54141,8 +54415,8 @@ async function loadPendingReview(cwd) {
54141
54415
  if (!await fs62.pathExists(filePath)) {
54142
54416
  return [];
54143
54417
  }
54144
- const stat9 = await fs62.stat(filePath);
54145
- if (stat9.size > MAX_CONFIG_FILE_BYTES3) {
54418
+ const stat10 = await fs62.stat(filePath);
54419
+ if (stat10.size > MAX_CONFIG_FILE_BYTES3) {
54146
54420
  throw new Error(`${filePath} exceeds max allowed size 10MB`);
54147
54421
  }
54148
54422
  const text = await fs62.readFile(filePath, "utf8");
@@ -54853,7 +55127,7 @@ var codebase_upgrade_wiki_exports = {};
54853
55127
  __export(codebase_upgrade_wiki_exports, {
54854
55128
  upgradeCodebaseWiki: () => upgradeCodebaseWiki
54855
55129
  });
54856
- import { readdir as readdir8, readFile as readFile14 } from "fs/promises";
55130
+ import { readdir as readdir8, readFile as readFile15 } from "fs/promises";
54857
55131
  import path147 from "path";
54858
55132
  import chalk6 from "chalk";
54859
55133
  import matter13 from "gray-matter";
@@ -54885,7 +55159,7 @@ async function upgradeCodebaseWiki(opts) {
54885
55159
  const slug = file.replace(".md", "");
54886
55160
  const filePath = path147.join(teamCodebaseDir, file);
54887
55161
  try {
54888
- const content = await readFile14(filePath, "utf-8");
55162
+ const content = await readFile15(filePath, "utf-8");
54889
55163
  const parsed = matter13(content);
54890
55164
  const source = parsed.data["source"] ?? parsed.data["repo_url"];
54891
55165
  if (!source) {
@@ -54952,7 +55226,7 @@ __export(codebase_wiki_lint_exports, {
54952
55226
  formatWikiLintReport: () => formatWikiLintReport,
54953
55227
  lintTeamwiki: () => lintTeamwiki
54954
55228
  });
54955
- import { readFile as readFile15, readdir as readdir9, stat as stat8 } from "fs/promises";
55229
+ import { readFile as readFile16, readdir as readdir9, stat as stat9 } from "fs/promises";
54956
55230
  import path148 from "path";
54957
55231
  import chalk7 from "chalk";
54958
55232
  async function lintTeamwiki(opts) {
@@ -54977,7 +55251,7 @@ async function lintTeamwiki(opts) {
54977
55251
  });
54978
55252
  } else {
54979
55253
  try {
54980
- const raw = await readFile15(graphPath, "utf-8");
55254
+ const raw = await readFile16(graphPath, "utf-8");
54981
55255
  graph = JSON.parse(raw);
54982
55256
  } catch {
54983
55257
  addIssue({
@@ -55008,7 +55282,7 @@ async function lintTeamwiki(opts) {
55008
55282
  }
55009
55283
  for (const project of projects) {
55010
55284
  const projectDir = path148.join(evidenceDir, project);
55011
- const pStat = await stat8(projectDir).catch(() => null);
55285
+ const pStat = await stat9(projectDir).catch(() => null);
55012
55286
  if (!pStat?.isDirectory()) {
55013
55287
  if (!pStat) {
55014
55288
  addIssue({ severity: "low", category: "stat-failed", location: `evidence/code/${project}`, message: "\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\u72B6\u6001" });
@@ -55046,7 +55320,7 @@ async function lintTeamwiki(opts) {
55046
55320
  });
55047
55321
  } else {
55048
55322
  try {
55049
- const raw = await readFile15(manifestPath2, "utf-8");
55323
+ const raw = await readFile16(manifestPath2, "utf-8");
55050
55324
  const manifest = JSON.parse(raw);
55051
55325
  if (manifest.lastScan) {
55052
55326
  const daysSince = (Date.now() - new Date(manifest.lastScan).getTime()) / (1e3 * 60 * 60 * 24);
@@ -55152,7 +55426,7 @@ __export(codebase_cmd_exports, {
55152
55426
  codebaseCmd: () => codebaseCmd
55153
55427
  });
55154
55428
  import path149 from "path";
55155
- import { readFile as readFile16 } from "fs/promises";
55429
+ import { readFile as readFile17 } from "fs/promises";
55156
55430
  import chalk8 from "chalk";
55157
55431
  async function codebaseCmd(opts) {
55158
55432
  const cwd = process.cwd();
@@ -55233,7 +55507,7 @@ async function codebaseCmd(opts) {
55233
55507
  }
55234
55508
  let componentCount = 0;
55235
55509
  try {
55236
- const manifest = JSON.parse(await readFile16(path149.join(evidenceDir, "_manifest.json"), "utf-8"));
55510
+ const manifest = JSON.parse(await readFile17(path149.join(evidenceDir, "_manifest.json"), "utf-8"));
55237
55511
  componentCount = Array.isArray(manifest.components) ? manifest.components.length : 0;
55238
55512
  } catch {
55239
55513
  componentCount = 0;
@@ -55313,7 +55587,7 @@ async function printCodebaseStatus(opts) {
55313
55587
  const manifestPath2 = path149.join(teamwikiDir, "source-manifest.json");
55314
55588
  let manifest;
55315
55589
  try {
55316
- manifest = JSON.parse(await readFile16(manifestPath2, "utf-8"));
55590
+ manifest = JSON.parse(await readFile17(manifestPath2, "utf-8"));
55317
55591
  } catch {
55318
55592
  if (opts.json) {
55319
55593
  console.log(JSON.stringify({ error: "no-manifest", manifestPath: manifestPath2 }));
@@ -56914,7 +57188,7 @@ program.command("stats").description("Show local skill usage statistics").option
56914
57188
  await showStats2({ byRepo: cmdOpts.byRepo, byTime: cmdOpts.byTime });
56915
57189
  });
56916
57190
  var sessionCmd = program.command("session").description("Record and inspect coding-session summaries");
56917
- sessionCmd.command("save").description("Record a privacy-scrubbed summary of a coding session to a local monthly log").option("--session-id <id>", "Session to record (default: most recent, or $CLAUDE_SESSION_ID)").option("--push", "Also push the summary to the team repo (feeds `teamai digest`)").option("--force", "Push even if the session is not flagged as valuable").option("--include-prompt", "Include the redacted first-prompt line in the pushed summary (default: off)").option("--scope <scope>", "Config scope for --push: user | project (default: auto-detect)").action(async (cmdOpts) => {
57191
+ sessionCmd.command("save").description("Record a privacy-scrubbed summary of a coding session to a local monthly log").option("--session-id <id>", "Session to record (default: the agent's session, e.g. $CLAUDE_CODE_SESSION_ID, or the most recent)").option("--push", "Also push the summary to the team repo (feeds `teamai digest`)").option("--force", "Push even if the session is not flagged as valuable").option("--include-prompt", "Include the redacted first-prompt line in the pushed summary (default: off)").option("--scope <scope>", "Config scope for --push: user | project (default: auto-detect)").action(async (cmdOpts) => {
56918
57192
  const globalOpts = program.opts();
56919
57193
  const { saveSession: saveSession2 } = await Promise.resolve().then(() => (init_save_session(), save_session_exports));
56920
57194
  await saveSession2({ ...globalOpts, ...cmdOpts });