@dot-agent/compiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/chunk-IT25ILTQ.js +180 -0
- package/dist/chunk-IT25ILTQ.js.map +1 -0
- package/dist/core-DLZGpzev.d.cts +161 -0
- package/dist/core-DLZGpzev.d.ts +161 -0
- package/dist/core.cjs +224 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +23 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1059 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +831 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
aboutmeToJson: () => aboutmeToJson,
|
|
34
|
+
buildAboutme: () => buildAboutme,
|
|
35
|
+
buildId: () => buildId,
|
|
36
|
+
collectFiles: () => collectFiles,
|
|
37
|
+
createLinter: () => createLinter,
|
|
38
|
+
createZip: () => createZip,
|
|
39
|
+
extractDigest: () => extractDigest,
|
|
40
|
+
extractFiles: () => extractFiles,
|
|
41
|
+
extractName: () => extractName,
|
|
42
|
+
getBehaviorScxml: () => getBehaviorScxml,
|
|
43
|
+
getContextNode: () => getContextNode,
|
|
44
|
+
initBehaviorParser: () => initBehaviorParser,
|
|
45
|
+
initParsers: () => initParsers,
|
|
46
|
+
lintBehavior: () => lintBehavior,
|
|
47
|
+
lintDescription: () => lintDescription,
|
|
48
|
+
nodeAtOffset: () => nodeAtOffset,
|
|
49
|
+
nodeToRange: () => nodeToRange,
|
|
50
|
+
nodesOfType: () => nodesOfType,
|
|
51
|
+
pack: () => pack,
|
|
52
|
+
parse: () => parse,
|
|
53
|
+
parseAboutme: () => parseAboutme,
|
|
54
|
+
parseBehaviorFile: () => parseBehaviorFile,
|
|
55
|
+
parseDescriptionFile: () => parseDescriptionFile,
|
|
56
|
+
parseId: () => parseId,
|
|
57
|
+
parseSync: () => parseSync,
|
|
58
|
+
positionToOffset: () => positionToOffset,
|
|
59
|
+
readZip: () => readZip,
|
|
60
|
+
validateMagicBytes: () => validateMagicBytes,
|
|
61
|
+
validateZipBomb: () => validateZipBomb,
|
|
62
|
+
writeZip: () => writeZip
|
|
63
|
+
});
|
|
64
|
+
module.exports = __toCommonJS(index_exports);
|
|
65
|
+
|
|
66
|
+
// ../../node_modules/tsup/assets/cjs_shims.js
|
|
67
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
68
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
69
|
+
|
|
70
|
+
// src/parser.ts
|
|
71
|
+
var import_module = require("module");
|
|
72
|
+
var import_web_tree_sitter = require("web-tree-sitter");
|
|
73
|
+
var import_parser_dsl = __toESM(require("@dot-agent/parser-dsl"), 1);
|
|
74
|
+
var require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
75
|
+
var { descriptionWasmPath, behaviorWasmPath } = require2("@dot-agent/tree-sitter");
|
|
76
|
+
var _initialized = false;
|
|
77
|
+
var _descriptionParser;
|
|
78
|
+
var _behaviorParser;
|
|
79
|
+
var _bpInitialized = false;
|
|
80
|
+
async function initBehaviorParser() {
|
|
81
|
+
if (_bpInitialized) return;
|
|
82
|
+
await (0, import_parser_dsl.default)();
|
|
83
|
+
_bpInitialized = true;
|
|
84
|
+
}
|
|
85
|
+
function parseBehaviorFile(text) {
|
|
86
|
+
return JSON.parse((0, import_parser_dsl.parse_behavior)(text));
|
|
87
|
+
}
|
|
88
|
+
function parseDescriptionFile(text) {
|
|
89
|
+
return JSON.parse((0, import_parser_dsl.parse_description)(text));
|
|
90
|
+
}
|
|
91
|
+
function getBehaviorScxml(text) {
|
|
92
|
+
return (0, import_parser_dsl.get_graph)(text);
|
|
93
|
+
}
|
|
94
|
+
async function initParsers() {
|
|
95
|
+
if (_initialized) return;
|
|
96
|
+
await import_web_tree_sitter.Parser.init();
|
|
97
|
+
const descriptionLang = await import_web_tree_sitter.Language.load(descriptionWasmPath);
|
|
98
|
+
const behaviorLang = await import_web_tree_sitter.Language.load(behaviorWasmPath);
|
|
99
|
+
_descriptionParser = new import_web_tree_sitter.Parser();
|
|
100
|
+
_descriptionParser.setLanguage(descriptionLang);
|
|
101
|
+
_behaviorParser = new import_web_tree_sitter.Parser();
|
|
102
|
+
_behaviorParser.setLanguage(behaviorLang);
|
|
103
|
+
_initialized = true;
|
|
104
|
+
}
|
|
105
|
+
async function parse(langId, text, previousTree) {
|
|
106
|
+
await initParsers();
|
|
107
|
+
const parser = langId === "behavior" ? _behaviorParser : _descriptionParser;
|
|
108
|
+
return parser.parse(text, previousTree);
|
|
109
|
+
}
|
|
110
|
+
function parseSync(langId, text, previousTree) {
|
|
111
|
+
if (!_initialized) return null;
|
|
112
|
+
const parser = langId === "behavior" ? _behaviorParser : _descriptionParser;
|
|
113
|
+
return parser.parse(text, previousTree);
|
|
114
|
+
}
|
|
115
|
+
function nodesOfType(tree, type) {
|
|
116
|
+
if (!tree) return [];
|
|
117
|
+
return tree.rootNode.descendantsOfType(type).filter((n) => n !== null);
|
|
118
|
+
}
|
|
119
|
+
function nodeAtOffset(tree, offset) {
|
|
120
|
+
if (!tree) return null;
|
|
121
|
+
return tree.rootNode.descendantForIndex(offset);
|
|
122
|
+
}
|
|
123
|
+
function nodeToRange(node) {
|
|
124
|
+
return {
|
|
125
|
+
start: { line: node.startPosition.row, character: node.startPosition.column },
|
|
126
|
+
end: { line: node.endPosition.row, character: node.endPosition.column }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function positionToOffset(text, line, character) {
|
|
130
|
+
let offset = 0;
|
|
131
|
+
for (let i = 0; i < line; i++) {
|
|
132
|
+
const nl = text.indexOf("\n", offset);
|
|
133
|
+
offset = nl === -1 ? text.length : nl + 1;
|
|
134
|
+
}
|
|
135
|
+
return offset + character;
|
|
136
|
+
}
|
|
137
|
+
function getContextNode(tree, offset) {
|
|
138
|
+
let node = nodeAtOffset(tree, offset);
|
|
139
|
+
while (node && (node.isError || node.isMissing)) {
|
|
140
|
+
node = node.parent;
|
|
141
|
+
}
|
|
142
|
+
if (!node || node.type === "ERROR") {
|
|
143
|
+
const raw = nodeAtOffset(tree, offset);
|
|
144
|
+
const parent = raw?.parent;
|
|
145
|
+
if (parent) {
|
|
146
|
+
const siblings = parent.children;
|
|
147
|
+
const idx = siblings.findIndex((c) => c !== null && c.startIndex > offset);
|
|
148
|
+
const prev = idx > 0 ? siblings[idx - 1] : siblings[siblings.length - 1];
|
|
149
|
+
if (prev && !prev.isError) node = prev;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return node ?? tree.rootNode;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/linter.ts
|
|
156
|
+
var import_fs = require("fs");
|
|
157
|
+
var import_path = require("path");
|
|
158
|
+
function nodePosition(node) {
|
|
159
|
+
return { line: node.startPosition.row + 1, col: node.startPosition.column + 1 };
|
|
160
|
+
}
|
|
161
|
+
var MISSING_HINTS = {
|
|
162
|
+
interact_stmt: "Missing 'interact' \u2014 oriented states require 'goal \u2026 interact \u2026 on intent \u2026 on offtopic'.",
|
|
163
|
+
offtopic_handler: "Missing 'on offtopic' handler \u2014 every state with 'interact' must handle the offtopic case.",
|
|
164
|
+
intent_handler: "Missing at least one 'on intent' handler after 'interact'.",
|
|
165
|
+
goal_stmt: "Missing 'goal' \u2014 every state with 'interact' must declare a goal.",
|
|
166
|
+
state_name: "Missing state name after 'state' keyword.",
|
|
167
|
+
quoted_string: "Missing quoted string \u2014 expected a value in double quotes."
|
|
168
|
+
};
|
|
169
|
+
function collectSyntaxErrors(root, messages, file) {
|
|
170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
171
|
+
function push(node, message, code, severity = "error") {
|
|
172
|
+
const key = `${node.startIndex}:${node.endIndex}:${message}`;
|
|
173
|
+
if (seen.has(key)) return;
|
|
174
|
+
seen.add(key);
|
|
175
|
+
const { line, col } = nodePosition(node);
|
|
176
|
+
messages.push({ file, line, col, severity, code, message });
|
|
177
|
+
}
|
|
178
|
+
function walk(node) {
|
|
179
|
+
if (node.isMissing) {
|
|
180
|
+
const hint = MISSING_HINTS[node.type];
|
|
181
|
+
push(node, hint ?? `Syntax error: missing '${node.type}'.`, "E004");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (node.type === "ERROR") {
|
|
185
|
+
const hasNestedError = node.descendantsOfType("ERROR").some((e) => e !== null && e.id !== node.id);
|
|
186
|
+
if (!hasNestedError) {
|
|
187
|
+
const snippet = node.text.replace(/\s+/g, " ").trim().slice(0, 40);
|
|
188
|
+
push(node, snippet ? `Syntax error near '${snippet}'.` : "Syntax error.", "E004");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const child of node.children) {
|
|
193
|
+
if (child) walk(child);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
walk(root);
|
|
197
|
+
}
|
|
198
|
+
var STRICT_BLOCK_TYPES = {
|
|
199
|
+
input_block: "input",
|
|
200
|
+
output_block: "output",
|
|
201
|
+
requires_block: "requires",
|
|
202
|
+
capabilities_block: "capabilities"
|
|
203
|
+
};
|
|
204
|
+
async function lintDescription(text, file = "agent.description") {
|
|
205
|
+
const tree = await parse("description", text);
|
|
206
|
+
const messages = [];
|
|
207
|
+
collectSyntaxErrors(tree.rootNode, messages, file);
|
|
208
|
+
for (const metaNode of nodesOfType(tree, "agent_meta")) {
|
|
209
|
+
const keyNode = metaNode.childForFieldName("key");
|
|
210
|
+
if (keyNode?.text !== "domain") continue;
|
|
211
|
+
const valueNode = metaNode.childForFieldName("value");
|
|
212
|
+
if (valueNode?.text === "example.com") {
|
|
213
|
+
const { line, col } = nodePosition(valueNode);
|
|
214
|
+
messages.push({
|
|
215
|
+
file,
|
|
216
|
+
line,
|
|
217
|
+
col,
|
|
218
|
+
severity: "warning",
|
|
219
|
+
code: "W003",
|
|
220
|
+
message: 'domain still has default value "example.com"'
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const hasDomain = nodesOfType(tree, "agent_meta").some(
|
|
225
|
+
(n) => n.childForFieldName("key")?.text === "domain" && n.childForFieldName("value")?.text?.trim()
|
|
226
|
+
);
|
|
227
|
+
if (!hasDomain) {
|
|
228
|
+
messages.push({
|
|
229
|
+
file,
|
|
230
|
+
line: 1,
|
|
231
|
+
col: 1,
|
|
232
|
+
severity: "warning",
|
|
233
|
+
code: "W007",
|
|
234
|
+
message: "No domain declared \u2014 agent will be packaged as unknown/name. Interoperability between runtimes is not guaranteed."
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const declaredTypes = new Set(
|
|
238
|
+
nodesOfType(tree, "type_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean)
|
|
239
|
+
);
|
|
240
|
+
if (declaredTypes.size > 0) {
|
|
241
|
+
for (const blockType of Object.keys(STRICT_BLOCK_TYPES)) {
|
|
242
|
+
for (const blockNode of nodesOfType(tree, blockType)) {
|
|
243
|
+
for (const typeRefNode of blockNode.descendantsOfType("type_reference").filter((n) => n !== null)) {
|
|
244
|
+
const idNode = typeRefNode.firstNamedChild;
|
|
245
|
+
if (!idNode) continue;
|
|
246
|
+
const typeName = idNode.text;
|
|
247
|
+
if (!declaredTypes.has(typeName)) {
|
|
248
|
+
const { line, col } = nodePosition(idNode);
|
|
249
|
+
messages.push({
|
|
250
|
+
file,
|
|
251
|
+
line,
|
|
252
|
+
col,
|
|
253
|
+
severity: "warning",
|
|
254
|
+
code: "W004",
|
|
255
|
+
message: `Type '${typeName}' is not declared in this file (assuming native or external).`
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return messages;
|
|
263
|
+
}
|
|
264
|
+
function levenshtein(a, b) {
|
|
265
|
+
const la = a.length, lb = b.length;
|
|
266
|
+
const dp = Array.from(
|
|
267
|
+
{ length: la + 1 },
|
|
268
|
+
(_, i) => Array.from({ length: lb + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
269
|
+
);
|
|
270
|
+
for (let i = 1; i <= la; i++) {
|
|
271
|
+
for (let j = 1; j <= lb; j++) {
|
|
272
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return dp[la][lb];
|
|
276
|
+
}
|
|
277
|
+
function closestName(token, candidates) {
|
|
278
|
+
let best;
|
|
279
|
+
let bestDist = Infinity;
|
|
280
|
+
for (const name of candidates) {
|
|
281
|
+
const d = levenshtein(token.toLowerCase(), name.toLowerCase());
|
|
282
|
+
if (d > 0 && d <= 2 && d < bestDist) {
|
|
283
|
+
bestDist = d;
|
|
284
|
+
best = name;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return best;
|
|
288
|
+
}
|
|
289
|
+
var KERNEL_LIFECYCLE_NAMES = ["init", "welcome", "end", "online", "offline"];
|
|
290
|
+
function collectTransitionTargets(stmts) {
|
|
291
|
+
const targets = [];
|
|
292
|
+
for (const stmt of stmts) {
|
|
293
|
+
if (!stmt || typeof stmt !== "object") continue;
|
|
294
|
+
if (stmt.type === "transition_stmt" && typeof stmt["state"] === "string") {
|
|
295
|
+
targets.push(stmt["state"]);
|
|
296
|
+
}
|
|
297
|
+
if (stmt.type === "intent_handler" && typeof stmt["body"] === "string") {
|
|
298
|
+
targets.push(stmt["body"]);
|
|
299
|
+
}
|
|
300
|
+
const recurse = (v) => {
|
|
301
|
+
if (Array.isArray(v)) targets.push(...collectTransitionTargets(v));
|
|
302
|
+
};
|
|
303
|
+
if (stmt.type !== "intent_handler") recurse(stmt["body"]);
|
|
304
|
+
recurse(stmt["handlers"]);
|
|
305
|
+
recurse(stmt["then"]);
|
|
306
|
+
recurse(stmt["else"]);
|
|
307
|
+
recurse(stmt["on_complete"]);
|
|
308
|
+
recurse(stmt["on_failure"]);
|
|
309
|
+
}
|
|
310
|
+
return targets;
|
|
311
|
+
}
|
|
312
|
+
function collectMergedStates(tree, docDir, states, visited = /* @__PURE__ */ new Set()) {
|
|
313
|
+
for (const mergeNode of nodesOfType(tree, "merge_decl")) {
|
|
314
|
+
const pathNode = mergeNode.childForFieldName("path");
|
|
315
|
+
if (!pathNode) continue;
|
|
316
|
+
const filename = pathNode.text.replace(/^"|"$/g, "");
|
|
317
|
+
const absPath = (0, import_path.resolve)(docDir, filename);
|
|
318
|
+
if (visited.has(absPath)) continue;
|
|
319
|
+
visited.add(absPath);
|
|
320
|
+
let mergedText;
|
|
321
|
+
try {
|
|
322
|
+
mergedText = (0, import_fs.readFileSync)(absPath, "utf-8");
|
|
323
|
+
} catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const mergedTree = parseSync("behavior", mergedText);
|
|
327
|
+
if (!mergedTree) continue;
|
|
328
|
+
nodesOfType(mergedTree, "state_decl").forEach((n) => {
|
|
329
|
+
const name = n.childForFieldName("name")?.text;
|
|
330
|
+
if (name) states.add(name);
|
|
331
|
+
});
|
|
332
|
+
collectMergedStates(mergedTree, (0, import_path.dirname)(absPath), states, visited);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
336
|
+
await initBehaviorParser();
|
|
337
|
+
if (!text.endsWith("\n")) text = text + "\n";
|
|
338
|
+
const tree = await parse("behavior", text);
|
|
339
|
+
const messages = [];
|
|
340
|
+
collectSyntaxErrors(tree.rootNode, messages, file);
|
|
341
|
+
for (const stateNode of nodesOfType(tree, "state_decl")) {
|
|
342
|
+
if (stateNode.hasError) continue;
|
|
343
|
+
const nameNode = stateNode.childForFieldName("name");
|
|
344
|
+
const stateName = nameNode?.text ?? "?";
|
|
345
|
+
const hasInteract = stateNode.descendantsOfType("interact_stmt").length > 0;
|
|
346
|
+
const hasGoal = stateNode.descendantsOfType("goal_stmt").length > 0;
|
|
347
|
+
const intentHandlers = stateNode.descendantsOfType("intent_handler");
|
|
348
|
+
if (hasInteract && !hasGoal) {
|
|
349
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
350
|
+
messages.push({
|
|
351
|
+
file,
|
|
352
|
+
line,
|
|
353
|
+
col,
|
|
354
|
+
severity: "warning",
|
|
355
|
+
code: "W013",
|
|
356
|
+
message: `'interact' without 'goal' \u2014 the prettifier will insert one. To set it explicitly, add 'goal "..."' before 'interact'.`
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (hasGoal && !hasInteract) {
|
|
360
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
361
|
+
messages.push({
|
|
362
|
+
file,
|
|
363
|
+
line,
|
|
364
|
+
col,
|
|
365
|
+
severity: "warning",
|
|
366
|
+
code: "W012",
|
|
367
|
+
message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal' \u2014 the prettifier can adjust this.`
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (hasInteract && intentHandlers.length === 0) {
|
|
371
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
372
|
+
messages.push({
|
|
373
|
+
file,
|
|
374
|
+
line,
|
|
375
|
+
col,
|
|
376
|
+
severity: "error",
|
|
377
|
+
code: "E009",
|
|
378
|
+
message: `Oriented state '${stateName}' has no 'on intent' handlers. At least one is required.`
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
const seenIntents = /* @__PURE__ */ new Map();
|
|
382
|
+
for (const handler of intentHandlers) {
|
|
383
|
+
const intentNode = handler.childForFieldName("intent");
|
|
384
|
+
if (!intentNode) continue;
|
|
385
|
+
const label = intentNode.text.replace(/^"|"$/g, "");
|
|
386
|
+
if (seenIntents.has(label)) {
|
|
387
|
+
const { line, col } = nodePosition(handler);
|
|
388
|
+
messages.push({
|
|
389
|
+
file,
|
|
390
|
+
line,
|
|
391
|
+
col,
|
|
392
|
+
severity: "error",
|
|
393
|
+
code: "W008",
|
|
394
|
+
message: `Duplicate 'on intent "${label}"' in state '${stateName}'. The FSM cannot route ambiguous intents \u2014 use a unique label for each handler.`
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
seenIntents.set(label, true);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
for (const handler of intentHandlers) {
|
|
401
|
+
for (const t of handler.descendantsOfType("transition_stmt")) {
|
|
402
|
+
const targetNode = t.childForFieldName("state");
|
|
403
|
+
if (targetNode?.text === stateName) {
|
|
404
|
+
const { line, col } = nodePosition(handler);
|
|
405
|
+
messages.push({
|
|
406
|
+
file,
|
|
407
|
+
line,
|
|
408
|
+
col,
|
|
409
|
+
severity: "warning",
|
|
410
|
+
code: "W011",
|
|
411
|
+
message: `'on intent' handler in state '${stateName}' transitions back to itself. The user expressed an intent but receives no progress \u2014 did you mean a different target state?`
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (stateName === "init" || stateName === "end") {
|
|
417
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
418
|
+
if (stateName === "init") {
|
|
419
|
+
messages.push({
|
|
420
|
+
file,
|
|
421
|
+
line,
|
|
422
|
+
col,
|
|
423
|
+
severity: "info",
|
|
424
|
+
code: "I001",
|
|
425
|
+
message: `State 'init' overrides the kernel's default entry lifecycle. The kernel will use this definition instead of its built-in init sequence.`
|
|
426
|
+
});
|
|
427
|
+
} else {
|
|
428
|
+
messages.push({
|
|
429
|
+
file,
|
|
430
|
+
line,
|
|
431
|
+
col,
|
|
432
|
+
severity: "info",
|
|
433
|
+
code: "I002",
|
|
434
|
+
message: `State 'end' overrides the kernel's default terminal lifecycle. The kernel will treat this state as the canonical exit point.`
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (!KERNEL_LIFECYCLE_NAMES.includes(stateName)) {
|
|
439
|
+
const closest = closestName(stateName, KERNEL_LIFECYCLE_NAMES);
|
|
440
|
+
if (closest) {
|
|
441
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
442
|
+
messages.push({
|
|
443
|
+
file,
|
|
444
|
+
line,
|
|
445
|
+
col,
|
|
446
|
+
severity: "hint",
|
|
447
|
+
code: "H001",
|
|
448
|
+
message: `State name '${stateName}' resembles the kernel lifecycle name '${closest}'. If this is intentional, ignore this hint.`
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
for (const parallelNode of nodesOfType(tree, "parallel_stmt")) {
|
|
454
|
+
if (parallelNode.hasError) continue;
|
|
455
|
+
if (parallelNode.descendantsOfType("run_stmt").length === 0) {
|
|
456
|
+
const { line, col } = nodePosition(parallelNode);
|
|
457
|
+
let stateName = "?";
|
|
458
|
+
let ancestor = parallelNode.parent;
|
|
459
|
+
while (ancestor && ancestor.type !== "state_decl") ancestor = ancestor.parent;
|
|
460
|
+
if (ancestor) stateName = ancestor.childForFieldName("name")?.text ?? "?";
|
|
461
|
+
messages.push({
|
|
462
|
+
file,
|
|
463
|
+
line,
|
|
464
|
+
col,
|
|
465
|
+
severity: "warning",
|
|
466
|
+
code: "E010",
|
|
467
|
+
message: `'parallel' block in state '${stateName}' has no 'run' statements and does nothing.`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
for (const afterNode of nodesOfType(tree, "after_stmt")) {
|
|
472
|
+
if (afterNode.hasError) continue;
|
|
473
|
+
const countNode = afterNode.childForFieldName("count");
|
|
474
|
+
if (countNode?.text === "0") {
|
|
475
|
+
const { line, col } = nodePosition(countNode);
|
|
476
|
+
messages.push({
|
|
477
|
+
file,
|
|
478
|
+
line,
|
|
479
|
+
col,
|
|
480
|
+
severity: "error",
|
|
481
|
+
code: "E011",
|
|
482
|
+
message: `'after 0' is invalid \u2014 prompts must be \u2265 1. Use 'after 1' for the next prompt.`
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
for (const stmtNode of nodesOfType(tree, "goal_stmt")) {
|
|
487
|
+
const strNode = stmtNode.descendantsOfType("quoted_string").find((n) => n !== null);
|
|
488
|
+
if (!strNode) continue;
|
|
489
|
+
const content = strNode.text.replace(/^"|"$/g, "");
|
|
490
|
+
if (content.length > 280) {
|
|
491
|
+
const { line, col } = nodePosition(strNode);
|
|
492
|
+
messages.push({
|
|
493
|
+
file,
|
|
494
|
+
line,
|
|
495
|
+
col,
|
|
496
|
+
severity: "warning",
|
|
497
|
+
code: "W002",
|
|
498
|
+
message: `Text block exceeds 280 characters (${content.length}). Consider using 'teach' to load long goal text from an external file.`
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
for (const stmtNode of nodesOfType(tree, "guide_stmt")) {
|
|
503
|
+
const strNode = stmtNode.descendantsOfType("quoted_string").find((n) => n !== null);
|
|
504
|
+
if (!strNode) continue;
|
|
505
|
+
const content = strNode.text.replace(/^"|"$/g, "");
|
|
506
|
+
if (content.length > 280) {
|
|
507
|
+
const { line, col } = nodePosition(strNode);
|
|
508
|
+
let p = stmtNode.parent;
|
|
509
|
+
while (p && p.type !== "state_decl") p = p.parent;
|
|
510
|
+
const stateName = p?.childForFieldName("name")?.text ?? "?";
|
|
511
|
+
messages.push({
|
|
512
|
+
file,
|
|
513
|
+
line,
|
|
514
|
+
col,
|
|
515
|
+
severity: "warning",
|
|
516
|
+
code: "W010",
|
|
517
|
+
message: `'guide' text in state '${stateName}' is ${content.length} characters (limit: 280). Consider using an external file.`
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const definedStates = new Set(
|
|
522
|
+
nodesOfType(tree, "state_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean)
|
|
523
|
+
);
|
|
524
|
+
if (docPath) {
|
|
525
|
+
try {
|
|
526
|
+
collectMergedStates(tree, (0, import_path.dirname)(docPath), definedStates);
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const stateNameList = Array.from(definedStates);
|
|
531
|
+
for (const transitionNode of nodesOfType(tree, "transition_stmt")) {
|
|
532
|
+
const stateNode = transitionNode.childForFieldName("state");
|
|
533
|
+
if (!stateNode) continue;
|
|
534
|
+
const target = stateNode.text;
|
|
535
|
+
if (!definedStates.has(target)) {
|
|
536
|
+
const { line, col } = nodePosition(stateNode);
|
|
537
|
+
const isExternal = target.includes(".");
|
|
538
|
+
const hint = !isExternal ? closestName(target, stateNameList) : void 0;
|
|
539
|
+
messages.push({
|
|
540
|
+
file,
|
|
541
|
+
line,
|
|
542
|
+
col,
|
|
543
|
+
severity: isExternal ? "warning" : "error",
|
|
544
|
+
code: isExternal ? "W005" : "E005",
|
|
545
|
+
message: isExternal ? `State '${target}' is not defined locally (assuming external flow reference).` : `State '${target}' is not defined in this file.`,
|
|
546
|
+
...hint ? { hint: `did you mean '${hint}'?` } : {}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
for (const interactNode of nodesOfType(tree, "interact_stmt")) {
|
|
551
|
+
let ancestor = interactNode.parent;
|
|
552
|
+
while (ancestor && ancestor.type !== "state_decl") ancestor = ancestor.parent;
|
|
553
|
+
if (!ancestor) continue;
|
|
554
|
+
const hasHandlers = ancestor.descendantsOfType("intent_handler").length + ancestor.descendantsOfType("offtopic_handler").length > 0;
|
|
555
|
+
if (!hasHandlers) {
|
|
556
|
+
const { line, col } = nodePosition(interactNode);
|
|
557
|
+
messages.push({
|
|
558
|
+
file,
|
|
559
|
+
line,
|
|
560
|
+
col,
|
|
561
|
+
severity: "warning",
|
|
562
|
+
code: "W006",
|
|
563
|
+
message: "This state calls interact but has no handlers (on intent/offtopic). This will trap the agent."
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const fsmResult = parseBehaviorFile(text);
|
|
568
|
+
if (fsmResult.ok === null) {
|
|
569
|
+
const firstMsg = fsmResult.diagnostics[0]?.message ?? "Parse error";
|
|
570
|
+
messages.push({
|
|
571
|
+
file,
|
|
572
|
+
line: 1,
|
|
573
|
+
col: 1,
|
|
574
|
+
severity: "error",
|
|
575
|
+
code: "E006",
|
|
576
|
+
message: `Parse error: ${firstMsg}`
|
|
577
|
+
});
|
|
578
|
+
return messages;
|
|
579
|
+
}
|
|
580
|
+
const fsm = fsmResult.ok;
|
|
581
|
+
const allTargets = new Set(collectTransitionTargets(fsm.states.flatMap((s) => s.body)));
|
|
582
|
+
for (const state of fsm.states) {
|
|
583
|
+
const hasOutgoing = collectTransitionTargets(state.body).length > 0;
|
|
584
|
+
const hasIncoming = allTargets.has(state.name);
|
|
585
|
+
if (!hasIncoming && !hasOutgoing) {
|
|
586
|
+
messages.push({
|
|
587
|
+
file,
|
|
588
|
+
line: 1,
|
|
589
|
+
col: 1,
|
|
590
|
+
severity: "warning",
|
|
591
|
+
code: "W001",
|
|
592
|
+
message: `State "${state.name}" has no transitions`
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
for (let i = 1; i < fsm.states.length; i++) {
|
|
597
|
+
const state = fsm.states[i];
|
|
598
|
+
if (!allTargets.has(state.name)) {
|
|
599
|
+
const stateNode = nodesOfType(tree, "state_decl").find((n) => n.childForFieldName("name")?.text === state.name);
|
|
600
|
+
const pos = stateNode ? nodePosition(stateNode) : { line: 1, col: 1 };
|
|
601
|
+
messages.push({
|
|
602
|
+
file,
|
|
603
|
+
line: pos.line,
|
|
604
|
+
col: pos.col,
|
|
605
|
+
severity: "warning",
|
|
606
|
+
code: "W009",
|
|
607
|
+
message: `State '${state.name}' is unreachable \u2014 no other state transitions to it.`
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return messages;
|
|
612
|
+
}
|
|
613
|
+
async function createLinter() {
|
|
614
|
+
return { lintDescription, lintBehavior };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/manifest.ts
|
|
618
|
+
function parseAboutme(json) {
|
|
619
|
+
if (!json.schemaVersion) throw new Error("Missing schemaVersion in aboutme.json");
|
|
620
|
+
if (!json.id) throw new Error("Missing id in aboutme.json");
|
|
621
|
+
if (!json.name) throw new Error("Missing name in aboutme.json");
|
|
622
|
+
if (!json.description) throw new Error("Missing description in aboutme.json");
|
|
623
|
+
if (!json.version) throw new Error("Missing version in aboutme.json");
|
|
624
|
+
if (!json.domain) throw new Error("Missing domain in aboutme.json");
|
|
625
|
+
if (!json.persona) throw new Error("Missing persona in aboutme.json");
|
|
626
|
+
if (!json.compiler) throw new Error("Missing compiler in aboutme.json");
|
|
627
|
+
if (!Array.isArray(json.capabilities)) throw new Error("Missing capabilities array in aboutme.json");
|
|
628
|
+
if (!Array.isArray(json.requires)) throw new Error("Missing requires array in aboutme.json");
|
|
629
|
+
if (!json.integrity) throw new Error("Missing integrity in aboutme.json");
|
|
630
|
+
return {
|
|
631
|
+
schemaVersion: json.schemaVersion,
|
|
632
|
+
id: json.id,
|
|
633
|
+
name: json.name,
|
|
634
|
+
description: json.description,
|
|
635
|
+
version: json.version,
|
|
636
|
+
domain: json.domain,
|
|
637
|
+
license: json.license ?? "",
|
|
638
|
+
persona: json.persona,
|
|
639
|
+
purpose: json.purpose ?? "unknown",
|
|
640
|
+
compiler: json.compiler,
|
|
641
|
+
commit: json.commit,
|
|
642
|
+
capabilities: json.capabilities,
|
|
643
|
+
requires: json.requires,
|
|
644
|
+
integrity: json.integrity
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function buildAboutme(opts) {
|
|
648
|
+
return {
|
|
649
|
+
schemaVersion: "dot-agent/1.0",
|
|
650
|
+
id: opts.id,
|
|
651
|
+
name: opts.name,
|
|
652
|
+
description: opts.description,
|
|
653
|
+
version: opts.version,
|
|
654
|
+
domain: opts.domain,
|
|
655
|
+
license: opts.license ?? "",
|
|
656
|
+
persona: opts.persona,
|
|
657
|
+
purpose: opts.purpose ?? "unknown",
|
|
658
|
+
compiler: opts.compiler,
|
|
659
|
+
commit: opts.commit,
|
|
660
|
+
capabilities: opts.capabilities ?? [],
|
|
661
|
+
requires: opts.requires ?? [],
|
|
662
|
+
integrity: opts.integrity
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function aboutmeToJson(aboutme) {
|
|
666
|
+
return JSON.stringify(aboutme, null, 2);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/id.ts
|
|
670
|
+
var KNOWN_PLATFORMS = ["github.com", "gitlab.com", "codeberg.org", "sr.ht"];
|
|
671
|
+
function parseNamespaceName(identifier) {
|
|
672
|
+
const atIdx = identifier.indexOf("@");
|
|
673
|
+
if (atIdx !== -1 && (identifier.indexOf("/") === -1 || atIdx < identifier.indexOf("/"))) {
|
|
674
|
+
const slashIdx = identifier.indexOf("/");
|
|
675
|
+
if (slashIdx === -1) {
|
|
676
|
+
throw new Error(`Invalid agent ID: email namespace requires a name after '/' (got '${identifier}')`);
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
namespace: identifier.slice(0, slashIdx),
|
|
680
|
+
name: identifier.slice(slashIdx + 1)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
for (const platform of KNOWN_PLATFORMS) {
|
|
684
|
+
if (identifier.startsWith(platform + "/")) {
|
|
685
|
+
const afterPlatform = identifier.slice(platform.length + 1);
|
|
686
|
+
const slashIdx = afterPlatform.indexOf("/");
|
|
687
|
+
if (slashIdx === -1) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`Invalid agent ID: '${platform}' namespace requires a user segment and a name (got '${identifier}')`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
namespace: platform + "/" + afterPlatform.slice(0, slashIdx),
|
|
694
|
+
name: afterPlatform.slice(slashIdx + 1)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const lastSlash = identifier.lastIndexOf("/");
|
|
699
|
+
if (lastSlash === -1) {
|
|
700
|
+
throw new Error(`Invalid agent ID: missing '/' separator in '${identifier}'`);
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
namespace: identifier.slice(0, lastSlash),
|
|
704
|
+
name: identifier.slice(lastSlash + 1)
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function parseId(id) {
|
|
708
|
+
if (!id) throw new Error("Invalid agent ID: empty string");
|
|
709
|
+
const colonIdx = id.indexOf(":");
|
|
710
|
+
let identifier;
|
|
711
|
+
let version;
|
|
712
|
+
let digest;
|
|
713
|
+
if (colonIdx === -1) {
|
|
714
|
+
identifier = id;
|
|
715
|
+
} else {
|
|
716
|
+
identifier = id.slice(0, colonIdx);
|
|
717
|
+
const rest = id.slice(colonIdx + 1);
|
|
718
|
+
if (!rest) throw new Error(`Invalid agent ID: empty version after ':' in '${id}'`);
|
|
719
|
+
const tildeIdx = rest.indexOf("~");
|
|
720
|
+
if (tildeIdx === -1) {
|
|
721
|
+
version = rest;
|
|
722
|
+
} else {
|
|
723
|
+
version = rest.slice(0, tildeIdx);
|
|
724
|
+
digest = rest.slice(tildeIdx + 1);
|
|
725
|
+
if (!version) throw new Error(`Invalid agent ID: empty version before '~' in '${id}'`);
|
|
726
|
+
if (!digest) throw new Error(`Invalid agent ID: empty digest after '~' in '${id}'`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const { namespace, name } = parseNamespaceName(identifier);
|
|
730
|
+
if (!namespace) throw new Error(`Invalid agent ID: empty namespace in '${id}'`);
|
|
731
|
+
if (!name) throw new Error(`Invalid agent ID: empty name in '${id}'`);
|
|
732
|
+
return { namespace, name, version, digest };
|
|
733
|
+
}
|
|
734
|
+
function buildId(parts) {
|
|
735
|
+
const { namespace, name, version, digest } = parts;
|
|
736
|
+
if (!namespace || !name) {
|
|
737
|
+
throw new Error("Missing required ID parts: namespace and name");
|
|
738
|
+
}
|
|
739
|
+
if (digest && !version) {
|
|
740
|
+
throw new Error("Invalid ID parts: digest requires version");
|
|
741
|
+
}
|
|
742
|
+
let result = `${namespace}/${name}`;
|
|
743
|
+
if (version) {
|
|
744
|
+
result += `:${version}`;
|
|
745
|
+
if (digest) result += `~${digest}`;
|
|
746
|
+
}
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
function extractDigest(id) {
|
|
750
|
+
try {
|
|
751
|
+
return parseId(id).digest ?? "";
|
|
752
|
+
} catch {
|
|
753
|
+
return "";
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function extractName(id) {
|
|
757
|
+
try {
|
|
758
|
+
return parseId(id).name;
|
|
759
|
+
} catch {
|
|
760
|
+
return "";
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/zip.ts
|
|
765
|
+
var import_jszip2 = __toESM(require("jszip"), 1);
|
|
766
|
+
var import_promises = require("fs/promises");
|
|
767
|
+
|
|
768
|
+
// src/zip-core.ts
|
|
769
|
+
var import_jszip = __toESM(require("jszip"), 1);
|
|
770
|
+
function createZip() {
|
|
771
|
+
return new import_jszip.default();
|
|
772
|
+
}
|
|
773
|
+
async function extractFiles(zip, filter) {
|
|
774
|
+
const files = /* @__PURE__ */ new Map();
|
|
775
|
+
const promises = [];
|
|
776
|
+
zip.forEach((relativePath, file) => {
|
|
777
|
+
if (file.dir) return;
|
|
778
|
+
if (filter && !filter.some((f) => relativePath.startsWith(f))) return;
|
|
779
|
+
promises.push(
|
|
780
|
+
file.async("text").then((content) => {
|
|
781
|
+
files.set(relativePath, content);
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
await Promise.all(promises);
|
|
786
|
+
return files;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/zip.ts
|
|
790
|
+
var MAX_ZIP_SIZE = 500 * 1024 * 1024;
|
|
791
|
+
var MAX_COMPRESSION_RATIO = 100;
|
|
792
|
+
async function readZip(filePath) {
|
|
793
|
+
const data = await (0, import_promises.readFile)(filePath);
|
|
794
|
+
return import_jszip2.default.loadAsync(data);
|
|
795
|
+
}
|
|
796
|
+
async function validateZipBomb(filePath) {
|
|
797
|
+
const data = await (0, import_promises.readFile)(filePath);
|
|
798
|
+
const zip = await import_jszip2.default.loadAsync(data);
|
|
799
|
+
let totalUncompressed = 0;
|
|
800
|
+
zip.forEach((_relativePath, file) => {
|
|
801
|
+
if (!file.dir) {
|
|
802
|
+
totalUncompressed += file._data?.uncompressedSize || 0;
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
const compressedSize = data.length;
|
|
806
|
+
const ratio = totalUncompressed / compressedSize;
|
|
807
|
+
if (totalUncompressed > MAX_ZIP_SIZE) {
|
|
808
|
+
throw new Error(`ZIP uncompressed size exceeds 500MB limit: ${totalUncompressed}`);
|
|
809
|
+
}
|
|
810
|
+
if (ratio > MAX_COMPRESSION_RATIO) {
|
|
811
|
+
throw new Error(`ZIP compression ratio exceeds 100x limit: ${ratio.toFixed(1)}x`);
|
|
812
|
+
}
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
async function validateMagicBytes(filePath) {
|
|
816
|
+
const data = await (0, import_promises.readFile)(filePath);
|
|
817
|
+
const magic = data.subarray(0, 4);
|
|
818
|
+
if (!(magic[0] === 80 && magic[1] === 75 && magic[2] === 3 && magic[3] === 4)) {
|
|
819
|
+
throw new Error("File is not a valid ZIP (invalid magic bytes)");
|
|
820
|
+
}
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
async function writeZip(zip, outPath) {
|
|
824
|
+
const data = await zip.generateAsync({ type: "arraybuffer" });
|
|
825
|
+
await (0, import_promises.writeFile)(outPath, Buffer.from(data));
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/pack.ts
|
|
829
|
+
var import_promises2 = require("fs/promises");
|
|
830
|
+
var import_path2 = require("path");
|
|
831
|
+
var import_child_process = require("child_process");
|
|
832
|
+
var import_crypto = require("crypto");
|
|
833
|
+
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
834
|
+
|
|
835
|
+
// src/schema.ts
|
|
836
|
+
var STD_BASE = "https://dot-agent.dev/schema/std/v1/";
|
|
837
|
+
function propertyTypeToSchema(pt) {
|
|
838
|
+
switch (pt.kind) {
|
|
839
|
+
case "primitive":
|
|
840
|
+
return { type: pt.value };
|
|
841
|
+
case "reference":
|
|
842
|
+
return pt.value.startsWith("std.") ? { $ref: `${STD_BASE}${pt.value.slice(4)}.json` } : { $ref: `#/$defs/${pt.value}` };
|
|
843
|
+
case "array":
|
|
844
|
+
return { type: "array", items: propertyTypeToSchema(pt.value) };
|
|
845
|
+
case "enum":
|
|
846
|
+
return { type: "string", enum: pt.value };
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function typeDefinitionToSchema(td) {
|
|
850
|
+
const required = td.properties.filter((p) => !p.is_optional).map((p) => p.name);
|
|
851
|
+
const properties = {};
|
|
852
|
+
for (const p of td.properties) {
|
|
853
|
+
properties[p.name] = {
|
|
854
|
+
...propertyTypeToSchema(p.type),
|
|
855
|
+
...p.description ? { description: p.description } : {}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
860
|
+
"x-category": td.category.uri,
|
|
861
|
+
...td.category.label ? { "x-category-label": td.category.label } : {},
|
|
862
|
+
...td.concept ? { "x-concept": td.concept.uri } : {},
|
|
863
|
+
...td.concept?.label ? { "x-concept-label": td.concept.label } : {},
|
|
864
|
+
type: "object",
|
|
865
|
+
properties,
|
|
866
|
+
...required.length ? { required } : {}
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function refFromAnnotatedRef(ref) {
|
|
870
|
+
return ref.name.startsWith("std.") ? { $ref: `${STD_BASE}${ref.name.slice(4)}.json` } : { $ref: `#/$defs/${ref.name}` };
|
|
871
|
+
}
|
|
872
|
+
function buildTypesJson(df) {
|
|
873
|
+
if (df.types.length === 0 && df.input.length === 0 && df.output.length === 0) return null;
|
|
874
|
+
const defs = {};
|
|
875
|
+
for (const td of df.types) {
|
|
876
|
+
defs[td.name] = typeDefinitionToSchema(td);
|
|
877
|
+
}
|
|
878
|
+
return JSON.stringify(
|
|
879
|
+
{
|
|
880
|
+
...df.input.length ? { input: df.input.map(refFromAnnotatedRef) } : {},
|
|
881
|
+
...df.output.length ? { output: df.output.map(refFromAnnotatedRef) } : {},
|
|
882
|
+
...Object.keys(defs).length ? { $defs: defs } : {}
|
|
883
|
+
},
|
|
884
|
+
null,
|
|
885
|
+
2
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/pack.ts
|
|
890
|
+
function gitDescribeTags() {
|
|
891
|
+
try {
|
|
892
|
+
return (0, import_child_process.execSync)("git describe --tags --abbrev=0", { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
893
|
+
} catch {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function gitRevParseShort() {
|
|
898
|
+
try {
|
|
899
|
+
return (0, import_child_process.execSync)("git rev-parse --short HEAD", { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
900
|
+
} catch {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function resolveVersion(explicit) {
|
|
905
|
+
if (explicit) return explicit;
|
|
906
|
+
return gitDescribeTags() ?? "v1.0.0";
|
|
907
|
+
}
|
|
908
|
+
async function resolveCommit(explicit) {
|
|
909
|
+
if (explicit) return explicit;
|
|
910
|
+
return gitRevParseShort() ?? void 0;
|
|
911
|
+
}
|
|
912
|
+
async function collectFiles(dir) {
|
|
913
|
+
const files = /* @__PURE__ */ new Map();
|
|
914
|
+
async function walk(subdir, prefix = "") {
|
|
915
|
+
try {
|
|
916
|
+
const entries = await (0, import_promises2.readdir)(subdir);
|
|
917
|
+
for (const entry of entries) {
|
|
918
|
+
if (entry === ".gitkeep" || entry.startsWith(".")) continue;
|
|
919
|
+
const fullPath = (0, import_path2.join)(subdir, entry);
|
|
920
|
+
const stats = await (0, import_promises2.stat)(fullPath);
|
|
921
|
+
const relativePath = prefix ? `${prefix}/${entry}` : entry;
|
|
922
|
+
if (stats.isDirectory()) {
|
|
923
|
+
await walk(fullPath, relativePath);
|
|
924
|
+
} else {
|
|
925
|
+
files.set(relativePath, await (0, import_promises2.readFile)(fullPath, "utf-8"));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
files.set("agent.description", await (0, import_promises2.readFile)((0, import_path2.join)(dir, "agent.description"), "utf-8"));
|
|
932
|
+
files.set("agent.behavior", await (0, import_promises2.readFile)((0, import_path2.join)(dir, "agent.behavior"), "utf-8"));
|
|
933
|
+
try {
|
|
934
|
+
files.set("SOUL.md", await (0, import_promises2.readFile)((0, import_path2.join)(dir, "SOUL.md"), "utf-8"));
|
|
935
|
+
} catch {
|
|
936
|
+
}
|
|
937
|
+
await walk((0, import_path2.join)(dir, "behaviors"), "behaviors");
|
|
938
|
+
await walk((0, import_path2.join)(dir, "guides"), "guides");
|
|
939
|
+
await walk((0, import_path2.join)(dir, "knowledge"), "knowledge");
|
|
940
|
+
return files;
|
|
941
|
+
}
|
|
942
|
+
async function pack(options = {}) {
|
|
943
|
+
const dir = options.dir ?? process.cwd();
|
|
944
|
+
const outPath = options.out ?? (0, import_path2.join)(dir, `${(0, import_path2.basename)(dir)}.agent`);
|
|
945
|
+
let descriptionText;
|
|
946
|
+
let behaviorText;
|
|
947
|
+
try {
|
|
948
|
+
descriptionText = await (0, import_promises2.readFile)((0, import_path2.join)(dir, "agent.description"), "utf-8");
|
|
949
|
+
} catch {
|
|
950
|
+
throw new Error("E003: File agent.description not found");
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
behaviorText = await (0, import_promises2.readFile)((0, import_path2.join)(dir, "agent.behavior"), "utf-8");
|
|
954
|
+
} catch {
|
|
955
|
+
throw new Error("E007: File agent.behavior not found");
|
|
956
|
+
}
|
|
957
|
+
const [descriptionMessages, behaviorMessages] = await Promise.all([
|
|
958
|
+
lintDescription(descriptionText),
|
|
959
|
+
lintBehavior(behaviorText)
|
|
960
|
+
]);
|
|
961
|
+
const allMessages = [...descriptionMessages, ...behaviorMessages];
|
|
962
|
+
const errors = allMessages.filter((m) => m.severity === "error");
|
|
963
|
+
const warnings = allMessages.filter((m) => m.severity === "warning");
|
|
964
|
+
if (errors.length > 0) {
|
|
965
|
+
throw new Error(
|
|
966
|
+
`Lint failed:
|
|
967
|
+
${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
await initBehaviorParser();
|
|
971
|
+
const descResult = parseDescriptionFile(descriptionText);
|
|
972
|
+
if (descResult.ok === null) {
|
|
973
|
+
const firstError = descResult.diagnostics.find((d) => d.severity === "error");
|
|
974
|
+
throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
|
|
975
|
+
}
|
|
976
|
+
const df = descResult.ok;
|
|
977
|
+
const version = await resolveVersion(options.version);
|
|
978
|
+
const commit = await resolveCommit(options.commit);
|
|
979
|
+
const allFiles = await collectFiles(dir);
|
|
980
|
+
const contentForHash = Array.from(allFiles.values()).join("");
|
|
981
|
+
const sha256 = (0, import_crypto.createHash)("sha256").update(contentForHash).digest("hex");
|
|
982
|
+
const namespace = df.agent.domain ?? "unknown";
|
|
983
|
+
const id = buildId({ namespace, name: df.agent.name, version, digest: commit });
|
|
984
|
+
const typesJson = buildTypesJson(df);
|
|
985
|
+
const integrity = {
|
|
986
|
+
sha256,
|
|
987
|
+
...typesJson ? { types: ".agent/types.json" } : {},
|
|
988
|
+
files: ".agent/files.json"
|
|
989
|
+
};
|
|
990
|
+
const aboutme = buildAboutme({
|
|
991
|
+
id,
|
|
992
|
+
name: df.agent.name,
|
|
993
|
+
description: df.description ?? "",
|
|
994
|
+
version,
|
|
995
|
+
domain: df.agent.domain ?? "",
|
|
996
|
+
license: df.agent.license ?? "",
|
|
997
|
+
persona: df.persona ?? "SOUL.md",
|
|
998
|
+
purpose: "unknown",
|
|
999
|
+
compiler: "dot-agent/1.0.0",
|
|
1000
|
+
commit,
|
|
1001
|
+
capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
|
|
1002
|
+
requires: df.requires,
|
|
1003
|
+
integrity
|
|
1004
|
+
});
|
|
1005
|
+
const zip = new import_jszip3.default();
|
|
1006
|
+
const agentFolder = zip.folder(".agent");
|
|
1007
|
+
agentFolder.file("aboutme.json", aboutmeToJson(aboutme));
|
|
1008
|
+
if (typesJson) {
|
|
1009
|
+
agentFolder.file("types.json", typesJson);
|
|
1010
|
+
}
|
|
1011
|
+
const gitkeepPaths = /* @__PURE__ */ new Set(["behaviors/.gitkeep", "guides/.gitkeep", "knowledge/.gitkeep"]);
|
|
1012
|
+
const filesJson = {
|
|
1013
|
+
description: "agent.description",
|
|
1014
|
+
behavior: "agent.behavior",
|
|
1015
|
+
behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/") && !gitkeepPaths.has(f)),
|
|
1016
|
+
guides: Array.from(allFiles.keys()).filter((f) => f.startsWith("guides/") && !gitkeepPaths.has(f)),
|
|
1017
|
+
knowledge: Array.from(allFiles.keys()).filter((f) => f.startsWith("knowledge/") && !gitkeepPaths.has(f))
|
|
1018
|
+
};
|
|
1019
|
+
agentFolder.file("files.json", JSON.stringify(filesJson, null, 2));
|
|
1020
|
+
for (const [path, content] of allFiles) {
|
|
1021
|
+
if (!gitkeepPaths.has(path)) zip.file(path, content);
|
|
1022
|
+
}
|
|
1023
|
+
await writeZip(zip, outPath);
|
|
1024
|
+
return { path: outPath, id, warnings };
|
|
1025
|
+
}
|
|
1026
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1027
|
+
0 && (module.exports = {
|
|
1028
|
+
aboutmeToJson,
|
|
1029
|
+
buildAboutme,
|
|
1030
|
+
buildId,
|
|
1031
|
+
collectFiles,
|
|
1032
|
+
createLinter,
|
|
1033
|
+
createZip,
|
|
1034
|
+
extractDigest,
|
|
1035
|
+
extractFiles,
|
|
1036
|
+
extractName,
|
|
1037
|
+
getBehaviorScxml,
|
|
1038
|
+
getContextNode,
|
|
1039
|
+
initBehaviorParser,
|
|
1040
|
+
initParsers,
|
|
1041
|
+
lintBehavior,
|
|
1042
|
+
lintDescription,
|
|
1043
|
+
nodeAtOffset,
|
|
1044
|
+
nodeToRange,
|
|
1045
|
+
nodesOfType,
|
|
1046
|
+
pack,
|
|
1047
|
+
parse,
|
|
1048
|
+
parseAboutme,
|
|
1049
|
+
parseBehaviorFile,
|
|
1050
|
+
parseDescriptionFile,
|
|
1051
|
+
parseId,
|
|
1052
|
+
parseSync,
|
|
1053
|
+
positionToOffset,
|
|
1054
|
+
readZip,
|
|
1055
|
+
validateMagicBytes,
|
|
1056
|
+
validateZipBomb,
|
|
1057
|
+
writeZip
|
|
1058
|
+
});
|
|
1059
|
+
//# sourceMappingURL=index.cjs.map
|