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