agentopology 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +450 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/analyzer/__tests__/analyzer.test.js +258 -0
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
- package/dist/analyzer/index.d.ts +44 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +308 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
- package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/bindings.test.js +2089 -0
- package/dist/bindings/__tests__/bindings.test.js.map +1 -0
- package/dist/bindings/claude-code.d.ts +12 -0
- package/dist/bindings/claude-code.d.ts.map +1 -0
- package/dist/bindings/claude-code.js +2063 -0
- package/dist/bindings/claude-code.js.map +1 -0
- package/dist/bindings/codex.d.ts +18 -0
- package/dist/bindings/codex.d.ts.map +1 -0
- package/dist/bindings/codex.js +1633 -0
- package/dist/bindings/codex.js.map +1 -0
- package/dist/bindings/copilot-cli.d.ts +20 -0
- package/dist/bindings/copilot-cli.d.ts.map +1 -0
- package/dist/bindings/copilot-cli.js +1515 -0
- package/dist/bindings/copilot-cli.js.map +1 -0
- package/dist/bindings/cursor.d.ts +17 -0
- package/dist/bindings/cursor.d.ts.map +1 -0
- package/dist/bindings/cursor.js +1715 -0
- package/dist/bindings/cursor.js.map +1 -0
- package/dist/bindings/gemini-cli.d.ts +18 -0
- package/dist/bindings/gemini-cli.d.ts.map +1 -0
- package/dist/bindings/gemini-cli.js +1327 -0
- package/dist/bindings/gemini-cli.js.map +1 -0
- package/dist/bindings/index.d.ts +20 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +33 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/kiro.d.ts +17 -0
- package/dist/bindings/kiro.d.ts.map +1 -0
- package/dist/bindings/kiro.js +1624 -0
- package/dist/bindings/kiro.js.map +1 -0
- package/dist/bindings/openclaw.d.ts +18 -0
- package/dist/bindings/openclaw.d.ts.map +1 -0
- package/dist/bindings/openclaw.js +1578 -0
- package/dist/bindings/openclaw.js.map +1 -0
- package/dist/bindings/types.d.ts +33 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +23 -0
- package/dist/bindings/types.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +679 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/docs/__tests__/docs.test.d.ts +2 -0
- package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
- package/dist/docs/__tests__/docs.test.js +438 -0
- package/dist/docs/__tests__/docs.test.js.map +1 -0
- package/dist/docs/content.d.ts +16 -0
- package/dist/docs/content.d.ts.map +1 -0
- package/dist/docs/content.js +3273 -0
- package/dist/docs/content.js.map +1 -0
- package/dist/docs/index.d.ts +17 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +117 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
- package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
- package/dist/exporters/__tests__/exporters.test.js +731 -0
- package/dist/exporters/__tests__/exporters.test.js.map +1 -0
- package/dist/exporters/index.d.ts +13 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +18 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/json.d.ts +8 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +25 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/markdown.d.ts +9 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +686 -0
- package/dist/exporters/markdown.js.map +1 -0
- package/dist/exporters/mermaid.d.ts +11 -0
- package/dist/exporters/mermaid.d.ts.map +1 -0
- package/dist/exporters/mermaid.js +161 -0
- package/dist/exporters/mermaid.js.map +1 -0
- package/dist/exporters/types.d.ts +22 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +10 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/import/__tests__/import.test.d.ts +2 -0
- package/dist/import/__tests__/import.test.d.ts.map +1 -0
- package/dist/import/__tests__/import.test.js +781 -0
- package/dist/import/__tests__/import.test.js.map +1 -0
- package/dist/import/claude-code.d.ts +38 -0
- package/dist/import/claude-code.d.ts.map +1 -0
- package/dist/import/claude-code.js +897 -0
- package/dist/import/claude-code.js.map +1 -0
- package/dist/import/index.d.ts +18 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +28 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/serializer.d.ts +11 -0
- package/dist/import/serializer.d.ts.map +1 -0
- package/dist/import/serializer.js +960 -0
- package/dist/import/serializer.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/__tests__/parser.test.d.ts +2 -0
- package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
- package/dist/parser/__tests__/parser.test.js +6514 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -0
- package/dist/parser/ast.d.ts +708 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +12 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/index.d.ts +253 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +1985 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lexer.d.ts +100 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +304 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/validator.d.ts +42 -0
- package/dist/parser/validator.d.ts.map +1 -0
- package/dist/parser/validator.js +2568 -0
- package/dist/parser/validator.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +295 -0
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
- package/dist/scaffold/incremental.d.ts +43 -0
- package/dist/scaffold/incremental.d.ts.map +1 -0
- package/dist/scaffold/incremental.js +185 -0
- package/dist/scaffold/incremental.js.map +1 -0
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +13 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/manifest.d.ts +30 -0
- package/dist/scaffold/manifest.d.ts.map +1 -0
- package/dist/scaffold/manifest.js +71 -0
- package/dist/scaffold/manifest.js.map +1 -0
- package/dist/scaffold/merge.d.ts +52 -0
- package/dist/scaffold/merge.d.ts.map +1 -0
- package/dist/scaffold/merge.js +134 -0
- package/dist/scaffold/merge.js.map +1 -0
- package/dist/scaffold/types.d.ts +51 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +10 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/sync/index.d.ts +34 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +392 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/visualizer/index.d.ts +17 -0
- package/dist/visualizer/index.d.ts.map +1 -0
- package/dist/visualizer/index.js +1976 -0
- package/dist/visualizer/index.js.map +1 -0
- package/examples/code-review.at +167 -0
- package/examples/data-processing.at +296 -0
- package/examples/openclaw-assistant.at +142 -0
- package/examples/scheduled-monitor.at +193 -0
- package/examples/simple-pipeline.at +163 -0
- package/package.json +77 -0
- package/skill/SKILL.md +403 -0
- package/skill/scripts/emit.ts +853 -0
- package/skill/scripts/visualize.ts +89 -0
- package/spec/grammar.md +1466 -0
- package/spec/reserved-keywords.md +147 -0
- package/spec/validation.md +535 -0
|
@@ -0,0 +1,1715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor IDE binding.
|
|
3
|
+
*
|
|
4
|
+
* Generates `.cursor/rules/*.mdc`, `.cursor/mcp.json`, `.cursor/hooks.json`,
|
|
5
|
+
* `AGENTS.md`, and supporting shell scripts from a parsed {@link TopologyAST}.
|
|
6
|
+
*
|
|
7
|
+
* Cursor rules use `.mdc` files with YAML frontmatter supporting only three
|
|
8
|
+
* fields: `description`, `globs`, and `alwaysApply`. All other AST concepts
|
|
9
|
+
* are either mapped to hooks.json, shell scripts, or embedded as polyfill
|
|
10
|
+
* instructions in rule body text.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { deduplicateFiles } from "./types.js";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/** Convert a kebab-case id to Title Case. */
|
|
19
|
+
function toTitle(id) {
|
|
20
|
+
return id
|
|
21
|
+
.split(/[-_]/)
|
|
22
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
23
|
+
.join(" ");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Downshift all markdown headings by one level so they nest properly
|
|
27
|
+
* inside ## Instructions. ## -> ###, ### -> ####, etc.
|
|
28
|
+
*/
|
|
29
|
+
function downshiftHeadings(text) {
|
|
30
|
+
return text.replace(/^(#{2,6})\s/gm, (_, hashes) => `#${hashes} `);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate .mdc-style YAML frontmatter.
|
|
34
|
+
*
|
|
35
|
+
* Only three fields are valid for Cursor: description, globs, alwaysApply.
|
|
36
|
+
* Fields with undefined/null values are omitted. When alwaysApply is false
|
|
37
|
+
* (the default), it is omitted.
|
|
38
|
+
*/
|
|
39
|
+
function frontmatter(fields) {
|
|
40
|
+
const lines = ["---"];
|
|
41
|
+
// Always emit all three fields (matching real-world .mdc convention).
|
|
42
|
+
// description and globs are empty when not set; alwaysApply defaults to false.
|
|
43
|
+
lines.push(`description: ${fields.description ?? ""}`);
|
|
44
|
+
if (fields.globs && fields.globs.length > 0) {
|
|
45
|
+
// Real .mdc files use comma-separated globs on a single line
|
|
46
|
+
lines.push(`globs: ${fields.globs.join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
lines.push("globs:");
|
|
50
|
+
}
|
|
51
|
+
lines.push(`alwaysApply: ${fields.alwaysApply ? "true" : "false"}`);
|
|
52
|
+
lines.push("---");
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
/** Generate a shell script stub with a header comment. */
|
|
56
|
+
function shellStub(description) {
|
|
57
|
+
return [
|
|
58
|
+
"#!/usr/bin/env bash",
|
|
59
|
+
`# ${description}`,
|
|
60
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
61
|
+
"set -euo pipefail",
|
|
62
|
+
"",
|
|
63
|
+
'echo "TODO: implement this script"',
|
|
64
|
+
"",
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
/** Generate an empty directory marker (a .gitkeep file). */
|
|
68
|
+
function gitkeep(dirPath) {
|
|
69
|
+
return { path: `${dirPath}/.gitkeep`, content: "", category: "machine" };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert PascalCase .at hook event to camelCase Cursor event name.
|
|
73
|
+
* e.g. PreToolUse -> preToolUse, SessionStart -> sessionStart
|
|
74
|
+
*/
|
|
75
|
+
function mapHookEvent(atEvent) {
|
|
76
|
+
return atEvent.charAt(0).toLowerCase() + atEvent.slice(1);
|
|
77
|
+
}
|
|
78
|
+
/** Format a SchemaType to a human-readable string. */
|
|
79
|
+
function formatSchemaType(t, schemas) {
|
|
80
|
+
switch (t.kind) {
|
|
81
|
+
case "primitive":
|
|
82
|
+
return t.value;
|
|
83
|
+
case "array":
|
|
84
|
+
return `${formatSchemaType(t.itemType, schemas)}[]`;
|
|
85
|
+
case "enum":
|
|
86
|
+
return t.values.join(" | ");
|
|
87
|
+
case "ref": {
|
|
88
|
+
if (schemas) {
|
|
89
|
+
const resolved = schemas.find((s) => s.id === t.name);
|
|
90
|
+
if (resolved) {
|
|
91
|
+
const fields = resolved.fields.map((f) => `${f.name}${f.optional ? "?" : ""}: ${formatSchemaType(f.type, schemas)}`);
|
|
92
|
+
return `${t.name} { ${fields.join(", ")} }`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return t.name;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Format a SchemaFieldDef to a readable line. */
|
|
100
|
+
function formatSchemaField(f) {
|
|
101
|
+
const opt = f.optional ? "?" : "";
|
|
102
|
+
return `- ${f.name}${opt}: ${formatSchemaType(f.type)}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compile edges into natural-language workflow instructions.
|
|
106
|
+
*/
|
|
107
|
+
function compileEdges(ast) {
|
|
108
|
+
if (ast.edges.length === 0)
|
|
109
|
+
return "";
|
|
110
|
+
const lines = ["## Workflow", ""];
|
|
111
|
+
const nodeMap = new Map(ast.nodes.map((n) => [n.id, n.label]));
|
|
112
|
+
const label = (id) => nodeMap.get(id) ?? toTitle(id);
|
|
113
|
+
let step = 1;
|
|
114
|
+
for (const edge of ast.edges) {
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (edge.isError) {
|
|
117
|
+
const errType = edge.errorType ? `${edge.errorType} ` : "";
|
|
118
|
+
parts.push(`${step}. On ${errType}error from **${label(edge.from)}**, route to **${label(edge.to)}**`);
|
|
119
|
+
}
|
|
120
|
+
else if (edge.condition) {
|
|
121
|
+
// Clean up condition: parser may include trailing "] [max N" artifacts
|
|
122
|
+
const cleanCondition = edge.condition
|
|
123
|
+
.replace(/\]\s*\[max\s+\d+\s*$/i, "")
|
|
124
|
+
.replace(/\]\s*$/, "")
|
|
125
|
+
.trim();
|
|
126
|
+
parts.push(`${step}. If ${cleanCondition}, proceed to **${label(edge.to)}**`);
|
|
127
|
+
}
|
|
128
|
+
else if (edge.reflection) {
|
|
129
|
+
parts.push(`${step}. Loop back to **${label(edge.from)}** for self-evaluation`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
parts.push(`${step}. After **${label(edge.from)}**, proceed to **${label(edge.to)}**`);
|
|
133
|
+
}
|
|
134
|
+
if (edge.maxIterations) {
|
|
135
|
+
parts.push(` - Repeat up to ${edge.maxIterations} times`);
|
|
136
|
+
}
|
|
137
|
+
if (edge.per) {
|
|
138
|
+
parts.push(` - For each ${edge.per} item, run **${label(edge.to)}**`);
|
|
139
|
+
}
|
|
140
|
+
if (edge.race) {
|
|
141
|
+
parts.push(` - Race **${label(edge.to)}** agents -- use first result`);
|
|
142
|
+
}
|
|
143
|
+
if (edge.tolerance != null) {
|
|
144
|
+
parts.push(` - Allow up to ${edge.tolerance} failures`);
|
|
145
|
+
}
|
|
146
|
+
if (edge.wait) {
|
|
147
|
+
parts.push(` - Wait ${edge.wait} before proceeding to **${label(edge.to)}**`);
|
|
148
|
+
}
|
|
149
|
+
if (edge.weight != null) {
|
|
150
|
+
parts.push(` - Route to **${label(edge.to)}** with weight ${edge.weight}`);
|
|
151
|
+
}
|
|
152
|
+
lines.push(...parts);
|
|
153
|
+
step++;
|
|
154
|
+
}
|
|
155
|
+
// Delegation summary
|
|
156
|
+
const agents = ast.nodes.filter((n) => n.type === "agent");
|
|
157
|
+
if (agents.length > 0) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("### Delegation");
|
|
160
|
+
for (const agent of agents) {
|
|
161
|
+
const desc = agent.description ?? agent.label;
|
|
162
|
+
lines.push(`- **${agent.id}**: ${desc} -- activate with \`@${agent.id}\``);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
lines.push("");
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Section generators
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
/** Generate the topology overview rule (always-apply). */
|
|
172
|
+
function generateTopologyOverview(ast) {
|
|
173
|
+
const orch = ast.nodes.find((n) => n.type === "orchestrator");
|
|
174
|
+
const fm = frontmatter({
|
|
175
|
+
description: ast.topology.description || `${ast.topology.name} topology overview`,
|
|
176
|
+
alwaysApply: true,
|
|
177
|
+
});
|
|
178
|
+
const sections = [fm, ""];
|
|
179
|
+
sections.push(`# ${ast.topology.name}`);
|
|
180
|
+
sections.push("");
|
|
181
|
+
sections.push(`<!-- Version: ${ast.topology.version} -->`);
|
|
182
|
+
sections.push("");
|
|
183
|
+
if (ast.topology.description) {
|
|
184
|
+
sections.push(ast.topology.description);
|
|
185
|
+
sections.push("");
|
|
186
|
+
}
|
|
187
|
+
// Orchestrator prompt merged in
|
|
188
|
+
if (orch) {
|
|
189
|
+
if (orch.model) {
|
|
190
|
+
sections.push(`**Model:** ${orch.model}`);
|
|
191
|
+
sections.push("");
|
|
192
|
+
}
|
|
193
|
+
if (orch.generates) {
|
|
194
|
+
sections.push(`**Generates:** ${orch.generates}`);
|
|
195
|
+
sections.push("");
|
|
196
|
+
}
|
|
197
|
+
if (orch.handles && orch.handles.length > 0) {
|
|
198
|
+
sections.push(`**Handles:** ${orch.handles.join(", ")}`);
|
|
199
|
+
sections.push("");
|
|
200
|
+
}
|
|
201
|
+
if (orch.outputs) {
|
|
202
|
+
sections.push("### Orchestrator Outputs");
|
|
203
|
+
for (const [field, values] of Object.entries(orch.outputs)) {
|
|
204
|
+
sections.push(`- ${field}: ${values.join(" | ")}`);
|
|
205
|
+
}
|
|
206
|
+
sections.push("");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Agent table
|
|
210
|
+
const allAgents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
|
|
211
|
+
if (allAgents.length > 0) {
|
|
212
|
+
sections.push("## Agents");
|
|
213
|
+
sections.push("");
|
|
214
|
+
sections.push("| Agent | Type | Description |");
|
|
215
|
+
sections.push("|-------|------|-------------|");
|
|
216
|
+
for (const node of allAgents) {
|
|
217
|
+
let desc = node.label;
|
|
218
|
+
if (node.type === "agent") {
|
|
219
|
+
desc = node.description ?? node.label;
|
|
220
|
+
}
|
|
221
|
+
else if (node.type === "human") {
|
|
222
|
+
desc = node.description ?? node.label;
|
|
223
|
+
}
|
|
224
|
+
else if (node.type === "group") {
|
|
225
|
+
desc = node.description ?? node.label;
|
|
226
|
+
}
|
|
227
|
+
sections.push(`| ${node.id} | ${node.type} | ${desc} |`);
|
|
228
|
+
}
|
|
229
|
+
sections.push("");
|
|
230
|
+
}
|
|
231
|
+
// Compiled edges
|
|
232
|
+
const edgeText = compileEdges(ast);
|
|
233
|
+
if (edgeText) {
|
|
234
|
+
sections.push(edgeText);
|
|
235
|
+
}
|
|
236
|
+
// Defaults
|
|
237
|
+
if (ast.defaults) {
|
|
238
|
+
const d = ast.defaults;
|
|
239
|
+
sections.push("## Defaults");
|
|
240
|
+
sections.push("");
|
|
241
|
+
if (d.temperature != null)
|
|
242
|
+
sections.push(`- Temperature: ${d.temperature}`);
|
|
243
|
+
if (d.maxTokens != null)
|
|
244
|
+
sections.push(`- Max tokens: ${d.maxTokens}`);
|
|
245
|
+
if (d.topP != null)
|
|
246
|
+
sections.push(`- Top-p: ${d.topP}`);
|
|
247
|
+
if (d.topK != null)
|
|
248
|
+
sections.push(`- Top-k: ${d.topK}`);
|
|
249
|
+
if (d.stop)
|
|
250
|
+
sections.push(`- Stop sequences: ${d.stop.join(", ")}`);
|
|
251
|
+
if (d.seed != null)
|
|
252
|
+
sections.push(`- Seed: ${d.seed}`);
|
|
253
|
+
if (d.thinking)
|
|
254
|
+
sections.push(`- Thinking: ${d.thinking}`);
|
|
255
|
+
if (d.thinkingBudget != null)
|
|
256
|
+
sections.push(`- Thinking budget: ${d.thinkingBudget}`);
|
|
257
|
+
if (d.outputFormat)
|
|
258
|
+
sections.push(`- Output format: ${d.outputFormat}`);
|
|
259
|
+
if (d.timeout)
|
|
260
|
+
sections.push(`- Timeout: ${d.timeout}`);
|
|
261
|
+
if (d.logLevel)
|
|
262
|
+
sections.push(`- Log level: ${d.logLevel}`);
|
|
263
|
+
sections.push("");
|
|
264
|
+
}
|
|
265
|
+
// Settings as behavioral instructions
|
|
266
|
+
if (ast.settings && Object.keys(ast.settings).length > 0) {
|
|
267
|
+
sections.push("## Settings");
|
|
268
|
+
sections.push("");
|
|
269
|
+
for (const [key, value] of Object.entries(ast.settings)) {
|
|
270
|
+
if (Array.isArray(value)) {
|
|
271
|
+
sections.push(`- ${key}: ${value.join(", ")}`);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
sections.push(`- ${key}: ${JSON.stringify(value)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
sections.push("");
|
|
278
|
+
}
|
|
279
|
+
// Depth levels
|
|
280
|
+
if (ast.depth && ast.depth.levels && ast.depth.levels.length > 0) {
|
|
281
|
+
sections.push("<!-- Depth levels:");
|
|
282
|
+
if (ast.depth.factors.length > 0) {
|
|
283
|
+
sections.push(` Factors: ${ast.depth.factors.join(", ")}`);
|
|
284
|
+
}
|
|
285
|
+
for (const level of ast.depth.levels) {
|
|
286
|
+
let line = ` Level ${level.level}: ${level.label}`;
|
|
287
|
+
if (level.omit.length > 0)
|
|
288
|
+
line += ` (omit: ${level.omit.join(", ")})`;
|
|
289
|
+
sections.push(line);
|
|
290
|
+
}
|
|
291
|
+
sections.push("-->");
|
|
292
|
+
sections.push("");
|
|
293
|
+
}
|
|
294
|
+
// Batch
|
|
295
|
+
if (ast.batch && Object.keys(ast.batch).length > 0) {
|
|
296
|
+
sections.push("## Batch");
|
|
297
|
+
sections.push("");
|
|
298
|
+
for (const [key, value] of Object.entries(ast.batch)) {
|
|
299
|
+
sections.push(`- ${key}: ${JSON.stringify(value)}`);
|
|
300
|
+
}
|
|
301
|
+
sections.push("");
|
|
302
|
+
}
|
|
303
|
+
// Environments
|
|
304
|
+
if (ast.environments && Object.keys(ast.environments).length > 0) {
|
|
305
|
+
sections.push("<!-- Environments:");
|
|
306
|
+
for (const [envName, envConfig] of Object.entries(ast.environments)) {
|
|
307
|
+
sections.push(` ${envName}:`);
|
|
308
|
+
for (const [key, value] of Object.entries(envConfig)) {
|
|
309
|
+
sections.push(` ${key}: ${JSON.stringify(value)}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
sections.push("-->");
|
|
313
|
+
sections.push("");
|
|
314
|
+
}
|
|
315
|
+
// Providers
|
|
316
|
+
if (ast.providers && ast.providers.length > 0) {
|
|
317
|
+
sections.push("<!-- Providers:");
|
|
318
|
+
for (const provider of ast.providers) {
|
|
319
|
+
let line = ` ${provider.name}`;
|
|
320
|
+
if (provider.default)
|
|
321
|
+
line += " (default)";
|
|
322
|
+
if (provider.models.length > 0)
|
|
323
|
+
line += ` -- models: ${provider.models.join(", ")}`;
|
|
324
|
+
sections.push(line);
|
|
325
|
+
}
|
|
326
|
+
sections.push("-->");
|
|
327
|
+
sections.push("");
|
|
328
|
+
}
|
|
329
|
+
// Schedules
|
|
330
|
+
if (ast.schedules.length > 0) {
|
|
331
|
+
sections.push("<!-- Schedules:");
|
|
332
|
+
for (const job of ast.schedules) {
|
|
333
|
+
const expr = job.cron
|
|
334
|
+
? `cron: ${job.cron}`
|
|
335
|
+
: job.every
|
|
336
|
+
? `every: ${job.every}`
|
|
337
|
+
: "";
|
|
338
|
+
const target = job.agent
|
|
339
|
+
? `agent: ${job.agent}`
|
|
340
|
+
: job.action
|
|
341
|
+
? `action: ${job.action}`
|
|
342
|
+
: "";
|
|
343
|
+
sections.push(` ${job.id}: ${expr}${target ? ` -> ${target}` : ""}${job.enabled === false ? " (disabled)" : ""}`);
|
|
344
|
+
}
|
|
345
|
+
sections.push("-->");
|
|
346
|
+
sections.push("");
|
|
347
|
+
}
|
|
348
|
+
// Interfaces
|
|
349
|
+
if (ast.interfaces.length > 0) {
|
|
350
|
+
sections.push("<!-- Interfaces:");
|
|
351
|
+
for (const iface of ast.interfaces) {
|
|
352
|
+
const typePart = iface.type ? ` (${iface.type})` : "";
|
|
353
|
+
sections.push(` ${iface.id}${typePart}`);
|
|
354
|
+
}
|
|
355
|
+
sections.push("-->");
|
|
356
|
+
sections.push("");
|
|
357
|
+
}
|
|
358
|
+
// Pattern metadata
|
|
359
|
+
if (ast.topology.patterns.length > 0) {
|
|
360
|
+
sections.push(`<!-- Patterns: ${ast.topology.patterns.join(", ")} -->`);
|
|
361
|
+
}
|
|
362
|
+
if (ast.topology.domain) {
|
|
363
|
+
sections.push(`<!-- Domain: ${ast.topology.domain} -->`);
|
|
364
|
+
}
|
|
365
|
+
if (ast.topology.foundations && ast.topology.foundations.length > 0) {
|
|
366
|
+
sections.push(`<!-- Foundations: ${ast.topology.foundations.join(", ")} -->`);
|
|
367
|
+
}
|
|
368
|
+
if (ast.topology.advanced && ast.topology.advanced.length > 0) {
|
|
369
|
+
sections.push(`<!-- Advanced: ${ast.topology.advanced.join(", ")} -->`);
|
|
370
|
+
}
|
|
371
|
+
if (ast.topology.durable) {
|
|
372
|
+
sections.push("<!-- Durable: yes -->");
|
|
373
|
+
}
|
|
374
|
+
if (ast.topology.timeout) {
|
|
375
|
+
sections.push(`<!-- Timeout: ${ast.topology.timeout} -->`);
|
|
376
|
+
}
|
|
377
|
+
if (ast.topology.errorHandler) {
|
|
378
|
+
sections.push(`<!-- Error handler: ${ast.topology.errorHandler} -->`);
|
|
379
|
+
}
|
|
380
|
+
// Params
|
|
381
|
+
if (ast.params && ast.params.length > 0) {
|
|
382
|
+
sections.push("<!-- Parameters:");
|
|
383
|
+
for (const p of ast.params) {
|
|
384
|
+
const req = p.required ? " (required)" : "";
|
|
385
|
+
const def = p.default != null ? ` = ${p.default}` : "";
|
|
386
|
+
sections.push(` ${p.name}: ${p.type}${req}${def}`);
|
|
387
|
+
}
|
|
388
|
+
sections.push("-->");
|
|
389
|
+
}
|
|
390
|
+
// Interface endpoints
|
|
391
|
+
if (ast.interfaceEndpoints) {
|
|
392
|
+
sections.push(`<!-- Interface: entry=${ast.interfaceEndpoints.entry}, exit=${ast.interfaceEndpoints.exit} -->`);
|
|
393
|
+
}
|
|
394
|
+
// Imports
|
|
395
|
+
if (ast.imports && ast.imports.length > 0) {
|
|
396
|
+
sections.push("<!-- Imports:");
|
|
397
|
+
for (const imp of ast.imports) {
|
|
398
|
+
let line = ` ${imp.alias} from ${imp.source}`;
|
|
399
|
+
if (imp.sha256)
|
|
400
|
+
line += ` (sha256: ${imp.sha256})`;
|
|
401
|
+
sections.push(line);
|
|
402
|
+
}
|
|
403
|
+
sections.push("-->");
|
|
404
|
+
}
|
|
405
|
+
// Checkpoint
|
|
406
|
+
if (ast.checkpoint) {
|
|
407
|
+
const cp = ast.checkpoint;
|
|
408
|
+
sections.push("<!-- Checkpoint:");
|
|
409
|
+
sections.push(` backend: ${cp.backend}`);
|
|
410
|
+
sections.push(` strategy: ${cp.strategy}`);
|
|
411
|
+
if (cp.connection)
|
|
412
|
+
sections.push(` connection: ${cp.connection}`);
|
|
413
|
+
if (cp.ttl)
|
|
414
|
+
sections.push(` ttl: ${cp.ttl}`);
|
|
415
|
+
if (cp.replay) {
|
|
416
|
+
sections.push(` replay: ${cp.replay.enabled ? "enabled" : "disabled"}`);
|
|
417
|
+
}
|
|
418
|
+
sections.push("-->");
|
|
419
|
+
}
|
|
420
|
+
// Artifacts
|
|
421
|
+
if (ast.artifacts && ast.artifacts.length > 0) {
|
|
422
|
+
sections.push("<!-- Artifacts:");
|
|
423
|
+
for (const a of ast.artifacts) {
|
|
424
|
+
let line = ` ${a.id} (${a.type})`;
|
|
425
|
+
if (a.path)
|
|
426
|
+
line += ` -- path: ${a.path}`;
|
|
427
|
+
if (a.retention)
|
|
428
|
+
line += `, retention: ${a.retention}`;
|
|
429
|
+
sections.push(line);
|
|
430
|
+
}
|
|
431
|
+
sections.push("-->");
|
|
432
|
+
}
|
|
433
|
+
sections.push("");
|
|
434
|
+
return {
|
|
435
|
+
path: ".cursor/rules/topology-overview.mdc",
|
|
436
|
+
content: sections.join("\n"),
|
|
437
|
+
category: "agent",
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/** Generate .mdc rule files for each AgentNode. */
|
|
441
|
+
function generateAgentRules(ast) {
|
|
442
|
+
const files = [];
|
|
443
|
+
for (const node of ast.nodes) {
|
|
444
|
+
if (node.type !== "agent")
|
|
445
|
+
continue;
|
|
446
|
+
const agent = node;
|
|
447
|
+
// Warn about impossible features
|
|
448
|
+
if (agent.permissions) {
|
|
449
|
+
console.warn(`[cursor] Warning: permission "${agent.permissions}" has no Cursor equivalent -- skipped.`);
|
|
450
|
+
}
|
|
451
|
+
if (agent.isolation) {
|
|
452
|
+
console.warn(`[cursor] Warning: isolation "${agent.isolation}" has no Cursor equivalent -- skipped.`);
|
|
453
|
+
}
|
|
454
|
+
// Build frontmatter
|
|
455
|
+
const fm = {};
|
|
456
|
+
fm.description = agent.description ?? agent.label;
|
|
457
|
+
// Cursor-specific globs extension
|
|
458
|
+
const cursorExt = agent.extensions?.cursor;
|
|
459
|
+
if (cursorExt?.globs) {
|
|
460
|
+
fm.globs = cursorExt.globs;
|
|
461
|
+
}
|
|
462
|
+
// alwaysApply override
|
|
463
|
+
if (cursorExt?.alwaysApply) {
|
|
464
|
+
fm.alwaysApply = true;
|
|
465
|
+
}
|
|
466
|
+
// Manual invocation: omit description to make it @-mention only
|
|
467
|
+
if (agent.invocation === "manual") {
|
|
468
|
+
delete fm.description;
|
|
469
|
+
}
|
|
470
|
+
// Build body
|
|
471
|
+
const sections = [frontmatter(fm), ""];
|
|
472
|
+
sections.push(`# ${agent.label || toTitle(agent.id)}`);
|
|
473
|
+
sections.push("");
|
|
474
|
+
// Role text
|
|
475
|
+
const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
|
|
476
|
+
if (roleText) {
|
|
477
|
+
sections.push(roleText);
|
|
478
|
+
sections.push("");
|
|
479
|
+
}
|
|
480
|
+
// Phase comment
|
|
481
|
+
if (agent.phase != null) {
|
|
482
|
+
sections.push(`<!-- Phase: ${agent.phase} -->`);
|
|
483
|
+
sections.push("");
|
|
484
|
+
}
|
|
485
|
+
// Model instruction
|
|
486
|
+
if (agent.model) {
|
|
487
|
+
sections.push(`**Model:** ${agent.model}`);
|
|
488
|
+
sections.push("");
|
|
489
|
+
}
|
|
490
|
+
// Instructions
|
|
491
|
+
sections.push("## Instructions");
|
|
492
|
+
sections.push("");
|
|
493
|
+
if (agent.prompt) {
|
|
494
|
+
sections.push(downshiftHeadings(agent.prompt));
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
sections.push(`${agent.description || agent.role || "Complete the assigned task."}`);
|
|
498
|
+
}
|
|
499
|
+
sections.push("");
|
|
500
|
+
// Tools
|
|
501
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
502
|
+
sections.push("## Tools");
|
|
503
|
+
for (const tool of agent.tools) {
|
|
504
|
+
sections.push(`- ${tool}`);
|
|
505
|
+
}
|
|
506
|
+
sections.push("");
|
|
507
|
+
}
|
|
508
|
+
// Disallowed tools
|
|
509
|
+
if (agent.disallowedTools && agent.disallowedTools.length > 0) {
|
|
510
|
+
sections.push("## Disallowed Tools");
|
|
511
|
+
sections.push(`**Do NOT use:** ${agent.disallowedTools.join(", ")}`);
|
|
512
|
+
sections.push("");
|
|
513
|
+
}
|
|
514
|
+
// Skills references
|
|
515
|
+
if (agent.skills && agent.skills.length > 0) {
|
|
516
|
+
sections.push("## Skills");
|
|
517
|
+
for (const skill of agent.skills) {
|
|
518
|
+
sections.push(`- @skill-${skill}`);
|
|
519
|
+
}
|
|
520
|
+
sections.push("");
|
|
521
|
+
}
|
|
522
|
+
// Memory: reads/writes
|
|
523
|
+
if ((agent.reads && agent.reads.length > 0) ||
|
|
524
|
+
(agent.writes && agent.writes.length > 0)) {
|
|
525
|
+
sections.push("## Memory");
|
|
526
|
+
if (agent.reads && agent.reads.length > 0) {
|
|
527
|
+
sections.push(`**Reads:** ${agent.reads.join(", ")}`);
|
|
528
|
+
}
|
|
529
|
+
if (agent.writes && agent.writes.length > 0) {
|
|
530
|
+
sections.push(`**Writes:** ${agent.writes.join(", ")}`);
|
|
531
|
+
}
|
|
532
|
+
sections.push("");
|
|
533
|
+
}
|
|
534
|
+
// Outputs
|
|
535
|
+
if (agent.outputs) {
|
|
536
|
+
sections.push("## Outputs");
|
|
537
|
+
for (const [field, values] of Object.entries(agent.outputs)) {
|
|
538
|
+
sections.push(`- ${field}: ${values.join(" | ")}`);
|
|
539
|
+
}
|
|
540
|
+
sections.push("");
|
|
541
|
+
}
|
|
542
|
+
// Sampling params
|
|
543
|
+
const hasModelConfig = agent.temperature != null ||
|
|
544
|
+
agent.maxTokens != null ||
|
|
545
|
+
agent.topP != null ||
|
|
546
|
+
agent.topK != null ||
|
|
547
|
+
agent.stop != null ||
|
|
548
|
+
agent.seed != null;
|
|
549
|
+
if (hasModelConfig) {
|
|
550
|
+
sections.push("## Model Configuration");
|
|
551
|
+
if (agent.temperature != null)
|
|
552
|
+
sections.push(`- Temperature: ${agent.temperature}`);
|
|
553
|
+
if (agent.maxTokens != null)
|
|
554
|
+
sections.push(`- Max tokens: ${agent.maxTokens}`);
|
|
555
|
+
if (agent.topP != null)
|
|
556
|
+
sections.push(`- Top-p: ${agent.topP}`);
|
|
557
|
+
if (agent.topK != null)
|
|
558
|
+
sections.push(`- Top-k: ${agent.topK}`);
|
|
559
|
+
if (agent.stop)
|
|
560
|
+
sections.push(`- Stop sequences: ${agent.stop.join(", ")}`);
|
|
561
|
+
if (agent.seed != null)
|
|
562
|
+
sections.push(`- Seed: ${agent.seed}`);
|
|
563
|
+
sections.push("");
|
|
564
|
+
}
|
|
565
|
+
// Thinking
|
|
566
|
+
if (agent.thinking) {
|
|
567
|
+
let thinkingLine = `Use ${agent.thinking} level reasoning.`;
|
|
568
|
+
if (agent.thinkingBudget != null) {
|
|
569
|
+
thinkingLine = `Use ${agent.thinking} level reasoning (budget: ${agent.thinkingBudget} tokens).`;
|
|
570
|
+
}
|
|
571
|
+
sections.push(thinkingLine);
|
|
572
|
+
sections.push("");
|
|
573
|
+
}
|
|
574
|
+
// Output format
|
|
575
|
+
if (agent.outputFormat) {
|
|
576
|
+
sections.push(`Output format: ${agent.outputFormat}`);
|
|
577
|
+
sections.push("");
|
|
578
|
+
}
|
|
579
|
+
// Log level
|
|
580
|
+
if (agent.logLevel) {
|
|
581
|
+
sections.push(`<!-- Log level: ${agent.logLevel} -->`);
|
|
582
|
+
sections.push("");
|
|
583
|
+
}
|
|
584
|
+
// Timeout
|
|
585
|
+
if (agent.timeout) {
|
|
586
|
+
sections.push(`**Timeout:** ${agent.timeout}`);
|
|
587
|
+
sections.push("");
|
|
588
|
+
}
|
|
589
|
+
// Max turns
|
|
590
|
+
if (agent.maxTurns != null) {
|
|
591
|
+
sections.push(`**Max turns:** ${agent.maxTurns}`);
|
|
592
|
+
sections.push("");
|
|
593
|
+
}
|
|
594
|
+
// On fail
|
|
595
|
+
if (agent.onFail) {
|
|
596
|
+
sections.push(`If you fail: ${agent.onFail}`);
|
|
597
|
+
sections.push("");
|
|
598
|
+
}
|
|
599
|
+
// Retry
|
|
600
|
+
if (agent.retry != null) {
|
|
601
|
+
if (typeof agent.retry === "number") {
|
|
602
|
+
sections.push(`**Retry:** up to ${agent.retry} times`);
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
const r = agent.retry;
|
|
606
|
+
const parts = [`max ${r.max} attempts`];
|
|
607
|
+
if (r.backoff)
|
|
608
|
+
parts.push(`${r.backoff} backoff`);
|
|
609
|
+
if (r.interval)
|
|
610
|
+
parts.push(`interval ${r.interval}`);
|
|
611
|
+
if (r.maxInterval)
|
|
612
|
+
parts.push(`max interval ${r.maxInterval}`);
|
|
613
|
+
if (r.jitter)
|
|
614
|
+
parts.push("with jitter");
|
|
615
|
+
if (r.nonRetryable && r.nonRetryable.length > 0) {
|
|
616
|
+
parts.push(`non-retryable: ${r.nonRetryable.join(", ")}`);
|
|
617
|
+
}
|
|
618
|
+
sections.push(`Retry strategy: ${parts.join(", ")}.`);
|
|
619
|
+
}
|
|
620
|
+
sections.push("");
|
|
621
|
+
}
|
|
622
|
+
// Circuit breaker
|
|
623
|
+
if (agent.circuitBreaker) {
|
|
624
|
+
const cb = agent.circuitBreaker;
|
|
625
|
+
sections.push("## Circuit Breaker");
|
|
626
|
+
sections.push(`- Failure threshold: ${cb.threshold}`);
|
|
627
|
+
sections.push(`- Window: ${cb.window}`);
|
|
628
|
+
sections.push(`- Cooldown: ${cb.cooldown}`);
|
|
629
|
+
sections.push("");
|
|
630
|
+
}
|
|
631
|
+
// Compensates
|
|
632
|
+
if (agent.compensates) {
|
|
633
|
+
sections.push(`This agent compensates (can undo) the work of: ${agent.compensates}`);
|
|
634
|
+
sections.push("");
|
|
635
|
+
}
|
|
636
|
+
// Input Schema
|
|
637
|
+
if (agent.inputSchema && agent.inputSchema.length > 0) {
|
|
638
|
+
sections.push("## Input Schema");
|
|
639
|
+
for (const field of agent.inputSchema) {
|
|
640
|
+
sections.push(formatSchemaField(field));
|
|
641
|
+
}
|
|
642
|
+
sections.push("");
|
|
643
|
+
}
|
|
644
|
+
// Output Schema
|
|
645
|
+
if (agent.outputSchema && agent.outputSchema.length > 0) {
|
|
646
|
+
sections.push("## Output Schema");
|
|
647
|
+
for (const field of agent.outputSchema) {
|
|
648
|
+
sections.push(formatSchemaField(field));
|
|
649
|
+
}
|
|
650
|
+
sections.push("");
|
|
651
|
+
}
|
|
652
|
+
// Rate limit
|
|
653
|
+
if (agent.rateLimit) {
|
|
654
|
+
sections.push(`Rate limit: ${agent.rateLimit}`);
|
|
655
|
+
sections.push("");
|
|
656
|
+
}
|
|
657
|
+
// Artifacts: produces/consumes
|
|
658
|
+
if ((agent.produces && agent.produces.length > 0) ||
|
|
659
|
+
(agent.consumes && agent.consumes.length > 0)) {
|
|
660
|
+
sections.push("## Artifacts");
|
|
661
|
+
if (agent.produces && agent.produces.length > 0) {
|
|
662
|
+
sections.push(`Produces: ${agent.produces.join(", ")}`);
|
|
663
|
+
}
|
|
664
|
+
if (agent.consumes && agent.consumes.length > 0) {
|
|
665
|
+
sections.push(`Consumes: ${agent.consumes.join(", ")}`);
|
|
666
|
+
}
|
|
667
|
+
sections.push("");
|
|
668
|
+
}
|
|
669
|
+
// Variants
|
|
670
|
+
if (agent.variants && agent.variants.length > 0) {
|
|
671
|
+
sections.push("## Variants");
|
|
672
|
+
for (const v of agent.variants) {
|
|
673
|
+
const parts = [`**${v.id}** (weight: ${v.weight})`];
|
|
674
|
+
if (v.model)
|
|
675
|
+
parts.push(`model: ${v.model}`);
|
|
676
|
+
if (v.temperature != null)
|
|
677
|
+
parts.push(`temperature: ${v.temperature}`);
|
|
678
|
+
sections.push(`- ${parts.join(", ")}`);
|
|
679
|
+
if (v.prompt) {
|
|
680
|
+
sections.push(` Prompt: ${v.prompt}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
sections.push("");
|
|
684
|
+
}
|
|
685
|
+
// Scale
|
|
686
|
+
if (agent.scale) {
|
|
687
|
+
const s = agent.scale;
|
|
688
|
+
sections.push("## Scale");
|
|
689
|
+
sections.push(`- Mode: ${s.mode}`);
|
|
690
|
+
sections.push(`- By: ${s.by}`);
|
|
691
|
+
sections.push(`- Min: ${s.min}`);
|
|
692
|
+
sections.push(`- Max: ${s.max}`);
|
|
693
|
+
if (s.batchSize != null)
|
|
694
|
+
sections.push(`- Batch size: ${s.batchSize}`);
|
|
695
|
+
sections.push("");
|
|
696
|
+
}
|
|
697
|
+
// Sandbox
|
|
698
|
+
if (agent.sandbox != null) {
|
|
699
|
+
sections.push(`Sandbox: ${agent.sandbox}`);
|
|
700
|
+
sections.push("");
|
|
701
|
+
}
|
|
702
|
+
// Fallback chain
|
|
703
|
+
if (agent.fallbackChain && agent.fallbackChain.length > 0) {
|
|
704
|
+
sections.push(`Fallback chain: ${agent.fallbackChain.join(" -> ")}`);
|
|
705
|
+
sections.push("");
|
|
706
|
+
}
|
|
707
|
+
// Join
|
|
708
|
+
if (agent.join) {
|
|
709
|
+
sections.push(`Wait strategy: ${agent.join} (wait for upstream agents)`);
|
|
710
|
+
sections.push("");
|
|
711
|
+
}
|
|
712
|
+
// Background
|
|
713
|
+
if (agent.background) {
|
|
714
|
+
sections.push("This agent runs in the background.");
|
|
715
|
+
sections.push("");
|
|
716
|
+
}
|
|
717
|
+
// Skip
|
|
718
|
+
if (agent.skip) {
|
|
719
|
+
sections.push(`Skip when: ${agent.skip}`);
|
|
720
|
+
sections.push("");
|
|
721
|
+
}
|
|
722
|
+
// Behavior
|
|
723
|
+
if (agent.behavior) {
|
|
724
|
+
sections.push(`**Behavior:** ${agent.behavior}`);
|
|
725
|
+
sections.push("");
|
|
726
|
+
}
|
|
727
|
+
files.push({
|
|
728
|
+
path: `.cursor/rules/${agent.id}.mdc`,
|
|
729
|
+
content: sections.join("\n"),
|
|
730
|
+
category: "agent",
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return files;
|
|
734
|
+
}
|
|
735
|
+
/** Generate .mdc rule files for each HumanNode. */
|
|
736
|
+
function generateHumanRules(ast) {
|
|
737
|
+
const files = [];
|
|
738
|
+
for (const node of ast.nodes) {
|
|
739
|
+
if (node.type !== "human")
|
|
740
|
+
continue;
|
|
741
|
+
const human = node;
|
|
742
|
+
const fm = frontmatter({
|
|
743
|
+
description: human.description ?? `Human approval required: ${human.label}`,
|
|
744
|
+
});
|
|
745
|
+
const sections = [fm, ""];
|
|
746
|
+
sections.push(`# ${human.label || toTitle(human.id)}`);
|
|
747
|
+
sections.push("");
|
|
748
|
+
sections.push("This step requires human review and approval.");
|
|
749
|
+
sections.push("");
|
|
750
|
+
if (human.description) {
|
|
751
|
+
sections.push(human.description);
|
|
752
|
+
sections.push("");
|
|
753
|
+
}
|
|
754
|
+
sections.push("## Process");
|
|
755
|
+
sections.push("1. Review the current state and outputs");
|
|
756
|
+
sections.push("2. Approve or reject the proposed changes");
|
|
757
|
+
sections.push("3. Provide feedback if rejecting");
|
|
758
|
+
sections.push("");
|
|
759
|
+
if (human.timeout) {
|
|
760
|
+
sections.push(`**Timeout:** ${human.timeout}`);
|
|
761
|
+
sections.push("");
|
|
762
|
+
}
|
|
763
|
+
if (human.onTimeout) {
|
|
764
|
+
sections.push(`**On timeout:** ${human.onTimeout}`);
|
|
765
|
+
sections.push("");
|
|
766
|
+
}
|
|
767
|
+
files.push({
|
|
768
|
+
path: `.cursor/rules/${human.id}.mdc`,
|
|
769
|
+
content: sections.join("\n"),
|
|
770
|
+
category: "agent",
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return files;
|
|
774
|
+
}
|
|
775
|
+
/** Generate .mdc rule files for each GroupNode. */
|
|
776
|
+
function generateGroupRules(ast) {
|
|
777
|
+
const files = [];
|
|
778
|
+
for (const node of ast.nodes) {
|
|
779
|
+
if (node.type !== "group")
|
|
780
|
+
continue;
|
|
781
|
+
const group = node;
|
|
782
|
+
const fm = frontmatter({
|
|
783
|
+
description: group.description ?? `Group: ${group.label}`,
|
|
784
|
+
});
|
|
785
|
+
const sections = [fm, ""];
|
|
786
|
+
sections.push(`# ${group.label || toTitle(group.id)}`);
|
|
787
|
+
sections.push("");
|
|
788
|
+
sections.push("## Members");
|
|
789
|
+
for (const member of group.members) {
|
|
790
|
+
sections.push(`- @${member}`);
|
|
791
|
+
}
|
|
792
|
+
sections.push("");
|
|
793
|
+
sections.push("## Interaction Pattern");
|
|
794
|
+
if (group.speakerSelection) {
|
|
795
|
+
sections.push(`Speaker selection: ${group.speakerSelection}`);
|
|
796
|
+
}
|
|
797
|
+
if (group.maxRounds != null) {
|
|
798
|
+
sections.push(`Max rounds: ${group.maxRounds}`);
|
|
799
|
+
}
|
|
800
|
+
if (group.termination) {
|
|
801
|
+
sections.push(`Termination: ${group.termination}`);
|
|
802
|
+
}
|
|
803
|
+
if (group.timeout) {
|
|
804
|
+
sections.push(`Timeout: ${group.timeout}`);
|
|
805
|
+
}
|
|
806
|
+
sections.push("");
|
|
807
|
+
files.push({
|
|
808
|
+
path: `.cursor/rules/${group.id}.mdc`,
|
|
809
|
+
content: sections.join("\n"),
|
|
810
|
+
category: "agent",
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
return files;
|
|
814
|
+
}
|
|
815
|
+
/** Generate shell scripts for each ActionNode. */
|
|
816
|
+
function generateActionScripts(ast) {
|
|
817
|
+
const files = [];
|
|
818
|
+
for (const node of ast.nodes) {
|
|
819
|
+
if (node.type !== "action")
|
|
820
|
+
continue;
|
|
821
|
+
const action = node;
|
|
822
|
+
const lines = ["#!/usr/bin/env bash"];
|
|
823
|
+
lines.push(`# Action: ${action.id} -- ${action.description ?? action.label}`);
|
|
824
|
+
lines.push("# Auto-generated by agentopology scaffold -- edit as needed.");
|
|
825
|
+
lines.push("set -euo pipefail");
|
|
826
|
+
lines.push("");
|
|
827
|
+
if (action.timeout) {
|
|
828
|
+
lines.push(`# Timeout: ${action.timeout}`);
|
|
829
|
+
}
|
|
830
|
+
if (action.onFail) {
|
|
831
|
+
lines.push(`# On failure: ${action.onFail}`);
|
|
832
|
+
}
|
|
833
|
+
if (action.join) {
|
|
834
|
+
lines.push(`# Join semantics: ${action.join}`);
|
|
835
|
+
}
|
|
836
|
+
switch (action.kind) {
|
|
837
|
+
case "external":
|
|
838
|
+
lines.push(`# External source: ${action.source ?? "unknown"}`);
|
|
839
|
+
lines.push("");
|
|
840
|
+
lines.push(`echo "TODO: implement external action -- fetch from ${action.source ?? "source"}"`);
|
|
841
|
+
break;
|
|
842
|
+
case "inline":
|
|
843
|
+
if (action.commands && action.commands.length > 0) {
|
|
844
|
+
lines.push("");
|
|
845
|
+
for (const cmd of action.commands) {
|
|
846
|
+
lines.push(cmd);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
lines.push("");
|
|
851
|
+
lines.push('echo "TODO: implement inline action"');
|
|
852
|
+
}
|
|
853
|
+
break;
|
|
854
|
+
case "git":
|
|
855
|
+
lines.push("");
|
|
856
|
+
lines.push("# Git operation");
|
|
857
|
+
if (action.commands && action.commands.length > 0) {
|
|
858
|
+
for (const cmd of action.commands) {
|
|
859
|
+
lines.push(cmd);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
lines.push('echo "TODO: implement git action"');
|
|
864
|
+
}
|
|
865
|
+
break;
|
|
866
|
+
case "report":
|
|
867
|
+
lines.push("");
|
|
868
|
+
lines.push("# Report generation");
|
|
869
|
+
lines.push('echo "TODO: implement report action"');
|
|
870
|
+
break;
|
|
871
|
+
default:
|
|
872
|
+
lines.push("");
|
|
873
|
+
lines.push(`echo "TODO: implement ${action.kind ?? "unknown"} action"`);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
lines.push("");
|
|
877
|
+
files.push({
|
|
878
|
+
path: `.cursor/scripts/actions/${action.id}.sh`,
|
|
879
|
+
content: lines.join("\n"),
|
|
880
|
+
category: "script",
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
return files;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Generate gate hooks and scripts.
|
|
887
|
+
*
|
|
888
|
+
* Returns gate scripts as GeneratedFile[]. The hook entries are collected
|
|
889
|
+
* by generateHooksJson separately.
|
|
890
|
+
*/
|
|
891
|
+
function generateGateHooks(ast) {
|
|
892
|
+
const files = [];
|
|
893
|
+
for (const node of ast.nodes) {
|
|
894
|
+
if (node.type !== "gate")
|
|
895
|
+
continue;
|
|
896
|
+
const gate = node;
|
|
897
|
+
const lines = [
|
|
898
|
+
"#!/usr/bin/env bash",
|
|
899
|
+
`# Gate: ${gate.id}`,
|
|
900
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
901
|
+
"set -euo pipefail",
|
|
902
|
+
"",
|
|
903
|
+
];
|
|
904
|
+
lines.push(`# After: ${gate.after ?? "any"}`);
|
|
905
|
+
lines.push(`# Before: ${gate.before ?? "any"}`);
|
|
906
|
+
if (gate.onFail)
|
|
907
|
+
lines.push(`# On failure: ${gate.onFail}`);
|
|
908
|
+
if (gate.behavior)
|
|
909
|
+
lines.push(`# Behavior: ${gate.behavior}`);
|
|
910
|
+
if (gate.timeout)
|
|
911
|
+
lines.push(`# Timeout: ${gate.timeout}`);
|
|
912
|
+
lines.push("");
|
|
913
|
+
if (gate.run) {
|
|
914
|
+
lines.push(`# Run: ${gate.run}`);
|
|
915
|
+
lines.push("");
|
|
916
|
+
if (gate.retry != null && gate.retry > 0) {
|
|
917
|
+
lines.push(`MAX_RETRIES=${gate.retry}`);
|
|
918
|
+
lines.push("ATTEMPT=0");
|
|
919
|
+
lines.push("GATE_RESULT=1");
|
|
920
|
+
lines.push("");
|
|
921
|
+
lines.push("while [ $ATTEMPT -le $MAX_RETRIES ] && [ $GATE_RESULT -ne 0 ]; do");
|
|
922
|
+
lines.push(" ATTEMPT=$((ATTEMPT + 1))");
|
|
923
|
+
lines.push(` ${gate.run} && GATE_RESULT=0 || GATE_RESULT=$?`);
|
|
924
|
+
lines.push(' if [ $GATE_RESULT -ne 0 ] && [ $ATTEMPT -le $MAX_RETRIES ]; then');
|
|
925
|
+
lines.push(` echo "Gate '${gate.id}' attempt $ATTEMPT failed, retrying..."`);
|
|
926
|
+
lines.push(" sleep 1");
|
|
927
|
+
lines.push(" fi");
|
|
928
|
+
lines.push("done");
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
lines.push("GATE_RESULT=0");
|
|
932
|
+
lines.push(`${gate.run} || GATE_RESULT=$?`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
else if (gate.checks && gate.checks.length > 0) {
|
|
936
|
+
lines.push("# Checks:");
|
|
937
|
+
lines.push("GATE_RESULT=0");
|
|
938
|
+
lines.push("");
|
|
939
|
+
for (const check of gate.checks) {
|
|
940
|
+
lines.push(`# Check: ${check}`);
|
|
941
|
+
lines.push(`echo "TODO: implement check '${check}'" # || GATE_RESULT=1`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
lines.push("GATE_RESULT=0");
|
|
946
|
+
lines.push('echo "TODO: implement gate check"');
|
|
947
|
+
}
|
|
948
|
+
lines.push("");
|
|
949
|
+
const exitAction = gate.onFail === "halt" || gate.behavior === "blocking"
|
|
950
|
+
? "exit 1"
|
|
951
|
+
: 'echo "Gate failed -- advisory only"';
|
|
952
|
+
lines.push('if [ "$GATE_RESULT" -ne 0 ]; then');
|
|
953
|
+
lines.push(` echo "Gate '${gate.id}' FAILED (exit code $GATE_RESULT)"`);
|
|
954
|
+
lines.push(` ${exitAction}`);
|
|
955
|
+
lines.push("fi");
|
|
956
|
+
lines.push("");
|
|
957
|
+
files.push({
|
|
958
|
+
path: `.cursor/hooks/gate-${gate.id}.sh`,
|
|
959
|
+
content: lines.join("\n"),
|
|
960
|
+
category: "script",
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
return files;
|
|
964
|
+
}
|
|
965
|
+
/** Generate .mdc rule files for each SkillDef. */
|
|
966
|
+
function generateSkillRules(ast) {
|
|
967
|
+
const files = [];
|
|
968
|
+
for (const skill of ast.skills) {
|
|
969
|
+
const isManual = skill.userInvocable === true;
|
|
970
|
+
const fm = isManual
|
|
971
|
+
? frontmatter({})
|
|
972
|
+
: frontmatter({ description: skill.description || skill.id });
|
|
973
|
+
const sections = [fm, ""];
|
|
974
|
+
sections.push(`# ${toTitle(skill.id)}`);
|
|
975
|
+
sections.push("");
|
|
976
|
+
if (skill.description) {
|
|
977
|
+
sections.push(skill.description);
|
|
978
|
+
sections.push("");
|
|
979
|
+
}
|
|
980
|
+
// Prompt content
|
|
981
|
+
if (skill.prompt) {
|
|
982
|
+
sections.push(`See: ${skill.prompt}`);
|
|
983
|
+
sections.push("");
|
|
984
|
+
}
|
|
985
|
+
// Scripts references
|
|
986
|
+
if (skill.scripts && skill.scripts.length > 0) {
|
|
987
|
+
sections.push("## Scripts");
|
|
988
|
+
for (const s of skill.scripts) {
|
|
989
|
+
sections.push(`- ${s}`);
|
|
990
|
+
}
|
|
991
|
+
sections.push("");
|
|
992
|
+
}
|
|
993
|
+
// Domains references
|
|
994
|
+
if (skill.domains && skill.domains.length > 0) {
|
|
995
|
+
sections.push("## Domains");
|
|
996
|
+
for (const d of skill.domains) {
|
|
997
|
+
sections.push(`- ${d}`);
|
|
998
|
+
}
|
|
999
|
+
sections.push("");
|
|
1000
|
+
}
|
|
1001
|
+
// File references
|
|
1002
|
+
if (skill.references && skill.references.length > 0) {
|
|
1003
|
+
sections.push("## References");
|
|
1004
|
+
for (const r of skill.references) {
|
|
1005
|
+
sections.push(`- @${r}`);
|
|
1006
|
+
}
|
|
1007
|
+
sections.push("");
|
|
1008
|
+
}
|
|
1009
|
+
// Allowed tools
|
|
1010
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1011
|
+
sections.push("## Allowed Tools");
|
|
1012
|
+
for (const tool of skill.allowedTools) {
|
|
1013
|
+
sections.push(`- ${tool}`);
|
|
1014
|
+
}
|
|
1015
|
+
sections.push("");
|
|
1016
|
+
}
|
|
1017
|
+
// disableModelInvocation
|
|
1018
|
+
if (skill.disableModelInvocation) {
|
|
1019
|
+
sections.push("**Note:** Model invocation is disabled for this skill. Execute scripts directly.");
|
|
1020
|
+
sections.push("");
|
|
1021
|
+
}
|
|
1022
|
+
// Context mode
|
|
1023
|
+
if (skill.context) {
|
|
1024
|
+
sections.push(`<!-- Context mode: ${skill.context} -->`);
|
|
1025
|
+
}
|
|
1026
|
+
// Agent type
|
|
1027
|
+
if (skill.agent) {
|
|
1028
|
+
sections.push(`<!-- Agent type: ${skill.agent} -->`);
|
|
1029
|
+
}
|
|
1030
|
+
files.push({
|
|
1031
|
+
path: `.cursor/rules/skill-${skill.id}.mdc`,
|
|
1032
|
+
content: sections.join("\n"),
|
|
1033
|
+
category: "agent",
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
return files;
|
|
1037
|
+
}
|
|
1038
|
+
/** Generate .mdc rule files for each TriggerDef. */
|
|
1039
|
+
function generateTriggerRules(ast) {
|
|
1040
|
+
const files = [];
|
|
1041
|
+
for (const trigger of ast.triggers) {
|
|
1042
|
+
// Manual rule -- empty frontmatter
|
|
1043
|
+
const fm = frontmatter({});
|
|
1044
|
+
const sections = [fm, ""];
|
|
1045
|
+
sections.push(`# ${toTitle(trigger.name)}`);
|
|
1046
|
+
sections.push("");
|
|
1047
|
+
sections.push(trigger.pattern);
|
|
1048
|
+
sections.push("");
|
|
1049
|
+
if (trigger.argument) {
|
|
1050
|
+
sections.push("## Arguments");
|
|
1051
|
+
sections.push(`- ${trigger.argument}: extracted from the command pattern`);
|
|
1052
|
+
sections.push("");
|
|
1053
|
+
}
|
|
1054
|
+
files.push({
|
|
1055
|
+
path: `.cursor/rules/trigger-${trigger.name}.mdc`,
|
|
1056
|
+
content: sections.join("\n"),
|
|
1057
|
+
category: "agent",
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
return files;
|
|
1061
|
+
}
|
|
1062
|
+
/** Generate .cursor/mcp.json from global and per-agent MCP servers. */
|
|
1063
|
+
function generateMcpJson(ast) {
|
|
1064
|
+
// Collect all MCP servers (global + per-agent)
|
|
1065
|
+
const allServers = {};
|
|
1066
|
+
// Global MCP servers
|
|
1067
|
+
for (const [name, config] of Object.entries(ast.mcpServers)) {
|
|
1068
|
+
allServers[name] = config;
|
|
1069
|
+
}
|
|
1070
|
+
// Per-agent MCP servers: merge by reference
|
|
1071
|
+
for (const node of ast.nodes) {
|
|
1072
|
+
if (node.type !== "agent")
|
|
1073
|
+
continue;
|
|
1074
|
+
const agent = node;
|
|
1075
|
+
if (!agent.mcpServers || agent.mcpServers.length === 0)
|
|
1076
|
+
continue;
|
|
1077
|
+
// Agent mcpServers are just names (strings), they reference global config
|
|
1078
|
+
// The actual config comes from ast.mcpServers
|
|
1079
|
+
}
|
|
1080
|
+
if (Object.keys(allServers).length === 0)
|
|
1081
|
+
return null;
|
|
1082
|
+
const mcpConfig = { mcpServers: {} };
|
|
1083
|
+
const servers = mcpConfig.mcpServers;
|
|
1084
|
+
for (const [name, config] of Object.entries(allServers)) {
|
|
1085
|
+
const entry = {};
|
|
1086
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1087
|
+
if (key === "env" && typeof value === "object" && value !== null) {
|
|
1088
|
+
// Handle SensitiveValue env vars
|
|
1089
|
+
const envObj = {};
|
|
1090
|
+
for (const [envKey, envVal] of Object.entries(value)) {
|
|
1091
|
+
if (typeof envVal === "string") {
|
|
1092
|
+
envObj[envKey] = envVal;
|
|
1093
|
+
}
|
|
1094
|
+
else if (typeof envVal === "object" &&
|
|
1095
|
+
envVal !== null &&
|
|
1096
|
+
"value" in envVal) {
|
|
1097
|
+
const sv = envVal;
|
|
1098
|
+
if (sv.sensitive) {
|
|
1099
|
+
envObj[envKey] = `\${${envKey}}`;
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
envObj[envKey] = sv.value;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
entry.env = envObj;
|
|
1107
|
+
}
|
|
1108
|
+
else if (key === "args" && Array.isArray(value)) {
|
|
1109
|
+
entry.args = value;
|
|
1110
|
+
}
|
|
1111
|
+
else if (key === "command" || key === "url") {
|
|
1112
|
+
// Valid MCP server fields: command (stdio), url (SSE)
|
|
1113
|
+
entry[key] = value;
|
|
1114
|
+
}
|
|
1115
|
+
// Skip unknown fields — the parser may leak env vars to top level
|
|
1116
|
+
}
|
|
1117
|
+
// Only include servers that have a command or url (filter parser artifacts)
|
|
1118
|
+
if (!("command" in entry) && !("url" in entry))
|
|
1119
|
+
continue;
|
|
1120
|
+
servers[name] = entry;
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
path: ".cursor/mcp.json",
|
|
1124
|
+
content: JSON.stringify(mcpConfig, null, 2) + "\n",
|
|
1125
|
+
category: "shared-config",
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Generate .cursor/hooks.json from global hooks, per-agent hooks, gate nodes,
|
|
1130
|
+
* and metering configuration.
|
|
1131
|
+
*/
|
|
1132
|
+
function generateHooksJson(ast) {
|
|
1133
|
+
const hooksByEvent = {};
|
|
1134
|
+
// Helper to add a hook entry
|
|
1135
|
+
const addHook = (event, entry) => {
|
|
1136
|
+
if (!hooksByEvent[event])
|
|
1137
|
+
hooksByEvent[event] = [];
|
|
1138
|
+
hooksByEvent[event].push(entry);
|
|
1139
|
+
};
|
|
1140
|
+
// Global hooks
|
|
1141
|
+
for (const hook of ast.hooks) {
|
|
1142
|
+
const event = mapHookEvent(hook.on);
|
|
1143
|
+
const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
|
|
1144
|
+
const command = hook.run.startsWith("./")
|
|
1145
|
+
? hook.run
|
|
1146
|
+
: `.cursor/hooks/${scriptName}`;
|
|
1147
|
+
const cursorExt = hook.extensions?.cursor;
|
|
1148
|
+
const entry = {
|
|
1149
|
+
command,
|
|
1150
|
+
};
|
|
1151
|
+
if (hook.matcher)
|
|
1152
|
+
entry.matcher = hook.matcher;
|
|
1153
|
+
if (hook.timeout != null)
|
|
1154
|
+
entry.timeout = Math.ceil(hook.timeout / 1000);
|
|
1155
|
+
// Cursor hooks do NOT have a `type` field (unlike Claude Code).
|
|
1156
|
+
// Only include failClosed when true (false is the default).
|
|
1157
|
+
const failClosed = cursorExt?.failClosed ?? false;
|
|
1158
|
+
if (failClosed)
|
|
1159
|
+
entry.failClosed = true;
|
|
1160
|
+
if (cursorExt?.loop_limit != null) {
|
|
1161
|
+
entry.loop_limit = cursorExt.loop_limit;
|
|
1162
|
+
}
|
|
1163
|
+
addHook(event, entry);
|
|
1164
|
+
}
|
|
1165
|
+
// Per-agent hooks
|
|
1166
|
+
for (const node of ast.nodes) {
|
|
1167
|
+
if (node.type !== "agent")
|
|
1168
|
+
continue;
|
|
1169
|
+
const agent = node;
|
|
1170
|
+
if (!agent.hooks || agent.hooks.length === 0)
|
|
1171
|
+
continue;
|
|
1172
|
+
for (const hook of agent.hooks) {
|
|
1173
|
+
const event = mapHookEvent(hook.on);
|
|
1174
|
+
const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
|
|
1175
|
+
const command = hook.run.startsWith("./")
|
|
1176
|
+
? hook.run
|
|
1177
|
+
: `.cursor/hooks/${agent.id}-${scriptName}`;
|
|
1178
|
+
const cursorExt = hook.extensions?.cursor;
|
|
1179
|
+
const entry = {
|
|
1180
|
+
command,
|
|
1181
|
+
};
|
|
1182
|
+
if (hook.matcher)
|
|
1183
|
+
entry.matcher = hook.matcher;
|
|
1184
|
+
else
|
|
1185
|
+
entry.matcher = agent.id;
|
|
1186
|
+
if (hook.timeout != null)
|
|
1187
|
+
entry.timeout = Math.ceil(hook.timeout / 1000);
|
|
1188
|
+
// Cursor hooks do NOT have a `type` field.
|
|
1189
|
+
const agentFailClosed = cursorExt?.failClosed ?? false;
|
|
1190
|
+
if (agentFailClosed)
|
|
1191
|
+
entry.failClosed = true;
|
|
1192
|
+
if (cursorExt?.loop_limit != null) {
|
|
1193
|
+
entry.loop_limit = cursorExt.loop_limit;
|
|
1194
|
+
}
|
|
1195
|
+
addHook(event, entry);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// Gate compilation to hooks
|
|
1199
|
+
const gates = ast.nodes.filter((n) => n.type === "gate");
|
|
1200
|
+
for (const gate of gates) {
|
|
1201
|
+
const scriptPath = `.cursor/hooks/gate-${gate.id}.sh`;
|
|
1202
|
+
// Determine event
|
|
1203
|
+
let event;
|
|
1204
|
+
if (gate.before) {
|
|
1205
|
+
event = "preToolUse";
|
|
1206
|
+
}
|
|
1207
|
+
else if (gate.after) {
|
|
1208
|
+
event = "postToolUse";
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
event = "preToolUse";
|
|
1212
|
+
}
|
|
1213
|
+
// Determine matcher
|
|
1214
|
+
const matcher = gate.after ?? gate.before ?? undefined;
|
|
1215
|
+
// Determine failClosed
|
|
1216
|
+
const failClosed = gate.behavior === "blocking" || gate.onFail === "halt";
|
|
1217
|
+
const entry = {
|
|
1218
|
+
command: scriptPath,
|
|
1219
|
+
};
|
|
1220
|
+
if (failClosed)
|
|
1221
|
+
entry.failClosed = true;
|
|
1222
|
+
if (matcher)
|
|
1223
|
+
entry.matcher = matcher;
|
|
1224
|
+
if (gate.timeout) {
|
|
1225
|
+
// Parse duration string to seconds (best-effort)
|
|
1226
|
+
const match = gate.timeout.match(/^(\d+)(s|m|h)?$/);
|
|
1227
|
+
if (match) {
|
|
1228
|
+
const val = parseInt(match[1], 10);
|
|
1229
|
+
const unit = match[2] ?? "s";
|
|
1230
|
+
const seconds = unit === "h" ? val * 3600 : unit === "m" ? val * 60 : val;
|
|
1231
|
+
entry.timeout = seconds;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
const cursorExt = gate.extensions?.cursor;
|
|
1235
|
+
if (cursorExt?.loop_limit != null) {
|
|
1236
|
+
entry.loop_limit = cursorExt.loop_limit;
|
|
1237
|
+
}
|
|
1238
|
+
addHook(event, entry);
|
|
1239
|
+
}
|
|
1240
|
+
// Metering -> postToolUse hook
|
|
1241
|
+
if (ast.metering) {
|
|
1242
|
+
addHook("postToolUse", {
|
|
1243
|
+
command: ".cursor/hooks/metering.sh",
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (Object.keys(hooksByEvent).length === 0)
|
|
1247
|
+
return null;
|
|
1248
|
+
const hooksJson = {
|
|
1249
|
+
version: 1,
|
|
1250
|
+
hooks: hooksByEvent,
|
|
1251
|
+
};
|
|
1252
|
+
return {
|
|
1253
|
+
path: ".cursor/hooks.json",
|
|
1254
|
+
content: JSON.stringify(hooksJson, null, 2) + "\n",
|
|
1255
|
+
category: "shared-config",
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
/** Generate hook script stubs for global hooks. */
|
|
1259
|
+
function generateHookScripts(ast) {
|
|
1260
|
+
const files = [];
|
|
1261
|
+
for (const hook of ast.hooks) {
|
|
1262
|
+
if (!hook.run)
|
|
1263
|
+
continue;
|
|
1264
|
+
// Skip if run already has an absolute/relative path prefix (external script)
|
|
1265
|
+
if (hook.run.startsWith("./") || hook.run.startsWith("/"))
|
|
1266
|
+
continue;
|
|
1267
|
+
const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
|
|
1268
|
+
files.push({
|
|
1269
|
+
path: `.cursor/hooks/${scriptName}`,
|
|
1270
|
+
content: [
|
|
1271
|
+
"#!/usr/bin/env bash",
|
|
1272
|
+
`# Hook: ${hook.name} -- fires on ${hook.on}`,
|
|
1273
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1274
|
+
"set -euo pipefail",
|
|
1275
|
+
"",
|
|
1276
|
+
`echo "TODO: implement ${hook.name} hook"`,
|
|
1277
|
+
"",
|
|
1278
|
+
].join("\n"),
|
|
1279
|
+
category: "script",
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return files;
|
|
1283
|
+
}
|
|
1284
|
+
/** Generate per-agent hook script stubs. */
|
|
1285
|
+
function generateAgentHookScripts(ast) {
|
|
1286
|
+
const files = [];
|
|
1287
|
+
for (const node of ast.nodes) {
|
|
1288
|
+
if (node.type !== "agent")
|
|
1289
|
+
continue;
|
|
1290
|
+
const agent = node;
|
|
1291
|
+
if (!agent.hooks || agent.hooks.length === 0)
|
|
1292
|
+
continue;
|
|
1293
|
+
for (const hook of agent.hooks) {
|
|
1294
|
+
if (!hook.run)
|
|
1295
|
+
continue;
|
|
1296
|
+
if (hook.run.startsWith("./") || hook.run.startsWith("/"))
|
|
1297
|
+
continue;
|
|
1298
|
+
const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
|
|
1299
|
+
files.push({
|
|
1300
|
+
path: `.cursor/hooks/${agent.id}-${scriptName}`,
|
|
1301
|
+
content: [
|
|
1302
|
+
"#!/usr/bin/env bash",
|
|
1303
|
+
`# Hook: ${hook.name} -- fires on ${hook.on} (scoped to agent: ${agent.id})`,
|
|
1304
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1305
|
+
"set -euo pipefail",
|
|
1306
|
+
"",
|
|
1307
|
+
`echo "TODO: implement ${hook.name} hook for ${agent.id}"`,
|
|
1308
|
+
"",
|
|
1309
|
+
].join("\n"),
|
|
1310
|
+
category: "script",
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return files;
|
|
1315
|
+
}
|
|
1316
|
+
/** Generate tool script stubs. */
|
|
1317
|
+
function generateToolScripts(ast) {
|
|
1318
|
+
const files = [];
|
|
1319
|
+
for (const tool of ast.toolDefs) {
|
|
1320
|
+
const ext = tool.lang === "python" ? "py" : tool.lang === "node" ? "js" : "sh";
|
|
1321
|
+
const shebang = tool.lang === "python"
|
|
1322
|
+
? "#!/usr/bin/env python3"
|
|
1323
|
+
: tool.lang === "node"
|
|
1324
|
+
? "#!/usr/bin/env node"
|
|
1325
|
+
: "#!/usr/bin/env bash";
|
|
1326
|
+
const lines = [
|
|
1327
|
+
shebang,
|
|
1328
|
+
`# Tool: ${tool.id} -- ${tool.description}`,
|
|
1329
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1330
|
+
"",
|
|
1331
|
+
];
|
|
1332
|
+
if (tool.lang === "bash" || !tool.lang) {
|
|
1333
|
+
lines.push("set -euo pipefail");
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
}
|
|
1336
|
+
if (tool.args && tool.args.length > 0) {
|
|
1337
|
+
lines.push(`# Args: ${tool.args.join(", ")}`);
|
|
1338
|
+
lines.push("");
|
|
1339
|
+
}
|
|
1340
|
+
if (tool.lang === "python") {
|
|
1341
|
+
lines.push(`print("TODO: implement ${tool.id}")`);
|
|
1342
|
+
}
|
|
1343
|
+
else if (tool.lang === "node") {
|
|
1344
|
+
lines.push(`console.log("TODO: implement ${tool.id}");`);
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
lines.push(`echo "TODO: implement ${tool.id}"`);
|
|
1348
|
+
}
|
|
1349
|
+
lines.push("");
|
|
1350
|
+
files.push({
|
|
1351
|
+
path: `.cursor/scripts/tools/${tool.id}.${ext}`,
|
|
1352
|
+
content: lines.join("\n"),
|
|
1353
|
+
category: "script",
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
return files;
|
|
1357
|
+
}
|
|
1358
|
+
/** Generate metering script stub. */
|
|
1359
|
+
function generateMeteringScript(ast) {
|
|
1360
|
+
if (!ast.metering)
|
|
1361
|
+
return null;
|
|
1362
|
+
const m = ast.metering;
|
|
1363
|
+
const lines = [
|
|
1364
|
+
"#!/usr/bin/env bash",
|
|
1365
|
+
"# Metering / cost tracking script",
|
|
1366
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1367
|
+
"set -euo pipefail",
|
|
1368
|
+
"",
|
|
1369
|
+
`# Track: ${m.track.join(", ")}`,
|
|
1370
|
+
`# Per: ${m.per.join(", ")}`,
|
|
1371
|
+
`# Output: ${m.output} (${m.format})`,
|
|
1372
|
+
`# Pricing: ${m.pricing}`,
|
|
1373
|
+
"",
|
|
1374
|
+
'# Read hook input from stdin',
|
|
1375
|
+
'INPUT=$(cat)',
|
|
1376
|
+
"",
|
|
1377
|
+
'# TODO: Parse input JSON and record metrics',
|
|
1378
|
+
`echo "TODO: implement metrics collection"`,
|
|
1379
|
+
"",
|
|
1380
|
+
];
|
|
1381
|
+
return {
|
|
1382
|
+
path: ".cursor/hooks/metering.sh",
|
|
1383
|
+
content: lines.join("\n"),
|
|
1384
|
+
category: "script",
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
/** Generate the AGENTS.md context file. */
|
|
1388
|
+
function generateContextFile(ast) {
|
|
1389
|
+
const DEFAULT_PATH = "AGENTS.md";
|
|
1390
|
+
const fileName = ast.context.file ?? DEFAULT_PATH;
|
|
1391
|
+
const sections = [];
|
|
1392
|
+
sections.push(`# ${ast.topology.name}`);
|
|
1393
|
+
sections.push("");
|
|
1394
|
+
if (ast.topology.description) {
|
|
1395
|
+
sections.push(ast.topology.description);
|
|
1396
|
+
sections.push("");
|
|
1397
|
+
}
|
|
1398
|
+
// Agent roster table
|
|
1399
|
+
const allAgents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
|
|
1400
|
+
if (allAgents.length > 0) {
|
|
1401
|
+
sections.push("## Agents");
|
|
1402
|
+
sections.push("");
|
|
1403
|
+
sections.push("| Agent | Description |");
|
|
1404
|
+
sections.push("|-------|-------------|");
|
|
1405
|
+
for (const node of allAgents) {
|
|
1406
|
+
let desc = node.label;
|
|
1407
|
+
if (node.type === "agent") {
|
|
1408
|
+
desc = node.description ?? node.label;
|
|
1409
|
+
}
|
|
1410
|
+
else if (node.type === "human") {
|
|
1411
|
+
desc = node.description ?? node.label;
|
|
1412
|
+
}
|
|
1413
|
+
else if (node.type === "group") {
|
|
1414
|
+
desc = node.description ?? node.label;
|
|
1415
|
+
}
|
|
1416
|
+
sections.push(`| ${node.id} | ${desc} |`);
|
|
1417
|
+
}
|
|
1418
|
+
sections.push("");
|
|
1419
|
+
}
|
|
1420
|
+
// Workflow summary (compiled edges)
|
|
1421
|
+
const edgeText = compileEdges(ast);
|
|
1422
|
+
if (edgeText) {
|
|
1423
|
+
sections.push(edgeText);
|
|
1424
|
+
}
|
|
1425
|
+
// Rules usage guide
|
|
1426
|
+
sections.push("## Rules");
|
|
1427
|
+
sections.push("");
|
|
1428
|
+
sections.push("- Use `@{rule-name}` to activate specific agent rules");
|
|
1429
|
+
sections.push("- Agent rules are in `.cursor/rules/`");
|
|
1430
|
+
sections.push("- MCP servers configured in `.cursor/mcp.json`");
|
|
1431
|
+
sections.push("- Cursor supports Enterprise > Team > Project > User rule hierarchy. These are Project-level rules.");
|
|
1432
|
+
sections.push("");
|
|
1433
|
+
// Providers
|
|
1434
|
+
if (ast.providers && ast.providers.length > 0) {
|
|
1435
|
+
sections.push("## Providers");
|
|
1436
|
+
sections.push("");
|
|
1437
|
+
for (const provider of ast.providers) {
|
|
1438
|
+
let line = `- **${provider.name}**`;
|
|
1439
|
+
if (provider.default)
|
|
1440
|
+
line += " (default)";
|
|
1441
|
+
if (provider.models.length > 0)
|
|
1442
|
+
line += ` -- models: ${provider.models.join(", ")}`;
|
|
1443
|
+
sections.push(line);
|
|
1444
|
+
}
|
|
1445
|
+
sections.push("");
|
|
1446
|
+
}
|
|
1447
|
+
// Checkpoint
|
|
1448
|
+
if (ast.checkpoint) {
|
|
1449
|
+
const cp = ast.checkpoint;
|
|
1450
|
+
sections.push("## Checkpoint");
|
|
1451
|
+
sections.push("");
|
|
1452
|
+
sections.push(`- Backend: ${cp.backend}`);
|
|
1453
|
+
sections.push(`- Strategy: ${cp.strategy}`);
|
|
1454
|
+
if (cp.connection)
|
|
1455
|
+
sections.push(`- Connection: ${cp.connection}`);
|
|
1456
|
+
if (cp.ttl)
|
|
1457
|
+
sections.push(`- TTL: ${cp.ttl}`);
|
|
1458
|
+
sections.push("");
|
|
1459
|
+
}
|
|
1460
|
+
// Artifacts
|
|
1461
|
+
if (ast.artifacts && ast.artifacts.length > 0) {
|
|
1462
|
+
sections.push("## Artifacts");
|
|
1463
|
+
sections.push("");
|
|
1464
|
+
for (const a of ast.artifacts) {
|
|
1465
|
+
let line = `- **${a.id}** (${a.type})`;
|
|
1466
|
+
if (a.path)
|
|
1467
|
+
line += ` -- path: ${a.path}`;
|
|
1468
|
+
if (a.retention)
|
|
1469
|
+
line += `, retention: ${a.retention}`;
|
|
1470
|
+
sections.push(line);
|
|
1471
|
+
}
|
|
1472
|
+
sections.push("");
|
|
1473
|
+
}
|
|
1474
|
+
// Context includes
|
|
1475
|
+
if (ast.context.includes && ast.context.includes.length > 0) {
|
|
1476
|
+
sections.push("## Includes");
|
|
1477
|
+
sections.push("");
|
|
1478
|
+
for (const inc of ast.context.includes) {
|
|
1479
|
+
sections.push(`- ${inc}`);
|
|
1480
|
+
}
|
|
1481
|
+
sections.push("");
|
|
1482
|
+
}
|
|
1483
|
+
return {
|
|
1484
|
+
path: fileName,
|
|
1485
|
+
content: sections.join("\n"),
|
|
1486
|
+
category: "agent",
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
/** Generate legacy .cursorrules file (flat markdown, no frontmatter). */
|
|
1490
|
+
function generateCursorrules(ast) {
|
|
1491
|
+
// Only generate if explicitly requested via extensions
|
|
1492
|
+
const cursorExt = ast.extensions?.cursor;
|
|
1493
|
+
if (!cursorExt || cursorExt.legacyRules !== true)
|
|
1494
|
+
return null;
|
|
1495
|
+
const sections = [];
|
|
1496
|
+
sections.push(`# ${ast.topology.name}`);
|
|
1497
|
+
sections.push("");
|
|
1498
|
+
if (ast.topology.description) {
|
|
1499
|
+
sections.push(ast.topology.description);
|
|
1500
|
+
sections.push("");
|
|
1501
|
+
}
|
|
1502
|
+
// Agent summaries
|
|
1503
|
+
const agents = ast.nodes.filter((n) => n.type === "agent" || n.type === "human" || n.type === "group");
|
|
1504
|
+
if (agents.length > 0) {
|
|
1505
|
+
sections.push("## Agents");
|
|
1506
|
+
sections.push("");
|
|
1507
|
+
for (const node of agents) {
|
|
1508
|
+
let desc = node.label;
|
|
1509
|
+
if (node.type === "agent") {
|
|
1510
|
+
desc = node.description ?? node.label;
|
|
1511
|
+
}
|
|
1512
|
+
else if (node.type === "human") {
|
|
1513
|
+
desc = node.description ?? node.label;
|
|
1514
|
+
}
|
|
1515
|
+
else if (node.type === "group") {
|
|
1516
|
+
desc = node.description ?? node.label;
|
|
1517
|
+
}
|
|
1518
|
+
sections.push(`- **${node.id}**: ${desc}`);
|
|
1519
|
+
}
|
|
1520
|
+
sections.push("");
|
|
1521
|
+
}
|
|
1522
|
+
// Workflow
|
|
1523
|
+
const edgeText = compileEdges(ast);
|
|
1524
|
+
if (edgeText) {
|
|
1525
|
+
sections.push(edgeText);
|
|
1526
|
+
}
|
|
1527
|
+
// Settings/defaults as behavioral text
|
|
1528
|
+
if (ast.defaults) {
|
|
1529
|
+
const d = ast.defaults;
|
|
1530
|
+
sections.push("## Defaults");
|
|
1531
|
+
sections.push("");
|
|
1532
|
+
if (d.temperature != null)
|
|
1533
|
+
sections.push(`- Temperature: ${d.temperature}`);
|
|
1534
|
+
if (d.maxTokens != null)
|
|
1535
|
+
sections.push(`- Max tokens: ${d.maxTokens}`);
|
|
1536
|
+
if (d.timeout)
|
|
1537
|
+
sections.push(`- Timeout: ${d.timeout}`);
|
|
1538
|
+
sections.push("");
|
|
1539
|
+
}
|
|
1540
|
+
return {
|
|
1541
|
+
path: ".cursorrules",
|
|
1542
|
+
content: sections.join("\n"),
|
|
1543
|
+
category: "agent",
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
/** Generate memory directory markers. */
|
|
1547
|
+
function generateMemory(ast) {
|
|
1548
|
+
const files = [];
|
|
1549
|
+
const memKeys = Object.keys(ast.memory);
|
|
1550
|
+
if (memKeys.length > 0) {
|
|
1551
|
+
files.push(gitkeep(".cursor/memory"));
|
|
1552
|
+
}
|
|
1553
|
+
return files;
|
|
1554
|
+
}
|
|
1555
|
+
/** Generate observability hook scripts. */
|
|
1556
|
+
function generateObservabilityScripts(ast) {
|
|
1557
|
+
const files = [];
|
|
1558
|
+
if (!ast.observability || !ast.observability.enabled)
|
|
1559
|
+
return files;
|
|
1560
|
+
const o = ast.observability;
|
|
1561
|
+
const lines = [
|
|
1562
|
+
"#!/usr/bin/env bash",
|
|
1563
|
+
"# Observability capture script",
|
|
1564
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1565
|
+
"set -euo pipefail",
|
|
1566
|
+
"",
|
|
1567
|
+
`# Exporter: ${o.exporter}`,
|
|
1568
|
+
`# Level: ${o.level}`,
|
|
1569
|
+
`# Sample rate: ${o.sampleRate}`,
|
|
1570
|
+
];
|
|
1571
|
+
if (o.endpoint)
|
|
1572
|
+
lines.push(`# Endpoint: ${o.endpoint}`);
|
|
1573
|
+
if (o.service)
|
|
1574
|
+
lines.push(`# Service: ${o.service}`);
|
|
1575
|
+
lines.push("");
|
|
1576
|
+
lines.push("# Read hook input from stdin");
|
|
1577
|
+
lines.push("INPUT=$(cat)");
|
|
1578
|
+
lines.push("");
|
|
1579
|
+
lines.push('echo "TODO: forward trace data to exporter"');
|
|
1580
|
+
lines.push("");
|
|
1581
|
+
files.push({
|
|
1582
|
+
path: ".cursor/scripts/observability.sh",
|
|
1583
|
+
content: lines.join("\n"),
|
|
1584
|
+
category: "script",
|
|
1585
|
+
});
|
|
1586
|
+
return files;
|
|
1587
|
+
}
|
|
1588
|
+
/** Generate schedule scripts. */
|
|
1589
|
+
function generateScheduleScripts(ast) {
|
|
1590
|
+
const files = [];
|
|
1591
|
+
if (!ast.schedules || ast.schedules.length === 0)
|
|
1592
|
+
return files;
|
|
1593
|
+
for (const job of ast.schedules) {
|
|
1594
|
+
const target = job.agent
|
|
1595
|
+
? `agent ${job.agent}`
|
|
1596
|
+
: job.action
|
|
1597
|
+
? `action ${job.action}`
|
|
1598
|
+
: "unknown";
|
|
1599
|
+
const lines = [
|
|
1600
|
+
"#!/usr/bin/env bash",
|
|
1601
|
+
`# Schedule: ${job.id} -- invokes ${target}`,
|
|
1602
|
+
"# Auto-generated by agentopology scaffold -- edit as needed.",
|
|
1603
|
+
"set -euo pipefail",
|
|
1604
|
+
"",
|
|
1605
|
+
];
|
|
1606
|
+
if (job.enabled === false) {
|
|
1607
|
+
lines.push("# DISABLED -- this schedule is not enabled");
|
|
1608
|
+
lines.push("exit 0");
|
|
1609
|
+
lines.push("");
|
|
1610
|
+
}
|
|
1611
|
+
if (job.cron) {
|
|
1612
|
+
lines.push(`# Cron: ${job.cron}`);
|
|
1613
|
+
}
|
|
1614
|
+
else if (job.every) {
|
|
1615
|
+
lines.push(`# Every: ${job.every}`);
|
|
1616
|
+
}
|
|
1617
|
+
if (job.agent) {
|
|
1618
|
+
lines.push(`# Invoke agent: ${job.agent}`);
|
|
1619
|
+
lines.push(`echo "TODO: invoke agent ${job.agent}"`);
|
|
1620
|
+
}
|
|
1621
|
+
else if (job.action) {
|
|
1622
|
+
lines.push(`# Invoke action: ${job.action}`);
|
|
1623
|
+
lines.push(`bash ./.cursor/scripts/actions/${job.action}.sh`);
|
|
1624
|
+
}
|
|
1625
|
+
lines.push("");
|
|
1626
|
+
files.push({
|
|
1627
|
+
path: `.cursor/scripts/schedule-${job.id}.sh`,
|
|
1628
|
+
content: lines.join("\n"),
|
|
1629
|
+
category: "script",
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
return files;
|
|
1633
|
+
}
|
|
1634
|
+
/** Generate schema documentation files. */
|
|
1635
|
+
function generateSchemas(ast) {
|
|
1636
|
+
const files = [];
|
|
1637
|
+
if (!ast.schemas || ast.schemas.length === 0)
|
|
1638
|
+
return files;
|
|
1639
|
+
for (const schema of ast.schemas) {
|
|
1640
|
+
const sections = [];
|
|
1641
|
+
sections.push(`# Schema: ${schema.id}`);
|
|
1642
|
+
sections.push("");
|
|
1643
|
+
sections.push("## Fields");
|
|
1644
|
+
sections.push("");
|
|
1645
|
+
for (const field of schema.fields) {
|
|
1646
|
+
const opt = field.optional ? " (optional)" : "";
|
|
1647
|
+
sections.push(`- **${field.name}**${opt}: ${formatSchemaType(field.type, ast.schemas)}`);
|
|
1648
|
+
}
|
|
1649
|
+
sections.push("");
|
|
1650
|
+
// Embed as a rule that agents can reference
|
|
1651
|
+
const fm = frontmatter({
|
|
1652
|
+
description: `Schema definition: ${schema.id}`,
|
|
1653
|
+
});
|
|
1654
|
+
files.push({
|
|
1655
|
+
path: `.cursor/rules/schema-${schema.id}.mdc`,
|
|
1656
|
+
content: fm + "\n\n" + sections.join("\n"),
|
|
1657
|
+
category: "agent",
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
return files;
|
|
1661
|
+
}
|
|
1662
|
+
// ---------------------------------------------------------------------------
|
|
1663
|
+
// Binding export
|
|
1664
|
+
// ---------------------------------------------------------------------------
|
|
1665
|
+
/** Cursor IDE binding. */
|
|
1666
|
+
export const cursorBinding = {
|
|
1667
|
+
name: "cursor",
|
|
1668
|
+
description: "Cursor IDE -- generates .cursor/rules/*.mdc, .cursor/mcp.json, .cursor/hooks.json, and scripts.",
|
|
1669
|
+
scaffold(ast) {
|
|
1670
|
+
// Fragment check
|
|
1671
|
+
if (ast.isFragment) {
|
|
1672
|
+
return deduplicateFiles([
|
|
1673
|
+
generateTopologyOverview(ast),
|
|
1674
|
+
...generateAgentRules(ast),
|
|
1675
|
+
]);
|
|
1676
|
+
}
|
|
1677
|
+
const files = [];
|
|
1678
|
+
// Rules
|
|
1679
|
+
files.push(generateTopologyOverview(ast));
|
|
1680
|
+
files.push(...generateAgentRules(ast));
|
|
1681
|
+
files.push(...generateHumanRules(ast));
|
|
1682
|
+
files.push(...generateGroupRules(ast));
|
|
1683
|
+
files.push(...generateSkillRules(ast));
|
|
1684
|
+
files.push(...generateTriggerRules(ast));
|
|
1685
|
+
files.push(...generateSchemas(ast));
|
|
1686
|
+
// Scripts
|
|
1687
|
+
files.push(...generateActionScripts(ast));
|
|
1688
|
+
files.push(...generateGateHooks(ast));
|
|
1689
|
+
files.push(...generateHookScripts(ast));
|
|
1690
|
+
files.push(...generateAgentHookScripts(ast));
|
|
1691
|
+
files.push(...generateToolScripts(ast));
|
|
1692
|
+
files.push(...generateScheduleScripts(ast));
|
|
1693
|
+
files.push(...generateObservabilityScripts(ast));
|
|
1694
|
+
// Config files
|
|
1695
|
+
const mcpFile = generateMcpJson(ast);
|
|
1696
|
+
if (mcpFile)
|
|
1697
|
+
files.push(mcpFile);
|
|
1698
|
+
const hooksFile = generateHooksJson(ast);
|
|
1699
|
+
if (hooksFile)
|
|
1700
|
+
files.push(hooksFile);
|
|
1701
|
+
const meteringFile = generateMeteringScript(ast);
|
|
1702
|
+
if (meteringFile)
|
|
1703
|
+
files.push(meteringFile);
|
|
1704
|
+
// Context
|
|
1705
|
+
files.push(generateContextFile(ast));
|
|
1706
|
+
// Legacy
|
|
1707
|
+
const legacyFile = generateCursorrules(ast);
|
|
1708
|
+
if (legacyFile)
|
|
1709
|
+
files.push(legacyFile);
|
|
1710
|
+
// Memory
|
|
1711
|
+
files.push(...generateMemory(ast));
|
|
1712
|
+
return deduplicateFiles(files);
|
|
1713
|
+
},
|
|
1714
|
+
};
|
|
1715
|
+
//# sourceMappingURL=cursor.js.map
|