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,1515 @@
1
+ /**
2
+ * GitHub Copilot CLI binding.
3
+ *
4
+ * Generates the `.github/` directory structure for GitHub Copilot CLI
5
+ * (terminal agent) and Copilot Coding Agent (server-side):
6
+ * - `.github/copilot-instructions.md` — project-level instructions
7
+ * - `.github/instructions/{id}.instructions.md` — per-agent scoped instructions
8
+ * - `.github/agents/{id}.agent.md` — per-agent definitions with tools and prompt
9
+ * - `.github/copilot/settings.json` — CLI config (MCP, permissions)
10
+ * - `.github/skills/{name}/SKILL.md` — agent skills
11
+ * - `.github/hooks/{name}.json` — preToolUse/postToolUse hooks
12
+ * - `.github/workflows/copilot-setup-steps.yml` — server-side env setup
13
+ * - `AGENTS.md` — topology overview
14
+ *
15
+ * @module
16
+ */
17
+ import { deduplicateFiles } from "./types.js";
18
+ // ---------------------------------------------------------------------------
19
+ // Helpers
20
+ // ---------------------------------------------------------------------------
21
+ /** Convert a kebab-case id to Title Case. */
22
+ function toTitle(id) {
23
+ return id
24
+ .split(/[-_]/)
25
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
26
+ .join(" ");
27
+ }
28
+ /**
29
+ * Build YAML frontmatter block for a `.instructions.md` file.
30
+ *
31
+ * Copilot `.instructions.md` files support only:
32
+ * - `applyTo` — glob pattern(s) for path-scoping
33
+ * - `excludeAgent` — optional, `"code-review"` or `"coding-agent"`
34
+ */
35
+ function frontmatter(fields) {
36
+ const lines = ["---"];
37
+ for (const [key, value] of Object.entries(fields)) {
38
+ if (value === undefined || value === null)
39
+ continue;
40
+ lines.push(`${key}: "${value}"`);
41
+ }
42
+ lines.push("---");
43
+ return lines.join("\n");
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Tool mapping
47
+ // ---------------------------------------------------------------------------
48
+ /**
49
+ * Map topology-level tool names to Copilot Coding Agent tool aliases.
50
+ *
51
+ * Copilot tool aliases: read, edit, execute, search, web, agent, todo
52
+ */
53
+ const TOOL_MAP = {
54
+ // Claude Code tools -> Copilot aliases
55
+ Read: "read",
56
+ Write: "edit",
57
+ Edit: "edit",
58
+ Bash: "execute",
59
+ Grep: "search",
60
+ Glob: "search",
61
+ WebFetch: "web",
62
+ WebSearch: "web",
63
+ // Pass through Copilot-native aliases
64
+ read: "read",
65
+ edit: "edit",
66
+ execute: "execute",
67
+ search: "search",
68
+ web: "web",
69
+ agent: "agent",
70
+ todo: "todo",
71
+ };
72
+ /** Map a single tool name to its Copilot equivalent. Unknown tools pass through. */
73
+ function mapTool(tool) {
74
+ return TOOL_MAP[tool] ?? tool;
75
+ }
76
+ /** Map and deduplicate a list of tool names. */
77
+ function mapTools(tools) {
78
+ const mapped = new Set();
79
+ for (const t of tools) {
80
+ mapped.add(mapTool(t));
81
+ }
82
+ return [...mapped];
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // Schema & retry helpers
86
+ // ---------------------------------------------------------------------------
87
+ /** Format a SchemaType to a human-readable string. */
88
+ function formatSchemaType(t) {
89
+ switch (t.kind) {
90
+ case "primitive":
91
+ return t.value;
92
+ case "array":
93
+ return `${formatSchemaType(t.itemType)}[]`;
94
+ case "enum":
95
+ return t.values.join(" | ");
96
+ case "ref":
97
+ return t.name;
98
+ }
99
+ }
100
+ /** Format a schema field for documentation. */
101
+ function formatSchemaField(field) {
102
+ const opt = field.optional ? "?" : "";
103
+ const typeStr = formatSchemaType(field.type);
104
+ return `${field.name}${opt}: ${typeStr}`;
105
+ }
106
+ /** Format retry config for documentation. */
107
+ function formatRetry(retry) {
108
+ if (typeof retry === "number")
109
+ return `max ${retry} attempts`;
110
+ const parts = [`max ${retry.max} attempts`];
111
+ if (retry.backoff)
112
+ parts.push(`backoff: ${retry.backoff}`);
113
+ if (retry.interval)
114
+ parts.push(`interval: ${retry.interval}`);
115
+ if (retry.maxInterval)
116
+ parts.push(`max interval: ${retry.maxInterval}`);
117
+ if (retry.jitter)
118
+ parts.push("jitter: on");
119
+ if (retry.nonRetryable && retry.nonRetryable.length > 0) {
120
+ parts.push(`non-retryable: ${retry.nonRetryable.join(", ")}`);
121
+ }
122
+ return parts.join(", ");
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Section generators
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Generate `.github/copilot-instructions.md` — project-level instructions
129
+ * with topology overview, flow, roles, and memory layout.
130
+ */
131
+ function generateInstructions(ast) {
132
+ const sections = [];
133
+ sections.push(`# ${toTitle(ast.topology.name)}`);
134
+ sections.push("");
135
+ if (ast.topology.description) {
136
+ sections.push(ast.topology.description);
137
+ sections.push("");
138
+ }
139
+ sections.push(`Version: ${ast.topology.version}`);
140
+ if (ast.topology.patterns.length > 0) {
141
+ sections.push(`Patterns: ${ast.topology.patterns.join(", ")}`);
142
+ }
143
+ sections.push("");
144
+ // Orchestrator info
145
+ const orch = ast.nodes.find((n) => n.type === "orchestrator");
146
+ if (orch) {
147
+ sections.push("## Orchestrator");
148
+ sections.push("");
149
+ sections.push(`Model: ${orch.model}`);
150
+ if (orch.handles?.length > 0) {
151
+ sections.push(`Handles: ${orch.handles.join(", ")}`);
152
+ }
153
+ if (orch.generates) {
154
+ sections.push(`Generates: ${orch.generates}`);
155
+ }
156
+ sections.push("");
157
+ }
158
+ // Flow overview
159
+ if (ast.edges.length > 0) {
160
+ sections.push("## Flow");
161
+ sections.push("");
162
+ for (const edge of ast.edges) {
163
+ let line = edge.isError ? `${edge.from} -x-> ${edge.to}` : `${edge.from} -> ${edge.to}`;
164
+ if (edge.condition)
165
+ line += ` [when ${edge.condition}]`;
166
+ if (edge.maxIterations)
167
+ line += ` [max ${edge.maxIterations}]`;
168
+ if (edge.errorType)
169
+ line += ` [error: ${edge.errorType}]`;
170
+ if (edge.race)
171
+ line += ` [race]`;
172
+ if (edge.tolerance != null)
173
+ line += ` [tolerance: ${edge.tolerance}]`;
174
+ if (edge.wait)
175
+ line += ` [wait ${edge.wait}]`;
176
+ sections.push(`- ${line}`);
177
+ }
178
+ sections.push("");
179
+ }
180
+ // Agent summary table
181
+ const agents = ast.nodes.filter((n) => n.type === "agent");
182
+ if (agents.length > 0) {
183
+ sections.push("## Agents");
184
+ sections.push("");
185
+ sections.push("| Agent | Phase | Model | Role |");
186
+ sections.push("|-------|-------|-------|------|");
187
+ for (const agent of agents) {
188
+ const phase = agent.phase != null ? String(agent.phase) : "-";
189
+ const model = agent.model ?? "-";
190
+ const role = agent.role ?? "-";
191
+ sections.push(`| ${agent.id} | ${phase} | ${model} | ${role} |`);
192
+ }
193
+ sections.push("");
194
+ }
195
+ // Roles section
196
+ if (Object.keys(ast.roles).length > 0) {
197
+ sections.push("## Roles");
198
+ sections.push("");
199
+ for (const [name, desc] of Object.entries(ast.roles)) {
200
+ sections.push(`### ${toTitle(name)}`);
201
+ sections.push(desc);
202
+ sections.push("");
203
+ }
204
+ }
205
+ // Gates section
206
+ const gates = ast.nodes.filter((n) => n.type === "gate");
207
+ if (gates.length > 0) {
208
+ sections.push("## Gates");
209
+ sections.push("");
210
+ for (const gate of gates) {
211
+ sections.push(`### ${toTitle(gate.id)}`);
212
+ if (gate.after)
213
+ sections.push(`After: ${gate.after}`);
214
+ if (gate.before)
215
+ sections.push(`Before: ${gate.before}`);
216
+ if (gate.run)
217
+ sections.push(`Run: ${gate.run}`);
218
+ if (gate.checks && gate.checks.length > 0) {
219
+ sections.push(`Checks: ${gate.checks.join(", ")}`);
220
+ }
221
+ if (gate.onFail)
222
+ sections.push(`On fail: ${gate.onFail}`);
223
+ sections.push("");
224
+ }
225
+ }
226
+ // Triggers section
227
+ if (ast.triggers.length > 0) {
228
+ sections.push("## Triggers");
229
+ sections.push("");
230
+ for (const trigger of ast.triggers) {
231
+ sections.push(`### /${trigger.name}`);
232
+ sections.push(`Pattern: \`${trigger.pattern}\``);
233
+ if (trigger.argument)
234
+ sections.push(`Argument: ${trigger.argument}`);
235
+ sections.push("");
236
+ }
237
+ }
238
+ // Interfaces section
239
+ if (ast.interfaces.length > 0) {
240
+ sections.push("## Interfaces");
241
+ sections.push("");
242
+ for (const iface of ast.interfaces) {
243
+ const typePart = iface.type ? ` (${iface.type})` : "";
244
+ const configKeys = Object.keys(iface.config);
245
+ const configPart = configKeys.length > 0 ? ` — ${configKeys.map((k) => `${k}: ${iface.config[k]}`).join(", ")}` : "";
246
+ sections.push(`- **${iface.id}**${typePart}${configPart}`);
247
+ }
248
+ sections.push("");
249
+ }
250
+ // Memory section
251
+ if (Object.keys(ast.memory).length > 0) {
252
+ sections.push("## Memory");
253
+ sections.push("");
254
+ for (const [key, value] of Object.entries(ast.memory)) {
255
+ if (value) {
256
+ sections.push(`- **${key}**: configured`);
257
+ }
258
+ }
259
+ sections.push("");
260
+ }
261
+ // Provider credentials
262
+ if (ast.providers && ast.providers.length > 0) {
263
+ const githubProvider = ast.providers.find((p) => p.name === "github");
264
+ if (githubProvider?.apiKey) {
265
+ sections.push("## Authentication");
266
+ sections.push("");
267
+ sections.push(`Set the environment variable referenced by the provider: \`${githubProvider.apiKey}\``);
268
+ sections.push("");
269
+ }
270
+ }
271
+ // Context includes
272
+ if (ast.context.includes && ast.context.includes.length > 0) {
273
+ sections.push("## Includes");
274
+ sections.push("");
275
+ for (const inc of ast.context.includes) {
276
+ sections.push(`- ${inc}`);
277
+ }
278
+ sections.push("");
279
+ }
280
+ return {
281
+ path: ".github/copilot-instructions.md",
282
+ content: sections.join("\n") + "\n",
283
+ category: "agent",
284
+ };
285
+ }
286
+ /**
287
+ * Build an `applyTo` glob pattern from an agent's reads/writes paths.
288
+ *
289
+ * If the agent has file paths, they are joined with commas.
290
+ * If no paths are available, returns `"**"` as a general scope.
291
+ */
292
+ function buildApplyTo(agent) {
293
+ const paths = [];
294
+ if (agent.reads && agent.reads.length > 0)
295
+ paths.push(...agent.reads);
296
+ if (agent.writes && agent.writes.length > 0)
297
+ paths.push(...agent.writes);
298
+ if (paths.length === 0)
299
+ return "**";
300
+ // Convert directory paths (ending with /) to glob patterns
301
+ const globs = paths.map((p) => (p.endsWith("/") ? `${p}**` : p));
302
+ return globs.join(",");
303
+ }
304
+ /**
305
+ * Generate `.github/instructions/{id}.instructions.md` for each agent node.
306
+ *
307
+ * Each file has YAML frontmatter with `applyTo` (the only supported Copilot
308
+ * frontmatter key for scoping). The markdown body contains role, tools,
309
+ * reads/writes, outputs, skills, and behavior information.
310
+ */
311
+ function generateAgents(ast) {
312
+ const files = [];
313
+ for (const node of ast.nodes) {
314
+ if (node.type !== "agent")
315
+ continue;
316
+ const agent = node;
317
+ // --- Build frontmatter (only supported keys: applyTo, excludeAgent) ---
318
+ const fm = {};
319
+ fm.applyTo = buildApplyTo(agent);
320
+ // --- Build body ---
321
+ const sections = [frontmatter(fm), ""];
322
+ // Title
323
+ sections.push(`# ${toTitle(agent.id)} Agent`);
324
+ sections.push("");
325
+ // Description
326
+ const desc = agent.description ??
327
+ ast.roles[agent.role ?? ""] ??
328
+ ast.roles[agent.id] ??
329
+ agent.role;
330
+ if (desc) {
331
+ sections.push(desc);
332
+ sections.push("");
333
+ }
334
+ // Role section
335
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
336
+ if (roleText && roleText !== desc) {
337
+ sections.push("## Role");
338
+ sections.push(roleText);
339
+ sections.push("");
340
+ }
341
+ if (agent.prompt) {
342
+ sections.push("## Instructions");
343
+ sections.push(agent.prompt);
344
+ sections.push("");
345
+ }
346
+ // Tools section
347
+ if (agent.tools && agent.tools.length > 0) {
348
+ const mapped = mapTools(agent.tools);
349
+ sections.push("## Tools");
350
+ sections.push(mapped.join(", "));
351
+ sections.push("");
352
+ }
353
+ // Disallowed tools — document deny-tool flags in agent body
354
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
355
+ const mapped = mapTools(agent.disallowedTools);
356
+ sections.push("## Disallowed Tools");
357
+ sections.push("");
358
+ sections.push("The following tools are denied for this agent via `--deny-tool` flags:");
359
+ for (const t of mapped) {
360
+ sections.push(`- \`--deny-tool ${t}\``);
361
+ }
362
+ sections.push("");
363
+ }
364
+ // Constraints section (phase, timeout, model, etc.)
365
+ {
366
+ const constraints = [];
367
+ if (agent.phase != null)
368
+ constraints.push(`- Phase: ${agent.phase}`);
369
+ if (agent.model)
370
+ constraints.push(`- Model: ${agent.model}`);
371
+ if (agent.timeout)
372
+ constraints.push(`- Timeout: ${agent.timeout}`);
373
+ if (agent.permissions)
374
+ constraints.push(`- Permissions: ${agent.permissions}`);
375
+ if (constraints.length > 0) {
376
+ sections.push("## Constraints");
377
+ sections.push(...constraints);
378
+ sections.push("");
379
+ }
380
+ }
381
+ // Reads section
382
+ if (agent.reads && agent.reads.length > 0) {
383
+ sections.push("## Reads");
384
+ for (const r of agent.reads) {
385
+ sections.push(`- ${r}`);
386
+ }
387
+ sections.push("");
388
+ }
389
+ // Writes section
390
+ if (agent.writes && agent.writes.length > 0) {
391
+ sections.push("## Writes");
392
+ for (const w of agent.writes) {
393
+ sections.push(`- ${w}`);
394
+ }
395
+ sections.push("");
396
+ }
397
+ // Outputs section
398
+ if (agent.outputs) {
399
+ sections.push("## Outputs");
400
+ for (const [field, values] of Object.entries(agent.outputs)) {
401
+ sections.push(`- ${field}: ${values.join(" | ")}`);
402
+ }
403
+ sections.push("");
404
+ }
405
+ // Skills
406
+ if (agent.skills && agent.skills.length > 0) {
407
+ sections.push(`Skills: ${agent.skills.join(", ")}`);
408
+ sections.push("");
409
+ }
410
+ // Behavior
411
+ if (agent.behavior) {
412
+ sections.push(`Behavior: ${agent.behavior}`);
413
+ sections.push("");
414
+ }
415
+ // Skip condition
416
+ if (agent.skip) {
417
+ sections.push(`Skip when: ${agent.skip}`);
418
+ sections.push("");
419
+ }
420
+ // Scale info
421
+ if (agent.scale) {
422
+ sections.push("## Scale");
423
+ sections.push(`Mode: ${agent.scale.mode}`);
424
+ sections.push(`By: ${agent.scale.by}`);
425
+ sections.push(`Range: ${agent.scale.min}-${agent.scale.max}`);
426
+ if (agent.scale.batchSize != null) {
427
+ sections.push(`Batch size: ${agent.scale.batchSize}`);
428
+ }
429
+ sections.push("");
430
+ }
431
+ // Fallback chain
432
+ if (agent.fallbackChain && agent.fallbackChain.length > 0) {
433
+ sections.push("## Fallback Chain");
434
+ sections.push(`Models: ${agent.fallbackChain.join(" -> ")}`);
435
+ sections.push("");
436
+ }
437
+ // On-fail
438
+ if (agent.onFail) {
439
+ sections.push(`On failure: ${agent.onFail}`);
440
+ sections.push("");
441
+ }
442
+ // Retry
443
+ if (agent.retry != null) {
444
+ sections.push(`Retry strategy: ${formatRetry(agent.retry)}`);
445
+ sections.push("");
446
+ }
447
+ // Sampling parameters
448
+ {
449
+ const samplingParts = [];
450
+ if (agent.temperature != null)
451
+ samplingParts.push(`temperature=${agent.temperature}`);
452
+ if (agent.maxTokens != null)
453
+ samplingParts.push(`max-tokens=${agent.maxTokens}`);
454
+ if (agent.topP != null)
455
+ samplingParts.push(`top-p=${agent.topP}`);
456
+ if (agent.topK != null)
457
+ samplingParts.push(`top-k=${agent.topK}`);
458
+ if (agent.seed != null)
459
+ samplingParts.push(`seed=${agent.seed}`);
460
+ if (agent.stop && agent.stop.length > 0)
461
+ samplingParts.push(`stop=${agent.stop.join(", ")}`);
462
+ if (samplingParts.length > 0) {
463
+ sections.push(`Model config: ${samplingParts.join(", ")}`);
464
+ sections.push("");
465
+ }
466
+ }
467
+ // Thinking
468
+ if (agent.thinking) {
469
+ let thinkingLine = `Reasoning level: ${agent.thinking}`;
470
+ if (agent.thinkingBudget != null) {
471
+ thinkingLine += ` (budget: ${agent.thinkingBudget} tokens)`;
472
+ }
473
+ sections.push(thinkingLine);
474
+ sections.push("");
475
+ }
476
+ // Output format
477
+ if (agent.outputFormat) {
478
+ sections.push(`Output format: ${agent.outputFormat}`);
479
+ sections.push("");
480
+ }
481
+ // Log level
482
+ if (agent.logLevel) {
483
+ sections.push(`Log verbosity: ${agent.logLevel}`);
484
+ sections.push("");
485
+ }
486
+ // Join semantics
487
+ if (agent.join) {
488
+ sections.push(`Wait for: ${agent.join} (join strategy for upstream agents)`);
489
+ sections.push("");
490
+ }
491
+ // Circuit breaker
492
+ if (agent.circuitBreaker) {
493
+ const cb = agent.circuitBreaker;
494
+ sections.push("## Circuit Breaker");
495
+ sections.push(`- Failure threshold: ${cb.threshold}`);
496
+ sections.push(`- Window: ${cb.window}`);
497
+ sections.push(`- Cooldown: ${cb.cooldown}`);
498
+ sections.push("");
499
+ }
500
+ // Compensation (saga)
501
+ if (agent.compensates) {
502
+ sections.push(`This agent compensates (can undo) the work of: ${agent.compensates}`);
503
+ sections.push("");
504
+ }
505
+ // Input/output schemas
506
+ if (agent.inputSchema && agent.inputSchema.length > 0) {
507
+ sections.push("## Input Schema");
508
+ for (const field of agent.inputSchema) {
509
+ sections.push(`- ${formatSchemaField(field)}`);
510
+ }
511
+ sections.push("");
512
+ }
513
+ if (agent.outputSchema && agent.outputSchema.length > 0) {
514
+ sections.push("## Output Schema");
515
+ for (const field of agent.outputSchema) {
516
+ sections.push(`- ${formatSchemaField(field)}`);
517
+ }
518
+ sections.push("");
519
+ }
520
+ // Rate limit
521
+ if (agent.rateLimit) {
522
+ sections.push(`Rate limit: ${agent.rateLimit}`);
523
+ sections.push("");
524
+ }
525
+ // Produces / consumes artifacts
526
+ if ((agent.produces && agent.produces.length > 0) || (agent.consumes && agent.consumes.length > 0)) {
527
+ sections.push("## Artifacts");
528
+ if (agent.produces && agent.produces.length > 0) {
529
+ sections.push(`Produces: ${agent.produces.join(", ")}`);
530
+ }
531
+ if (agent.consumes && agent.consumes.length > 0) {
532
+ sections.push(`Consumes: ${agent.consumes.join(", ")}`);
533
+ }
534
+ sections.push("");
535
+ }
536
+ // Prompt variants
537
+ if (agent.variants && agent.variants.length > 0) {
538
+ sections.push("## Prompt Variants");
539
+ for (const v of agent.variants) {
540
+ const parts = [`**${v.id}** (weight: ${v.weight})`];
541
+ if (v.model)
542
+ parts.push(`model: ${v.model}`);
543
+ if (v.temperature != null)
544
+ parts.push(`temperature: ${v.temperature}`);
545
+ sections.push(`- ${parts.join(", ")}`);
546
+ }
547
+ sections.push("");
548
+ }
549
+ files.push({
550
+ path: `.github/instructions/${agent.id}.instructions.md`,
551
+ content: sections.join("\n") + "\n",
552
+ category: "agent",
553
+ });
554
+ }
555
+ return files;
556
+ }
557
+ /**
558
+ * Generate `.github/instructions/{id}.instructions.md` for each human node.
559
+ *
560
+ * Human nodes represent approval/input points. The instructions file tells
561
+ * Copilot to pause and request human intervention. These are informational
562
+ * context files only — no `.agent.md` is generated for human nodes since
563
+ * they are not automated agents.
564
+ */
565
+ function generateHumanAgents(ast) {
566
+ const files = [];
567
+ for (const node of ast.nodes) {
568
+ if (node.type !== "human")
569
+ continue;
570
+ const human = node;
571
+ const sections = ["<!-- Informational: human-in-the-loop node (not an automated agent) -->", ""];
572
+ sections.push(`# ${toTitle(human.id)} (Human Approval)`);
573
+ sections.push("");
574
+ sections.push("This is a human-in-the-loop checkpoint. You must pause execution and request human input or approval before proceeding.");
575
+ sections.push("");
576
+ if (human.description) {
577
+ sections.push("## Instructions");
578
+ sections.push(human.description);
579
+ sections.push("");
580
+ }
581
+ if (human.timeout) {
582
+ sections.push(`Timeout: ${human.timeout}`);
583
+ sections.push("");
584
+ }
585
+ if (human.onTimeout) {
586
+ sections.push(`On timeout: ${human.onTimeout}`);
587
+ sections.push("");
588
+ }
589
+ files.push({
590
+ path: `.github/instructions/${human.id}.instructions.md`,
591
+ content: sections.join("\n") + "\n",
592
+ category: "machine",
593
+ });
594
+ }
595
+ return files;
596
+ }
597
+ /**
598
+ * Generate `.github/instructions/{id}.instructions.md` for each group node.
599
+ *
600
+ * Group nodes represent multi-agent conversation/debate coordination points.
601
+ * These are informational context files only — no `.agent.md` is generated
602
+ * for group nodes since they are coordination constructs, not automated agents.
603
+ */
604
+ function generateGroupAgents(ast) {
605
+ const files = [];
606
+ for (const node of ast.nodes) {
607
+ if (node.type !== "group")
608
+ continue;
609
+ const group = node;
610
+ const sections = ["<!-- Informational: group chat coordination node (not an automated agent) -->", ""];
611
+ sections.push(`# ${toTitle(group.id)} (Group Chat)`);
612
+ sections.push("");
613
+ if (group.description) {
614
+ sections.push(group.description);
615
+ sections.push("");
616
+ }
617
+ sections.push("## Role");
618
+ sections.push("Coordinate a multi-agent conversation among the participating members.");
619
+ sections.push("");
620
+ sections.push("## Members");
621
+ for (const member of group.members) {
622
+ sections.push(`- ${member}`);
623
+ }
624
+ sections.push("");
625
+ if (group.speakerSelection) {
626
+ sections.push(`Speaker selection: ${group.speakerSelection}`);
627
+ sections.push("");
628
+ }
629
+ if (group.maxRounds != null) {
630
+ sections.push(`Maximum rounds: ${group.maxRounds}`);
631
+ sections.push("");
632
+ }
633
+ if (group.termination) {
634
+ sections.push(`Termination condition: ${group.termination}`);
635
+ sections.push("");
636
+ }
637
+ if (group.timeout) {
638
+ sections.push(`Timeout: ${group.timeout}`);
639
+ sections.push("");
640
+ }
641
+ files.push({
642
+ path: `.github/instructions/${group.id}.instructions.md`,
643
+ content: sections.join("\n") + "\n",
644
+ category: "agent",
645
+ });
646
+ }
647
+ return files;
648
+ }
649
+ /**
650
+ * Build YAML frontmatter block for a `.agent.md` file.
651
+ *
652
+ * Copilot `.agent.md` files support:
653
+ * - `name` — agent identifier
654
+ * - `description` — agent description
655
+ * - `tools` — list of Copilot tool aliases
656
+ */
657
+ function agentFrontmatter(fields) {
658
+ const lines = ["---"];
659
+ lines.push(`name: ${fields.name}`);
660
+ lines.push(`description: ${fields.description}`);
661
+ if (fields.model) {
662
+ lines.push(`model: ${fields.model}`);
663
+ }
664
+ if (fields.tools.length > 0) {
665
+ lines.push("tools:");
666
+ for (const tool of fields.tools) {
667
+ lines.push(` - ${tool}`);
668
+ }
669
+ }
670
+ lines.push("---");
671
+ return lines.join("\n");
672
+ }
673
+ /**
674
+ * Generate `.github/agents/{id}.agent.md` for each agent node.
675
+ *
676
+ * Each file has YAML frontmatter with `name`, `description`, and `tools`
677
+ * (mapped to Copilot tool aliases). The markdown body contains the agent
678
+ * prompt and role information.
679
+ *
680
+ * Human and group nodes do NOT get `.agent.md` files since they are not agents.
681
+ */
682
+ function generateAgentMdFiles(ast) {
683
+ const files = [];
684
+ for (const node of ast.nodes) {
685
+ if (node.type !== "agent")
686
+ continue;
687
+ const agent = node;
688
+ // Build description from available sources
689
+ const desc = agent.description ??
690
+ ast.roles[agent.role ?? ""] ??
691
+ ast.roles[agent.id] ??
692
+ agent.role ??
693
+ toTitle(agent.id);
694
+ // Map tools to Copilot aliases
695
+ const tools = agent.tools ? mapTools(agent.tools) : [];
696
+ const sections = [];
697
+ sections.push(agentFrontmatter({ name: agent.id, description: desc, tools, model: agent.model }));
698
+ sections.push("");
699
+ // Agent prompt as body
700
+ if (agent.prompt) {
701
+ sections.push(agent.prompt);
702
+ sections.push("");
703
+ }
704
+ // Role context
705
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
706
+ if (roleText && roleText !== agent.prompt) {
707
+ sections.push(`## Role`);
708
+ sections.push(roleText);
709
+ sections.push("");
710
+ }
711
+ // Description if different from prompt
712
+ if (agent.description && agent.description !== agent.prompt) {
713
+ sections.push(`## Description`);
714
+ sections.push(agent.description);
715
+ sections.push("");
716
+ }
717
+ // Behavior constraints
718
+ if (agent.permissions) {
719
+ sections.push(`Permissions: ${agent.permissions}`);
720
+ sections.push("");
721
+ }
722
+ if (agent.onFail) {
723
+ sections.push(`On failure: ${agent.onFail}`);
724
+ sections.push("");
725
+ }
726
+ files.push({
727
+ path: `.github/agents/${agent.id}.agent.md`,
728
+ content: sections.join("\n") + "\n",
729
+ category: "agent",
730
+ });
731
+ }
732
+ return files;
733
+ }
734
+ /**
735
+ * Generate `.github/workflows/copilot-setup-steps.yml` — the REQUIRED
736
+ * Copilot environment setup workflow.
737
+ *
738
+ * This file must have the exact job name `copilot-setup-steps` and is used
739
+ * for environment setup only (checkout, install deps, set env vars, make
740
+ * scripts executable). It is NOT a topology orchestration workflow.
741
+ *
742
+ * Generated from the topology's gate scripts, tool scripts, and env vars.
743
+ */
744
+ function generateSetupSteps(ast) {
745
+ const gates = ast.nodes.filter((n) => n.type === "gate");
746
+ const hasGateScripts = gates.some((g) => g.run && g.behavior !== "advisory");
747
+ const hasPythonTools = ast.toolDefs.some((t) => t.lang === "python");
748
+ const hasEnv = Object.keys(ast.env).length > 0;
749
+ // Only generate if there's something to set up
750
+ if (!hasGateScripts && !hasPythonTools && ast.toolDefs.length === 0 && !hasEnv) {
751
+ return null;
752
+ }
753
+ const name = ast.topology.name;
754
+ const lines = [];
755
+ lines.push(`# Auto-generated by agentopology scaffold for topology: ${name}`);
756
+ lines.push(`# Copilot setup steps — environment preparation for the coding agent.`);
757
+ lines.push("");
758
+ lines.push(`name: "Copilot Setup Steps"`);
759
+ lines.push("on: workflow_dispatch");
760
+ lines.push("");
761
+ lines.push("jobs:");
762
+ lines.push(" copilot-setup-steps:");
763
+ lines.push(" runs-on: ubuntu-latest");
764
+ // Set env vars from topology
765
+ if (hasEnv) {
766
+ lines.push(" env:");
767
+ for (const [key, val] of Object.entries(ast.env)) {
768
+ const value = typeof val === "string" ? val : val.value;
769
+ // Skip secret references — those should be in GitHub Secrets
770
+ if (typeof val !== "string" && val.sensitive) {
771
+ lines.push(` ${key}: \${{ secrets.${key} }}`);
772
+ }
773
+ else {
774
+ lines.push(` ${key}: "${value}"`);
775
+ }
776
+ }
777
+ }
778
+ lines.push(" steps:");
779
+ lines.push(" - uses: actions/checkout@v4");
780
+ // Make gate scripts executable
781
+ if (hasGateScripts) {
782
+ lines.push("");
783
+ lines.push(" - name: Make gate scripts executable");
784
+ lines.push(" run: |");
785
+ lines.push(" chmod +x scripts/*.sh 2>/dev/null || true");
786
+ }
787
+ // Make tool scripts executable
788
+ const bashTools = ast.toolDefs.filter((t) => t.lang === "bash");
789
+ if (bashTools.length > 0) {
790
+ lines.push("");
791
+ lines.push(" - name: Make tool scripts executable");
792
+ lines.push(" run: |");
793
+ for (const tool of bashTools) {
794
+ lines.push(` chmod +x ${tool.script}`);
795
+ }
796
+ }
797
+ // Install Python dependencies if Python tools exist
798
+ if (hasPythonTools) {
799
+ lines.push("");
800
+ lines.push(" - name: Install Python dependencies");
801
+ lines.push(" run: |");
802
+ lines.push(" pip install -r requirements.txt 2>/dev/null || true");
803
+ }
804
+ lines.push("");
805
+ return {
806
+ path: ".github/workflows/copilot-setup-steps.yml",
807
+ content: lines.join("\n") + "\n",
808
+ category: "machine",
809
+ };
810
+ }
811
+ /**
812
+ * Generate gate check documentation in copilot-instructions.
813
+ *
814
+ * Since Copilot agents run in sandboxed GitHub Actions, gates are
815
+ * expressed as run_command invocations documented in instructions.
816
+ */
817
+ function generateGateInstructions(ast) {
818
+ const gates = ast.nodes.filter((n) => n.type === "gate");
819
+ if (gates.length === 0)
820
+ return "";
821
+ const lines = [];
822
+ lines.push("## Quality Gates");
823
+ lines.push("");
824
+ lines.push("The following quality gates must pass before proceeding:");
825
+ lines.push("");
826
+ for (const gate of gates) {
827
+ lines.push(`### ${toTitle(gate.id)}`);
828
+ if (gate.after)
829
+ lines.push(`- Run after: ${gate.after}`);
830
+ if (gate.before)
831
+ lines.push(`- Run before: ${gate.before}`);
832
+ if (gate.run)
833
+ lines.push(`- Command: \`${gate.run}\``);
834
+ if (gate.checks && gate.checks.length > 0) {
835
+ lines.push(`- Checks: ${gate.checks.join(", ")}`);
836
+ }
837
+ if (gate.onFail)
838
+ lines.push(`- On failure: ${gate.onFail}`);
839
+ lines.push("");
840
+ }
841
+ return lines.join("\n");
842
+ }
843
+ /**
844
+ * Generate hook documentation for copilot-instructions.
845
+ *
846
+ * Copilot does not have a native hook system, so hooks are documented
847
+ * as recommended practices in the instructions file.
848
+ */
849
+ function generateHookInstructions(ast) {
850
+ if (ast.hooks.length === 0)
851
+ return "";
852
+ const lines = [];
853
+ lines.push("## Hooks");
854
+ lines.push("");
855
+ lines.push("The following hooks should be run at the appropriate times:");
856
+ lines.push("");
857
+ for (const hook of ast.hooks) {
858
+ lines.push(`### ${hook.name}`);
859
+ lines.push(`- Event: ${hook.on}`);
860
+ if (hook.matcher)
861
+ lines.push(`- Matcher: ${hook.matcher}`);
862
+ lines.push(`- Run: \`${hook.run}\``);
863
+ if (hook.timeout)
864
+ lines.push(`- Timeout: ${hook.timeout}ms`);
865
+ lines.push("");
866
+ }
867
+ return lines.join("\n");
868
+ }
869
+ /**
870
+ * Generate memory documentation as a section to append to copilot-instructions.
871
+ *
872
+ * Since Copilot agents operate on the repository, memory paths are documented
873
+ * so agents know where to read/write shared state.
874
+ */
875
+ function generateMemoryInstructions(ast) {
876
+ const agents = ast.nodes.filter((n) => n.type === "agent");
877
+ const hasReadsWrites = agents.some((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
878
+ if (!hasReadsWrites && Object.keys(ast.memory).length === 0)
879
+ return "";
880
+ const lines = [];
881
+ lines.push("## Memory Paths");
882
+ lines.push("");
883
+ if (Object.keys(ast.memory).length > 0) {
884
+ lines.push("### Configured Memory");
885
+ for (const [key, value] of Object.entries(ast.memory)) {
886
+ if (value) {
887
+ lines.push(`- **${key}**: configured`);
888
+ }
889
+ }
890
+ lines.push("");
891
+ }
892
+ if (hasReadsWrites) {
893
+ lines.push("### Agent Read/Write Access");
894
+ lines.push("");
895
+ lines.push("| Agent | Reads | Writes |");
896
+ lines.push("|-------|-------|--------|");
897
+ for (const agent of agents) {
898
+ const reads = agent.reads?.join(", ") ?? "-";
899
+ const writes = agent.writes?.join(", ") ?? "-";
900
+ if (reads !== "-" || writes !== "-") {
901
+ lines.push(`| ${agent.id} | ${reads} | ${writes} |`);
902
+ }
903
+ }
904
+ lines.push("");
905
+ }
906
+ return lines.join("\n");
907
+ }
908
+ /**
909
+ * Generate metering documentation for copilot-instructions.
910
+ */
911
+ function generateMeteringInstructions(ast) {
912
+ if (!ast.metering)
913
+ return "";
914
+ const m = ast.metering;
915
+ const lines = [];
916
+ lines.push("## Metering");
917
+ lines.push("");
918
+ lines.push(`- Track: ${m.track.join(", ")}`);
919
+ lines.push(`- Per: ${m.per.join(", ")}`);
920
+ lines.push(`- Output: ${m.output} (${m.format})`);
921
+ lines.push(`- Pricing: ${m.pricing}`);
922
+ lines.push("");
923
+ return lines.join("\n");
924
+ }
925
+ /**
926
+ * Generate defaults documentation for copilot-instructions.
927
+ */
928
+ function generateDefaultsInstructions(ast) {
929
+ if (!ast.defaults)
930
+ return "";
931
+ const d = ast.defaults;
932
+ const lines = [];
933
+ lines.push("## Defaults");
934
+ lines.push("");
935
+ lines.push("The following default settings apply to all agents unless overridden:");
936
+ lines.push("");
937
+ if (d.temperature != null)
938
+ lines.push(`- Temperature: ${d.temperature}`);
939
+ if (d.maxTokens != null)
940
+ lines.push(`- Max tokens: ${d.maxTokens}`);
941
+ if (d.topP != null)
942
+ lines.push(`- Top-p: ${d.topP}`);
943
+ if (d.topK != null)
944
+ lines.push(`- Top-k: ${d.topK}`);
945
+ if (d.stop && d.stop.length > 0)
946
+ lines.push(`- Stop sequences: ${d.stop.join(", ")}`);
947
+ if (d.seed != null)
948
+ lines.push(`- Seed: ${d.seed}`);
949
+ if (d.thinking)
950
+ lines.push(`- Thinking level: ${d.thinking}`);
951
+ if (d.thinkingBudget != null)
952
+ lines.push(`- Thinking budget: ${d.thinkingBudget} tokens`);
953
+ if (d.outputFormat)
954
+ lines.push(`- Output format: ${d.outputFormat}`);
955
+ if (d.timeout)
956
+ lines.push(`- Timeout: ${d.timeout}`);
957
+ if (d.logLevel)
958
+ lines.push(`- Log level: ${d.logLevel}`);
959
+ lines.push("");
960
+ return lines.join("\n");
961
+ }
962
+ /**
963
+ * Generate observability documentation for copilot-instructions.
964
+ */
965
+ function generateObservabilityInstructions(ast) {
966
+ if (!ast.observability)
967
+ return "";
968
+ const o = ast.observability;
969
+ const lines = [];
970
+ lines.push("## Observability");
971
+ lines.push("");
972
+ lines.push(`- Enabled: ${o.enabled}`);
973
+ lines.push(`- Level: ${o.level}`);
974
+ lines.push(`- Exporter: ${o.exporter}`);
975
+ if (o.endpoint)
976
+ lines.push(`- Endpoint: ${o.endpoint}`);
977
+ if (o.service)
978
+ lines.push(`- Service: ${o.service}`);
979
+ lines.push(`- Sample rate: ${o.sampleRate}`);
980
+ lines.push("");
981
+ const captureItems = [];
982
+ if (o.capture.prompts)
983
+ captureItems.push("prompts");
984
+ if (o.capture.completions)
985
+ captureItems.push("completions");
986
+ if (o.capture.toolArgs)
987
+ captureItems.push("tool-args");
988
+ if (o.capture.toolResults)
989
+ captureItems.push("tool-results");
990
+ if (captureItems.length > 0)
991
+ lines.push(`- Capture: ${captureItems.join(", ")}`);
992
+ const spanItems = [];
993
+ if (o.spans.agents)
994
+ spanItems.push("agents");
995
+ if (o.spans.tools)
996
+ spanItems.push("tools");
997
+ if (o.spans.gates)
998
+ spanItems.push("gates");
999
+ if (o.spans.memory)
1000
+ spanItems.push("memory");
1001
+ if (spanItems.length > 0)
1002
+ lines.push(`- Spans: ${spanItems.join(", ")}`);
1003
+ lines.push("");
1004
+ return lines.join("\n");
1005
+ }
1006
+ /**
1007
+ * Generate checkpoint documentation for copilot-instructions.
1008
+ */
1009
+ function generateCheckpointInstructions(ast) {
1010
+ if (!ast.checkpoint)
1011
+ return "";
1012
+ const c = ast.checkpoint;
1013
+ const lines = [];
1014
+ lines.push("## Checkpoint");
1015
+ lines.push("");
1016
+ lines.push(`- Backend: ${c.backend}`);
1017
+ lines.push(`- Strategy: ${c.strategy}`);
1018
+ if (c.connection)
1019
+ lines.push(`- Connection: ${c.connection}`);
1020
+ if (c.ttl)
1021
+ lines.push(`- TTL: ${c.ttl}`);
1022
+ if (c.replay) {
1023
+ lines.push(`- Replay: ${c.replay.enabled ? "enabled" : "disabled"}`);
1024
+ if (c.replay.maxHistory != null)
1025
+ lines.push(` - Max history: ${c.replay.maxHistory}`);
1026
+ if (c.replay.branch != null)
1027
+ lines.push(` - Branch: ${c.replay.branch}`);
1028
+ }
1029
+ lines.push("");
1030
+ return lines.join("\n");
1031
+ }
1032
+ /**
1033
+ * Generate depth levels documentation for copilot-instructions.
1034
+ */
1035
+ function generateDepthInstructions(ast) {
1036
+ if (!ast.depth || !ast.depth.levels || ast.depth.levels.length === 0)
1037
+ return "";
1038
+ const lines = [];
1039
+ lines.push("## Depth Levels");
1040
+ lines.push("");
1041
+ if (ast.depth.factors.length > 0) {
1042
+ lines.push(`Factors: ${ast.depth.factors.join(", ")}`);
1043
+ }
1044
+ for (const level of ast.depth.levels) {
1045
+ let line = `- **Level ${level.level}**: ${level.label}`;
1046
+ if (level.omit.length > 0)
1047
+ line += ` (omit: ${level.omit.join(", ")})`;
1048
+ lines.push(line);
1049
+ }
1050
+ lines.push("");
1051
+ return lines.join("\n");
1052
+ }
1053
+ /**
1054
+ * Generate params documentation for copilot-instructions.
1055
+ */
1056
+ function generateParamsInstructions(ast) {
1057
+ if (!ast.params || ast.params.length === 0)
1058
+ return "";
1059
+ const lines = [];
1060
+ lines.push("## Parameters");
1061
+ lines.push("");
1062
+ for (const p of ast.params) {
1063
+ const req = p.required ? " (required)" : "";
1064
+ const def = p.default != null ? ` = ${p.default}` : "";
1065
+ lines.push(`- **${p.name}**: ${p.type}${req}${def}`);
1066
+ }
1067
+ lines.push("");
1068
+ return lines.join("\n");
1069
+ }
1070
+ /**
1071
+ * Generate interface endpoints documentation for copilot-instructions.
1072
+ */
1073
+ function generateInterfaceEndpointsInstructions(ast) {
1074
+ if (!ast.interfaceEndpoints)
1075
+ return "";
1076
+ const lines = [];
1077
+ lines.push("## Interface");
1078
+ lines.push("");
1079
+ lines.push(`- Entry: ${ast.interfaceEndpoints.entry}`);
1080
+ lines.push(`- Exit: ${ast.interfaceEndpoints.exit}`);
1081
+ lines.push("");
1082
+ return lines.join("\n");
1083
+ }
1084
+ /**
1085
+ * Generate imports documentation for copilot-instructions.
1086
+ */
1087
+ function generateImportsInstructions(ast) {
1088
+ if (!ast.imports || ast.imports.length === 0)
1089
+ return "";
1090
+ const lines = [];
1091
+ lines.push("## Imports");
1092
+ lines.push("");
1093
+ for (const imp of ast.imports) {
1094
+ let line = `- **${imp.alias}** from \`${imp.source}\``;
1095
+ if (imp.sha256)
1096
+ line += ` (sha256: ${imp.sha256})`;
1097
+ if (imp.registry)
1098
+ line += ` [registry: ${imp.registryPackage}@${imp.registryVersion}]`;
1099
+ lines.push(line);
1100
+ if (Object.keys(imp.params).length > 0) {
1101
+ for (const [k, v] of Object.entries(imp.params)) {
1102
+ lines.push(` - ${k}: ${v}`);
1103
+ }
1104
+ }
1105
+ }
1106
+ lines.push("");
1107
+ return lines.join("\n");
1108
+ }
1109
+ /**
1110
+ * Generate includes documentation for copilot-instructions.
1111
+ */
1112
+ function generateIncludesInstructions(ast) {
1113
+ if (!ast.includes || ast.includes.length === 0)
1114
+ return "";
1115
+ const lines = [];
1116
+ lines.push("## Includes");
1117
+ lines.push("");
1118
+ for (const inc of ast.includes) {
1119
+ lines.push(`- ${inc.source}`);
1120
+ }
1121
+ lines.push("");
1122
+ return lines.join("\n");
1123
+ }
1124
+ /**
1125
+ * Generate environment variables documentation for copilot-instructions.
1126
+ */
1127
+ function generateEnvInstructions(ast) {
1128
+ if (Object.keys(ast.env).length === 0)
1129
+ return "";
1130
+ const lines = [];
1131
+ lines.push("## Environment");
1132
+ lines.push("");
1133
+ for (const [key, val] of Object.entries(ast.env)) {
1134
+ const value = typeof val === "string" ? val : val.value;
1135
+ lines.push(`- \`${key}\`: ${value}`);
1136
+ }
1137
+ lines.push("");
1138
+ return lines.join("\n");
1139
+ }
1140
+ /**
1141
+ * Generate artifacts documentation for copilot-instructions.
1142
+ */
1143
+ function generateArtifactsInstructions(ast) {
1144
+ if (!ast.artifacts || ast.artifacts.length === 0)
1145
+ return "";
1146
+ const lines = [];
1147
+ lines.push("## Artifacts");
1148
+ lines.push("");
1149
+ for (const artifact of ast.artifacts) {
1150
+ const parts = [`**${artifact.id}** (${artifact.type})`];
1151
+ if (artifact.path)
1152
+ parts.push(`path: ${artifact.path}`);
1153
+ if (artifact.retention)
1154
+ parts.push(`retention: ${artifact.retention}`);
1155
+ lines.push(`- ${parts.join(", ")}`);
1156
+ if (artifact.dependsOn && artifact.dependsOn.length > 0) {
1157
+ lines.push(` - Depends on: ${artifact.dependsOn.join(", ")}`);
1158
+ }
1159
+ }
1160
+ lines.push("");
1161
+ return lines.join("\n");
1162
+ }
1163
+ // ---------------------------------------------------------------------------
1164
+ // Gate script generation
1165
+ // ---------------------------------------------------------------------------
1166
+ /**
1167
+ * Generate gate scripts at `scripts/gate-{id}.sh`.
1168
+ *
1169
+ * For each non-advisory gate with a `run:` field, a wrapper script is created
1170
+ * that executes the gate command and handles failure according to `on-fail`.
1171
+ */
1172
+ function generateGateScripts(ast) {
1173
+ const files = [];
1174
+ for (const node of ast.nodes) {
1175
+ if (node.type !== "gate")
1176
+ continue;
1177
+ const gate = node;
1178
+ if (!gate.run)
1179
+ continue;
1180
+ if (gate.behavior === "advisory")
1181
+ continue;
1182
+ const isHalt = gate.onFail === "halt";
1183
+ const scriptLines = [
1184
+ "#!/usr/bin/env bash",
1185
+ `# Gate: ${gate.id}`,
1186
+ `# Auto-generated by agentopology scaffold — edit as needed.`,
1187
+ "set -euo pipefail",
1188
+ "",
1189
+ "# Gate runs after: " + (gate.after || "any"),
1190
+ "# Gate runs before: " + (gate.before || "any"),
1191
+ `# On failure: ${gate.onFail || "halt"}`,
1192
+ "",
1193
+ "# Run the gate check",
1194
+ "GATE_RESULT=0",
1195
+ `${gate.run} || GATE_RESULT=$?`,
1196
+ "",
1197
+ 'if [ "$GATE_RESULT" -ne 0 ]; then',
1198
+ ` echo "Gate '${gate.id}' FAILED (exit code $GATE_RESULT)"`,
1199
+ ];
1200
+ if (isHalt) {
1201
+ scriptLines.push(" exit 1");
1202
+ }
1203
+ else {
1204
+ scriptLines.push(` echo "Gate failed — bounce-back requested"`);
1205
+ scriptLines.push(" exit 0 # allow continuation for bounce-back");
1206
+ }
1207
+ scriptLines.push("fi");
1208
+ scriptLines.push("");
1209
+ scriptLines.push(`echo "Gate '${gate.id}' PASSED"`);
1210
+ scriptLines.push("");
1211
+ const content = scriptLines.join("\n");
1212
+ files.push({
1213
+ path: `scripts/gate-${gate.id}.sh`,
1214
+ content,
1215
+ category: "script",
1216
+ });
1217
+ }
1218
+ return files;
1219
+ }
1220
+ // ---------------------------------------------------------------------------
1221
+ // Settings, MCP, Skills, Hooks, AGENTS.md — Copilot CLI terminal features
1222
+ // ---------------------------------------------------------------------------
1223
+ /**
1224
+ * Generate `.github/copilot/settings.json` with MCP servers and permissions.
1225
+ */
1226
+ function generateSettings(ast) {
1227
+ const settings = {
1228
+ trusted_folders: ["."],
1229
+ };
1230
+ // URL allow/deny from settings block
1231
+ const allow = ast.settings?.allow;
1232
+ const deny = ast.settings?.deny;
1233
+ const allowedUrls = (allow ?? []).filter((t) => t.startsWith("http"));
1234
+ const deniedUrls = (deny ?? []).filter((t) => t.startsWith("http"));
1235
+ if (allowedUrls.length > 0)
1236
+ settings.allowed_urls = allowedUrls;
1237
+ if (deniedUrls.length > 0)
1238
+ settings.denied_urls = deniedUrls;
1239
+ // MCP servers
1240
+ if (Object.keys(ast.mcpServers).length > 0) {
1241
+ const mcpServers = {};
1242
+ for (const [name, config] of Object.entries(ast.mcpServers)) {
1243
+ const entry = {};
1244
+ for (const [key, value] of Object.entries(config)) {
1245
+ entry[key] = value;
1246
+ }
1247
+ // Copilot CLI uses "local" for stdio servers
1248
+ if (entry.command && (!entry.type || entry.type === "stdio")) {
1249
+ entry.type = "local";
1250
+ }
1251
+ if (!entry.env && entry.command) {
1252
+ entry.env = {};
1253
+ }
1254
+ mcpServers[name] = entry;
1255
+ }
1256
+ settings.mcpServers = mcpServers;
1257
+ }
1258
+ return {
1259
+ path: ".github/copilot/settings.json",
1260
+ content: JSON.stringify(settings, null, 2) + "\n",
1261
+ category: "machine",
1262
+ };
1263
+ }
1264
+ /**
1265
+ * Generate `.github/skills/{name}/SKILL.md` files.
1266
+ */
1267
+ function generateSkills(ast) {
1268
+ const files = [];
1269
+ for (const skill of ast.skills) {
1270
+ const lines = [];
1271
+ lines.push("---");
1272
+ lines.push(`name: ${skill.id}`);
1273
+ lines.push(`description: ${skill.description}`);
1274
+ lines.push("---");
1275
+ lines.push("");
1276
+ if (skill.prompt) {
1277
+ lines.push(skill.prompt);
1278
+ }
1279
+ else {
1280
+ const bodyParts = [];
1281
+ if (skill.scripts && skill.scripts.length > 0) {
1282
+ bodyParts.push(`Scripts: ${skill.scripts.join(", ")}`);
1283
+ }
1284
+ if (skill.domains && skill.domains.length > 0) {
1285
+ bodyParts.push(`Domains: ${skill.domains.join(", ")}`);
1286
+ }
1287
+ if (skill.references && skill.references.length > 0) {
1288
+ bodyParts.push(`References: ${skill.references.join(", ")}`);
1289
+ }
1290
+ lines.push(bodyParts.length > 0 ? bodyParts.join("\n") : skill.description);
1291
+ }
1292
+ lines.push("");
1293
+ files.push({
1294
+ path: `.github/skills/${skill.id}/SKILL.md`,
1295
+ content: lines.join("\n"),
1296
+ category: "agent",
1297
+ });
1298
+ }
1299
+ return files;
1300
+ }
1301
+ /** Map hook event names to Copilot CLI hook events. */
1302
+ function mapCopilotHookEvent(event) {
1303
+ switch (event) {
1304
+ case "PreToolUse":
1305
+ return "preToolUse";
1306
+ case "PostToolUse":
1307
+ case "ToolUse":
1308
+ return "postToolUse";
1309
+ default:
1310
+ return "preToolUse";
1311
+ }
1312
+ }
1313
+ /**
1314
+ * Generate `.github/hooks/{name}.json` files for preToolUse/postToolUse hooks.
1315
+ */
1316
+ function generateHookFiles(ast) {
1317
+ const files = [];
1318
+ // Collect all hooks from topology-level and per-agent
1319
+ const allHooks = [...ast.hooks];
1320
+ for (const node of ast.nodes) {
1321
+ if (node.type === "agent") {
1322
+ const agent = node;
1323
+ if (agent.hooks)
1324
+ allHooks.push(...agent.hooks);
1325
+ }
1326
+ }
1327
+ // Only generate hook files for tool-use events
1328
+ const toolHooks = allHooks.filter((h) => h.on === "PreToolUse" || h.on === "PostToolUse" || h.on === "ToolUse");
1329
+ for (const hook of toolHooks) {
1330
+ if (!hook.run)
1331
+ continue;
1332
+ const hookObj = {
1333
+ event: mapCopilotHookEvent(hook.on),
1334
+ };
1335
+ if (hook.matcher)
1336
+ hookObj.matcher = hook.matcher;
1337
+ // Inline commands vs file paths
1338
+ const isInline = /^(exit|true|false|echo)\b/.test(hook.run);
1339
+ hookObj.command = isInline ? hook.run : `./${hook.run}`;
1340
+ if (hook.timeout != null)
1341
+ hookObj.timeout = hook.timeout;
1342
+ files.push({
1343
+ path: `.github/hooks/${hook.name}.json`,
1344
+ content: JSON.stringify(hookObj, null, 2) + "\n",
1345
+ category: "machine",
1346
+ });
1347
+ }
1348
+ // Gate hooks — compile gates as postToolUse hooks
1349
+ const gates = ast.nodes.filter((n) => n.type === "gate");
1350
+ for (const gate of gates) {
1351
+ if (!gate.run)
1352
+ continue;
1353
+ const hookObj = {
1354
+ event: "postToolUse",
1355
+ command: `./scripts/gate-${gate.id}.sh`,
1356
+ };
1357
+ files.push({
1358
+ path: `.github/hooks/gate-${gate.id}.json`,
1359
+ content: JSON.stringify(hookObj, null, 2) + "\n",
1360
+ category: "machine",
1361
+ });
1362
+ }
1363
+ return files;
1364
+ }
1365
+ /**
1366
+ * Generate `AGENTS.md` at project root — topology overview.
1367
+ */
1368
+ function generateAgentsMd(ast) {
1369
+ const sections = [];
1370
+ sections.push(`# ${toTitle(ast.topology.name)}`);
1371
+ sections.push("");
1372
+ if (ast.topology.description) {
1373
+ sections.push(ast.topology.description);
1374
+ sections.push("");
1375
+ }
1376
+ // Agents
1377
+ const agents = ast.nodes.filter((n) => n.type === "agent");
1378
+ if (agents.length > 0) {
1379
+ sections.push("## Agents");
1380
+ sections.push("");
1381
+ for (const agent of agents) {
1382
+ const desc = agent.description ?? agent.role ?? toTitle(agent.id);
1383
+ const model = agent.model ? ` (${agent.model})` : "";
1384
+ sections.push(`- **${toTitle(agent.id)}**${model}: ${desc}`);
1385
+ }
1386
+ sections.push("");
1387
+ }
1388
+ // Flow
1389
+ if (ast.edges.length > 0) {
1390
+ sections.push("## Flow");
1391
+ sections.push("");
1392
+ for (const edge of ast.edges) {
1393
+ let line = `${edge.from} → ${edge.to}`;
1394
+ if (edge.condition)
1395
+ line += ` [when ${edge.condition}]`;
1396
+ if (edge.maxIterations)
1397
+ line += ` [max ${edge.maxIterations}]`;
1398
+ if (edge.isError)
1399
+ line = `${edge.from} ⚠→ ${edge.to}`;
1400
+ sections.push(`- ${line}`);
1401
+ }
1402
+ sections.push("");
1403
+ }
1404
+ // Gates
1405
+ const gates = ast.nodes.filter((n) => n.type === "gate");
1406
+ if (gates.length > 0) {
1407
+ sections.push("## Gates");
1408
+ sections.push("");
1409
+ for (const gate of gates) {
1410
+ const checks = gate.checks?.join(", ") ?? "";
1411
+ sections.push(`- **${toTitle(gate.id)}**: ${checks} (on fail: ${gate.onFail ?? "halt"})`);
1412
+ }
1413
+ sections.push("");
1414
+ }
1415
+ // Human checkpoints
1416
+ const humans = ast.nodes.filter((n) => n.type === "human");
1417
+ if (humans.length > 0) {
1418
+ sections.push("## Human Checkpoints");
1419
+ sections.push("");
1420
+ for (const human of humans) {
1421
+ sections.push(`- **${toTitle(human.id)}**: ${human.description ?? "Human approval required"}`);
1422
+ }
1423
+ sections.push("");
1424
+ }
1425
+ return {
1426
+ path: "AGENTS.md",
1427
+ content: sections.join("\n") + "\n",
1428
+ category: "agent",
1429
+ };
1430
+ }
1431
+ // ---------------------------------------------------------------------------
1432
+ // Binding export
1433
+ // ---------------------------------------------------------------------------
1434
+ /** GitHub Copilot CLI binding. */
1435
+ export const copilotCliBinding = {
1436
+ name: "copilot-cli",
1437
+ description: "GitHub Copilot CLI — generates .github/copilot-instructions.md, .github/instructions/*.instructions.md, .github/agents/*.agent.md, settings.json, skills, hooks, and AGENTS.md.",
1438
+ scaffold(ast) {
1439
+ const files = [];
1440
+ // 1. Project-level instructions file
1441
+ const instructions = generateInstructions(ast);
1442
+ // Append supplementary sections to instructions
1443
+ const supplementary = [];
1444
+ const gateInstructions = generateGateInstructions(ast);
1445
+ if (gateInstructions)
1446
+ supplementary.push(gateInstructions);
1447
+ const hookInstructions = generateHookInstructions(ast);
1448
+ if (hookInstructions)
1449
+ supplementary.push(hookInstructions);
1450
+ const memoryInstructions = generateMemoryInstructions(ast);
1451
+ if (memoryInstructions)
1452
+ supplementary.push(memoryInstructions);
1453
+ const meteringInstructions = generateMeteringInstructions(ast);
1454
+ if (meteringInstructions)
1455
+ supplementary.push(meteringInstructions);
1456
+ const defaultsInstr = generateDefaultsInstructions(ast);
1457
+ if (defaultsInstr)
1458
+ supplementary.push(defaultsInstr);
1459
+ const observabilityInstr = generateObservabilityInstructions(ast);
1460
+ if (observabilityInstr)
1461
+ supplementary.push(observabilityInstr);
1462
+ const checkpointInstr = generateCheckpointInstructions(ast);
1463
+ if (checkpointInstr)
1464
+ supplementary.push(checkpointInstr);
1465
+ const artifactsInstr = generateArtifactsInstructions(ast);
1466
+ if (artifactsInstr)
1467
+ supplementary.push(artifactsInstr);
1468
+ const depthInstr = generateDepthInstructions(ast);
1469
+ if (depthInstr)
1470
+ supplementary.push(depthInstr);
1471
+ const paramsInstr = generateParamsInstructions(ast);
1472
+ if (paramsInstr)
1473
+ supplementary.push(paramsInstr);
1474
+ const ifaceEndpointsInstr = generateInterfaceEndpointsInstructions(ast);
1475
+ if (ifaceEndpointsInstr)
1476
+ supplementary.push(ifaceEndpointsInstr);
1477
+ const importsInstr = generateImportsInstructions(ast);
1478
+ if (importsInstr)
1479
+ supplementary.push(importsInstr);
1480
+ const includesInstr = generateIncludesInstructions(ast);
1481
+ if (includesInstr)
1482
+ supplementary.push(includesInstr);
1483
+ const envInstr = generateEnvInstructions(ast);
1484
+ if (envInstr)
1485
+ supplementary.push(envInstr);
1486
+ if (supplementary.length > 0) {
1487
+ instructions.content += supplementary.join("\n");
1488
+ }
1489
+ files.push(instructions);
1490
+ // 2. Per-agent .instructions.md files (scoped behavioral rules)
1491
+ files.push(...generateAgents(ast));
1492
+ // 3. Per-agent .agent.md files (agent definitions with tools)
1493
+ files.push(...generateAgentMdFiles(ast));
1494
+ // 4. Human node .instructions.md files (informational only)
1495
+ files.push(...generateHumanAgents(ast));
1496
+ // 5. Group node .instructions.md files (informational only)
1497
+ files.push(...generateGroupAgents(ast));
1498
+ // 6. Settings file (MCP, permissions)
1499
+ files.push(generateSettings(ast));
1500
+ // 7. Skills (.github/skills/{name}/SKILL.md)
1501
+ files.push(...generateSkills(ast));
1502
+ // 8. Hook files (.github/hooks/{name}.json)
1503
+ files.push(...generateHookFiles(ast));
1504
+ // 9. AGENTS.md at project root
1505
+ files.push(generateAgentsMd(ast));
1506
+ // 10. Setup steps workflow (server-side Copilot Coding Agent only)
1507
+ const setupSteps = generateSetupSteps(ast);
1508
+ if (setupSteps)
1509
+ files.push(setupSteps);
1510
+ // 11. Gate scripts
1511
+ files.push(...generateGateScripts(ast));
1512
+ return deduplicateFiles(files);
1513
+ },
1514
+ };
1515
+ //# sourceMappingURL=copilot-cli.js.map