@mmnto/cli 1.43.6 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +80 -2
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/mail.d.ts +77 -0
- package/dist/commands/mail.d.ts.map +1 -0
- package/dist/commands/mail.js +339 -0
- package/dist/commands/mail.js.map +1 -0
- package/dist/commands/mail.test.d.ts +11 -0
- package/dist/commands/mail.test.d.ts.map +1 -0
- package/dist/commands/mail.test.js +470 -0
- package/dist/commands/mail.test.js.map +1 -0
- package/dist/hook/schema.d.ts +2 -2
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `totem mail` — canonical cross-repo outbox poll (ADR-106 § 3 / ADR-107).
|
|
3
|
+
*
|
|
4
|
+
* Senders write to their own `<repoRoot>/.totem/orchestration/<sender-agent>/outbox/*.md`
|
|
5
|
+
* with `to: <recipient-agent>` (or `to: broadcast`) in the frontmatter.
|
|
6
|
+
* Recipients invoke this command at session-start (typically via a
|
|
7
|
+
* vendor-specific hook in `.claude/hooks/` or `.gemini/hooks/`) to surface
|
|
8
|
+
* unread mail addressed to themselves.
|
|
9
|
+
*
|
|
10
|
+
* SELF_AGENTS resolution flows through `resolveSelfAgents` from
|
|
11
|
+
* `@mmnto/totem` (env > config.json > basename map). Workspace defaults to
|
|
12
|
+
* the parent directory of the calling repo, overridable via the
|
|
13
|
+
* `TOTEM_WORKSPACE` env var or `--workspace` flag.
|
|
14
|
+
*
|
|
15
|
+
* Ports the strategy-side reference implementation
|
|
16
|
+
* (`mmnto-ai/totem-strategy:.claude/hooks/SessionStart.cjs:pollInboundOutboxes`,
|
|
17
|
+
* merged via mmnto-ai/totem-strategy#373) into a cohort-portable command
|
|
18
|
+
* surface per the ADR-107 § Consequences direction.
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { resolveSelfAgents } from '@mmnto/totem';
|
|
23
|
+
// ─── Constants ──────────────────────────────────────────
|
|
24
|
+
const TAG = 'Mail';
|
|
25
|
+
/**
|
|
26
|
+
* Hard cap on filesystem entries scanned per invocation. Matches the
|
|
27
|
+
* strategy reference impl's MAX_SCAN; balances thoroughness against hook
|
|
28
|
+
* latency. When tripped, the result carries `truncated: true` so the
|
|
29
|
+
* caller can surface the warning instead of silently dropping mail.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_SCAN = 500;
|
|
32
|
+
// ─── Frontmatter parsing ────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Extract `to:` / `from:` / `subject:` / `date:` from a leading frontmatter
|
|
35
|
+
* block. Restricts the regex search to the header (text before the first
|
|
36
|
+
* blank line) so body lines starting with `to:` etc. cannot fabricate a
|
|
37
|
+
* match or overwrite displayed metadata — same defense pattern as the
|
|
38
|
+
* reference impl.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` if no `to:` field is present (the only frontmatter field
|
|
41
|
+
* required for a file to be eligible mail).
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Hard cap on bytes scanned for frontmatter when no blank-line separator
|
|
45
|
+
* exists. Defense against forged frontmatter: a malformed handoff without
|
|
46
|
+
* the `---\n\n` delimiter would otherwise let body lines starting with
|
|
47
|
+
* `to:` fabricate metadata. Real frontmatter is dozens of bytes; 2 KiB
|
|
48
|
+
* is generous-but-bounded.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_HEADER_BYTES = 2048;
|
|
51
|
+
function parseHeader(content) {
|
|
52
|
+
// Defense in depth: real handoffs open with a YAML frontmatter delimiter.
|
|
53
|
+
// Reject anything that doesn't, so a stray .md file in an outbox cannot
|
|
54
|
+
// be coerced into mail.
|
|
55
|
+
if (!content.startsWith('---'))
|
|
56
|
+
return null;
|
|
57
|
+
const parts = content.split(/\r?\n\r?\n/, 2);
|
|
58
|
+
const header = parts[0] ?? '';
|
|
59
|
+
// When there's no blank-line separator, the split returns the whole
|
|
60
|
+
// file as parts[0]. Cap header size in that case so body content can't
|
|
61
|
+
// fabricate `to:`/`from:` matches.
|
|
62
|
+
if (parts.length < 2 && content.length > MAX_HEADER_BYTES)
|
|
63
|
+
return null;
|
|
64
|
+
const toMatch = header.match(/^to:\s*(.+)$/im);
|
|
65
|
+
if (!toMatch)
|
|
66
|
+
return null;
|
|
67
|
+
const fromMatch = header.match(/^from:\s*(.+)$/im);
|
|
68
|
+
const subjectMatch = header.match(/^[-\s]*subject:\s*(.+)$/im);
|
|
69
|
+
const dateMatch = header.match(/^date:\s*(.+)$/im);
|
|
70
|
+
return {
|
|
71
|
+
to: toMatch[1].trim(),
|
|
72
|
+
from: fromMatch ? fromMatch[1].trim() : null,
|
|
73
|
+
subject: subjectMatch ? subjectMatch[1].trim() : null,
|
|
74
|
+
date: dateMatch ? dateMatch[1].trim() : null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build the set of basenames that should be skipped because they have
|
|
79
|
+
* already been actioned. Drains `processed/` and `processed/_broadcast/`
|
|
80
|
+
* for each SELF_AGENT in the calling repo.
|
|
81
|
+
*/
|
|
82
|
+
function buildProcessedSet(repoRoot, selfAgents, warnings) {
|
|
83
|
+
const processed = new Set();
|
|
84
|
+
for (const agent of selfAgents) {
|
|
85
|
+
const agentDir = path.join(repoRoot, '.totem', 'orchestration', agent, 'processed');
|
|
86
|
+
drainProcessedDir(agentDir, processed, warnings);
|
|
87
|
+
drainProcessedDir(path.join(agentDir, '_broadcast'), processed, warnings);
|
|
88
|
+
}
|
|
89
|
+
return processed;
|
|
90
|
+
}
|
|
91
|
+
function drainProcessedDir(dir, into, warnings) {
|
|
92
|
+
if (!fs.existsSync(dir))
|
|
93
|
+
return;
|
|
94
|
+
// totem-context: intentional cleanup — an unreadable processed/ subtree (EACCES, race with concurrent rename) emits a warning and degrades to a stale exclusion set rather than blocking the poll. Mail still surfaces; the agent may see already-actioned items in that worst case, which is observable (the warning) rather than silent.
|
|
95
|
+
try {
|
|
96
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
97
|
+
if (entry.endsWith('.md'))
|
|
98
|
+
into.add(entry);
|
|
99
|
+
}
|
|
100
|
+
// totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
warnings.push(`processed/ scan failed (${dir}): ${String(err)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Enumerate outbox directories under `<workspace>/<repo>/.totem/orchestration/<agent>/outbox`.
|
|
108
|
+
* Single-level by default; recursive mode walks `<workspace>/**` (capped at MAX_SCAN
|
|
109
|
+
* to bound runtime on deep trees).
|
|
110
|
+
*/
|
|
111
|
+
function enumerateOutboxes(workspace, recursive, warnings) {
|
|
112
|
+
const slots = [];
|
|
113
|
+
if (!fs.existsSync(workspace)) {
|
|
114
|
+
warnings.push(`workspace does not exist: ${workspace}`);
|
|
115
|
+
return slots;
|
|
116
|
+
}
|
|
117
|
+
let repos;
|
|
118
|
+
try {
|
|
119
|
+
repos = fs
|
|
120
|
+
.readdirSync(workspace, { withFileTypes: true })
|
|
121
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('.') && d.name !== 'node_modules')
|
|
122
|
+
.map((d) => d.name)
|
|
123
|
+
.sort();
|
|
124
|
+
// totem-context: intentional cleanup — a workspace readdir failure (EACCES, ENOTDIR via raced symlink) is recorded as a structured warning so the CLI surfaces it; throwing here would block hook-driven session start over a non-fatal scan issue.
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
warnings.push(`workspace scan failed: ${String(err)}`);
|
|
128
|
+
return slots;
|
|
129
|
+
}
|
|
130
|
+
const visit = (repoLabel, orchDir) => {
|
|
131
|
+
if (!fs.existsSync(orchDir))
|
|
132
|
+
return;
|
|
133
|
+
let agents;
|
|
134
|
+
try {
|
|
135
|
+
agents = fs.readdirSync(orchDir).sort();
|
|
136
|
+
// totem-context: intentional cleanup — per-repo readdir failure skips this slot, emits a structured warning, and lets sibling repos continue; one inaccessible orchestration tree must not block the rest of the scan.
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
warnings.push(`orchestration scan failed (${repoLabel}): ${String(err)}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const agent of agents) {
|
|
143
|
+
const outbox = path.join(orchDir, agent, 'outbox');
|
|
144
|
+
if (fs.existsSync(outbox)) {
|
|
145
|
+
slots.push({ repo: repoLabel, agent, outbox });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (!recursive) {
|
|
150
|
+
for (const repo of repos) {
|
|
151
|
+
visit(repo, path.join(workspace, repo, '.totem', 'orchestration'));
|
|
152
|
+
}
|
|
153
|
+
return slots;
|
|
154
|
+
}
|
|
155
|
+
// Recursive variant: descend into each top-level repo and look for any
|
|
156
|
+
// `.totem/orchestration/` under it. Bounded depth so a malformed tree
|
|
157
|
+
// can't pin us — the MAX_SCAN file-open cap is the second guard.
|
|
158
|
+
const RECURSIVE_DEPTH_CAP = 6;
|
|
159
|
+
const stack = repos.map((r) => ({
|
|
160
|
+
dir: path.join(workspace, r),
|
|
161
|
+
label: r,
|
|
162
|
+
depth: 0,
|
|
163
|
+
}));
|
|
164
|
+
while (stack.length > 0) {
|
|
165
|
+
const node = stack.pop();
|
|
166
|
+
visit(node.label, path.join(node.dir, '.totem', 'orchestration'));
|
|
167
|
+
if (node.depth >= RECURSIVE_DEPTH_CAP)
|
|
168
|
+
continue;
|
|
169
|
+
let children;
|
|
170
|
+
try {
|
|
171
|
+
children = fs.readdirSync(node.dir, { withFileTypes: true });
|
|
172
|
+
// totem-context: intentional cleanup — recursive-descent readdir failure on one node emits a structured warning and skips that subtree; a single inaccessible dir must not abort the whole scan.
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
warnings.push(`recursive scan failed (${node.dir}): ${String(err)}`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
for (const child of children) {
|
|
179
|
+
if (!child.isDirectory() || child.name.startsWith('.'))
|
|
180
|
+
continue;
|
|
181
|
+
if (child.name === 'node_modules')
|
|
182
|
+
continue;
|
|
183
|
+
stack.push({
|
|
184
|
+
dir: path.join(node.dir, child.name),
|
|
185
|
+
// Use the immediate-parent directory name as the repo label for
|
|
186
|
+
// nested layouts (e.g. `wrapper/nested-strategy/.totem/orchestration/`
|
|
187
|
+
// surfaces as repo='nested-strategy'). The top-level label is
|
|
188
|
+
// misleading when the orchestration tree lives under a wrapper dir.
|
|
189
|
+
label: child.name,
|
|
190
|
+
depth: node.depth + 1,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return slots;
|
|
195
|
+
}
|
|
196
|
+
// ─── Core poll ──────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Programmatic entry point. Returns a structured `MailPollResult` for
|
|
199
|
+
* consumers that want to render their own output (hooks, MCP audits,
|
|
200
|
+
* future surfaces). The CLI wrapper calls this then formats the result
|
|
201
|
+
* for human consumption.
|
|
202
|
+
*
|
|
203
|
+
* Never throws — filesystem failures degrade to warnings on the result.
|
|
204
|
+
*/
|
|
205
|
+
export function pollMail(opts = {}) {
|
|
206
|
+
const env = opts.env ?? process.env;
|
|
207
|
+
const repoRoot = path.resolve(opts.repoRoot ?? process.cwd());
|
|
208
|
+
const workspaceRaw = opts.workspace ?? env['TOTEM_WORKSPACE'] ?? path.dirname(repoRoot);
|
|
209
|
+
const workspace = path.resolve(workspaceRaw);
|
|
210
|
+
const selfResolution = resolveSelfAgents(repoRoot, env);
|
|
211
|
+
const selfLower = new Set(selfResolution.agents.map((a) => a.toLowerCase()));
|
|
212
|
+
const warnings = [];
|
|
213
|
+
if (selfResolution.agents.length === 0) {
|
|
214
|
+
warnings.push(`no SELF_AGENT resolved (set TOTEM_SELF_AGENT, add .totem/orchestration/config.json host_agents, or run from a known cohort repo)`);
|
|
215
|
+
}
|
|
216
|
+
const processedNames = selfResolution.agents.length > 0
|
|
217
|
+
? buildProcessedSet(repoRoot, selfResolution.agents, warnings)
|
|
218
|
+
: new Set();
|
|
219
|
+
const slots = enumerateOutboxes(workspace, opts.recursive === true, warnings);
|
|
220
|
+
// Two-pass scan for global newest-first fairness under MAX_SCAN.
|
|
221
|
+
// Pass 1 (cheap): readdirSync every outbox to collect all unread filenames.
|
|
222
|
+
// Pass 2 (expensive): sort globally by filename (ISO-timestamp prefix), then
|
|
223
|
+
// readFileSync only the top MAX_SCAN. Without the global sort, alphabet-early
|
|
224
|
+
// repos can exhaust the cap before later repos are touched (per GCA review on
|
|
225
|
+
// mmnto-ai/totem#1971). Pre-collect of cheap reads is faster than the prior
|
|
226
|
+
// interleaved per-slot loop when truncation actually trips.
|
|
227
|
+
const unread = [];
|
|
228
|
+
for (const slot of slots) {
|
|
229
|
+
let files;
|
|
230
|
+
try {
|
|
231
|
+
files = fs.readdirSync(slot.outbox).filter((f) => f.endsWith('.md'));
|
|
232
|
+
// totem-context: intentional cleanup — outbox readdir failure (mid-rename race, EACCES, removed-during-scan) emits a structured warning and skips this slot.
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
warnings.push(`outbox scan failed (${slot.repo}/${slot.agent}): ${String(err)}`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
if (processedNames.has(file))
|
|
240
|
+
continue;
|
|
241
|
+
unread.push({ slot, file });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Global newest-first by filename. ISO-timestamp prefixes give a total
|
|
245
|
+
// order; non-ISO filenames sort lexically (stable; only matters within a
|
|
246
|
+
// sender's outbox).
|
|
247
|
+
unread.sort((a, b) => b.file.localeCompare(a.file));
|
|
248
|
+
let scanned = 0;
|
|
249
|
+
let truncated = false;
|
|
250
|
+
if (unread.length > MAX_SCAN) {
|
|
251
|
+
truncated = true;
|
|
252
|
+
}
|
|
253
|
+
const mail = [];
|
|
254
|
+
const inScope = unread.length > MAX_SCAN ? unread.slice(0, MAX_SCAN) : unread;
|
|
255
|
+
for (const { slot, file } of inScope) {
|
|
256
|
+
scanned += 1;
|
|
257
|
+
let content;
|
|
258
|
+
try {
|
|
259
|
+
content = fs.readFileSync(path.join(slot.outbox, file), 'utf-8');
|
|
260
|
+
// totem-context: intentional cleanup — per-file readFileSync failure emits a structured warning and skips that file; mail surfacing must degrade gracefully on a single unreadable handoff (mid-write race or transient FS hiccup).
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
warnings.push(`mail read failed (${slot.repo}/${slot.agent}/${file}): ${String(err)}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const header = parseHeader(content);
|
|
267
|
+
if (!header)
|
|
268
|
+
continue;
|
|
269
|
+
const toLower = header.to.toLowerCase();
|
|
270
|
+
if (toLower !== 'broadcast' && !selfLower.has(toLower))
|
|
271
|
+
continue;
|
|
272
|
+
mail.push({
|
|
273
|
+
file,
|
|
274
|
+
repo: slot.repo,
|
|
275
|
+
from: header.from ?? slot.agent,
|
|
276
|
+
to: header.to,
|
|
277
|
+
date: header.date,
|
|
278
|
+
subject: header.subject ?? '(no subject)',
|
|
279
|
+
filePath: path.join(slot.outbox, file),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Re-sort the surviving mail by frontmatter date when available (filename
|
|
283
|
+
// sort already handled the primary order; this refines for files whose
|
|
284
|
+
// `date:` differs from the filename prefix).
|
|
285
|
+
mail.sort((a, b) => (b.date ?? b.file).localeCompare(a.date ?? a.file));
|
|
286
|
+
return {
|
|
287
|
+
selfAgents: { agents: [...selfResolution.agents], source: selfResolution.source },
|
|
288
|
+
mail,
|
|
289
|
+
scanned,
|
|
290
|
+
truncated,
|
|
291
|
+
workspace,
|
|
292
|
+
warnings,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// ─── Output formatting ──────────────────────────────────
|
|
296
|
+
function formatTextResult(result) {
|
|
297
|
+
const lines = [];
|
|
298
|
+
const selfList = result.selfAgents.agents.length > 0 ? result.selfAgents.agents.join(', ') : '(none)';
|
|
299
|
+
lines.push(`Workspace: ${result.workspace}`);
|
|
300
|
+
lines.push(`Self agents: ${selfList} (source: ${result.selfAgents.source})`);
|
|
301
|
+
if (result.warnings.length > 0) {
|
|
302
|
+
for (const w of result.warnings)
|
|
303
|
+
lines.push(`Warning: ${w}`);
|
|
304
|
+
}
|
|
305
|
+
if (result.mail.length === 0) {
|
|
306
|
+
lines.push(`No unread mail addressed to ${selfList} or broadcast.`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
lines.push(`${result.mail.length} unread:`);
|
|
310
|
+
for (const m of result.mail) {
|
|
311
|
+
lines.push(` - ${m.file} (from ${m.from} @ ${m.repo}, to: ${m.to})`);
|
|
312
|
+
lines.push(` subject: ${m.subject}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (result.truncated) {
|
|
316
|
+
lines.push(`[scan truncated at ${result.scanned} files; raise concern if this persists]`);
|
|
317
|
+
}
|
|
318
|
+
return lines.join('\n');
|
|
319
|
+
}
|
|
320
|
+
// ─── CLI entry ──────────────────────────────────────────
|
|
321
|
+
export async function mailCommand(opts = {}) {
|
|
322
|
+
const result = pollMail(opts);
|
|
323
|
+
if (opts.json === true) {
|
|
324
|
+
// JSON output goes to stdout (hook-friendly); structured logger goes to stderr
|
|
325
|
+
// via the standard CLI path. Using process.stdout keeps the JSON stream clean.
|
|
326
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
const { log } = await import('../ui.js');
|
|
330
|
+
const text = formatTextResult(result);
|
|
331
|
+
// log.info is stderr-bound across the CLI; mail output is informational,
|
|
332
|
+
// not a primary data product, so it joins the stderr stream consumers
|
|
333
|
+
// already attach to.
|
|
334
|
+
for (const line of text.split('\n')) {
|
|
335
|
+
log.info(TAG, line);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=mail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/commands/mail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `totem mail` (mmnto-ai/totem#1970, ADR-106 § 3 / ADR-107).
|
|
3
|
+
*
|
|
4
|
+
* Filesystem-driven: every test builds a fresh `<tmp>/workspace/<repo>/.totem/orchestration/`
|
|
5
|
+
* tree, exercises the poll, and asserts on the structured `MailPollResult`.
|
|
6
|
+
* Skips the human-text formatter path except where the formatter's behavior
|
|
7
|
+
* itself is under test — JSON output is the durable contract for hook
|
|
8
|
+
* consumers, the text output is human-only.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=mail.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.test.d.ts","sourceRoot":"","sources":["../../src/commands/mail.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|