@damian87/omp 0.4.1 → 0.5.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 (56) hide show
  1. package/.github/agents/researcher.md +7 -6
  2. package/.github/copilot-instructions.md +23 -0
  3. package/.github/skills/daily-log/SKILL.md +64 -0
  4. package/.github/skills/goal/SKILL.md +33 -0
  5. package/dist/src/cli.js +190 -9
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/comms/index.d.ts +116 -0
  8. package/dist/src/comms/index.js +258 -0
  9. package/dist/src/comms/index.js.map +1 -0
  10. package/dist/src/comms/resolve-session.d.ts +35 -0
  11. package/dist/src/comms/resolve-session.js +53 -0
  12. package/dist/src/comms/resolve-session.js.map +1 -0
  13. package/dist/src/daily-log.d.ts +18 -0
  14. package/dist/src/daily-log.js +138 -0
  15. package/dist/src/daily-log.js.map +1 -0
  16. package/dist/src/goal.d.ts +4 -0
  17. package/dist/src/goal.js +44 -0
  18. package/dist/src/goal.js.map +1 -0
  19. package/dist/src/instructions-memory.d.ts +9 -0
  20. package/dist/src/instructions-memory.js +72 -0
  21. package/dist/src/instructions-memory.js.map +1 -0
  22. package/dist/src/mcp/tools/daily-log.d.ts +2 -0
  23. package/dist/src/mcp/tools/daily-log.js +148 -0
  24. package/dist/src/mcp/tools/daily-log.js.map +1 -0
  25. package/dist/src/omp-root.d.ts +1 -0
  26. package/dist/src/omp-root.js +19 -0
  27. package/dist/src/omp-root.js.map +1 -0
  28. package/dist/src/project-memory.d.ts +13 -0
  29. package/dist/src/project-memory.js +105 -0
  30. package/dist/src/project-memory.js.map +1 -0
  31. package/dist/src/state.d.ts +17 -0
  32. package/dist/src/state.js +101 -0
  33. package/dist/src/state.js.map +1 -0
  34. package/dist/src/trace.d.ts +19 -0
  35. package/dist/src/trace.js +74 -0
  36. package/dist/src/trace.js.map +1 -0
  37. package/dist/test/catalog.test.d.ts +1 -0
  38. package/dist/test/catalog.test.js +21 -0
  39. package/dist/test/catalog.test.js.map +1 -0
  40. package/dist/test/jira.test.d.ts +1 -0
  41. package/dist/test/jira.test.js +26 -0
  42. package/dist/test/jira.test.js.map +1 -0
  43. package/dist/test/lint.test.d.ts +1 -0
  44. package/dist/test/lint.test.js +9 -0
  45. package/dist/test/lint.test.js.map +1 -0
  46. package/dist/test/sync.test.d.ts +1 -0
  47. package/dist/test/sync.test.js +15 -0
  48. package/dist/test/sync.test.js.map +1 -0
  49. package/package.json +1 -1
  50. package/scripts/lib/daily-log.mjs +155 -0
  51. package/scripts/lib/hook-output.mjs +2 -1
  52. package/scripts/lib/omp-root.mjs +15 -0
  53. package/scripts/lib/project-memory.mjs +21 -0
  54. package/scripts/prompt-submit.mjs +14 -2
  55. package/scripts/session-end.mjs +6 -1
  56. package/scripts/session-start.mjs +52 -2
@@ -0,0 +1,116 @@
1
+ import { type TmuxApi } from "../team/tmux.js";
2
+ /**
3
+ * Strip ANSI escape sequences (OSC, CSI, and single-char Fe) from pane text.
4
+ * The OSC branch is bounded — it stops at the next BEL or ESC and tolerates a
5
+ * missing terminator (capture-pane can truncate mid-sequence) so it never
6
+ * greedily consumes real output between two sequences.
7
+ */
8
+ export declare function stripAnsi(input: string): string;
9
+ export interface OnlineOptions {
10
+ host?: string;
11
+ port?: number;
12
+ timeoutMs?: number;
13
+ }
14
+ /** Low-level reachability probe: resolves true if a TCP connection opens. */
15
+ export type Connector = (host: string, port: number, timeoutMs: number) => Promise<boolean>;
16
+ /**
17
+ * Returns true when the host appears to have internet connectivity. Defaults
18
+ * to a TCP probe of 1.1.1.1:443; overridable via env or options for tests and
19
+ * restricted networks.
20
+ *
21
+ * Note: this confirms reachability of the probe host only, not that Copilot's
22
+ * upstream (GitHub / model API) is reachable. It is a coarse "are we online"
23
+ * gate, not a Copilot health check.
24
+ */
25
+ export declare function checkOnline(opts?: OnlineOptions, connect?: Connector): Promise<boolean>;
26
+ export interface CommsDeps {
27
+ /** tmux API (defaults to the real one). */
28
+ tmux?: TmuxApi;
29
+ /** internet reachability check (defaults to {@link checkOnline}). */
30
+ isOnline?: () => Promise<boolean>;
31
+ /** sleep used by polling loops (injectable for tests). */
32
+ sleep?: (ms: number) => Promise<void>;
33
+ }
34
+ export interface StatusResult {
35
+ ok: true;
36
+ session: string;
37
+ /** tmux session exists ("on"). */
38
+ exists: boolean;
39
+ /** internet reachable. */
40
+ online: boolean;
41
+ /** pane shows an idle prompt (ready for input). */
42
+ ready: boolean;
43
+ /** pane shows an active task in progress. */
44
+ busy: boolean;
45
+ /** set when the pane existed but its contents could not be read. */
46
+ error?: string;
47
+ }
48
+ export declare function commsStatus(session: string, deps?: CommsDeps): Promise<StatusResult>;
49
+ export interface SendResult {
50
+ ok: boolean;
51
+ session?: string;
52
+ /** true if the text was observed in the pane before Enter was pressed. */
53
+ confirmed?: boolean;
54
+ error?: string;
55
+ }
56
+ export interface SendOptions {
57
+ /** send even when the pane shows an active task in progress. */
58
+ force?: boolean;
59
+ }
60
+ /**
61
+ * Send a prompt into the Copilot pane. Refuses unless the session exists AND
62
+ * the host is online. Sends the text literally, then a single Enter (C-m).
63
+ *
64
+ * Before pressing Enter it tries to confirm the text actually landed in the
65
+ * input buffer by comparing pane captures before/after the keystrokes (a
66
+ * delta check, so prompt text already present in scrollback can't false-
67
+ * confirm). Enter is sent exactly once regardless, so a slow render never
68
+ * causes a double-submit.
69
+ */
70
+ export declare function commsSend(session: string, text: string, deps?: CommsDeps, opts?: SendOptions): Promise<SendResult>;
71
+ export interface RecvOptions {
72
+ /** number of trailing pane lines to capture (default 80). */
73
+ lines?: number;
74
+ /** poll until the pane returns to an idle prompt before capturing. */
75
+ wait?: boolean;
76
+ /** max time to wait when `wait` is set (default 30000ms). */
77
+ timeoutMs?: number;
78
+ /** poll interval when `wait` is set (default 500ms). */
79
+ pollMs?: number;
80
+ }
81
+ export interface RecvResult {
82
+ ok: boolean;
83
+ session?: string;
84
+ text?: string;
85
+ /** true if `wait` gave up before the pane became ready (text may be mid-generation). */
86
+ timedOut?: boolean;
87
+ error?: string;
88
+ }
89
+ /** Read Copilot's latest pane output back (ANSI-stripped). */
90
+ export declare function commsRecv(session: string, deps?: CommsDeps, opts?: RecvOptions): Promise<RecvResult>;
91
+ /**
92
+ * Best-effort isolation of the output that appeared *after* a baseline capture.
93
+ * tmux scrollback has no reliable delimiters, so this uses an order-preserving
94
+ * multiset diff: lines present in `after` but not already accounted for in
95
+ * `before` are treated as new, with leading/trailing blank or prompt-only lines
96
+ * trimmed. It is a heuristic — not a guaranteed exact reply boundary.
97
+ */
98
+ export declare function newOutputSince(before: string, after: string): string;
99
+ export interface AskOptions extends SendOptions {
100
+ /** trailing pane lines to consider (default 200). */
101
+ lines?: number;
102
+ /** max time to wait for Copilot to go idle (default 30000ms). */
103
+ timeoutMs?: number;
104
+ /** poll interval while waiting (default 500ms). */
105
+ pollMs?: number;
106
+ }
107
+ export interface AskResult extends RecvResult {
108
+ /** whether the prompt was actually sent. */
109
+ sent: boolean;
110
+ }
111
+ /**
112
+ * Send a prompt and return only Copilot's new reply: snapshot the pane, send
113
+ * (with the same on/online/busy guards as {@link commsSend}), wait until the
114
+ * pane goes idle, then diff against the snapshot via {@link newOutputSince}.
115
+ */
116
+ export declare function commsAsk(session: string, text: string, deps?: CommsDeps, opts?: AskOptions): Promise<AskResult>;
@@ -0,0 +1,258 @@
1
+ /**
2
+ * comms — a thin wrapper that lets you talk to a running Copilot CLI session
3
+ * over tmux: send a prompt in, read the reply back, and check whether Copilot
4
+ * is reachable first.
5
+ *
6
+ * Design notes:
7
+ * - Reuses the tmux primitives in `../team/tmux.ts` (no new tmux plumbing).
8
+ * - Every external dependency (tmux, connectivity probe, sleep) is injectable
9
+ * so the logic is unit-testable without a real tmux server or network.
10
+ * - A send is gated on two conditions, mirroring "connected to the internet
11
+ * AND on": the target tmux session must exist, and an internet reachability
12
+ * probe must succeed. If either fails the send is refused with a structured
13
+ * error (never a silent no-op).
14
+ */
15
+ import { createConnection } from "node:net";
16
+ import { makeTmux, paneLooksReady, paneHasActiveTask, } from "../team/tmux.js";
17
+ /**
18
+ * Strip ANSI escape sequences (OSC, CSI, and single-char Fe) from pane text.
19
+ * The OSC branch is bounded — it stops at the next BEL or ESC and tolerates a
20
+ * missing terminator (capture-pane can truncate mid-sequence) so it never
21
+ * greedily consumes real output between two sequences.
22
+ */
23
+ export function stripAnsi(input) {
24
+ // OSC: ESC ] ... up to BEL or ST (optional, to tolerate truncation).
25
+ // eslint-disable-next-line no-control-regex
26
+ const osc = /\][^]*(?:|\\)?/g;
27
+ // CSI: ESC [ params intermediates final; or a single-char Fe escape.
28
+ // eslint-disable-next-line no-control-regex
29
+ const csiFe = /(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
30
+ return input.replace(osc, "").replace(csiFe, "");
31
+ }
32
+ const defaultConnector = (host, port, timeoutMs) => new Promise((resolve) => {
33
+ let settled = false;
34
+ const done = (ok) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ try {
39
+ socket.destroy();
40
+ }
41
+ catch {
42
+ /* ignore */
43
+ }
44
+ resolve(ok);
45
+ };
46
+ const socket = createConnection({ host, port });
47
+ socket.setTimeout(timeoutMs);
48
+ socket.once("connect", () => done(true));
49
+ socket.once("timeout", () => done(false));
50
+ socket.once("error", () => done(false));
51
+ });
52
+ /**
53
+ * Returns true when the host appears to have internet connectivity. Defaults
54
+ * to a TCP probe of 1.1.1.1:443; overridable via env or options for tests and
55
+ * restricted networks.
56
+ *
57
+ * Note: this confirms reachability of the probe host only, not that Copilot's
58
+ * upstream (GitHub / model API) is reachable. It is a coarse "are we online"
59
+ * gate, not a Copilot health check.
60
+ */
61
+ export async function checkOnline(opts = {}, connect = defaultConnector) {
62
+ const host = opts.host ?? process.env.OMP_NET_PROBE_HOST ?? "1.1.1.1";
63
+ const port = opts.port ?? (Number(process.env.OMP_NET_PROBE_PORT) || 443);
64
+ const timeoutMs = opts.timeoutMs ?? (Number(process.env.OMP_NET_PROBE_TIMEOUT_MS) || 3000);
65
+ try {
66
+ return await connect(host, port, timeoutMs);
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
73
+ export async function commsStatus(session, deps = {}) {
74
+ const tmux = deps.tmux ?? makeTmux();
75
+ const exists = tmux.sessionExists(session);
76
+ const online = deps.isOnline ? await deps.isOnline() : await checkOnline();
77
+ let ready = false;
78
+ let busy = false;
79
+ let error;
80
+ if (exists) {
81
+ const cap = tmux.capturePane(session, 40);
82
+ if (cap.status !== 0) {
83
+ // Session is "on" but unreadable — surface it rather than reporting a
84
+ // misleading running-but-idle state.
85
+ error = `failed to read copilot pane (tmux exit ${cap.status})`;
86
+ }
87
+ else {
88
+ const captured = stripAnsi(cap.stdout);
89
+ ready = paneLooksReady(captured);
90
+ busy = paneHasActiveTask(captured);
91
+ }
92
+ }
93
+ return { ok: true, session, exists, online, ready, busy, ...(error ? { error } : {}) };
94
+ }
95
+ /**
96
+ * Send a prompt into the Copilot pane. Refuses unless the session exists AND
97
+ * the host is online. Sends the text literally, then a single Enter (C-m).
98
+ *
99
+ * Before pressing Enter it tries to confirm the text actually landed in the
100
+ * input buffer by comparing pane captures before/after the keystrokes (a
101
+ * delta check, so prompt text already present in scrollback can't false-
102
+ * confirm). Enter is sent exactly once regardless, so a slow render never
103
+ * causes a double-submit.
104
+ */
105
+ export async function commsSend(session, text, deps = {}, opts = {}) {
106
+ if (!text || !text.trim()) {
107
+ return { ok: false, error: "empty message" };
108
+ }
109
+ if (/[\r\n]/.test(text)) {
110
+ // send-keys delivers embedded newlines literally, which most TUIs treat as
111
+ // separate submissions — refuse rather than mis-submit a multi-line prompt.
112
+ return { ok: false, error: "multi-line prompts are not supported; send a single line" };
113
+ }
114
+ const tmux = deps.tmux ?? makeTmux();
115
+ if (!tmux.sessionExists(session)) {
116
+ return { ok: false, error: `copilot session not running: ${session}` };
117
+ }
118
+ const online = deps.isOnline ? await deps.isOnline() : await checkOnline();
119
+ if (!online) {
120
+ return { ok: false, error: "offline: no internet connectivity" };
121
+ }
122
+ // Read the pane once: doubles as a failure check and a busy-gate baseline.
123
+ const pre = tmux.capturePane(session, 5);
124
+ if (pre.status !== 0) {
125
+ return { ok: false, error: `failed to read copilot pane (tmux exit ${pre.status})` };
126
+ }
127
+ const baseline = stripAnsi(pre.stdout);
128
+ if (!opts.force && paneHasActiveTask(baseline)) {
129
+ // Don't inject a prompt while Copilot is mid-response — pass force to override.
130
+ return { ok: false, error: "copilot is busy (task in progress); retry or use --force" };
131
+ }
132
+ const sleep = deps.sleep ?? defaultSleep;
133
+ const probe = text.slice(-Math.min(20, text.length));
134
+ const probeInBaseline = baseline.includes(probe);
135
+ const sent = tmux.sendText(session, text);
136
+ if (sent.status !== 0) {
137
+ return { ok: false, error: `tmux send-keys failed (exit ${sent.status})` };
138
+ }
139
+ // Confirm the literal text landed in the input buffer before pressing Enter,
140
+ // rather than relying on a fixed delay. Best-effort: send Enter once after
141
+ // the confirm window regardless, so we never double-submit.
142
+ let confirmed = false;
143
+ for (let i = 0; i < 6; i++) {
144
+ const cap = stripAnsi(tmux.capturePane(session, 5).stdout);
145
+ // If the probe wasn't already on screen, its appearance confirms the send.
146
+ // If it was, require the pane to have changed (the new text was appended).
147
+ if (cap.includes(probe) && (!probeInBaseline || cap !== baseline)) {
148
+ confirmed = true;
149
+ break;
150
+ }
151
+ await sleep(50);
152
+ }
153
+ const enter = tmux.sendKeys(session, "C-m");
154
+ if (enter.status !== 0) {
155
+ return { ok: false, error: `tmux send-keys C-m failed (exit ${enter.status})` };
156
+ }
157
+ return { ok: true, session, confirmed };
158
+ }
159
+ /** Read Copilot's latest pane output back (ANSI-stripped). */
160
+ export async function commsRecv(session, deps = {}, opts = {}) {
161
+ const tmux = deps.tmux ?? makeTmux();
162
+ if (!tmux.sessionExists(session)) {
163
+ return { ok: false, error: `copilot session not running: ${session}` };
164
+ }
165
+ const lines = opts.lines ?? 80;
166
+ let timedOut = false;
167
+ if (opts.wait) {
168
+ const sleep = deps.sleep ?? defaultSleep;
169
+ const timeoutMs = opts.timeoutMs ?? 30000;
170
+ const pollMs = opts.pollMs ?? 500;
171
+ const deadline = Date.now() + timeoutMs;
172
+ timedOut = true;
173
+ while (Date.now() < deadline) {
174
+ const snapshot = stripAnsi(tmux.capturePane(session, lines).stdout);
175
+ if (paneLooksReady(snapshot) && !paneHasActiveTask(snapshot)) {
176
+ timedOut = false;
177
+ break;
178
+ }
179
+ await sleep(pollMs);
180
+ }
181
+ }
182
+ const captured = tmux.capturePane(session, lines);
183
+ if (captured.status !== 0) {
184
+ return { ok: false, error: `failed to read copilot pane (tmux exit ${captured.status})` };
185
+ }
186
+ const text = stripAnsi(captured.stdout);
187
+ return { ok: true, session, text, ...(opts.wait ? { timedOut } : {}) };
188
+ }
189
+ // --- ask (send + wait + isolate the new reply) ---------------------------
190
+ /** A line that is only whitespace, box-drawing, or a bare prompt glyph. */
191
+ // eslint-disable-next-line no-misleading-character-class
192
+ const PROMPT_ONLY = /^[\s│┃║▌▐▏▕╎┆┊›>❯$#%]*$/;
193
+ /**
194
+ * Best-effort isolation of the output that appeared *after* a baseline capture.
195
+ * tmux scrollback has no reliable delimiters, so this uses an order-preserving
196
+ * multiset diff: lines present in `after` but not already accounted for in
197
+ * `before` are treated as new, with leading/trailing blank or prompt-only lines
198
+ * trimmed. It is a heuristic — not a guaranteed exact reply boundary.
199
+ */
200
+ export function newOutputSince(before, after) {
201
+ const norm = (s) => s.split("\n").map((l) => l.replace(/\s+$/, ""));
202
+ const counts = new Map();
203
+ for (const line of norm(before))
204
+ counts.set(line, (counts.get(line) ?? 0) + 1);
205
+ const out = [];
206
+ for (const line of norm(after)) {
207
+ const remaining = counts.get(line) ?? 0;
208
+ if (remaining > 0) {
209
+ counts.set(line, remaining - 1);
210
+ continue;
211
+ }
212
+ out.push(line);
213
+ }
214
+ while (out.length && (!out[0].trim() || PROMPT_ONLY.test(out[0])))
215
+ out.shift();
216
+ while (out.length && (!out[out.length - 1].trim() || PROMPT_ONLY.test(out[out.length - 1]))) {
217
+ out.pop();
218
+ }
219
+ return out.join("\n");
220
+ }
221
+ /**
222
+ * Send a prompt and return only Copilot's new reply: snapshot the pane, send
223
+ * (with the same on/online/busy guards as {@link commsSend}), wait until the
224
+ * pane goes idle, then diff against the snapshot via {@link newOutputSince}.
225
+ */
226
+ export async function commsAsk(session, text, deps = {}, opts = {}) {
227
+ const tmux = deps.tmux ?? makeTmux();
228
+ if (!tmux.sessionExists(session)) {
229
+ return { ok: false, error: `copilot session not running: ${session}`, sent: false };
230
+ }
231
+ const lines = opts.lines ?? 200;
232
+ const pre = tmux.capturePane(session, lines);
233
+ if (pre.status !== 0) {
234
+ return { ok: false, error: `failed to read copilot pane (tmux exit ${pre.status})`, sent: false };
235
+ }
236
+ const baseline = stripAnsi(pre.stdout);
237
+ const sent = await commsSend(session, text, deps, { force: opts.force });
238
+ if (!sent.ok) {
239
+ return { ok: false, error: sent.error, sent: false };
240
+ }
241
+ const recv = await commsRecv(session, deps, {
242
+ wait: true,
243
+ lines,
244
+ timeoutMs: opts.timeoutMs,
245
+ pollMs: opts.pollMs,
246
+ });
247
+ if (!recv.ok) {
248
+ return { ok: false, error: recv.error, sent: true };
249
+ }
250
+ return {
251
+ ok: true,
252
+ session,
253
+ text: newOutputSince(baseline, recv.text ?? ""),
254
+ timedOut: recv.timedOut,
255
+ sent: true,
256
+ };
257
+ }
258
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/comms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,QAAQ,EACR,cAAc,EACd,iBAAiB,GAElB,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,qEAAqE;IACrE,4CAA4C;IAC5C,MAAM,GAAG,GAAG,sBAAsB,CAAC;IACnC,qEAAqE;IACrE,4CAA4C;IAC5C,MAAM,KAAK,GAAG,qCAAqC,CAAC;IACpD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAaD,MAAM,gBAAgB,GAAc,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAC5D,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;IAC/B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,IAAI,GAAG,CAAC,EAAW,EAAE,EAAE;QAC3B,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,CAAC;YACH,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;QACD,OAAO,CAAC,EAAE,CAAC,CAAC;IACd,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1C,CAAC,CAAC,CAAC;AAEL;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAsB,EAAE,EACxB,UAAqB,gBAAgB;IAErC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,SAAS,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,IAAI,IAAI,CAAC,CAAC;IAC3F,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAaD,MAAM,YAAY,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAmBjF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,OAAkB,EAAE;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC;IAC3E,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,KAAyB,CAAC;IAC9B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,sEAAsE;YACtE,qCAAqC;YACrC,KAAK,GAAG,0CAA0C,GAAG,CAAC,MAAM,GAAG,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACvC,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;YACjC,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC;AAiBD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAe,EACf,IAAY,EACZ,OAAkB,EAAE,EACpB,OAAoB,EAAE;IAEtB,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,2EAA2E;QAC3E,4EAA4E;QAC5E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC;IAC1F,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,OAAO,EAAE,EAAE,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC;IAC3E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;IACnE,CAAC;IACD,2EAA2E;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0CAA0C,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;IACvF,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,gFAAgF;QAChF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC;IAC1F,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,MAAM,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IAC7E,CAAC;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,4DAA4D;IAC5D,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC3D,2EAA2E;QAC3E,2EAA2E;QAC3E,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,eAAe,IAAI,GAAG,KAAK,QAAQ,CAAC,EAAE,CAAC;YAClE,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM;QACR,CAAC;QACD,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAC1C,CAAC;AAwBD,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAe,EACf,OAAkB,EAAE,EACpB,OAAoB,EAAE;IAEtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,OAAO,EAAE,EAAE,CAAC;IACzE,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,QAAQ,GAAG,IAAI,CAAC;QAChB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;YACpE,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7D,QAAQ,GAAG,KAAK,CAAC;gBACjB,MAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0CAA0C,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAC;IAC5F,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,4EAA4E;AAE5E,2EAA2E;AAC3E,yDAAyD;AACzD,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,KAAa;IAC1D,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;YAChC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;QAAE,GAAG,CAAC,KAAK,EAAE,CAAC;IACjF,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,CAAC,EAAE,CAAC;QAC9F,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAgBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAe,EACf,IAAY,EACZ,OAAkB,EAAE,EACpB,OAAmB,EAAE;IAErB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACtF,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0CAA0C,GAAG,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACpG,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;QAC1C,IAAI,EAAE,IAAI;QACV,KAAK;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtD,CAAC;IACD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO;QACP,IAAI,EAAE,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type TmuxApi } from "../team/tmux.js";
2
+ /**
3
+ * Regex matching a Copilot session name produced by the launch scheme
4
+ * (`omp-${Date.now()}`). Naturally excludes `omp-team-*` and any other
5
+ * non-digit suffixes.
6
+ */
7
+ export declare const COPILOT_SESSION_RE: RegExp;
8
+ export type ResolveSessionSuccess = {
9
+ ok: true;
10
+ session: string;
11
+ source: "flag" | "env" | "discovery";
12
+ };
13
+ export type ResolveSessionFailure = {
14
+ ok: false;
15
+ error: string;
16
+ candidates?: string[];
17
+ };
18
+ export type ResolveSessionResult = ResolveSessionSuccess | ResolveSessionFailure;
19
+ export interface ResolveSessionOpts {
20
+ /** Value of the --session CLI flag, if provided. */
21
+ flag?: string;
22
+ /** Value of the COPILOT_TMUX_SESSION env var, if set. */
23
+ env?: string;
24
+ /** Injected tmux API (defaults to the real one). */
25
+ tmux?: TmuxApi;
26
+ }
27
+ /**
28
+ * Resolve the target Copilot tmux session via three-tier precedence:
29
+ * 1. Explicit --session flag
30
+ * 2. COPILOT_TMUX_SESSION env var
31
+ * 3. Auto-discovery: scan tmux for exactly one session matching COPILOT_SESSION_RE
32
+ *
33
+ * Returns a structured result — never throws.
34
+ */
35
+ export declare function resolveSession(opts?: ResolveSessionOpts): ResolveSessionResult;
@@ -0,0 +1,53 @@
1
+ import { makeTmux } from "../team/tmux.js";
2
+ /**
3
+ * Regex matching a Copilot session name produced by the launch scheme
4
+ * (`omp-${Date.now()}`). Naturally excludes `omp-team-*` and any other
5
+ * non-digit suffixes.
6
+ */
7
+ export const COPILOT_SESSION_RE = /^omp-\d+$/;
8
+ /**
9
+ * Resolve the target Copilot tmux session via three-tier precedence:
10
+ * 1. Explicit --session flag
11
+ * 2. COPILOT_TMUX_SESSION env var
12
+ * 3. Auto-discovery: scan tmux for exactly one session matching COPILOT_SESSION_RE
13
+ *
14
+ * Returns a structured result — never throws.
15
+ */
16
+ export function resolveSession(opts = {}) {
17
+ const { flag, env } = opts;
18
+ const tmux = opts.tmux ?? makeTmux();
19
+ if (flag && flag.length > 0) {
20
+ return { ok: true, session: flag, source: "flag" };
21
+ }
22
+ if (env && env.length > 0) {
23
+ return { ok: true, session: env, source: "env" };
24
+ }
25
+ let sessions;
26
+ try {
27
+ sessions = tmux.listSessions();
28
+ }
29
+ catch (err) {
30
+ // Honor the no-throw contract: surface tmux failures as a structured result.
31
+ return {
32
+ ok: false,
33
+ error: `failed to list tmux sessions (${err instanceof Error ? err.message : String(err)}) — pass --session <name>`,
34
+ };
35
+ }
36
+ const candidates = sessions.filter((name) => COPILOT_SESSION_RE.test(name));
37
+ if (candidates.length === 1) {
38
+ return { ok: true, session: candidates[0], source: "discovery" };
39
+ }
40
+ if (candidates.length === 0) {
41
+ return {
42
+ ok: false,
43
+ error: "no running copilot session found — launch with `omp`, or pass --session <name>",
44
+ };
45
+ }
46
+ // More than one match — refuse to pick silently.
47
+ return {
48
+ ok: false,
49
+ error: `multiple copilot sessions found — pass --session <name> to specify one:\n ${candidates.join("\n ")}`,
50
+ candidates,
51
+ };
52
+ }
53
+ //# sourceMappingURL=resolve-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-session.js","sourceRoot":"","sources":["../../../src/comms/resolve-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAgB,MAAM,iBAAiB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAyB9C;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B,EAAE;IAC1D,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;IAErC,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACrD,CAAC;IAED,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACnD,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6EAA6E;QAC7E,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,2BAA2B;SACpH,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACpE,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,gFAAgF;SACxF,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,8EAA8E,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QAC9G,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ /** Set/replace today's goal. Returns the date and stored goal. */
2
+ export declare function setDailyGoal(cwd: string, goal: string): {
3
+ date: string;
4
+ goal: string;
5
+ };
6
+ /** Append a timestamped entry (`- HH:MM — <text>`) to today's log. */
7
+ export declare function addLogEntry(cwd: string, text: string): {
8
+ date: string;
9
+ count: number;
10
+ };
11
+ /**
12
+ * Delete day-files older than `keepDays` days; returns the removed dates.
13
+ * Daily files already age out of context (only a breadcrumb is injected and
14
+ * reads are recency-bounded), so this is disk housekeeping, not a context fix.
15
+ */
16
+ export declare function pruneDailyLog(cwd: string, keepDays: number): string[];
17
+ /** Read today + the previous `days` days, newest-first, capped to ~4KB. */
18
+ export declare function readDailyLog(cwd: string, days: number): string;
@@ -0,0 +1,138 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { ompRoot } from "./omp-root.js";
4
+ const DAY_FILE_RE = /^\d{4}-\d{2}-\d{2}\.md$/;
5
+ const READ_CHAR_BUDGET = 4000;
6
+ function pad(n) {
7
+ return String(n).padStart(2, "0");
8
+ }
9
+ function todayStr(d = new Date()) {
10
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
11
+ }
12
+ function timeStr(d = new Date()) {
13
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
14
+ }
15
+ function dailyDir(cwd) {
16
+ return join(ompRoot(cwd), ".omp", "memory", "daily");
17
+ }
18
+ function dayFile(cwd, date = todayStr()) {
19
+ return join(dailyDir(cwd), `${date}.md`);
20
+ }
21
+ function parseDay(text) {
22
+ let section = null;
23
+ const goalLines = [];
24
+ const log = [];
25
+ for (const line of text.split("\n")) {
26
+ if (/^#\s+/.test(line))
27
+ continue;
28
+ if (/^##\s+Goal\s*$/i.test(line)) {
29
+ section = "goal";
30
+ continue;
31
+ }
32
+ if (/^##\s+Log\s*$/i.test(line)) {
33
+ section = "log";
34
+ continue;
35
+ }
36
+ if (section === "goal")
37
+ goalLines.push(line);
38
+ // Preserve any non-empty line a user may have hand-written, verbatim (bullets,
39
+ // prose, indented sub-notes). Only blank spacer lines are dropped on round-trip.
40
+ else if (section === "log" && line.trim() !== "")
41
+ log.push(line);
42
+ }
43
+ return { goal: goalLines.join("\n").trim(), log };
44
+ }
45
+ function serializeDay(date, doc) {
46
+ const parts = [`# ${date}`, "", "## Goal", doc.goal.trim(), "", "## Log", ...doc.log];
47
+ return `${parts.join("\n").replace(/\n+$/, "")}\n`;
48
+ }
49
+ function readDay(cwd, date = todayStr()) {
50
+ const p = dayFile(cwd, date);
51
+ if (!existsSync(p))
52
+ return { goal: "", log: [] };
53
+ try {
54
+ return parseDay(readFileSync(p, "utf8"));
55
+ }
56
+ catch {
57
+ return { goal: "", log: [] };
58
+ }
59
+ }
60
+ function writeDay(cwd, doc, date = todayStr()) {
61
+ const p = dayFile(cwd, date);
62
+ mkdirSync(dirname(p), { recursive: true });
63
+ const tmp = `${p}.tmp.${process.pid}.${Date.now()}`;
64
+ writeFileSync(tmp, serializeDay(date, doc), "utf8");
65
+ renameSync(tmp, p);
66
+ }
67
+ /** Set/replace today's goal. Returns the date and stored goal. */
68
+ export function setDailyGoal(cwd, goal) {
69
+ const doc = readDay(cwd);
70
+ doc.goal = String(goal ?? "").trim();
71
+ writeDay(cwd, doc);
72
+ return { date: todayStr(), goal: doc.goal };
73
+ }
74
+ /** Append a timestamped entry (`- HH:MM — <text>`) to today's log. */
75
+ export function addLogEntry(cwd, text) {
76
+ // Collapse to a single line so an entry can never contain a `## Goal`/`## Log`
77
+ // marker that parseDay would later misread as a section boundary.
78
+ const clean = String(text ?? "")
79
+ .replace(/\s*\n\s*/g, " ")
80
+ .trim();
81
+ const doc = readDay(cwd);
82
+ doc.log.push(`- ${timeStr()} — ${clean}`);
83
+ writeDay(cwd, doc);
84
+ return { date: todayStr(), count: doc.log.length };
85
+ }
86
+ /**
87
+ * Delete day-files older than `keepDays` days; returns the removed dates.
88
+ * Daily files already age out of context (only a breadcrumb is injected and
89
+ * reads are recency-bounded), so this is disk housekeeping, not a context fix.
90
+ */
91
+ export function pruneDailyLog(cwd, keepDays) {
92
+ const dir = dailyDir(cwd);
93
+ if (!existsSync(dir))
94
+ return [];
95
+ const cutoff = todayStr(new Date(Date.now() - Math.max(0, keepDays) * 86400000));
96
+ const removed = [];
97
+ for (const f of readdirSync(dir)) {
98
+ if (!DAY_FILE_RE.test(f))
99
+ continue;
100
+ const date = f.slice(0, 10);
101
+ if (date < cutoff) {
102
+ try {
103
+ unlinkSync(join(dir, f));
104
+ removed.push(date);
105
+ }
106
+ catch {
107
+ // skip unremovable file
108
+ }
109
+ }
110
+ }
111
+ return removed.sort();
112
+ }
113
+ /** Read today + the previous `days` days, newest-first, capped to ~4KB. */
114
+ export function readDailyLog(cwd, days) {
115
+ const dir = dailyDir(cwd);
116
+ if (!existsSync(dir))
117
+ return "";
118
+ const files = readdirSync(dir)
119
+ .filter((f) => DAY_FILE_RE.test(f))
120
+ .sort()
121
+ .reverse()
122
+ .slice(0, Math.max(0, days) + 1);
123
+ let out = "";
124
+ for (const f of files) {
125
+ try {
126
+ out += `${readFileSync(join(dir, f), "utf8").trim()}\n\n`;
127
+ }
128
+ catch {
129
+ // skip unreadable day file
130
+ }
131
+ if (out.length > READ_CHAR_BUDGET) {
132
+ out = `${out.slice(0, READ_CHAR_BUDGET)}\n…(truncated)`;
133
+ break;
134
+ }
135
+ }
136
+ return out.trim();
137
+ }
138
+ //# sourceMappingURL=daily-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daily-log.js","sourceRoot":"","sources":["../../src/daily-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAYxC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAC9C,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,QAAQ,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE;IAC9B,OAAO,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,SAAS,OAAO,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE;IAC7B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,IAAI,GAAG,QAAQ,EAAE;IAC7C,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,OAAO,GAA0B,IAAI,CAAC;IAC1C,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACjC,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,GAAG,MAAM,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,GAAG,KAAK,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,OAAO,KAAK,MAAM;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,+EAA+E;QAC/E,iFAAiF;aAC5E,IAAI,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,GAAW;IAC7C,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACtF,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,IAAI,GAAG,QAAQ,EAAE;IAC7C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IACjD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW,EAAE,IAAI,GAAG,QAAQ,EAAE;IAC3D,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACpD,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IACpD,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACnB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY;IACnD,+EAA+E;IAC/E,kEAAkE;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SAC7B,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,IAAI,EAAE,CAAC;IACV,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,MAAM,KAAK,EAAE,CAAC,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACnB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,QAAgB;IACzD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IACjF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,IAAI,GAAG,MAAM,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC;SAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAClC,IAAI,EAAE;SACN,OAAO,EAAE;SACT,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACnC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,GAAG,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YAClC,GAAG,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,gBAAgB,CAAC;YACxD,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** The repo objective, or "" when unset. */
2
+ export declare function readRepoGoal(cwd: string): string;
3
+ /** Set/replace the repo objective (collapsed to one north-star line). */
4
+ export declare function writeRepoGoal(cwd: string, goal: string): string;