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