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