agentopology 0.1.1 → 0.2.1
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 +8 -6
- package/dist/analyzer/index.d.ts +1 -0
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +35 -0
- package/dist/analyzer/index.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +11 -11
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +339 -85
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/openclaw.d.ts.map +1 -1
- package/dist/bindings/openclaw.js +367 -5
- package/dist/bindings/openclaw.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/__tests__/docs.test.js +2 -1
- package/dist/docs/__tests__/docs.test.js.map +1 -1
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +730 -4
- package/dist/docs/content.js.map +1 -1
- package/dist/exporters/markdown.d.ts.map +1 -1
- package/dist/exporters/markdown.js +165 -0
- package/dist/exporters/markdown.js.map +1 -1
- package/dist/exporters/mermaid.d.ts.map +1 -1
- package/dist/exporters/mermaid.js +30 -0
- package/dist/exporters/mermaid.js.map +1 -1
- package/dist/import/__tests__/import.test.js +556 -2
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/claude-code.d.ts.map +1 -1
- package/dist/import/claude-code.js +2 -0
- package/dist/import/claude-code.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +7 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/openclaw.d.ts +70 -0
- package/dist/import/openclaw.d.ts.map +1 -0
- package/dist/import/openclaw.js +1060 -0
- package/dist/import/openclaw.js.map +1 -0
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +171 -4
- package/dist/import/serializer.js.map +1 -1
- package/dist/parser/__tests__/parser.test.js +463 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -1
- package/dist/parser/ast.d.ts +122 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/index.d.ts +14 -2
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +226 -5
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/validator.d.ts +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +214 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/visualizer/index.d.ts.map +1 -1
- package/dist/visualizer/index.js +104 -16
- package/dist/visualizer/index.js.map +1 -1
- package/examples/memory-system.at +162 -0
- package/examples/script-agent-pipeline.at +65 -0
- package/package.json +2 -1
- package/skill/SKILL.md +7 -7
- package/spec/grammar.md +345 -10
- package/spec/reserved-keywords.md +25 -1
- package/spec/validation.md +171 -2
|
@@ -48,6 +48,8 @@ function toTitle(id) {
|
|
|
48
48
|
* - item1
|
|
49
49
|
* - item2
|
|
50
50
|
*/
|
|
51
|
+
/** Characters that require quoting in YAML scalar values. */
|
|
52
|
+
const YAML_SPECIAL = /[:#\[\]{}&*!|>'"%@`]/;
|
|
51
53
|
function frontmatter(fields) {
|
|
52
54
|
const lines = ["---"];
|
|
53
55
|
for (const [key, value] of Object.entries(fields)) {
|
|
@@ -65,7 +67,10 @@ function frontmatter(fields) {
|
|
|
65
67
|
lines.push(`${key}: ${value}`);
|
|
66
68
|
}
|
|
67
69
|
else {
|
|
68
|
-
|
|
70
|
+
// Auto-quote strings containing YAML-special characters
|
|
71
|
+
const str = String(value);
|
|
72
|
+
const needsQuotes = YAML_SPECIAL.test(str) || str.trim() !== str;
|
|
73
|
+
lines.push(`${key}: ${needsQuotes ? `"${str.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : str}`);
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
lines.push("---");
|
|
@@ -93,10 +98,10 @@ function gitkeep(dirPath) {
|
|
|
93
98
|
*/
|
|
94
99
|
function mapPermissionMode(perm) {
|
|
95
100
|
// Claude Code native values — pass through
|
|
96
|
-
const nativeValues = ["plan", "
|
|
101
|
+
const nativeValues = ["plan", "default", "acceptEdits", "dontAsk", "bypassPermissions"];
|
|
97
102
|
if (nativeValues.includes(perm)) {
|
|
98
|
-
// "
|
|
99
|
-
if (perm === "
|
|
103
|
+
// "default" is the default, omit it
|
|
104
|
+
if (perm === "default")
|
|
100
105
|
return undefined;
|
|
101
106
|
return perm;
|
|
102
107
|
}
|
|
@@ -106,8 +111,10 @@ function mapPermissionMode(perm) {
|
|
|
106
111
|
return "plan";
|
|
107
112
|
case "autonomous":
|
|
108
113
|
return undefined; // default, omit
|
|
114
|
+
case "auto":
|
|
115
|
+
return undefined; // default, omit
|
|
109
116
|
case "interactive":
|
|
110
|
-
return "
|
|
117
|
+
return "acceptEdits";
|
|
111
118
|
case "unrestricted":
|
|
112
119
|
return "bypassPermissions";
|
|
113
120
|
default:
|
|
@@ -229,7 +236,7 @@ function generateAgents(ast) {
|
|
|
229
236
|
if (agent.invocation !== "manual") {
|
|
230
237
|
const desc = agent.description ?? ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
|
|
231
238
|
if (desc) {
|
|
232
|
-
fm.description =
|
|
239
|
+
fm.description = desc;
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
242
|
if (agent.model)
|
|
@@ -237,9 +244,9 @@ function generateAgents(ast) {
|
|
|
237
244
|
if (agent.maxTurns != null)
|
|
238
245
|
fm.maxTurns = String(agent.maxTurns);
|
|
239
246
|
if (agent.tools && agent.tools.length > 0)
|
|
240
|
-
fm.tools = agent.tools.map(convertMcpToolName);
|
|
247
|
+
fm.tools = agent.tools.map(convertMcpToolName).join(", ");
|
|
241
248
|
if (agent.disallowedTools && agent.disallowedTools.length > 0)
|
|
242
|
-
fm.disallowedTools = agent.disallowedTools.map(convertMcpToolName);
|
|
249
|
+
fm.disallowedTools = agent.disallowedTools.map(convertMcpToolName).join(", ");
|
|
243
250
|
if (agent.mcpServers && agent.mcpServers.length > 0)
|
|
244
251
|
fm.mcpServers = agent.mcpServers;
|
|
245
252
|
if (agent.background === true)
|
|
@@ -352,6 +359,103 @@ function generateAgents(ast) {
|
|
|
352
359
|
}
|
|
353
360
|
sections.push("");
|
|
354
361
|
}
|
|
362
|
+
// Memory stores — rich instructions with store details and access methods
|
|
363
|
+
if (agent.memory && agent.memory.length > 0) {
|
|
364
|
+
const storeMap = new Map((ast.stores ?? []).map(s => [s.id, s]));
|
|
365
|
+
sections.push("## Memory Stores");
|
|
366
|
+
sections.push("");
|
|
367
|
+
sections.push("You have access to the following memory stores. Use them to persist and retrieve knowledge across conversations.");
|
|
368
|
+
sections.push("");
|
|
369
|
+
for (const storeId of agent.memory) {
|
|
370
|
+
const store = storeMap.get(storeId);
|
|
371
|
+
if (!store) {
|
|
372
|
+
sections.push(`### ${storeId}`);
|
|
373
|
+
sections.push(`- Store not found in topology`);
|
|
374
|
+
sections.push("");
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
sections.push(`### ${storeId}`);
|
|
378
|
+
if (store.description)
|
|
379
|
+
sections.push(store.description);
|
|
380
|
+
sections.push("");
|
|
381
|
+
sections.push(`- **Type**: ${store.type}`);
|
|
382
|
+
sections.push(`- **Backend**: ${store.backend}`);
|
|
383
|
+
if (store.path)
|
|
384
|
+
sections.push(`- **Path**: \`${store.path}\``);
|
|
385
|
+
if (store.scope)
|
|
386
|
+
sections.push(`- **Scope**: ${store.scope}`);
|
|
387
|
+
// Access method — MCP or skill-based
|
|
388
|
+
const hasMcp = STORE_MCP_COMMANDS[store.backend] != null;
|
|
389
|
+
if (hasMcp) {
|
|
390
|
+
sections.push(`- **Access**: MCP server \`${storeId}\` (auto-configured in .mcp.json)`);
|
|
391
|
+
}
|
|
392
|
+
else if (store.path) {
|
|
393
|
+
sections.push(`- **Access**: Direct file access at \`${store.path}\``);
|
|
394
|
+
}
|
|
395
|
+
// Embedding config
|
|
396
|
+
if (store.embedding) {
|
|
397
|
+
sections.push(`- **Embedding**: ${store.embedding.model} (${store.embedding.provider}, ${store.embedding.dimensions}d)`);
|
|
398
|
+
}
|
|
399
|
+
// Search strategy
|
|
400
|
+
if (store.search) {
|
|
401
|
+
const parts = [`strategy: ${store.search.strategy}`];
|
|
402
|
+
if (store.search.topK)
|
|
403
|
+
parts.push(`top-k: ${store.search.topK}`);
|
|
404
|
+
if (store.search.rerank)
|
|
405
|
+
parts.push(`rerank: yes`);
|
|
406
|
+
sections.push(`- **Search**: ${parts.join(", ")}`);
|
|
407
|
+
}
|
|
408
|
+
// Ingestion sources
|
|
409
|
+
if (store.ingestion?.sources) {
|
|
410
|
+
sections.push(`- **Ingestion sources**: ${store.ingestion.sources.join(", ")}`);
|
|
411
|
+
}
|
|
412
|
+
// Lifecycle
|
|
413
|
+
if (store.lifecycle?.retention) {
|
|
414
|
+
sections.push(`- **Retention**: ${store.lifecycle.retention}`);
|
|
415
|
+
}
|
|
416
|
+
sections.push("");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Retrieval strategy — rich details
|
|
420
|
+
if (agent.retrieval) {
|
|
421
|
+
const retrieval = (ast.retrievals ?? []).find(r => r.id === agent.retrieval);
|
|
422
|
+
if (retrieval) {
|
|
423
|
+
sections.push("## Retrieval Strategy");
|
|
424
|
+
sections.push("");
|
|
425
|
+
sections.push(`**${retrieval.id}** — When you need to recall information, use this strategy:`);
|
|
426
|
+
sections.push("");
|
|
427
|
+
if (retrieval.sources)
|
|
428
|
+
sections.push(`- **Sources**: ${retrieval.sources.join(", ")}`);
|
|
429
|
+
if (retrieval.budget)
|
|
430
|
+
sections.push(`- **Token budget**: ${retrieval.budget} tokens max for retrieved context`);
|
|
431
|
+
if (retrieval.paths)
|
|
432
|
+
sections.push(`- **Search paths**: ${retrieval.paths.join(", ")}`);
|
|
433
|
+
if (retrieval.rerank)
|
|
434
|
+
sections.push(`- **Reranking**: enabled — results are cross-ranked for relevance`);
|
|
435
|
+
if (retrieval.diversity)
|
|
436
|
+
sections.push(`- **Diversity**: enabled — results span different sources`);
|
|
437
|
+
if (retrieval.cacheHitThreshold != null) {
|
|
438
|
+
sections.push(`- **Cache hit**: threshold ${retrieval.cacheHitThreshold} → ${retrieval.cacheHitAction ?? "short-circuit"}`);
|
|
439
|
+
}
|
|
440
|
+
if (retrieval.scoring) {
|
|
441
|
+
const s = retrieval.scoring;
|
|
442
|
+
const weights = [];
|
|
443
|
+
if (s.recencyWeight != null)
|
|
444
|
+
weights.push(`recency: ${s.recencyWeight}`);
|
|
445
|
+
if (s.semanticWeight != null)
|
|
446
|
+
weights.push(`semantic: ${s.semanticWeight}`);
|
|
447
|
+
if (s.importanceWeight != null)
|
|
448
|
+
weights.push(`importance: ${s.importanceWeight}`);
|
|
449
|
+
if (weights.length)
|
|
450
|
+
sections.push(`- **Scoring weights**: ${weights.join(", ")}`);
|
|
451
|
+
}
|
|
452
|
+
sections.push("");
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
sections.push(`Retrieval strategy: ${agent.retrieval}`);
|
|
456
|
+
sections.push("");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
355
459
|
// Outputs section
|
|
356
460
|
if (agent.outputs) {
|
|
357
461
|
sections.push("## Outputs");
|
|
@@ -739,7 +843,7 @@ function generateGroupNodes(ast) {
|
|
|
739
843
|
const skillFm = {};
|
|
740
844
|
skillFm.name = group.id;
|
|
741
845
|
if (group.description)
|
|
742
|
-
skillFm.description =
|
|
846
|
+
skillFm.description = group.description;
|
|
743
847
|
skillFm["user-invocable"] = true;
|
|
744
848
|
const skillSections = [];
|
|
745
849
|
skillSections.push(frontmatter(skillFm));
|
|
@@ -756,7 +860,7 @@ function generateGroupNodes(ast) {
|
|
|
756
860
|
const agentFm = {};
|
|
757
861
|
agentFm.name = group.id;
|
|
758
862
|
if (group.description)
|
|
759
|
-
agentFm.description =
|
|
863
|
+
agentFm.description = group.description;
|
|
760
864
|
const agentSections = [];
|
|
761
865
|
agentSections.push(frontmatter(agentFm));
|
|
762
866
|
agentSections.push("");
|
|
@@ -782,9 +886,9 @@ function generateTopologySkill(ast) {
|
|
|
782
886
|
const fm = {};
|
|
783
887
|
fm.name = name;
|
|
784
888
|
if (ast.topology.description) {
|
|
785
|
-
fm.description =
|
|
889
|
+
fm.description = ast.topology.description;
|
|
786
890
|
}
|
|
787
|
-
fm.version =
|
|
891
|
+
fm.version = ast.topology.version;
|
|
788
892
|
fm.topology = name;
|
|
789
893
|
if (ast.topology.patterns.length > 0) {
|
|
790
894
|
fm.patterns = ast.topology.patterns;
|
|
@@ -808,7 +912,7 @@ function generateTopologySkill(ast) {
|
|
|
808
912
|
if (mainSkill.agent)
|
|
809
913
|
fm.agent = mainSkill.agent;
|
|
810
914
|
if (mainSkill.allowedTools && mainSkill.allowedTools.length > 0) {
|
|
811
|
-
fm["allowed-tools"] = mainSkill.allowedTools.map(convertMcpToolName);
|
|
915
|
+
fm["allowed-tools"] = mainSkill.allowedTools.map(convertMcpToolName).join(", ");
|
|
812
916
|
}
|
|
813
917
|
}
|
|
814
918
|
const sections = [];
|
|
@@ -1085,6 +1189,30 @@ function generateTopologySkill(ast) {
|
|
|
1085
1189
|
sections.push("");
|
|
1086
1190
|
}
|
|
1087
1191
|
}
|
|
1192
|
+
// Memory Stores
|
|
1193
|
+
const stores = ast.stores ?? [];
|
|
1194
|
+
if (stores.length > 0) {
|
|
1195
|
+
sections.push("## Memory Stores");
|
|
1196
|
+
sections.push("");
|
|
1197
|
+
for (const store of stores) {
|
|
1198
|
+
sections.push(`### ${store.id}`);
|
|
1199
|
+
sections.push(`- Type: ${store.type}`);
|
|
1200
|
+
sections.push(`- Backend: ${store.backend}`);
|
|
1201
|
+
if (store.path)
|
|
1202
|
+
sections.push(`- Path: ${store.path}`);
|
|
1203
|
+
if (store.connection)
|
|
1204
|
+
sections.push(`- Connection: ${store.connection}`);
|
|
1205
|
+
if (store.scope)
|
|
1206
|
+
sections.push(`- Scope: ${store.scope}`);
|
|
1207
|
+
if (store.search?.strategy)
|
|
1208
|
+
sections.push(`- Search: ${store.search.strategy}`);
|
|
1209
|
+
if (store.embedding?.model)
|
|
1210
|
+
sections.push(`- Embedding: ${store.embedding.model}`);
|
|
1211
|
+
if (store.lifecycle?.retention)
|
|
1212
|
+
sections.push(`- Retention: ${store.lifecycle.retention}`);
|
|
1213
|
+
sections.push("");
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1088
1216
|
files.push({
|
|
1089
1217
|
path: `.claude/skills/${name}/SKILL.md`,
|
|
1090
1218
|
content: sections.join("\n") + "\n",
|
|
@@ -1101,7 +1229,7 @@ function generateSkills(ast) {
|
|
|
1101
1229
|
const sfm = {};
|
|
1102
1230
|
sfm.name = skill.id;
|
|
1103
1231
|
if (skill.description)
|
|
1104
|
-
sfm.description =
|
|
1232
|
+
sfm.description = skill.description;
|
|
1105
1233
|
if (skill.disableModelInvocation != null) {
|
|
1106
1234
|
sfm["disable-model-invocation"] = skill.disableModelInvocation;
|
|
1107
1235
|
}
|
|
@@ -1113,7 +1241,7 @@ function generateSkills(ast) {
|
|
|
1113
1241
|
if (skill.agent)
|
|
1114
1242
|
sfm.agent = skill.agent;
|
|
1115
1243
|
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1116
|
-
sfm["allowed-tools"] = skill.allowedTools.map(convertMcpToolName);
|
|
1244
|
+
sfm["allowed-tools"] = skill.allowedTools.map(convertMcpToolName).join(", ");
|
|
1117
1245
|
}
|
|
1118
1246
|
// Only emit frontmatter if there are fields beyond just the name
|
|
1119
1247
|
if (Object.keys(sfm).length > 1) {
|
|
@@ -1188,6 +1316,109 @@ function generateMemory(ast) {
|
|
|
1188
1316
|
if (memory.workspace) {
|
|
1189
1317
|
files.push(gitkeep(`.claude/skills/${name}/runs`));
|
|
1190
1318
|
}
|
|
1319
|
+
// Store directory markers
|
|
1320
|
+
const stores = ast.stores ?? [];
|
|
1321
|
+
for (const store of stores) {
|
|
1322
|
+
files.push(gitkeep(`.claude/stores/${store.id}`));
|
|
1323
|
+
}
|
|
1324
|
+
return files;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Generate memory command files for each store.
|
|
1328
|
+
*
|
|
1329
|
+
* These are Claude Code slash-commands (`.claude/commands/`) that give agents
|
|
1330
|
+
* a concrete way to interact with memory stores — search, ingest, and inspect.
|
|
1331
|
+
* This works alongside (or instead of) MCP servers.
|
|
1332
|
+
*/
|
|
1333
|
+
function generateMemoryCommands(ast) {
|
|
1334
|
+
const files = [];
|
|
1335
|
+
const stores = ast.stores ?? [];
|
|
1336
|
+
for (const store of stores) {
|
|
1337
|
+
// Search command — for stores with search config or semantic/episodic type
|
|
1338
|
+
if (store.search || store.type === "semantic" || store.type === "episodic") {
|
|
1339
|
+
const hasMcp = STORE_MCP_COMMANDS[store.backend] != null;
|
|
1340
|
+
const searchLines = [
|
|
1341
|
+
"---",
|
|
1342
|
+
`description: "Search the ${store.id} memory store"`,
|
|
1343
|
+
"---",
|
|
1344
|
+
"",
|
|
1345
|
+
`# /memory-search-${store.id}`,
|
|
1346
|
+
"",
|
|
1347
|
+
`Search the **${store.id}** store for relevant information.`,
|
|
1348
|
+
"",
|
|
1349
|
+
`## Store Details`,
|
|
1350
|
+
`- **Type**: ${store.type}`,
|
|
1351
|
+
`- **Backend**: ${store.backend}`,
|
|
1352
|
+
];
|
|
1353
|
+
if (store.path)
|
|
1354
|
+
searchLines.push(`- **Path**: \`${store.path}\``);
|
|
1355
|
+
if (store.search) {
|
|
1356
|
+
searchLines.push(`- **Strategy**: ${store.search.strategy}`);
|
|
1357
|
+
if (store.search.topK)
|
|
1358
|
+
searchLines.push(`- **Top-K**: ${store.search.topK}`);
|
|
1359
|
+
if (store.search.rerank)
|
|
1360
|
+
searchLines.push(`- **Reranking**: enabled`);
|
|
1361
|
+
}
|
|
1362
|
+
if (store.embedding) {
|
|
1363
|
+
searchLines.push(`- **Embedding**: ${store.embedding.model} (${store.embedding.provider}, ${store.embedding.dimensions}d)`);
|
|
1364
|
+
}
|
|
1365
|
+
searchLines.push("");
|
|
1366
|
+
searchLines.push("## Instructions");
|
|
1367
|
+
searchLines.push("");
|
|
1368
|
+
if (hasMcp) {
|
|
1369
|
+
searchLines.push(`Use the \`${store.id}\` MCP server to search this store.`);
|
|
1370
|
+
searchLines.push(`The MCP server is configured in \`.mcp.json\` and provides tools for querying the ${store.backend} database.`);
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
searchLines.push(`This store uses ${store.backend} at \`${store.path ?? ".claude/stores/" + store.id}\`.`);
|
|
1374
|
+
searchLines.push(`Read the database directly or use an appropriate library to query it.`);
|
|
1375
|
+
}
|
|
1376
|
+
searchLines.push("");
|
|
1377
|
+
searchLines.push("Search for: $ARGUMENTS");
|
|
1378
|
+
searchLines.push("");
|
|
1379
|
+
files.push({
|
|
1380
|
+
path: `.claude/commands/memory-search-${store.id}.md`,
|
|
1381
|
+
content: searchLines.join("\n"),
|
|
1382
|
+
category: "machine",
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
// Ingest command — for stores with ingestion config
|
|
1386
|
+
if (store.ingestion) {
|
|
1387
|
+
const ingestLines = [
|
|
1388
|
+
"---",
|
|
1389
|
+
`description: "Ingest documents into the ${store.id} memory store"`,
|
|
1390
|
+
"---",
|
|
1391
|
+
"",
|
|
1392
|
+
`# /memory-ingest-${store.id}`,
|
|
1393
|
+
"",
|
|
1394
|
+
`Ingest documents into the **${store.id}** store.`,
|
|
1395
|
+
"",
|
|
1396
|
+
"## Ingestion Config",
|
|
1397
|
+
`- **Sources**: ${store.ingestion.sources?.join(", ") ?? "not specified"}`,
|
|
1398
|
+
`- **Chunking**: ${store.ingestion.chunking ?? "recursive"}`,
|
|
1399
|
+
];
|
|
1400
|
+
if (store.ingestion.chunkSize)
|
|
1401
|
+
ingestLines.push(`- **Chunk size**: ${store.ingestion.chunkSize}`);
|
|
1402
|
+
if (store.ingestion.overlap)
|
|
1403
|
+
ingestLines.push(`- **Overlap**: ${store.ingestion.overlap}`);
|
|
1404
|
+
if (store.embedding) {
|
|
1405
|
+
ingestLines.push(`- **Embedding**: ${store.embedding.model} (${store.embedding.provider}, ${store.embedding.dimensions}d)`);
|
|
1406
|
+
}
|
|
1407
|
+
ingestLines.push("");
|
|
1408
|
+
ingestLines.push("## Instructions");
|
|
1409
|
+
ingestLines.push("");
|
|
1410
|
+
ingestLines.push(`1. Read files from the configured sources: ${store.ingestion.sources?.join(", ") ?? "docs/"}`);
|
|
1411
|
+
ingestLines.push(`2. Split content into chunks (${store.ingestion.chunking ?? "recursive"}, size: ${store.ingestion.chunkSize ?? 512})`);
|
|
1412
|
+
ingestLines.push(`3. Generate embeddings using ${store.embedding?.model ?? "the configured model"}`);
|
|
1413
|
+
ingestLines.push(`4. Insert into ${store.backend} at \`${store.path ?? ".claude/stores/" + store.id}\``);
|
|
1414
|
+
ingestLines.push("");
|
|
1415
|
+
files.push({
|
|
1416
|
+
path: `.claude/commands/memory-ingest-${store.id}.md`,
|
|
1417
|
+
content: ingestLines.join("\n"),
|
|
1418
|
+
category: "machine",
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1191
1422
|
return files;
|
|
1192
1423
|
}
|
|
1193
1424
|
/** Generate gate script stubs. */
|
|
@@ -1210,7 +1441,7 @@ function generateGateScripts(ast) {
|
|
|
1210
1441
|
}
|
|
1211
1442
|
return files;
|
|
1212
1443
|
}
|
|
1213
|
-
/** Generate gate wrapper scripts that
|
|
1444
|
+
/** Generate gate wrapper scripts that run validation when agents complete. */
|
|
1214
1445
|
function generateGateWrapperScripts(ast) {
|
|
1215
1446
|
const files = [];
|
|
1216
1447
|
const name = ast.topology.name;
|
|
@@ -1223,11 +1454,13 @@ function generateGateWrapperScripts(ast) {
|
|
|
1223
1454
|
if (gate.behavior === "advisory")
|
|
1224
1455
|
continue;
|
|
1225
1456
|
const gateScriptName = gate.run.replace(/^.*\//, "").replace(/\s.*$/, "");
|
|
1226
|
-
const onFailExit = gate.onFail === "halt"
|
|
1457
|
+
const onFailExit = gate.onFail === "halt"
|
|
1458
|
+
? `echo "Gate '${gate.id}' validation failed. Fix issues before proceeding." >&2\n exit 2`
|
|
1459
|
+
: `echo "Gate '${gate.id}' failed — bounce back and fix the issues." >&2\n exit 2`;
|
|
1227
1460
|
const contentLines = [
|
|
1228
1461
|
"#!/usr/bin/env bash",
|
|
1229
1462
|
`# Gate wrapper: ${gate.id}`,
|
|
1230
|
-
`# Enforces gate "${gate.id}" on
|
|
1463
|
+
`# Enforces gate "${gate.id}" on SubagentStop (after: ${gate.after || "any"}).`,
|
|
1231
1464
|
`# Auto-generated by agentopology scaffold — edit as needed.`,
|
|
1232
1465
|
"set -euo pipefail",
|
|
1233
1466
|
"",
|
|
@@ -1377,14 +1610,17 @@ function generateSettings(ast) {
|
|
|
1377
1610
|
// Per-agent hooks are now rendered in AGENT.md frontmatter (not in settings.json).
|
|
1378
1611
|
// This is the Claude Code native approach: hooks in the agent file are scoped
|
|
1379
1612
|
// to that agent automatically.
|
|
1380
|
-
// Gate enforcement — compile gates with run: to
|
|
1613
|
+
// Gate enforcement — compile gates with run: to SubagentStop hooks
|
|
1614
|
+
// Gates fire AFTER an agent finishes (SubagentStop), not before a tool runs.
|
|
1615
|
+
// The matcher is the gate's `after:` agent name so the hook fires when that
|
|
1616
|
+
// specific agent completes.
|
|
1381
1617
|
const gates = ast.nodes.filter((n) => n.type === "gate");
|
|
1382
1618
|
for (const gate of gates) {
|
|
1383
1619
|
if (!gate.run)
|
|
1384
1620
|
continue;
|
|
1385
1621
|
if (gate.behavior === "advisory")
|
|
1386
1622
|
continue; // advisory gates stay in markdown only
|
|
1387
|
-
const eventName = "
|
|
1623
|
+
const eventName = "SubagentStop";
|
|
1388
1624
|
if (!hooksByEvent[eventName])
|
|
1389
1625
|
hooksByEvent[eventName] = [];
|
|
1390
1626
|
const scriptName = `gate-${gate.id}.sh`;
|
|
@@ -1396,8 +1632,12 @@ function generateSettings(ast) {
|
|
|
1396
1632
|
command,
|
|
1397
1633
|
},
|
|
1398
1634
|
],
|
|
1399
|
-
matcher: "Task", // Gates fire on Task tool (agent spawning)
|
|
1400
1635
|
};
|
|
1636
|
+
// Use the gate's after: agent as matcher so the hook only fires
|
|
1637
|
+
// when that specific agent completes. Without after:, match all agents.
|
|
1638
|
+
if (gate.after) {
|
|
1639
|
+
hookEntry.matcher = gate.after;
|
|
1640
|
+
}
|
|
1401
1641
|
hooksByEvent[eventName].push(hookEntry);
|
|
1402
1642
|
}
|
|
1403
1643
|
// Write hooksByEvent to settings if any hooks or gates were registered
|
|
@@ -1409,66 +1649,21 @@ function generateSettings(ast) {
|
|
|
1409
1649
|
if (envSettings) {
|
|
1410
1650
|
settings.env = envSettings;
|
|
1411
1651
|
}
|
|
1412
|
-
// Permissions section from settings block
|
|
1652
|
+
// Permissions section from settings block — nested under "permissions" key
|
|
1413
1653
|
const perms = ast.settings;
|
|
1414
1654
|
if (perms) {
|
|
1415
1655
|
const allow = perms.allow;
|
|
1416
1656
|
const deny = perms.deny;
|
|
1417
|
-
const
|
|
1657
|
+
const permObj = {};
|
|
1418
1658
|
if (allow && allow.length > 0)
|
|
1419
|
-
|
|
1659
|
+
permObj.allow = allow;
|
|
1420
1660
|
if (deny && deny.length > 0)
|
|
1421
|
-
|
|
1422
|
-
if (
|
|
1423
|
-
settings.
|
|
1424
|
-
}
|
|
1425
|
-
// Defaults documentation
|
|
1426
|
-
if (ast.defaults) {
|
|
1427
|
-
const d = ast.defaults;
|
|
1428
|
-
const defaultsDoc = {};
|
|
1429
|
-
if (d.temperature != null)
|
|
1430
|
-
defaultsDoc.temperature = d.temperature;
|
|
1431
|
-
if (d.maxTokens != null)
|
|
1432
|
-
defaultsDoc.maxTokens = d.maxTokens;
|
|
1433
|
-
if (d.topP != null)
|
|
1434
|
-
defaultsDoc.topP = d.topP;
|
|
1435
|
-
if (d.topK != null)
|
|
1436
|
-
defaultsDoc.topK = d.topK;
|
|
1437
|
-
if (d.stop)
|
|
1438
|
-
defaultsDoc.stop = d.stop;
|
|
1439
|
-
if (d.seed != null)
|
|
1440
|
-
defaultsDoc.seed = d.seed;
|
|
1441
|
-
if (d.thinking)
|
|
1442
|
-
defaultsDoc.thinking = d.thinking;
|
|
1443
|
-
if (d.thinkingBudget != null)
|
|
1444
|
-
defaultsDoc.thinkingBudget = d.thinkingBudget;
|
|
1445
|
-
if (d.outputFormat)
|
|
1446
|
-
defaultsDoc.outputFormat = d.outputFormat;
|
|
1447
|
-
if (d.timeout)
|
|
1448
|
-
defaultsDoc.timeout = d.timeout;
|
|
1449
|
-
if (d.logLevel)
|
|
1450
|
-
defaultsDoc.logLevel = d.logLevel;
|
|
1451
|
-
if (Object.keys(defaultsDoc).length > 0) {
|
|
1452
|
-
settings._defaults = defaultsDoc;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
// Observability documentation
|
|
1456
|
-
if (ast.observability) {
|
|
1457
|
-
const o = ast.observability;
|
|
1458
|
-
const obsDoc = {
|
|
1459
|
-
enabled: o.enabled,
|
|
1460
|
-
level: o.level,
|
|
1461
|
-
exporter: o.exporter,
|
|
1462
|
-
sampleRate: o.sampleRate,
|
|
1463
|
-
};
|
|
1464
|
-
if (o.endpoint)
|
|
1465
|
-
obsDoc.endpoint = o.endpoint;
|
|
1466
|
-
if (o.service)
|
|
1467
|
-
obsDoc.service = o.service;
|
|
1468
|
-
obsDoc.capture = o.capture;
|
|
1469
|
-
obsDoc.spans = o.spans;
|
|
1470
|
-
settings._observability = obsDoc;
|
|
1661
|
+
permObj.deny = deny;
|
|
1662
|
+
if (Object.keys(permObj).length > 0)
|
|
1663
|
+
settings.permissions = permObj;
|
|
1471
1664
|
}
|
|
1665
|
+
// Defaults and observability are documented in the CLAUDE.md context file,
|
|
1666
|
+
// not in settings.json (Claude Code doesn't recognize these keys).
|
|
1472
1667
|
// Top-level extensions for claude-code
|
|
1473
1668
|
if (ast.extensions?.["claude-code"]) {
|
|
1474
1669
|
for (const [k, v] of Object.entries(ast.extensions["claude-code"])) {
|
|
@@ -1483,9 +1678,34 @@ function generateSettings(ast) {
|
|
|
1483
1678
|
category: "shared-config",
|
|
1484
1679
|
};
|
|
1485
1680
|
}
|
|
1486
|
-
/**
|
|
1681
|
+
/**
|
|
1682
|
+
* Backend → MCP server mapping.
|
|
1683
|
+
*
|
|
1684
|
+
* Only packages verified to exist on npm or PyPI are included.
|
|
1685
|
+
* npm packages use `npx -y`, Python packages use `uvx`.
|
|
1686
|
+
* `pathFlag` controls how the DB path is passed (positional, --db, --db-path, etc.).
|
|
1687
|
+
*
|
|
1688
|
+
* Verified 2026-03-21:
|
|
1689
|
+
* npm: kuzu-mcp-server@0.1.0, mongodb-mcp-server@1.8.1
|
|
1690
|
+
* pip: mcp-server-sqlite@2025.4.25, mcp-server-qdrant@0.8.1,
|
|
1691
|
+
* mcp-server-neo4j@0.1.8, chroma-mcp@0.2.6
|
|
1692
|
+
*/
|
|
1693
|
+
const STORE_MCP_COMMANDS = {
|
|
1694
|
+
// npm packages
|
|
1695
|
+
"kuzu": { command: "npx", args: ["-y", "kuzu-mcp-server"] }, // positional
|
|
1696
|
+
"mongodb": { command: "npx", args: ["-y", "mongodb-mcp-server"], pathFlag: "--uri" },
|
|
1697
|
+
// Python MCP servers (via uvx)
|
|
1698
|
+
"sqlite": { command: "uvx", args: ["mcp-server-sqlite"], pathFlag: "--db-path" },
|
|
1699
|
+
"sqlite-vec": { command: "uvx", args: ["mcp-server-sqlite"], pathFlag: "--db-path" },
|
|
1700
|
+
"qdrant": { command: "uvx", args: ["mcp-server-qdrant"], pathFlag: "--qdrant-url" },
|
|
1701
|
+
"neo4j": { command: "uvx", args: ["mcp-server-neo4j"], pathFlag: "--db-uri" },
|
|
1702
|
+
"chroma": { command: "uvx", args: ["chroma-mcp"], pathFlag: "--host" },
|
|
1703
|
+
// No verified MCP servers: lancedb, pinecone, pgvector, falkordb
|
|
1704
|
+
// These backends use skill-based access (slash commands) instead
|
|
1705
|
+
};
|
|
1487
1706
|
function generateMcpJson(ast) {
|
|
1488
|
-
|
|
1707
|
+
const stores = ast.stores ?? [];
|
|
1708
|
+
if (Object.keys(ast.mcpServers).length === 0 && stores.length === 0)
|
|
1489
1709
|
return null;
|
|
1490
1710
|
const mcpConfig = { mcpServers: {} };
|
|
1491
1711
|
const servers = mcpConfig.mcpServers;
|
|
@@ -1512,6 +1732,36 @@ function generateMcpJson(ast) {
|
|
|
1512
1732
|
}
|
|
1513
1733
|
servers[name] = entry;
|
|
1514
1734
|
}
|
|
1735
|
+
// Merge memory store MCP server entries
|
|
1736
|
+
for (const store of stores) {
|
|
1737
|
+
const mcpCmd = STORE_MCP_COMMANDS[store.backend];
|
|
1738
|
+
if (!mcpCmd) {
|
|
1739
|
+
// Backend not in the known MCP map — skip (skill-based access only)
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
const args = [...mcpCmd.args];
|
|
1743
|
+
const storePath = store.path ?? `.claude/stores/${store.id}`;
|
|
1744
|
+
// Each server has its own CLI convention for the DB path
|
|
1745
|
+
if (mcpCmd.pathFlag) {
|
|
1746
|
+
args.push(mcpCmd.pathFlag, storePath);
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
// No pathFlag = positional argument (e.g., kuzu-mcp-server ./path)
|
|
1750
|
+
args.push(storePath);
|
|
1751
|
+
}
|
|
1752
|
+
const env = {};
|
|
1753
|
+
if (store.connection) {
|
|
1754
|
+
env.CONNECTION_STRING = store.connection;
|
|
1755
|
+
}
|
|
1756
|
+
servers[store.id] = {
|
|
1757
|
+
command: mcpCmd.command,
|
|
1758
|
+
args,
|
|
1759
|
+
env,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
// If no servers were added (all stores had unsupported backends), return null
|
|
1763
|
+
if (Object.keys(servers).length === 0)
|
|
1764
|
+
return null;
|
|
1515
1765
|
return {
|
|
1516
1766
|
path: ".mcp.json",
|
|
1517
1767
|
content: JSON.stringify(mcpConfig, null, 2) + "\n",
|
|
@@ -1676,21 +1926,23 @@ function generateCommandFiles(ast) {
|
|
|
1676
1926
|
// Frontmatter
|
|
1677
1927
|
const fm = {};
|
|
1678
1928
|
if (ast.topology.description) {
|
|
1679
|
-
fm.description =
|
|
1929
|
+
fm.description = ast.topology.description;
|
|
1930
|
+
}
|
|
1931
|
+
if (trigger.argument) {
|
|
1932
|
+
fm["argument-hint"] = `[${trigger.argument}]`;
|
|
1680
1933
|
}
|
|
1681
1934
|
sections.push(frontmatter(fm));
|
|
1682
1935
|
sections.push("");
|
|
1683
|
-
// Header
|
|
1936
|
+
// Header — use $ARGUMENTS for Claude Code command argument substitution
|
|
1684
1937
|
sections.push(`# /${trigger.name}`);
|
|
1685
1938
|
sections.push("");
|
|
1686
|
-
sections.push(trigger.pattern);
|
|
1687
|
-
sections.push("");
|
|
1688
|
-
// Arguments
|
|
1689
1939
|
if (trigger.argument) {
|
|
1690
|
-
sections.push(
|
|
1691
|
-
sections.push(`- ${trigger.argument}: extracted from the command pattern`);
|
|
1692
|
-
sections.push("");
|
|
1940
|
+
sections.push(`/${trigger.name} $ARGUMENTS`);
|
|
1693
1941
|
}
|
|
1942
|
+
else {
|
|
1943
|
+
sections.push(trigger.pattern);
|
|
1944
|
+
}
|
|
1945
|
+
sections.push("");
|
|
1694
1946
|
// Pipeline (from edges, enhanced)
|
|
1695
1947
|
if (ast.edges.length > 0) {
|
|
1696
1948
|
sections.push("## Pipeline");
|
|
@@ -1797,7 +2049,7 @@ function generateWorkspaceProtocol(ast) {
|
|
|
1797
2049
|
* destroy their content.
|
|
1798
2050
|
*/
|
|
1799
2051
|
function generateContextFile(ast) {
|
|
1800
|
-
const DEFAULT_PATH = ".claude/
|
|
2052
|
+
const DEFAULT_PATH = ".claude/CLAUDE.md";
|
|
1801
2053
|
let fileName = ast.context.file ?? DEFAULT_PATH;
|
|
1802
2054
|
// Safety: never overwrite the user's root-level CLAUDE.md
|
|
1803
2055
|
if (fileName === "CLAUDE.md" || fileName === "./CLAUDE.md") {
|
|
@@ -2026,6 +2278,8 @@ export const claudeCodeBinding = {
|
|
|
2026
2278
|
files.push(...generateSchemas(ast));
|
|
2027
2279
|
// Memory directories
|
|
2028
2280
|
files.push(...generateMemory(ast));
|
|
2281
|
+
// Memory store command files (search/ingest per store)
|
|
2282
|
+
files.push(...generateMemoryCommands(ast));
|
|
2029
2283
|
// Gate scripts
|
|
2030
2284
|
files.push(...generateGateScripts(ast));
|
|
2031
2285
|
files.push(...generateGateWrapperScripts(ast));
|