swarmdo 1.45.0 → 1.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/.claude/helpers/hook-handler.cjs +16 -0
  2. package/.claude/helpers/router.cjs +27 -1
  3. package/.claude/settings.local.json +7 -0
  4. package/README.md +6 -2
  5. package/package.json +1 -1
  6. package/v3/@swarmdo/cli/dist/src/agent-bridge/bridge.d.ts +104 -0
  7. package/v3/@swarmdo/cli/dist/src/agent-bridge/bridge.js +133 -0
  8. package/v3/@swarmdo/cli/dist/src/commands/agent.js +133 -1
  9. package/v3/@swarmdo/cli/dist/src/commands/config.js +32 -1
  10. package/v3/@swarmdo/cli/dist/src/commands/hidden-coupling.d.ts +19 -0
  11. package/v3/@swarmdo/cli/dist/src/commands/hidden-coupling.js +92 -0
  12. package/v3/@swarmdo/cli/dist/src/commands/index.js +14 -4
  13. package/v3/@swarmdo/cli/dist/src/commands/ownership.d.ts +18 -0
  14. package/v3/@swarmdo/cli/dist/src/commands/ownership.js +88 -0
  15. package/v3/@swarmdo/cli/dist/src/commands/pack.js +4 -4
  16. package/v3/@swarmdo/cli/dist/src/commands/standup.d.ts +18 -0
  17. package/v3/@swarmdo/cli/dist/src/commands/standup.js +116 -0
  18. package/v3/@swarmdo/cli/dist/src/commands/swarm.js +29 -0
  19. package/v3/@swarmdo/cli/dist/src/config-lint/commands-lint.d.ts +31 -0
  20. package/v3/@swarmdo/cli/dist/src/config-lint/commands-lint.js +127 -0
  21. package/v3/@swarmdo/cli/dist/src/config-lint/lint.d.ts +5 -0
  22. package/v3/@swarmdo/cli/dist/src/config-lint/lint.js +4 -0
  23. package/v3/@swarmdo/cli/dist/src/coupling/hidden.d.ts +45 -0
  24. package/v3/@swarmdo/cli/dist/src/coupling/hidden.js +66 -0
  25. package/v3/@swarmdo/cli/dist/src/init/helpers-generator.js +41 -1
  26. package/v3/@swarmdo/cli/dist/src/mcp-client.js +2 -0
  27. package/v3/@swarmdo/cli/dist/src/mcp-tools/agent-tools.js +166 -0
  28. package/v3/@swarmdo/cli/dist/src/mcp-tools/index.d.ts +1 -0
  29. package/v3/@swarmdo/cli/dist/src/mcp-tools/index.js +1 -0
  30. package/v3/@swarmdo/cli/dist/src/mcp-tools/ownership-tools.d.ts +14 -0
  31. package/v3/@swarmdo/cli/dist/src/mcp-tools/ownership-tools.js +51 -0
  32. package/v3/@swarmdo/cli/dist/src/mcp-tools/profiles.js +1 -0
  33. package/v3/@swarmdo/cli/dist/src/mcp-tools/swarm-tools.d.ts +33 -0
  34. package/v3/@swarmdo/cli/dist/src/mcp-tools/swarm-tools.js +73 -38
  35. package/v3/@swarmdo/cli/dist/src/ownership/ownership.d.ts +61 -0
  36. package/v3/@swarmdo/cli/dist/src/ownership/ownership.js +151 -0
  37. package/v3/@swarmdo/cli/dist/src/pack/pack.d.ts +1 -1
  38. package/v3/@swarmdo/cli/dist/src/pack/pack.js +12 -4
  39. package/v3/@swarmdo/cli/dist/src/redact/redact.js +28 -9
  40. package/v3/@swarmdo/cli/dist/src/standup/standup.d.ts +83 -0
  41. package/v3/@swarmdo/cli/dist/src/standup/standup.js +157 -0
  42. package/v3/@swarmdo/cli/package.json +1 -1
  43. package/v3/@swarmdo/cli-core/dist/src/index.js +1 -1
  44. package/v3/@swarmdo/codex/dist/cli.js +0 -0
  45. package/v3/@swarmdo/hooks/dist/cli/guidance-cli.js +0 -0
  46. package/v3/@swarmdo/hooks/dist/statusline/index.d.ts +3 -3
  47. package/v3/@swarmdo/hooks/dist/statusline/index.js +3 -3
  48. package/v3/@swarmdo/integration/dist/__tests__/agentic-flow-agent.test.js +1 -4
  49. package/v3/@swarmdo/neural/dist/flash-attention.d.ts +1 -1
  50. package/v3/@swarmdo/neural/dist/flash-attention.js +1 -1
  51. package/v3/@swarmdo/plugin-agent-federation/dist/plugin.js +1 -1
  52. package/v3/@swarmdo/plugin-agent-federation/dist/transport/midstream-aware-loader.js +2 -2
  53. package/v3/@swarmdo/plugin-iot-cognitum/dist/application/index.d.ts +2 -0
  54. package/v3/@swarmdo/plugin-iot-cognitum/dist/application/index.js +2 -0
  55. package/v3/@swarmdo/plugin-iot-cognitum/dist/application/iot-coordinator.d.ts +138 -0
  56. package/v3/@swarmdo/plugin-iot-cognitum/dist/application/iot-coordinator.js +418 -0
  57. package/v3/@swarmdo/plugin-iot-cognitum/dist/bin.d.ts +15 -0
  58. package/v3/@swarmdo/plugin-iot-cognitum/dist/bin.js +240 -0
  59. package/v3/@swarmdo/plugin-iot-cognitum/dist/cli-commands.d.ts +7 -0
  60. package/v3/@swarmdo/plugin-iot-cognitum/dist/cli-commands.js +508 -0
  61. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-agent.d.ts +54 -0
  62. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-agent.js +2 -0
  63. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-fleet.d.ts +46 -0
  64. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-fleet.js +2 -0
  65. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-trust-level.d.ts +18 -0
  66. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/device-trust-level.js +28 -0
  67. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/index.d.ts +5 -0
  68. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/index.js +2 -0
  69. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/telemetry.d.ts +35 -0
  70. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/entities/telemetry.js +2 -0
  71. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/device-repository.d.ts +11 -0
  72. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/device-repository.js +2 -0
  73. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/fleet-repository.d.ts +9 -0
  74. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/fleet-repository.js +2 -0
  75. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/index.d.ts +5 -0
  76. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/index.js +2 -0
  77. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/telemetry-repository.d.ts +12 -0
  78. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/telemetry-repository.js +2 -0
  79. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/trust-history-repository.d.ts +19 -0
  80. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/repositories/trust-history-repository.js +2 -0
  81. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/anomaly-detection-service.d.ts +44 -0
  82. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/anomaly-detection-service.js +117 -0
  83. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/device-lifecycle-service.d.ts +70 -0
  84. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/device-lifecycle-service.js +194 -0
  85. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/firmware-orchestration-service.d.ts +69 -0
  86. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/firmware-orchestration-service.js +139 -0
  87. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/fleet-topology-service.d.ts +34 -0
  88. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/fleet-topology-service.js +101 -0
  89. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/index.d.ts +10 -0
  90. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/index.js +10 -0
  91. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/mesh-service.d.ts +64 -0
  92. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/mesh-service.js +70 -0
  93. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/sona-integration-service.d.ts +44 -0
  94. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/sona-integration-service.js +79 -0
  95. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/telemetry-ingestion-service.d.ts +37 -0
  96. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/telemetry-ingestion-service.js +39 -0
  97. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/telemetry-service.d.ts +67 -0
  98. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/telemetry-service.js +58 -0
  99. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/witness-verification-service.d.ts +45 -0
  100. package/v3/@swarmdo/plugin-iot-cognitum/dist/domain/services/witness-verification-service.js +63 -0
  101. package/v3/@swarmdo/plugin-iot-cognitum/dist/index.d.ts +26 -0
  102. package/v3/@swarmdo/plugin-iot-cognitum/dist/index.js +17 -0
  103. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/agentdb-telemetry-repository.d.ts +51 -0
  104. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/agentdb-telemetry-repository.js +110 -0
  105. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-device-repository.d.ts +13 -0
  106. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-device-repository.js +25 -0
  107. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-fleet-repository.d.ts +11 -0
  108. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-fleet-repository.js +19 -0
  109. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-trust-history-repository.d.ts +11 -0
  110. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/in-memory-trust-history-repository.js +27 -0
  111. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/index.d.ts +6 -0
  112. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/index.js +6 -0
  113. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/seed-client-factory.d.ts +41 -0
  114. package/v3/@swarmdo/plugin-iot-cognitum/dist/infrastructure/seed-client-factory.js +64 -0
  115. package/v3/@swarmdo/plugin-iot-cognitum/dist/mcp-tools.d.ts +8 -0
  116. package/v3/@swarmdo/plugin-iot-cognitum/dist/mcp-tools.js +703 -0
  117. package/v3/@swarmdo/plugin-iot-cognitum/dist/plugin.d.ts +23 -0
  118. package/v3/@swarmdo/plugin-iot-cognitum/dist/plugin.js +239 -0
  119. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/anomaly-scan-worker.d.ts +17 -0
  120. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/anomaly-scan-worker.js +42 -0
  121. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/firmware-watch-worker.d.ts +18 -0
  122. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/firmware-watch-worker.js +45 -0
  123. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/health-probe-worker.d.ts +19 -0
  124. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/health-probe-worker.js +50 -0
  125. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/mesh-sync-worker.d.ts +19 -0
  126. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/mesh-sync-worker.js +47 -0
  127. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/telemetry-ingest-worker.d.ts +17 -0
  128. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/telemetry-ingest-worker.js +40 -0
  129. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/witness-audit-worker.d.ts +18 -0
  130. package/v3/@swarmdo/plugin-iot-cognitum/dist/workers/witness-audit-worker.js +51 -0
  131. package/v3/@swarmdo/providers/dist/swarmvector-provider.d.ts +1 -1
  132. package/v3/@swarmdo/providers/dist/swarmvector-provider.js +1 -1
  133. package/v3/@swarmdo/security/dist/input-validator.d.ts +6 -6
  134. package/v3/@swarmdo/shared/dist/hooks/safety/git-commit.js +1 -1
  135. package/v3/@swarmdo/shared/dist/mcp/transport/http.js +1 -8
  136. package/v3/@swarmdo/testing/dist/fixtures/agent-fixtures.d.ts +214 -0
  137. package/v3/@swarmdo/testing/dist/fixtures/agent-fixtures.js +595 -0
  138. package/v3/@swarmdo/testing/dist/fixtures/agents.d.ts +76 -0
  139. package/v3/@swarmdo/testing/dist/fixtures/agents.js +158 -0
  140. package/v3/@swarmdo/testing/dist/fixtures/configurations.d.ts +157 -0
  141. package/v3/@swarmdo/testing/dist/fixtures/configurations.js +337 -0
  142. package/v3/@swarmdo/testing/dist/fixtures/index.d.ts +10 -0
  143. package/v3/@swarmdo/testing/dist/fixtures/index.js +17 -0
  144. package/v3/@swarmdo/testing/dist/fixtures/mcp-fixtures.d.ts +329 -0
  145. package/v3/@swarmdo/testing/dist/fixtures/mcp-fixtures.js +714 -0
  146. package/v3/@swarmdo/testing/dist/fixtures/memory-entries.d.ts +159 -0
  147. package/v3/@swarmdo/testing/dist/fixtures/memory-entries.js +262 -0
  148. package/v3/@swarmdo/testing/dist/fixtures/memory-fixtures.d.ts +290 -0
  149. package/v3/@swarmdo/testing/dist/fixtures/memory-fixtures.js +547 -0
  150. package/v3/@swarmdo/testing/dist/fixtures/swarm-fixtures.d.ts +289 -0
  151. package/v3/@swarmdo/testing/dist/fixtures/swarm-fixtures.js +534 -0
  152. package/v3/@swarmdo/testing/dist/fixtures/tasks.d.ts +106 -0
  153. package/v3/@swarmdo/testing/dist/fixtures/tasks.js +229 -0
  154. package/v3/@swarmdo/testing/dist/helpers/assertion-helpers.d.ts +213 -0
  155. package/v3/@swarmdo/testing/dist/helpers/assertion-helpers.js +435 -0
  156. package/v3/@swarmdo/testing/dist/helpers/assertions.d.ts +135 -0
  157. package/v3/@swarmdo/testing/dist/helpers/assertions.js +217 -0
  158. package/v3/@swarmdo/testing/dist/helpers/create-mock.d.ts +96 -0
  159. package/v3/@swarmdo/testing/dist/helpers/create-mock.js +164 -0
  160. package/v3/@swarmdo/testing/dist/helpers/index.d.ts +14 -0
  161. package/v3/@swarmdo/testing/dist/helpers/index.js +22 -0
  162. package/v3/@swarmdo/testing/dist/helpers/mock-factory.d.ts +323 -0
  163. package/v3/@swarmdo/testing/dist/helpers/mock-factory.js +350 -0
  164. package/v3/@swarmdo/testing/dist/helpers/setup-teardown.d.ts +271 -0
  165. package/v3/@swarmdo/testing/dist/helpers/setup-teardown.js +446 -0
  166. package/v3/@swarmdo/testing/dist/helpers/swarm-instance.d.ts +142 -0
  167. package/v3/@swarmdo/testing/dist/helpers/swarm-instance.js +205 -0
  168. package/v3/@swarmdo/testing/dist/helpers/test-application.d.ts +190 -0
  169. package/v3/@swarmdo/testing/dist/helpers/test-application.js +138 -0
  170. package/v3/@swarmdo/testing/dist/helpers/test-utils.d.ts +274 -0
  171. package/v3/@swarmdo/testing/dist/helpers/test-utils.js +456 -0
  172. package/v3/@swarmdo/testing/dist/index.d.ts +42 -0
  173. package/v3/@swarmdo/testing/dist/index.js +66 -0
  174. package/v3/@swarmdo/testing/dist/mocks/index.d.ts +8 -0
  175. package/v3/@swarmdo/testing/dist/mocks/index.js +10 -0
  176. package/v3/@swarmdo/testing/dist/mocks/mock-mcp-client.d.ts +206 -0
  177. package/v3/@swarmdo/testing/dist/mocks/mock-mcp-client.js +575 -0
  178. package/v3/@swarmdo/testing/dist/mocks/mock-services.d.ts +287 -0
  179. package/v3/@swarmdo/testing/dist/mocks/mock-services.js +563 -0
  180. package/v3/@swarmdo/testing/dist/regression/api-contract.d.ts +109 -0
  181. package/v3/@swarmdo/testing/dist/regression/api-contract.js +374 -0
  182. package/v3/@swarmdo/testing/dist/regression/index.d.ts +18 -0
  183. package/v3/@swarmdo/testing/dist/regression/index.js +18 -0
  184. package/v3/@swarmdo/testing/dist/regression/integration-regression.d.ts +67 -0
  185. package/v3/@swarmdo/testing/dist/regression/integration-regression.js +334 -0
  186. package/v3/@swarmdo/testing/dist/regression/performance-baseline.d.ts +106 -0
  187. package/v3/@swarmdo/testing/dist/regression/performance-baseline.js +268 -0
  188. package/v3/@swarmdo/testing/dist/regression/regression-runner.d.ts +124 -0
  189. package/v3/@swarmdo/testing/dist/regression/regression-runner.js +227 -0
  190. package/v3/@swarmdo/testing/dist/regression/security-regression.d.ts +73 -0
  191. package/v3/@swarmdo/testing/dist/regression/security-regression.js +258 -0
  192. package/v3/@swarmdo/testing/dist/setup.d.ts +43 -0
  193. package/v3/@swarmdo/testing/dist/setup.js +91 -0
  194. package/v3/@swarmdo/testing/dist/v2-compat/api-compat.test.d.ts +10 -0
  195. package/v3/@swarmdo/testing/dist/v2-compat/api-compat.test.js +433 -0
  196. package/v3/@swarmdo/testing/dist/v2-compat/cli-compat.test.d.ts +10 -0
  197. package/v3/@swarmdo/testing/dist/v2-compat/cli-compat.test.js +352 -0
  198. package/v3/@swarmdo/testing/dist/v2-compat/compatibility-validator.d.ts +206 -0
  199. package/v3/@swarmdo/testing/dist/v2-compat/compatibility-validator.js +839 -0
  200. package/v3/@swarmdo/testing/dist/v2-compat/hooks-compat.test.d.ts +10 -0
  201. package/v3/@swarmdo/testing/dist/v2-compat/hooks-compat.test.js +428 -0
  202. package/v3/@swarmdo/testing/dist/v2-compat/index.d.ts +38 -0
  203. package/v3/@swarmdo/testing/dist/v2-compat/index.js +41 -0
  204. package/v3/@swarmdo/testing/dist/v2-compat/mcp-compat.test.d.ts +10 -0
  205. package/v3/@swarmdo/testing/dist/v2-compat/mcp-compat.test.js +419 -0
  206. package/v3/@swarmdo/testing/dist/v2-compat/report-generator.d.ts +34 -0
  207. package/v3/@swarmdo/testing/dist/v2-compat/report-generator.js +372 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * hidden.ts — HIDDEN coupling: files that change together but have no import
3
+ * edge to explain it. "Logical coupling minus structural coupling."
4
+ *
5
+ * `coupling` mines temporal (co-change) coupling from git; `codegraph` builds
6
+ * the static import graph. Join them and emit the SET DIFFERENCE: pairs with a
7
+ * high co-change degree yet NO import edge connecting them in either direction.
8
+ * That's a dependency real enough to move two files in lockstep but invisible in
9
+ * the code — a JSON schema and the type that mirrors it, a serializer/parser
10
+ * split across modules, a config file and its consumers, a doc that must track
11
+ * an API. An agent editing A won't reach B by following imports, so it forgets
12
+ * the co-edit; a reviewer sees a missing abstraction / architectural smell.
13
+ *
14
+ * This is the divergence between evolutionary coupling (Gall et al., "Detection
15
+ * of Logical Coupling," ICSM 1998) and structural coupling — CodeScene flags
16
+ * change-coupling that crosses architectural boundaries as "surprising." swarmdo
17
+ * is uniquely positioned to compute it because it already owns BOTH captures.
18
+ *
19
+ * Pure + deterministic: a set-difference over two already-built inputs (the
20
+ * ranked CouplingPair[] from computeCoupling + the ImportEdge[] from the index).
21
+ * No git, no fs — fully fixture-testable.
22
+ */
23
+ import type { CouplingPair } from './coupling.js';
24
+ import type { ImportEdge } from '../codegraph/codegraph.js';
25
+ export interface AnnotatedPair extends CouplingPair {
26
+ /** true iff an import edge connects a↔b in EITHER direction (a structural link
27
+ * that explains the co-change); the returned pairs are all `false`. */
28
+ importLinked: boolean;
29
+ }
30
+ export interface HiddenCouplingOptions {
31
+ /** keep only the top N hidden pairs after ranking (default: all) */
32
+ top?: number;
33
+ }
34
+ /**
35
+ * Return the co-change pairs that NO import edge explains, ranked as they came
36
+ * from `computeCoupling` (degree desc, shared desc, path order). A type-only
37
+ * import still counts as a structural link — it's a real code reference that
38
+ * explains the co-change — so those pairs are excluded too. Pure.
39
+ */
40
+ export declare function computeHiddenCoupling(pairs: CouplingPair[], imports: ImportEdge[], opts?: HiddenCouplingOptions): AnnotatedPair[];
41
+ /** Export the hidden-coupling ranking as CSV for review. Pure. */
42
+ export declare function hiddenCouplingToCsv(pairs: AnnotatedPair[]): string;
43
+ /** Human-readable table. Pure. */
44
+ export declare function formatHiddenCoupling(pairs: AnnotatedPair[]): string;
45
+ //# sourceMappingURL=hidden.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * hidden.ts — HIDDEN coupling: files that change together but have no import
3
+ * edge to explain it. "Logical coupling minus structural coupling."
4
+ *
5
+ * `coupling` mines temporal (co-change) coupling from git; `codegraph` builds
6
+ * the static import graph. Join them and emit the SET DIFFERENCE: pairs with a
7
+ * high co-change degree yet NO import edge connecting them in either direction.
8
+ * That's a dependency real enough to move two files in lockstep but invisible in
9
+ * the code — a JSON schema and the type that mirrors it, a serializer/parser
10
+ * split across modules, a config file and its consumers, a doc that must track
11
+ * an API. An agent editing A won't reach B by following imports, so it forgets
12
+ * the co-edit; a reviewer sees a missing abstraction / architectural smell.
13
+ *
14
+ * This is the divergence between evolutionary coupling (Gall et al., "Detection
15
+ * of Logical Coupling," ICSM 1998) and structural coupling — CodeScene flags
16
+ * change-coupling that crosses architectural boundaries as "surprising." swarmdo
17
+ * is uniquely positioned to compute it because it already owns BOTH captures.
18
+ *
19
+ * Pure + deterministic: a set-difference over two already-built inputs (the
20
+ * ranked CouplingPair[] from computeCoupling + the ImportEdge[] from the index).
21
+ * No git, no fs — fully fixture-testable.
22
+ */
23
+ import { toCsv } from '../util/csv.js';
24
+ const US = '\x1f'; // pair-key separator (never appears in a file path)
25
+ /** Canonical undirected pair key so direction doesn't matter. */
26
+ const pairKey = (a, b) => (a < b ? a + US + b : b + US + a);
27
+ /**
28
+ * Return the co-change pairs that NO import edge explains, ranked as they came
29
+ * from `computeCoupling` (degree desc, shared desc, path order). A type-only
30
+ * import still counts as a structural link — it's a real code reference that
31
+ * explains the co-change — so those pairs are excluded too. Pure.
32
+ */
33
+ export function computeHiddenCoupling(pairs, imports, opts = {}) {
34
+ // Every file pair connected by a resolved import edge, either direction.
35
+ const linked = new Set();
36
+ for (const e of imports) {
37
+ if (e.resolved)
38
+ linked.add(pairKey(e.from, e.resolved));
39
+ }
40
+ const hidden = pairs
41
+ .map((p) => ({ ...p, importLinked: linked.has(pairKey(p.a, p.b)) }))
42
+ .filter((p) => !p.importLinked);
43
+ return opts.top && opts.top > 0 ? hidden.slice(0, opts.top) : hidden;
44
+ }
45
+ /** Export the hidden-coupling ranking as CSV for review. Pure. */
46
+ export function hiddenCouplingToCsv(pairs) {
47
+ const headers = ['fileA', 'fileB', 'degree', 'shared', 'commitsA', 'commitsB'];
48
+ const rows = pairs.map((p) => [p.a, p.b, p.degree, p.shared, p.aCommits, p.bCommits]);
49
+ return toCsv(headers, rows);
50
+ }
51
+ /** Human-readable table. Pure. */
52
+ export function formatHiddenCoupling(pairs) {
53
+ if (pairs.length === 0)
54
+ return 'no hidden coupling found (every co-change pair has an import edge, or none met the threshold)';
55
+ const lines = [];
56
+ lines.push('rank degree shared commits(A/B) files (co-change, no import edge)');
57
+ pairs.forEach((p, i) => {
58
+ const rank = String(i + 1).padStart(4);
59
+ const degree = `${Math.round(p.degree * 100)}%`.padStart(6);
60
+ const shared = String(p.shared).padStart(6);
61
+ const ab = `${p.aCommits}/${p.bCommits}`.padStart(11);
62
+ lines.push(`${rank} ${degree} ${shared} ${ab} ${p.a} ⇢ ${p.b}`);
63
+ });
64
+ return lines.join('\n');
65
+ }
66
+ //# sourceMappingURL=hidden.js.map
@@ -277,6 +277,31 @@ function routeTask(task) {
277
277
  };
278
278
  }
279
279
 
280
+ // Mirror of src/agent-bridge/bridge.ts classifyPrompt — decides whether a
281
+ // prompt warrants spinning up a bound-agent swarm (and which roles), so the
282
+ // UserPromptSubmit hook can tell the main agent to spawn + bridge agents
283
+ // (making Swarmdo used by default instead of sitting idle).
284
+ const AGENTIC_RE = /\\b(implement|build|create|add(?:ing)?|develop|refactor\\w*|migrat\\w*|redesign|re-?architect\\w*|architect\\w*|feature|integrat\\w*|overhaul|rewrite|port|scaffold|end-to-end|multi-file|test suite|coverage|audit|harden\\w*|vulnerab\\w*|cve|fix\\w*|debug\\w*|optimi[sz]e|performance)\\b/i;
285
+ const TRIVIAL_RE = /\\b(what|why|how|explain|describe|show|list|find|search|where|which|typo|readme|comment|one-?liner|quick question|rename|bump (?:the )?version|status|help)\\b/i;
286
+ const ROLE_SIGNALS = [
287
+ { re: /\\b(security|vulnerab\\w*|cve|auth\\w*|inject\\w*|xss|ssrf)\\b/i, roles: ['security-auditor', 'coder', 'reviewer'] },
288
+ { re: /\\b(refactor\\w*|migrat\\w*|redesign|re-?architect\\w*|overhaul|rewrite)\\b/i, roles: ['system-architect', 'coder', 'reviewer'] },
289
+ { re: /\\b(perf\\w*|optimi[sz]e|benchmark|latency|throughput)\\b/i, roles: ['perf-analyzer', 'coder', 'tester'] },
290
+ { re: /\\b(test|coverage|tdd|spec)\\b/i, roles: ['tester', 'coder', 'reviewer'] },
291
+ { re: /\\b(feature|implement|build|integrat\\w*|end-to-end|api)\\b/i, roles: ['researcher', 'system-architect', 'coder', 'tester', 'reviewer'] },
292
+ ];
293
+ function classifyAgentic(task) {
294
+ const p = String(task == null ? '' : task).trim();
295
+ if (!p) return { requiresAgents: false, roles: [] };
296
+ if (!AGENTIC_RE.test(p)) return { requiresAgents: false, roles: [] };
297
+ if (TRIVIAL_RE.test(p) && p.length < 40) return { requiresAgents: false, roles: [] };
298
+ let roles = ['coder'];
299
+ for (const s of ROLE_SIGNALS) {
300
+ if (s.re.test(p)) { roles = s.roles; break; }
301
+ }
302
+ return { requiresAgents: true, roles };
303
+ }
304
+
280
305
  // CLI
281
306
  const task = process.argv.slice(2).join(' ');
282
307
 
@@ -288,7 +313,7 @@ if (task) {
288
313
  console.log('\\nAvailable agents:', Object.keys(AGENT_CAPABILITIES).join(', '));
289
314
  }
290
315
 
291
- module.exports = { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS, buildPattern };
316
+ module.exports = { routeTask, classifyAgentic, AGENT_CAPABILITIES, TASK_PATTERNS, buildPattern };
292
317
  `;
293
318
  }
294
319
  /**
@@ -482,6 +507,21 @@ export function generateHookHandler() {
482
507
  ' } else {',
483
508
  " console.log('[INFO] Router not available, using default routing');",
484
509
  ' }',
510
+ ' // Agentic prompts → remind the main agent to actually USE Swarmdo:',
511
+ ' // spawn Claude Code agents AND bridge them (auto-forms a swarm).',
512
+ ' // Advisory + fully guarded — never breaks the prompt.',
513
+ ' try {',
514
+ ' if (router && router.classifyAgentic) {',
515
+ ' var intent = router.classifyAgentic(prompt);',
516
+ ' if (intent.requiresAgents) {',
517
+ " console.log('');",
518
+ " console.log('[SWARMDO] Agentic task — use the swarm: after you spawn Claude Code agents,');",
519
+ " console.log(' register each so Swarmdo tracks them and auto-forms a swarm from your config:');",
520
+ " console.log(' swarmdo agent bridge register -n <agent-name> -t <role> -s <session>');",
521
+ " console.log(' suggested roles: ' + intent.roles.join(', ') + ' then: swarmdo agent bridge list / swarm status');",
522
+ ' }',
523
+ ' }',
524
+ ' } catch (e) { /* advisory only — never break the prompt */ }',
485
525
  ' },',
486
526
  '',
487
527
  " 'pre-bash': () => {",
@@ -27,6 +27,7 @@ import { licenseTools } from './mcp-tools/license-tools.js';
27
27
  import { applyTools } from './mcp-tools/apply-tools.js';
28
28
  import { hotspotsTools } from './mcp-tools/hotspots-tools.js';
29
29
  import { couplingTools } from './mcp-tools/coupling-tools.js';
30
+ import { ownershipTools } from './mcp-tools/ownership-tools.js';
30
31
  import { affectedTools } from './mcp-tools/affected-tools.js';
31
32
  import { cyclesTools } from './mcp-tools/cycles-tools.js';
32
33
  import { testreportTools } from './mcp-tools/testreport-tools.js';
@@ -121,6 +122,7 @@ const TOOL_GROUPS = {
121
122
  apply: () => applyTools,
122
123
  hotspots: () => hotspotsTools,
123
124
  coupling: () => couplingTools,
125
+ ownership: () => ownershipTools,
124
126
  affected: () => affectedTools,
125
127
  cycles: () => cyclesTools,
126
128
  testreport: () => testreportTools,
@@ -9,6 +9,7 @@ import { join } from 'node:path';
9
9
  import { getProjectCwd } from './types.js';
10
10
  import { validateIdentifier, validateText, validateAgentSpawn } from './validate-input.js';
11
11
  import { executeAgentTask } from './agent-execute-core.js';
12
+ import { buildSpawnInput, reconcile, isBound, orphanedAgentIds } from '../agent-bridge/bridge.js';
12
13
  // Storage paths
13
14
  const STORAGE_DIR = '.swarmdo';
14
15
  const AGENT_DIR = 'agents';
@@ -74,6 +75,27 @@ function loadHiveAgents() {
74
75
  function loadAllAgents() {
75
76
  return { ...loadHiveAgents(), ...loadAgentStore().agents };
76
77
  }
78
+ /**
79
+ * Read the project's swarm topology defaults from swarmdo.config.json for the
80
+ * agent bridge's auto-swarm. Defensive — returns {} on any error so the bridge
81
+ * falls back to the anti-drift defaults (hierarchical / 8 / specialized).
82
+ * Accepts either a top-level `swarm` block or the root object.
83
+ */
84
+ function readSwarmConfigDefaults() {
85
+ try {
86
+ const p = join(getProjectCwd(), 'swarmdo.config.json');
87
+ if (!existsSync(p))
88
+ return {};
89
+ const j = JSON.parse(readFileSync(p, 'utf-8'));
90
+ const sw = (j.swarm || j) ?? {};
91
+ const str = (v) => (typeof v === 'string' ? v : undefined);
92
+ const num = (v) => (typeof v === 'number' ? v : undefined);
93
+ return { topology: str(sw.topology), maxAgents: num(sw.maxAgents), strategy: str(sw.strategy) };
94
+ }
95
+ catch {
96
+ return {};
97
+ }
98
+ }
77
99
  // Default model mappings for agent types (can be overridden)
78
100
  const AGENT_TYPE_MODEL_DEFAULTS = {
79
101
  // Complex agents → opus
@@ -323,6 +345,150 @@ export const agentTools = [
323
345
  return response;
324
346
  },
325
347
  },
348
+ {
349
+ name: 'agent_bridge_register',
350
+ description: "Register a REAL Claude Code Agent-tool agent into Swarmdo's registry so `swarmdo agent list` and `swarm_status` reflect it. Call this right AFTER you spawn a Claude Code subagent (Task/Agent tool): pass its name, session id, subagent type, and a one-line task. Idempotent — re-registering the same name+session UPDATES the bound record instead of duplicating. This is the bridge that stops Swarmdo sitting empty while real agents run: unlike agent_spawn (which only registers coordination metadata), this binds the record to a worker that actually exists.",
351
+ category: 'agent',
352
+ inputSchema: {
353
+ type: 'object',
354
+ properties: {
355
+ name: { type: 'string', description: 'The Claude Code agent name (e.g. "research-ccgap")' },
356
+ sessionId: { type: 'string', description: 'The Claude Code session id (from name@session-…)' },
357
+ agentType: { type: 'string', description: 'The subagent_type (e.g. general-purpose, coder). Default general-purpose.' },
358
+ task: { type: 'string', description: 'One-line task/prompt summary' },
359
+ },
360
+ required: ['name'],
361
+ },
362
+ handler: async (input) => {
363
+ const src = input;
364
+ const name = String(src.name || '').trim();
365
+ if (!name)
366
+ return { success: false, error: 'name is required (the Claude Code agent name)' };
367
+ const descriptor = {
368
+ name,
369
+ sessionId: src.sessionId ? String(src.sessionId) : undefined,
370
+ agentType: String(src.agentType || 'general-purpose'),
371
+ task: src.task ? String(src.task) : undefined,
372
+ };
373
+ const spawnInput = buildSpawnInput(descriptor, new Date().toISOString());
374
+ // Auto-swarm: registering a Claude Code agent spins up a swarm from the
375
+ // project config (anti-drift defaults if absent) when none is running, and
376
+ // enrolls this agent into it — so bridged agents coordinate instead of
377
+ // floating unattached. registerAgent honors spawnInput.swarmId to join.
378
+ let swarm;
379
+ try {
380
+ const { ensureActiveSwarm } = await import('./swarm-tools.js');
381
+ const cfg = readSwarmConfigDefaults();
382
+ swarm = ensureActiveSwarm({
383
+ topology: cfg.topology || 'hierarchical',
384
+ maxAgents: cfg.maxAgents || 8,
385
+ strategy: cfg.strategy || 'specialized',
386
+ });
387
+ spawnInput.swarmId = swarm.swarmId;
388
+ }
389
+ catch {
390
+ /* swarm optional — the agent still registers in the global store */
391
+ }
392
+ const res = await registerAgent(spawnInput);
393
+ if (!res.success)
394
+ return res;
395
+ return {
396
+ ...res,
397
+ bound: true,
398
+ origin: 'claude-code',
399
+ claudeName: descriptor.name,
400
+ status: 'registered',
401
+ ...(swarm ? { swarm } : {}),
402
+ };
403
+ },
404
+ },
405
+ {
406
+ name: 'agent_bridge_list',
407
+ description: 'List Swarmdo agent records split into Claude-Code-BOUND (each mirrors a real Task/Agent-tool agent) vs NATIVE, with binding detail. Pass `live` (array of current Claude Code agent names) to also get a reconciliation: which live agents are unmirrored (need agent_bridge_register), and which bound records are orphaned (their Claude agent is gone).',
408
+ category: 'agent',
409
+ inputSchema: {
410
+ type: 'object',
411
+ properties: {
412
+ live: {
413
+ type: 'array',
414
+ items: { type: 'string' },
415
+ description: 'Current live Claude Code agent names, for reconciliation',
416
+ },
417
+ },
418
+ },
419
+ handler: async (input) => {
420
+ const all = Object.values(loadAllAgents());
421
+ const summarize = (a) => ({
422
+ agentId: a.agentId,
423
+ agentType: a.agentType,
424
+ status: a.status,
425
+ ...(isBound(a) ? { binding: a.config.binding } : {}),
426
+ });
427
+ const bound = all.filter(isBound).map(summarize);
428
+ const native = all.filter((a) => !isBound(a)).map(summarize);
429
+ const liveRaw = input.live;
430
+ const live = Array.isArray(liveRaw) ? liveRaw.map(String) : undefined;
431
+ const reconciliation = live ? reconcile(all, live) : undefined;
432
+ return {
433
+ total: all.length,
434
+ boundCount: bound.length,
435
+ nativeCount: native.length,
436
+ bound,
437
+ native,
438
+ ...(reconciliation ? { reconciliation } : {}),
439
+ };
440
+ },
441
+ },
442
+ {
443
+ name: 'agent_bridge_prune',
444
+ description: "Reap orphaned Claude-Code-bound records — bound agents whose Claude Code agent is no longer live. Pass the CURRENT live Claude Code agent names as `live`; any bound record NOT in that list is removed from the agent store and from every swarm roster. Native (unbound) Swarmdo agents are never touched. Idempotent — use it to stop stale bindings accumulating across sessions.",
445
+ category: 'agent',
446
+ inputSchema: {
447
+ type: 'object',
448
+ properties: {
449
+ live: {
450
+ type: 'array',
451
+ items: { type: 'string' },
452
+ description: 'Current live Claude Code agent names; bound records not in this list are pruned',
453
+ },
454
+ },
455
+ required: ['live'],
456
+ },
457
+ handler: async (input) => {
458
+ const liveRaw = input.live;
459
+ const live = Array.isArray(liveRaw) ? liveRaw.map(String) : [];
460
+ const all = Object.values(loadAllAgents());
461
+ const toPrune = orphanedAgentIds(all, live);
462
+ if (toPrune.length === 0)
463
+ return { pruned: [], count: 0 };
464
+ // Remove from the canonical agent store (bound records always live here).
465
+ const store = loadAgentStore();
466
+ for (const id of toPrune)
467
+ delete store.agents[id];
468
+ saveAgentStore(store);
469
+ // Remove from any swarm roster so swarm_status doesn't report ghosts.
470
+ try {
471
+ const { loadSwarmStore: _loadSwarmStore, saveSwarmStore: _saveSwarmStore } = await import('./swarm-tools.js');
472
+ const swarmStore = _loadSwarmStore();
473
+ const pruneSet = new Set(toPrune);
474
+ let changed = false;
475
+ for (const swarm of Object.values(swarmStore.swarms)) {
476
+ if (Array.isArray(swarm.agents)) {
477
+ const before = swarm.agents.length;
478
+ swarm.agents = swarm.agents.filter((a) => !pruneSet.has(a));
479
+ if (swarm.agents.length !== before)
480
+ changed = true;
481
+ }
482
+ }
483
+ if (changed)
484
+ _saveSwarmStore(swarmStore);
485
+ }
486
+ catch {
487
+ /* swarm store optional — records already removed from the agent store */
488
+ }
489
+ return { pruned: toPrune, count: toPrune.length };
490
+ },
491
+ },
326
492
  {
327
493
  // ADR-095 G3 — spawn + execute fused. Closes the #1 UX trap from the
328
494
  // 2026-04 audit (@roman-rr): `agent_spawn` looks like it spawns a worker
@@ -23,6 +23,7 @@ export { licenseTools } from './license-tools.js';
23
23
  export { applyTools } from './apply-tools.js';
24
24
  export { hotspotsTools } from './hotspots-tools.js';
25
25
  export { couplingTools } from './coupling-tools.js';
26
+ export { ownershipTools } from './ownership-tools.js';
26
27
  export { affectedTools } from './affected-tools.js';
27
28
  export { cyclesTools } from './cycles-tools.js';
28
29
  export { testreportTools } from './testreport-tools.js';
@@ -22,6 +22,7 @@ export { licenseTools } from './license-tools.js';
22
22
  export { applyTools } from './apply-tools.js';
23
23
  export { hotspotsTools } from './hotspots-tools.js';
24
24
  export { couplingTools } from './coupling-tools.js';
25
+ export { ownershipTools } from './ownership-tools.js';
25
26
  export { affectedTools } from './affected-tools.js';
26
27
  export { cyclesTools } from './cycles-tools.js';
27
28
  export { testreportTools } from './testreport-tools.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Ownership MCP Tool
3
+ *
4
+ * Let an agent ask "who owns this file, and is it a bus-factor-1 key-person
5
+ * file?" and get ranked JSON — the risk/reviewer dimension it is otherwise
6
+ * blind to. An agent about to refactor a fragile single-owner file gets the
7
+ * signal that the change needs extra care, and "who should review this" gets a
8
+ * data-backed answer (the file's main-dev). Completes the git-mining trio's
9
+ * agent-callable set (hotspots + coupling + ownership). Shares the pure engine
10
+ * in ../ownership/ownership.ts with the CLI; captures git history via subprocess.
11
+ */
12
+ import type { MCPTool } from './types.js';
13
+ export declare const ownershipTools: MCPTool[];
14
+ //# sourceMappingURL=ownership-tools.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Ownership MCP Tool
3
+ *
4
+ * Let an agent ask "who owns this file, and is it a bus-factor-1 key-person
5
+ * file?" and get ranked JSON — the risk/reviewer dimension it is otherwise
6
+ * blind to. An agent about to refactor a fragile single-owner file gets the
7
+ * signal that the change needs extra care, and "who should review this" gets a
8
+ * data-backed answer (the file's main-dev). Completes the git-mining trio's
9
+ * agent-callable set (hotspots + coupling + ownership). Shares the pure engine
10
+ * in ../ownership/ownership.ts with the CLI; captures git history via subprocess.
11
+ */
12
+ import { execFileSync } from 'node:child_process';
13
+ import { parseGitLog } from '../hotspots/hotspots.js';
14
+ import { computeOwnership, repoBusFactor } from '../ownership/ownership.js';
15
+ import { normalizeSince } from '../util/since.js';
16
+ const ownershipTool = {
17
+ name: 'ownership',
18
+ description: 'Map per-file authorship concentration + bus factor from git history — "who owns each file, and what breaks if they leave?" (code-maat main-dev / knowledge map). Returns ranked JSON, most-fragile-first, so an agent editing a file knows if it is a single-owner (bus factor 1) key-person risk that needs extra care, and who its main-dev reviewer is. Includes a repo-level truck factor. Runs in a git repository.',
19
+ category: 'ownership',
20
+ tags: ['git', 'ownership', 'bus-factor', 'knowledge-map', 'risk', 'review'],
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ path: { type: 'string', description: 'repo path to run in (default cwd)' },
25
+ since: { type: 'string', description: 'history window, e.g. 90d or "3 months ago" (default 1 year)' },
26
+ top: { type: 'number', description: 'keep only the top N files (default 40; 0 = all)' },
27
+ minChurn: { type: 'number', description: 'drop files with total churn below N (default 1)' },
28
+ },
29
+ },
30
+ handler: async (params) => {
31
+ const cwd = typeof params.path === 'string' ? params.path : process.cwd();
32
+ const since = typeof params.since === 'string' ? params.since : '1 year ago';
33
+ const top = typeof params.top === 'number' ? params.top : 40;
34
+ const minChurn = typeof params.minChurn === 'number' ? params.minChurn : 1;
35
+ // Same `--numstat` dump `hotspots`/`coupling` parse; `%aN` folds author
36
+ // name/email variants through .mailmap so one person is one owner.
37
+ const args = ['log', '--no-merges', '--numstat', `--since=${normalizeSince(since)}`, '--format=format:%x01%H%x1f%aN%x1f%aI'];
38
+ let raw;
39
+ try {
40
+ raw = execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 128 * 1024 * 1024 });
41
+ }
42
+ catch {
43
+ return { error: true, message: 'git log failed — not a git repository?' };
44
+ }
45
+ const commits = parseGitLog(raw);
46
+ const ownership = computeOwnership(commits, { minChurn, top: top > 0 ? top : undefined });
47
+ return { since, minChurn, repoBusFactor: repoBusFactor(commits), count: ownership.length, ownership };
48
+ },
49
+ };
50
+ export const ownershipTools = [ownershipTool];
51
+ //# sourceMappingURL=ownership-tools.js.map
@@ -28,6 +28,7 @@ const LEAN_GROUPS = [
28
28
  'apply', // apply_patch — fuzzy unified-diff applier for agent edits
29
29
  'hotspots', // hotspots — git-history change-risk ranking ("where's the debt?")
30
30
  'coupling', // coupling — files that change together (temporal co-change), complements affected
31
+ 'ownership', // ownership — per-file bus factor / knowledge map (who owns it, key-person risk)
31
32
  'affected', // affected — impacted files/tests from a change via the import graph
32
33
  'cycles', // cycles — circular-import detection via the import graph
33
34
  'testreport', // testreport — JUnit/TAP → structured failure digest (pairs w/ repair)
@@ -32,6 +32,39 @@ interface SwarmStore {
32
32
  }
33
33
  export declare function loadSwarmStore(): SwarmStore;
34
34
  export declare function saveSwarmStore(store: SwarmStore): void;
35
+ export interface CreateSwarmOptions {
36
+ topology?: string;
37
+ maxAgents?: number;
38
+ strategy?: string;
39
+ config?: Record<string, unknown>;
40
+ /**
41
+ * When true, do NOT stamp the host `pid` — the swarm is a persistent logical
42
+ * coordination record, not tied to one process's lifetime, so it survives
43
+ * ephemeral CLI invocations (the agent bridge) instead of being reaped by the
44
+ * #1799 PID-orphan check the instant the creating process exits. Such swarms
45
+ * still expire via the 24h idle-TTL fallback. Daemon-hosted swarms
46
+ * (`swarm_init`) leave this false so a dead host IS reaped promptly.
47
+ */
48
+ persistent?: boolean;
49
+ }
50
+ /**
51
+ * Create + persist a swarm. Extracted from the `swarm_init` handler so other
52
+ * subsystems (the Claude-Code agent bridge) can spin up a swarm from config
53
+ * WITHOUT duplicating the store-write. Throws on an invalid topology. Shared
54
+ * single source of truth — no parallel swarm creation.
55
+ */
56
+ export declare function createSwarm(opts: CreateSwarmOptions): SwarmState;
57
+ /**
58
+ * Return the most-recent RUNNING swarm, or create one from `defaults` if none
59
+ * exists. Used by the agent bridge so registering a Claude Code agent
60
+ * auto-spins-up a swarm (from the project's anti-drift config) and enrolls it,
61
+ * instead of leaving the agent unattached.
62
+ */
63
+ export declare function ensureActiveSwarm(defaults: CreateSwarmOptions): {
64
+ swarmId: string;
65
+ topology: string;
66
+ created: boolean;
67
+ };
35
68
  export declare const swarmTools: MCPTool[];
36
69
  export {};
37
70
  //# sourceMappingURL=swarm-tools.d.ts.map
@@ -109,6 +109,64 @@ export function saveSwarmStore(store) {
109
109
  const VALID_TOPOLOGIES = new Set([
110
110
  'hierarchical', 'mesh', 'hierarchical-mesh', 'ring', 'star', 'hybrid', 'adaptive',
111
111
  ]);
112
+ /**
113
+ * Create + persist a swarm. Extracted from the `swarm_init` handler so other
114
+ * subsystems (the Claude-Code agent bridge) can spin up a swarm from config
115
+ * WITHOUT duplicating the store-write. Throws on an invalid topology. Shared
116
+ * single source of truth — no parallel swarm creation.
117
+ */
118
+ export function createSwarm(opts) {
119
+ const topology = opts.topology || 'hierarchical-mesh';
120
+ const maxAgents = Math.min(Math.max(opts.maxAgents || 15, 1), 50);
121
+ const strategy = opts.strategy || 'specialized';
122
+ const config = opts.config || {};
123
+ if (!VALID_TOPOLOGIES.has(topology)) {
124
+ throw new Error(`Invalid topology: ${topology}. Valid: ${[...VALID_TOPOLOGIES].join(', ')}`);
125
+ }
126
+ const swarmId = `swarm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
127
+ const now = new Date().toISOString();
128
+ const swarmState = {
129
+ swarmId,
130
+ topology,
131
+ maxAgents,
132
+ status: 'running',
133
+ agents: [],
134
+ tasks: [],
135
+ config: {
136
+ topology,
137
+ maxAgents,
138
+ strategy,
139
+ communicationProtocol: config.communicationProtocol || 'message-bus',
140
+ autoScaling: config.autoScaling ?? true,
141
+ consensusMechanism: config.consensusMechanism || 'majority',
142
+ },
143
+ createdAt: now,
144
+ updatedAt: now,
145
+ // Persistent (bridge) swarms omit the pid so they aren't reaped on process
146
+ // exit; daemon-hosted swarms record it for prompt orphan reconciliation.
147
+ ...(opts.persistent ? {} : { pid: process.pid }),
148
+ };
149
+ const store = loadSwarmStore();
150
+ store.swarms[swarmId] = swarmState;
151
+ saveSwarmStore(store);
152
+ return swarmState;
153
+ }
154
+ /**
155
+ * Return the most-recent RUNNING swarm, or create one from `defaults` if none
156
+ * exists. Used by the agent bridge so registering a Claude Code agent
157
+ * auto-spins-up a swarm (from the project's anti-drift config) and enrolls it,
158
+ * instead of leaving the agent unattached.
159
+ */
160
+ export function ensureActiveSwarm(defaults) {
161
+ const store = loadSwarmStore();
162
+ const running = Object.values(store.swarms)
163
+ .filter((s) => s.status === 'running')
164
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
165
+ if (running)
166
+ return { swarmId: running.swarmId, topology: running.topology, created: false };
167
+ const s = createSwarm({ ...defaults, persistent: true });
168
+ return { swarmId: s.swarmId, topology: s.topology, created: true };
169
+ }
112
170
  export const swarmTools = [
113
171
  {
114
172
  name: 'swarm_init',
@@ -135,49 +193,26 @@ export const swarmTools = [
135
193
  if (!v.valid)
136
194
  return { success: false, error: v.error };
137
195
  }
138
- const topology = input.topology || 'hierarchical-mesh';
139
- const maxAgents = Math.min(Math.max(input.maxAgents || 15, 1), 50);
140
196
  const strategy = input.strategy || 'specialized';
141
- const config = (input.config || {});
142
- if (!VALID_TOPOLOGIES.has(topology)) {
143
- return {
144
- success: false,
145
- error: `Invalid topology: ${topology}. Valid: ${[...VALID_TOPOLOGIES].join(', ')}`,
146
- };
147
- }
148
- const swarmId = `swarm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
149
- const now = new Date().toISOString();
150
- const swarmState = {
151
- swarmId,
152
- topology,
153
- maxAgents,
154
- status: 'running',
155
- agents: [],
156
- tasks: [],
157
- config: {
158
- topology,
159
- maxAgents,
197
+ let swarmState;
198
+ try {
199
+ swarmState = createSwarm({
200
+ topology: input.topology,
201
+ maxAgents: input.maxAgents,
160
202
  strategy,
161
- communicationProtocol: config.communicationProtocol || 'message-bus',
162
- autoScaling: config.autoScaling ?? true,
163
- consensusMechanism: config.consensusMechanism || 'majority',
164
- },
165
- createdAt: now,
166
- updatedAt: now,
167
- // #1799 — record host PID so subsequent loads can detect orphans
168
- // when this process exits without a graceful swarm_shutdown.
169
- pid: process.pid,
170
- };
171
- const store = loadSwarmStore();
172
- store.swarms[swarmId] = swarmState;
173
- saveSwarmStore(store);
203
+ config: (input.config || {}),
204
+ });
205
+ }
206
+ catch (e) {
207
+ return { success: false, error: e.message };
208
+ }
174
209
  return {
175
210
  success: true,
176
- swarmId,
177
- topology,
211
+ swarmId: swarmState.swarmId,
212
+ topology: swarmState.topology,
178
213
  strategy,
179
- maxAgents,
180
- initializedAt: now,
214
+ maxAgents: swarmState.maxAgents,
215
+ initializedAt: swarmState.createdAt,
181
216
  config: swarmState.config,
182
217
  persisted: true,
183
218
  };