@askexenow/exe-os 0.9.293 → 0.9.294

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateStatusBrief
4
- } from "../chunk-I35KAK3E.js";
4
+ } from "../chunk-GM6W3IXQ.js";
5
5
  import {
6
6
  DEFAULT_EXE,
7
7
  getSessionPrompt
@@ -1025,6 +1025,26 @@ async function boot(options) {
1025
1025
  };
1026
1026
  } catch {
1027
1027
  }
1028
+ try {
1029
+ const { scanUnmergedBranches } = await import("../branch-hygiene-55TKIWAZ.js");
1030
+ const STALE_HOURS = 24;
1031
+ const scan = scanUnmergedBranches(process.cwd(), { staleHours: STALE_HOURS });
1032
+ if (scan.total > 0) {
1033
+ briefData.unmergedBranches = {
1034
+ total: scan.total,
1035
+ staleCount: scan.staleCount,
1036
+ staleHours: STALE_HOURS,
1037
+ truncated: scan.truncated,
1038
+ branches: scan.branches.map((b) => ({
1039
+ name: b.name,
1040
+ ahead: b.ahead,
1041
+ ageHours: b.ageHours,
1042
+ filesChanged: b.filesChanged
1043
+ }))
1044
+ };
1045
+ }
1046
+ } catch {
1047
+ }
1028
1048
  try {
1029
1049
  const { fileURLToPath } = await import("url");
1030
1050
  const thisFile = fileURLToPath(import.meta.url);
@@ -21,9 +21,9 @@ import {
21
21
  parseFlags,
22
22
  runAudit,
23
23
  splitAtSentences
24
- } from "../chunk-LSTM35QE.js";
25
- import "../chunk-KQ4Z2G24.js";
24
+ } from "../chunk-5ALAYSQY.js";
26
25
  import "../chunk-BNJ3YCPD.js";
26
+ import "../chunk-KQ4Z2G24.js";
27
27
  import "../chunk-OTVEIJUQ.js";
28
28
  import "../chunk-L3TB7CC3.js";
29
29
  import "../chunk-6Y4B3QF6.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "../chunk-QAK47LG3.js";
5
5
  import {
6
6
  lightweightSearch
7
- } from "../chunk-XCP56OMR.js";
7
+ } from "../chunk-CZYJEUNA.js";
8
8
  import "../chunk-AB4PAQYK.js";
9
9
  import "../chunk-EM5WXY6H.js";
10
10
  import "../chunk-CHCA3ZM2.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  hybridSearch,
4
4
  lightweightSearch
5
- } from "../chunk-XCP56OMR.js";
5
+ } from "../chunk-CZYJEUNA.js";
6
6
  import "../chunk-AB4PAQYK.js";
7
7
  import {
8
8
  initStore
@@ -0,0 +1,136 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/lib/branch-hygiene.ts
4
+ import { execFileSync } from "child_process";
5
+ var GIT_TIMEOUT_MS = 4e3;
6
+ var MAX_BRANCHES_SCANNED = 60;
7
+ var DEFAULT_LISTING_CAP = 10;
8
+ function git(repoRoot, args) {
9
+ return execFileSync("git", args, {
10
+ cwd: repoRoot,
11
+ encoding: "utf8",
12
+ timeout: GIT_TIMEOUT_MS,
13
+ stdio: ["pipe", "pipe", "pipe"]
14
+ }).trim();
15
+ }
16
+ function detectRemoteBase(repoRoot) {
17
+ for (const candidate of ["main", "master"]) {
18
+ try {
19
+ git(repoRoot, ["rev-parse", "--verify", `refs/remotes/origin/${candidate}`]);
20
+ return `origin/${candidate}`;
21
+ } catch {
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function parseRefLine(line) {
27
+ const [name, committedAt] = line.split("");
28
+ if (!name) return null;
29
+ return { name: name.replace(/^origin\//, ""), committedAt: committedAt ?? "" };
30
+ }
31
+ function scanUnmergedBranches(repoRoot, options = {}) {
32
+ const staleHours = options.staleHours ?? 24;
33
+ const listingCap = options.listingCap ?? DEFAULT_LISTING_CAP;
34
+ const empty = { branches: [], total: 0, staleCount: 0, truncated: false };
35
+ let base;
36
+ try {
37
+ base = detectRemoteBase(repoRoot);
38
+ } catch {
39
+ return empty;
40
+ }
41
+ if (!base) return empty;
42
+ let lines;
43
+ try {
44
+ const raw = git(repoRoot, [
45
+ "for-each-ref",
46
+ `--no-merged=${base}`,
47
+ "--sort=-committerdate",
48
+ "--format=%(refname:short)%(committerdate:iso-strict)",
49
+ "refs/remotes/origin"
50
+ ]);
51
+ lines = raw ? raw.split("\n") : [];
52
+ } catch {
53
+ return empty;
54
+ }
55
+ const baseShort = base.replace(/^origin\//, "");
56
+ const now = Date.now();
57
+ const out = [];
58
+ let scanned = 0;
59
+ let truncated = false;
60
+ for (const line of lines) {
61
+ const parsed = parseRefLine(line);
62
+ if (!parsed) continue;
63
+ if (parsed.name === baseShort || parsed.name === "HEAD") continue;
64
+ if (scanned >= MAX_BRANCHES_SCANNED) {
65
+ truncated = true;
66
+ break;
67
+ }
68
+ scanned++;
69
+ let ahead = 0;
70
+ try {
71
+ const cnt = git(repoRoot, ["rev-list", "--count", `${base}..origin/${parsed.name}`]);
72
+ ahead = parseInt(cnt, 10) || 0;
73
+ } catch {
74
+ ahead = 0;
75
+ }
76
+ if (ahead <= 0) continue;
77
+ let filesChanged = 0;
78
+ try {
79
+ const diff = git(repoRoot, ["diff", "--name-only", `${base}...origin/${parsed.name}`]);
80
+ filesChanged = diff ? diff.split("\n").filter(Boolean).length : 0;
81
+ } catch {
82
+ filesChanged = 0;
83
+ }
84
+ const committedMs = parsed.committedAt ? Date.parse(parsed.committedAt) : NaN;
85
+ const ageHours = Number.isFinite(committedMs) ? Math.max(0, (now - committedMs) / 36e5) : 0;
86
+ out.push({
87
+ name: parsed.name,
88
+ ahead,
89
+ committedAt: parsed.committedAt,
90
+ ageHours,
91
+ filesChanged
92
+ });
93
+ }
94
+ const staleCount = out.filter((b) => b.ageHours >= staleHours).length;
95
+ return {
96
+ branches: out.slice(0, listingCap),
97
+ total: out.length,
98
+ staleCount,
99
+ truncated
100
+ };
101
+ }
102
+ function slugForMatch(input) {
103
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
104
+ }
105
+ function findMatchingBranches(repoRoot, opts) {
106
+ const result = [];
107
+ const scan = scanUnmergedBranches(repoRoot, { listingCap: MAX_BRANCHES_SCANNED });
108
+ if (scan.branches.length === 0) return result;
109
+ const idToken = opts.id ? slugForMatch(opts.id) : "";
110
+ const shortId = idToken ? idToken.replace(/-/g, "").slice(0, 8) : "";
111
+ const titleSlug = opts.title ? slugForMatch(opts.title) : "";
112
+ const titleTokens = new Set(titleSlug.split("-").filter((t) => t.length >= 4));
113
+ for (const b of scan.branches) {
114
+ const branchSlug = slugForMatch(b.name);
115
+ const branchCompact = branchSlug.replace(/-/g, "");
116
+ let matched = false;
117
+ if (idToken && (branchSlug.includes(idToken) || shortId && branchCompact.includes(shortId))) {
118
+ matched = true;
119
+ } else if (titleTokens.size > 0) {
120
+ const branchTokens = new Set(branchSlug.split("-"));
121
+ let overlap = 0;
122
+ for (const t of titleTokens) if (branchTokens.has(t)) overlap++;
123
+ if (overlap >= 2) matched = true;
124
+ }
125
+ if (matched) {
126
+ result.push({ name: b.name, ahead: b.ahead, ageHours: b.ageHours });
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+ export {
132
+ DEFAULT_LISTING_CAP,
133
+ findMatchingBranches,
134
+ scanUnmergedBranches,
135
+ slugForMatch
136
+ };
@@ -0,0 +1,175 @@
1
+ import {
2
+ lightweightSearch
3
+ } from "./chunk-CZYJEUNA.js";
4
+ import "./chunk-AB4PAQYK.js";
5
+ import "./chunk-EM5WXY6H.js";
6
+ import "./chunk-CHCA3ZM2.js";
7
+ import "./chunk-OTVEIJUQ.js";
8
+ import "./chunk-DQBHY3Q4.js";
9
+ import "./chunk-Y25OJWOQ.js";
10
+ import {
11
+ sessionScopeFilter,
12
+ strictSessionScopeFilter
13
+ } from "./chunk-2ZXMXFGH.js";
14
+ import "./chunk-E45SQG7C.js";
15
+ import "./chunk-4JERP7NT.js";
16
+ import "./chunk-4FVVJ7ME.js";
17
+ import "./chunk-RAERYWCL.js";
18
+ import "./chunk-HN3HGFBL.js";
19
+ import "./chunk-YNORJIBH.js";
20
+ import "./chunk-SSTLTIF3.js";
21
+ import "./chunk-DVFHAY2J.js";
22
+ import "./chunk-QRTJNSKU.js";
23
+ import "./chunk-NGP6LSV2.js";
24
+ import "./chunk-GJS7CJRC.js";
25
+ import "./chunk-CVYC6DUW.js";
26
+ import "./chunk-OPU3NYOO.js";
27
+ import "./chunk-GJV3WDWM.js";
28
+ import "./chunk-MP2AFCGL.js";
29
+ import "./chunk-HH3MBGMJ.js";
30
+ import "./chunk-HYZV25LY.js";
31
+ import {
32
+ getClient
33
+ } from "./chunk-TM2GZWIT.js";
34
+ import "./chunk-2I23RPSI.js";
35
+ import "./chunk-D3X64YSB.js";
36
+ import "./chunk-PNQDP3OA.js";
37
+ import "./chunk-7HLWBYH7.js";
38
+ import "./chunk-FXU7JOXK.js";
39
+ import "./chunk-Z42MXYWW.js";
40
+ import "./chunk-YTKVJJSU.js";
41
+ import "./chunk-R36FAN53.js";
42
+ import "./chunk-LYH5HE24.js";
43
+ import "./chunk-MLKGABMK.js";
44
+
45
+ // src/lib/catchup-brief.ts
46
+ import { execSync } from "child_process";
47
+ var HEADER = "## Catchup Brief\nHere's what happened while you were offline:";
48
+ var DEFAULT_LOOKBACK_MS = 60 * 60 * 1e3;
49
+ var MAX_BRIEF_CHARS = 2e3;
50
+ var MAX_CHECKPOINT_CHARS = 500;
51
+ var MAX_MESSAGE_CHARS = 100;
52
+ function clipText(text, maxChars) {
53
+ if (text.length <= maxChars) return text;
54
+ if (maxChars <= 1) return "\u2026";
55
+ return `${text.slice(0, maxChars - 1).trimEnd()}\u2026`;
56
+ }
57
+ function clampSections(header, sections, maxChars) {
58
+ const bounded = [];
59
+ let remaining = Math.max(0, maxChars - header.length);
60
+ for (const section of sections) {
61
+ const separatorChars = 2;
62
+ if (remaining <= separatorChars) break;
63
+ remaining -= separatorChars;
64
+ if (section.length <= remaining) {
65
+ bounded.push(section);
66
+ remaining -= section.length;
67
+ continue;
68
+ }
69
+ const clipped = clipText(section, remaining);
70
+ if (clipped.trim().length > 0) bounded.push(clipped);
71
+ break;
72
+ }
73
+ return bounded;
74
+ }
75
+ async function buildCatchupBrief(agentId, projectName, cwd, sessionScope) {
76
+ const sections = [];
77
+ let lastTimestamp = new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString();
78
+ try {
79
+ const checkpointMemories = await lightweightSearch(
80
+ "CONTEXT CHECKPOINT",
81
+ agentId,
82
+ { projectName, limit: 1 }
83
+ );
84
+ const checkpoint = checkpointMemories[0];
85
+ if (checkpoint?.timestamp) {
86
+ lastTimestamp = checkpoint.timestamp;
87
+ }
88
+ if (checkpoint?.raw_text) {
89
+ sections.push(
90
+ `### Last Checkpoint
91
+ ${clipText(checkpoint.raw_text, MAX_CHECKPOINT_CHARS)}`
92
+ );
93
+ }
94
+ } catch {
95
+ }
96
+ try {
97
+ const gitLog = execSync(
98
+ `git log --oneline --since=${JSON.stringify(lastTimestamp)} --format="%h %an: %s" | head -10`,
99
+ { cwd, timeout: 3e3, encoding: "utf-8" }
100
+ ).trim();
101
+ if (gitLog) {
102
+ sections.push(`### Git Activity Since You Left
103
+ \`\`\`
104
+ ${gitLog}
105
+ \`\`\``);
106
+ }
107
+ } catch {
108
+ }
109
+ try {
110
+ const client = getClient();
111
+ const taskScope = sessionScopeFilter(sessionScope);
112
+ const taskChanges = await client.execute({
113
+ sql: `SELECT title, status, priority FROM tasks
114
+ WHERE assigned_to = ? AND project_name = ?
115
+ AND updated_at > ? AND status IN ('open', 'in_progress')${taskScope.sql}
116
+ ORDER BY priority ASC, updated_at DESC LIMIT 5`,
117
+ args: [agentId, projectName, lastTimestamp, ...taskScope.args]
118
+ });
119
+ if (taskChanges.rows.length > 0) {
120
+ const taskLines = taskChanges.rows.map((row) => {
121
+ const record = row;
122
+ return `- [${String(record.priority).toUpperCase()}] ${String(record.title)} (${String(record.status)})`;
123
+ }).join("\n");
124
+ sections.push(`### Your Task Queue
125
+ ${taskLines}`);
126
+ }
127
+ } catch {
128
+ }
129
+ try {
130
+ const client = getClient();
131
+ const messageScope = strictSessionScopeFilter(sessionScope);
132
+ const messages = await client.execute({
133
+ sql: `SELECT from_agent, content, created_at FROM messages
134
+ WHERE target_agent = ? AND status = 'pending'${messageScope.sql}
135
+ ORDER BY created_at DESC LIMIT 3`,
136
+ args: [agentId, ...messageScope.args]
137
+ });
138
+ if (messages.rows.length > 0) {
139
+ const msgLines = messages.rows.map((row) => {
140
+ const record = row;
141
+ return `- From ${String(record.from_agent)}: ${clipText(String(record.content ?? ""), MAX_MESSAGE_CHARS)}`;
142
+ }).join("\n");
143
+ sections.push(`### Unread Messages
144
+ ${msgLines}`);
145
+ }
146
+ } catch {
147
+ }
148
+ try {
149
+ const { getPendingProspective, markSurfaced } = await import("./prospective-memory-JOKZL7QJ.js");
150
+ const due = await getPendingProspective(agentId, { projectName, limit: 5 });
151
+ if (due.length > 0) {
152
+ const now = Date.now();
153
+ const lines = due.map((item) => {
154
+ const overdue = item.dueDate != null && new Date(item.dueDate).getTime() < now;
155
+ const when = item.dueDate ? `due ${item.dueDate}${overdue ? " \u2014 OVERDUE" : ""}` : "someday";
156
+ const recur = item.recurrence !== "once" ? `, ${item.recurrence}` : "";
157
+ return `- [P${item.priority}] ${clipText(item.content, MAX_CHECKPOINT_CHARS)} (${when}${recur})`;
158
+ });
159
+ sections.push(`### Reminders Due (Prospective Memory)
160
+ ${lines.join("\n")}`);
161
+ try {
162
+ await markSurfaced(due.map((d) => d.id));
163
+ } catch {
164
+ }
165
+ }
166
+ } catch {
167
+ }
168
+ return {
169
+ header: HEADER,
170
+ sections: clampSections(HEADER, sections, MAX_BRIEF_CHARS)
171
+ };
172
+ }
173
+ export {
174
+ buildCatchupBrief
175
+ };