swarmdo 1.29.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/.claude/helpers/auto-memory-hook.mjs +66 -33
- package/README.md +6 -3
- package/package.json +1 -1
- package/v3/@swarmdo/cli/.claude/helpers/auto-memory-hook.mjs +65 -32
- 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 +208 -3
- 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/limits.d.ts +77 -0
- package/v3/@swarmdo/cli/dist/src/usage/limits.js +134 -0
- 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.d.ts +23 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect-html.js +95 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect.d.ts +131 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect.js +209 -0
- package/v3/@swarmdo/cli/dist/src/usage/spend-forecast.d.ts +28 -0
- package/v3/@swarmdo/cli/dist/src/usage/spend-forecast.js +33 -0
- package/v3/@swarmdo/cli/dist/src/usage/transcript-errors.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/usage/transcript-errors.js +10 -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/csv.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/util/csv.js +20 -0
- 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
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
15
|
import { join, dirname } from 'path';
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
@@ -52,13 +52,30 @@ async function gracefulExit(signal) {
|
|
|
52
52
|
process.on('SIGTERM', () => { gracefulExit('SIGTERM'); });
|
|
53
53
|
process.on('SIGINT', () => { gracefulExit('SIGINT'); });
|
|
54
54
|
|
|
55
|
-
// Ensure data dir
|
|
56
|
-
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
57
|
-
|
|
58
55
|
// ============================================================================
|
|
59
56
|
// Simple JSON File Backend (implements IMemoryBackend interface)
|
|
60
57
|
// ============================================================================
|
|
61
58
|
|
|
59
|
+
// Collapse entries that are the SAME memory — identical content signature but
|
|
60
|
+
// distinct ids — keeping the first occurrence. The auto-memory bridge
|
|
61
|
+
// historically re-imported every session with a fresh random id, bloating the
|
|
62
|
+
// store ~22x (#53); this heals already-bloated stores and de-dupes defensively.
|
|
63
|
+
// Keyed by namespace + content hash (or raw content) so genuinely distinct
|
|
64
|
+
// memories are never merged. Pure.
|
|
65
|
+
function dedupeByContentSignature(entries) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
const ns = (e && e.namespace) || 'default';
|
|
70
|
+
const body = (e && e.metadata && e.metadata.contentHash) || (e && (e.content || e.value)) || '';
|
|
71
|
+
const sig = ns + '\u0001' + body;
|
|
72
|
+
if (seen.has(sig)) continue;
|
|
73
|
+
seen.add(sig);
|
|
74
|
+
out.push(e);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
class JsonFileBackend {
|
|
63
80
|
constructor(filePath) {
|
|
64
81
|
this.filePath = filePath;
|
|
@@ -70,7 +87,12 @@ class JsonFileBackend {
|
|
|
70
87
|
try {
|
|
71
88
|
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
72
89
|
if (Array.isArray(data)) {
|
|
73
|
-
|
|
90
|
+
// Collapse content-duplicates left by the pre-#53 import bug (same
|
|
91
|
+
// memory re-imported each session under a fresh id). Auto-heals an
|
|
92
|
+
// already-bloated store by rewriting it once when dupes are dropped.
|
|
93
|
+
const deduped = dedupeByContentSignature(data);
|
|
94
|
+
for (const entry of deduped) this.entries.set(entry.id, entry);
|
|
95
|
+
if (deduped.length < data.length) this._persist();
|
|
74
96
|
}
|
|
75
97
|
} catch { /* start fresh */ }
|
|
76
98
|
}
|
|
@@ -617,33 +639,44 @@ async function doImportAll() {
|
|
|
617
639
|
// Main
|
|
618
640
|
// ============================================================================
|
|
619
641
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
|
|
625
|
-
//
|
|
626
|
-
|
|
627
|
-
process.
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
642
|
+
// Only dispatch CLI commands when this file is executed directly
|
|
643
|
+
// (`node auto-memory-hook.mjs …`), NOT when it is imported by a test —
|
|
644
|
+
// importing must not run a command or exit the process (#53 regression test
|
|
645
|
+
// imports JsonFileBackend + dedupeByContentSignature below).
|
|
646
|
+
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
|
647
|
+
// Ensure data dir (only when actually running a command, not on import)
|
|
648
|
+
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
649
|
+
const command = process.argv[2] || 'status';
|
|
650
|
+
|
|
651
|
+
// Dynamic import() failures can surface as unhandled rejections on a later
|
|
652
|
+
// microtask even when the awaiting call site already caught them, which would
|
|
653
|
+
// otherwise force a non-zero exit. Swallow to keep hooks exit-0, but surface the
|
|
654
|
+
// reason under SWARMDO_DEBUG/DEBUG so genuine async bugs aren't silently hidden
|
|
655
|
+
// (FIX 2 — the previous `() => {}` discarded every rejection process-wide).
|
|
656
|
+
process.on('unhandledRejection', (reason) => {
|
|
657
|
+
if (DEBUG) {
|
|
658
|
+
const detail = reason && reason.message ? reason.message : String(reason);
|
|
659
|
+
process.stderr.write(`[AutoMemory] unhandledRejection (suppressed): ${detail}\n`);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
try {
|
|
664
|
+
switch (command) {
|
|
665
|
+
case 'import': await doImport(); break;
|
|
666
|
+
case 'import-all': await doImportAll(); break;
|
|
667
|
+
case 'sync': await doSync(); break;
|
|
668
|
+
case 'status': await doStatus(); break;
|
|
669
|
+
default:
|
|
670
|
+
console.log('Usage: auto-memory-hook.mjs <import|sync|status>');
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
} catch (err) {
|
|
674
|
+
// Hooks must never crash Claude Code - fail silently
|
|
675
|
+
dim(`Error (non-critical): ${err.message}`);
|
|
643
676
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
dim(`Error (non-critical): ${err.message}`);
|
|
677
|
+
// Ensure clean exit for Claude Code hooks (exit 0 = success, no stderr = no error)
|
|
678
|
+
process.exit(0);
|
|
647
679
|
}
|
|
648
|
-
|
|
649
|
-
|
|
680
|
+
|
|
681
|
+
// Exported for unit tests (the run-guard above keeps import side-effect-free).
|
|
682
|
+
export { JsonFileBackend, dedupeByContentSignature };
|
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`, `
|
|
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",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
15
|
import { join, dirname } from 'path';
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
@@ -52,13 +52,30 @@ async function gracefulExit(signal) {
|
|
|
52
52
|
process.on('SIGTERM', () => { gracefulExit('SIGTERM'); });
|
|
53
53
|
process.on('SIGINT', () => { gracefulExit('SIGINT'); });
|
|
54
54
|
|
|
55
|
-
// Ensure data dir
|
|
56
|
-
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
57
|
-
|
|
58
55
|
// ============================================================================
|
|
59
56
|
// Simple JSON File Backend (implements IMemoryBackend interface)
|
|
60
57
|
// ============================================================================
|
|
61
58
|
|
|
59
|
+
// Collapse entries that are the SAME memory — identical content signature but
|
|
60
|
+
// distinct ids — keeping the first occurrence. The auto-memory bridge
|
|
61
|
+
// historically re-imported every session with a fresh random id, bloating the
|
|
62
|
+
// store ~22x (#53); this heals already-bloated stores and de-dupes defensively.
|
|
63
|
+
// Keyed by namespace + content hash (or raw content) so genuinely distinct
|
|
64
|
+
// memories are never merged. Pure.
|
|
65
|
+
function dedupeByContentSignature(entries) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
const ns = (e && e.namespace) || 'default';
|
|
70
|
+
const body = (e && e.metadata && e.metadata.contentHash) || (e && (e.content || e.value)) || '';
|
|
71
|
+
const sig = ns + '\u0001' + body;
|
|
72
|
+
if (seen.has(sig)) continue;
|
|
73
|
+
seen.add(sig);
|
|
74
|
+
out.push(e);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
class JsonFileBackend {
|
|
63
80
|
constructor(filePath) {
|
|
64
81
|
this.filePath = filePath;
|
|
@@ -70,7 +87,12 @@ class JsonFileBackend {
|
|
|
70
87
|
try {
|
|
71
88
|
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
72
89
|
if (Array.isArray(data)) {
|
|
73
|
-
|
|
90
|
+
// Collapse content-duplicates left by the pre-#53 import bug (same
|
|
91
|
+
// memory re-imported each session under a fresh id). Auto-heals an
|
|
92
|
+
// already-bloated store by rewriting it once when dupes are dropped.
|
|
93
|
+
const deduped = dedupeByContentSignature(data);
|
|
94
|
+
for (const entry of deduped) this.entries.set(entry.id, entry);
|
|
95
|
+
if (deduped.length < data.length) this._persist();
|
|
74
96
|
}
|
|
75
97
|
} catch { /* start fresh */ }
|
|
76
98
|
}
|
|
@@ -371,32 +393,43 @@ async function doStatus() {
|
|
|
371
393
|
// Main
|
|
372
394
|
// ============================================================================
|
|
373
395
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
process.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
+
// Only dispatch CLI commands when this file is executed directly
|
|
397
|
+
// (`node auto-memory-hook.mjs …`), NOT when it is imported by a test —
|
|
398
|
+
// importing must not run a command or exit the process (#53 regression test
|
|
399
|
+
// imports JsonFileBackend + dedupeByContentSignature below).
|
|
400
|
+
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
|
401
|
+
// Ensure data dir (only when actually running a command, not on import)
|
|
402
|
+
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
403
|
+
const command = process.argv[2] || 'status';
|
|
404
|
+
|
|
405
|
+
// Dynamic import() failures can surface as unhandled rejections on a later
|
|
406
|
+
// microtask even when the awaiting call site already caught them, which would
|
|
407
|
+
// otherwise force a non-zero exit. Swallow to keep hooks exit-0, but surface the
|
|
408
|
+
// reason under SWARMDO_DEBUG/DEBUG so genuine async bugs aren't silently hidden
|
|
409
|
+
// (FIX 2 — the previous `() => {}` discarded every rejection process-wide).
|
|
410
|
+
process.on('unhandledRejection', (reason) => {
|
|
411
|
+
if (DEBUG) {
|
|
412
|
+
const detail = reason && reason.message ? reason.message : String(reason);
|
|
413
|
+
process.stderr.write(`[AutoMemory] unhandledRejection (suppressed): ${detail}\n`);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
switch (command) {
|
|
419
|
+
case 'import': await doImport(); break;
|
|
420
|
+
case 'sync': await doSync(); break;
|
|
421
|
+
case 'status': await doStatus(); break;
|
|
422
|
+
default:
|
|
423
|
+
console.log('Usage: auto-memory-hook.mjs <import|sync|status>');
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
// Hooks must never crash Claude Code - fail silently
|
|
428
|
+
try { dim(`Error (non-critical): ${err.message}`); } catch (_) {}
|
|
396
429
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
try { dim(`Error (non-critical): ${err.message}`); } catch (_) {}
|
|
430
|
+
// Force clean exit — process.exitCode alone isn't enough if async errors override it
|
|
431
|
+
process.exit(0);
|
|
400
432
|
}
|
|
401
|
-
|
|
402
|
-
|
|
433
|
+
|
|
434
|
+
// Exported for unit tests (the run-guard above keeps import side-effect-free).
|
|
435
|
+
export { JsonFileBackend, dedupeByContentSignature };
|
|
@@ -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
|