@desplega.ai/agent-swarm 1.103.0 → 1.104.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.
Files changed (122) hide show
  1. package/README.md +2 -0
  2. package/dist/{actions-7txktrdj.js → actions-2expzt47.js} +6 -6
  3. package/dist/{app-xm3xsdye.js → app-dr8hwx7z.js} +3 -3
  4. package/dist/{assistant-qpv63wk6.js → assistant-9rhgsw7v.js} +9 -10
  5. package/dist/{boot-reembed-chxhrwv8.js → boot-reembed-3q1aywnz.js} +5 -4
  6. package/dist/{boot-reembed-9cxqka3e.js → boot-reembed-9kt97g1z.js} +4 -3
  7. package/dist/{boot-scrub-logs-8qtfkcz8.js → boot-scrub-logs-vskq108e.js} +2 -2
  8. package/dist/{cli-fedn86nz.js → cli-0jw6j755.js} +8 -3
  9. package/dist/{cli-yeemwevj.js → cli-23zk7xqy.js} +20 -16
  10. package/dist/{cli-v9283nma.js → cli-2g9jf9ek.js} +1 -1
  11. package/dist/{cli-rvae030h.js → cli-2hap19dv.js} +3 -3
  12. package/dist/{cli-wjyyv518.js → cli-3j01d33e.js} +9 -14
  13. package/dist/{cli-8hyv6c2v.js → cli-54h5daha.js} +41 -23
  14. package/dist/{cli-fygaw191.js → cli-5pf0b84v.js} +2 -2
  15. package/dist/{cli-m8f6qzck.js → cli-7v4y3hyd.js} +1 -1
  16. package/dist/{cli-fwxdt7kt.js → cli-d4z1csms.js} +30 -5
  17. package/dist/{cli-jt9d9fjw.js → cli-e7bwjntj.js} +1 -1
  18. package/dist/{cli-s14sb64w.js → cli-etvgz5x8.js} +5 -7
  19. package/dist/{cli-2qynerth.js → cli-gj2n2ec1.js} +1 -1
  20. package/dist/{cli-dz3d6zjg.js → cli-nzc0h54v.js} +121 -2
  21. package/dist/{cli-8w95v19d.js → cli-p4cj4vhc.js} +351 -24
  22. package/dist/{cli-xmtsan9k.js → cli-q5tshxd6.js} +1 -1
  23. package/dist/{http-zts4haq7.js → cli-qvzgncd8.js} +83966 -76422
  24. package/dist/{cli-4fnp2tc3.js → cli-s42f54ja.js} +1 -1
  25. package/dist/{cli-krvxq8bc.js → cli-t9v14cnd.js} +23 -4
  26. package/dist/{cli-gzepjz54.js → cli-v1syqkcb.js} +900 -352
  27. package/dist/{cli-ksq38x4n.js → cli-w1nfskkg.js} +2 -2
  28. package/dist/{cli-ns0r7mkr.js → cli-y9p3e94h.js} +3 -3
  29. package/dist/{cli-85pf4z38.js → cli-yjt8a36k.js} +3 -3
  30. package/dist/{cli-yfwwjeft.js → cli-yvektv79.js} +1 -1
  31. package/dist/{cli-kk51f3tf.js → cli-zaxksbgj.js} +5 -5
  32. package/dist/{cli-rttgde5f.js → cli-zghef0em.js} +1 -1
  33. package/dist/cli.js +10 -10
  34. package/dist/{commands-aqn63mcw.js → commands-wfrkef57.js} +2 -2
  35. package/dist/{db-azrvwsnj.js → db-c42pxt7h.js} +2 -2
  36. package/dist/{handlers-hf61har3.js → handlers-n36zkfvx.js} +10 -12
  37. package/dist/{hook-306p3yz4.js → hook-gw3vhv6s.js} +7 -5
  38. package/dist/http-59ygpn2h.js +17263 -0
  39. package/dist/{index-h2yqjyk0.js → index-881c1baf.js} +9 -8
  40. package/dist/{index-29vg51x6.js → index-jca7jdpt.js} +23 -10
  41. package/dist/{index-bfh9hgek.js → index-xf6fp4y1.js} +5 -5
  42. package/dist/{index-3c651yfk.js → index-xz8vaa5c.js} +14 -11
  43. package/dist/{keepalive-a0dg5pds.js → keepalive-mj4302vw.js} +4 -4
  44. package/dist/{lead-7mcxedkc.js → lead-qm0xjvkv.js} +19 -19
  45. package/dist/{maintenance-mrzqd53t.js → maintenance-xppxbe4t.js} +5 -4
  46. package/dist/{onboard-4dkpv9wa.js → onboard-ejx6yxh8.js} +2 -2
  47. package/dist/{otel-impl-jt7gpyp9.js → otel-impl-zs5exkpk.js} +1 -1
  48. package/dist/{pricing-refresh-xqnzh8dt.js → pricing-refresh-nqyf8bkb.js} +4 -4
  49. package/dist/{seed-pricing-56tjm1fj.js → seed-pricing-w3x550cj.js} +3 -3
  50. package/dist/{setup-tbwk8j10.js → setup-7729ktfj.js} +2 -2
  51. package/dist/{worker-e03za92t.js → worker-4ttemwhw.js} +19 -19
  52. package/openapi.json +90 -4
  53. package/package.json +3 -3
  54. package/src/be/memory/constants.ts +6 -0
  55. package/src/be/memory/providers/sqlite-store.ts +468 -71
  56. package/src/be/memory/raters/retrieval.ts +4 -2
  57. package/src/be/memory/reranker.ts +5 -2
  58. package/src/be/memory/retrieval-store.ts +2 -0
  59. package/src/be/memory/types.ts +46 -0
  60. package/src/be/migrations/099_memory_structured_key_versioning.sql +41 -0
  61. package/src/be/migrations/100_memory_retrieval_source.sql +8 -0
  62. package/src/be/migrations/101_script_connections.sql +67 -0
  63. package/src/be/script-connections.ts +744 -0
  64. package/src/be/script-credential-broker.ts +38 -0
  65. package/src/be/scripts/typecheck.ts +23 -2
  66. package/src/be/seed-scripts/catalog/boot-triage.inline.ts +16 -4
  67. package/src/be/seed-scripts/catalog/boot-triage.ts +16 -4
  68. package/src/commands/runner.ts +8 -6
  69. package/src/heartbeat/heartbeat.ts +42 -1
  70. package/src/heartbeat/index.ts +7 -1
  71. package/src/http/memory.ts +127 -14
  72. package/src/http/scripts.ts +15 -14
  73. package/src/scripts-runtime/api-client.ts +100 -0
  74. package/src/scripts-runtime/api-types.ts +45 -0
  75. package/src/scripts-runtime/credential-broker/README.md +40 -0
  76. package/src/scripts-runtime/credential-broker/broker.ts +51 -0
  77. package/src/scripts-runtime/credential-broker/default-bindings.ts +19 -0
  78. package/src/scripts-runtime/credential-broker/fetch-patch.ts +102 -0
  79. package/src/scripts-runtime/credential-broker/index.ts +17 -0
  80. package/src/scripts-runtime/credential-broker/store.ts +72 -0
  81. package/src/scripts-runtime/credential-broker/types.ts +54 -0
  82. package/src/scripts-runtime/ctx.ts +11 -1
  83. package/src/scripts-runtime/egress-secrets.ts +14 -76
  84. package/src/scripts-runtime/eval-harness.ts +1 -1
  85. package/src/scripts-runtime/executors/types.ts +5 -5
  86. package/src/scripts-runtime/loader.ts +6 -1
  87. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  88. package/src/scripts-runtime/types/stdlib.d.ts +11 -0
  89. package/src/scripts-runtime/types/swarm-sdk.d.ts +11 -0
  90. package/src/server.ts +6 -0
  91. package/src/tests/credential-broker.test.ts +281 -0
  92. package/src/tests/heartbeat.test.ts +239 -0
  93. package/src/tests/memory-edit.test.ts +177 -0
  94. package/src/tests/memory-hybrid.test.ts +212 -0
  95. package/src/tests/runner-repo-autostash.test.ts +39 -8
  96. package/src/tests/script-connections.test.ts +360 -0
  97. package/src/tests/scripts-http.test.ts +16 -10
  98. package/src/tests/scripts-runtime-secret-egress.test.ts +26 -5
  99. package/src/tests/swarm-config-reserved-keys.test.ts +23 -0
  100. package/src/tests/update-schedule-mcp-tool.test.ts +83 -4
  101. package/src/tools/credential-bindings/index.ts +1 -0
  102. package/src/tools/credential-bindings/tool.ts +238 -0
  103. package/src/tools/memory-edit.ts +148 -0
  104. package/src/tools/memory-search.ts +17 -10
  105. package/src/tools/schedules/delete-schedule.ts +18 -8
  106. package/src/tools/schedules/update-schedule.ts +4 -9
  107. package/src/tools/script-connections/index.ts +1 -0
  108. package/src/tools/script-connections/tool.ts +167 -0
  109. package/src/tools/swarm-config/delete-config.ts +5 -0
  110. package/src/tools/swarm-config/set-config.ts +23 -1
  111. package/src/tools/tool-config.ts +6 -1
  112. package/src/types.ts +6 -0
  113. package/src/workflows/executors/swarm-script.ts +2 -0
  114. package/templates/community/dora-metrics/PLAYBOOK.md +294 -0
  115. package/templates/community/dora-metrics/README.md +39 -0
  116. package/templates/community/dora-metrics/lead-prompt.md +56 -0
  117. package/templates/community/dora-metrics/report.mjs +393 -0
  118. package/templates/community/dora-metrics/run.sh +113 -0
  119. package/templates/schedules/weekly-dora-metrics/config.json +13 -0
  120. package/templates/schedules/weekly-dora-metrics/content.md +61 -0
  121. package/dist/cli-2307phk1.js +0 -24223
  122. package/dist/cli-2deg0cen.js +0 -120
@@ -0,0 +1,167 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById } from "@/be/db";
4
+ import {
5
+ listScriptConnections,
6
+ setScriptConnectionEnabled,
7
+ upsertCredentialBinding,
8
+ upsertScriptConnection,
9
+ } from "@/be/script-connections";
10
+ import { placeholderForConfigKey } from "@/scripts-runtime/credential-broker";
11
+ import { createToolRegistrar } from "@/tools/utils";
12
+
13
+ const scriptConnectionsInputSchema = z.object({
14
+ action: z
15
+ .enum(["list", "upsert-openapi", "disable"])
16
+ .describe("List, create/update, or disable a script connection."),
17
+ id: z.string().uuid().optional(),
18
+ slug: z.string().min(1).max(80).optional(),
19
+ displayName: z.string().max(160).optional(),
20
+ scope: z.enum(["global", "agent", "repo"]).default("global").optional(),
21
+ scopeId: z.string().uuid().nullable().optional(),
22
+ baseUrl: z.string().url().optional(),
23
+ allowedHosts: z.array(z.string().min(1)).optional(),
24
+ credentialBindingId: z.string().uuid().nullable().optional(),
25
+ configKey: z.string().min(1).max(255).optional(),
26
+ headerTemplate: z.string().min(1).optional(),
27
+ queryTemplate: z.string().min(1).optional(),
28
+ openapiSpecJson: z.string().optional(),
29
+ enabled: z.boolean().default(true).optional(),
30
+ });
31
+
32
+ const scriptConnectionsOutputSchema = z.object({
33
+ yourAgentId: z.string().uuid().optional(),
34
+ success: z.boolean(),
35
+ message: z.string(),
36
+ connections: z.array(z.unknown()),
37
+ });
38
+
39
+ export const registerScriptConnectionsTool = (server: McpServer) => {
40
+ createToolRegistrar(server)(
41
+ "script-connections",
42
+ {
43
+ title: "Script Connections",
44
+ description:
45
+ "Lead-only registry management for scripts ctx.api/ctx.mcp connections. Phase 1 supports OpenAPI ctx.api connections with generated args and response types.",
46
+ annotations: { idempotentHint: true },
47
+ inputSchema: scriptConnectionsInputSchema,
48
+ outputSchema: scriptConnectionsOutputSchema,
49
+ },
50
+ async (args, requestInfo) => {
51
+ if (!requestInfo.agentId) {
52
+ return {
53
+ content: [{ type: "text", text: 'Agent ID not found. Set the "X-Agent-ID" header.' }],
54
+ structuredContent: {
55
+ success: false,
56
+ message: 'Agent ID not found. Set the "X-Agent-ID" header.',
57
+ connections: [],
58
+ },
59
+ };
60
+ }
61
+
62
+ const agent = getAgentById(requestInfo.agentId);
63
+ if (!agent?.isLead) {
64
+ return {
65
+ content: [{ type: "text", text: "Only the lead can manage script connections." }],
66
+ structuredContent: {
67
+ yourAgentId: requestInfo.agentId,
68
+ success: false,
69
+ message: "Only the lead can manage script connections.",
70
+ connections: [],
71
+ },
72
+ };
73
+ }
74
+
75
+ if (args.action === "list") {
76
+ const connections = listScriptConnections({ includeDisabled: true });
77
+ return {
78
+ content: [{ type: "text", text: `Found ${connections.length} script connection(s).` }],
79
+ structuredContent: {
80
+ yourAgentId: requestInfo.agentId,
81
+ success: true,
82
+ message: `Found ${connections.length} script connection(s).`,
83
+ connections,
84
+ },
85
+ };
86
+ }
87
+
88
+ if (args.action === "disable") {
89
+ if (!args.id) {
90
+ return {
91
+ content: [{ type: "text", text: "id is required for disable." }],
92
+ structuredContent: {
93
+ yourAgentId: requestInfo.agentId,
94
+ success: false,
95
+ message: "id is required for disable.",
96
+ connections: listScriptConnections({ includeDisabled: true }),
97
+ },
98
+ };
99
+ }
100
+ setScriptConnectionEnabled(args.id, false);
101
+ const connections = listScriptConnections({ includeDisabled: true });
102
+ return {
103
+ content: [{ type: "text", text: "Script connection disabled." }],
104
+ structuredContent: {
105
+ yourAgentId: requestInfo.agentId,
106
+ success: true,
107
+ message: "Script connection disabled.",
108
+ connections,
109
+ },
110
+ };
111
+ }
112
+
113
+ if (!args.slug || !args.baseUrl || !args.openapiSpecJson) {
114
+ return {
115
+ content: [{ type: "text", text: "slug, baseUrl, and openapiSpecJson are required." }],
116
+ structuredContent: {
117
+ yourAgentId: requestInfo.agentId,
118
+ success: false,
119
+ message: "slug, baseUrl, and openapiSpecJson are required.",
120
+ connections: listScriptConnections({ includeDisabled: true }),
121
+ },
122
+ };
123
+ }
124
+
125
+ let credentialBindingId = args.credentialBindingId ?? null;
126
+ if (!credentialBindingId && args.configKey) {
127
+ const placeholder = placeholderForConfigKey(args.configKey);
128
+ const binding = upsertCredentialBinding({
129
+ configKey: args.configKey,
130
+ allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
131
+ headerTemplate: args.headerTemplate ?? `Authorization: Bearer ${placeholder}`,
132
+ queryTemplate: args.queryTemplate,
133
+ scope: args.scope ?? "global",
134
+ scopeId: args.scope === "global" ? null : (args.scopeId ?? null),
135
+ });
136
+ credentialBindingId = binding.id;
137
+ }
138
+
139
+ const connection = upsertScriptConnection({
140
+ id: args.id,
141
+ slug: args.slug,
142
+ displayName: args.displayName,
143
+ kind: "openapi",
144
+ scope: args.scope ?? "global",
145
+ scopeId: args.scope === "global" ? null : (args.scopeId ?? null),
146
+ baseUrl: args.baseUrl,
147
+ allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
148
+ credentialBindingId,
149
+ openapiSpecJson: args.openapiSpecJson,
150
+ enabled: args.enabled !== false,
151
+ });
152
+
153
+ const connections = listScriptConnections({ includeDisabled: true });
154
+ return {
155
+ content: [{ type: "text", text: `Script connection ${connection.slug} saved.` }],
156
+ structuredContent: {
157
+ yourAgentId: requestInfo.agentId,
158
+ success: !connection.generationError,
159
+ message: connection.generationError
160
+ ? `Saved but generation failed: ${connection.generationError}`
161
+ : `Script connection ${connection.slug} saved.`,
162
+ connections,
163
+ },
164
+ };
165
+ },
166
+ );
167
+ };
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { deleteSwarmConfig, getSwarmConfigLookupById } from "@/be/db";
4
+ import { scheduleIntegrationsReload } from "@/http/core";
4
5
  import { createToolRegistrar } from "@/tools/utils";
5
6
 
6
7
  export const registerDeleteConfigTool = (server: McpServer) => {
@@ -58,6 +59,10 @@ export const registerDeleteConfigTool = (server: McpServer) => {
58
59
  };
59
60
  }
60
61
 
62
+ if (existing.scope === "global") {
63
+ scheduleIntegrationsReload();
64
+ }
65
+
61
66
  return {
62
67
  content: [
63
68
  {
@@ -1,11 +1,13 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
- import { maskSecrets, upsertSwarmConfig } from "@/be/db";
3
+ import { getAgentById, maskSecrets, upsertSwarmConfig } from "@/be/db";
4
4
  import {
5
5
  isReservedConfigKey,
6
6
  reservedKeyError,
7
7
  validateConfigValue,
8
8
  } from "@/be/swarm-config-guard";
9
+ import { scheduleIntegrationsReload } from "@/http/core";
10
+ import { CREDENTIAL_BINDINGS_CONFIG_KEY } from "@/scripts-runtime/credential-broker";
9
11
  import { createToolRegistrar } from "@/tools/utils";
10
12
  import { SwarmConfigSchema, SwarmConfigScopeSchema } from "@/types";
11
13
 
@@ -93,6 +95,22 @@ export const registerSetConfigTool = (server: McpServer) => {
93
95
  };
94
96
  }
95
97
 
98
+ if (key.toUpperCase() === CREDENTIAL_BINDINGS_CONFIG_KEY) {
99
+ const agent = getAgentById(requestInfo.agentId);
100
+ if (!agent?.isLead) {
101
+ const message =
102
+ "Only the lead can manage SCRIPT_CREDENTIAL_BINDINGS. Use the credential-bindings tool.";
103
+ return {
104
+ content: [{ type: "text", text: message }],
105
+ structuredContent: {
106
+ yourAgentId: requestInfo.agentId,
107
+ success: false,
108
+ message,
109
+ },
110
+ };
111
+ }
112
+ }
113
+
96
114
  const validationError = validateConfigValue(key, value);
97
115
  if (validationError) {
98
116
  return {
@@ -115,6 +133,10 @@ export const registerSetConfigTool = (server: McpServer) => {
115
133
  description,
116
134
  });
117
135
 
136
+ if (scope === "global") {
137
+ scheduleIntegrationsReload();
138
+ }
139
+
118
140
  const [masked] = maskSecrets([config]);
119
141
 
120
142
  return {
@@ -66,11 +66,16 @@ export const DEFERRED_TOOLS = new Set([
66
66
  "list-services",
67
67
  "update-service-status",
68
68
 
69
- // Config (4)
69
+ // Config (5)
70
70
  "set-config",
71
71
  "get-config",
72
72
  "list-config",
73
73
  "delete-config",
74
+ "credential-bindings",
75
+ "script-connections",
76
+
77
+ // Memory (1)
78
+ "memory-edit",
74
79
 
75
80
  // Repos (2)
76
81
  "get-repos",
package/src/types.ts CHANGED
@@ -1001,6 +1001,8 @@ export const EventNameSchema = z.enum([
1001
1001
  "system.error",
1002
1002
  // Script catalog events
1003
1003
  "script.global_upsert",
1004
+ // Schedule events
1005
+ "schedule.deleted",
1004
1006
  ]);
1005
1007
 
1006
1008
  export const SwarmEventSchema = z.object({
@@ -1146,6 +1148,7 @@ export const AgentMemorySchema = z.object({
1146
1148
  id: z.string().uuid(),
1147
1149
  agentId: z.string().uuid().nullable(),
1148
1150
  scope: AgentMemoryScopeSchema,
1151
+ key: z.string().nullable().optional(),
1149
1152
  name: z.string().min(1).max(500),
1150
1153
  content: z.string(),
1151
1154
  summary: z.string().nullable(),
@@ -1156,10 +1159,13 @@ export const AgentMemorySchema = z.object({
1156
1159
  totalChunks: z.number().int().min(1).default(1),
1157
1160
  tags: z.array(z.string()),
1158
1161
  createdAt: z.string(),
1162
+ updatedAt: z.string().nullable().optional(),
1159
1163
  accessedAt: z.string(),
1160
1164
  expiresAt: z.string().nullable().optional(),
1161
1165
  accessCount: z.number().int().min(0).default(0).optional(),
1162
1166
  embeddingModel: z.string().nullable().optional(),
1167
+ contentHash: z.string().nullable().optional(),
1168
+ version: z.number().int().min(1).default(1).optional(),
1163
1169
  });
1164
1170
 
1165
1171
  export type AgentMemoryScope = z.infer<typeof AgentMemoryScopeSchema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { buildScriptCredentialBindings } from "../../be/script-credential-broker";
2
3
  import { getScript, getScriptVersion } from "../../be/scripts/db";
3
4
  import { DEFAULT_SCRIPT_RESOURCES } from "../../scripts-runtime/executors/types";
4
5
  import { runScript } from "../../scripts-runtime/loader";
@@ -72,6 +73,7 @@ export class SwarmScriptExecutor extends BaseExecutor<
72
73
  args: config.args,
73
74
  fsMode: "none",
74
75
  agentId: agentId ?? "workflow",
76
+ egressSecrets: buildScriptCredentialBindings({ agentId: agentId ?? undefined }),
75
77
  timeoutMs: config.timeoutMs,
76
78
  });
77
79
 
@@ -0,0 +1,294 @@
1
+ # DORA Metrics for Your Codebase, on Autopilot
2
+
3
+ An Agent-Swarm playbook template for running recurring DORA metrics reports on any Git repository that has a reliable deployment tag signal.
4
+
5
+ ## What You Get
6
+
7
+ This setup gives your swarm a stable DORA report page for one repository:
8
+
9
+ - Deployment Frequency: release throughput over a configurable window.
10
+ - Lead Time for Changes: median time from commit to the release tag that deployed it.
11
+ - Change Failure Rate: proxy estimate from revert/hotfix-style remediation signals.
12
+ - Failed Deployment Recovery Time: proxy estimate from the failed-release tag to the fixing tag.
13
+ - A weekly refresh that updates the same page in place, so the URL does not change.
14
+
15
+ The key caveat is part of the product: Deployment Frequency and Lead Time for Changes are exact when release tags map to production deployments. Change Failure Rate and Failed Deployment Recovery Time are proxy estimates until you connect a formal incident source.
16
+
17
+ ## Template Files
18
+
19
+ The community template lives in `templates/community/dora-metrics/` and contains:
20
+
21
+ - `PLAYBOOK.md`: this playbook.
22
+ - `run.sh`: the parameterized runner.
23
+ - `report.mjs`: the static report generator.
24
+ - `lead-prompt.md`: the copy-paste Lead kickoff prompt.
25
+
26
+ Install shape:
27
+
28
+ ```bash
29
+ mkdir -p /workspace/dora-metrics
30
+ cp templates/community/dora-metrics/run.sh /workspace/dora-metrics/
31
+ cp templates/community/dora-metrics/report.mjs /workspace/dora-metrics/
32
+ cp templates/community/dora-metrics/lead-prompt.md /workspace/dora-metrics/
33
+ chmod +x /workspace/dora-metrics/run.sh
34
+ ```
35
+
36
+ Parameterize each run with environment variables:
37
+
38
+ ```bash
39
+ BASE_DIR=/workspace/dora-metrics \
40
+ REPO_NAME=my-repo \
41
+ REPO_URL=https://github.com/OWNER/REPO.git \
42
+ BRANCH=main \
43
+ TAG_PATTERN='v*' \
44
+ WINDOW_DAYS=90 \
45
+ bash /workspace/dora-metrics/run.sh
46
+ ```
47
+
48
+ ## Libraries and Runtime Shape
49
+
50
+ The workflow uses:
51
+
52
+ - Git tags: the canonical deployment event, when `TAG_PATTERN` maps 1:1 to production releases.
53
+ - Git commit history: exact commit timestamps used for lead time.
54
+ - GitHub CLI: optional but preferred for merged PR title metadata when identifying hotfix/revert signals.
55
+ - jq: required utility dependency for predictable JSON handling in the runner environment.
56
+ - Node.js: runs `report.mjs`, which computes metrics and writes static `report.html` plus `summary.json`.
57
+
58
+ The generated HTML is static. The only network fetch at view time is D3:
59
+
60
+ ```html
61
+ <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
62
+ ```
63
+
64
+ ## Template Directory Structure
65
+
66
+ Use a workspace outside the target repository so report artifacts do not pollute the codebase:
67
+
68
+ ```text
69
+ /workspace/dora-metrics/
70
+ run.sh
71
+ report.mjs
72
+ lead-prompt.md
73
+ repos/
74
+ <repo-name>/ # scratch clone, push URL disabled
75
+ out/
76
+ <repo-name>/
77
+ <YYYY-MM-DD>/
78
+ tags.tsv
79
+ recent-commits.tsv
80
+ remediation-commits.tsv
81
+ prs.json
82
+ revision.txt
83
+ revision-summary.txt
84
+ summary.json
85
+ report.html
86
+ latest-pointer.json
87
+ latest.json
88
+ latest.html
89
+ latest-pointer.json
90
+ ```
91
+
92
+ ## First-Run Behavior
93
+
94
+ `run.sh` does the following:
95
+
96
+ - Installs `git`, `jq`, and `nodejs` if missing.
97
+ - Clones the target repository into a scratch directory.
98
+ - Disables the scratch clone push URL so the scheduled job cannot push accidentally.
99
+ - Fetches the configured branch and release tags matching `TAG_PATTERN`.
100
+ - Writes release tag, recent commit, remediation commit, and optional PR metadata.
101
+ - Runs `report.mjs`.
102
+ - Copies the latest artifacts to stable `latest.html`, `latest.json`, and `latest-pointer.json` paths.
103
+
104
+ ## Runner Parameters
105
+
106
+ Set these variables before calling `run.sh`:
107
+
108
+ ```bash
109
+ BASE_DIR=/workspace/dora-metrics
110
+ REPO_NAME=my-repo
111
+ REPO_URL=https://github.com/OWNER/REPO.git
112
+ BRANCH=main
113
+ TAG_PATTERN='v*'
114
+ WINDOW_DAYS=90
115
+ HOTFIX_WINDOW_HOURS=24
116
+ LOCAL_SOURCE= # optional local git clone seed
117
+ RUN_DATE=2026-06-26 # optional, defaults to current UTC date
118
+ ```
119
+
120
+ The runner extracts release tags with:
121
+
122
+ ```bash
123
+ git -C "$REPO_DIR" for-each-ref "refs/tags/$TAG_PATTERN" --sort=creatordate --format='%(refname:short)%09%(objectname)%09%(creatordate:iso-strict)%09%(creatordate:unix)'
124
+ ```
125
+
126
+ It extracts remediation signals with:
127
+
128
+ ```bash
129
+ git -C "$REPO_DIR" log "origin/$BRANCH" --since="$WINDOW_DAYS days ago" --grep='revert' --grep='rollback' --grep='hotfix' --grep='fix-forward' --regexp-ignore-case
130
+ ```
131
+
132
+ When authenticated, it also asks `gh pr list` for merged PR titles so hotfix-style PRs can contribute to the proxy stability keys.
133
+
134
+ ## Metric Definitions and Bands
135
+
136
+ The report uses the four DORA keys and 2024 performance bands as configurable constants in `report.mjs`.
137
+
138
+ | Metric | Source | Quality | Band logic |
139
+ |---|---|---|---|
140
+ | Deployment Frequency | `TAG_PATTERN` release tags in the window | EXACT | Elite: at least daily. High: daily to weekly. Medium: weekly to monthly. Low: slower. |
141
+ | Lead Time for Changes | Commit timestamp to containing release tag timestamp | EXACT | Elite: under 1 day. High: 1 day to 1 week. Medium: 1 week to 1 month. Low: slower. |
142
+ | Change Failure Rate | Releases paired to revert, rollback, hotfix, or fix-forward signals | PROXY | 2024 cluster thresholds: ~5%, ~20%, ~10%, ~40%. Medium's lower CFR than High is a known 2024 cluster anomaly. |
143
+ | Failed Deployment Recovery Time | Failed-release proxy tag to fixing tag | PROXY | Elite: under 1 hour. High/Medium: under 1 day. Low: slower. |
144
+
145
+ The stability keys are intentionally labeled as proxy/estimated because a release tag plus commit/PR title heuristic is not an incident tracker. It can undercount fix-forward/manual incidents and overcount ordinary fixes.
146
+
147
+ ## Report Generator
148
+
149
+ `report.mjs` parses the runner outputs, computes the four metrics, and embeds the final data in a static HTML file.
150
+
151
+ The generator interface:
152
+
153
+ ```bash
154
+ node /workspace/dora-metrics/report.mjs \
155
+ /workspace/dora-metrics/out/<repo-name>/<YYYY-MM-DD> \
156
+ /workspace/dora-metrics/repos/<repo-name> \
157
+ <repo-name> \
158
+ <YYYY-MM-DD> \
159
+ <BRANCH> \
160
+ <WINDOW_DAYS> \
161
+ <HOTFIX_WINDOW_HOURS> \
162
+ <TAG_PATTERN>
163
+ ```
164
+
165
+ The generated report includes:
166
+
167
+ - Four DORA metric cards.
168
+ - `EXACT` / `PROXY` quality labels.
169
+ - Deployment and lead-time charts.
170
+ - Recent deployments table.
171
+ - Proxy remediation signal table.
172
+ - D3 v7 loaded from CDN at view time.
173
+
174
+ ## Step-by-Step Playbook
175
+
176
+ 1. Confirm the deployment signal.
177
+
178
+ The default assumes `v*` tags map 1:1 to production releases. If your repository tags packages without deploying them, stop and configure a better deployment signal before using this template.
179
+
180
+ 2. Install the template.
181
+
182
+ Copy `run.sh`, `report.mjs`, and `lead-prompt.md` from `templates/community/dora-metrics/` into `/workspace/dora-metrics`, then make the runner executable.
183
+
184
+ 3. Run it once manually.
185
+
186
+ ```bash
187
+ BASE_DIR=/workspace/dora-metrics \
188
+ REPO_NAME=my-repo \
189
+ REPO_URL=https://github.com/OWNER/REPO.git \
190
+ BRANCH=main \
191
+ TAG_PATTERN='v*' \
192
+ WINDOW_DAYS=90 \
193
+ bash /workspace/dora-metrics/run.sh
194
+ ```
195
+
196
+ 4. Review local output.
197
+
198
+ ```bash
199
+ ls /workspace/dora-metrics/out/my-repo/latest.html
200
+ ls /workspace/dora-metrics/out/my-repo/latest.json
201
+ ```
202
+
203
+ 5. Publish the HTML to your swarm page system.
204
+
205
+ Create the page once, then store the returned stable page ID somewhere your scheduled task can read it. On later runs, update that same page by ID instead of creating a new page.
206
+
207
+ ```text
208
+ First run:
209
+ create page from /workspace/dora-metrics/out/my-repo/latest.html
210
+ save PAGE_ID=<stable-page-id>
211
+
212
+ Later runs:
213
+ update page PAGE_ID with /workspace/dora-metrics/out/my-repo/latest.html
214
+ ```
215
+
216
+ 6. Wire a weekly schedule.
217
+
218
+ Use a code-capable worker because this job may need to repair the script when upstream repo conventions, branch names, tag patterns, or page APIs change.
219
+
220
+ ```yaml
221
+ name: weekly-dora-metrics
222
+ cadence:
223
+ cron: "0 22 * * 0"
224
+ timezone: "UTC"
225
+ target:
226
+ worker: "<code-capable-worker>"
227
+ env:
228
+ BASE_DIR: "/workspace/dora-metrics"
229
+ REPO_NAME: "my-repo"
230
+ REPO_URL: "https://github.com/OWNER/REPO.git"
231
+ BRANCH: "main"
232
+ TAG_PATTERN: "v*"
233
+ WINDOW_DAYS: "90"
234
+ HOTFIX_WINDOW_HOURS: "24"
235
+ PAGE_ID: "<stable-page-id>"
236
+ task:
237
+ - run /workspace/dora-metrics/run.sh
238
+ - update the existing page PAGE_ID in place with latest.html
239
+ - verify D3 charts render and the exact/proxy labels are visible
240
+ - if the run fails, diagnose and repair the runner/report generator before reporting failure
241
+ ```
242
+
243
+ 7. Keep the same page URL.
244
+
245
+ The report should update in place. The page URL should not change between weekly runs.
246
+
247
+ ## Copy-Paste Lead Prompt
248
+
249
+ The canonical copy lives in `lead-prompt.md` in the template and is also included below so this playbook can stand alone.
250
+
251
+ # Lead Kickoff Prompt
252
+
253
+ Copy this into your agent-swarm Lead to bootstrap the recurring report.
254
+
255
+ ```text
256
+ Bootstrap a recurring DORA metrics report for my repository.
257
+
258
+ Parameters:
259
+ - Repository URL: <REPO_URL>
260
+ - Default branch: <BRANCH>
261
+ - Release tag pattern: <TAG_PATTERN, default v*>
262
+ - Report name/slug: <REPORT_NAME>
263
+ - Analysis window: <WINDOW_DAYS, default 90>
264
+ - Hotfix/revert matching window: <HOTFIX_WINDOW_HOURS, default 24>
265
+ - Cadence: weekly by default, cron "0 22 * * 0" in <TIMEZONE>
266
+ - Stable page behavior: create the page once, then update the same page ID in place on every run.
267
+
268
+ Requirements:
269
+ 1. Work outside the target repository under /workspace/dora-metrics.
270
+ 2. Install the community template files there: run.sh, report.mjs, and lead-prompt.md.
271
+ 3. Clone the repository into /workspace/dora-metrics/repos/<REPORT_NAME>, disable its push URL, fetch the requested branch, and fetch release tags matching <TAG_PATTERN>.
272
+ 4. Treat <TAG_PATTERN> tags as production deployments only if they map 1:1 to production releases.
273
+ 5. Compute Deployment Frequency and Lead Time for Changes exactly from tags and commits.
274
+ 6. Compute Change Failure Rate and Failed Deployment Recovery Time as proxy estimates from revert/hotfix signals.
275
+ 7. Label CFR and recovery time as proxy/estimated everywhere. Do not present them as precise incident metrics unless a formal incident source is added.
276
+ 8. Publish the first report as a swarm page and persist its stable page ID in the workflow configuration.
277
+ 9. Create a weekly schedule that executes run.sh, updates the same page ID, verifies render, and self-repairs local runner/report failures.
278
+ 10. Do not push any PR unless I explicitly ask for a versioned repository change.
279
+
280
+ Deliver back:
281
+ - The stable page URL.
282
+ - The workspace paths for run.sh, report.mjs, latest.html, and latest.json.
283
+ - The schedule name, cron, timezone, and how to change them.
284
+ - A one-line caveat naming which metrics are exact and which are proxy estimates.
285
+ - Any prerequisites or assumptions you could not satisfy automatically.
286
+ ```
287
+
288
+ ## References
289
+
290
+ - [DORA metrics guide](https://dora.dev/guides/dora-metrics/): official definitions for Deployment Frequency, Lead Time for Changes, Change Failure Rate, and Failed Deployment Recovery Time.
291
+ - [2024 Accelerate State of DevOps Report](https://dora.dev/research/2024/dora-report/): 2024 performance bands and cluster-analysis context.
292
+ - [DORA research program](https://dora.dev/research/): background on the annual DORA reports and metric evolution.
293
+ - [GitHub CLI](https://cli.github.com/manual/): optional PR metadata source used to enrich hotfix/revert proxy detection.
294
+ - [D3.js](https://d3js.org): JavaScript library used for the browser-side charts.
@@ -0,0 +1,39 @@
1
+ # DORA Metrics for Your Codebase, on Autopilot
2
+
3
+ Community template for running recurring DORA metrics reports from an agent-swarm instance.
4
+
5
+ Files:
6
+
7
+ - `PLAYBOOK.md`: end-to-end setup and weekly schedule playbook.
8
+ - `run.sh`: parameterized runner for any Git repository that uses release tags.
9
+ - `report.mjs`: static HTML + JSON report generator.
10
+ - `lead-prompt.md`: copy-paste prompt for your agent-swarm Lead.
11
+
12
+ Quick start:
13
+
14
+ ```bash
15
+ mkdir -p /workspace/dora-metrics
16
+ cp run.sh report.mjs lead-prompt.md /workspace/dora-metrics/
17
+ chmod +x /workspace/dora-metrics/run.sh
18
+
19
+ BASE_DIR=/workspace/dora-metrics \
20
+ REPO_NAME=my-repo \
21
+ REPO_URL=https://github.com/OWNER/REPO.git \
22
+ BRANCH=main \
23
+ TAG_PATTERN='v*' \
24
+ WINDOW_DAYS=90 \
25
+ bash /workspace/dora-metrics/run.sh
26
+ ```
27
+
28
+ Default weekly schedule cadence:
29
+
30
+ ```yaml
31
+ cron: "0 22 * * 0"
32
+ timezone: "UTC"
33
+ ```
34
+
35
+ Change the `cron` field to adjust when the report refreshes. Keep the same page ID when publishing refreshes so the report URL remains stable.
36
+
37
+ Deployment Frequency and Lead Time for Changes are exact when `TAG_PATTERN` maps 1:1 to production releases. Change Failure Rate and Failed Deployment Recovery Time are proxy estimates from revert/hotfix signals and are labeled that way in the generated report.
38
+
39
+ See `PLAYBOOK.md` for the full setup flow, references, and metric-definition notes.
@@ -0,0 +1,56 @@
1
+ # Lead Kickoff Prompt
2
+
3
+ Copy this into your agent-swarm Lead to bootstrap the recurring report.
4
+
5
+ ```text
6
+ Bootstrap a recurring DORA metrics report for my repository.
7
+
8
+ Parameters:
9
+ - Repository URL: <REPO_URL>
10
+ - Default branch: <BRANCH>
11
+ - Release tag pattern: <TAG_PATTERN, default v*>
12
+ - Report name/slug: <REPORT_NAME>
13
+ - Analysis window: <WINDOW_DAYS, default 90>
14
+ - Hotfix/revert matching window: <HOTFIX_WINDOW_HOURS, default 24>
15
+ - Cadence: weekly by default, cron "0 22 * * 0" in <TIMEZONE>
16
+ - Stable page behavior: create the page once, then update the same page ID in place on every run.
17
+
18
+ Requirements:
19
+ 1. Work outside the target repository under /workspace/dora-metrics.
20
+ 2. Install the community template files there:
21
+ - run.sh
22
+ - report.mjs
23
+ - lead-prompt.md
24
+ 3. On first run, install missing runtime dependencies:
25
+ - git
26
+ - jq
27
+ - Node.js
28
+ - GitHub CLI is optional but preferred for PR title metadata.
29
+ 4. Clone the repository into /workspace/dora-metrics/repos/<REPORT_NAME>, disable its push URL, fetch the requested branch, and fetch release tags matching <TAG_PATTERN>.
30
+ 5. Treat <TAG_PATTERN> tags as production deployments only if they map 1:1 to production releases. If they do not, stop and ask for the correct deployment signal.
31
+ 6. Compute the four DORA keys:
32
+ - Deployment Frequency: EXACT from release tags.
33
+ - Lead Time for Changes: EXACT from commit timestamps to the containing release tag.
34
+ - Change Failure Rate: PROXY from revert, rollback, hotfix, and fix-forward signals near release tags.
35
+ - Failed Deployment Recovery Time: PROXY from failed-release proxy tag to fixing tag.
36
+ 7. Label CFR and recovery time as proxy/estimated everywhere. Do not present them as precise incident metrics unless a formal incident source is added.
37
+ 8. Generate a static report with:
38
+ - Four DORA metric cards.
39
+ - EXACT/PROXY quality labels.
40
+ - Deployment and lead-time charts.
41
+ - Recent deployments table.
42
+ - Proxy remediation signal table.
43
+ 9. Publish the first report as a swarm page and persist its stable page ID in the workflow configuration.
44
+ 10. Create a schedule pinned to a code-capable worker:
45
+ - Default cadence: weekly, cron "0 22 * * 0".
46
+ - To change the cadence, edit the cron field and timezone only.
47
+ - Each run executes run.sh, updates the same page ID in place, verifies the page renders, and self-repairs the runner/report if the failure is local and fixable.
48
+ 11. Do not push any PR unless I explicitly ask for a versioned repository change.
49
+
50
+ Deliver back:
51
+ - The stable page URL.
52
+ - The workspace paths for run.sh, report.mjs, latest.html, and latest.json.
53
+ - The schedule name, cron, timezone, and how to change them.
54
+ - A one-line caveat naming which metrics are exact and which are proxy estimates.
55
+ - Any prerequisites or assumptions you could not satisfy automatically.
56
+ ```