opencode-swarm-plugin 0.30.6 → 0.31.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/.hive/memories.jsonl +10 -0
- package/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-test.log +339 -339
- package/CHANGELOG.md +103 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -49
- package/dist/memory-tools.d.ts +4 -0
- package/dist/memory-tools.d.ts.map +1 -1
- package/dist/memory.d.ts +2 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/model-selection.d.ts +37 -0
- package/dist/model-selection.d.ts.map +1 -0
- package/dist/plugin.js +241 -49
- package/dist/schemas/task.d.ts +2 -0
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/swarm-decompose.d.ts +8 -8
- package/dist/swarm-decompose.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +11 -7
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm.d.ts +8 -6
- package/dist/swarm.d.ts.map +1 -1
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/package.json +2 -2
- package/src/hive.integration.test.ts +332 -3
- package/src/hive.ts +171 -13
- package/src/memory-tools.ts +5 -1
- package/src/memory.ts +9 -0
- package/src/swarm-decompose.ts +7 -11
- package/src/swarm-orchestrate.ts +27 -1
- package/src/swarm-prompts.test.ts +12 -9
- package/src/swarm-prompts.ts +43 -19
- package/src/swarm.integration.test.ts +74 -4
package/src/hive.ts
CHANGED
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
createHiveAdapter,
|
|
20
20
|
FlushManager,
|
|
21
21
|
importFromJSONL,
|
|
22
|
+
syncMemories,
|
|
22
23
|
type HiveAdapter,
|
|
23
24
|
type Cell as AdapterCell,
|
|
24
25
|
getSwarmMail,
|
|
26
|
+
resolvePartialId,
|
|
25
27
|
} from "swarm-mail";
|
|
26
28
|
import { existsSync, readFileSync } from "node:fs";
|
|
27
29
|
import { join } from "node:path";
|
|
@@ -422,6 +424,78 @@ export async function importJsonlToPGLite(projectPath: string): Promise<{
|
|
|
422
424
|
*/
|
|
423
425
|
const adapterCache = new Map<string, HiveAdapter>();
|
|
424
426
|
|
|
427
|
+
// ============================================================================
|
|
428
|
+
// Process Exit Hook - Safety Net for Dirty Cells
|
|
429
|
+
// ============================================================================
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Track if exit hook is already registered (prevent duplicate registrations)
|
|
433
|
+
*/
|
|
434
|
+
let exitHookRegistered = false;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Track if exit hook is currently running (prevent re-entry)
|
|
438
|
+
*/
|
|
439
|
+
let exitHookRunning = false;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Register process.on('beforeExit') handler to flush dirty cells
|
|
443
|
+
* This is a safety net - catches any dirty cells that weren't explicitly synced
|
|
444
|
+
*
|
|
445
|
+
* Idempotent - safe to call multiple times (only registers once)
|
|
446
|
+
*/
|
|
447
|
+
function registerExitHook(): void {
|
|
448
|
+
if (exitHookRegistered) {
|
|
449
|
+
return; // Already registered
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
exitHookRegistered = true;
|
|
453
|
+
|
|
454
|
+
process.on('beforeExit', async (code) => {
|
|
455
|
+
// Prevent re-entry if already flushing
|
|
456
|
+
if (exitHookRunning) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
exitHookRunning = true;
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
// Flush all projects that have adapters (and potentially dirty cells)
|
|
464
|
+
const flushPromises: Promise<void>[] = [];
|
|
465
|
+
|
|
466
|
+
for (const [projectKey, adapter] of adapterCache.entries()) {
|
|
467
|
+
const flushPromise = (async () => {
|
|
468
|
+
try {
|
|
469
|
+
ensureHiveDirectory(projectKey);
|
|
470
|
+
const flushManager = new FlushManager({
|
|
471
|
+
adapter,
|
|
472
|
+
projectKey,
|
|
473
|
+
outputPath: `${projectKey}/.hive/issues.jsonl`,
|
|
474
|
+
});
|
|
475
|
+
await flushManager.flush();
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// Non-fatal - log and continue
|
|
478
|
+
console.warn(
|
|
479
|
+
`[hive exit hook] Failed to flush ${projectKey}:`,
|
|
480
|
+
error instanceof Error ? error.message : String(error)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
})();
|
|
484
|
+
|
|
485
|
+
flushPromises.push(flushPromise);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Wait for all flushes to complete
|
|
489
|
+
await Promise.all(flushPromises);
|
|
490
|
+
} finally {
|
|
491
|
+
exitHookRunning = false;
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Register exit hook immediately when module is imported
|
|
497
|
+
registerExitHook();
|
|
498
|
+
|
|
425
499
|
/**
|
|
426
500
|
* Get or create a HiveAdapter instance for a project
|
|
427
501
|
* Exported for testing - allows tests to verify state directly
|
|
@@ -693,6 +767,23 @@ export const hive_create_epic = tool({
|
|
|
693
767
|
}
|
|
694
768
|
}
|
|
695
769
|
|
|
770
|
+
// Sync cells to JSONL so spawned workers can see them immediately
|
|
771
|
+
try {
|
|
772
|
+
ensureHiveDirectory(projectKey);
|
|
773
|
+
const flushManager = new FlushManager({
|
|
774
|
+
adapter,
|
|
775
|
+
projectKey,
|
|
776
|
+
outputPath: `${projectKey}/.hive/issues.jsonl`,
|
|
777
|
+
});
|
|
778
|
+
await flushManager.flush();
|
|
779
|
+
} catch (error) {
|
|
780
|
+
// Non-fatal - log and continue
|
|
781
|
+
console.warn(
|
|
782
|
+
"[hive_create_epic] Failed to sync to JSONL:",
|
|
783
|
+
error,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
696
787
|
return JSON.stringify(result, null, 2);
|
|
697
788
|
} catch (error) {
|
|
698
789
|
// Partial failure - rollback via deleteCell
|
|
@@ -789,7 +880,7 @@ export const hive_query = tool({
|
|
|
789
880
|
export const hive_update = tool({
|
|
790
881
|
description: "Update cell status/description",
|
|
791
882
|
args: {
|
|
792
|
-
id: tool.schema.string().describe("Cell ID"),
|
|
883
|
+
id: tool.schema.string().describe("Cell ID or partial hash"),
|
|
793
884
|
status: tool.schema
|
|
794
885
|
.enum(["open", "in_progress", "blocked", "closed"])
|
|
795
886
|
.optional()
|
|
@@ -808,26 +899,29 @@ export const hive_update = tool({
|
|
|
808
899
|
const adapter = await getHiveAdapter(projectKey);
|
|
809
900
|
|
|
810
901
|
try {
|
|
902
|
+
// Resolve partial ID to full ID
|
|
903
|
+
const cellId = await resolvePartialId(adapter, projectKey, validated.id) || validated.id;
|
|
904
|
+
|
|
811
905
|
let cell: AdapterCell;
|
|
812
906
|
|
|
813
907
|
// Status changes use changeCellStatus, other fields use updateCell
|
|
814
908
|
if (validated.status) {
|
|
815
909
|
cell = await adapter.changeCellStatus(
|
|
816
910
|
projectKey,
|
|
817
|
-
|
|
911
|
+
cellId,
|
|
818
912
|
validated.status,
|
|
819
913
|
);
|
|
820
914
|
}
|
|
821
915
|
|
|
822
916
|
// Update other fields if provided
|
|
823
917
|
if (validated.description !== undefined || validated.priority !== undefined) {
|
|
824
|
-
cell = await adapter.updateCell(projectKey,
|
|
918
|
+
cell = await adapter.updateCell(projectKey, cellId, {
|
|
825
919
|
description: validated.description,
|
|
826
920
|
priority: validated.priority,
|
|
827
921
|
});
|
|
828
922
|
} else if (!validated.status) {
|
|
829
923
|
// No changes requested
|
|
830
|
-
const existingCell = await adapter.getCell(projectKey,
|
|
924
|
+
const existingCell = await adapter.getCell(projectKey, cellId);
|
|
831
925
|
if (!existingCell) {
|
|
832
926
|
throw new HiveError(
|
|
833
927
|
`Cell not found: ${validated.id}`,
|
|
@@ -837,12 +931,27 @@ export const hive_update = tool({
|
|
|
837
931
|
cell = existingCell;
|
|
838
932
|
}
|
|
839
933
|
|
|
840
|
-
await adapter.markDirty(projectKey,
|
|
934
|
+
await adapter.markDirty(projectKey, cellId);
|
|
841
935
|
|
|
842
936
|
const formatted = formatCellForOutput(cell!);
|
|
843
937
|
return JSON.stringify(formatted, null, 2);
|
|
844
938
|
} catch (error) {
|
|
845
939
|
const message = error instanceof Error ? error.message : String(error);
|
|
940
|
+
|
|
941
|
+
// Provide helpful error messages
|
|
942
|
+
if (message.includes("Ambiguous hash")) {
|
|
943
|
+
throw new HiveError(
|
|
944
|
+
`Ambiguous ID '${validated.id}': multiple cells match. Please provide more characters.`,
|
|
945
|
+
"hive_update",
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
if (message.includes("Bead not found") || message.includes("Cell not found")) {
|
|
949
|
+
throw new HiveError(
|
|
950
|
+
`No cell found matching ID '${validated.id}'`,
|
|
951
|
+
"hive_update",
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
846
955
|
throw new HiveError(
|
|
847
956
|
`Failed to update cell: ${message}`,
|
|
848
957
|
"hive_update",
|
|
@@ -857,7 +966,7 @@ export const hive_update = tool({
|
|
|
857
966
|
export const hive_close = tool({
|
|
858
967
|
description: "Close a cell with reason",
|
|
859
968
|
args: {
|
|
860
|
-
id: tool.schema.string().describe("Cell ID"),
|
|
969
|
+
id: tool.schema.string().describe("Cell ID or partial hash"),
|
|
861
970
|
reason: tool.schema.string().describe("Completion reason"),
|
|
862
971
|
},
|
|
863
972
|
async execute(args, ctx) {
|
|
@@ -866,17 +975,35 @@ export const hive_close = tool({
|
|
|
866
975
|
const adapter = await getHiveAdapter(projectKey);
|
|
867
976
|
|
|
868
977
|
try {
|
|
978
|
+
// Resolve partial ID to full ID
|
|
979
|
+
const cellId = await resolvePartialId(adapter, projectKey, validated.id) || validated.id;
|
|
980
|
+
|
|
869
981
|
const cell = await adapter.closeCell(
|
|
870
982
|
projectKey,
|
|
871
|
-
|
|
983
|
+
cellId,
|
|
872
984
|
validated.reason,
|
|
873
985
|
);
|
|
874
986
|
|
|
875
|
-
await adapter.markDirty(projectKey,
|
|
987
|
+
await adapter.markDirty(projectKey, cellId);
|
|
876
988
|
|
|
877
989
|
return `Closed ${cell.id}: ${validated.reason}`;
|
|
878
990
|
} catch (error) {
|
|
879
991
|
const message = error instanceof Error ? error.message : String(error);
|
|
992
|
+
|
|
993
|
+
// Provide helpful error messages
|
|
994
|
+
if (message.includes("Ambiguous hash")) {
|
|
995
|
+
throw new HiveError(
|
|
996
|
+
`Ambiguous ID '${validated.id}': multiple cells match. Please provide more characters.`,
|
|
997
|
+
"hive_close",
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
if (message.includes("Bead not found") || message.includes("Cell not found")) {
|
|
1001
|
+
throw new HiveError(
|
|
1002
|
+
`No cell found matching ID '${validated.id}'`,
|
|
1003
|
+
"hive_close",
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
880
1007
|
throw new HiveError(
|
|
881
1008
|
`Failed to close cell: ${message}`,
|
|
882
1009
|
"hive_close",
|
|
@@ -892,24 +1019,42 @@ export const hive_start = tool({
|
|
|
892
1019
|
description:
|
|
893
1020
|
"Mark a cell as in-progress (shortcut for update --status in_progress)",
|
|
894
1021
|
args: {
|
|
895
|
-
id: tool.schema.string().describe("Cell ID"),
|
|
1022
|
+
id: tool.schema.string().describe("Cell ID or partial hash"),
|
|
896
1023
|
},
|
|
897
1024
|
async execute(args, ctx) {
|
|
898
1025
|
const projectKey = getHiveWorkingDirectory();
|
|
899
1026
|
const adapter = await getHiveAdapter(projectKey);
|
|
900
1027
|
|
|
901
1028
|
try {
|
|
1029
|
+
// Resolve partial ID to full ID
|
|
1030
|
+
const cellId = await resolvePartialId(adapter, projectKey, args.id) || args.id;
|
|
1031
|
+
|
|
902
1032
|
const cell = await adapter.changeCellStatus(
|
|
903
1033
|
projectKey,
|
|
904
|
-
|
|
1034
|
+
cellId,
|
|
905
1035
|
"in_progress",
|
|
906
1036
|
);
|
|
907
1037
|
|
|
908
|
-
await adapter.markDirty(projectKey,
|
|
1038
|
+
await adapter.markDirty(projectKey, cellId);
|
|
909
1039
|
|
|
910
1040
|
return `Started: ${cell.id}`;
|
|
911
1041
|
} catch (error) {
|
|
912
1042
|
const message = error instanceof Error ? error.message : String(error);
|
|
1043
|
+
|
|
1044
|
+
// Provide helpful error messages
|
|
1045
|
+
if (message.includes("Ambiguous hash")) {
|
|
1046
|
+
throw new HiveError(
|
|
1047
|
+
`Ambiguous ID '${args.id}': multiple cells match. Please provide more characters.`,
|
|
1048
|
+
"hive_start",
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
if (message.includes("Bead not found") || message.includes("Cell not found")) {
|
|
1052
|
+
throw new HiveError(
|
|
1053
|
+
`No cell found matching ID '${args.id}'`,
|
|
1054
|
+
"hive_start",
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
913
1058
|
throw new HiveError(
|
|
914
1059
|
`Failed to start cell: ${message}`,
|
|
915
1060
|
"hive_start",
|
|
@@ -1012,8 +1157,21 @@ export const hive_sync = tool({
|
|
|
1012
1157
|
"flush hive",
|
|
1013
1158
|
);
|
|
1014
1159
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1160
|
+
// 2b. Sync memories to JSONL
|
|
1161
|
+
const swarmMail = await getSwarmMail(projectKey);
|
|
1162
|
+
const db = await swarmMail.getDatabase();
|
|
1163
|
+
const hivePath = join(projectKey, ".hive");
|
|
1164
|
+
let memoriesSynced = 0;
|
|
1165
|
+
try {
|
|
1166
|
+
const memoryResult = await syncMemories(db, hivePath);
|
|
1167
|
+
memoriesSynced = memoryResult.exported;
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
// Memory sync is optional - don't fail if it errors
|
|
1170
|
+
console.warn("[hive_sync] Memory sync warning:", err);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (flushResult.cellsExported === 0 && memoriesSynced === 0) {
|
|
1174
|
+
return "No cells or memories to sync";
|
|
1017
1175
|
}
|
|
1018
1176
|
|
|
1019
1177
|
// 3. Check if there are changes to commit
|
package/src/memory-tools.ts
CHANGED
|
@@ -104,7 +104,7 @@ export { createMemoryAdapter };
|
|
|
104
104
|
*/
|
|
105
105
|
export const semantic_memory_store = tool({
|
|
106
106
|
description:
|
|
107
|
-
"Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections.",
|
|
107
|
+
"Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections. Confidence affects decay rate: high confidence (1.0) = 135 day half-life, low confidence (0.0) = 45 day half-life.",
|
|
108
108
|
args: {
|
|
109
109
|
information: tool.schema
|
|
110
110
|
.string()
|
|
@@ -121,6 +121,10 @@ export const semantic_memory_store = tool({
|
|
|
121
121
|
.string()
|
|
122
122
|
.optional()
|
|
123
123
|
.describe("JSON string with additional metadata"),
|
|
124
|
+
confidence: tool.schema
|
|
125
|
+
.number()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7"),
|
|
124
128
|
},
|
|
125
129
|
async execute(args, ctx: ToolContext) {
|
|
126
130
|
const adapter = await getMemoryAdapter();
|
package/src/memory.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface StoreArgs {
|
|
|
68
68
|
readonly collection?: string;
|
|
69
69
|
readonly tags?: string;
|
|
70
70
|
readonly metadata?: string;
|
|
71
|
+
/** Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7 */
|
|
72
|
+
readonly confidence?: number;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/** Arguments for find operation */
|
|
@@ -288,12 +290,19 @@ export async function createMemoryAdapter(
|
|
|
288
290
|
metadata.tags = tags;
|
|
289
291
|
}
|
|
290
292
|
|
|
293
|
+
// Clamp confidence to valid range [0.0, 1.0]
|
|
294
|
+
const clampConfidence = (c: number | undefined): number => {
|
|
295
|
+
if (c === undefined) return 0.7;
|
|
296
|
+
return Math.max(0.0, Math.min(1.0, c));
|
|
297
|
+
};
|
|
298
|
+
|
|
291
299
|
const memory: Memory = {
|
|
292
300
|
id,
|
|
293
301
|
content: args.information,
|
|
294
302
|
metadata,
|
|
295
303
|
collection,
|
|
296
304
|
createdAt: new Date(),
|
|
305
|
+
confidence: clampConfidence(args.confidence),
|
|
297
306
|
};
|
|
298
307
|
|
|
299
308
|
// Generate embedding
|
package/src/swarm-decompose.ts
CHANGED
|
@@ -52,7 +52,7 @@ Agents MUST update their bead status as they work. No silent progress.
|
|
|
52
52
|
|
|
53
53
|
## Requirements
|
|
54
54
|
|
|
55
|
-
1. **Break into
|
|
55
|
+
1. **Break into independent subtasks** that can run in parallel (as many as needed)
|
|
56
56
|
2. **Assign files** - each subtask must specify which files it will modify
|
|
57
57
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
58
58
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
@@ -129,7 +129,7 @@ Agents MUST update their bead status as they work. No silent progress.
|
|
|
129
129
|
|
|
130
130
|
## Requirements
|
|
131
131
|
|
|
132
|
-
1. **Break into
|
|
132
|
+
1. **Break into independent subtasks** that can run in parallel (as many as needed)
|
|
133
133
|
2. **Assign files** - each subtask must specify which files it will modify
|
|
134
134
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
135
135
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
@@ -437,10 +437,9 @@ export const swarm_decompose = tool({
|
|
|
437
437
|
max_subtasks: tool.schema
|
|
438
438
|
.number()
|
|
439
439
|
.int()
|
|
440
|
-
.min(
|
|
441
|
-
.
|
|
442
|
-
.
|
|
443
|
-
.describe("Maximum number of subtasks (default: 5)"),
|
|
440
|
+
.min(1)
|
|
441
|
+
.optional()
|
|
442
|
+
.describe("Suggested max subtasks (optional - LLM decides if not specified)"),
|
|
444
443
|
context: tool.schema
|
|
445
444
|
.string()
|
|
446
445
|
.optional()
|
|
@@ -453,7 +452,6 @@ export const swarm_decompose = tool({
|
|
|
453
452
|
.number()
|
|
454
453
|
.int()
|
|
455
454
|
.min(1)
|
|
456
|
-
.max(10)
|
|
457
455
|
.optional()
|
|
458
456
|
.describe("Max CASS results to include (default: 3)"),
|
|
459
457
|
},
|
|
@@ -702,11 +700,9 @@ export const swarm_delegate_planning = tool({
|
|
|
702
700
|
max_subtasks: tool.schema
|
|
703
701
|
.number()
|
|
704
702
|
.int()
|
|
705
|
-
.min(
|
|
706
|
-
.max(10)
|
|
703
|
+
.min(1)
|
|
707
704
|
.optional()
|
|
708
|
-
.
|
|
709
|
-
.describe("Maximum number of subtasks (default: 5)"),
|
|
705
|
+
.describe("Suggested max subtasks (optional - LLM decides if not specified)"),
|
|
710
706
|
strategy: tool.schema
|
|
711
707
|
.enum(["auto", "file-based", "feature-based", "risk-based"])
|
|
712
708
|
.optional()
|
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
isToolAvailable,
|
|
73
73
|
warnMissingTool,
|
|
74
74
|
} from "./tool-availability";
|
|
75
|
-
import { getHiveAdapter } from "./hive";
|
|
75
|
+
import { getHiveAdapter, hive_sync, setHiveWorkingDirectory, getHiveWorkingDirectory } from "./hive";
|
|
76
76
|
import { listSkills } from "./skills";
|
|
77
77
|
import {
|
|
78
78
|
canUseWorktreeIsolation,
|
|
@@ -1570,6 +1570,30 @@ This will be recorded as a negative learning signal.`;
|
|
|
1570
1570
|
);
|
|
1571
1571
|
}
|
|
1572
1572
|
|
|
1573
|
+
// Sync cell to .hive/issues.jsonl (auto-sync on complete)
|
|
1574
|
+
// This ensures the worker's completed work persists before process exits
|
|
1575
|
+
let syncSuccess = false;
|
|
1576
|
+
let syncError: string | undefined;
|
|
1577
|
+
try {
|
|
1578
|
+
// Save current working directory and set to project path
|
|
1579
|
+
const previousWorkingDir = getHiveWorkingDirectory();
|
|
1580
|
+
setHiveWorkingDirectory(args.project_key);
|
|
1581
|
+
|
|
1582
|
+
try {
|
|
1583
|
+
const syncResult = await hive_sync.execute({ auto_pull: false }, _ctx);
|
|
1584
|
+
syncSuccess = !syncResult.includes("error");
|
|
1585
|
+
} finally {
|
|
1586
|
+
// Restore previous working directory
|
|
1587
|
+
setHiveWorkingDirectory(previousWorkingDir);
|
|
1588
|
+
}
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
// Non-fatal - log warning but don't block completion
|
|
1591
|
+
syncError = error instanceof Error ? error.message : String(error);
|
|
1592
|
+
console.warn(
|
|
1593
|
+
`[swarm_complete] Auto-sync failed (non-fatal): ${syncError}`,
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1573
1597
|
// Emit SubtaskOutcomeEvent for learning system
|
|
1574
1598
|
try {
|
|
1575
1599
|
const epicId = args.bead_id.includes(".")
|
|
@@ -1709,6 +1733,8 @@ This will be recorded as a negative learning signal.`;
|
|
|
1709
1733
|
bead_id: args.bead_id,
|
|
1710
1734
|
closed: true,
|
|
1711
1735
|
reservations_released: true,
|
|
1736
|
+
synced: syncSuccess,
|
|
1737
|
+
sync_error: syncError,
|
|
1712
1738
|
message_sent: messageSent,
|
|
1713
1739
|
message_error: messageError,
|
|
1714
1740
|
agent_registration: {
|
|
@@ -113,8 +113,9 @@ describe("SUBTASK_PROMPT_V2", () => {
|
|
|
113
113
|
new RegExp(`### Step ${lastStepNum}:[\\s\\S]*?(?=## \\[|$)`)
|
|
114
114
|
);
|
|
115
115
|
expect(lastStepMatch).not.toBeNull();
|
|
116
|
+
if (!lastStepMatch) return;
|
|
116
117
|
|
|
117
|
-
const lastStepContent = lastStepMatch
|
|
118
|
+
const lastStepContent = lastStepMatch[0];
|
|
118
119
|
expect(lastStepContent).toContain("swarm_complete");
|
|
119
120
|
expect(lastStepContent).toMatch(/NOT.*hive_close|DO NOT.*hive_close/i);
|
|
120
121
|
});
|
|
@@ -124,16 +125,18 @@ describe("SUBTASK_PROMPT_V2", () => {
|
|
|
124
125
|
test("lists memory query as non-negotiable", () => {
|
|
125
126
|
const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
|
|
126
127
|
expect(criticalSection).not.toBeNull();
|
|
128
|
+
if (!criticalSection) return;
|
|
127
129
|
|
|
128
|
-
expect(criticalSection
|
|
130
|
+
expect(criticalSection[0]).toMatch(/semantic-memory_find|memory.*MUST|Step 2.*MUST/i);
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
test("lists consequences of skipping memory steps", () => {
|
|
132
134
|
const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
|
|
133
135
|
expect(criticalSection).not.toBeNull();
|
|
136
|
+
if (!criticalSection) return;
|
|
134
137
|
|
|
135
138
|
// Should mention consequences for skipping memory
|
|
136
|
-
expect(criticalSection
|
|
139
|
+
expect(criticalSection[0]).toMatch(/repeat|waste|already.solved|mistakes/i);
|
|
137
140
|
});
|
|
138
141
|
});
|
|
139
142
|
});
|
|
@@ -141,8 +144,8 @@ describe("SUBTASK_PROMPT_V2", () => {
|
|
|
141
144
|
describe("formatSubtaskPromptV2", () => {
|
|
142
145
|
test("substitutes all placeholders correctly", () => {
|
|
143
146
|
const result = formatSubtaskPromptV2({
|
|
144
|
-
bead_id: "test-
|
|
145
|
-
epic_id: "test-
|
|
147
|
+
bead_id: "test-project-abc123-bead456",
|
|
148
|
+
epic_id: "test-project-abc123-epic789",
|
|
146
149
|
subtask_title: "Test Subtask",
|
|
147
150
|
subtask_description: "Do the test thing",
|
|
148
151
|
files: ["src/test.ts", "src/test.test.ts"],
|
|
@@ -150,8 +153,8 @@ describe("formatSubtaskPromptV2", () => {
|
|
|
150
153
|
project_path: "/path/to/project",
|
|
151
154
|
});
|
|
152
155
|
|
|
153
|
-
expect(result).toContain("test-
|
|
154
|
-
expect(result).toContain("test-
|
|
156
|
+
expect(result).toContain("test-project-abc123-bead456");
|
|
157
|
+
expect(result).toContain("test-project-abc123-epic789");
|
|
155
158
|
expect(result).toContain("Test Subtask");
|
|
156
159
|
expect(result).toContain("Do the test thing");
|
|
157
160
|
expect(result).toContain("src/test.ts");
|
|
@@ -160,8 +163,8 @@ describe("formatSubtaskPromptV2", () => {
|
|
|
160
163
|
|
|
161
164
|
test("includes memory query step with MANDATORY emphasis", () => {
|
|
162
165
|
const result = formatSubtaskPromptV2({
|
|
163
|
-
bead_id: "test-
|
|
164
|
-
epic_id: "test-
|
|
166
|
+
bead_id: "test-project-abc123-def456",
|
|
167
|
+
epic_id: "test-project-abc123-ghi789",
|
|
165
168
|
subtask_title: "Test",
|
|
166
169
|
subtask_description: "",
|
|
167
170
|
files: [],
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -46,7 +46,7 @@ Agents MUST update their cell status as they work. No silent progress.
|
|
|
46
46
|
|
|
47
47
|
## Requirements
|
|
48
48
|
|
|
49
|
-
1. **Break into
|
|
49
|
+
1. **Break into independent subtasks** that can run in parallel (as many as needed)
|
|
50
50
|
2. **Assign files** - each subtask must specify which files it will modify
|
|
51
51
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
52
52
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
@@ -123,7 +123,7 @@ Agents MUST update their cell status as they work. No silent progress.
|
|
|
123
123
|
|
|
124
124
|
## Requirements
|
|
125
125
|
|
|
126
|
-
1. **Break into
|
|
126
|
+
1. **Break into independent subtasks** that can run in parallel (as many as needed)
|
|
127
127
|
2. **Assign files** - each subtask must specify which files it will modify
|
|
128
128
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
129
129
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
@@ -406,21 +406,44 @@ swarm_checkpoint(
|
|
|
406
406
|
|
|
407
407
|
**Checkpoints preserve context so you can recover if things go wrong.**
|
|
408
408
|
|
|
409
|
-
### Step 8:
|
|
409
|
+
### Step 8: 💾 STORE YOUR LEARNINGS (if you discovered something)
|
|
410
|
+
|
|
411
|
+
**If you learned it the hard way, STORE IT so the next agent doesn't have to.**
|
|
412
|
+
|
|
410
413
|
\`\`\`
|
|
411
414
|
semantic-memory_store(
|
|
412
415
|
information="<what you learned, WHY it matters, how to apply it>",
|
|
413
|
-
|
|
416
|
+
tags="<domain, tech-stack, pattern-type>"
|
|
414
417
|
)
|
|
415
418
|
\`\`\`
|
|
416
419
|
|
|
417
|
-
**Store:**
|
|
418
|
-
-
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
420
|
+
**MANDATORY Storage Triggers - Store when you:**
|
|
421
|
+
- 🐛 **Solved a tricky bug** (>15min debugging) - include root cause + solution
|
|
422
|
+
- 💡 **Discovered a project-specific pattern** - domain rules, business logic quirks
|
|
423
|
+
- ⚠️ **Found a tool/library gotcha** - API quirks, version-specific bugs, workarounds
|
|
424
|
+
- 🚫 **Tried an approach that failed** - anti-patterns to avoid, why it didn't work
|
|
425
|
+
- 🏗️ **Made an architectural decision** - reasoning, alternatives considered, tradeoffs
|
|
422
426
|
|
|
423
|
-
**
|
|
427
|
+
**What Makes a GOOD Memory:**
|
|
428
|
+
|
|
429
|
+
✅ **GOOD** (actionable, explains WHY):
|
|
430
|
+
\`\`\`
|
|
431
|
+
"OAuth refresh tokens need 5min buffer before expiry to avoid race conditions.
|
|
432
|
+
Without buffer, token refresh can fail mid-request if expiry happens between
|
|
433
|
+
check and use. Implemented with: if (expiresAt - Date.now() < 300000) refresh()"
|
|
434
|
+
\`\`\`
|
|
435
|
+
|
|
436
|
+
❌ **BAD** (generic, no context):
|
|
437
|
+
\`\`\`
|
|
438
|
+
"Fixed the auth bug by adding a null check"
|
|
439
|
+
\`\`\`
|
|
440
|
+
|
|
441
|
+
**What NOT to Store:**
|
|
442
|
+
- Generic knowledge that's in official documentation
|
|
443
|
+
- Implementation details that change frequently
|
|
444
|
+
- Vague descriptions without context ("fixed the thing")
|
|
445
|
+
|
|
446
|
+
**The WHY matters more than the WHAT.** Future agents need context to apply your learning.
|
|
424
447
|
|
|
425
448
|
### Step 9: Complete (REQUIRED - releases reservations)
|
|
426
449
|
\`\`\`
|
|
@@ -511,17 +534,20 @@ Other cell operations:
|
|
|
511
534
|
|
|
512
535
|
**NON-NEGOTIABLE:**
|
|
513
536
|
1. Step 1 (swarmmail_init) MUST be first - do it before anything else
|
|
514
|
-
2. Step 2 (semantic-memory_find) MUST happen
|
|
537
|
+
2. 🧠 Step 2 (semantic-memory_find) MUST happen BEFORE starting work - query first, code second
|
|
515
538
|
3. Step 4 (swarmmail_reserve) - YOU reserve files, not coordinator
|
|
516
539
|
4. Step 6 (swarm_progress) - Report at milestones, don't work silently
|
|
517
|
-
5. Step
|
|
540
|
+
5. 💾 Step 8 (semantic-memory_store) - If you learned something hard, STORE IT
|
|
541
|
+
6. Step 9 (swarm_complete) - Use this to close, NOT hive_close
|
|
518
542
|
|
|
519
543
|
**If you skip these steps:**
|
|
520
544
|
- Your work won't be tracked (swarm_complete will fail)
|
|
521
|
-
- You'll waste time repeating solved problems (no semantic memory query)
|
|
545
|
+
- 🔄 You'll waste time repeating already-solved problems (no semantic memory query)
|
|
522
546
|
- Edit conflicts with other agents (no file reservation)
|
|
523
547
|
- Lost work if you crash (no checkpoints)
|
|
524
|
-
- Future agents repeat
|
|
548
|
+
- 🔄 Future agents repeat YOUR mistakes (no learnings stored)
|
|
549
|
+
|
|
550
|
+
**Memory is the swarm's collective intelligence. Query it. Feed it.**
|
|
525
551
|
|
|
526
552
|
Begin now.`;
|
|
527
553
|
|
|
@@ -901,10 +927,9 @@ export const swarm_plan_prompt = tool({
|
|
|
901
927
|
max_subtasks: tool.schema
|
|
902
928
|
.number()
|
|
903
929
|
.int()
|
|
904
|
-
.min(
|
|
905
|
-
.
|
|
906
|
-
.
|
|
907
|
-
.describe("Maximum number of subtasks (default: 5)"),
|
|
930
|
+
.min(1)
|
|
931
|
+
.optional()
|
|
932
|
+
.describe("Suggested max subtasks (optional - LLM decides if not specified)"),
|
|
908
933
|
context: tool.schema
|
|
909
934
|
.string()
|
|
910
935
|
.optional()
|
|
@@ -917,7 +942,6 @@ export const swarm_plan_prompt = tool({
|
|
|
917
942
|
.number()
|
|
918
943
|
.int()
|
|
919
944
|
.min(1)
|
|
920
|
-
.max(10)
|
|
921
945
|
.optional()
|
|
922
946
|
.describe("Max CASS results to include (default: 3)"),
|
|
923
947
|
include_skills: tool.schema
|