swarmdo 1.30.0 → 1.37.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/README.md +6 -3
- package/package.json +1 -1
- package/v3/@swarmdo/cli/dist/src/affected/affected.d.ts +6 -1
- package/v3/@swarmdo/cli/dist/src/affected/affected.js +7 -2
- package/v3/@swarmdo/cli/dist/src/apply/apply.js +18 -7
- package/v3/@swarmdo/cli/dist/src/changelog/changelog.js +4 -1
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.js +11 -1
- package/v3/@swarmdo/cli/dist/src/commands/comms.d.ts +16 -0
- package/v3/@swarmdo/cli/dist/src/commands/comms.js +280 -0
- package/v3/@swarmdo/cli/dist/src/commands/compact.js +6 -2
- package/v3/@swarmdo/cli/dist/src/commands/config.js +1 -0
- package/v3/@swarmdo/cli/dist/src/commands/hooks.js +121 -1
- package/v3/@swarmdo/cli/dist/src/commands/hotspots.js +8 -2
- package/v3/@swarmdo/cli/dist/src/commands/index.js +6 -0
- package/v3/@swarmdo/cli/dist/src/commands/permissions.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/commands/permissions.js +92 -0
- package/v3/@swarmdo/cli/dist/src/commands/usage.js +34 -1
- package/v3/@swarmdo/cli/dist/src/comms/mailbox.d.ts +88 -0
- package/v3/@swarmdo/cli/dist/src/comms/mailbox.js +138 -0
- package/v3/@swarmdo/cli/dist/src/comms/store.d.ts +21 -0
- package/v3/@swarmdo/cli/dist/src/comms/store.js +44 -0
- package/v3/@swarmdo/cli/dist/src/compact/compact.js +4 -1
- package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.js +8 -3
- package/v3/@swarmdo/cli/dist/src/config-lint/lint.d.ts +3 -1
- package/v3/@swarmdo/cli/dist/src/config-lint/lint.js +11 -4
- package/v3/@swarmdo/cli/dist/src/env/env.js +40 -14
- package/v3/@swarmdo/cli/dist/src/hooks-recipe/recipe.js +20 -1
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.d.ts +3 -0
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.js +9 -0
- package/v3/@swarmdo/cli/dist/src/index.js +8 -0
- package/v3/@swarmdo/cli/dist/src/license/license.js +4 -1
- package/v3/@swarmdo/cli/dist/src/mcp-client.js +2 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/comms-tools.d.ts +11 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/comms-tools.js +93 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.js +1 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/task-tools.js +4 -0
- package/v3/@swarmdo/cli/dist/src/memory/memory-bridge.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/memory/memory-bridge.js +4 -2
- package/v3/@swarmdo/cli/dist/src/memory-inject/select.d.ts +82 -0
- package/v3/@swarmdo/cli/dist/src/memory-inject/select.js +164 -0
- package/v3/@swarmdo/cli/dist/src/memory-vault/import.js +11 -1
- package/v3/@swarmdo/cli/dist/src/pack/pack.js +6 -2
- package/v3/@swarmdo/cli/dist/src/permissions/audit.d.ts +50 -0
- package/v3/@swarmdo/cli/dist/src/permissions/audit.js +127 -0
- package/v3/@swarmdo/cli/dist/src/plugins/store/search.js +1 -1
- package/v3/@swarmdo/cli/dist/src/redact/redact.js +7 -2
- package/v3/@swarmdo/cli/dist/src/release/release.js +1 -1
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +5 -0
- package/v3/@swarmdo/cli/dist/src/swarmvector/semantic-router.js +8 -3
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +9 -2
- package/v3/@swarmdo/cli/dist/src/transcript/export.js +8 -4
- package/v3/@swarmdo/cli/dist/src/update/checker.d.ts +1 -0
- package/v3/@swarmdo/cli/dist/src/update/checker.js +7 -2
- package/v3/@swarmdo/cli/dist/src/usage/model-efficiency.d.ts +29 -0
- package/v3/@swarmdo/cli/dist/src/usage/model-efficiency.js +28 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect-html.js +1 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect.d.ts +26 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect.js +44 -0
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.d.ts +8 -1
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +21 -1
- package/v3/@swarmdo/cli/dist/src/util/stdout.d.ts +23 -1
- package/v3/@swarmdo/cli/dist/src/util/stdout.js +34 -2
- package/v3/@swarmdo/cli/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://swarmdo.com)
|
|
4
4
|
|
|
5
|
-
[](https://img.shields.io/badge/npx%20swarmdo-v1.37.0-cb3837?style=for-the-badge&logo=npm&logoColor=white)](https://github.com/SwarmDo/swarmdo/releases)
|
|
6
6
|
[](https://github.com/SwarmDo/swarmdo/blob/main/LICENSE)
|
|
7
7
|
[](https://swarmdo.com)
|
|
8
8
|
[](https://github.com/SwarmDo/swarmdo)
|
|
@@ -271,7 +271,7 @@ The recent release train added a full day-to-day operations layer around the swa
|
|
|
271
271
|
|
|
272
272
|
| Command | What it does |
|
|
273
273
|
|---------|-------------|
|
|
274
|
-
| `swarmdo usage` (alias `cost`) | Claude Code **spend analytics** from your local transcripts — `daily`, `monthly` (with a month-end spend projection), `models`, `projects`, `sessions`, live 5-hour `blocks` burn, `errors` (tool-failure analytics), `cache` (prompt-cache efficiency + $ saved), `diff` (period-over-period comparison with per-model movers), `reflect` (a wrapped-style retrospective — busiest day, streak, top models/projects, peak hour, delegation ratio — with a shareable `--html` dashboard), and `limits` (an official-quota **exhaustion forecaster** over Claude Code's `rate_limits`); the standard views take `--csv` for spreadsheet export |
|
|
274
|
+
| `swarmdo usage` (alias `cost`) | Claude Code **spend analytics** from your local transcripts — `daily`, `weekly`, `monthly` (with a month-end spend projection), `models`, `projects`, `sessions`, live 5-hour `blocks` burn, `errors` (tool-failure analytics), `cache` (prompt-cache efficiency + $ saved), `diff` (period-over-period comparison with per-model movers), `reflect` (a wrapped-style retrospective — busiest day, streak, top models/projects, peak hour, delegation ratio — with a shareable `--html` dashboard), and `limits` (an official-quota **exhaustion forecaster** over Claude Code's `rate_limits`); the standard views take `--csv` for spreadsheet export |
|
|
275
275
|
| `swarmdo usage guard` | **Budget policy** — limits for the active 5h block / today / month via flags or `SWARMDO_GUARD_*` env → ok / warn / over; `--strict` exits 1, safe for CI gates and Stop hooks |
|
|
276
276
|
| `swarmdo hud` | **One-screen ops HUD** — 5h block burn, task readiness, daemon workers, memory snapshots (`--watch`, `--json`) |
|
|
277
277
|
| `swarmdo repair` (alias `tdd-repair`) | **Test-Driven Repair** — a bounded, budget-capped headless `claude` loop that fixes source until a failing test passes; dry-run unless `--confirm` |
|
|
@@ -294,8 +294,11 @@ The recent release train added a full day-to-day operations layer around the swa
|
|
|
294
294
|
| OpenRouter model pool | **Let swarms pick from any models you configure** — declare tier-mapped OpenRouter models in `swarmdo.config.json`; the router Thompson-samples among them per task and the execution layer dispatches the winner |
|
|
295
295
|
| `swarmdo changelog` (alias `notes`) | **Release notes from conventional commits** — `--out NOTES.md` feeds `gh release create --notes-file` |
|
|
296
296
|
| `swarmdo mcp doctor` | **MCP config diagnosis** — missing binaries, bad URLs, malformed entries across `.mcp.json` + `~/.claude.json` |
|
|
297
|
+
| `swarmdo permissions` (alias `perms`) | **Audit your Claude Code permission rules** — static analysis of `permissions.allow`/`deny`/`ask` in `.claude/settings*.json`: flags allow↔deny conflicts (dead rules), over-broad `Bash(*)` grants, shadowed/redundant rules, duplicates, and malformed entries. `--strict` gates CI. Read-only; the static-safety sibling of `config lint` / `mcp doctor` |
|
|
298
|
+
| `swarmdo comms` (alias `mailbox`) | **Cross-session agent mailbox** — one Claude Code session messages another by name (`send -t <session>`, `-t all` broadcasts, `inbox`, `read`, `watch`); sessions on the same repo share `.swarmdo/comms/`. `inbox --hook` surfaces new mail as prompt context without polling. Also MCP tools (`comms_send`/`comms_inbox`) |
|
|
299
|
+
| `swarmdo hooks memory-inject` | **Prompt-time semantic memory injection** — embeds each prompt, vector-searches your stored memories, and injects the most relevant under a token budget (recall at the moment of need); wire it with `hooks recipe memory-inject` |
|
|
297
300
|
| `swarmdo hooks notify -d` | **Desktop notifications** — OS-native toast (macOS `osascript`, Linux `notify-send`) |
|
|
298
|
-
| `swarmdo hooks recipe` | **One-command Claude Code hooks** —
|
|
301
|
+
| `swarmdo hooks recipe` | **One-command Claude Code hooks** — install `notify-done`/`notify-input` (desktop pings), `memory-inject` (relevant memories each prompt), or `comms-inbox` (new mail as context); dry-run by default, idempotent merge that never clobbers your settings |
|
|
299
302
|
| `swarmdo preset` + `init --preset` | **5-tier capability ladder** — `minimal` → `basic`★ → `standard` → `advanced` → `max`; one word instead of dozens of flags |
|
|
300
303
|
| `swarmdo memory export/import -f obsidian` | **Obsidian vault roundtrip** — DB → markdown notes (YAML frontmatter, `[[wikilinks]]` stay live) → edit in Obsidian → sync back, re-embedded; `import --watch` keeps the vault live-synced as you edit |
|
|
301
304
|
| `swarmdo memory backup` / `revectorize` | WAL-safe nightly DB snapshots · repair hash-era vectors |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarmdo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "Swarmdo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
* diff and index load live in ../commands/affected.ts, so it's fixture-testable.
|
|
11
11
|
*/
|
|
12
12
|
import type { CodeIndex } from '../codegraph/codegraph.js';
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Default matcher for test files (Jest/Vitest/Mocha conventions). A file under
|
|
15
|
+
* `__tests__/` counts only if it has a JS/TS extension — matching Jest's default
|
|
16
|
+
* `testMatch` (`**/__tests__/**/*.[jt]s?(x)`); docs/fixtures (`.md`, `.json`)
|
|
17
|
+
* living beside the tests are NOT test files and must not be fed to the runner.
|
|
18
|
+
*/
|
|
14
19
|
export declare function isTestFile(path: string): boolean;
|
|
15
20
|
/** file it resolves to → files that import it (reverse of the import edges). Pure. */
|
|
16
21
|
export declare function reverseDeps(index: CodeIndex): Map<string, string[]>;
|
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
* closure). Pure — CodeIndex + changed paths in, affected/tests out; the git
|
|
10
10
|
* diff and index load live in ../commands/affected.ts, so it's fixture-testable.
|
|
11
11
|
*/
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Default matcher for test files (Jest/Vitest/Mocha conventions). A file under
|
|
14
|
+
* `__tests__/` counts only if it has a JS/TS extension — matching Jest's default
|
|
15
|
+
* `testMatch` (`**/__tests__/**/*.[jt]s?(x)`); docs/fixtures (`.md`, `.json`)
|
|
16
|
+
* living beside the tests are NOT test files and must not be fed to the runner.
|
|
17
|
+
*/
|
|
13
18
|
export function isTestFile(path) {
|
|
14
|
-
return /(^|\/)__tests__
|
|
19
|
+
return /(^|\/)__tests__\/.*\.[cm]?[jt]sx?$/.test(path) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(path);
|
|
15
20
|
}
|
|
16
21
|
/** file it resolves to → files that import it (reverse of the import edges). Pure. */
|
|
17
22
|
export function reverseDeps(index) {
|
|
@@ -184,16 +184,27 @@ export function applyPatch(source, patch, opts = {}) {
|
|
|
184
184
|
// matched (trimmed) block against the current lines BEFORE splicing.
|
|
185
185
|
const matchedBlock = oldBlock.slice(trimLead, oldBlock.length - trimTail);
|
|
186
186
|
const ambiguous = countMatches(matchLines, matchedBlock) > 1;
|
|
187
|
-
// Splice
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
// Splice ONLY the matched middle. When fuzz trimmed leading/trailing context
|
|
188
|
+
// (trimLead/trimTail > 0) the FULL block matched nowhere, so those trimmed
|
|
189
|
+
// context lines are unverified anchors — the real source at those positions
|
|
190
|
+
// may differ (drift). Splicing the whole oldBlock.length at `placed` would
|
|
191
|
+
// delete a drifted-but-real line and rewrite it with the hunk's stale context
|
|
192
|
+
// copy — silent data loss on a line the hunk never marked to change. So splice
|
|
193
|
+
// from the matched position (placed + trimLead) over just the matched span,
|
|
194
|
+
// inserting newBlock minus the same lead/tail (context is identical on both
|
|
195
|
+
// sides), leaving the anchor lines exactly as the source has them. For a
|
|
196
|
+
// non-fuzzy hunk (trimLead = trimTail = 0) this is byte-identical to before.
|
|
197
|
+
const removeAt = placed + trimLead;
|
|
198
|
+
const removeCount = oldBlock.length - trimLead - trimTail;
|
|
199
|
+
const insert = newBlock.slice(trimLead, newBlock.length - trimTail);
|
|
200
|
+
const insertRaw = dominantCrlf ? insert.map((l) => l + '\r') : insert;
|
|
201
|
+
lines.splice(removeAt, removeCount, ...insertRaw);
|
|
202
|
+
matchLines.splice(removeAt, removeCount, ...insert);
|
|
203
|
+
offset += insert.length - removeCount; // === newBlock.length - oldBlock.length
|
|
193
204
|
results.push({ hunk, applied: true, at: placed, fuzzUsed: usedFuzz, ...(ambiguous && { ambiguous: true }) });
|
|
194
205
|
// If this hunk's new content is now the tail of the file, its markers
|
|
195
206
|
// determine whether the file ends with a newline.
|
|
196
|
-
if (
|
|
207
|
+
if (removeAt + insert.length === lines.length)
|
|
197
208
|
eofNoEol = newSideEndsNoEol(hunk);
|
|
198
209
|
}
|
|
199
210
|
let result = lines.join('\n');
|
|
@@ -44,7 +44,10 @@ export function parseCommit(hash, subject, body = '') {
|
|
|
44
44
|
hash,
|
|
45
45
|
type: 'revert',
|
|
46
46
|
scope: inner ? inner[2] ?? null : null,
|
|
47
|
-
breaking
|
|
47
|
+
// A revert of a breaking commit is itself breaking — mirror the non-revert
|
|
48
|
+
// branch and consult the reverted subject's `!` marker (inner group 3),
|
|
49
|
+
// not just a BREAKING CHANGE footer on the revert body.
|
|
50
|
+
breaking: (inner ? inner[3] === '!' : false) || BREAKING_RE.test(body),
|
|
48
51
|
subject: inner ? inner[4].trim() : rev[1].trim(),
|
|
49
52
|
raw: trimmed,
|
|
50
53
|
};
|
|
@@ -37,6 +37,7 @@ const MATCHERS = [
|
|
|
37
37
|
function declaratorNames(decl) {
|
|
38
38
|
const names = [];
|
|
39
39
|
let depth = 0;
|
|
40
|
+
let angle = 0; // generic type-argument depth: `Map<string, number>`
|
|
40
41
|
let start = 0;
|
|
41
42
|
let str = null;
|
|
42
43
|
const take = (seg) => {
|
|
@@ -57,7 +58,16 @@ function declaratorNames(decl) {
|
|
|
57
58
|
depth++;
|
|
58
59
|
else if (ch === ')' || ch === ']' || ch === '}')
|
|
59
60
|
depth--;
|
|
60
|
-
|
|
61
|
+
// Generic type-args: a comma inside `Map<string, number>` must not split the
|
|
62
|
+
// declarator list. Only treat `<` as an opener when it directly follows an
|
|
63
|
+
// identifier char (Map<, Record<, Array<) — so a spaced comparison `a < b`
|
|
64
|
+
// isn't misread as a generic — and exclude `<<`/`<=`. Close on `>`, skipping
|
|
65
|
+
// the `>` of an arrow `=>` so `Record<string, () => void>` stays balanced.
|
|
66
|
+
else if (ch === '<' && i > 0 && /[\w$]/.test(decl[i - 1]) && decl[i - 1] !== '<' && decl[i + 1] !== '<' && decl[i + 1] !== '=')
|
|
67
|
+
angle++;
|
|
68
|
+
else if (ch === '>' && angle > 0 && decl[i - 1] !== '=')
|
|
69
|
+
angle--;
|
|
70
|
+
else if (ch === ',' && depth === 0 && angle === 0) {
|
|
61
71
|
take(decl.slice(start, i));
|
|
62
72
|
start = i + 1;
|
|
63
73
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* comms.ts — `swarmdo comms` — a cross-session agent mailbox (#44).
|
|
3
|
+
*
|
|
4
|
+
* swarmdo comms send -t <session> -m "<message>" leave a message for a session
|
|
5
|
+
* swarmdo comms inbox [-t <self>] [-u] list your messages (newest first)
|
|
6
|
+
* swarmdo comms read <id> show a full message + mark it read
|
|
7
|
+
*
|
|
8
|
+
* Any Claude Code session on the same repo shares `.swarmdo/comms/store.json`, so
|
|
9
|
+
* one session can message another by name ("multiplayer swarms"). Address `all`
|
|
10
|
+
* to broadcast. The message model, filtering, and retention live in the pure
|
|
11
|
+
* comms/mailbox.ts engine; this layer is fs persistence + presentation.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from '../types.js';
|
|
14
|
+
export declare const commsCommand: Command;
|
|
15
|
+
export default commsCommand;
|
|
16
|
+
//# sourceMappingURL=comms.d.ts.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* comms.ts — `swarmdo comms` — a cross-session agent mailbox (#44).
|
|
3
|
+
*
|
|
4
|
+
* swarmdo comms send -t <session> -m "<message>" leave a message for a session
|
|
5
|
+
* swarmdo comms inbox [-t <self>] [-u] list your messages (newest first)
|
|
6
|
+
* swarmdo comms read <id> show a full message + mark it read
|
|
7
|
+
*
|
|
8
|
+
* Any Claude Code session on the same repo shares `.swarmdo/comms/store.json`, so
|
|
9
|
+
* one session can message another by name ("multiplayer swarms"). Address `all`
|
|
10
|
+
* to broadcast. The message model, filtering, and retention live in the pure
|
|
11
|
+
* comms/mailbox.ts engine; this layer is fs persistence + presentation.
|
|
12
|
+
*/
|
|
13
|
+
import { output } from '../output.js';
|
|
14
|
+
import { createMessage, addMessage, filterInbox, unreadCount, markRead, pruneMailbox, renderInboxContext, } from '../comms/mailbox.js';
|
|
15
|
+
import { loadMailbox, saveMailbox, resolveSelf, newMessageId, RETENTION } from '../comms/store.js';
|
|
16
|
+
/** Compact relative age, e.g. "3m", "2h", "5d", or "now". */
|
|
17
|
+
function relAge(iso, nowMs) {
|
|
18
|
+
const ms = nowMs - Date.parse(iso);
|
|
19
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
20
|
+
return '—';
|
|
21
|
+
const s = Math.floor(ms / 1000);
|
|
22
|
+
if (s < 60)
|
|
23
|
+
return 'now';
|
|
24
|
+
const m = Math.floor(s / 60);
|
|
25
|
+
if (m < 60)
|
|
26
|
+
return `${m}m`;
|
|
27
|
+
const h = Math.floor(m / 60);
|
|
28
|
+
if (h < 24)
|
|
29
|
+
return `${h}h`;
|
|
30
|
+
return `${Math.floor(h / 24)}d`;
|
|
31
|
+
}
|
|
32
|
+
const sendCommand = {
|
|
33
|
+
name: 'send',
|
|
34
|
+
description: 'Send a message to another session (address "all" to broadcast)',
|
|
35
|
+
options: [
|
|
36
|
+
{ name: 'to', short: 't', description: 'Recipient session name (or "all")', type: 'string' },
|
|
37
|
+
{ name: 'message', short: 'm', description: 'Message body', type: 'string' },
|
|
38
|
+
{ name: 'subject', short: 's', description: 'Subject line', type: 'string' },
|
|
39
|
+
{ name: 'from', short: 'f', description: 'Sender name (default: this session)', type: 'string' },
|
|
40
|
+
{ name: 'json', description: 'Machine-readable output', type: 'boolean' },
|
|
41
|
+
],
|
|
42
|
+
examples: [
|
|
43
|
+
{ command: 'swarmdo comms send -t reviewer -m "auth PR is ready" -s "PR #42"', description: 'Message the "reviewer" session' },
|
|
44
|
+
{ command: 'swarmdo comms send -t all -m "rebasing main in 5min"', description: 'Broadcast to every session' },
|
|
45
|
+
],
|
|
46
|
+
action: async (ctx) => {
|
|
47
|
+
const cwd = ctx.cwd || process.cwd();
|
|
48
|
+
const to = (ctx.flags.to || ctx.args[0] || '').trim();
|
|
49
|
+
const body = (ctx.flags.message ?? ctx.args[1] ?? '').toString();
|
|
50
|
+
if (!to) {
|
|
51
|
+
output.printError('Recipient required. Use --to/-t <session> (or "all").');
|
|
52
|
+
return { success: false, exitCode: 1 };
|
|
53
|
+
}
|
|
54
|
+
if (!body.trim()) {
|
|
55
|
+
output.printError('Message body required. Use --message/-m "<text>".');
|
|
56
|
+
return { success: false, exitCode: 1 };
|
|
57
|
+
}
|
|
58
|
+
const from = resolveSelf(ctx.flags.from);
|
|
59
|
+
const msg = createMessage({
|
|
60
|
+
id: newMessageId(),
|
|
61
|
+
from,
|
|
62
|
+
to,
|
|
63
|
+
subject: ctx.flags.subject,
|
|
64
|
+
body,
|
|
65
|
+
createdAt: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
let box = addMessage(loadMailbox(cwd), msg);
|
|
68
|
+
box = pruneMailbox(box, { ...RETENTION, nowMs: Date.now() });
|
|
69
|
+
saveMailbox(cwd, box);
|
|
70
|
+
if (ctx.flags.json === true) {
|
|
71
|
+
output.printJson({ sent: true, id: msg.id, to: msg.to, from: msg.from });
|
|
72
|
+
return { success: true, data: { id: msg.id } };
|
|
73
|
+
}
|
|
74
|
+
output.printSuccess(`Sent to ${output.highlight(msg.to)} — id ${output.dim(msg.id)}`);
|
|
75
|
+
return { success: true, data: { id: msg.id } };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const inboxCommand = {
|
|
79
|
+
name: 'inbox',
|
|
80
|
+
aliases: ['list'],
|
|
81
|
+
description: 'List messages addressed to you (newest first)',
|
|
82
|
+
options: [
|
|
83
|
+
{ name: 'to', short: 't', description: 'Whose inbox (default: this session)', type: 'string' },
|
|
84
|
+
{ name: 'from', short: 'f', description: 'Only messages from this sender', type: 'string' },
|
|
85
|
+
{ name: 'unread', short: 'u', description: 'Only unread messages', type: 'boolean' },
|
|
86
|
+
{ name: 'since', description: 'Only messages after this ISO timestamp', type: 'string' },
|
|
87
|
+
{ name: 'mark-read', description: 'Mark all shown messages as read', type: 'boolean' },
|
|
88
|
+
{ name: 'hook', description: 'Emit unread messages as UserPromptSubmit additionalContext JSON, then mark them read (for hook delivery)', type: 'boolean' },
|
|
89
|
+
{ name: 'json', description: 'Machine-readable output', type: 'boolean' },
|
|
90
|
+
],
|
|
91
|
+
examples: [
|
|
92
|
+
{ command: 'swarmdo comms inbox', description: 'Your unread + read messages' },
|
|
93
|
+
{ command: 'swarmdo comms inbox -u --mark-read', description: 'Show unread, then mark them read' },
|
|
94
|
+
],
|
|
95
|
+
action: async (ctx) => {
|
|
96
|
+
const cwd = ctx.cwd || process.cwd();
|
|
97
|
+
const self = resolveSelf(ctx.flags.to);
|
|
98
|
+
// --hook: delivery path. Emit unread mail as UserPromptSubmit additionalContext
|
|
99
|
+
// and mark it read so it surfaces once, without polling. Hook-safe: NEVER
|
|
100
|
+
// errors (that would break the prompt) and stays silent when there's no mail.
|
|
101
|
+
if (ctx.flags.hook === true) {
|
|
102
|
+
try {
|
|
103
|
+
if (process.env.SWARMDO_COMMS_DISABLE === '1')
|
|
104
|
+
return { success: true };
|
|
105
|
+
const box = loadMailbox(cwd);
|
|
106
|
+
const unread = filterInbox(box, { to: self, unreadOnly: true });
|
|
107
|
+
if (unread.length > 0) {
|
|
108
|
+
const block = renderInboxContext(unread);
|
|
109
|
+
process.stdout.write(JSON.stringify({
|
|
110
|
+
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: block },
|
|
111
|
+
}) + '\n');
|
|
112
|
+
const { box: next, marked } = markRead(box, unread.map((m) => m.id));
|
|
113
|
+
if (marked > 0)
|
|
114
|
+
saveMailbox(cwd, next);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* never fail a hook */
|
|
119
|
+
}
|
|
120
|
+
return { success: true };
|
|
121
|
+
}
|
|
122
|
+
const box = loadMailbox(cwd);
|
|
123
|
+
const inbox = filterInbox(box, {
|
|
124
|
+
to: self,
|
|
125
|
+
from: ctx.flags.from,
|
|
126
|
+
unreadOnly: ctx.flags.unread === true,
|
|
127
|
+
since: ctx.flags.since,
|
|
128
|
+
});
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
if (ctx.flags.json === true) {
|
|
131
|
+
output.printJson({ to: self, unread: unreadCount(box, self), messages: inbox });
|
|
132
|
+
}
|
|
133
|
+
else if (inbox.length === 0) {
|
|
134
|
+
output.printInfo(`No messages for ${output.highlight(self)}.`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
output.writeln(output.bold(`Inbox: ${self}`) + output.dim(` (${unreadCount(box, self)} unread)`));
|
|
138
|
+
output.printTable({
|
|
139
|
+
columns: [
|
|
140
|
+
{ key: 'flag', header: '', width: 2 },
|
|
141
|
+
{ key: 'id', header: 'ID', width: 14 },
|
|
142
|
+
{ key: 'from', header: 'From', width: 16 },
|
|
143
|
+
{ key: 'subject', header: 'Subject', width: 34 },
|
|
144
|
+
{ key: 'age', header: 'Age', width: 6, align: 'right' },
|
|
145
|
+
],
|
|
146
|
+
data: inbox.map((m) => ({ flag: m.read ? ' ' : '●', id: m.id, from: m.from, subject: m.subject, age: relAge(m.createdAt, now) })),
|
|
147
|
+
});
|
|
148
|
+
output.writeln(output.dim('● = unread · read a message: swarmdo comms read <id>'));
|
|
149
|
+
}
|
|
150
|
+
if (ctx.flags['mark-read'] === true && inbox.length > 0) {
|
|
151
|
+
const { box: next, marked } = markRead(box, inbox.map((m) => m.id));
|
|
152
|
+
if (marked > 0)
|
|
153
|
+
saveMailbox(cwd, next);
|
|
154
|
+
if (ctx.flags.json !== true)
|
|
155
|
+
output.printInfo(`Marked ${marked} read.`);
|
|
156
|
+
}
|
|
157
|
+
return { success: true, data: { to: self, count: inbox.length } };
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const readCommand = {
|
|
161
|
+
name: 'read',
|
|
162
|
+
description: 'Show a full message and mark it read',
|
|
163
|
+
options: [{ name: 'json', description: 'Machine-readable output', type: 'boolean' }],
|
|
164
|
+
examples: [{ command: 'swarmdo comms read msg_ab12cd34ef56', description: 'Open a message by id' }],
|
|
165
|
+
action: async (ctx) => {
|
|
166
|
+
const cwd = ctx.cwd || process.cwd();
|
|
167
|
+
const id = (ctx.args[0] || '').trim();
|
|
168
|
+
if (!id) {
|
|
169
|
+
output.printError('Message id required: swarmdo comms read <id>');
|
|
170
|
+
return { success: false, exitCode: 1 };
|
|
171
|
+
}
|
|
172
|
+
const box = loadMailbox(cwd);
|
|
173
|
+
const m = box.messages[id];
|
|
174
|
+
if (!m) {
|
|
175
|
+
output.printError(`No message with id ${id}.`);
|
|
176
|
+
return { success: false, exitCode: 1 };
|
|
177
|
+
}
|
|
178
|
+
const { box: next, marked } = markRead(box, [id]);
|
|
179
|
+
if (marked > 0)
|
|
180
|
+
saveMailbox(cwd, next);
|
|
181
|
+
if (ctx.flags.json === true) {
|
|
182
|
+
output.printJson({ ...m, read: true });
|
|
183
|
+
return { success: true, data: { id } };
|
|
184
|
+
}
|
|
185
|
+
output.writeln();
|
|
186
|
+
output.printList([
|
|
187
|
+
`${output.bold('From')}: ${m.from}`,
|
|
188
|
+
`${output.bold('To')}: ${m.to}`,
|
|
189
|
+
`${output.bold('Subject')}: ${m.subject}`,
|
|
190
|
+
`${output.bold('Sent')}: ${m.createdAt}`,
|
|
191
|
+
]);
|
|
192
|
+
output.writeln();
|
|
193
|
+
output.writeln(m.body);
|
|
194
|
+
output.writeln();
|
|
195
|
+
return { success: true, data: { id } };
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
const watchCommand = {
|
|
199
|
+
name: 'watch',
|
|
200
|
+
description: 'Live-tail your inbox — print new messages as they arrive (Ctrl-C to stop)',
|
|
201
|
+
options: [
|
|
202
|
+
{ name: 'to', short: 't', description: 'Whose inbox (default: this session)', type: 'string' },
|
|
203
|
+
{ name: 'from', short: 'f', description: 'Only messages from this sender', type: 'string' },
|
|
204
|
+
{ name: 'interval', short: 'i', description: 'Poll interval in seconds', type: 'number', default: 5 },
|
|
205
|
+
],
|
|
206
|
+
examples: [
|
|
207
|
+
{ command: 'swarmdo comms watch', description: 'Live-tail your inbox' },
|
|
208
|
+
{ command: 'swarmdo comms watch -i 2', description: 'Poll every 2 seconds' },
|
|
209
|
+
],
|
|
210
|
+
action: async (ctx) => {
|
|
211
|
+
const cwd = ctx.cwd || process.cwd();
|
|
212
|
+
const self = resolveSelf(ctx.flags.to);
|
|
213
|
+
const from = ctx.flags.from;
|
|
214
|
+
const intervalMs = Math.max(1, Number(ctx.flags.interval ?? 5)) * 1000;
|
|
215
|
+
// Only surface messages that arrive AFTER watch starts.
|
|
216
|
+
let cursor = new Date().toISOString();
|
|
217
|
+
output.printInfo(`Watching ${output.highlight(self)}'s inbox every ${intervalMs / 1000}s (Ctrl-C to stop)…`);
|
|
218
|
+
return await new Promise((resolve) => {
|
|
219
|
+
const tick = () => {
|
|
220
|
+
try {
|
|
221
|
+
const box = loadMailbox(cwd);
|
|
222
|
+
// filterInbox is newest-first; reverse to print in arrival order.
|
|
223
|
+
const fresh = filterInbox(box, { to: self, from, since: cursor }).reverse();
|
|
224
|
+
for (const m of fresh) {
|
|
225
|
+
output.writeln(`${output.dim(new Date(m.createdAt).toLocaleTimeString())} ${output.bold(m.from)} › ${m.subject}: ${m.body.replace(/\s+/g, ' ').trim()}`);
|
|
226
|
+
if (m.createdAt > cursor)
|
|
227
|
+
cursor = m.createdAt;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* keep watching despite a transient read error */
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const timer = setInterval(tick, intervalMs);
|
|
235
|
+
const stop = () => {
|
|
236
|
+
clearInterval(timer);
|
|
237
|
+
output.writeln();
|
|
238
|
+
output.printInfo('Stopped watching.');
|
|
239
|
+
resolve({ success: true });
|
|
240
|
+
};
|
|
241
|
+
process.once('SIGINT', stop);
|
|
242
|
+
process.once('SIGTERM', stop);
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
export const commsCommand = {
|
|
247
|
+
name: 'comms',
|
|
248
|
+
aliases: ['mailbox'],
|
|
249
|
+
description: 'Cross-session agent mailbox — message another session by name',
|
|
250
|
+
subcommands: [sendCommand, inboxCommand, readCommand, watchCommand],
|
|
251
|
+
options: [],
|
|
252
|
+
examples: [
|
|
253
|
+
{ command: 'swarmdo comms send -t reviewer -m "PR ready"', description: 'Send a message' },
|
|
254
|
+
{ command: 'swarmdo comms inbox -u', description: 'List your unread messages' },
|
|
255
|
+
{ command: 'swarmdo comms read <id>', description: 'Open a message' },
|
|
256
|
+
],
|
|
257
|
+
action: async (ctx) => {
|
|
258
|
+
const cwd = ctx.cwd || process.cwd();
|
|
259
|
+
const self = resolveSelf();
|
|
260
|
+
const box = loadMailbox(cwd);
|
|
261
|
+
const unread = unreadCount(box, self);
|
|
262
|
+
output.writeln();
|
|
263
|
+
output.writeln(output.bold('Cross-session agent mailbox'));
|
|
264
|
+
output.writeln();
|
|
265
|
+
output.writeln(`You are ${output.highlight(self)} — ${unread} unread message${unread === 1 ? '' : 's'}.`);
|
|
266
|
+
output.writeln();
|
|
267
|
+
output.writeln('Usage: swarmdo comms <send|inbox|read|watch> [options]');
|
|
268
|
+
output.printList([
|
|
269
|
+
`${output.highlight('send')} - Send a message (\`-t <session> -m "…"\`, \`-t all\` to broadcast)`,
|
|
270
|
+
`${output.highlight('inbox')} - List your messages (\`-u\` unread, \`--mark-read\`, \`--hook\`)`,
|
|
271
|
+
`${output.highlight('read')} - Show a full message by id and mark it read`,
|
|
272
|
+
`${output.highlight('watch')} - Live-tail your inbox for new messages`,
|
|
273
|
+
]);
|
|
274
|
+
output.writeln();
|
|
275
|
+
output.writeln(output.dim('Sessions on the same repo share .swarmdo/comms/. Name yourself with $SWARMDO_SESSION.'));
|
|
276
|
+
return { success: true, data: { self, unread } };
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
export default commsCommand;
|
|
280
|
+
//# sourceMappingURL=comms.js.map
|
|
@@ -34,8 +34,12 @@ function optsFromFlags(ctx) {
|
|
|
34
34
|
};
|
|
35
35
|
if (typeof ctx.flags['min-run'] === 'number')
|
|
36
36
|
opts.minRun = ctx.flags['min-run'];
|
|
37
|
-
else if (typeof ctx.flags['min-run'] === 'string')
|
|
38
|
-
|
|
37
|
+
else if (typeof ctx.flags['min-run'] === 'string') {
|
|
38
|
+
// Explicit NaN check, NOT `|| 3` — the documented `--min-run 0` ("disables")
|
|
39
|
+
// is falsy and would otherwise be silently coerced back to the default 3.
|
|
40
|
+
const parsed = parseInt(ctx.flags['min-run'], 10);
|
|
41
|
+
opts.minRun = Number.isNaN(parsed) ? 3 : parsed;
|
|
42
|
+
}
|
|
39
43
|
const win = ctx.flags.window;
|
|
40
44
|
if (typeof win === 'string' && /^\d+:\d+$/.test(win)) {
|
|
41
45
|
const [head, tail] = win.split(':').map((n) => parseInt(n, 10));
|
|
@@ -438,6 +438,7 @@ const lintCommand = {
|
|
|
438
438
|
settingsFiles: [read('.claude/settings.json'), read('.claude/settings.local.json')],
|
|
439
439
|
mcpConfig: read('.mcp.json'),
|
|
440
440
|
commandsRoot: list('.claude/commands'),
|
|
441
|
+
sdoCommands: list('.claude/commands/sDo'),
|
|
441
442
|
skills: list('.claude/skills'),
|
|
442
443
|
});
|
|
443
444
|
const failed = report.errors > 0 || (ctx.flags.strict === true && report.warnings > 0);
|
|
@@ -8,6 +8,8 @@ import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
|
8
8
|
import { storeCommand } from './transfer-store.js';
|
|
9
9
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
+
import { bridgeSearchEntries } from '../memory/memory-bridge.js';
|
|
12
|
+
import { selectInjectionMemories, mapSearchResultsToCandidates, extractPromptFromPayload, } from '../memory-inject/select.js';
|
|
11
13
|
/**
|
|
12
14
|
* #1686 — `?? 0` only defaults null/undefined; NaN slips through and
|
|
13
15
|
* surfaces as `"NaN"` (or earlier crashed `.toFixed`) in the metrics
|
|
@@ -731,6 +733,122 @@ const routeCommand = {
|
|
|
731
733
|
}
|
|
732
734
|
}
|
|
733
735
|
};
|
|
736
|
+
/**
|
|
737
|
+
* Read a Claude Code hook payload from stdin (JSON on UserPromptSubmit), with a
|
|
738
|
+
* short timeout so it never hangs when stdin isn't closed (a known Windows
|
|
739
|
+
* failure mode). Returns '' for a TTY or on any error — the caller then simply
|
|
740
|
+
* injects nothing.
|
|
741
|
+
*/
|
|
742
|
+
async function readHookStdin(timeoutMs = 1500) {
|
|
743
|
+
if (process.stdin.isTTY)
|
|
744
|
+
return '';
|
|
745
|
+
return new Promise((resolve) => {
|
|
746
|
+
let data = '';
|
|
747
|
+
const finish = (v) => {
|
|
748
|
+
try {
|
|
749
|
+
process.stdin.pause();
|
|
750
|
+
}
|
|
751
|
+
catch { /* noop */ }
|
|
752
|
+
resolve(v);
|
|
753
|
+
};
|
|
754
|
+
const timer = setTimeout(() => finish(data), timeoutMs);
|
|
755
|
+
try {
|
|
756
|
+
process.stdin.setEncoding('utf8');
|
|
757
|
+
process.stdin.on('data', (c) => { data += c; });
|
|
758
|
+
process.stdin.on('end', () => { clearTimeout(timer); finish(data); });
|
|
759
|
+
process.stdin.on('error', () => { clearTimeout(timer); finish(data); });
|
|
760
|
+
process.stdin.resume();
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
clearTimeout(timer);
|
|
764
|
+
finish('');
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const DEFAULT_INJECT_NAMESPACES = ['claude-memories', 'auto-memory', 'patterns'];
|
|
769
|
+
/**
|
|
770
|
+
* memory-inject — the #43 prompt-time semantic memory injection hook. Embeds the
|
|
771
|
+
* submitted prompt, vector-searches AgentDB across the memory namespaces, and
|
|
772
|
+
* emits the most relevant stored memories as UserPromptSubmit `additionalContext`
|
|
773
|
+
* under a strict token budget. Wired for the hook path: it NEVER errors out
|
|
774
|
+
* (that would break the user's prompt) and stays silent when nothing is relevant.
|
|
775
|
+
*/
|
|
776
|
+
const memoryInjectCommand = {
|
|
777
|
+
name: 'memory-inject',
|
|
778
|
+
description: 'Inject relevant stored memories into the prompt (UserPromptSubmit hook)',
|
|
779
|
+
options: [
|
|
780
|
+
{ name: 'prompt', short: 'p', description: 'Prompt text (else read the hook JSON from stdin)', type: 'string' },
|
|
781
|
+
{ name: 'namespaces', description: 'Comma-separated namespaces to search', type: 'string', default: DEFAULT_INJECT_NAMESPACES.join(',') },
|
|
782
|
+
{ name: 'budget', description: 'Max tokens of injected context', type: 'number', default: 800 },
|
|
783
|
+
{ name: 'min-relevance', description: 'Minimum relevance score (0..1)', type: 'number', default: 0.35 },
|
|
784
|
+
{ name: 'top-k', short: 'K', description: 'Max memories to inject', type: 'number', default: 5 },
|
|
785
|
+
{ name: 'limit', description: 'Max search results to rank', type: 'number', default: 20 },
|
|
786
|
+
{ name: 'preview', description: 'Print the block for humans instead of emitting hook JSON', type: 'boolean' },
|
|
787
|
+
{ name: 'db', description: 'Path to the memory database', type: 'string' },
|
|
788
|
+
],
|
|
789
|
+
examples: [
|
|
790
|
+
{ command: 'swarmdo hooks memory-inject -p "refactor the auth module" --preview', description: 'Preview relevant memories for a prompt' },
|
|
791
|
+
{ command: "echo '{\"prompt\":\"fix bug\"}' | swarmdo hooks memory-inject", description: 'Run as a UserPromptSubmit hook' },
|
|
792
|
+
],
|
|
793
|
+
action: async (ctx) => {
|
|
794
|
+
const preview = Boolean(ctx.flags.preview);
|
|
795
|
+
// A hook must NEVER break the user's prompt: swallow every error and emit nothing.
|
|
796
|
+
try {
|
|
797
|
+
if (process.env.SWARMDO_MEMORY_INJECT_DISABLE === '1')
|
|
798
|
+
return { success: true };
|
|
799
|
+
let prompt = (ctx.flags.prompt || ctx.args.join(' ')).trim();
|
|
800
|
+
if (!prompt)
|
|
801
|
+
prompt = extractPromptFromPayload(await readHookStdin());
|
|
802
|
+
if (!prompt)
|
|
803
|
+
return { success: true }; // nothing to search on
|
|
804
|
+
const namespaces = String(ctx.flags.namespaces ?? DEFAULT_INJECT_NAMESPACES.join(','))
|
|
805
|
+
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
806
|
+
const minRelevance = ctx.flags.minRelevance ?? 0.35;
|
|
807
|
+
const topK = ctx.flags.topK ?? 5;
|
|
808
|
+
const limit = Math.max(ctx.flags.limit ?? 20, topK);
|
|
809
|
+
const search = await bridgeSearchEntries({
|
|
810
|
+
query: prompt,
|
|
811
|
+
namespace: 'all',
|
|
812
|
+
limit,
|
|
813
|
+
threshold: minRelevance,
|
|
814
|
+
fullContent: true,
|
|
815
|
+
dbPath: ctx.flags.db,
|
|
816
|
+
});
|
|
817
|
+
const rows = (search && search.success ? search.results : []);
|
|
818
|
+
const candidates = mapSearchResultsToCandidates(rows, namespaces);
|
|
819
|
+
const result = selectInjectionMemories(candidates, {
|
|
820
|
+
budgetTokens: ctx.flags.budget ?? 800,
|
|
821
|
+
minRelevance,
|
|
822
|
+
topK,
|
|
823
|
+
});
|
|
824
|
+
if (preview) {
|
|
825
|
+
if (!result.block) {
|
|
826
|
+
output.printInfo('No relevant memories to inject.');
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
output.writeln(result.block);
|
|
830
|
+
output.writeln();
|
|
831
|
+
output.printInfo(`Injected ${result.used.length} memor${result.used.length === 1 ? 'y' : 'ies'} ` +
|
|
832
|
+
`(~${result.tokensUsed} tokens${result.skipped ? `, ${result.skipped} skipped` : ''}).`);
|
|
833
|
+
}
|
|
834
|
+
return { success: true, data: { used: result.used.length, skipped: result.skipped, tokensUsed: result.tokensUsed } };
|
|
835
|
+
}
|
|
836
|
+
// Hook mode: emit additionalContext JSON ONLY when there's something to add,
|
|
837
|
+
// so an irrelevant prompt leaves Claude's context untouched.
|
|
838
|
+
if (result.block) {
|
|
839
|
+
process.stdout.write(JSON.stringify({
|
|
840
|
+
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: result.block },
|
|
841
|
+
}) + '\n');
|
|
842
|
+
}
|
|
843
|
+
return { success: true };
|
|
844
|
+
}
|
|
845
|
+
catch (err) {
|
|
846
|
+
if (preview)
|
|
847
|
+
output.printError(`Memory injection failed: ${String(err)}`);
|
|
848
|
+
return { success: true }; // never fail the hook
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
};
|
|
734
852
|
// Explain subcommand
|
|
735
853
|
const explainCommand = {
|
|
736
854
|
name: 'explain',
|
|
@@ -4447,7 +4565,7 @@ const notifyCommand = {
|
|
|
4447
4565
|
const recipeCommand = {
|
|
4448
4566
|
name: 'recipe',
|
|
4449
4567
|
aliases: ['recipes'],
|
|
4450
|
-
description: 'Install ready-made Claude Code hook recipes (desktop notify) into settings — dry-run unless --apply',
|
|
4568
|
+
description: 'Install ready-made Claude Code hook recipes (desktop notify, memory injection) into settings — dry-run unless --apply',
|
|
4451
4569
|
options: [
|
|
4452
4570
|
{ name: 'apply', type: 'boolean', description: 'write the change (default: preview only)', default: false },
|
|
4453
4571
|
{ name: 'shared', type: 'boolean', description: 'target .claude/settings.json (shared/committed) instead of settings.local.json', default: false },
|
|
@@ -4553,6 +4671,7 @@ export const hooksCommand = {
|
|
|
4553
4671
|
sessionEndCommand,
|
|
4554
4672
|
sessionRestoreCommand,
|
|
4555
4673
|
routeCommand,
|
|
4674
|
+
memoryInjectCommand,
|
|
4556
4675
|
explainCommand,
|
|
4557
4676
|
pretrainCommand,
|
|
4558
4677
|
buildAgentsCommand,
|
|
@@ -4610,6 +4729,7 @@ export const hooksCommand = {
|
|
|
4610
4729
|
`${output.highlight('session-end')} - End current session and persist state`,
|
|
4611
4730
|
`${output.highlight('session-restore')} - Restore a previous session`,
|
|
4612
4731
|
`${output.highlight('route')} - Route tasks to optimal agents`,
|
|
4732
|
+
`${output.highlight('memory-inject')} - Inject relevant memories into the prompt (UserPromptSubmit)`,
|
|
4613
4733
|
`${output.highlight('explain')} - Explain routing decisions`,
|
|
4614
4734
|
`${output.highlight('pretrain')} - Bootstrap intelligence from repository`,
|
|
4615
4735
|
`${output.highlight('build-agents')} - Generate optimized agent configs`,
|