agentopology 0.1.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 (174) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +450 -0
  3. package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
  4. package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
  5. package/dist/analyzer/__tests__/analyzer.test.js +258 -0
  6. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
  7. package/dist/analyzer/index.d.ts +44 -0
  8. package/dist/analyzer/index.d.ts.map +1 -0
  9. package/dist/analyzer/index.js +308 -0
  10. package/dist/analyzer/index.js.map +1 -0
  11. package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
  12. package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
  13. package/dist/bindings/__tests__/bindings.test.js +2089 -0
  14. package/dist/bindings/__tests__/bindings.test.js.map +1 -0
  15. package/dist/bindings/claude-code.d.ts +12 -0
  16. package/dist/bindings/claude-code.d.ts.map +1 -0
  17. package/dist/bindings/claude-code.js +2063 -0
  18. package/dist/bindings/claude-code.js.map +1 -0
  19. package/dist/bindings/codex.d.ts +18 -0
  20. package/dist/bindings/codex.d.ts.map +1 -0
  21. package/dist/bindings/codex.js +1633 -0
  22. package/dist/bindings/codex.js.map +1 -0
  23. package/dist/bindings/copilot-cli.d.ts +20 -0
  24. package/dist/bindings/copilot-cli.d.ts.map +1 -0
  25. package/dist/bindings/copilot-cli.js +1515 -0
  26. package/dist/bindings/copilot-cli.js.map +1 -0
  27. package/dist/bindings/cursor.d.ts +17 -0
  28. package/dist/bindings/cursor.d.ts.map +1 -0
  29. package/dist/bindings/cursor.js +1715 -0
  30. package/dist/bindings/cursor.js.map +1 -0
  31. package/dist/bindings/gemini-cli.d.ts +18 -0
  32. package/dist/bindings/gemini-cli.d.ts.map +1 -0
  33. package/dist/bindings/gemini-cli.js +1327 -0
  34. package/dist/bindings/gemini-cli.js.map +1 -0
  35. package/dist/bindings/index.d.ts +20 -0
  36. package/dist/bindings/index.d.ts.map +1 -0
  37. package/dist/bindings/index.js +33 -0
  38. package/dist/bindings/index.js.map +1 -0
  39. package/dist/bindings/kiro.d.ts +17 -0
  40. package/dist/bindings/kiro.d.ts.map +1 -0
  41. package/dist/bindings/kiro.js +1624 -0
  42. package/dist/bindings/kiro.js.map +1 -0
  43. package/dist/bindings/openclaw.d.ts +18 -0
  44. package/dist/bindings/openclaw.d.ts.map +1 -0
  45. package/dist/bindings/openclaw.js +1578 -0
  46. package/dist/bindings/openclaw.js.map +1 -0
  47. package/dist/bindings/types.d.ts +33 -0
  48. package/dist/bindings/types.d.ts.map +1 -0
  49. package/dist/bindings/types.js +23 -0
  50. package/dist/bindings/types.js.map +1 -0
  51. package/dist/cli/index.d.ts +19 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +679 -0
  54. package/dist/cli/index.js.map +1 -0
  55. package/dist/docs/__tests__/docs.test.d.ts +2 -0
  56. package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
  57. package/dist/docs/__tests__/docs.test.js +438 -0
  58. package/dist/docs/__tests__/docs.test.js.map +1 -0
  59. package/dist/docs/content.d.ts +16 -0
  60. package/dist/docs/content.d.ts.map +1 -0
  61. package/dist/docs/content.js +3273 -0
  62. package/dist/docs/content.js.map +1 -0
  63. package/dist/docs/index.d.ts +17 -0
  64. package/dist/docs/index.d.ts.map +1 -0
  65. package/dist/docs/index.js +117 -0
  66. package/dist/docs/index.js.map +1 -0
  67. package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
  68. package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
  69. package/dist/exporters/__tests__/exporters.test.js +731 -0
  70. package/dist/exporters/__tests__/exporters.test.js.map +1 -0
  71. package/dist/exporters/index.d.ts +13 -0
  72. package/dist/exporters/index.d.ts.map +1 -0
  73. package/dist/exporters/index.js +18 -0
  74. package/dist/exporters/index.js.map +1 -0
  75. package/dist/exporters/json.d.ts +8 -0
  76. package/dist/exporters/json.d.ts.map +1 -0
  77. package/dist/exporters/json.js +25 -0
  78. package/dist/exporters/json.js.map +1 -0
  79. package/dist/exporters/markdown.d.ts +9 -0
  80. package/dist/exporters/markdown.d.ts.map +1 -0
  81. package/dist/exporters/markdown.js +686 -0
  82. package/dist/exporters/markdown.js.map +1 -0
  83. package/dist/exporters/mermaid.d.ts +11 -0
  84. package/dist/exporters/mermaid.d.ts.map +1 -0
  85. package/dist/exporters/mermaid.js +161 -0
  86. package/dist/exporters/mermaid.js.map +1 -0
  87. package/dist/exporters/types.d.ts +22 -0
  88. package/dist/exporters/types.d.ts.map +1 -0
  89. package/dist/exporters/types.js +10 -0
  90. package/dist/exporters/types.js.map +1 -0
  91. package/dist/import/__tests__/import.test.d.ts +2 -0
  92. package/dist/import/__tests__/import.test.d.ts.map +1 -0
  93. package/dist/import/__tests__/import.test.js +781 -0
  94. package/dist/import/__tests__/import.test.js.map +1 -0
  95. package/dist/import/claude-code.d.ts +38 -0
  96. package/dist/import/claude-code.d.ts.map +1 -0
  97. package/dist/import/claude-code.js +897 -0
  98. package/dist/import/claude-code.js.map +1 -0
  99. package/dist/import/index.d.ts +18 -0
  100. package/dist/import/index.d.ts.map +1 -0
  101. package/dist/import/index.js +28 -0
  102. package/dist/import/index.js.map +1 -0
  103. package/dist/import/serializer.d.ts +11 -0
  104. package/dist/import/serializer.d.ts.map +1 -0
  105. package/dist/import/serializer.js +960 -0
  106. package/dist/import/serializer.js.map +1 -0
  107. package/dist/index.d.ts +26 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +21 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/parser/__tests__/parser.test.d.ts +2 -0
  112. package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
  113. package/dist/parser/__tests__/parser.test.js +6514 -0
  114. package/dist/parser/__tests__/parser.test.js.map +1 -0
  115. package/dist/parser/ast.d.ts +708 -0
  116. package/dist/parser/ast.d.ts.map +1 -0
  117. package/dist/parser/ast.js +12 -0
  118. package/dist/parser/ast.js.map +1 -0
  119. package/dist/parser/index.d.ts +253 -0
  120. package/dist/parser/index.d.ts.map +1 -0
  121. package/dist/parser/index.js +1985 -0
  122. package/dist/parser/index.js.map +1 -0
  123. package/dist/parser/lexer.d.ts +100 -0
  124. package/dist/parser/lexer.d.ts.map +1 -0
  125. package/dist/parser/lexer.js +304 -0
  126. package/dist/parser/lexer.js.map +1 -0
  127. package/dist/parser/validator.d.ts +42 -0
  128. package/dist/parser/validator.d.ts.map +1 -0
  129. package/dist/parser/validator.js +2568 -0
  130. package/dist/parser/validator.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
  132. package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
  133. package/dist/scaffold/__tests__/scaffold.test.js +295 -0
  134. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
  135. package/dist/scaffold/incremental.d.ts +43 -0
  136. package/dist/scaffold/incremental.d.ts.map +1 -0
  137. package/dist/scaffold/incremental.js +185 -0
  138. package/dist/scaffold/incremental.js.map +1 -0
  139. package/dist/scaffold/index.d.ts +13 -0
  140. package/dist/scaffold/index.d.ts.map +1 -0
  141. package/dist/scaffold/index.js +13 -0
  142. package/dist/scaffold/index.js.map +1 -0
  143. package/dist/scaffold/manifest.d.ts +30 -0
  144. package/dist/scaffold/manifest.d.ts.map +1 -0
  145. package/dist/scaffold/manifest.js +71 -0
  146. package/dist/scaffold/manifest.js.map +1 -0
  147. package/dist/scaffold/merge.d.ts +52 -0
  148. package/dist/scaffold/merge.d.ts.map +1 -0
  149. package/dist/scaffold/merge.js +134 -0
  150. package/dist/scaffold/merge.js.map +1 -0
  151. package/dist/scaffold/types.d.ts +51 -0
  152. package/dist/scaffold/types.d.ts.map +1 -0
  153. package/dist/scaffold/types.js +10 -0
  154. package/dist/scaffold/types.js.map +1 -0
  155. package/dist/sync/index.d.ts +34 -0
  156. package/dist/sync/index.d.ts.map +1 -0
  157. package/dist/sync/index.js +392 -0
  158. package/dist/sync/index.js.map +1 -0
  159. package/dist/visualizer/index.d.ts +17 -0
  160. package/dist/visualizer/index.d.ts.map +1 -0
  161. package/dist/visualizer/index.js +1976 -0
  162. package/dist/visualizer/index.js.map +1 -0
  163. package/examples/code-review.at +167 -0
  164. package/examples/data-processing.at +296 -0
  165. package/examples/openclaw-assistant.at +142 -0
  166. package/examples/scheduled-monitor.at +193 -0
  167. package/examples/simple-pipeline.at +163 -0
  168. package/package.json +77 -0
  169. package/skill/SKILL.md +403 -0
  170. package/skill/scripts/emit.ts +853 -0
  171. package/skill/scripts/visualize.ts +89 -0
  172. package/spec/grammar.md +1466 -0
  173. package/spec/reserved-keywords.md +147 -0
  174. package/spec/validation.md +535 -0
@@ -0,0 +1,1715 @@
1
+ /**
2
+ * Cursor IDE binding.
3
+ *
4
+ * Generates `.cursor/rules/*.mdc`, `.cursor/mcp.json`, `.cursor/hooks.json`,
5
+ * `AGENTS.md`, and supporting shell scripts from a parsed {@link TopologyAST}.
6
+ *
7
+ * Cursor rules use `.mdc` files with YAML frontmatter supporting only three
8
+ * fields: `description`, `globs`, and `alwaysApply`. All other AST concepts
9
+ * are either mapped to hooks.json, shell scripts, or embedded as polyfill
10
+ * instructions in rule body text.
11
+ *
12
+ * @module
13
+ */
14
+ import { deduplicateFiles } from "./types.js";
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+ /** Convert a kebab-case id to Title Case. */
19
+ function toTitle(id) {
20
+ return id
21
+ .split(/[-_]/)
22
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
23
+ .join(" ");
24
+ }
25
+ /**
26
+ * Downshift all markdown headings by one level so they nest properly
27
+ * inside ## Instructions. ## -> ###, ### -> ####, etc.
28
+ */
29
+ function downshiftHeadings(text) {
30
+ return text.replace(/^(#{2,6})\s/gm, (_, hashes) => `#${hashes} `);
31
+ }
32
+ /**
33
+ * Generate .mdc-style YAML frontmatter.
34
+ *
35
+ * Only three fields are valid for Cursor: description, globs, alwaysApply.
36
+ * Fields with undefined/null values are omitted. When alwaysApply is false
37
+ * (the default), it is omitted.
38
+ */
39
+ function frontmatter(fields) {
40
+ const lines = ["---"];
41
+ // Always emit all three fields (matching real-world .mdc convention).
42
+ // description and globs are empty when not set; alwaysApply defaults to false.
43
+ lines.push(`description: ${fields.description ?? ""}`);
44
+ if (fields.globs && fields.globs.length > 0) {
45
+ // Real .mdc files use comma-separated globs on a single line
46
+ lines.push(`globs: ${fields.globs.join(", ")}`);
47
+ }
48
+ else {
49
+ lines.push("globs:");
50
+ }
51
+ lines.push(`alwaysApply: ${fields.alwaysApply ? "true" : "false"}`);
52
+ lines.push("---");
53
+ return lines.join("\n");
54
+ }
55
+ /** Generate a shell script stub with a header comment. */
56
+ function shellStub(description) {
57
+ return [
58
+ "#!/usr/bin/env bash",
59
+ `# ${description}`,
60
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
61
+ "set -euo pipefail",
62
+ "",
63
+ 'echo "TODO: implement this script"',
64
+ "",
65
+ ].join("\n");
66
+ }
67
+ /** Generate an empty directory marker (a .gitkeep file). */
68
+ function gitkeep(dirPath) {
69
+ return { path: `${dirPath}/.gitkeep`, content: "", category: "machine" };
70
+ }
71
+ /**
72
+ * Convert PascalCase .at hook event to camelCase Cursor event name.
73
+ * e.g. PreToolUse -> preToolUse, SessionStart -> sessionStart
74
+ */
75
+ function mapHookEvent(atEvent) {
76
+ return atEvent.charAt(0).toLowerCase() + atEvent.slice(1);
77
+ }
78
+ /** Format a SchemaType to a human-readable string. */
79
+ function formatSchemaType(t, schemas) {
80
+ switch (t.kind) {
81
+ case "primitive":
82
+ return t.value;
83
+ case "array":
84
+ return `${formatSchemaType(t.itemType, schemas)}[]`;
85
+ case "enum":
86
+ return t.values.join(" | ");
87
+ case "ref": {
88
+ if (schemas) {
89
+ const resolved = schemas.find((s) => s.id === t.name);
90
+ if (resolved) {
91
+ const fields = resolved.fields.map((f) => `${f.name}${f.optional ? "?" : ""}: ${formatSchemaType(f.type, schemas)}`);
92
+ return `${t.name} { ${fields.join(", ")} }`;
93
+ }
94
+ }
95
+ return t.name;
96
+ }
97
+ }
98
+ }
99
+ /** Format a SchemaFieldDef to a readable line. */
100
+ function formatSchemaField(f) {
101
+ const opt = f.optional ? "?" : "";
102
+ return `- ${f.name}${opt}: ${formatSchemaType(f.type)}`;
103
+ }
104
+ /**
105
+ * Compile edges into natural-language workflow instructions.
106
+ */
107
+ function compileEdges(ast) {
108
+ if (ast.edges.length === 0)
109
+ return "";
110
+ const lines = ["## Workflow", ""];
111
+ const nodeMap = new Map(ast.nodes.map((n) => [n.id, n.label]));
112
+ const label = (id) => nodeMap.get(id) ?? toTitle(id);
113
+ let step = 1;
114
+ for (const edge of ast.edges) {
115
+ const parts = [];
116
+ if (edge.isError) {
117
+ const errType = edge.errorType ? `${edge.errorType} ` : "";
118
+ parts.push(`${step}. On ${errType}error from **${label(edge.from)}**, route to **${label(edge.to)}**`);
119
+ }
120
+ else if (edge.condition) {
121
+ // Clean up condition: parser may include trailing "] [max N" artifacts
122
+ const cleanCondition = edge.condition
123
+ .replace(/\]\s*\[max\s+\d+\s*$/i, "")
124
+ .replace(/\]\s*$/, "")
125
+ .trim();
126
+ parts.push(`${step}. If ${cleanCondition}, proceed to **${label(edge.to)}**`);
127
+ }
128
+ else if (edge.reflection) {
129
+ parts.push(`${step}. Loop back to **${label(edge.from)}** for self-evaluation`);
130
+ }
131
+ else {
132
+ parts.push(`${step}. After **${label(edge.from)}**, proceed to **${label(edge.to)}**`);
133
+ }
134
+ if (edge.maxIterations) {
135
+ parts.push(` - Repeat up to ${edge.maxIterations} times`);
136
+ }
137
+ if (edge.per) {
138
+ parts.push(` - For each ${edge.per} item, run **${label(edge.to)}**`);
139
+ }
140
+ if (edge.race) {
141
+ parts.push(` - Race **${label(edge.to)}** agents -- use first result`);
142
+ }
143
+ if (edge.tolerance != null) {
144
+ parts.push(` - Allow up to ${edge.tolerance} failures`);
145
+ }
146
+ if (edge.wait) {
147
+ parts.push(` - Wait ${edge.wait} before proceeding to **${label(edge.to)}**`);
148
+ }
149
+ if (edge.weight != null) {
150
+ parts.push(` - Route to **${label(edge.to)}** with weight ${edge.weight}`);
151
+ }
152
+ lines.push(...parts);
153
+ step++;
154
+ }
155
+ // Delegation summary
156
+ const agents = ast.nodes.filter((n) => n.type === "agent");
157
+ if (agents.length > 0) {
158
+ lines.push("");
159
+ lines.push("### Delegation");
160
+ for (const agent of agents) {
161
+ const desc = agent.description ?? agent.label;
162
+ lines.push(`- **${agent.id}**: ${desc} -- activate with \`@${agent.id}\``);
163
+ }
164
+ }
165
+ lines.push("");
166
+ return lines.join("\n");
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // Section generators
170
+ // ---------------------------------------------------------------------------
171
+ /** Generate the topology overview rule (always-apply). */
172
+ function generateTopologyOverview(ast) {
173
+ const orch = ast.nodes.find((n) => n.type === "orchestrator");
174
+ const fm = frontmatter({
175
+ description: ast.topology.description || `${ast.topology.name} topology overview`,
176
+ alwaysApply: true,
177
+ });
178
+ const sections = [fm, ""];
179
+ sections.push(`# ${ast.topology.name}`);
180
+ sections.push("");
181
+ sections.push(`<!-- Version: ${ast.topology.version} -->`);
182
+ sections.push("");
183
+ if (ast.topology.description) {
184
+ sections.push(ast.topology.description);
185
+ sections.push("");
186
+ }
187
+ // Orchestrator prompt merged in
188
+ if (orch) {
189
+ if (orch.model) {
190
+ sections.push(`**Model:** ${orch.model}`);
191
+ sections.push("");
192
+ }
193
+ if (orch.generates) {
194
+ sections.push(`**Generates:** ${orch.generates}`);
195
+ sections.push("");
196
+ }
197
+ if (orch.handles && orch.handles.length > 0) {
198
+ sections.push(`**Handles:** ${orch.handles.join(", ")}`);
199
+ sections.push("");
200
+ }
201
+ if (orch.outputs) {
202
+ sections.push("### Orchestrator Outputs");
203
+ for (const [field, values] of Object.entries(orch.outputs)) {
204
+ sections.push(`- ${field}: ${values.join(" | ")}`);
205
+ }
206
+ sections.push("");
207
+ }
208
+ }
209
+ // Agent table
210
+ const allAgents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
211
+ if (allAgents.length > 0) {
212
+ sections.push("## Agents");
213
+ sections.push("");
214
+ sections.push("| Agent | Type | Description |");
215
+ sections.push("|-------|------|-------------|");
216
+ for (const node of allAgents) {
217
+ let desc = node.label;
218
+ if (node.type === "agent") {
219
+ desc = node.description ?? node.label;
220
+ }
221
+ else if (node.type === "human") {
222
+ desc = node.description ?? node.label;
223
+ }
224
+ else if (node.type === "group") {
225
+ desc = node.description ?? node.label;
226
+ }
227
+ sections.push(`| ${node.id} | ${node.type} | ${desc} |`);
228
+ }
229
+ sections.push("");
230
+ }
231
+ // Compiled edges
232
+ const edgeText = compileEdges(ast);
233
+ if (edgeText) {
234
+ sections.push(edgeText);
235
+ }
236
+ // Defaults
237
+ if (ast.defaults) {
238
+ const d = ast.defaults;
239
+ sections.push("## Defaults");
240
+ sections.push("");
241
+ if (d.temperature != null)
242
+ sections.push(`- Temperature: ${d.temperature}`);
243
+ if (d.maxTokens != null)
244
+ sections.push(`- Max tokens: ${d.maxTokens}`);
245
+ if (d.topP != null)
246
+ sections.push(`- Top-p: ${d.topP}`);
247
+ if (d.topK != null)
248
+ sections.push(`- Top-k: ${d.topK}`);
249
+ if (d.stop)
250
+ sections.push(`- Stop sequences: ${d.stop.join(", ")}`);
251
+ if (d.seed != null)
252
+ sections.push(`- Seed: ${d.seed}`);
253
+ if (d.thinking)
254
+ sections.push(`- Thinking: ${d.thinking}`);
255
+ if (d.thinkingBudget != null)
256
+ sections.push(`- Thinking budget: ${d.thinkingBudget}`);
257
+ if (d.outputFormat)
258
+ sections.push(`- Output format: ${d.outputFormat}`);
259
+ if (d.timeout)
260
+ sections.push(`- Timeout: ${d.timeout}`);
261
+ if (d.logLevel)
262
+ sections.push(`- Log level: ${d.logLevel}`);
263
+ sections.push("");
264
+ }
265
+ // Settings as behavioral instructions
266
+ if (ast.settings && Object.keys(ast.settings).length > 0) {
267
+ sections.push("## Settings");
268
+ sections.push("");
269
+ for (const [key, value] of Object.entries(ast.settings)) {
270
+ if (Array.isArray(value)) {
271
+ sections.push(`- ${key}: ${value.join(", ")}`);
272
+ }
273
+ else {
274
+ sections.push(`- ${key}: ${JSON.stringify(value)}`);
275
+ }
276
+ }
277
+ sections.push("");
278
+ }
279
+ // Depth levels
280
+ if (ast.depth && ast.depth.levels && ast.depth.levels.length > 0) {
281
+ sections.push("<!-- Depth levels:");
282
+ if (ast.depth.factors.length > 0) {
283
+ sections.push(` Factors: ${ast.depth.factors.join(", ")}`);
284
+ }
285
+ for (const level of ast.depth.levels) {
286
+ let line = ` Level ${level.level}: ${level.label}`;
287
+ if (level.omit.length > 0)
288
+ line += ` (omit: ${level.omit.join(", ")})`;
289
+ sections.push(line);
290
+ }
291
+ sections.push("-->");
292
+ sections.push("");
293
+ }
294
+ // Batch
295
+ if (ast.batch && Object.keys(ast.batch).length > 0) {
296
+ sections.push("## Batch");
297
+ sections.push("");
298
+ for (const [key, value] of Object.entries(ast.batch)) {
299
+ sections.push(`- ${key}: ${JSON.stringify(value)}`);
300
+ }
301
+ sections.push("");
302
+ }
303
+ // Environments
304
+ if (ast.environments && Object.keys(ast.environments).length > 0) {
305
+ sections.push("<!-- Environments:");
306
+ for (const [envName, envConfig] of Object.entries(ast.environments)) {
307
+ sections.push(` ${envName}:`);
308
+ for (const [key, value] of Object.entries(envConfig)) {
309
+ sections.push(` ${key}: ${JSON.stringify(value)}`);
310
+ }
311
+ }
312
+ sections.push("-->");
313
+ sections.push("");
314
+ }
315
+ // Providers
316
+ if (ast.providers && ast.providers.length > 0) {
317
+ sections.push("<!-- Providers:");
318
+ for (const provider of ast.providers) {
319
+ let line = ` ${provider.name}`;
320
+ if (provider.default)
321
+ line += " (default)";
322
+ if (provider.models.length > 0)
323
+ line += ` -- models: ${provider.models.join(", ")}`;
324
+ sections.push(line);
325
+ }
326
+ sections.push("-->");
327
+ sections.push("");
328
+ }
329
+ // Schedules
330
+ if (ast.schedules.length > 0) {
331
+ sections.push("<!-- Schedules:");
332
+ for (const job of ast.schedules) {
333
+ const expr = job.cron
334
+ ? `cron: ${job.cron}`
335
+ : job.every
336
+ ? `every: ${job.every}`
337
+ : "";
338
+ const target = job.agent
339
+ ? `agent: ${job.agent}`
340
+ : job.action
341
+ ? `action: ${job.action}`
342
+ : "";
343
+ sections.push(` ${job.id}: ${expr}${target ? ` -> ${target}` : ""}${job.enabled === false ? " (disabled)" : ""}`);
344
+ }
345
+ sections.push("-->");
346
+ sections.push("");
347
+ }
348
+ // Interfaces
349
+ if (ast.interfaces.length > 0) {
350
+ sections.push("<!-- Interfaces:");
351
+ for (const iface of ast.interfaces) {
352
+ const typePart = iface.type ? ` (${iface.type})` : "";
353
+ sections.push(` ${iface.id}${typePart}`);
354
+ }
355
+ sections.push("-->");
356
+ sections.push("");
357
+ }
358
+ // Pattern metadata
359
+ if (ast.topology.patterns.length > 0) {
360
+ sections.push(`<!-- Patterns: ${ast.topology.patterns.join(", ")} -->`);
361
+ }
362
+ if (ast.topology.domain) {
363
+ sections.push(`<!-- Domain: ${ast.topology.domain} -->`);
364
+ }
365
+ if (ast.topology.foundations && ast.topology.foundations.length > 0) {
366
+ sections.push(`<!-- Foundations: ${ast.topology.foundations.join(", ")} -->`);
367
+ }
368
+ if (ast.topology.advanced && ast.topology.advanced.length > 0) {
369
+ sections.push(`<!-- Advanced: ${ast.topology.advanced.join(", ")} -->`);
370
+ }
371
+ if (ast.topology.durable) {
372
+ sections.push("<!-- Durable: yes -->");
373
+ }
374
+ if (ast.topology.timeout) {
375
+ sections.push(`<!-- Timeout: ${ast.topology.timeout} -->`);
376
+ }
377
+ if (ast.topology.errorHandler) {
378
+ sections.push(`<!-- Error handler: ${ast.topology.errorHandler} -->`);
379
+ }
380
+ // Params
381
+ if (ast.params && ast.params.length > 0) {
382
+ sections.push("<!-- Parameters:");
383
+ for (const p of ast.params) {
384
+ const req = p.required ? " (required)" : "";
385
+ const def = p.default != null ? ` = ${p.default}` : "";
386
+ sections.push(` ${p.name}: ${p.type}${req}${def}`);
387
+ }
388
+ sections.push("-->");
389
+ }
390
+ // Interface endpoints
391
+ if (ast.interfaceEndpoints) {
392
+ sections.push(`<!-- Interface: entry=${ast.interfaceEndpoints.entry}, exit=${ast.interfaceEndpoints.exit} -->`);
393
+ }
394
+ // Imports
395
+ if (ast.imports && ast.imports.length > 0) {
396
+ sections.push("<!-- Imports:");
397
+ for (const imp of ast.imports) {
398
+ let line = ` ${imp.alias} from ${imp.source}`;
399
+ if (imp.sha256)
400
+ line += ` (sha256: ${imp.sha256})`;
401
+ sections.push(line);
402
+ }
403
+ sections.push("-->");
404
+ }
405
+ // Checkpoint
406
+ if (ast.checkpoint) {
407
+ const cp = ast.checkpoint;
408
+ sections.push("<!-- Checkpoint:");
409
+ sections.push(` backend: ${cp.backend}`);
410
+ sections.push(` strategy: ${cp.strategy}`);
411
+ if (cp.connection)
412
+ sections.push(` connection: ${cp.connection}`);
413
+ if (cp.ttl)
414
+ sections.push(` ttl: ${cp.ttl}`);
415
+ if (cp.replay) {
416
+ sections.push(` replay: ${cp.replay.enabled ? "enabled" : "disabled"}`);
417
+ }
418
+ sections.push("-->");
419
+ }
420
+ // Artifacts
421
+ if (ast.artifacts && ast.artifacts.length > 0) {
422
+ sections.push("<!-- Artifacts:");
423
+ for (const a of ast.artifacts) {
424
+ let line = ` ${a.id} (${a.type})`;
425
+ if (a.path)
426
+ line += ` -- path: ${a.path}`;
427
+ if (a.retention)
428
+ line += `, retention: ${a.retention}`;
429
+ sections.push(line);
430
+ }
431
+ sections.push("-->");
432
+ }
433
+ sections.push("");
434
+ return {
435
+ path: ".cursor/rules/topology-overview.mdc",
436
+ content: sections.join("\n"),
437
+ category: "agent",
438
+ };
439
+ }
440
+ /** Generate .mdc rule files for each AgentNode. */
441
+ function generateAgentRules(ast) {
442
+ const files = [];
443
+ for (const node of ast.nodes) {
444
+ if (node.type !== "agent")
445
+ continue;
446
+ const agent = node;
447
+ // Warn about impossible features
448
+ if (agent.permissions) {
449
+ console.warn(`[cursor] Warning: permission "${agent.permissions}" has no Cursor equivalent -- skipped.`);
450
+ }
451
+ if (agent.isolation) {
452
+ console.warn(`[cursor] Warning: isolation "${agent.isolation}" has no Cursor equivalent -- skipped.`);
453
+ }
454
+ // Build frontmatter
455
+ const fm = {};
456
+ fm.description = agent.description ?? agent.label;
457
+ // Cursor-specific globs extension
458
+ const cursorExt = agent.extensions?.cursor;
459
+ if (cursorExt?.globs) {
460
+ fm.globs = cursorExt.globs;
461
+ }
462
+ // alwaysApply override
463
+ if (cursorExt?.alwaysApply) {
464
+ fm.alwaysApply = true;
465
+ }
466
+ // Manual invocation: omit description to make it @-mention only
467
+ if (agent.invocation === "manual") {
468
+ delete fm.description;
469
+ }
470
+ // Build body
471
+ const sections = [frontmatter(fm), ""];
472
+ sections.push(`# ${agent.label || toTitle(agent.id)}`);
473
+ sections.push("");
474
+ // Role text
475
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
476
+ if (roleText) {
477
+ sections.push(roleText);
478
+ sections.push("");
479
+ }
480
+ // Phase comment
481
+ if (agent.phase != null) {
482
+ sections.push(`<!-- Phase: ${agent.phase} -->`);
483
+ sections.push("");
484
+ }
485
+ // Model instruction
486
+ if (agent.model) {
487
+ sections.push(`**Model:** ${agent.model}`);
488
+ sections.push("");
489
+ }
490
+ // Instructions
491
+ sections.push("## Instructions");
492
+ sections.push("");
493
+ if (agent.prompt) {
494
+ sections.push(downshiftHeadings(agent.prompt));
495
+ }
496
+ else {
497
+ sections.push(`${agent.description || agent.role || "Complete the assigned task."}`);
498
+ }
499
+ sections.push("");
500
+ // Tools
501
+ if (agent.tools && agent.tools.length > 0) {
502
+ sections.push("## Tools");
503
+ for (const tool of agent.tools) {
504
+ sections.push(`- ${tool}`);
505
+ }
506
+ sections.push("");
507
+ }
508
+ // Disallowed tools
509
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
510
+ sections.push("## Disallowed Tools");
511
+ sections.push(`**Do NOT use:** ${agent.disallowedTools.join(", ")}`);
512
+ sections.push("");
513
+ }
514
+ // Skills references
515
+ if (agent.skills && agent.skills.length > 0) {
516
+ sections.push("## Skills");
517
+ for (const skill of agent.skills) {
518
+ sections.push(`- @skill-${skill}`);
519
+ }
520
+ sections.push("");
521
+ }
522
+ // Memory: reads/writes
523
+ if ((agent.reads && agent.reads.length > 0) ||
524
+ (agent.writes && agent.writes.length > 0)) {
525
+ sections.push("## Memory");
526
+ if (agent.reads && agent.reads.length > 0) {
527
+ sections.push(`**Reads:** ${agent.reads.join(", ")}`);
528
+ }
529
+ if (agent.writes && agent.writes.length > 0) {
530
+ sections.push(`**Writes:** ${agent.writes.join(", ")}`);
531
+ }
532
+ sections.push("");
533
+ }
534
+ // Outputs
535
+ if (agent.outputs) {
536
+ sections.push("## Outputs");
537
+ for (const [field, values] of Object.entries(agent.outputs)) {
538
+ sections.push(`- ${field}: ${values.join(" | ")}`);
539
+ }
540
+ sections.push("");
541
+ }
542
+ // Sampling params
543
+ const hasModelConfig = agent.temperature != null ||
544
+ agent.maxTokens != null ||
545
+ agent.topP != null ||
546
+ agent.topK != null ||
547
+ agent.stop != null ||
548
+ agent.seed != null;
549
+ if (hasModelConfig) {
550
+ sections.push("## Model Configuration");
551
+ if (agent.temperature != null)
552
+ sections.push(`- Temperature: ${agent.temperature}`);
553
+ if (agent.maxTokens != null)
554
+ sections.push(`- Max tokens: ${agent.maxTokens}`);
555
+ if (agent.topP != null)
556
+ sections.push(`- Top-p: ${agent.topP}`);
557
+ if (agent.topK != null)
558
+ sections.push(`- Top-k: ${agent.topK}`);
559
+ if (agent.stop)
560
+ sections.push(`- Stop sequences: ${agent.stop.join(", ")}`);
561
+ if (agent.seed != null)
562
+ sections.push(`- Seed: ${agent.seed}`);
563
+ sections.push("");
564
+ }
565
+ // Thinking
566
+ if (agent.thinking) {
567
+ let thinkingLine = `Use ${agent.thinking} level reasoning.`;
568
+ if (agent.thinkingBudget != null) {
569
+ thinkingLine = `Use ${agent.thinking} level reasoning (budget: ${agent.thinkingBudget} tokens).`;
570
+ }
571
+ sections.push(thinkingLine);
572
+ sections.push("");
573
+ }
574
+ // Output format
575
+ if (agent.outputFormat) {
576
+ sections.push(`Output format: ${agent.outputFormat}`);
577
+ sections.push("");
578
+ }
579
+ // Log level
580
+ if (agent.logLevel) {
581
+ sections.push(`<!-- Log level: ${agent.logLevel} -->`);
582
+ sections.push("");
583
+ }
584
+ // Timeout
585
+ if (agent.timeout) {
586
+ sections.push(`**Timeout:** ${agent.timeout}`);
587
+ sections.push("");
588
+ }
589
+ // Max turns
590
+ if (agent.maxTurns != null) {
591
+ sections.push(`**Max turns:** ${agent.maxTurns}`);
592
+ sections.push("");
593
+ }
594
+ // On fail
595
+ if (agent.onFail) {
596
+ sections.push(`If you fail: ${agent.onFail}`);
597
+ sections.push("");
598
+ }
599
+ // Retry
600
+ if (agent.retry != null) {
601
+ if (typeof agent.retry === "number") {
602
+ sections.push(`**Retry:** up to ${agent.retry} times`);
603
+ }
604
+ else {
605
+ const r = agent.retry;
606
+ const parts = [`max ${r.max} attempts`];
607
+ if (r.backoff)
608
+ parts.push(`${r.backoff} backoff`);
609
+ if (r.interval)
610
+ parts.push(`interval ${r.interval}`);
611
+ if (r.maxInterval)
612
+ parts.push(`max interval ${r.maxInterval}`);
613
+ if (r.jitter)
614
+ parts.push("with jitter");
615
+ if (r.nonRetryable && r.nonRetryable.length > 0) {
616
+ parts.push(`non-retryable: ${r.nonRetryable.join(", ")}`);
617
+ }
618
+ sections.push(`Retry strategy: ${parts.join(", ")}.`);
619
+ }
620
+ sections.push("");
621
+ }
622
+ // Circuit breaker
623
+ if (agent.circuitBreaker) {
624
+ const cb = agent.circuitBreaker;
625
+ sections.push("## Circuit Breaker");
626
+ sections.push(`- Failure threshold: ${cb.threshold}`);
627
+ sections.push(`- Window: ${cb.window}`);
628
+ sections.push(`- Cooldown: ${cb.cooldown}`);
629
+ sections.push("");
630
+ }
631
+ // Compensates
632
+ if (agent.compensates) {
633
+ sections.push(`This agent compensates (can undo) the work of: ${agent.compensates}`);
634
+ sections.push("");
635
+ }
636
+ // Input Schema
637
+ if (agent.inputSchema && agent.inputSchema.length > 0) {
638
+ sections.push("## Input Schema");
639
+ for (const field of agent.inputSchema) {
640
+ sections.push(formatSchemaField(field));
641
+ }
642
+ sections.push("");
643
+ }
644
+ // Output Schema
645
+ if (agent.outputSchema && agent.outputSchema.length > 0) {
646
+ sections.push("## Output Schema");
647
+ for (const field of agent.outputSchema) {
648
+ sections.push(formatSchemaField(field));
649
+ }
650
+ sections.push("");
651
+ }
652
+ // Rate limit
653
+ if (agent.rateLimit) {
654
+ sections.push(`Rate limit: ${agent.rateLimit}`);
655
+ sections.push("");
656
+ }
657
+ // Artifacts: produces/consumes
658
+ if ((agent.produces && agent.produces.length > 0) ||
659
+ (agent.consumes && agent.consumes.length > 0)) {
660
+ sections.push("## Artifacts");
661
+ if (agent.produces && agent.produces.length > 0) {
662
+ sections.push(`Produces: ${agent.produces.join(", ")}`);
663
+ }
664
+ if (agent.consumes && agent.consumes.length > 0) {
665
+ sections.push(`Consumes: ${agent.consumes.join(", ")}`);
666
+ }
667
+ sections.push("");
668
+ }
669
+ // Variants
670
+ if (agent.variants && agent.variants.length > 0) {
671
+ sections.push("## Variants");
672
+ for (const v of agent.variants) {
673
+ const parts = [`**${v.id}** (weight: ${v.weight})`];
674
+ if (v.model)
675
+ parts.push(`model: ${v.model}`);
676
+ if (v.temperature != null)
677
+ parts.push(`temperature: ${v.temperature}`);
678
+ sections.push(`- ${parts.join(", ")}`);
679
+ if (v.prompt) {
680
+ sections.push(` Prompt: ${v.prompt}`);
681
+ }
682
+ }
683
+ sections.push("");
684
+ }
685
+ // Scale
686
+ if (agent.scale) {
687
+ const s = agent.scale;
688
+ sections.push("## Scale");
689
+ sections.push(`- Mode: ${s.mode}`);
690
+ sections.push(`- By: ${s.by}`);
691
+ sections.push(`- Min: ${s.min}`);
692
+ sections.push(`- Max: ${s.max}`);
693
+ if (s.batchSize != null)
694
+ sections.push(`- Batch size: ${s.batchSize}`);
695
+ sections.push("");
696
+ }
697
+ // Sandbox
698
+ if (agent.sandbox != null) {
699
+ sections.push(`Sandbox: ${agent.sandbox}`);
700
+ sections.push("");
701
+ }
702
+ // Fallback chain
703
+ if (agent.fallbackChain && agent.fallbackChain.length > 0) {
704
+ sections.push(`Fallback chain: ${agent.fallbackChain.join(" -> ")}`);
705
+ sections.push("");
706
+ }
707
+ // Join
708
+ if (agent.join) {
709
+ sections.push(`Wait strategy: ${agent.join} (wait for upstream agents)`);
710
+ sections.push("");
711
+ }
712
+ // Background
713
+ if (agent.background) {
714
+ sections.push("This agent runs in the background.");
715
+ sections.push("");
716
+ }
717
+ // Skip
718
+ if (agent.skip) {
719
+ sections.push(`Skip when: ${agent.skip}`);
720
+ sections.push("");
721
+ }
722
+ // Behavior
723
+ if (agent.behavior) {
724
+ sections.push(`**Behavior:** ${agent.behavior}`);
725
+ sections.push("");
726
+ }
727
+ files.push({
728
+ path: `.cursor/rules/${agent.id}.mdc`,
729
+ content: sections.join("\n"),
730
+ category: "agent",
731
+ });
732
+ }
733
+ return files;
734
+ }
735
+ /** Generate .mdc rule files for each HumanNode. */
736
+ function generateHumanRules(ast) {
737
+ const files = [];
738
+ for (const node of ast.nodes) {
739
+ if (node.type !== "human")
740
+ continue;
741
+ const human = node;
742
+ const fm = frontmatter({
743
+ description: human.description ?? `Human approval required: ${human.label}`,
744
+ });
745
+ const sections = [fm, ""];
746
+ sections.push(`# ${human.label || toTitle(human.id)}`);
747
+ sections.push("");
748
+ sections.push("This step requires human review and approval.");
749
+ sections.push("");
750
+ if (human.description) {
751
+ sections.push(human.description);
752
+ sections.push("");
753
+ }
754
+ sections.push("## Process");
755
+ sections.push("1. Review the current state and outputs");
756
+ sections.push("2. Approve or reject the proposed changes");
757
+ sections.push("3. Provide feedback if rejecting");
758
+ sections.push("");
759
+ if (human.timeout) {
760
+ sections.push(`**Timeout:** ${human.timeout}`);
761
+ sections.push("");
762
+ }
763
+ if (human.onTimeout) {
764
+ sections.push(`**On timeout:** ${human.onTimeout}`);
765
+ sections.push("");
766
+ }
767
+ files.push({
768
+ path: `.cursor/rules/${human.id}.mdc`,
769
+ content: sections.join("\n"),
770
+ category: "agent",
771
+ });
772
+ }
773
+ return files;
774
+ }
775
+ /** Generate .mdc rule files for each GroupNode. */
776
+ function generateGroupRules(ast) {
777
+ const files = [];
778
+ for (const node of ast.nodes) {
779
+ if (node.type !== "group")
780
+ continue;
781
+ const group = node;
782
+ const fm = frontmatter({
783
+ description: group.description ?? `Group: ${group.label}`,
784
+ });
785
+ const sections = [fm, ""];
786
+ sections.push(`# ${group.label || toTitle(group.id)}`);
787
+ sections.push("");
788
+ sections.push("## Members");
789
+ for (const member of group.members) {
790
+ sections.push(`- @${member}`);
791
+ }
792
+ sections.push("");
793
+ sections.push("## Interaction Pattern");
794
+ if (group.speakerSelection) {
795
+ sections.push(`Speaker selection: ${group.speakerSelection}`);
796
+ }
797
+ if (group.maxRounds != null) {
798
+ sections.push(`Max rounds: ${group.maxRounds}`);
799
+ }
800
+ if (group.termination) {
801
+ sections.push(`Termination: ${group.termination}`);
802
+ }
803
+ if (group.timeout) {
804
+ sections.push(`Timeout: ${group.timeout}`);
805
+ }
806
+ sections.push("");
807
+ files.push({
808
+ path: `.cursor/rules/${group.id}.mdc`,
809
+ content: sections.join("\n"),
810
+ category: "agent",
811
+ });
812
+ }
813
+ return files;
814
+ }
815
+ /** Generate shell scripts for each ActionNode. */
816
+ function generateActionScripts(ast) {
817
+ const files = [];
818
+ for (const node of ast.nodes) {
819
+ if (node.type !== "action")
820
+ continue;
821
+ const action = node;
822
+ const lines = ["#!/usr/bin/env bash"];
823
+ lines.push(`# Action: ${action.id} -- ${action.description ?? action.label}`);
824
+ lines.push("# Auto-generated by agentopology scaffold -- edit as needed.");
825
+ lines.push("set -euo pipefail");
826
+ lines.push("");
827
+ if (action.timeout) {
828
+ lines.push(`# Timeout: ${action.timeout}`);
829
+ }
830
+ if (action.onFail) {
831
+ lines.push(`# On failure: ${action.onFail}`);
832
+ }
833
+ if (action.join) {
834
+ lines.push(`# Join semantics: ${action.join}`);
835
+ }
836
+ switch (action.kind) {
837
+ case "external":
838
+ lines.push(`# External source: ${action.source ?? "unknown"}`);
839
+ lines.push("");
840
+ lines.push(`echo "TODO: implement external action -- fetch from ${action.source ?? "source"}"`);
841
+ break;
842
+ case "inline":
843
+ if (action.commands && action.commands.length > 0) {
844
+ lines.push("");
845
+ for (const cmd of action.commands) {
846
+ lines.push(cmd);
847
+ }
848
+ }
849
+ else {
850
+ lines.push("");
851
+ lines.push('echo "TODO: implement inline action"');
852
+ }
853
+ break;
854
+ case "git":
855
+ lines.push("");
856
+ lines.push("# Git operation");
857
+ if (action.commands && action.commands.length > 0) {
858
+ for (const cmd of action.commands) {
859
+ lines.push(cmd);
860
+ }
861
+ }
862
+ else {
863
+ lines.push('echo "TODO: implement git action"');
864
+ }
865
+ break;
866
+ case "report":
867
+ lines.push("");
868
+ lines.push("# Report generation");
869
+ lines.push('echo "TODO: implement report action"');
870
+ break;
871
+ default:
872
+ lines.push("");
873
+ lines.push(`echo "TODO: implement ${action.kind ?? "unknown"} action"`);
874
+ break;
875
+ }
876
+ lines.push("");
877
+ files.push({
878
+ path: `.cursor/scripts/actions/${action.id}.sh`,
879
+ content: lines.join("\n"),
880
+ category: "script",
881
+ });
882
+ }
883
+ return files;
884
+ }
885
+ /**
886
+ * Generate gate hooks and scripts.
887
+ *
888
+ * Returns gate scripts as GeneratedFile[]. The hook entries are collected
889
+ * by generateHooksJson separately.
890
+ */
891
+ function generateGateHooks(ast) {
892
+ const files = [];
893
+ for (const node of ast.nodes) {
894
+ if (node.type !== "gate")
895
+ continue;
896
+ const gate = node;
897
+ const lines = [
898
+ "#!/usr/bin/env bash",
899
+ `# Gate: ${gate.id}`,
900
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
901
+ "set -euo pipefail",
902
+ "",
903
+ ];
904
+ lines.push(`# After: ${gate.after ?? "any"}`);
905
+ lines.push(`# Before: ${gate.before ?? "any"}`);
906
+ if (gate.onFail)
907
+ lines.push(`# On failure: ${gate.onFail}`);
908
+ if (gate.behavior)
909
+ lines.push(`# Behavior: ${gate.behavior}`);
910
+ if (gate.timeout)
911
+ lines.push(`# Timeout: ${gate.timeout}`);
912
+ lines.push("");
913
+ if (gate.run) {
914
+ lines.push(`# Run: ${gate.run}`);
915
+ lines.push("");
916
+ if (gate.retry != null && gate.retry > 0) {
917
+ lines.push(`MAX_RETRIES=${gate.retry}`);
918
+ lines.push("ATTEMPT=0");
919
+ lines.push("GATE_RESULT=1");
920
+ lines.push("");
921
+ lines.push("while [ $ATTEMPT -le $MAX_RETRIES ] && [ $GATE_RESULT -ne 0 ]; do");
922
+ lines.push(" ATTEMPT=$((ATTEMPT + 1))");
923
+ lines.push(` ${gate.run} && GATE_RESULT=0 || GATE_RESULT=$?`);
924
+ lines.push(' if [ $GATE_RESULT -ne 0 ] && [ $ATTEMPT -le $MAX_RETRIES ]; then');
925
+ lines.push(` echo "Gate '${gate.id}' attempt $ATTEMPT failed, retrying..."`);
926
+ lines.push(" sleep 1");
927
+ lines.push(" fi");
928
+ lines.push("done");
929
+ }
930
+ else {
931
+ lines.push("GATE_RESULT=0");
932
+ lines.push(`${gate.run} || GATE_RESULT=$?`);
933
+ }
934
+ }
935
+ else if (gate.checks && gate.checks.length > 0) {
936
+ lines.push("# Checks:");
937
+ lines.push("GATE_RESULT=0");
938
+ lines.push("");
939
+ for (const check of gate.checks) {
940
+ lines.push(`# Check: ${check}`);
941
+ lines.push(`echo "TODO: implement check '${check}'" # || GATE_RESULT=1`);
942
+ }
943
+ }
944
+ else {
945
+ lines.push("GATE_RESULT=0");
946
+ lines.push('echo "TODO: implement gate check"');
947
+ }
948
+ lines.push("");
949
+ const exitAction = gate.onFail === "halt" || gate.behavior === "blocking"
950
+ ? "exit 1"
951
+ : 'echo "Gate failed -- advisory only"';
952
+ lines.push('if [ "$GATE_RESULT" -ne 0 ]; then');
953
+ lines.push(` echo "Gate '${gate.id}' FAILED (exit code $GATE_RESULT)"`);
954
+ lines.push(` ${exitAction}`);
955
+ lines.push("fi");
956
+ lines.push("");
957
+ files.push({
958
+ path: `.cursor/hooks/gate-${gate.id}.sh`,
959
+ content: lines.join("\n"),
960
+ category: "script",
961
+ });
962
+ }
963
+ return files;
964
+ }
965
+ /** Generate .mdc rule files for each SkillDef. */
966
+ function generateSkillRules(ast) {
967
+ const files = [];
968
+ for (const skill of ast.skills) {
969
+ const isManual = skill.userInvocable === true;
970
+ const fm = isManual
971
+ ? frontmatter({})
972
+ : frontmatter({ description: skill.description || skill.id });
973
+ const sections = [fm, ""];
974
+ sections.push(`# ${toTitle(skill.id)}`);
975
+ sections.push("");
976
+ if (skill.description) {
977
+ sections.push(skill.description);
978
+ sections.push("");
979
+ }
980
+ // Prompt content
981
+ if (skill.prompt) {
982
+ sections.push(`See: ${skill.prompt}`);
983
+ sections.push("");
984
+ }
985
+ // Scripts references
986
+ if (skill.scripts && skill.scripts.length > 0) {
987
+ sections.push("## Scripts");
988
+ for (const s of skill.scripts) {
989
+ sections.push(`- ${s}`);
990
+ }
991
+ sections.push("");
992
+ }
993
+ // Domains references
994
+ if (skill.domains && skill.domains.length > 0) {
995
+ sections.push("## Domains");
996
+ for (const d of skill.domains) {
997
+ sections.push(`- ${d}`);
998
+ }
999
+ sections.push("");
1000
+ }
1001
+ // File references
1002
+ if (skill.references && skill.references.length > 0) {
1003
+ sections.push("## References");
1004
+ for (const r of skill.references) {
1005
+ sections.push(`- @${r}`);
1006
+ }
1007
+ sections.push("");
1008
+ }
1009
+ // Allowed tools
1010
+ if (skill.allowedTools && skill.allowedTools.length > 0) {
1011
+ sections.push("## Allowed Tools");
1012
+ for (const tool of skill.allowedTools) {
1013
+ sections.push(`- ${tool}`);
1014
+ }
1015
+ sections.push("");
1016
+ }
1017
+ // disableModelInvocation
1018
+ if (skill.disableModelInvocation) {
1019
+ sections.push("**Note:** Model invocation is disabled for this skill. Execute scripts directly.");
1020
+ sections.push("");
1021
+ }
1022
+ // Context mode
1023
+ if (skill.context) {
1024
+ sections.push(`<!-- Context mode: ${skill.context} -->`);
1025
+ }
1026
+ // Agent type
1027
+ if (skill.agent) {
1028
+ sections.push(`<!-- Agent type: ${skill.agent} -->`);
1029
+ }
1030
+ files.push({
1031
+ path: `.cursor/rules/skill-${skill.id}.mdc`,
1032
+ content: sections.join("\n"),
1033
+ category: "agent",
1034
+ });
1035
+ }
1036
+ return files;
1037
+ }
1038
+ /** Generate .mdc rule files for each TriggerDef. */
1039
+ function generateTriggerRules(ast) {
1040
+ const files = [];
1041
+ for (const trigger of ast.triggers) {
1042
+ // Manual rule -- empty frontmatter
1043
+ const fm = frontmatter({});
1044
+ const sections = [fm, ""];
1045
+ sections.push(`# ${toTitle(trigger.name)}`);
1046
+ sections.push("");
1047
+ sections.push(trigger.pattern);
1048
+ sections.push("");
1049
+ if (trigger.argument) {
1050
+ sections.push("## Arguments");
1051
+ sections.push(`- ${trigger.argument}: extracted from the command pattern`);
1052
+ sections.push("");
1053
+ }
1054
+ files.push({
1055
+ path: `.cursor/rules/trigger-${trigger.name}.mdc`,
1056
+ content: sections.join("\n"),
1057
+ category: "agent",
1058
+ });
1059
+ }
1060
+ return files;
1061
+ }
1062
+ /** Generate .cursor/mcp.json from global and per-agent MCP servers. */
1063
+ function generateMcpJson(ast) {
1064
+ // Collect all MCP servers (global + per-agent)
1065
+ const allServers = {};
1066
+ // Global MCP servers
1067
+ for (const [name, config] of Object.entries(ast.mcpServers)) {
1068
+ allServers[name] = config;
1069
+ }
1070
+ // Per-agent MCP servers: merge by reference
1071
+ for (const node of ast.nodes) {
1072
+ if (node.type !== "agent")
1073
+ continue;
1074
+ const agent = node;
1075
+ if (!agent.mcpServers || agent.mcpServers.length === 0)
1076
+ continue;
1077
+ // Agent mcpServers are just names (strings), they reference global config
1078
+ // The actual config comes from ast.mcpServers
1079
+ }
1080
+ if (Object.keys(allServers).length === 0)
1081
+ return null;
1082
+ const mcpConfig = { mcpServers: {} };
1083
+ const servers = mcpConfig.mcpServers;
1084
+ for (const [name, config] of Object.entries(allServers)) {
1085
+ const entry = {};
1086
+ for (const [key, value] of Object.entries(config)) {
1087
+ if (key === "env" && typeof value === "object" && value !== null) {
1088
+ // Handle SensitiveValue env vars
1089
+ const envObj = {};
1090
+ for (const [envKey, envVal] of Object.entries(value)) {
1091
+ if (typeof envVal === "string") {
1092
+ envObj[envKey] = envVal;
1093
+ }
1094
+ else if (typeof envVal === "object" &&
1095
+ envVal !== null &&
1096
+ "value" in envVal) {
1097
+ const sv = envVal;
1098
+ if (sv.sensitive) {
1099
+ envObj[envKey] = `\${${envKey}}`;
1100
+ }
1101
+ else {
1102
+ envObj[envKey] = sv.value;
1103
+ }
1104
+ }
1105
+ }
1106
+ entry.env = envObj;
1107
+ }
1108
+ else if (key === "args" && Array.isArray(value)) {
1109
+ entry.args = value;
1110
+ }
1111
+ else if (key === "command" || key === "url") {
1112
+ // Valid MCP server fields: command (stdio), url (SSE)
1113
+ entry[key] = value;
1114
+ }
1115
+ // Skip unknown fields — the parser may leak env vars to top level
1116
+ }
1117
+ // Only include servers that have a command or url (filter parser artifacts)
1118
+ if (!("command" in entry) && !("url" in entry))
1119
+ continue;
1120
+ servers[name] = entry;
1121
+ }
1122
+ return {
1123
+ path: ".cursor/mcp.json",
1124
+ content: JSON.stringify(mcpConfig, null, 2) + "\n",
1125
+ category: "shared-config",
1126
+ };
1127
+ }
1128
+ /**
1129
+ * Generate .cursor/hooks.json from global hooks, per-agent hooks, gate nodes,
1130
+ * and metering configuration.
1131
+ */
1132
+ function generateHooksJson(ast) {
1133
+ const hooksByEvent = {};
1134
+ // Helper to add a hook entry
1135
+ const addHook = (event, entry) => {
1136
+ if (!hooksByEvent[event])
1137
+ hooksByEvent[event] = [];
1138
+ hooksByEvent[event].push(entry);
1139
+ };
1140
+ // Global hooks
1141
+ for (const hook of ast.hooks) {
1142
+ const event = mapHookEvent(hook.on);
1143
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1144
+ const command = hook.run.startsWith("./")
1145
+ ? hook.run
1146
+ : `.cursor/hooks/${scriptName}`;
1147
+ const cursorExt = hook.extensions?.cursor;
1148
+ const entry = {
1149
+ command,
1150
+ };
1151
+ if (hook.matcher)
1152
+ entry.matcher = hook.matcher;
1153
+ if (hook.timeout != null)
1154
+ entry.timeout = Math.ceil(hook.timeout / 1000);
1155
+ // Cursor hooks do NOT have a `type` field (unlike Claude Code).
1156
+ // Only include failClosed when true (false is the default).
1157
+ const failClosed = cursorExt?.failClosed ?? false;
1158
+ if (failClosed)
1159
+ entry.failClosed = true;
1160
+ if (cursorExt?.loop_limit != null) {
1161
+ entry.loop_limit = cursorExt.loop_limit;
1162
+ }
1163
+ addHook(event, entry);
1164
+ }
1165
+ // Per-agent hooks
1166
+ for (const node of ast.nodes) {
1167
+ if (node.type !== "agent")
1168
+ continue;
1169
+ const agent = node;
1170
+ if (!agent.hooks || agent.hooks.length === 0)
1171
+ continue;
1172
+ for (const hook of agent.hooks) {
1173
+ const event = mapHookEvent(hook.on);
1174
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1175
+ const command = hook.run.startsWith("./")
1176
+ ? hook.run
1177
+ : `.cursor/hooks/${agent.id}-${scriptName}`;
1178
+ const cursorExt = hook.extensions?.cursor;
1179
+ const entry = {
1180
+ command,
1181
+ };
1182
+ if (hook.matcher)
1183
+ entry.matcher = hook.matcher;
1184
+ else
1185
+ entry.matcher = agent.id;
1186
+ if (hook.timeout != null)
1187
+ entry.timeout = Math.ceil(hook.timeout / 1000);
1188
+ // Cursor hooks do NOT have a `type` field.
1189
+ const agentFailClosed = cursorExt?.failClosed ?? false;
1190
+ if (agentFailClosed)
1191
+ entry.failClosed = true;
1192
+ if (cursorExt?.loop_limit != null) {
1193
+ entry.loop_limit = cursorExt.loop_limit;
1194
+ }
1195
+ addHook(event, entry);
1196
+ }
1197
+ }
1198
+ // Gate compilation to hooks
1199
+ const gates = ast.nodes.filter((n) => n.type === "gate");
1200
+ for (const gate of gates) {
1201
+ const scriptPath = `.cursor/hooks/gate-${gate.id}.sh`;
1202
+ // Determine event
1203
+ let event;
1204
+ if (gate.before) {
1205
+ event = "preToolUse";
1206
+ }
1207
+ else if (gate.after) {
1208
+ event = "postToolUse";
1209
+ }
1210
+ else {
1211
+ event = "preToolUse";
1212
+ }
1213
+ // Determine matcher
1214
+ const matcher = gate.after ?? gate.before ?? undefined;
1215
+ // Determine failClosed
1216
+ const failClosed = gate.behavior === "blocking" || gate.onFail === "halt";
1217
+ const entry = {
1218
+ command: scriptPath,
1219
+ };
1220
+ if (failClosed)
1221
+ entry.failClosed = true;
1222
+ if (matcher)
1223
+ entry.matcher = matcher;
1224
+ if (gate.timeout) {
1225
+ // Parse duration string to seconds (best-effort)
1226
+ const match = gate.timeout.match(/^(\d+)(s|m|h)?$/);
1227
+ if (match) {
1228
+ const val = parseInt(match[1], 10);
1229
+ const unit = match[2] ?? "s";
1230
+ const seconds = unit === "h" ? val * 3600 : unit === "m" ? val * 60 : val;
1231
+ entry.timeout = seconds;
1232
+ }
1233
+ }
1234
+ const cursorExt = gate.extensions?.cursor;
1235
+ if (cursorExt?.loop_limit != null) {
1236
+ entry.loop_limit = cursorExt.loop_limit;
1237
+ }
1238
+ addHook(event, entry);
1239
+ }
1240
+ // Metering -> postToolUse hook
1241
+ if (ast.metering) {
1242
+ addHook("postToolUse", {
1243
+ command: ".cursor/hooks/metering.sh",
1244
+ });
1245
+ }
1246
+ if (Object.keys(hooksByEvent).length === 0)
1247
+ return null;
1248
+ const hooksJson = {
1249
+ version: 1,
1250
+ hooks: hooksByEvent,
1251
+ };
1252
+ return {
1253
+ path: ".cursor/hooks.json",
1254
+ content: JSON.stringify(hooksJson, null, 2) + "\n",
1255
+ category: "shared-config",
1256
+ };
1257
+ }
1258
+ /** Generate hook script stubs for global hooks. */
1259
+ function generateHookScripts(ast) {
1260
+ const files = [];
1261
+ for (const hook of ast.hooks) {
1262
+ if (!hook.run)
1263
+ continue;
1264
+ // Skip if run already has an absolute/relative path prefix (external script)
1265
+ if (hook.run.startsWith("./") || hook.run.startsWith("/"))
1266
+ continue;
1267
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1268
+ files.push({
1269
+ path: `.cursor/hooks/${scriptName}`,
1270
+ content: [
1271
+ "#!/usr/bin/env bash",
1272
+ `# Hook: ${hook.name} -- fires on ${hook.on}`,
1273
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1274
+ "set -euo pipefail",
1275
+ "",
1276
+ `echo "TODO: implement ${hook.name} hook"`,
1277
+ "",
1278
+ ].join("\n"),
1279
+ category: "script",
1280
+ });
1281
+ }
1282
+ return files;
1283
+ }
1284
+ /** Generate per-agent hook script stubs. */
1285
+ function generateAgentHookScripts(ast) {
1286
+ const files = [];
1287
+ for (const node of ast.nodes) {
1288
+ if (node.type !== "agent")
1289
+ continue;
1290
+ const agent = node;
1291
+ if (!agent.hooks || agent.hooks.length === 0)
1292
+ continue;
1293
+ for (const hook of agent.hooks) {
1294
+ if (!hook.run)
1295
+ continue;
1296
+ if (hook.run.startsWith("./") || hook.run.startsWith("/"))
1297
+ continue;
1298
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1299
+ files.push({
1300
+ path: `.cursor/hooks/${agent.id}-${scriptName}`,
1301
+ content: [
1302
+ "#!/usr/bin/env bash",
1303
+ `# Hook: ${hook.name} -- fires on ${hook.on} (scoped to agent: ${agent.id})`,
1304
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1305
+ "set -euo pipefail",
1306
+ "",
1307
+ `echo "TODO: implement ${hook.name} hook for ${agent.id}"`,
1308
+ "",
1309
+ ].join("\n"),
1310
+ category: "script",
1311
+ });
1312
+ }
1313
+ }
1314
+ return files;
1315
+ }
1316
+ /** Generate tool script stubs. */
1317
+ function generateToolScripts(ast) {
1318
+ const files = [];
1319
+ for (const tool of ast.toolDefs) {
1320
+ const ext = tool.lang === "python" ? "py" : tool.lang === "node" ? "js" : "sh";
1321
+ const shebang = tool.lang === "python"
1322
+ ? "#!/usr/bin/env python3"
1323
+ : tool.lang === "node"
1324
+ ? "#!/usr/bin/env node"
1325
+ : "#!/usr/bin/env bash";
1326
+ const lines = [
1327
+ shebang,
1328
+ `# Tool: ${tool.id} -- ${tool.description}`,
1329
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1330
+ "",
1331
+ ];
1332
+ if (tool.lang === "bash" || !tool.lang) {
1333
+ lines.push("set -euo pipefail");
1334
+ lines.push("");
1335
+ }
1336
+ if (tool.args && tool.args.length > 0) {
1337
+ lines.push(`# Args: ${tool.args.join(", ")}`);
1338
+ lines.push("");
1339
+ }
1340
+ if (tool.lang === "python") {
1341
+ lines.push(`print("TODO: implement ${tool.id}")`);
1342
+ }
1343
+ else if (tool.lang === "node") {
1344
+ lines.push(`console.log("TODO: implement ${tool.id}");`);
1345
+ }
1346
+ else {
1347
+ lines.push(`echo "TODO: implement ${tool.id}"`);
1348
+ }
1349
+ lines.push("");
1350
+ files.push({
1351
+ path: `.cursor/scripts/tools/${tool.id}.${ext}`,
1352
+ content: lines.join("\n"),
1353
+ category: "script",
1354
+ });
1355
+ }
1356
+ return files;
1357
+ }
1358
+ /** Generate metering script stub. */
1359
+ function generateMeteringScript(ast) {
1360
+ if (!ast.metering)
1361
+ return null;
1362
+ const m = ast.metering;
1363
+ const lines = [
1364
+ "#!/usr/bin/env bash",
1365
+ "# Metering / cost tracking script",
1366
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1367
+ "set -euo pipefail",
1368
+ "",
1369
+ `# Track: ${m.track.join(", ")}`,
1370
+ `# Per: ${m.per.join(", ")}`,
1371
+ `# Output: ${m.output} (${m.format})`,
1372
+ `# Pricing: ${m.pricing}`,
1373
+ "",
1374
+ '# Read hook input from stdin',
1375
+ 'INPUT=$(cat)',
1376
+ "",
1377
+ '# TODO: Parse input JSON and record metrics',
1378
+ `echo "TODO: implement metrics collection"`,
1379
+ "",
1380
+ ];
1381
+ return {
1382
+ path: ".cursor/hooks/metering.sh",
1383
+ content: lines.join("\n"),
1384
+ category: "script",
1385
+ };
1386
+ }
1387
+ /** Generate the AGENTS.md context file. */
1388
+ function generateContextFile(ast) {
1389
+ const DEFAULT_PATH = "AGENTS.md";
1390
+ const fileName = ast.context.file ?? DEFAULT_PATH;
1391
+ const sections = [];
1392
+ sections.push(`# ${ast.topology.name}`);
1393
+ sections.push("");
1394
+ if (ast.topology.description) {
1395
+ sections.push(ast.topology.description);
1396
+ sections.push("");
1397
+ }
1398
+ // Agent roster table
1399
+ const allAgents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
1400
+ if (allAgents.length > 0) {
1401
+ sections.push("## Agents");
1402
+ sections.push("");
1403
+ sections.push("| Agent | Description |");
1404
+ sections.push("|-------|-------------|");
1405
+ for (const node of allAgents) {
1406
+ let desc = node.label;
1407
+ if (node.type === "agent") {
1408
+ desc = node.description ?? node.label;
1409
+ }
1410
+ else if (node.type === "human") {
1411
+ desc = node.description ?? node.label;
1412
+ }
1413
+ else if (node.type === "group") {
1414
+ desc = node.description ?? node.label;
1415
+ }
1416
+ sections.push(`| ${node.id} | ${desc} |`);
1417
+ }
1418
+ sections.push("");
1419
+ }
1420
+ // Workflow summary (compiled edges)
1421
+ const edgeText = compileEdges(ast);
1422
+ if (edgeText) {
1423
+ sections.push(edgeText);
1424
+ }
1425
+ // Rules usage guide
1426
+ sections.push("## Rules");
1427
+ sections.push("");
1428
+ sections.push("- Use `@{rule-name}` to activate specific agent rules");
1429
+ sections.push("- Agent rules are in `.cursor/rules/`");
1430
+ sections.push("- MCP servers configured in `.cursor/mcp.json`");
1431
+ sections.push("- Cursor supports Enterprise > Team > Project > User rule hierarchy. These are Project-level rules.");
1432
+ sections.push("");
1433
+ // Providers
1434
+ if (ast.providers && ast.providers.length > 0) {
1435
+ sections.push("## Providers");
1436
+ sections.push("");
1437
+ for (const provider of ast.providers) {
1438
+ let line = `- **${provider.name}**`;
1439
+ if (provider.default)
1440
+ line += " (default)";
1441
+ if (provider.models.length > 0)
1442
+ line += ` -- models: ${provider.models.join(", ")}`;
1443
+ sections.push(line);
1444
+ }
1445
+ sections.push("");
1446
+ }
1447
+ // Checkpoint
1448
+ if (ast.checkpoint) {
1449
+ const cp = ast.checkpoint;
1450
+ sections.push("## Checkpoint");
1451
+ sections.push("");
1452
+ sections.push(`- Backend: ${cp.backend}`);
1453
+ sections.push(`- Strategy: ${cp.strategy}`);
1454
+ if (cp.connection)
1455
+ sections.push(`- Connection: ${cp.connection}`);
1456
+ if (cp.ttl)
1457
+ sections.push(`- TTL: ${cp.ttl}`);
1458
+ sections.push("");
1459
+ }
1460
+ // Artifacts
1461
+ if (ast.artifacts && ast.artifacts.length > 0) {
1462
+ sections.push("## Artifacts");
1463
+ sections.push("");
1464
+ for (const a of ast.artifacts) {
1465
+ let line = `- **${a.id}** (${a.type})`;
1466
+ if (a.path)
1467
+ line += ` -- path: ${a.path}`;
1468
+ if (a.retention)
1469
+ line += `, retention: ${a.retention}`;
1470
+ sections.push(line);
1471
+ }
1472
+ sections.push("");
1473
+ }
1474
+ // Context includes
1475
+ if (ast.context.includes && ast.context.includes.length > 0) {
1476
+ sections.push("## Includes");
1477
+ sections.push("");
1478
+ for (const inc of ast.context.includes) {
1479
+ sections.push(`- ${inc}`);
1480
+ }
1481
+ sections.push("");
1482
+ }
1483
+ return {
1484
+ path: fileName,
1485
+ content: sections.join("\n"),
1486
+ category: "agent",
1487
+ };
1488
+ }
1489
+ /** Generate legacy .cursorrules file (flat markdown, no frontmatter). */
1490
+ function generateCursorrules(ast) {
1491
+ // Only generate if explicitly requested via extensions
1492
+ const cursorExt = ast.extensions?.cursor;
1493
+ if (!cursorExt || cursorExt.legacyRules !== true)
1494
+ return null;
1495
+ const sections = [];
1496
+ sections.push(`# ${ast.topology.name}`);
1497
+ sections.push("");
1498
+ if (ast.topology.description) {
1499
+ sections.push(ast.topology.description);
1500
+ sections.push("");
1501
+ }
1502
+ // Agent summaries
1503
+ const agents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
1504
+ if (agents.length > 0) {
1505
+ sections.push("## Agents");
1506
+ sections.push("");
1507
+ for (const node of agents) {
1508
+ let desc = node.label;
1509
+ if (node.type === "agent") {
1510
+ desc = node.description ?? node.label;
1511
+ }
1512
+ else if (node.type === "human") {
1513
+ desc = node.description ?? node.label;
1514
+ }
1515
+ else if (node.type === "group") {
1516
+ desc = node.description ?? node.label;
1517
+ }
1518
+ sections.push(`- **${node.id}**: ${desc}`);
1519
+ }
1520
+ sections.push("");
1521
+ }
1522
+ // Workflow
1523
+ const edgeText = compileEdges(ast);
1524
+ if (edgeText) {
1525
+ sections.push(edgeText);
1526
+ }
1527
+ // Settings/defaults as behavioral text
1528
+ if (ast.defaults) {
1529
+ const d = ast.defaults;
1530
+ sections.push("## Defaults");
1531
+ sections.push("");
1532
+ if (d.temperature != null)
1533
+ sections.push(`- Temperature: ${d.temperature}`);
1534
+ if (d.maxTokens != null)
1535
+ sections.push(`- Max tokens: ${d.maxTokens}`);
1536
+ if (d.timeout)
1537
+ sections.push(`- Timeout: ${d.timeout}`);
1538
+ sections.push("");
1539
+ }
1540
+ return {
1541
+ path: ".cursorrules",
1542
+ content: sections.join("\n"),
1543
+ category: "agent",
1544
+ };
1545
+ }
1546
+ /** Generate memory directory markers. */
1547
+ function generateMemory(ast) {
1548
+ const files = [];
1549
+ const memKeys = Object.keys(ast.memory);
1550
+ if (memKeys.length > 0) {
1551
+ files.push(gitkeep(".cursor/memory"));
1552
+ }
1553
+ return files;
1554
+ }
1555
+ /** Generate observability hook scripts. */
1556
+ function generateObservabilityScripts(ast) {
1557
+ const files = [];
1558
+ if (!ast.observability || !ast.observability.enabled)
1559
+ return files;
1560
+ const o = ast.observability;
1561
+ const lines = [
1562
+ "#!/usr/bin/env bash",
1563
+ "# Observability capture script",
1564
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1565
+ "set -euo pipefail",
1566
+ "",
1567
+ `# Exporter: ${o.exporter}`,
1568
+ `# Level: ${o.level}`,
1569
+ `# Sample rate: ${o.sampleRate}`,
1570
+ ];
1571
+ if (o.endpoint)
1572
+ lines.push(`# Endpoint: ${o.endpoint}`);
1573
+ if (o.service)
1574
+ lines.push(`# Service: ${o.service}`);
1575
+ lines.push("");
1576
+ lines.push("# Read hook input from stdin");
1577
+ lines.push("INPUT=$(cat)");
1578
+ lines.push("");
1579
+ lines.push('echo "TODO: forward trace data to exporter"');
1580
+ lines.push("");
1581
+ files.push({
1582
+ path: ".cursor/scripts/observability.sh",
1583
+ content: lines.join("\n"),
1584
+ category: "script",
1585
+ });
1586
+ return files;
1587
+ }
1588
+ /** Generate schedule scripts. */
1589
+ function generateScheduleScripts(ast) {
1590
+ const files = [];
1591
+ if (!ast.schedules || ast.schedules.length === 0)
1592
+ return files;
1593
+ for (const job of ast.schedules) {
1594
+ const target = job.agent
1595
+ ? `agent ${job.agent}`
1596
+ : job.action
1597
+ ? `action ${job.action}`
1598
+ : "unknown";
1599
+ const lines = [
1600
+ "#!/usr/bin/env bash",
1601
+ `# Schedule: ${job.id} -- invokes ${target}`,
1602
+ "# Auto-generated by agentopology scaffold -- edit as needed.",
1603
+ "set -euo pipefail",
1604
+ "",
1605
+ ];
1606
+ if (job.enabled === false) {
1607
+ lines.push("# DISABLED -- this schedule is not enabled");
1608
+ lines.push("exit 0");
1609
+ lines.push("");
1610
+ }
1611
+ if (job.cron) {
1612
+ lines.push(`# Cron: ${job.cron}`);
1613
+ }
1614
+ else if (job.every) {
1615
+ lines.push(`# Every: ${job.every}`);
1616
+ }
1617
+ if (job.agent) {
1618
+ lines.push(`# Invoke agent: ${job.agent}`);
1619
+ lines.push(`echo "TODO: invoke agent ${job.agent}"`);
1620
+ }
1621
+ else if (job.action) {
1622
+ lines.push(`# Invoke action: ${job.action}`);
1623
+ lines.push(`bash ./.cursor/scripts/actions/${job.action}.sh`);
1624
+ }
1625
+ lines.push("");
1626
+ files.push({
1627
+ path: `.cursor/scripts/schedule-${job.id}.sh`,
1628
+ content: lines.join("\n"),
1629
+ category: "script",
1630
+ });
1631
+ }
1632
+ return files;
1633
+ }
1634
+ /** Generate schema documentation files. */
1635
+ function generateSchemas(ast) {
1636
+ const files = [];
1637
+ if (!ast.schemas || ast.schemas.length === 0)
1638
+ return files;
1639
+ for (const schema of ast.schemas) {
1640
+ const sections = [];
1641
+ sections.push(`# Schema: ${schema.id}`);
1642
+ sections.push("");
1643
+ sections.push("## Fields");
1644
+ sections.push("");
1645
+ for (const field of schema.fields) {
1646
+ const opt = field.optional ? " (optional)" : "";
1647
+ sections.push(`- **${field.name}**${opt}: ${formatSchemaType(field.type, ast.schemas)}`);
1648
+ }
1649
+ sections.push("");
1650
+ // Embed as a rule that agents can reference
1651
+ const fm = frontmatter({
1652
+ description: `Schema definition: ${schema.id}`,
1653
+ });
1654
+ files.push({
1655
+ path: `.cursor/rules/schema-${schema.id}.mdc`,
1656
+ content: fm + "\n\n" + sections.join("\n"),
1657
+ category: "agent",
1658
+ });
1659
+ }
1660
+ return files;
1661
+ }
1662
+ // ---------------------------------------------------------------------------
1663
+ // Binding export
1664
+ // ---------------------------------------------------------------------------
1665
+ /** Cursor IDE binding. */
1666
+ export const cursorBinding = {
1667
+ name: "cursor",
1668
+ description: "Cursor IDE -- generates .cursor/rules/*.mdc, .cursor/mcp.json, .cursor/hooks.json, and scripts.",
1669
+ scaffold(ast) {
1670
+ // Fragment check
1671
+ if (ast.isFragment) {
1672
+ return deduplicateFiles([
1673
+ generateTopologyOverview(ast),
1674
+ ...generateAgentRules(ast),
1675
+ ]);
1676
+ }
1677
+ const files = [];
1678
+ // Rules
1679
+ files.push(generateTopologyOverview(ast));
1680
+ files.push(...generateAgentRules(ast));
1681
+ files.push(...generateHumanRules(ast));
1682
+ files.push(...generateGroupRules(ast));
1683
+ files.push(...generateSkillRules(ast));
1684
+ files.push(...generateTriggerRules(ast));
1685
+ files.push(...generateSchemas(ast));
1686
+ // Scripts
1687
+ files.push(...generateActionScripts(ast));
1688
+ files.push(...generateGateHooks(ast));
1689
+ files.push(...generateHookScripts(ast));
1690
+ files.push(...generateAgentHookScripts(ast));
1691
+ files.push(...generateToolScripts(ast));
1692
+ files.push(...generateScheduleScripts(ast));
1693
+ files.push(...generateObservabilityScripts(ast));
1694
+ // Config files
1695
+ const mcpFile = generateMcpJson(ast);
1696
+ if (mcpFile)
1697
+ files.push(mcpFile);
1698
+ const hooksFile = generateHooksJson(ast);
1699
+ if (hooksFile)
1700
+ files.push(hooksFile);
1701
+ const meteringFile = generateMeteringScript(ast);
1702
+ if (meteringFile)
1703
+ files.push(meteringFile);
1704
+ // Context
1705
+ files.push(generateContextFile(ast));
1706
+ // Legacy
1707
+ const legacyFile = generateCursorrules(ast);
1708
+ if (legacyFile)
1709
+ files.push(legacyFile);
1710
+ // Memory
1711
+ files.push(...generateMemory(ast));
1712
+ return deduplicateFiles(files);
1713
+ },
1714
+ };
1715
+ //# sourceMappingURL=cursor.js.map