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,1327 @@
1
+ /**
2
+ * Google Gemini CLI binding.
3
+ *
4
+ * Generates the `.gemini/` directory structure and supporting files from a
5
+ * parsed {@link TopologyAST}.
6
+ *
7
+ * Gemini CLI structure:
8
+ * .gemini/CONTEXT.md — Main instructions (never root GEMINI.md)
9
+ * .gemini/settings.json — Model config, permissions, MCP, hooks, sandbox
10
+ * .gemini/instructions.md — Detailed project instructions
11
+ * .gemini/commands/<name>.toml — Custom command files from triggers
12
+ *
13
+ * @module
14
+ */
15
+ import { deduplicateFiles } from "./types.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+ /** Convert a kebab-case id to Title Case. */
20
+ function toTitle(id) {
21
+ return id
22
+ .split(/[-_]/)
23
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
24
+ .join(" ");
25
+ }
26
+ /** Generate a shell script stub with a header comment. */
27
+ function shellStub(description) {
28
+ return [
29
+ "#!/usr/bin/env bash",
30
+ `# ${description}`,
31
+ "# Auto-generated by agentopology scaffold — edit as needed.",
32
+ 'set -euo pipefail',
33
+ "",
34
+ 'echo "TODO: implement this script"',
35
+ "",
36
+ ].join("\n");
37
+ }
38
+ /** Generate an empty directory marker (a .gitkeep file). */
39
+ function gitkeep(dirPath) {
40
+ return { path: `${dirPath}/.gitkeep`, content: "", category: "machine" };
41
+ }
42
+ /**
43
+ * Map topology permission strings to Gemini CLI defaultApprovalMode.
44
+ *
45
+ * Gemini CLI approval modes:
46
+ * "default" — asks for approval (supervised)
47
+ * "auto_edit" — auto-approve edits (autonomous)
48
+ * "plan" — plan mode (read-only)
49
+ */
50
+ function mapApprovalMode(perm) {
51
+ switch (perm) {
52
+ case "supervised":
53
+ case "interactive":
54
+ return "default";
55
+ case "autonomous":
56
+ case "unrestricted":
57
+ return "auto_edit";
58
+ case "plan":
59
+ return "plan";
60
+ default:
61
+ return "default";
62
+ }
63
+ }
64
+ /** Format a SchemaType to a human-readable string. */
65
+ function formatSchemaType(t) {
66
+ switch (t.kind) {
67
+ case "primitive":
68
+ return t.value;
69
+ case "array":
70
+ return `${formatSchemaType(t.itemType)}[]`;
71
+ case "enum":
72
+ return t.values.join(" | ");
73
+ case "ref":
74
+ return t.name;
75
+ }
76
+ }
77
+ /** Format a SchemaFieldDef to a readable line. */
78
+ function formatSchemaField(f) {
79
+ const opt = f.optional ? "?" : "";
80
+ return `- ${f.name}${opt}: ${formatSchemaType(f.type)}`;
81
+ }
82
+ /**
83
+ * Map topology hook event names to Gemini CLI hook event names.
84
+ */
85
+ function mapHookEvent(on) {
86
+ switch (on) {
87
+ case "PreToolUse":
88
+ return "BeforeTool";
89
+ case "PostToolUse":
90
+ case "ToolUse":
91
+ return "AfterTool";
92
+ case "AgentStart":
93
+ return "BeforeAgent";
94
+ case "AgentStop":
95
+ return "AfterAgent";
96
+ case "Error":
97
+ return "Notification";
98
+ default:
99
+ return on;
100
+ }
101
+ }
102
+ /**
103
+ * Determine if a hook run command is an inline command (not a file path).
104
+ * Inline commands are things like "exit 1", not file paths like "scripts/foo.sh".
105
+ */
106
+ function isInlineCommand(run) {
107
+ // If it doesn't contain a path separator and doesn't end in a script extension,
108
+ // treat it as an inline command.
109
+ if (run.includes("/") || run.includes("\\"))
110
+ return false;
111
+ if (/\.\w+$/.test(run))
112
+ return false;
113
+ return true;
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Section generators
117
+ // ---------------------------------------------------------------------------
118
+ /**
119
+ * Generate the main GEMINI.md file.
120
+ *
121
+ * This is the primary instructions file that Gemini CLI reads. It contains
122
+ * the topology description, agent roles, flow, and orchestration instructions.
123
+ */
124
+ function generateGeminiMd(ast) {
125
+ const sections = [];
126
+ // Header
127
+ sections.push(`# ${toTitle(ast.topology.name)}`);
128
+ sections.push("");
129
+ if (ast.topology.description) {
130
+ sections.push(ast.topology.description);
131
+ sections.push("");
132
+ }
133
+ sections.push(`Version: ${ast.topology.version}`);
134
+ if (ast.topology.patterns.length > 0) {
135
+ sections.push(`Patterns: ${ast.topology.patterns.join(", ")}`);
136
+ }
137
+ sections.push("");
138
+ // Orchestrator section
139
+ const orch = ast.nodes.find((n) => n.type === "orchestrator");
140
+ if (orch) {
141
+ sections.push("## Orchestrator");
142
+ sections.push("");
143
+ sections.push(`Model: ${orch.model}`);
144
+ if (orch.handles?.length > 0) {
145
+ sections.push(`Handles: ${orch.handles.join(", ")}`);
146
+ }
147
+ if (orch.generates) {
148
+ sections.push(`Generates: ${orch.generates}`);
149
+ }
150
+ sections.push("");
151
+ }
152
+ // Agent sections
153
+ const agents = ast.nodes.filter((n) => n.type === "agent");
154
+ if (agents.length > 0) {
155
+ sections.push("## Agents");
156
+ sections.push("");
157
+ for (const agent of agents) {
158
+ sections.push(`### ${toTitle(agent.id)}`);
159
+ sections.push("");
160
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
161
+ if (agent.description) {
162
+ sections.push(agent.description);
163
+ sections.push("");
164
+ }
165
+ else if (roleText) {
166
+ sections.push(roleText);
167
+ sections.push("");
168
+ }
169
+ if (agent.prompt) {
170
+ sections.push(agent.prompt);
171
+ sections.push("");
172
+ }
173
+ if (agent.model)
174
+ sections.push(`- Model: ${agent.model}`);
175
+ if (agent.phase != null)
176
+ sections.push(`- Phase: ${agent.phase}`);
177
+ if (agent.tools && agent.tools.length > 0) {
178
+ sections.push(`- Tools: ${agent.tools.join(", ")}`);
179
+ }
180
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
181
+ sections.push(`- Disallowed tools: ${agent.disallowedTools.join(", ")}`);
182
+ }
183
+ if (agent.skills && agent.skills.length > 0) {
184
+ sections.push(`- Skills: ${agent.skills.join(", ")}`);
185
+ }
186
+ if (agent.reads && agent.reads.length > 0) {
187
+ sections.push(`- Reads: ${agent.reads.join(", ")}`);
188
+ }
189
+ if (agent.writes && agent.writes.length > 0) {
190
+ sections.push(`- Writes: ${agent.writes.join(", ")}`);
191
+ }
192
+ if (agent.behavior)
193
+ sections.push(`- Behavior: ${agent.behavior}`);
194
+ if (agent.skip)
195
+ sections.push(`- Skip when: ${agent.skip}`);
196
+ // Outputs
197
+ if (agent.outputs) {
198
+ sections.push("- Outputs:");
199
+ for (const [field, values] of Object.entries(agent.outputs)) {
200
+ sections.push(` - ${field}: ${values.join(" | ")}`);
201
+ }
202
+ }
203
+ // Timeout
204
+ if (agent.timeout) {
205
+ sections.push(`- Maximum execution time: ${agent.timeout}`);
206
+ }
207
+ // On-fail
208
+ if (agent.onFail) {
209
+ sections.push(`- On failure: ${agent.onFail}`);
210
+ }
211
+ // Retry
212
+ if (agent.retry != null) {
213
+ if (typeof agent.retry === "number") {
214
+ sections.push(`- Retry: up to ${agent.retry} times on failure`);
215
+ }
216
+ else {
217
+ const r = agent.retry;
218
+ const parts = [`max ${r.max} attempts`];
219
+ if (r.backoff)
220
+ parts.push(`${r.backoff} backoff`);
221
+ if (r.interval)
222
+ parts.push(`interval ${r.interval}`);
223
+ if (r.maxInterval)
224
+ parts.push(`max interval ${r.maxInterval}`);
225
+ if (r.jitter)
226
+ parts.push(`with jitter`);
227
+ if (r.nonRetryable && r.nonRetryable.length > 0) {
228
+ parts.push(`non-retryable: ${r.nonRetryable.join(", ")}`);
229
+ }
230
+ sections.push(`- Retry strategy: ${parts.join(", ")}`);
231
+ }
232
+ }
233
+ // Output format
234
+ if (agent.outputFormat) {
235
+ sections.push(`- Output format: ${agent.outputFormat}`);
236
+ }
237
+ // Log level
238
+ if (agent.logLevel) {
239
+ sections.push(`- Log verbosity: ${agent.logLevel}`);
240
+ }
241
+ // Model params
242
+ if (agent.temperature != null) {
243
+ sections.push(`- Temperature: ${agent.temperature}`);
244
+ }
245
+ if (agent.maxTokens != null) {
246
+ sections.push(`- Max tokens: ${agent.maxTokens}`);
247
+ }
248
+ if (agent.topP != null) {
249
+ sections.push(`- Top-p: ${agent.topP}`);
250
+ }
251
+ if (agent.topK != null) {
252
+ sections.push(`- Top-k: ${agent.topK}`);
253
+ }
254
+ if (agent.seed != null) {
255
+ sections.push(`- Seed: ${agent.seed}`);
256
+ }
257
+ if (agent.thinking) {
258
+ sections.push(`- Thinking: ${agent.thinking}`);
259
+ }
260
+ if (agent.thinkingBudget != null) {
261
+ sections.push(`- Thinking budget: ${agent.thinkingBudget} tokens`);
262
+ }
263
+ if (agent.maxTurns != null) {
264
+ sections.push(`- Max turns: ${agent.maxTurns}`);
265
+ }
266
+ // Join semantics
267
+ if (agent.join) {
268
+ sections.push(`- Wait for: ${agent.join} (upstream join strategy)`);
269
+ }
270
+ // Circuit breaker
271
+ if (agent.circuitBreaker) {
272
+ const cb = agent.circuitBreaker;
273
+ sections.push(`- Circuit breaker: threshold=${cb.threshold}, window=${cb.window}, cooldown=${cb.cooldown}`);
274
+ }
275
+ // Compensates
276
+ if (agent.compensates) {
277
+ sections.push(`- Compensates: ${agent.compensates} (can undo its side effects)`);
278
+ }
279
+ // Input/Output schemas
280
+ if (agent.inputSchema && agent.inputSchema.length > 0) {
281
+ sections.push("- Input schema:");
282
+ for (const field of agent.inputSchema) {
283
+ sections.push(` ${formatSchemaField(field)}`);
284
+ }
285
+ }
286
+ if (agent.outputSchema && agent.outputSchema.length > 0) {
287
+ sections.push("- Output schema:");
288
+ for (const field of agent.outputSchema) {
289
+ sections.push(` ${formatSchemaField(field)}`);
290
+ }
291
+ }
292
+ // Rate limit
293
+ if (agent.rateLimit) {
294
+ sections.push(`- Rate limit: ${agent.rateLimit}`);
295
+ }
296
+ // Artifacts: produces/consumes
297
+ if (agent.produces && agent.produces.length > 0) {
298
+ sections.push(`- Produces: ${agent.produces.join(", ")}`);
299
+ }
300
+ if (agent.consumes && agent.consumes.length > 0) {
301
+ sections.push(`- Consumes: ${agent.consumes.join(", ")}`);
302
+ }
303
+ // Variants
304
+ if (agent.variants && agent.variants.length > 0) {
305
+ sections.push("- Variants:");
306
+ for (const v of agent.variants) {
307
+ const parts = [`${v.id} (weight: ${v.weight})`];
308
+ if (v.model)
309
+ parts.push(`model: ${v.model}`);
310
+ if (v.temperature != null)
311
+ parts.push(`temperature: ${v.temperature}`);
312
+ sections.push(` - ${parts.join(", ")}`);
313
+ }
314
+ }
315
+ sections.push("");
316
+ }
317
+ }
318
+ // Human nodes
319
+ const humans = ast.nodes.filter((n) => n.type === "human");
320
+ if (humans.length > 0) {
321
+ sections.push("## Human Checkpoints");
322
+ sections.push("");
323
+ for (const human of humans) {
324
+ sections.push(`### ${toTitle(human.id)}`);
325
+ sections.push("");
326
+ if (human.description) {
327
+ sections.push(human.description);
328
+ sections.push("");
329
+ }
330
+ sections.push("**This is a human pause point.** Execution stops here until a human provides input or approval.");
331
+ if (human.timeout)
332
+ sections.push(`- Timeout: ${human.timeout}`);
333
+ if (human.onTimeout)
334
+ sections.push(`- On timeout: ${human.onTimeout}`);
335
+ sections.push("");
336
+ }
337
+ }
338
+ // Group nodes
339
+ const groups = ast.nodes.filter((n) => n.type === "group");
340
+ if (groups.length > 0) {
341
+ sections.push("## Group Chats");
342
+ sections.push("");
343
+ for (const group of groups) {
344
+ sections.push(`### ${toTitle(group.id)}`);
345
+ sections.push("");
346
+ if (group.description) {
347
+ sections.push(group.description);
348
+ sections.push("");
349
+ }
350
+ sections.push(`- Members: ${group.members.join(", ")}`);
351
+ if (group.speakerSelection)
352
+ sections.push(`- Speaker selection: ${group.speakerSelection}`);
353
+ if (group.maxRounds != null)
354
+ sections.push(`- Max rounds: ${group.maxRounds}`);
355
+ if (group.termination)
356
+ sections.push(`- Termination: ${group.termination}`);
357
+ if (group.timeout)
358
+ sections.push(`- Timeout: ${group.timeout}`);
359
+ sections.push("");
360
+ }
361
+ }
362
+ // Flow section
363
+ if (ast.edges.length > 0) {
364
+ sections.push("## Flow");
365
+ sections.push("");
366
+ for (const edge of ast.edges) {
367
+ const arrow = edge.isError ? `-x->` : `->`;
368
+ let line = `${edge.from} ${arrow} ${edge.to}`;
369
+ if (edge.errorType)
370
+ line += ` [error: ${edge.errorType}]`;
371
+ if (edge.condition)
372
+ line += ` [when ${edge.condition}]`;
373
+ if (edge.maxIterations)
374
+ line += ` [max ${edge.maxIterations}]`;
375
+ if (edge.weight != null)
376
+ line += ` [weight ${edge.weight}]`;
377
+ if (edge.race)
378
+ line += ` [race]`;
379
+ if (edge.tolerance != null)
380
+ line += ` [tolerance: ${edge.tolerance}]`;
381
+ if (edge.wait)
382
+ line += ` [wait ${edge.wait}]`;
383
+ if (edge.reflection)
384
+ line += ` [reflection]`;
385
+ sections.push(`- ${line}`);
386
+ }
387
+ sections.push("");
388
+ }
389
+ // Gates section
390
+ const gates = ast.nodes.filter((n) => n.type === "gate");
391
+ if (gates.length > 0) {
392
+ sections.push("## Gates");
393
+ sections.push("");
394
+ for (const gate of gates) {
395
+ sections.push(`### ${toTitle(gate.id)}`);
396
+ if (gate.after)
397
+ sections.push(`- After: ${gate.after}`);
398
+ if (gate.before)
399
+ sections.push(`- Before: ${gate.before}`);
400
+ if (gate.run)
401
+ sections.push(`- Run: ${gate.run}`);
402
+ if (gate.checks && gate.checks.length > 0) {
403
+ sections.push(`- Checks: ${gate.checks.join(", ")}`);
404
+ }
405
+ if (gate.onFail)
406
+ sections.push(`- On fail: ${gate.onFail}`);
407
+ sections.push(`- Enforcement: script-level — run gate script manually between agents`);
408
+ sections.push("");
409
+ }
410
+ }
411
+ // Actions section
412
+ const actions = ast.nodes.filter((n) => n.type === "action");
413
+ if (actions.length > 0) {
414
+ sections.push("## Actions");
415
+ sections.push("");
416
+ for (const action of actions) {
417
+ sections.push(`### ${toTitle(action.id)}`);
418
+ if (action.description)
419
+ sections.push(action.description);
420
+ if (action.kind)
421
+ sections.push(`- Kind: ${action.kind}`);
422
+ if (action.source)
423
+ sections.push(`- Source: ${action.source}`);
424
+ if (action.commands && action.commands.length > 0) {
425
+ sections.push(`- Commands:`);
426
+ for (const cmd of action.commands) {
427
+ sections.push(` - \`${cmd}\``);
428
+ }
429
+ }
430
+ if (action.timeout)
431
+ sections.push(`- Timeout: ${action.timeout}`);
432
+ if (action.onFail)
433
+ sections.push(`- On fail: ${action.onFail}`);
434
+ if (action.join)
435
+ sections.push(`- Join: ${action.join}`);
436
+ sections.push("");
437
+ }
438
+ }
439
+ // Triggers section
440
+ if (ast.triggers.length > 0) {
441
+ sections.push("## Commands");
442
+ sections.push("");
443
+ for (const trigger of ast.triggers) {
444
+ sections.push(`- \`/${trigger.name}\`: ${trigger.pattern}`);
445
+ }
446
+ sections.push("");
447
+ }
448
+ // Include references
449
+ if (ast.context.includes && ast.context.includes.length > 0) {
450
+ sections.push("## References");
451
+ sections.push("");
452
+ for (const inc of ast.context.includes) {
453
+ sections.push(`- ${inc}`);
454
+ }
455
+ sections.push("");
456
+ }
457
+ const DEFAULT_PATH = ".gemini/CONTEXT.md";
458
+ let contextPath = ast.context.file ?? DEFAULT_PATH;
459
+ // Safety: never overwrite the user's root-level GEMINI.md
460
+ if (contextPath === "GEMINI.md" || contextPath === "./GEMINI.md") {
461
+ contextPath = DEFAULT_PATH;
462
+ }
463
+ return {
464
+ path: contextPath,
465
+ content: sections.join("\n") + "\n",
466
+ category: "agent",
467
+ };
468
+ }
469
+ /**
470
+ * Generate .gemini/settings.json.
471
+ *
472
+ * Maps topology to the real Gemini CLI settings structure:
473
+ * general.defaultApprovalMode — permission level
474
+ * model.name — model name
475
+ * modelConfigs.overrides — temperature, maxTokens, topP, topK
476
+ * tools.sandbox — sandbox mode
477
+ * context.fileName — context file path
478
+ * mcpServers — MCP server configuration
479
+ * hooks — hook definitions
480
+ * hooksConfig.enabled — hooks toggle
481
+ * experimental.enableAgents — agent support
482
+ */
483
+ function generateSettings(ast) {
484
+ const settings = {};
485
+ // --- general.defaultApprovalMode ---
486
+ let globalPermission;
487
+ const agents = ast.nodes.filter((n) => n.type === "agent");
488
+ for (const agent of agents) {
489
+ if (agent.permissions) {
490
+ // Use the most restrictive: 'supervised' takes priority
491
+ if (!globalPermission || agent.permissions === "supervised") {
492
+ globalPermission = agent.permissions;
493
+ }
494
+ }
495
+ }
496
+ if (globalPermission) {
497
+ settings.general = { defaultApprovalMode: mapApprovalMode(globalPermission) };
498
+ }
499
+ // --- model ---
500
+ // Only set model.name if it looks like a Gemini model or if a Google
501
+ // provider is configured. Non-Gemini model names (haiku, opus, sonnet,
502
+ // gpt-4o, etc.) would be invalid and could crash the CLI.
503
+ const defaults = ast.defaults;
504
+ const firstAgent = agents[0];
505
+ const hasGoogleProvider = ast.providers?.some((p) => p.name === "google" || p.name === "gemini");
506
+ const modelName = firstAgent?.model;
507
+ const isGeminiModel = modelName && /^gemini/i.test(modelName);
508
+ if (isGeminiModel || (modelName && hasGoogleProvider)) {
509
+ settings.model = { name: modelName };
510
+ }
511
+ // Note: modelConfigs.overrides requires a model match pattern and is
512
+ // complex — omitted to avoid crashing Gemini CLI. Temperature, topP,
513
+ // topK are documented in CONTEXT.md per-agent instead.
514
+ // --- tools.sandbox ---
515
+ const toolsConfig = {};
516
+ const settingsSandbox = ast.settings?.sandbox;
517
+ if (settingsSandbox !== undefined && settingsSandbox !== null) {
518
+ if (typeof settingsSandbox === "boolean") {
519
+ toolsConfig.sandbox = settingsSandbox;
520
+ }
521
+ else if (settingsSandbox === "true" || settingsSandbox === "false") {
522
+ toolsConfig.sandbox = settingsSandbox === "true";
523
+ }
524
+ else {
525
+ // String values like "docker", "network-only" — map to true
526
+ toolsConfig.sandbox = true;
527
+ }
528
+ }
529
+ else if (globalPermission === "unrestricted") {
530
+ toolsConfig.sandbox = false;
531
+ }
532
+ if (Object.keys(toolsConfig).length > 0) {
533
+ settings.tools = toolsConfig;
534
+ }
535
+ // --- context.fileName ---
536
+ const DEFAULT_CONTEXT = ".gemini/CONTEXT.md";
537
+ let contextPath = ast.context.file ?? DEFAULT_CONTEXT;
538
+ if (contextPath === "GEMINI.md" || contextPath === "./GEMINI.md") {
539
+ contextPath = DEFAULT_CONTEXT;
540
+ }
541
+ settings.context = { fileName: contextPath };
542
+ // --- mcpServers ---
543
+ if (Object.keys(ast.mcpServers).length > 0) {
544
+ const mcpServers = {};
545
+ for (const [name, config] of Object.entries(ast.mcpServers)) {
546
+ const entry = {};
547
+ for (const [key, value] of Object.entries(config)) {
548
+ entry[key] = value;
549
+ }
550
+ // Gemini CLI auto-detects stdio when command is present — strip type
551
+ if (entry.command) {
552
+ delete entry.type;
553
+ }
554
+ // Ensure env exists
555
+ if (!entry.env) {
556
+ entry.env = {};
557
+ }
558
+ mcpServers[name] = entry;
559
+ }
560
+ settings.mcpServers = mcpServers;
561
+ }
562
+ // --- hooks ---
563
+ const allHooks = collectAllHooks(ast);
564
+ if (allHooks.length > 0) {
565
+ const hooksMap = {};
566
+ for (const hook of allHooks) {
567
+ const geminiEvent = mapHookEvent(hook.on);
568
+ if (!hooksMap[geminiEvent]) {
569
+ hooksMap[geminiEvent] = [];
570
+ }
571
+ const hookEntry = {};
572
+ const hookDef = {
573
+ type: "command",
574
+ name: hook.name,
575
+ };
576
+ // Inline command vs file path
577
+ if (isInlineCommand(hook.run)) {
578
+ hookDef.command = hook.run;
579
+ }
580
+ else {
581
+ // Scripts are generated into .gemini/scripts/, so rewrite the path
582
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
583
+ hookDef.command = `./.gemini/scripts/${scriptName}`;
584
+ }
585
+ if (hook.timeout != null) {
586
+ hookDef.timeout = hook.timeout;
587
+ }
588
+ if (hook.matcher) {
589
+ hookEntry.matcher = hook.matcher;
590
+ hookEntry.hooks = [hookDef];
591
+ }
592
+ else {
593
+ // No matcher — just the hook definition
594
+ hookEntry.hooks = [hookDef];
595
+ }
596
+ hooksMap[geminiEvent].push(hookEntry);
597
+ }
598
+ settings.hooks = hooksMap;
599
+ settings.hooksConfig = { enabled: true };
600
+ }
601
+ // --- experimental ---
602
+ if (agents.length > 1) {
603
+ settings.experimental = { enableAgents: true };
604
+ }
605
+ // --- Environment variables ---
606
+ const envVars = {};
607
+ for (const [key, val] of Object.entries(ast.env)) {
608
+ if (typeof val === "string") {
609
+ // Plain string — check for vault:// prefix
610
+ envVars[key] = val.startsWith("vault://") ? "<set-via-environment>" : val;
611
+ }
612
+ else {
613
+ // SensitiveValue — use placeholder if sensitive or vault URI
614
+ const sv = val;
615
+ if (sv.sensitive || sv.secretRef || sv.value.startsWith("vault://")) {
616
+ envVars[key] = "<set-via-environment>";
617
+ }
618
+ else {
619
+ envVars[key] = sv.value;
620
+ }
621
+ }
622
+ }
623
+ // If a google provider exists, scaffold the GEMINI_API_KEY env var
624
+ if (ast.providers && ast.providers.length > 0) {
625
+ const googleProvider = ast.providers.find((p) => p.name === "google");
626
+ if (googleProvider?.apiKey) {
627
+ const envVarMatch = googleProvider.apiKey.match(/^\$\{(.+)\}$/);
628
+ if (envVarMatch) {
629
+ envVars[envVarMatch[1]] = googleProvider.apiKey;
630
+ }
631
+ }
632
+ }
633
+ if (Object.keys(envVars).length > 0) {
634
+ settings.env = envVars;
635
+ }
636
+ return {
637
+ path: ".gemini/settings.json",
638
+ content: JSON.stringify(settings, null, 2) + "\n",
639
+ category: "machine",
640
+ };
641
+ }
642
+ /**
643
+ * Collect all hooks from both the global hooks array and per-agent hooks.
644
+ */
645
+ function collectAllHooks(ast) {
646
+ const hooks = [...ast.hooks];
647
+ // Collect per-agent hooks
648
+ const agents = ast.nodes.filter((n) => n.type === "agent");
649
+ for (const agent of agents) {
650
+ if (agent.hooks && agent.hooks.length > 0) {
651
+ hooks.push(...agent.hooks);
652
+ }
653
+ }
654
+ return hooks;
655
+ }
656
+ /**
657
+ * Generate .gemini/instructions.md with detailed project instructions.
658
+ *
659
+ * Contains agent definitions, role descriptions, skill details, memory
660
+ * configuration, and workspace protocol information.
661
+ */
662
+ function generateInstructions(ast) {
663
+ const sections = [];
664
+ const name = ast.topology.name;
665
+ sections.push(`# ${toTitle(name)} — Project Instructions`);
666
+ sections.push("");
667
+ if (ast.topology.description) {
668
+ sections.push(ast.topology.description);
669
+ sections.push("");
670
+ }
671
+ // Roles
672
+ if (Object.keys(ast.roles).length > 0) {
673
+ sections.push("## Roles");
674
+ sections.push("");
675
+ for (const [role, desc] of Object.entries(ast.roles)) {
676
+ sections.push(`### ${toTitle(role)}`);
677
+ sections.push(desc);
678
+ sections.push("");
679
+ }
680
+ }
681
+ // Skills
682
+ if (ast.skills.length > 0) {
683
+ sections.push("## Skills");
684
+ sections.push("");
685
+ for (const skill of ast.skills) {
686
+ sections.push(`### ${toTitle(skill.id)}`);
687
+ if (skill.description)
688
+ sections.push(skill.description);
689
+ if (skill.scripts && skill.scripts.length > 0) {
690
+ sections.push(`Scripts: ${skill.scripts.join(", ")}`);
691
+ }
692
+ if (skill.domains && skill.domains.length > 0) {
693
+ sections.push(`Domains: ${skill.domains.join(", ")}`);
694
+ }
695
+ if (skill.references && skill.references.length > 0) {
696
+ sections.push(`References: ${skill.references.join(", ")}`);
697
+ }
698
+ sections.push("");
699
+ }
700
+ }
701
+ // Memory configuration
702
+ const memory = ast.memory;
703
+ if (Object.keys(memory).length > 0) {
704
+ sections.push("## Memory");
705
+ sections.push("");
706
+ if (memory.domains)
707
+ sections.push("- Domains: configured");
708
+ if (memory.references)
709
+ sections.push("- References: configured");
710
+ if (memory.metrics)
711
+ sections.push("- Metrics: configured");
712
+ if (memory.workspace)
713
+ sections.push("- Workspace: configured");
714
+ sections.push("");
715
+ }
716
+ // Workspace protocol
717
+ const workspace = memory.workspace;
718
+ if (workspace?.protocol) {
719
+ sections.push("## Workspace Protocol");
720
+ sections.push("");
721
+ const structure = workspace.structure;
722
+ if (structure && Array.isArray(structure)) {
723
+ sections.push("### Directory Structure");
724
+ for (const item of structure) {
725
+ sections.push(`- ${item}`);
726
+ }
727
+ sections.push("");
728
+ }
729
+ // Read/write rules from agents
730
+ const agents = ast.nodes.filter((n) => n.type === "agent");
731
+ const rwAgents = agents.filter((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
732
+ if (rwAgents.length > 0) {
733
+ sections.push("### Read/Write Rules");
734
+ for (const agent of rwAgents) {
735
+ sections.push(`#### ${toTitle(agent.id)}`);
736
+ if (agent.reads && agent.reads.length > 0) {
737
+ sections.push(`- Reads: ${agent.reads.join(", ")}`);
738
+ }
739
+ if (agent.writes && agent.writes.length > 0) {
740
+ sections.push(`- Writes: ${agent.writes.join(", ")}`);
741
+ }
742
+ sections.push("");
743
+ }
744
+ }
745
+ }
746
+ // Depth configuration
747
+ if (ast.depth.levels.length > 0) {
748
+ sections.push("## Depth Levels");
749
+ sections.push("");
750
+ if (ast.depth.factors.length > 0) {
751
+ sections.push(`Factors: ${ast.depth.factors.join(", ")}`);
752
+ sections.push("");
753
+ }
754
+ for (const level of ast.depth.levels) {
755
+ let line = `- Level ${level.level}: ${level.label}`;
756
+ if (level.omit.length > 0) {
757
+ line += ` (omit: ${level.omit.join(", ")})`;
758
+ }
759
+ sections.push(line);
760
+ }
761
+ sections.push("");
762
+ }
763
+ // Tool definitions
764
+ if (ast.toolDefs.length > 0) {
765
+ sections.push("## Tools");
766
+ sections.push("");
767
+ for (const tool of ast.toolDefs) {
768
+ sections.push(`### ${tool.id}`);
769
+ sections.push(tool.description);
770
+ sections.push(`Script: ${tool.script} (${tool.lang ?? "bash"})`);
771
+ if (tool.args && tool.args.length > 0) {
772
+ sections.push(`Args: ${tool.args.join(", ")}`);
773
+ }
774
+ sections.push("");
775
+ }
776
+ }
777
+ // Schedules section
778
+ if (ast.schedules.length > 0) {
779
+ sections.push("## Schedules");
780
+ sections.push("");
781
+ for (const job of ast.schedules) {
782
+ const expr = job.cron ? `cron: ${job.cron}` : job.every ? `every: ${job.every}` : "";
783
+ const target = job.agent ? `agent: ${job.agent}` : job.action ? `action: ${job.action}` : "";
784
+ sections.push(`- **${job.id}**: ${expr}${target ? ` -> ${target}` : ""}${job.enabled === false ? " (disabled)" : ""}`);
785
+ }
786
+ sections.push("");
787
+ }
788
+ // Interfaces section
789
+ if (ast.interfaces.length > 0) {
790
+ sections.push("## Interfaces");
791
+ sections.push("");
792
+ for (const iface of ast.interfaces) {
793
+ const typePart = iface.type ? ` (${iface.type})` : "";
794
+ const configKeys = Object.keys(iface.config);
795
+ const configPart = configKeys.length > 0 ? ` — ${configKeys.map((k) => `${k}: ${iface.config[k]}`).join(", ")}` : "";
796
+ sections.push(`- **${iface.id}**${typePart}${configPart}`);
797
+ }
798
+ sections.push("");
799
+ }
800
+ // Fallback chain from settings
801
+ const fallbackChain = ast.settings?.["fallback-chain"];
802
+ if (fallbackChain) {
803
+ sections.push("## Fallback Chain");
804
+ sections.push("");
805
+ if (Array.isArray(fallbackChain)) {
806
+ sections.push(`Models: ${fallbackChain.join(" -> ")}`);
807
+ }
808
+ else {
809
+ sections.push(`Fallback chain: ${fallbackChain}`);
810
+ }
811
+ sections.push("");
812
+ }
813
+ // Metering
814
+ if (ast.metering) {
815
+ const m = ast.metering;
816
+ sections.push("## Metering");
817
+ sections.push("");
818
+ sections.push(`- Track: ${m.track.join(", ")}`);
819
+ sections.push(`- Per: ${m.per.join(", ")}`);
820
+ sections.push(`- Output: ${m.output} (${m.format})`);
821
+ sections.push(`- Pricing: ${m.pricing}`);
822
+ sections.push("");
823
+ }
824
+ // Defaults
825
+ if (ast.defaults) {
826
+ const d = ast.defaults;
827
+ sections.push("## Defaults");
828
+ sections.push("");
829
+ if (d.temperature != null)
830
+ sections.push(`- Temperature: ${d.temperature}`);
831
+ if (d.maxTokens != null)
832
+ sections.push(`- Max tokens: ${d.maxTokens}`);
833
+ if (d.topP != null)
834
+ sections.push(`- Top-p: ${d.topP}`);
835
+ if (d.topK != null)
836
+ sections.push(`- Top-k: ${d.topK}`);
837
+ if (d.stop)
838
+ sections.push(`- Stop sequences: ${d.stop.join(", ")}`);
839
+ if (d.seed != null)
840
+ sections.push(`- Seed: ${d.seed}`);
841
+ if (d.thinking)
842
+ sections.push(`- Thinking: ${d.thinking}`);
843
+ if (d.thinkingBudget != null)
844
+ sections.push(`- Thinking budget: ${d.thinkingBudget} tokens`);
845
+ if (d.outputFormat)
846
+ sections.push(`- Output format: ${d.outputFormat}`);
847
+ if (d.timeout)
848
+ sections.push(`- Timeout: ${d.timeout}`);
849
+ if (d.logLevel)
850
+ sections.push(`- Log level: ${d.logLevel}`);
851
+ sections.push("");
852
+ }
853
+ // Observability
854
+ if (ast.observability) {
855
+ const o = ast.observability;
856
+ sections.push("## Observability");
857
+ sections.push("");
858
+ sections.push(`- Enabled: ${o.enabled}`);
859
+ sections.push(`- Level: ${o.level}`);
860
+ sections.push(`- Exporter: ${o.exporter}`);
861
+ if (o.endpoint)
862
+ sections.push(`- Endpoint: ${o.endpoint}`);
863
+ if (o.service)
864
+ sections.push(`- Service: ${o.service}`);
865
+ sections.push(`- Sample rate: ${o.sampleRate}`);
866
+ sections.push(`- Capture: prompts=${o.capture.prompts}, completions=${o.capture.completions}, toolArgs=${o.capture.toolArgs}, toolResults=${o.capture.toolResults}`);
867
+ sections.push(`- Spans: agents=${o.spans.agents}, tools=${o.spans.tools}, gates=${o.spans.gates}, memory=${o.spans.memory}`);
868
+ sections.push("");
869
+ }
870
+ // Checkpoint
871
+ if (ast.checkpoint) {
872
+ const c = ast.checkpoint;
873
+ sections.push("## Checkpoint");
874
+ sections.push("");
875
+ sections.push(`- Backend: ${c.backend}`);
876
+ if (c.connection)
877
+ sections.push(`- Connection: ${c.connection}`);
878
+ sections.push(`- Strategy: ${c.strategy}`);
879
+ if (c.ttl)
880
+ sections.push(`- TTL: ${c.ttl}`);
881
+ if (c.replay) {
882
+ sections.push(`- Replay: enabled=${c.replay.enabled}`);
883
+ if (c.replay.maxHistory != null)
884
+ sections.push(` - Max history: ${c.replay.maxHistory}`);
885
+ if (c.replay.branch != null)
886
+ sections.push(` - Branch: ${c.replay.branch}`);
887
+ }
888
+ sections.push("");
889
+ }
890
+ // Artifacts
891
+ if (ast.artifacts.length > 0) {
892
+ sections.push("## Artifacts");
893
+ sections.push("");
894
+ for (const art of ast.artifacts) {
895
+ let line = `- **${art.id}** (${art.type})`;
896
+ if (art.path)
897
+ line += ` — path: ${art.path}`;
898
+ if (art.retention)
899
+ line += `, retention: ${art.retention}`;
900
+ if (art.dependsOn && art.dependsOn.length > 0)
901
+ line += `, depends on: ${art.dependsOn.join(", ")}`;
902
+ sections.push(line);
903
+ }
904
+ sections.push("");
905
+ }
906
+ // Schemas
907
+ if (ast.schemas.length > 0) {
908
+ sections.push("## Schemas");
909
+ sections.push("");
910
+ for (const schema of ast.schemas) {
911
+ sections.push(`### ${toTitle(schema.id)}`);
912
+ for (const field of schema.fields) {
913
+ sections.push(formatSchemaField(field));
914
+ }
915
+ sections.push("");
916
+ }
917
+ }
918
+ // Composition: params
919
+ if (ast.params && ast.params.length > 0) {
920
+ sections.push("## Parameters");
921
+ sections.push("");
922
+ for (const p of ast.params) {
923
+ const req = p.required ? " (required)" : "";
924
+ const def = p.default != null ? ` = ${p.default}` : "";
925
+ sections.push(`- **${p.name}**: ${p.type}${req}${def}`);
926
+ }
927
+ sections.push("");
928
+ }
929
+ // Composition: interface endpoints
930
+ if (ast.interfaceEndpoints) {
931
+ sections.push("## Interface");
932
+ sections.push("");
933
+ sections.push(`- Entry: ${ast.interfaceEndpoints.entry}`);
934
+ sections.push(`- Exit: ${ast.interfaceEndpoints.exit}`);
935
+ sections.push("");
936
+ }
937
+ // Composition: imports
938
+ if (ast.imports && ast.imports.length > 0) {
939
+ sections.push("## Imports");
940
+ sections.push("");
941
+ for (const imp of ast.imports) {
942
+ let line = `- **${imp.alias}** from \`${imp.source}\``;
943
+ if (imp.sha256)
944
+ line += ` (sha256: ${imp.sha256})`;
945
+ if (imp.registry)
946
+ line += ` [registry: ${imp.registryPackage}@${imp.registryVersion}]`;
947
+ sections.push(line);
948
+ if (Object.keys(imp.params).length > 0) {
949
+ for (const [k, v] of Object.entries(imp.params)) {
950
+ sections.push(` - ${k}: ${v}`);
951
+ }
952
+ }
953
+ }
954
+ sections.push("");
955
+ }
956
+ // Composition: includes
957
+ if (ast.includes && ast.includes.length > 0) {
958
+ sections.push("## Includes");
959
+ sections.push("");
960
+ for (const inc of ast.includes) {
961
+ sections.push(`- ${inc.source}`);
962
+ }
963
+ sections.push("");
964
+ }
965
+ return {
966
+ path: ".gemini/instructions.md",
967
+ content: sections.join("\n") + "\n",
968
+ category: "agent",
969
+ };
970
+ }
971
+ /**
972
+ * Generate .gemini/commands/<name>.toml for each trigger.
973
+ *
974
+ * Gemini CLI supports custom commands defined as TOML files
975
+ * in the .gemini/commands/ directory.
976
+ */
977
+ function generateCommandFiles(ast) {
978
+ const files = [];
979
+ // Collect agents sorted by phase for orchestration prompts
980
+ const agents = ast.nodes.filter((n) => n.type === "agent");
981
+ const sortedAgents = [...agents].sort((a, b) => (a.phase ?? 999) - (b.phase ?? 999));
982
+ // Collect human/group nodes for special handling
983
+ const humans = ast.nodes.filter((n) => n.type === "human");
984
+ const groups = ast.nodes.filter((n) => n.type === "group");
985
+ // Build workspace paths from agent reads/writes
986
+ const workspacePaths = new Set();
987
+ for (const agent of agents) {
988
+ for (const p of agent.writes ?? [])
989
+ workspacePaths.add(p);
990
+ for (const p of agent.reads ?? [])
991
+ workspacePaths.add(p);
992
+ }
993
+ for (const trigger of ast.triggers) {
994
+ const promptLines = [];
995
+ // Opening instruction
996
+ const argRef = trigger.argument ? ` {{args}}` : "";
997
+ promptLines.push(`Execute the ${toTitle(ast.topology.name)} topology${argRef}.`);
998
+ promptLines.push("");
999
+ promptLines.push("Follow the phases below in order. Complete each phase fully before moving to the next.");
1000
+ if (workspacePaths.size > 0) {
1001
+ promptLines.push("Write outputs to the workspace directory at each step.");
1002
+ }
1003
+ // Generate a phase section for each agent
1004
+ for (const agent of sortedAgents) {
1005
+ promptLines.push("");
1006
+ const phaseLabel = agent.phase != null ? `Phase ${agent.phase}` : "Step";
1007
+ promptLines.push(`## ${phaseLabel}: ${toTitle(agent.id)}`);
1008
+ // Role / description
1009
+ if (agent.description) {
1010
+ promptLines.push(`Act as the **${toTitle(agent.id)}** agent. ${agent.description}`);
1011
+ }
1012
+ else if (agent.prompt) {
1013
+ promptLines.push(`Act as the **${toTitle(agent.id)}** agent.`);
1014
+ promptLines.push(agent.prompt);
1015
+ }
1016
+ else {
1017
+ promptLines.push(`Act as the **${toTitle(agent.id)}** agent.`);
1018
+ }
1019
+ // Read/write paths
1020
+ if (agent.reads && agent.reads.length > 0) {
1021
+ promptLines.push(`Read from: ${agent.reads.join(", ")}`);
1022
+ }
1023
+ if (agent.writes && agent.writes.length > 0) {
1024
+ promptLines.push(`Write results to: ${agent.writes.join(", ")}`);
1025
+ }
1026
+ // Outputs / routing
1027
+ if (agent.outputs) {
1028
+ promptLines.push("");
1029
+ for (const [field, values] of Object.entries(agent.outputs)) {
1030
+ promptLines.push(`Determine ${field}: ${values.join(" | ")}`);
1031
+ }
1032
+ }
1033
+ // Conditional edges from this agent
1034
+ const outEdges = ast.edges.filter((e) => e.from === agent.id);
1035
+ const conditionalEdges = outEdges.filter((e) => e.condition);
1036
+ if (conditionalEdges.length > 0) {
1037
+ promptLines.push("");
1038
+ promptLines.push("Routing:");
1039
+ for (const edge of conditionalEdges) {
1040
+ const maxNote = edge.maxIterations
1041
+ ? ` (max ${edge.maxIterations} times)`
1042
+ : "";
1043
+ promptLines.push(`- If ${edge.condition} → proceed to ${toTitle(edge.to)}${maxNote}`);
1044
+ }
1045
+ }
1046
+ // Timeout
1047
+ if (agent.timeout) {
1048
+ promptLines.push(`Maximum time: ${agent.timeout}`);
1049
+ }
1050
+ }
1051
+ // Human checkpoints
1052
+ for (const human of humans) {
1053
+ // Find which agents flow into the human node
1054
+ const inEdges = ast.edges.filter((e) => e.to === human.id);
1055
+ if (inEdges.length > 0) {
1056
+ promptLines.push("");
1057
+ promptLines.push(`## Checkpoint: ${toTitle(human.id)}`);
1058
+ if (human.description)
1059
+ promptLines.push(human.description);
1060
+ promptLines.push("**STOP and wait for human approval before proceeding.**");
1061
+ if (human.timeout) {
1062
+ promptLines.push(`Timeout: ${human.timeout}`);
1063
+ }
1064
+ }
1065
+ }
1066
+ // Group discussions
1067
+ for (const group of groups) {
1068
+ promptLines.push("");
1069
+ promptLines.push(`## Discussion: ${toTitle(group.id)}`);
1070
+ if (group.description)
1071
+ promptLines.push(group.description);
1072
+ promptLines.push(`Conduct a multi-perspective discussion considering the viewpoints of: ${group.members.join(", ")}.`);
1073
+ if (group.maxRounds != null) {
1074
+ promptLines.push(`Limit to ${group.maxRounds} rounds of discussion.`);
1075
+ }
1076
+ if (group.termination) {
1077
+ promptLines.push(`End when: ${group.termination}`);
1078
+ }
1079
+ }
1080
+ // Final instruction
1081
+ promptLines.push("");
1082
+ promptLines.push("## Summary");
1083
+ promptLines.push("After completing all phases, provide a summary of what happened at each phase and the final outcome.");
1084
+ // Build TOML
1085
+ const description = trigger.argument
1086
+ ? `${trigger.name} — ${ast.topology.description || toTitle(ast.topology.name)} (arg: ${trigger.argument})`
1087
+ : `${trigger.name} — ${ast.topology.description || toTitle(ast.topology.name)}`;
1088
+ // Replace argument placeholder with {{args}}
1089
+ let promptText = promptLines.join("\n");
1090
+ if (trigger.argument) {
1091
+ promptText = promptText.replace(new RegExp(`<${trigger.argument}>`, "g"), "{{args}}");
1092
+ }
1093
+ const tomlContent = [
1094
+ `description = ${JSON.stringify(description)}`,
1095
+ `prompt = """`,
1096
+ promptText,
1097
+ `"""`,
1098
+ "",
1099
+ ].join("\n");
1100
+ files.push({
1101
+ path: `.gemini/commands/${trigger.name}.toml`,
1102
+ content: tomlContent,
1103
+ category: "machine",
1104
+ });
1105
+ }
1106
+ return files;
1107
+ }
1108
+ /** Generate gate script stubs. */
1109
+ function generateGateScripts(ast) {
1110
+ const files = [];
1111
+ for (const node of ast.nodes) {
1112
+ if (node.type !== "gate")
1113
+ continue;
1114
+ const gate = node;
1115
+ if (gate.run) {
1116
+ const scriptName = gate.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1117
+ files.push({
1118
+ path: `.gemini/scripts/${scriptName}`,
1119
+ content: shellStub(`Gate: ${gate.id} — ${gate.run}`),
1120
+ category: "script",
1121
+ });
1122
+ }
1123
+ }
1124
+ return files;
1125
+ }
1126
+ /**
1127
+ * Generate hook script stubs for hooks that reference script files.
1128
+ * Inline commands (like "exit 1") do NOT get script stubs.
1129
+ */
1130
+ function generateHookScripts(ast) {
1131
+ const files = [];
1132
+ const allHooks = collectAllHooks(ast);
1133
+ for (const hook of allHooks) {
1134
+ if (!hook.run)
1135
+ continue;
1136
+ // Skip inline commands — they go directly into settings.json
1137
+ if (isInlineCommand(hook.run))
1138
+ continue;
1139
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1140
+ files.push({
1141
+ path: `.gemini/scripts/${scriptName}`,
1142
+ content: [
1143
+ "#!/usr/bin/env bash",
1144
+ `# Hook: ${hook.name} — fires on ${hook.on}`,
1145
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1146
+ 'set -euo pipefail',
1147
+ "",
1148
+ `echo "TODO: implement ${hook.name} hook"`,
1149
+ "",
1150
+ ].join("\n"),
1151
+ category: "script",
1152
+ });
1153
+ }
1154
+ return files;
1155
+ }
1156
+ /** Generate tool script stubs. */
1157
+ function generateToolScripts(ast) {
1158
+ const files = [];
1159
+ for (const tool of ast.toolDefs) {
1160
+ const ext = tool.lang === "python" ? "py" : tool.lang === "node" ? "js" : "sh";
1161
+ const shebang = tool.lang === "python"
1162
+ ? "#!/usr/bin/env python3"
1163
+ : tool.lang === "node"
1164
+ ? "#!/usr/bin/env node"
1165
+ : "#!/usr/bin/env bash";
1166
+ const lines = [
1167
+ shebang,
1168
+ `# Tool: ${tool.id} — ${tool.description}`,
1169
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1170
+ "",
1171
+ ];
1172
+ if (tool.lang === "bash" || !tool.lang) {
1173
+ lines.push('set -euo pipefail');
1174
+ lines.push("");
1175
+ }
1176
+ if (tool.args && tool.args.length > 0) {
1177
+ lines.push(`# Args: ${tool.args.join(", ")}`);
1178
+ lines.push("");
1179
+ }
1180
+ if (tool.lang === "python") {
1181
+ lines.push(`print("TODO: implement ${tool.id}")`);
1182
+ }
1183
+ else if (tool.lang === "node") {
1184
+ lines.push(`console.log("TODO: implement ${tool.id}");`);
1185
+ }
1186
+ else {
1187
+ lines.push(`echo "TODO: implement ${tool.id}"`);
1188
+ }
1189
+ lines.push("");
1190
+ files.push({
1191
+ path: `.gemini/scripts/${tool.id}.${ext}`,
1192
+ content: lines.join("\n"),
1193
+ category: "script",
1194
+ });
1195
+ }
1196
+ return files;
1197
+ }
1198
+ /** Generate memory structure directories. */
1199
+ function generateMemory(ast) {
1200
+ const files = [];
1201
+ const memory = ast.memory;
1202
+ if (memory.domains) {
1203
+ files.push(gitkeep(".gemini/context/domains"));
1204
+ }
1205
+ if (memory.references) {
1206
+ files.push(gitkeep(".gemini/context/references"));
1207
+ }
1208
+ if (memory.metrics) {
1209
+ files.push({
1210
+ path: ".gemini/metrics.jsonl",
1211
+ content: "",
1212
+ category: "machine",
1213
+ });
1214
+ }
1215
+ if (memory.workspace) {
1216
+ files.push(gitkeep(".gemini/workspace"));
1217
+ }
1218
+ return files;
1219
+ }
1220
+ /** Generate metering script stub. */
1221
+ function generateMetering(ast) {
1222
+ if (!ast.metering)
1223
+ return null;
1224
+ const m = ast.metering;
1225
+ const lines = [
1226
+ "#!/usr/bin/env bash",
1227
+ "# Metering / cost tracking script",
1228
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1229
+ 'set -euo pipefail',
1230
+ "",
1231
+ `# Track: ${m.track.join(", ")}`,
1232
+ `# Per: ${m.per.join(", ")}`,
1233
+ `# Output: ${m.output} (${m.format})`,
1234
+ `# Pricing: ${m.pricing}`,
1235
+ "",
1236
+ 'echo "TODO: implement metrics collection"',
1237
+ "",
1238
+ ];
1239
+ return {
1240
+ path: "scripts/collect-metrics.sh",
1241
+ content: lines.join("\n"),
1242
+ category: "script",
1243
+ };
1244
+ }
1245
+ /**
1246
+ * Generate `.gemini/skills/<skill-id>/SKILL.md` files.
1247
+ *
1248
+ * Gemini CLI discovers skills from this directory structure. Each SKILL.md has
1249
+ * YAML front-matter (name, description) and a body with instructions.
1250
+ */
1251
+ function generateSkills(ast) {
1252
+ const files = [];
1253
+ for (const skill of ast.skills) {
1254
+ const lines = [];
1255
+ // YAML front-matter
1256
+ lines.push("---");
1257
+ lines.push(`name: ${skill.id}`);
1258
+ lines.push(`description: ${skill.description}`);
1259
+ lines.push("---");
1260
+ lines.push("");
1261
+ // Body text
1262
+ if (skill.prompt) {
1263
+ lines.push(skill.prompt);
1264
+ }
1265
+ else {
1266
+ // Generate a body from scripts, domains, references
1267
+ const bodyParts = [];
1268
+ if (skill.scripts && skill.scripts.length > 0) {
1269
+ bodyParts.push(`Scripts: ${skill.scripts.join(", ")}`);
1270
+ }
1271
+ if (skill.domains && skill.domains.length > 0) {
1272
+ bodyParts.push(`Domains: ${skill.domains.join(", ")}`);
1273
+ }
1274
+ if (skill.references && skill.references.length > 0) {
1275
+ bodyParts.push(`References: ${skill.references.join(", ")}`);
1276
+ }
1277
+ if (bodyParts.length > 0) {
1278
+ lines.push(bodyParts.join("\n"));
1279
+ }
1280
+ else {
1281
+ lines.push(skill.description);
1282
+ }
1283
+ }
1284
+ lines.push("");
1285
+ files.push({
1286
+ path: `.gemini/skills/${skill.id}/SKILL.md`,
1287
+ content: lines.join("\n"),
1288
+ category: "agent",
1289
+ });
1290
+ }
1291
+ return files;
1292
+ }
1293
+ // ---------------------------------------------------------------------------
1294
+ // Binding export
1295
+ // ---------------------------------------------------------------------------
1296
+ /** Google Gemini CLI binding. */
1297
+ export const geminiCliBinding = {
1298
+ name: "gemini-cli",
1299
+ description: "Google Gemini CLI — generates GEMINI.md, .gemini/ directory structure, and scripts.",
1300
+ scaffold(ast) {
1301
+ const files = [];
1302
+ // Main instructions file (.gemini/CONTEXT.md — never root GEMINI.md)
1303
+ files.push(generateGeminiMd(ast));
1304
+ // Settings file (permissions, model, sandbox, MCP, hooks, env)
1305
+ files.push(generateSettings(ast));
1306
+ // Detailed project instructions
1307
+ files.push(generateInstructions(ast));
1308
+ // Command files from triggers (.toml format)
1309
+ files.push(...generateCommandFiles(ast));
1310
+ // Gate scripts
1311
+ files.push(...generateGateScripts(ast));
1312
+ // Hook scripts (only for file-based hooks, not inline commands)
1313
+ files.push(...generateHookScripts(ast));
1314
+ // Tool scripts
1315
+ files.push(...generateToolScripts(ast));
1316
+ // Skill files (.gemini/skills/<id>/SKILL.md)
1317
+ files.push(...generateSkills(ast));
1318
+ // Memory directories
1319
+ files.push(...generateMemory(ast));
1320
+ // Metering
1321
+ const meteringFile = generateMetering(ast);
1322
+ if (meteringFile)
1323
+ files.push(meteringFile);
1324
+ return deduplicateFiles(files);
1325
+ },
1326
+ };
1327
+ //# sourceMappingURL=gemini-cli.js.map