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.
- package/README.md +17 -19
- package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
- package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/action-guard.test.js +299 -0
- package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
- package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
- package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/conformance.test.js +109 -0
- package/dist/__tests__/e2e/conformance.test.js.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.js +142 -0
- package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.js +316 -0
- package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.js +198 -0
- package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
- package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
- package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/inventory.test.js +187 -0
- package/dist/__tests__/e2e/inventory.test.js.map +1 -0
- package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
- package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/matcher.test.js +145 -0
- package/dist/__tests__/e2e/matcher.test.js.map +1 -0
- package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/step-check.test.js +158 -0
- package/dist/__tests__/e2e/step-check.test.js.map +1 -0
- package/dist/__tests__/e2e/strength.test.d.ts +17 -0
- package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/strength.test.js +130 -0
- package/dist/__tests__/e2e/strength.test.js.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.js +57 -0
- package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
- package/dist/__tests__/open-findings.test.js +1 -0
- package/dist/__tests__/open-findings.test.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +6 -686
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
- package/dist/bindings/__tests__/hook-command.test.js +2 -2
- package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
- package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
- package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts +45 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +181 -15
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/index.d.ts +5 -5
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +9 -16
- package/dist/bindings/index.js.map +1 -1
- package/dist/bindings/types.d.ts +6 -1
- package/dist/bindings/types.d.ts.map +1 -1
- package/dist/cli/__tests__/cli.test.js +25 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -1
- package/dist/cli/index.js +222 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/conformance/claude-code.d.ts +99 -0
- package/dist/conformance/claude-code.d.ts.map +1 -0
- package/dist/conformance/claude-code.js +242 -0
- package/dist/conformance/claude-code.js.map +1 -0
- package/dist/conformance/extract.d.ts +70 -0
- package/dist/conformance/extract.d.ts.map +1 -0
- package/dist/conformance/extract.js +185 -0
- package/dist/conformance/extract.js.map +1 -0
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +57 -32
- package/dist/docs/content.js.map +1 -1
- package/dist/guard/check.d.ts +53 -0
- package/dist/guard/check.d.ts.map +1 -0
- package/dist/guard/check.js +366 -0
- package/dist/guard/check.js.map +1 -0
- package/dist/guard/target.d.ts +45 -0
- package/dist/guard/target.d.ts.map +1 -0
- package/dist/guard/target.js +168 -0
- package/dist/guard/target.js.map +1 -0
- package/dist/import/__tests__/import.test.js +0 -554
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/index.d.ts +0 -1
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -7
- package/dist/import/index.js.map +1 -1
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +41 -0
- package/dist/import/serializer.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/inventory/index.d.ts +65 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/index.js +272 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/parser/ast.d.ts +46 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/index.d.ts +6 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +66 -4
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/known-fields.d.ts +3 -0
- package/dist/parser/known-fields.d.ts.map +1 -1
- package/dist/parser/known-fields.js +5 -3
- package/dist/parser/known-fields.js.map +1 -1
- package/dist/parser/ledger.d.ts +36 -0
- package/dist/parser/ledger.d.ts.map +1 -0
- package/dist/parser/ledger.js +72 -0
- package/dist/parser/ledger.js.map +1 -0
- package/dist/parser/run.d.ts +6 -0
- package/dist/parser/run.d.ts.map +1 -1
- package/dist/parser/run.js +10 -0
- package/dist/parser/run.js.map +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +255 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +0 -8
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/merge.d.ts.map +1 -1
- package/dist/scaffold/merge.js +1 -3
- package/dist/scaffold/merge.js.map +1 -1
- package/dist/step/check.d.ts +56 -0
- package/dist/step/check.d.ts.map +1 -0
- package/dist/step/check.js +125 -0
- package/dist/step/check.js.map +1 -0
- package/dist/sync/index.d.ts +0 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +0 -109
- package/dist/sync/index.js.map +1 -1
- package/package.json +3 -1
- package/spec/claude-code/README.md +19 -0
- package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
- package/spec/claude-code/hooks-contract.json +142 -0
- package/spec/grammar.md +45 -7
- package/spec/testing.md +15 -0
- package/spec/validation.md +11 -2
- package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
- package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
- package/dist/__tests__/live/zz-debug.test.js +0 -4
- package/dist/__tests__/live/zz-debug.test.js.map +0 -1
- package/dist/bindings/copilot-cli.d.ts +0 -20
- package/dist/bindings/copilot-cli.d.ts.map +0 -1
- package/dist/bindings/copilot-cli.js +0 -1516
- package/dist/bindings/copilot-cli.js.map +0 -1
- package/dist/bindings/gemini-cli.d.ts +0 -18
- package/dist/bindings/gemini-cli.d.ts.map +0 -1
- package/dist/bindings/gemini-cli.js +0 -1318
- package/dist/bindings/gemini-cli.js.map +0 -1
- package/dist/bindings/kiro.d.ts +0 -17
- package/dist/bindings/kiro.d.ts.map +0 -1
- package/dist/bindings/kiro.js +0 -1615
- package/dist/bindings/kiro.js.map +0 -1
- package/dist/bindings/openclaw.d.ts +0 -18
- package/dist/bindings/openclaw.d.ts.map +0 -1
- package/dist/bindings/openclaw.js +0 -1947
- package/dist/bindings/openclaw.js.map +0 -1
- package/dist/import/openclaw.d.ts +0 -70
- package/dist/import/openclaw.d.ts.map +0 -1
- package/dist/import/openclaw.js +0 -1060
- 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
|