@askexenow/exe-os 0.9.9 → 0.9.10

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.
@@ -4755,11 +4755,124 @@ var init_session_kill_telemetry = __esm({
4755
4755
  }
4756
4756
  });
4757
4757
 
4758
+ // src/lib/project-name.ts
4759
+ var project_name_exports = {};
4760
+ __export(project_name_exports, {
4761
+ _resetCache: () => _resetCache,
4762
+ getProjectName: () => getProjectName
4763
+ });
4764
+ import { execSync as execSync5 } from "child_process";
4765
+ import path14 from "path";
4766
+ function getProjectName(cwd) {
4767
+ const dir = cwd ?? process.cwd();
4768
+ if (_cached2 && _cachedCwd === dir) return _cached2;
4769
+ try {
4770
+ let repoRoot;
4771
+ try {
4772
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4773
+ cwd: dir,
4774
+ encoding: "utf8",
4775
+ timeout: 2e3,
4776
+ stdio: ["pipe", "pipe", "pipe"]
4777
+ }).trim();
4778
+ repoRoot = path14.dirname(gitCommonDir);
4779
+ } catch {
4780
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
4781
+ cwd: dir,
4782
+ encoding: "utf8",
4783
+ timeout: 2e3,
4784
+ stdio: ["pipe", "pipe", "pipe"]
4785
+ }).trim();
4786
+ }
4787
+ _cached2 = path14.basename(repoRoot);
4788
+ _cachedCwd = dir;
4789
+ return _cached2;
4790
+ } catch {
4791
+ _cached2 = path14.basename(dir);
4792
+ _cachedCwd = dir;
4793
+ return _cached2;
4794
+ }
4795
+ }
4796
+ function _resetCache() {
4797
+ _cached2 = null;
4798
+ _cachedCwd = null;
4799
+ }
4800
+ var _cached2, _cachedCwd;
4801
+ var init_project_name = __esm({
4802
+ "src/lib/project-name.ts"() {
4803
+ "use strict";
4804
+ _cached2 = null;
4805
+ _cachedCwd = null;
4806
+ }
4807
+ });
4808
+
4809
+ // src/lib/session-scope.ts
4810
+ var session_scope_exports = {};
4811
+ __export(session_scope_exports, {
4812
+ assertSessionScope: () => assertSessionScope,
4813
+ findSessionForProject: () => findSessionForProject,
4814
+ getSessionProject: () => getSessionProject
4815
+ });
4816
+ function getSessionProject(sessionName) {
4817
+ const sessions = listSessions();
4818
+ const entry = sessions.find((s) => s.windowName === sessionName);
4819
+ if (!entry) return null;
4820
+ const parts = entry.projectDir.split("/").filter(Boolean);
4821
+ return parts[parts.length - 1] ?? null;
4822
+ }
4823
+ function findSessionForProject(projectName) {
4824
+ const sessions = listSessions();
4825
+ for (const s of sessions) {
4826
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
4827
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4828
+ }
4829
+ return null;
4830
+ }
4831
+ function assertSessionScope(actionType, targetProject) {
4832
+ try {
4833
+ const currentProject = getProjectName();
4834
+ const exeSession = resolveExeSession();
4835
+ if (!exeSession) {
4836
+ return { allowed: true, reason: "no_session" };
4837
+ }
4838
+ if (currentProject === targetProject) {
4839
+ return {
4840
+ allowed: true,
4841
+ reason: "same_session",
4842
+ currentProject,
4843
+ targetProject
4844
+ };
4845
+ }
4846
+ process.stderr.write(
4847
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4848
+ `
4849
+ );
4850
+ return {
4851
+ allowed: false,
4852
+ reason: "cross_session_denied",
4853
+ currentProject,
4854
+ targetProject,
4855
+ targetSession: findSessionForProject(targetProject)?.windowName
4856
+ };
4857
+ } catch {
4858
+ return { allowed: true, reason: "no_session" };
4859
+ }
4860
+ }
4861
+ var init_session_scope = __esm({
4862
+ "src/lib/session-scope.ts"() {
4863
+ "use strict";
4864
+ init_session_registry();
4865
+ init_project_name();
4866
+ init_tmux_routing();
4867
+ init_employees();
4868
+ }
4869
+ });
4870
+
4758
4871
  // src/lib/tasks-crud.ts
4759
4872
  import crypto4 from "crypto";
4760
- import path14 from "path";
4873
+ import path15 from "path";
4761
4874
  import os10 from "os";
4762
- import { execSync as execSync5 } from "child_process";
4875
+ import { execSync as execSync6 } from "child_process";
4763
4876
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4764
4877
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4765
4878
  async function writeCheckpoint(input) {
@@ -4881,9 +4994,24 @@ async function createTaskCore(input) {
4881
4994
  const now = (/* @__PURE__ */ new Date()).toISOString();
4882
4995
  const slug = slugify(input.title);
4883
4996
  let earlySessionScope = null;
4997
+ let scopeMismatchWarning;
4884
4998
  try {
4885
4999
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4886
- earlySessionScope = resolveExeSession2();
5000
+ const resolved = resolveExeSession2();
5001
+ if (resolved && input.projectName) {
5002
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
5003
+ const sessionProject = getSessionProject2(resolved);
5004
+ if (sessionProject && sessionProject !== input.projectName) {
5005
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
5006
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
5007
+ `);
5008
+ earlySessionScope = null;
5009
+ } else {
5010
+ earlySessionScope = resolved;
5011
+ }
5012
+ } else {
5013
+ earlySessionScope = resolved;
5014
+ }
4887
5015
  } catch {
4888
5016
  }
4889
5017
  const scope = earlySessionScope ?? "default";
@@ -4934,10 +5062,14 @@ async function createTaskCore(input) {
4934
5062
  ${laneWarning}` : laneWarning;
4935
5063
  }
4936
5064
  }
5065
+ if (scopeMismatchWarning) {
5066
+ warning = warning ? `${warning}
5067
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
5068
+ }
4937
5069
  if (input.baseDir) {
4938
5070
  try {
4939
- await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
4940
- await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
5071
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
5072
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
4941
5073
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4942
5074
  await ensureGitignoreExe(input.baseDir);
4943
5075
  } catch {
@@ -4973,9 +5105,9 @@ ${laneWarning}` : laneWarning;
4973
5105
  });
4974
5106
  if (input.baseDir) {
4975
5107
  try {
4976
- const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
4977
- const mdPath = path14.join(EXE_OS_DIR, taskFile);
4978
- const mdDir = path14.dirname(mdPath);
5108
+ const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
5109
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
5110
+ const mdDir = path15.dirname(mdPath);
4979
5111
  if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
4980
5112
  const reviewer = input.reviewer ?? input.assignedBy;
4981
5113
  const mdContent = `# ${input.title}
@@ -5080,14 +5212,14 @@ function isTmuxSessionAlive(identifier) {
5080
5212
  if (!identifier || identifier === "unknown") return true;
5081
5213
  try {
5082
5214
  if (identifier.startsWith("%")) {
5083
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
5215
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
5084
5216
  timeout: 2e3,
5085
5217
  encoding: "utf8",
5086
5218
  stdio: ["pipe", "pipe", "pipe"]
5087
5219
  });
5088
5220
  return output.split("\n").some((l) => l.trim() === identifier);
5089
5221
  } else {
5090
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5222
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5091
5223
  timeout: 2e3,
5092
5224
  stdio: ["pipe", "pipe", "pipe"]
5093
5225
  });
@@ -5096,7 +5228,7 @@ function isTmuxSessionAlive(identifier) {
5096
5228
  } catch {
5097
5229
  if (identifier.startsWith("%")) return true;
5098
5230
  try {
5099
- execSync5("tmux list-sessions", {
5231
+ execSync6("tmux list-sessions", {
5100
5232
  timeout: 2e3,
5101
5233
  stdio: ["pipe", "pipe", "pipe"]
5102
5234
  });
@@ -5111,12 +5243,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
5111
5243
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
5112
5244
  try {
5113
5245
  const since = new Date(taskCreatedAt).toISOString();
5114
- const branch = execSync5(
5246
+ const branch = execSync6(
5115
5247
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
5116
5248
  { encoding: "utf8", timeout: 3e3 }
5117
5249
  ).trim();
5118
5250
  const branchArg = branch && branch !== "HEAD" ? branch : "";
5119
- const commitCount = execSync5(
5251
+ const commitCount = execSync6(
5120
5252
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
5121
5253
  { encoding: "utf8", timeout: 5e3 }
5122
5254
  ).trim();
@@ -5276,7 +5408,7 @@ async function deleteTaskCore(taskId, _baseDir) {
5276
5408
  return { taskFile, assignedTo, assignedBy, taskSlug };
5277
5409
  }
5278
5410
  async function ensureArchitectureDoc(baseDir, projectName) {
5279
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
5411
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
5280
5412
  try {
5281
5413
  if (existsSync14(archPath)) return;
5282
5414
  const template = [
@@ -5311,7 +5443,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5311
5443
  }
5312
5444
  }
5313
5445
  async function ensureGitignoreExe(baseDir) {
5314
- const gitignorePath = path14.join(baseDir, ".gitignore");
5446
+ const gitignorePath = path15.join(baseDir, ".gitignore");
5315
5447
  try {
5316
5448
  if (existsSync14(gitignorePath)) {
5317
5449
  const content = readFileSync11(gitignorePath, "utf-8");
@@ -5345,7 +5477,7 @@ var init_tasks_crud = __esm({
5345
5477
  });
5346
5478
 
5347
5479
  // src/lib/tasks-review.ts
5348
- import path15 from "path";
5480
+ import path16 from "path";
5349
5481
  import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5350
5482
  async function countPendingReviews(sessionScope) {
5351
5483
  const client = getClient();
@@ -5511,11 +5643,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5511
5643
  );
5512
5644
  }
5513
5645
  try {
5514
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5646
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5515
5647
  if (existsSync15(cacheDir)) {
5516
5648
  for (const f of readdirSync3(cacheDir)) {
5517
5649
  if (f.startsWith("review-notified-")) {
5518
- unlinkSync4(path15.join(cacheDir, f));
5650
+ unlinkSync4(path16.join(cacheDir, f));
5519
5651
  }
5520
5652
  }
5521
5653
  }
@@ -5537,7 +5669,7 @@ var init_tasks_review = __esm({
5537
5669
  });
5538
5670
 
5539
5671
  // src/lib/tasks-chain.ts
5540
- import path16 from "path";
5672
+ import path17 from "path";
5541
5673
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5542
5674
  async function cascadeUnblock(taskId, baseDir, now) {
5543
5675
  const client = getClient();
@@ -5554,7 +5686,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5554
5686
  });
5555
5687
  for (const ur of unblockedRows.rows) {
5556
5688
  try {
5557
- const ubFile = path16.join(baseDir, String(ur.task_file));
5689
+ const ubFile = path17.join(baseDir, String(ur.task_file));
5558
5690
  let ubContent = await readFile4(ubFile, "utf-8");
5559
5691
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5560
5692
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5621,119 +5753,6 @@ var init_tasks_chain = __esm({
5621
5753
  }
5622
5754
  });
5623
5755
 
5624
- // src/lib/project-name.ts
5625
- var project_name_exports = {};
5626
- __export(project_name_exports, {
5627
- _resetCache: () => _resetCache,
5628
- getProjectName: () => getProjectName
5629
- });
5630
- import { execSync as execSync6 } from "child_process";
5631
- import path17 from "path";
5632
- function getProjectName(cwd) {
5633
- const dir = cwd ?? process.cwd();
5634
- if (_cached2 && _cachedCwd === dir) return _cached2;
5635
- try {
5636
- let repoRoot;
5637
- try {
5638
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
5639
- cwd: dir,
5640
- encoding: "utf8",
5641
- timeout: 2e3,
5642
- stdio: ["pipe", "pipe", "pipe"]
5643
- }).trim();
5644
- repoRoot = path17.dirname(gitCommonDir);
5645
- } catch {
5646
- repoRoot = execSync6("git rev-parse --show-toplevel", {
5647
- cwd: dir,
5648
- encoding: "utf8",
5649
- timeout: 2e3,
5650
- stdio: ["pipe", "pipe", "pipe"]
5651
- }).trim();
5652
- }
5653
- _cached2 = path17.basename(repoRoot);
5654
- _cachedCwd = dir;
5655
- return _cached2;
5656
- } catch {
5657
- _cached2 = path17.basename(dir);
5658
- _cachedCwd = dir;
5659
- return _cached2;
5660
- }
5661
- }
5662
- function _resetCache() {
5663
- _cached2 = null;
5664
- _cachedCwd = null;
5665
- }
5666
- var _cached2, _cachedCwd;
5667
- var init_project_name = __esm({
5668
- "src/lib/project-name.ts"() {
5669
- "use strict";
5670
- _cached2 = null;
5671
- _cachedCwd = null;
5672
- }
5673
- });
5674
-
5675
- // src/lib/session-scope.ts
5676
- var session_scope_exports = {};
5677
- __export(session_scope_exports, {
5678
- assertSessionScope: () => assertSessionScope,
5679
- findSessionForProject: () => findSessionForProject,
5680
- getSessionProject: () => getSessionProject
5681
- });
5682
- function getSessionProject(sessionName) {
5683
- const sessions = listSessions();
5684
- const entry = sessions.find((s) => s.windowName === sessionName);
5685
- if (!entry) return null;
5686
- const parts = entry.projectDir.split("/").filter(Boolean);
5687
- return parts[parts.length - 1] ?? null;
5688
- }
5689
- function findSessionForProject(projectName) {
5690
- const sessions = listSessions();
5691
- for (const s of sessions) {
5692
- const proj = s.projectDir.split("/").filter(Boolean).pop();
5693
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
5694
- }
5695
- return null;
5696
- }
5697
- function assertSessionScope(actionType, targetProject) {
5698
- try {
5699
- const currentProject = getProjectName();
5700
- const exeSession = resolveExeSession();
5701
- if (!exeSession) {
5702
- return { allowed: true, reason: "no_session" };
5703
- }
5704
- if (currentProject === targetProject) {
5705
- return {
5706
- allowed: true,
5707
- reason: "same_session",
5708
- currentProject,
5709
- targetProject
5710
- };
5711
- }
5712
- process.stderr.write(
5713
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5714
- `
5715
- );
5716
- return {
5717
- allowed: false,
5718
- reason: "cross_session_denied",
5719
- currentProject,
5720
- targetProject,
5721
- targetSession: findSessionForProject(targetProject)?.windowName
5722
- };
5723
- } catch {
5724
- return { allowed: true, reason: "no_session" };
5725
- }
5726
- }
5727
- var init_session_scope = __esm({
5728
- "src/lib/session-scope.ts"() {
5729
- "use strict";
5730
- init_session_registry();
5731
- init_project_name();
5732
- init_tmux_routing();
5733
- init_employees();
5734
- }
5735
- });
5736
-
5737
5756
  // src/lib/tasks-notify.ts
5738
5757
  async function dispatchTaskToEmployee(input) {
5739
5758
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };