@dosu/cli 0.62.1 → 0.63.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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/bin/dosu.js +228 -117
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -182,7 +182,8 @@ dosu knowledge incognito enable|disable [claude|cursor|codex] # the /dosu-incog
182
182
  ```
183
183
 
184
184
  The status line shows one of `📚 Dosu studying…`, `👻 Dosu incognito`, `⚪ Dosu paused`,
185
- `⚪ Dosu not studying this folder`, or `⚪ Dosu off`. Neither setup nor `enable` replaces a status
185
+ `⚪ Dosu not studying this repo`, or `⚪ Dosu off`. Only sessions inside a git repo are studied;
186
+ pick which repos under `dosu` → settings → study scope. Neither setup nor `enable` replaces a status
186
187
  line you already have; they print the one-liner to add to your own script instead.
187
188
 
188
189
  Running `/dosu-incognito` inside a session marks that session's transcript so studying skips it
package/bin/dosu.js CHANGED
@@ -1083,7 +1083,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
1083
1083
  function getVersionString() {
1084
1084
  return `v${VERSION}`;
1085
1085
  }
1086
- var VERSION = "0.62.1", INSTALL_CHANNEL = "npm";
1086
+ var VERSION = "0.63.0", INSTALL_CHANNEL = "npm";
1087
1087
 
1088
1088
  // src/debug/logger.ts
1089
1089
  import {
@@ -1472,6 +1472,59 @@ var init_agents = __esm(() => {
1472
1472
  init_formats();
1473
1473
  });
1474
1474
 
1475
+ // src/sessions/repo.ts
1476
+ import { execFileSync } from "node:child_process";
1477
+ function normalizeRepoRemote(remote) {
1478
+ const value = remote.trim();
1479
+ if (!value)
1480
+ return null;
1481
+ let host;
1482
+ let path;
1483
+ const scp = value.includes("://") ? null : /^(?:[^@/]+@)?([^:/]+):(.+)$/.exec(value);
1484
+ if (scp) {
1485
+ host = scp[1];
1486
+ path = scp[2];
1487
+ } else {
1488
+ try {
1489
+ const url = new URL(value);
1490
+ if (url.protocol === "file:")
1491
+ return null;
1492
+ host = url.hostname;
1493
+ path = url.pathname;
1494
+ } catch {
1495
+ return null;
1496
+ }
1497
+ }
1498
+ path = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
1499
+ if (!host || !path.includes("/"))
1500
+ return null;
1501
+ const lowerHost = host.toLowerCase();
1502
+ return `${lowerHost}/${lowerHost === "github.com" ? path.toLowerCase() : path}`;
1503
+ }
1504
+ function originRepoOfDir(dir) {
1505
+ const env = { ...process.env };
1506
+ for (const name of GIT_LOCATION_ENV)
1507
+ delete env[name];
1508
+ try {
1509
+ const out = execFileSync("git", ["-C", dir, "remote", "get-url", "origin"], {
1510
+ encoding: "utf-8",
1511
+ env,
1512
+ stdio: ["ignore", "pipe", "ignore"],
1513
+ timeout: 1000
1514
+ });
1515
+ return normalizeRepoRemote(out);
1516
+ } catch {
1517
+ return null;
1518
+ }
1519
+ }
1520
+ function displayRepo(repo) {
1521
+ return repo.split("/").slice(1).join("/") || repo;
1522
+ }
1523
+ var GIT_LOCATION_ENV;
1524
+ var init_repo = __esm(() => {
1525
+ GIT_LOCATION_ENV = ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"];
1526
+ });
1527
+
1475
1528
  // src/sessions/scan.ts
1476
1529
  import { existsSync as existsSync6, readdirSync, statSync as statSync2 } from "node:fs";
1477
1530
  import { createRequire as createRequire2 } from "node:module";
@@ -1919,6 +1972,9 @@ import { join as join6 } from "node:path";
1919
1972
  function syncStatePath(configDir = getConfigDir()) {
1920
1973
  return join6(configDir, STATE_FILENAME);
1921
1974
  }
1975
+ function stringsOf(values) {
1976
+ return values.filter((v) => typeof v === "string");
1977
+ }
1922
1978
  function loadSyncState(configDir = getConfigDir()) {
1923
1979
  const empty = {
1924
1980
  schema_version: STATE_SCHEMA_VERSION,
@@ -1954,9 +2010,8 @@ function loadSyncState(configDir = getConfigDir()) {
1954
2010
  ...lastRefusal ? { last_refusal: lastRefusal } : {},
1955
2011
  ...run ? { run } : {},
1956
2012
  ...raw.paused === true ? { paused: true } : {},
1957
- ...Array.isArray(raw.project_filter) ? {
1958
- project_filter: raw.project_filter.filter((p) => typeof p === "string")
1959
- } : {}
2013
+ ...Array.isArray(raw.repo_filter) ? { repo_filter: stringsOf(raw.repo_filter) } : {},
2014
+ ...Array.isArray(raw.project_filter) ? { project_filter: stringsOf(raw.project_filter) } : {}
1960
2015
  };
1961
2016
  } catch {
1962
2017
  return empty;
@@ -1976,6 +2031,7 @@ function resetSyncState(configDir = getConfigDir()) {
1976
2031
  schema_version: STATE_SCHEMA_VERSION,
1977
2032
  watermark: null,
1978
2033
  consecutive_failures: 0,
2034
+ ...previous.repo_filter ? { repo_filter: previous.repo_filter } : {},
1979
2035
  ...previous.project_filter ? { project_filter: previous.project_filter } : {},
1980
2036
  ...previous.paused ? { paused: true } : {}
1981
2037
  };
@@ -2002,17 +2058,31 @@ function isUnderDir(dir, base) {
2002
2058
  const root = base.endsWith("/") ? base.slice(0, -1) : base;
2003
2059
  return dir === root || dir.startsWith(`${root}/`);
2004
2060
  }
2005
- function filterSessionsByProject(sessions, filter, resolveDir) {
2006
- if (!filter || filter.length === 0)
2007
- return [...sessions];
2008
- const includeUnknown = filter.includes(UNKNOWN_PROJECT);
2009
- const dirs = filter.filter((entry) => entry !== UNKNOWN_PROJECT);
2010
- return sessions.filter((session) => {
2011
- const dir = resolveDir(session);
2012
- if (!dir)
2013
- return includeUnknown;
2014
- return dirs.some((base) => isUnderDir(dir, base));
2015
- });
2061
+ function studyRepoFilter(state, listSessions, locator) {
2062
+ if (state.repo_filter)
2063
+ return state.repo_filter;
2064
+ const folders = state.project_filter;
2065
+ if (!folders?.length)
2066
+ return null;
2067
+ const repos = new Set;
2068
+ for (const session of listSessions()) {
2069
+ const dir = locator.resolve(session);
2070
+ if (!dir || !folders.some((base) => isUnderDir(dir, base)))
2071
+ continue;
2072
+ const repo = locator.resolveRepo(session);
2073
+ if (repo)
2074
+ repos.add(repo);
2075
+ }
2076
+ return [...repos].sort();
2077
+ }
2078
+ function filterSessionsByRepo(sessions, filter, resolveRepo) {
2079
+ const kept = [];
2080
+ for (const session of sessions) {
2081
+ const repo = resolveRepo(session);
2082
+ if (repo !== null && (filter === null || filter.includes(repo)))
2083
+ kept.push({ ...session, repo });
2084
+ }
2085
+ return kept;
2016
2086
  }
2017
2087
  function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
2018
2088
  const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
@@ -2031,7 +2101,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
2031
2101
  }
2032
2102
  return { ready, open };
2033
2103
  }
2034
- var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
2104
+ var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500;
2035
2105
  var init_watermark = __esm(() => {
2036
2106
  init_config();
2037
2107
  DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
@@ -2067,12 +2137,18 @@ function defaultHookEnabled(agentId) {
2067
2137
  return false;
2068
2138
  }
2069
2139
  }
2070
- function cwdIsStudied(cwd, filter) {
2071
- if (!filter || filter.length === 0)
2072
- return true;
2140
+ function cwdIsStudied(cwd, state, repoOfDir) {
2073
2141
  if (!cwd)
2074
- return filter.includes(UNKNOWN_PROJECT);
2075
- return filter.some((base) => base !== UNKNOWN_PROJECT && isUnderDir(cwd, base));
2142
+ return false;
2143
+ const repo = repoOfDir(cwd);
2144
+ if (!repo)
2145
+ return false;
2146
+ if (state.repo_filter)
2147
+ return state.repo_filter.includes(repo);
2148
+ if (state.project_filter?.length) {
2149
+ return state.project_filter.some((base) => isUnderDir(cwd, base));
2150
+ }
2151
+ return true;
2076
2152
  }
2077
2153
  function resolveStatuslineState(payload, agentId, deps = {}) {
2078
2154
  const hookEnabled = deps.hookEnabled ?? defaultHookEnabled;
@@ -2085,7 +2161,7 @@ function resolveStatuslineState(payload, agentId, deps = {}) {
2085
2161
  const state = (deps.loadState ?? loadSyncState)();
2086
2162
  if (state.paused)
2087
2163
  return "paused";
2088
- if (!cwdIsStudied(payload.cwd, state.project_filter))
2164
+ if (!cwdIsStudied(payload.cwd, state, deps.repoOfDir ?? originRepoOfDir))
2089
2165
  return "not-studied";
2090
2166
  return "on";
2091
2167
  }
@@ -2099,13 +2175,14 @@ function renderStatusline(raw, agentId, deps = {}) {
2099
2175
  var STATUSLINE_LABELS;
2100
2176
  var init_render = __esm(() => {
2101
2177
  init_agents();
2178
+ init_repo();
2102
2179
  init_incognito();
2103
2180
  init_watermark();
2104
2181
  STATUSLINE_LABELS = {
2105
2182
  on: "\uD83D\uDCDA Dosu studying…",
2106
2183
  incognito: "\uD83D\uDC7B Dosu incognito",
2107
2184
  paused: "⚪ Dosu paused",
2108
- "not-studied": "⚪ Dosu not studying this folder",
2185
+ "not-studied": "⚪ Dosu not studying this repo",
2109
2186
  off: "⚪ Dosu off"
2110
2187
  };
2111
2188
  });
@@ -9863,7 +9940,7 @@ var init_default_browser_id = __esm(() => {
9863
9940
  // node_modules/run-applescript/index.js
9864
9941
  import process6 from "node:process";
9865
9942
  import { promisify as promisify4 } from "node:util";
9866
- import { execFile as execFile4, execFileSync } from "node:child_process";
9943
+ import { execFile as execFile4, execFileSync as execFileSync2 } from "node:child_process";
9867
9944
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
9868
9945
  if (process6.platform !== "darwin") {
9869
9946
  throw new Error("macOS only");
@@ -12000,20 +12077,39 @@ function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
12000
12077
  return null;
12001
12078
  }
12002
12079
  };
12080
+ const repoOfDir = deps.repoOfDir ?? originRepoOfDir;
12081
+ const repoByDir = new Map;
12082
+ const resolve = (session) => {
12083
+ const key = `${session.harness}/${session.id}`;
12084
+ const cached = entries[key];
12085
+ if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
12086
+ return cached.dir;
12087
+ }
12088
+ const dir = compute(session);
12089
+ entries[key] = { dir, mtime: mtime(session.path) };
12090
+ dirty = true;
12091
+ return dir;
12092
+ };
12003
12093
  return {
12004
12094
  cached(key) {
12005
12095
  return entries[key]?.dir ?? null;
12006
12096
  },
12007
- resolve(session) {
12008
- const key = `${session.harness}/${session.id}`;
12009
- const cached = entries[key];
12010
- if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
12011
- return cached.dir;
12012
- }
12013
- const dir = compute(session);
12014
- entries[key] = { dir, mtime: mtime(session.path) };
12097
+ resolve,
12098
+ resolveRepo(session) {
12099
+ const dir = resolve(session);
12100
+ if (dir === null)
12101
+ return null;
12102
+ const entry = entries[`${session.harness}/${session.id}`];
12103
+ if (entry.repo !== undefined && (entry.repo !== null || entry.mtime === mtime(session.path))) {
12104
+ return entry.repo;
12105
+ }
12106
+ if (!repoByDir.has(dir))
12107
+ repoByDir.set(dir, repoOfDir(dir));
12108
+ const repo = repoByDir.get(dir) ?? null;
12109
+ entry.repo = repo;
12110
+ entry.mtime = mtime(session.path);
12015
12111
  dirty = true;
12016
- return dir;
12112
+ return repo;
12017
12113
  },
12018
12114
  flush() {
12019
12115
  if (!dirty)
@@ -12026,6 +12122,7 @@ function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
12026
12122
  var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
12027
12123
  var init_project_dir = __esm(() => {
12028
12124
  init_config();
12125
+ init_repo();
12029
12126
  HEAD_BYTES = 128 * 1024;
12030
12127
  });
12031
12128
 
@@ -13309,12 +13406,11 @@ var init_generate = __esm(() => {
13309
13406
  function listSessionBacklog() {
13310
13407
  try {
13311
13408
  const state = loadSyncState();
13312
- let sessions = scanAgentSessions({});
13313
- if (state.project_filter?.length) {
13314
- const resolver = createProjectDirResolver();
13315
- sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
13316
- resolver.flush();
13317
- }
13409
+ const scanned = scanAgentSessions({});
13410
+ const resolver = createProjectDirResolver();
13411
+ const filter = studyRepoFilter(state, () => scanned, resolver);
13412
+ const sessions = filterSessionsByRepo(scanned, filter, resolver.resolveRepo);
13413
+ resolver.flush();
13318
13414
  const gate = gateSessions(sessions, state.watermark);
13319
13415
  const { kept, skipped } = partitionIncognitoSessions(gate.ready);
13320
13416
  return { queued: kept.reverse(), open: gate.open.reverse(), incognito: skipped.reverse() };
@@ -13540,19 +13636,24 @@ async function runKnowledgeSync(options = {}) {
13540
13636
  since: new Date(now().getTime() - GATE_WINDOW_DAYS * 24 * 60 * 60 * 1000),
13541
13637
  limit: GATE_WINDOW
13542
13638
  }));
13543
- let sessions = await listSessions();
13544
- if (state.project_filter?.length) {
13545
- let resolve = deps.resolveProjectDir;
13546
- let flush;
13547
- if (!resolve) {
13548
- const resolver = createProjectDirResolver();
13549
- resolve = resolver.resolve;
13550
- flush = resolver.flush;
13551
- }
13552
- sessions = filterSessionsByProject(sessions, state.project_filter, resolve);
13553
- flush?.();
13554
- logger.debug("sync", `project filter active: ${state.project_filter.join(", ")}`);
13555
- }
13639
+ const scanned = await listSessions();
13640
+ let flush;
13641
+ let locator = deps.locator;
13642
+ if (!locator) {
13643
+ const resolver = createProjectDirResolver();
13644
+ locator = resolver;
13645
+ flush = resolver.flush;
13646
+ }
13647
+ const repoFilter = studyRepoFilter(state, () => scanAgentSessions({}), locator);
13648
+ if (state.project_filter) {
13649
+ delete state.project_filter;
13650
+ if (repoFilter)
13651
+ state.repo_filter = repoFilter;
13652
+ logger.debug("sync", `folder scope converted to repos: ${repoFilter?.join(", ") || "none"}`);
13653
+ }
13654
+ const sessions = filterSessionsByRepo(scanned, repoFilter, (s) => locator.resolveRepo(s));
13655
+ flush?.();
13656
+ logger.debug("sync", `study scope ${repoFilter ? repoFilter.join(", ") || "none" : "all repos"}: ${sessions.length} of ${scanned.length} sessions in scope`);
13556
13657
  ({ ready, open } = gateSessions(sessions, state.watermark, now()));
13557
13658
  logGateResult(ready, open.length, state.watermark);
13558
13659
  } catch (err) {
@@ -13800,7 +13901,7 @@ var init_conflicts = __esm(() => {
13800
13901
  });
13801
13902
 
13802
13903
  // src/telemetry/debug-id.ts
13803
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "097dc848-8855-154b-6475-6e2164756e21";
13904
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "01f74cf6-cdbb-b296-6475-6e2164756e21";
13804
13905
 
13805
13906
  // src/telemetry/telemetry.ts
13806
13907
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -15477,9 +15578,8 @@ note cap are the volume guards.
15477
15578
  5. Title is a noun-phrase topic (like "page_version UniqueViolation race"), not a sentence.
15478
15579
  Content is a self-contained observation in plain language; include file/path pointers when
15479
15580
  useful.
15480
- 6. Only pass repo/branch to write_knowledge when the session itself verifies them (an explicit
15481
- cwd, git remote, or branch mentioned in the transcript). Never infer or guess a repo. When not
15482
- verified, omit both.
15581
+ 6. Never pass repo or branch to write_knowledge; the client attaches each note's repo from the
15582
+ session it came from.
15483
15583
  7. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
15484
15584
  long verbatim transcript spans. Summarize in your own words.
15485
15585
  8. A trivial session with no real user query is normal: skip it silently.`;
@@ -58494,6 +58594,7 @@ async function runLearner(options) {
58494
58594
  let currentSession;
58495
58595
  const readsSinceWrite = new Set;
58496
58596
  const sessionKeys = new Map(options.sessions.map((s) => [s.id, `${s.harness}/${s.id}`]));
58597
+ const sessionRepos = new Map(options.sessions.map((s) => [s.id, s.repo]));
58497
58598
  const notedSessions = new Set;
58498
58599
  const pendingNotes = new Map;
58499
58600
  let turns = 0;
@@ -58577,10 +58678,20 @@ async function runLearner(options) {
58577
58678
  const noted = currentSession && sessionKeys.get(currentSession);
58578
58679
  if (noted)
58579
58680
  pendingNotes.set(toolUseID, noted);
58580
- const { transcript_id: _authoredByModel, ...clean } = input;
58681
+ const {
58682
+ transcript_id: _transcriptByModel,
58683
+ repo: _repoByModel,
58684
+ branch: _branchByModel,
58685
+ ...clean
58686
+ } = input;
58687
+ const repo = currentSession && sessionRepos.get(currentSession);
58581
58688
  return {
58582
58689
  behavior: "allow",
58583
- updatedInput: currentSession ? { ...clean, transcript_id: currentSession } : clean
58690
+ updatedInput: {
58691
+ ...clean,
58692
+ ...currentSession ? { transcript_id: currentSession } : {},
58693
+ ...repo ? { repo } : {}
58694
+ }
58584
58695
  };
58585
58696
  }
58586
58697
  return { behavior: "allow", updatedInput: input };
@@ -58670,7 +58781,6 @@ var init_runner = __esm(() => {
58670
58781
 
58671
58782
  // src/commands/knowledge.ts
58672
58783
  import { existsSync as existsSync18 } from "node:fs";
58673
- import { homedir as homedir7 } from "node:os";
58674
58784
  import { delimiter as delimiter4, join as join21 } from "node:path";
58675
58785
  function requireConfig7() {
58676
58786
  const cfg = requireLoginConfig();
@@ -58938,10 +59048,11 @@ function printSyncStatus(status, now = new Date) {
58938
59048
  }
58939
59049
  const wm = status.state.watermark;
58940
59050
  console.log(` Studied through: ${wm ? `${wm} (${formatAge(wm, now)})` : "nothing studied yet"}`);
58941
- if (status.state.project_filter?.length) {
58942
- const home = homedir7();
58943
- const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
58944
- console.log(` Study scope: ${scope}`);
59051
+ const repoFilter = status.state.repo_filter;
59052
+ if (repoFilter) {
59053
+ console.log(` Study scope: ${repoFilter.length ? repoFilter.map(displayRepo).join(", ") : "no repos"}`);
59054
+ } else if (status.state.project_filter?.length) {
59055
+ console.log(` Study scope: ${status.state.project_filter.length} folders (converted to repos on the next sync)`);
58945
59056
  }
58946
59057
  if ((status.state.total_notes ?? 0) > 0) {
58947
59058
  const tokens = status.state.total_learning_tokens ?? 0;
@@ -59104,6 +59215,7 @@ var init_knowledge = __esm(() => {
59104
59215
  init_agents();
59105
59216
  init_formats();
59106
59217
  init_generate();
59218
+ init_repo();
59107
59219
  init_backlog();
59108
59220
  init_detach();
59109
59221
  init_status();
@@ -60674,7 +60786,7 @@ import {
60674
60786
  symlinkSync,
60675
60787
  writeFileSync as writeFileSync12
60676
60788
  } from "node:fs";
60677
- import { homedir as homedir8 } from "node:os";
60789
+ import { homedir as homedir7 } from "node:os";
60678
60790
  import { dirname as dirname10, join as join22, relative as relative3 } from "node:path";
60679
60791
  function skillAgentIDsForProviders(providerIDs) {
60680
60792
  return [
@@ -60685,15 +60797,15 @@ function isSkillAgentID(agent) {
60685
60797
  return SUPPORTED_SKILL_AGENTS.includes(agent);
60686
60798
  }
60687
60799
  function universalSkillsDir() {
60688
- return join22(homedir8(), ".agents", "skills");
60800
+ return join22(homedir7(), ".agents", "skills");
60689
60801
  }
60690
60802
  function linkedSkillsDir(agent) {
60691
60803
  if (agent === "claude-code") {
60692
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join22(homedir8(), ".claude");
60804
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join22(homedir7(), ".claude");
60693
60805
  return join22(claudeConfigDir, "skills");
60694
60806
  }
60695
60807
  if (agent === "windsurf")
60696
- return join22(homedir8(), ".codeium", "windsurf", "skills");
60808
+ return join22(homedir7(), ".codeium", "windsurf", "skills");
60697
60809
  return null;
60698
60810
  }
60699
60811
  function skillInstallTargetForProvider(providerID) {
@@ -60736,7 +60848,7 @@ function clearSkillInstallState() {
60736
60848
  rmSync5(installStatePath(), { force: true });
60737
60849
  }
60738
60850
  function pruneLegacySkillLock() {
60739
- const lockPath = join22(homedir8(), ".agents", ".skill-lock.json");
60851
+ const lockPath = join22(homedir7(), ".agents", ".skill-lock.json");
60740
60852
  try {
60741
60853
  if (!existsSync19(lockPath))
60742
60854
  return [];
@@ -62171,15 +62283,15 @@ var init_vscode = __esm(() => {
62171
62283
  });
62172
62284
 
62173
62285
  // src/mcp/providers/windsurf.ts
62174
- import { homedir as homedir9 } from "node:os";
62286
+ import { homedir as homedir8 } from "node:os";
62175
62287
  import { join as join36 } from "node:path";
62176
62288
  var WindsurfProvider = () => createJSONProvider({
62177
62289
  providerName: "Windsurf",
62178
62290
  providerID: "windsurf",
62179
62291
  local: false,
62180
62292
  priorityValue: 9,
62181
- paths: [join36(homedir9(), ".codeium", "windsurf")],
62182
- globalPath: join36(homedir9(), ".codeium", "windsurf", "mcp_config.json"),
62293
+ paths: [join36(homedir8(), ".codeium", "windsurf")],
62294
+ globalPath: join36(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
62183
62295
  topKey: "mcpServers"
62184
62296
  });
62185
62297
  var init_windsurf = __esm(() => {
@@ -62188,14 +62300,14 @@ var init_windsurf = __esm(() => {
62188
62300
 
62189
62301
  // src/mcp/providers/zed.ts
62190
62302
  import { rmSync as rmSync6 } from "node:fs";
62191
- import { homedir as homedir10, platform as platform3 } from "node:os";
62303
+ import { homedir as homedir9, platform as platform3 } from "node:os";
62192
62304
  import { join as join37 } from "node:path";
62193
62305
  function zedConfigDir() {
62194
62306
  const os = platform3();
62195
62307
  if (os === "win32")
62196
62308
  return join37(appSupportDir(), "Zed");
62197
62309
  if (os === "darwin")
62198
- return join37(homedir10(), ".config", "zed");
62310
+ return join37(homedir9(), ".config", "zed");
62199
62311
  return join37(appSupportDir(), "zed");
62200
62312
  }
62201
62313
  function zedDataDir() {
@@ -62204,7 +62316,7 @@ function zedDataDir() {
62204
62316
  return join37(appSupportDir(), "Zed");
62205
62317
  if (os === "win32")
62206
62318
  return join37(process.env.LOCALAPPDATA ?? "", "Zed");
62207
- return join37(process.env.XDG_DATA_HOME ?? join37(homedir10(), ".local", "share"), "zed");
62319
+ return join37(process.env.XDG_DATA_HOME ?? join37(homedir9(), ".local", "share"), "zed");
62208
62320
  }
62209
62321
  function legacyDarwinSettingsPath() {
62210
62322
  return platform3() === "darwin" ? join37(appSupportDir(), "Zed", "settings.json") : null;
@@ -62353,7 +62465,7 @@ import {
62353
62465
  writeFileSync as writeFileSync14,
62354
62466
  writeSync
62355
62467
  } from "node:fs";
62356
- import { homedir as homedir11 } from "node:os";
62468
+ import { homedir as homedir10 } from "node:os";
62357
62469
  import { join as join38 } from "node:path";
62358
62470
  function statePath() {
62359
62471
  return join38(getConfigDir(), STATE_FILENAME2);
@@ -62488,7 +62600,7 @@ function startAutoUpdate(version, options = {}) {
62488
62600
  return claim === "held" ? "in_progress" : "unavailable";
62489
62601
  try {
62490
62602
  const child = spawn3(invocation.command, invocation.args, {
62491
- cwd: homedir11(),
62603
+ cwd: homedir10(),
62492
62604
  detached: true,
62493
62605
  stdio: "ignore",
62494
62606
  windowsHide: true,
@@ -62889,7 +63001,7 @@ var init_follow = () => {};
62889
63001
 
62890
63002
  // src/sessions/session-title.ts
62891
63003
  import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync25, renameSync as renameSync6, statSync as statSync10, writeFileSync as writeFileSync17 } from "node:fs";
62892
- import { homedir as homedir12 } from "node:os";
63004
+ import { homedir as homedir11 } from "node:os";
62893
63005
  import { join as join41 } from "node:path";
62894
63006
  function cachePath2(configDir) {
62895
63007
  return join41(configDir, CACHE_FILENAME5);
@@ -62944,7 +63056,7 @@ function claudeSummaryFromHead(text) {
62944
63056
  }
62945
63057
  return null;
62946
63058
  }
62947
- function reconstructSession(harness, id, slug, home = homedir12(), exists = existsSync26) {
63059
+ function reconstructSession(harness, id, slug, home = homedir11(), exists = existsSync26) {
62948
63060
  if (!slug)
62949
63061
  return null;
62950
63062
  const candidates = [];
@@ -63620,13 +63732,13 @@ var init_activity_view = __esm(() => {
63620
63732
 
63621
63733
  // src/rules/installer.ts
63622
63734
  import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync27, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
63623
- import { homedir as homedir13 } from "node:os";
63735
+ import { homedir as homedir12 } from "node:os";
63624
63736
  import { dirname as dirname12, join as join42 } from "node:path";
63625
63737
  function claudeConfigDir4() {
63626
- return process.env.CLAUDE_CONFIG_DIR || join42(homedir13(), ".claude");
63738
+ return process.env.CLAUDE_CONFIG_DIR || join42(homedir12(), ".claude");
63627
63739
  }
63628
63740
  function codexHome4() {
63629
- return process.env.CODEX_HOME || join42(homedir13(), ".codex");
63741
+ return process.env.CODEX_HOME || join42(homedir12(), ".codex");
63630
63742
  }
63631
63743
  function isRuleAgent(agent) {
63632
63744
  return Object.hasOwn(RULE_TARGETS, agent);
@@ -63772,7 +63884,7 @@ var init_installer = __esm(() => {
63772
63884
  },
63773
63885
  cursor: {
63774
63886
  kind: "file",
63775
- path: () => join42(homedir13(), ".cursor", "rules", "dosu.mdc"),
63887
+ path: () => join42(homedir12(), ".cursor", "rules", "dosu.mdc"),
63776
63888
  cursorFrontmatter: true
63777
63889
  },
63778
63890
  codex: {
@@ -63781,15 +63893,15 @@ var init_installer = __esm(() => {
63781
63893
  },
63782
63894
  opencode: {
63783
63895
  kind: "section",
63784
- path: () => join42(homedir13(), ".config", "opencode", "AGENTS.md")
63896
+ path: () => join42(homedir12(), ".config", "opencode", "AGENTS.md")
63785
63897
  },
63786
63898
  gemini: {
63787
63899
  kind: "section",
63788
- path: () => join42(homedir13(), ".gemini", "GEMINI.md")
63900
+ path: () => join42(homedir12(), ".gemini", "GEMINI.md")
63789
63901
  },
63790
63902
  antigravity: {
63791
63903
  kind: "section",
63792
- path: () => join42(homedir13(), ".gemini", "GEMINI.md")
63904
+ path: () => join42(homedir12(), ".gemini", "GEMINI.md")
63793
63905
  }
63794
63906
  };
63795
63907
  });
@@ -65580,7 +65692,7 @@ var init_flow2 = __esm(() => {
65580
65692
  // src/commands/upgrade.ts
65581
65693
  import { spawnSync } from "node:child_process";
65582
65694
  import { realpathSync as realpathSync2 } from "node:fs";
65583
- import { homedir as homedir14 } from "node:os";
65695
+ import { homedir as homedir13 } from "node:os";
65584
65696
  import { posix, win32 as win322 } from "node:path";
65585
65697
  function isAbsoluteCommandProcessor(value) {
65586
65698
  return Boolean(value && win322.isAbsolute(value) && win322.basename(value).toLowerCase() === "cmd.exe");
@@ -65634,7 +65746,7 @@ function packageRoots(manager, platform, comSpec, env, cwd) {
65634
65746
  manager === "npm" ? path.join(lines[0], "@dosu", "cli") : path.join(lines[0], "node_modules", "@dosu", "cli")
65635
65747
  ];
65636
65748
  }
65637
- function owningPackageManager(entrypoint, platform, comSpec, env = process.env, cwd = homedir14()) {
65749
+ function owningPackageManager(entrypoint, platform, comSpec, env = process.env, cwd = homedir13()) {
65638
65750
  const path = platform === "win32" ? win322 : posix;
65639
65751
  if (!entrypoint || !path.isAbsolute(entrypoint))
65640
65752
  return null;
@@ -65687,7 +65799,7 @@ Or install Dosu globally with your package manager:`);
65687
65799
  function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
65688
65800
  const platform = options.platform ?? process.platform;
65689
65801
  const env = options.env ?? process.env;
65690
- const cwd = options.cwd ?? homedir14();
65802
+ const cwd = options.cwd ?? homedir13();
65691
65803
  let manager = null;
65692
65804
  if (channel === "npm") {
65693
65805
  if (isNpxInvocation(channel, env)) {
@@ -65754,7 +65866,7 @@ function finishWithNewBinary(channel, options) {
65754
65866
  return;
65755
65867
  }
65756
65868
  const result = spawnSync(invocation.command, invocation.args, {
65757
- cwd: options.cwd ?? homedir14(),
65869
+ cwd: options.cwd ?? homedir13(),
65758
65870
  shell: false,
65759
65871
  stdio: "inherit"
65760
65872
  });
@@ -66552,7 +66664,6 @@ var init_pages_view = __esm(() => {
66552
66664
  });
66553
66665
 
66554
66666
  // src/tui/tui.ts
66555
- import { homedir as homedir15 } from "node:os";
66556
66667
  import { basename as basename6 } from "node:path";
66557
66668
  async function probeSession(cfg) {
66558
66669
  if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
@@ -66781,8 +66892,9 @@ async function runSettings(cfg) {
66781
66892
  while (true) {
66782
66893
  const target = cfg.active_account?.target;
66783
66894
  const library = target?.library_name ?? target?.deployment_name ?? "not configured";
66784
- const filter = loadSyncState().project_filter;
66785
- const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" : basename6(dir)).join(", ") : `${filter.length} projects`;
66895
+ const sync = loadSyncState();
66896
+ const filter = sync.repo_filter;
66897
+ const scope = !filter ? sync.project_filter?.length ? `${sync.project_filter.length} folders (legacy)` : "all repos" : filter.length === 0 ? "no repos" : filter.length <= 2 ? filter.map(displayRepo).join(", ") : `${filter.length} repos`;
66786
66898
  const action = await menuSelect("settings", [
66787
66899
  { label: "switch organization", hint: target?.org_name, value: "switch-org" },
66788
66900
  { label: "switch library", hint: library, value: "switch-library" },
@@ -66816,45 +66928,43 @@ async function runSettings(cfg) {
66816
66928
  }
66817
66929
  }
66818
66930
  }
66819
- function displayDir(dir) {
66820
- const home = homedir15();
66821
- return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
66822
- }
66823
- function discoverProjectDirs() {
66931
+ function discoverSessionRepos(current) {
66824
66932
  const counts = new Map;
66825
66933
  try {
66826
66934
  const resolver = createProjectDirResolver();
66827
66935
  for (const session of scanAgentSessions({})) {
66828
- const key = resolver.resolve(session) ?? UNKNOWN_PROJECT;
66829
- counts.set(key, (counts.get(key) ?? 0) + 1);
66936
+ const repo = resolver.resolveRepo(session);
66937
+ if (repo)
66938
+ counts.set(repo, (counts.get(repo) ?? 0) + 1);
66830
66939
  }
66831
66940
  resolver.flush();
66832
66941
  } catch {}
66833
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([dir]) => dir);
66942
+ for (const repo of current ?? [])
66943
+ if (!counts.has(repo))
66944
+ counts.set(repo, 0);
66945
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([repo]) => repo);
66834
66946
  }
66835
66947
  async function runStudyingProjectsSetting() {
66836
- const dirs = discoverProjectDirs();
66837
- if (dirs.length === 0) {
66838
- log2.info("No local agent sessions found yet; nothing to scope.");
66948
+ const state = loadSyncState();
66949
+ const current = studyRepoFilter(state, () => scanAgentSessions({}), createProjectDirResolver());
66950
+ const repos = discoverSessionRepos(current);
66951
+ if (repos.length === 0) {
66952
+ log2.info("No agent sessions inside a git repo found yet; nothing to scope.");
66839
66953
  return;
66840
66954
  }
66841
- const current = loadSyncState().project_filter;
66842
66955
  const selected = await multiselect2({
66843
- message: "Study sessions from which folders?",
66844
- options: dirs.map((dir) => ({
66845
- label: dir === UNKNOWN_PROJECT ? "(unknown folder)" : displayDir(dir),
66846
- value: dir
66847
- })),
66848
- initialValues: current?.length ? current : dirs,
66849
- summary: (picked) => picked.length === dirs.length ? "all folders · new ones included automatically" : `${picked.length} of ${dirs.length} folders · subfolders included`,
66850
- validate: (picked) => picked.length === 0 ? "Select at least one folder." : undefined
66956
+ message: "Study sessions from which repos?",
66957
+ options: repos.map((repo) => ({ label: displayRepo(repo), hint: repo, value: repo })),
66958
+ initialValues: current ?? repos,
66959
+ summary: (picked) => picked.length === repos.length ? "all repos · new ones included automatically" : `${picked.length} of ${repos.length} repos`,
66960
+ validate: (picked) => picked.length === 0 ? "Select at least one repo." : undefined
66851
66961
  });
66852
66962
  if (isCancel2(selected))
66853
66963
  return;
66854
- const { project_filter: _previous, ...state } = loadSyncState();
66855
- const all = selected.length === dirs.length;
66856
- saveSyncState(all ? state : { ...state, project_filter: [...selected] });
66857
- const scope = all ? "all folders" : selected.map(displayDir).join(", ");
66964
+ const { repo_filter: _repos, project_filter: _folders, ...fresh } = loadSyncState();
66965
+ const all = selected.length === repos.length;
66966
+ saveSyncState(all ? fresh : { ...fresh, repo_filter: [...selected] });
66967
+ const scope = all ? "all repos" : selected.map(displayRepo).join(", ");
66858
66968
  log2.success(`Study scope ${dim(`· ${scope}`)}`);
66859
66969
  }
66860
66970
  async function handleAuthenticate(cfg) {
@@ -66907,6 +67017,7 @@ var init_tui = __esm(() => {
66907
67017
  init_providers();
66908
67018
  init_generate();
66909
67019
  init_project_dir();
67020
+ init_repo();
66910
67021
  init_scan();
66911
67022
  init_agents_md_step();
66912
67023
  init_flow2();
@@ -68115,4 +68226,4 @@ main().catch((err) => {
68115
68226
  process.exit(1);
68116
68227
  });
68117
68228
 
68118
- //# debugId=097dc848-8855-154b-6475-6e2164756e21
68229
+ //# debugId=01f74cf6-cdbb-b296-6475-6e2164756e21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.62.1",
3
+ "version": "0.63.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Set up Dosu MCP for AI tools and work with Dosu Libraries, docs, and reviews",
6
6
  "license": "MIT",