@dot-agent/compiler 0.10.0 → 0.10.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/dist/core.cjs +13 -248
- package/dist/core.d.cts +2 -3
- package/dist/core.d.mts +2 -0
- package/dist/core.mjs +2 -0
- package/dist/index.cjs +1008 -1305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -27
- package/dist/index.d.cts.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +29 -27
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1057 -0
- package/dist/index.mjs.map +1 -0
- package/dist/zip-core-BCiH0sUt.d.cts +123 -0
- package/dist/zip-core-BCiH0sUt.d.cts.map +1 -0
- package/dist/zip-core-BCiH0sUt.d.mts +123 -0
- package/dist/zip-core-BCiH0sUt.d.mts.map +1 -0
- package/dist/zip-core-BjnIQMRA.cjs +292 -0
- package/dist/zip-core-BjnIQMRA.cjs.map +1 -0
- package/dist/zip-core-Dw_OgIjV.mjs +180 -0
- package/dist/zip-core-Dw_OgIjV.mjs.map +1 -0
- package/package.json +27 -19
- package/dist/chunk-AYXHOOUX.js +0 -213
- package/dist/chunk-AYXHOOUX.js.map +0 -1
- package/dist/core-D2X2Eohl.d.cts +0 -120
- package/dist/core-D2X2Eohl.d.ts +0 -120
- package/dist/core.cjs.map +0 -1
- package/dist/core.d.ts +0 -3
- package/dist/core.js +0 -27
- package/dist/core.js.map +0 -1
- package/dist/index.js +0 -1134
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,1387 +1,1090 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
bundleFromDir: () => bundleFromDir,
|
|
37
|
-
collectFiles: () => collectFiles,
|
|
38
|
-
consolidate: () => consolidate,
|
|
39
|
-
createLinter: () => createLinter,
|
|
40
|
-
createZip: () => createZip,
|
|
41
|
-
extractDigest: () => extractDigest,
|
|
42
|
-
extractFiles: () => extractFiles,
|
|
43
|
-
extractName: () => extractName,
|
|
44
|
-
getBehaviorScxml: () => getBehaviorScxml,
|
|
45
|
-
getContextNode: () => getContextNode,
|
|
46
|
-
initBehaviorParser: () => initBehaviorParser,
|
|
47
|
-
initParsers: () => initParsers,
|
|
48
|
-
lintBehavior: () => lintBehavior,
|
|
49
|
-
lintDescription: () => lintDescription,
|
|
50
|
-
nodeAtOffset: () => nodeAtOffset,
|
|
51
|
-
nodeToRange: () => nodeToRange,
|
|
52
|
-
nodesOfType: () => nodesOfType,
|
|
53
|
-
pack: () => pack,
|
|
54
|
-
parse: () => parse,
|
|
55
|
-
parseAboutme: () => parseAboutme,
|
|
56
|
-
parseBehaviorFile: () => parseBehaviorFile,
|
|
57
|
-
parseDescriptionFile: () => parseDescriptionFile,
|
|
58
|
-
parseId: () => parseId,
|
|
59
|
-
parseSync: () => parseSync,
|
|
60
|
-
positionToOffset: () => positionToOffset,
|
|
61
|
-
readZip: () => readZip,
|
|
62
|
-
validateMagicBytes: () => validateMagicBytes2,
|
|
63
|
-
validateZipBomb: () => validateZipBomb2,
|
|
64
|
-
writeZip: () => writeZip
|
|
65
|
-
});
|
|
66
|
-
module.exports = __toCommonJS(index_exports);
|
|
67
|
-
|
|
68
|
-
// ../../node_modules/tsup/assets/cjs_shims.js
|
|
69
|
-
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;
|
|
70
|
-
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
71
|
-
|
|
72
|
-
// src/parser.ts
|
|
73
|
-
var import_module = require("module");
|
|
74
|
-
var import_web_tree_sitter = require("web-tree-sitter");
|
|
75
|
-
var import_parser_dsl = require("@dot-agent/parser-dsl");
|
|
76
|
-
var require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
77
|
-
var { descriptionWasmPath, behaviorWasmPath } = require2("@dot-agent/tree-sitter");
|
|
78
|
-
var _initialized = false;
|
|
79
|
-
var _descriptionParser;
|
|
80
|
-
var _behaviorParser;
|
|
81
|
-
var _bpInitialized = false;
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_zip_core = require("./zip-core-BjnIQMRA.cjs");
|
|
3
|
+
let module$1 = require("module");
|
|
4
|
+
let web_tree_sitter = require("web-tree-sitter");
|
|
5
|
+
let _dot_agent_parser_dsl = require("@dot-agent/parser-dsl");
|
|
6
|
+
let fs = require("fs");
|
|
7
|
+
let path = require("path");
|
|
8
|
+
let jszip = require("jszip");
|
|
9
|
+
jszip = require_zip_core.__toESM(jszip, 1);
|
|
10
|
+
let fs_promises = require("fs/promises");
|
|
11
|
+
let child_process = require("child_process");
|
|
12
|
+
let crypto = require("crypto");
|
|
13
|
+
//#region src/parser.ts
|
|
14
|
+
const { descriptionWasmPath, behaviorWasmPath } = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href)("@dot-agent/tree-sitter");
|
|
15
|
+
let _initialized = false;
|
|
16
|
+
let _descriptionParser;
|
|
17
|
+
let _behaviorParser;
|
|
18
|
+
let _bpInitialized = false;
|
|
82
19
|
async function initBehaviorParser() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
20
|
+
if (_bpInitialized) return;
|
|
21
|
+
await (0, _dot_agent_parser_dsl.init)();
|
|
22
|
+
_bpInitialized = true;
|
|
86
23
|
}
|
|
87
24
|
function parseBehaviorFile(text) {
|
|
88
|
-
|
|
25
|
+
return JSON.parse((0, _dot_agent_parser_dsl.parse_behavior)(text));
|
|
89
26
|
}
|
|
90
27
|
function parseDescriptionFile(text) {
|
|
91
|
-
|
|
28
|
+
return JSON.parse((0, _dot_agent_parser_dsl.parse_description)(text));
|
|
92
29
|
}
|
|
93
30
|
function getBehaviorScxml(text) {
|
|
94
|
-
|
|
31
|
+
return (0, _dot_agent_parser_dsl.get_graph)(text);
|
|
95
32
|
}
|
|
96
33
|
async function initParsers() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
34
|
+
if (_initialized) return;
|
|
35
|
+
await web_tree_sitter.Parser.init();
|
|
36
|
+
const descriptionLang = await web_tree_sitter.Language.load(descriptionWasmPath);
|
|
37
|
+
const behaviorLang = await web_tree_sitter.Language.load(behaviorWasmPath);
|
|
38
|
+
_descriptionParser = new web_tree_sitter.Parser();
|
|
39
|
+
_descriptionParser.setLanguage(descriptionLang);
|
|
40
|
+
_behaviorParser = new web_tree_sitter.Parser();
|
|
41
|
+
_behaviorParser.setLanguage(behaviorLang);
|
|
42
|
+
_initialized = true;
|
|
106
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse source text and return a tree-sitter Tree.
|
|
46
|
+
*
|
|
47
|
+
* @param langId - 'description' or 'behavior'
|
|
48
|
+
* @param text - source text to parse
|
|
49
|
+
* @param previousTree - optional previous tree for incremental parsing (LSP use)
|
|
50
|
+
*/
|
|
107
51
|
async function parse(langId, text, previousTree) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return parser.parse(text, previousTree);
|
|
52
|
+
await initParsers();
|
|
53
|
+
return (langId === "behavior" ? _behaviorParser : _descriptionParser).parse(text, previousTree);
|
|
111
54
|
}
|
|
55
|
+
/** Parse without initializing if parsers are already ready. Returns null if not initialized. */
|
|
112
56
|
function parseSync(langId, text, previousTree) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return parser.parse(text, previousTree);
|
|
57
|
+
if (!_initialized) return null;
|
|
58
|
+
return (langId === "behavior" ? _behaviorParser : _descriptionParser).parse(text, previousTree);
|
|
116
59
|
}
|
|
117
60
|
function nodesOfType(tree, type) {
|
|
118
|
-
|
|
119
|
-
|
|
61
|
+
if (!tree) return [];
|
|
62
|
+
return tree.rootNode.descendantsOfType(type).filter((n) => n !== null);
|
|
120
63
|
}
|
|
121
64
|
function nodeAtOffset(tree, offset) {
|
|
122
|
-
|
|
123
|
-
|
|
65
|
+
if (!tree) return null;
|
|
66
|
+
return tree.rootNode.descendantForIndex(offset);
|
|
124
67
|
}
|
|
125
68
|
function nodeToRange(node) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
69
|
+
return {
|
|
70
|
+
start: {
|
|
71
|
+
line: node.startPosition.row,
|
|
72
|
+
character: node.startPosition.column
|
|
73
|
+
},
|
|
74
|
+
end: {
|
|
75
|
+
line: node.endPosition.row,
|
|
76
|
+
character: node.endPosition.column
|
|
77
|
+
}
|
|
78
|
+
};
|
|
130
79
|
}
|
|
131
80
|
function positionToOffset(text, line, character) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
81
|
+
let offset = 0;
|
|
82
|
+
for (let i = 0; i < line; i++) {
|
|
83
|
+
const nl = text.indexOf("\n", offset);
|
|
84
|
+
offset = nl === -1 ? text.length : nl + 1;
|
|
85
|
+
}
|
|
86
|
+
return offset + character;
|
|
138
87
|
}
|
|
139
88
|
function getContextNode(tree, offset) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return node ?? tree.rootNode;
|
|
89
|
+
let node = nodeAtOffset(tree, offset);
|
|
90
|
+
while (node && (node.isError || node.isMissing)) node = node.parent;
|
|
91
|
+
if (!node || node.type === "ERROR") {
|
|
92
|
+
const parent = nodeAtOffset(tree, offset)?.parent;
|
|
93
|
+
if (parent) {
|
|
94
|
+
const siblings = parent.children;
|
|
95
|
+
const idx = siblings.findIndex((c) => c !== null && c.startIndex > offset);
|
|
96
|
+
const prev = idx > 0 ? siblings[idx - 1] : siblings[siblings.length - 1];
|
|
97
|
+
if (prev && !prev.isError) node = prev;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return node ?? tree.rootNode;
|
|
155
101
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
var import_fs = require("fs");
|
|
159
|
-
var import_path = require("path");
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/linter.ts
|
|
160
104
|
function nodePosition(node) {
|
|
161
|
-
|
|
105
|
+
return {
|
|
106
|
+
line: node.startPosition.row + 1,
|
|
107
|
+
col: node.startPosition.column + 1
|
|
108
|
+
};
|
|
162
109
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
110
|
+
const MISSING_HINTS = {
|
|
111
|
+
interact_stmt: "Missing 'interact' — oriented states require 'goal … interact … on intent … on offtopic'.",
|
|
112
|
+
offtopic_handler: "Missing 'on offtopic' handler — every state with 'interact' must handle the offtopic case.",
|
|
113
|
+
intent_handler: "Missing at least one 'on intent' handler after 'interact'.",
|
|
114
|
+
goal_stmt: "Missing 'goal' — every state with 'interact' must declare a goal.",
|
|
115
|
+
state_name: "Missing state name after 'state' keyword.",
|
|
116
|
+
quoted_string: "Missing quoted string — expected a value in double quotes."
|
|
170
117
|
};
|
|
171
118
|
function collectSyntaxErrors(root, messages, file) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
120
|
+
function push(node, message, code, severity = "error") {
|
|
121
|
+
const key = `${node.startIndex}:${node.endIndex}:${message}`;
|
|
122
|
+
if (seen.has(key)) return;
|
|
123
|
+
seen.add(key);
|
|
124
|
+
const { line, col } = nodePosition(node);
|
|
125
|
+
messages.push({
|
|
126
|
+
file,
|
|
127
|
+
line,
|
|
128
|
+
col,
|
|
129
|
+
severity,
|
|
130
|
+
code,
|
|
131
|
+
message
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function walk(node) {
|
|
135
|
+
if (node.isMissing) {
|
|
136
|
+
const hint = MISSING_HINTS[node.type];
|
|
137
|
+
push(node, hint ?? `Syntax error: missing '${node.type}'.`, "E004");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (node.type === "ERROR") {
|
|
141
|
+
if (!node.descendantsOfType("ERROR").some((e) => e !== null && e.id !== node.id)) {
|
|
142
|
+
const snippet = node.text.replace(/\s+/g, " ").trim().slice(0, 40);
|
|
143
|
+
push(node, snippet ? `Syntax error near '${snippet}'.` : "Syntax error.", "E004");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const child of node.children) if (child) walk(child);
|
|
148
|
+
}
|
|
149
|
+
walk(root);
|
|
199
150
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
151
|
+
const STRICT_BLOCK_TYPES = {
|
|
152
|
+
input_block: "input",
|
|
153
|
+
output_block: "output",
|
|
154
|
+
requires_block: "requires",
|
|
155
|
+
capabilities_block: "capabilities"
|
|
205
156
|
};
|
|
206
157
|
async function lintDescription(text, file = "agent.description") {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
messages.push({
|
|
264
|
-
file,
|
|
265
|
-
line,
|
|
266
|
-
col,
|
|
267
|
-
severity: "warning",
|
|
268
|
-
code: "W004",
|
|
269
|
-
message: `Type '${typeName}' is not declared in this file (assuming native or external).`
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return messages;
|
|
158
|
+
const tree = await parse("description", text);
|
|
159
|
+
const messages = [];
|
|
160
|
+
collectSyntaxErrors(tree.rootNode, messages, file);
|
|
161
|
+
for (const metaNode of nodesOfType(tree, "agent_meta")) {
|
|
162
|
+
if (metaNode.childForFieldName("key")?.text !== "domain") continue;
|
|
163
|
+
const valueNode = metaNode.childForFieldName("value");
|
|
164
|
+
if (valueNode?.text === "example.com") {
|
|
165
|
+
const { line, col } = nodePosition(valueNode);
|
|
166
|
+
messages.push({
|
|
167
|
+
file,
|
|
168
|
+
line,
|
|
169
|
+
col,
|
|
170
|
+
severity: "warning",
|
|
171
|
+
code: "W003",
|
|
172
|
+
message: "domain still has default value \"example.com\""
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!nodesOfType(tree, "agent_meta").some((n) => n.childForFieldName("key")?.text === "domain" && n.childForFieldName("value")?.text?.trim())) messages.push({
|
|
177
|
+
file,
|
|
178
|
+
line: 1,
|
|
179
|
+
col: 1,
|
|
180
|
+
severity: "warning",
|
|
181
|
+
code: "W007",
|
|
182
|
+
message: "No domain declared — agent will be packaged as unknown/name. Interoperability between runtimes is not guaranteed."
|
|
183
|
+
});
|
|
184
|
+
const behaviorBlocks = nodesOfType(tree, "behavior_block");
|
|
185
|
+
if (behaviorBlocks.length > 1) {
|
|
186
|
+
const { line, col } = nodePosition(behaviorBlocks[1]);
|
|
187
|
+
messages.push({
|
|
188
|
+
file,
|
|
189
|
+
line,
|
|
190
|
+
col,
|
|
191
|
+
severity: "error",
|
|
192
|
+
code: "E017",
|
|
193
|
+
message: "Multiple 'behavior' declarations — only one is allowed. To combine multiple files, use 'merge' in your .behavior file."
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const declaredTypes = new Set(nodesOfType(tree, "type_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean));
|
|
197
|
+
if (declaredTypes.size > 0) for (const blockType of Object.keys(STRICT_BLOCK_TYPES)) for (const blockNode of nodesOfType(tree, blockType)) for (const typeRefNode of blockNode.descendantsOfType("type_reference").filter((n) => n !== null)) {
|
|
198
|
+
const idNode = typeRefNode.firstNamedChild;
|
|
199
|
+
if (!idNode) continue;
|
|
200
|
+
const typeName = idNode.text;
|
|
201
|
+
if (!declaredTypes.has(typeName)) {
|
|
202
|
+
const { line, col } = nodePosition(idNode);
|
|
203
|
+
messages.push({
|
|
204
|
+
file,
|
|
205
|
+
line,
|
|
206
|
+
col,
|
|
207
|
+
severity: "warning",
|
|
208
|
+
code: "W004",
|
|
209
|
+
message: `Type '${typeName}' is not declared in this file (assuming native or external).`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return messages;
|
|
277
214
|
}
|
|
278
215
|
function levenshtein(a, b) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
);
|
|
284
|
-
for (let i = 1; i <= la; i++) {
|
|
285
|
-
for (let j = 1; j <= lb; j++) {
|
|
286
|
-
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]);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return dp[la][lb];
|
|
216
|
+
const la = a.length, lb = b.length;
|
|
217
|
+
const dp = Array.from({ length: la + 1 }, (_, i) => Array.from({ length: lb + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
218
|
+
for (let i = 1; i <= la; i++) for (let j = 1; j <= lb; j++) 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]);
|
|
219
|
+
return dp[la][lb];
|
|
290
220
|
}
|
|
291
221
|
function closestName(token, candidates) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
222
|
+
let best;
|
|
223
|
+
let bestDist = Infinity;
|
|
224
|
+
for (const name of candidates) {
|
|
225
|
+
const d = levenshtein(token.toLowerCase(), name.toLowerCase());
|
|
226
|
+
if (d > 0 && d <= 2 && d < bestDist) {
|
|
227
|
+
bestDist = d;
|
|
228
|
+
best = name;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return best;
|
|
302
232
|
}
|
|
303
|
-
|
|
233
|
+
const KERNEL_LIFECYCLE_NAMES = [
|
|
234
|
+
"init",
|
|
235
|
+
"welcome",
|
|
236
|
+
"end",
|
|
237
|
+
"online",
|
|
238
|
+
"offline"
|
|
239
|
+
];
|
|
304
240
|
function collectTransitionTargets(stmts) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
recurse(stmt["on_complete"]);
|
|
322
|
-
recurse(stmt["on_failure"]);
|
|
323
|
-
}
|
|
324
|
-
return targets;
|
|
241
|
+
const targets = [];
|
|
242
|
+
for (const stmt of stmts) {
|
|
243
|
+
if (!stmt || typeof stmt !== "object") continue;
|
|
244
|
+
if (stmt.type === "transition_stmt" && typeof stmt["state"] === "string") targets.push(stmt["state"]);
|
|
245
|
+
if (stmt.type === "intent_handler" && typeof stmt["body"] === "string") targets.push(stmt["body"]);
|
|
246
|
+
const recurse = (v) => {
|
|
247
|
+
if (Array.isArray(v)) targets.push(...collectTransitionTargets(v));
|
|
248
|
+
};
|
|
249
|
+
if (stmt.type !== "intent_handler") recurse(stmt["body"]);
|
|
250
|
+
recurse(stmt["handlers"]);
|
|
251
|
+
recurse(stmt["then"]);
|
|
252
|
+
recurse(stmt["else"]);
|
|
253
|
+
recurse(stmt["on_complete"]);
|
|
254
|
+
recurse(stmt["on_failure"]);
|
|
255
|
+
}
|
|
256
|
+
return targets;
|
|
325
257
|
}
|
|
326
258
|
function collectMergedStates(tree, docDir, states, visited = /* @__PURE__ */ new Set()) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
259
|
+
for (const mergeNode of nodesOfType(tree, "merge_decl")) {
|
|
260
|
+
const pathNode = mergeNode.childForFieldName("path");
|
|
261
|
+
if (!pathNode) continue;
|
|
262
|
+
const absPath = (0, path.resolve)(docDir, pathNode.text.replace(/^"|"$/g, ""));
|
|
263
|
+
if (visited.has(absPath)) continue;
|
|
264
|
+
visited.add(absPath);
|
|
265
|
+
let mergedText;
|
|
266
|
+
try {
|
|
267
|
+
mergedText = (0, fs.readFileSync)(absPath, "utf-8");
|
|
268
|
+
} catch {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const mergedTree = parseSync("behavior", mergedText);
|
|
272
|
+
if (!mergedTree) continue;
|
|
273
|
+
nodesOfType(mergedTree, "state_decl").forEach((n) => {
|
|
274
|
+
const name = n.childForFieldName("name")?.text;
|
|
275
|
+
if (name) states.add(name);
|
|
276
|
+
});
|
|
277
|
+
collectMergedStates(mergedTree, (0, path.dirname)(absPath), states, visited);
|
|
278
|
+
}
|
|
348
279
|
}
|
|
349
280
|
async function lintBehavior(text, file = "agent.behavior", docPath, consolidated = false, externalStates, mergedText) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
seenStates.add(state.name);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
if (!wholeTreeFsm.states.some((s) => s.name === "init")) {
|
|
648
|
-
messages.push({
|
|
649
|
-
file,
|
|
650
|
-
line: 1,
|
|
651
|
-
col: 1,
|
|
652
|
-
severity: "error",
|
|
653
|
-
code: "E016",
|
|
654
|
-
message: `Required 'init' state is missing. Add a state named 'init' as the entry point of your behavior.`
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
const seenTriggers = /* @__PURE__ */ new Set();
|
|
658
|
-
for (const trigger of wholeTreeFsm.global_triggers) {
|
|
659
|
-
if (seenTriggers.has(trigger.event)) {
|
|
660
|
-
messages.push({
|
|
661
|
-
file,
|
|
662
|
-
line: 1,
|
|
663
|
-
col: 1,
|
|
664
|
-
severity: "warning",
|
|
665
|
-
code: "W014",
|
|
666
|
-
message: `Duplicate global trigger '${trigger.event}' across merged files \u2014 only the last definition takes effect.`
|
|
667
|
-
});
|
|
668
|
-
} else {
|
|
669
|
-
seenTriggers.add(trigger.event);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return messages;
|
|
281
|
+
await initBehaviorParser();
|
|
282
|
+
if (!text.endsWith("\n")) text = text + "\n";
|
|
283
|
+
const tree = await parse("behavior", text);
|
|
284
|
+
const messages = [];
|
|
285
|
+
collectSyntaxErrors(tree.rootNode, messages, file);
|
|
286
|
+
for (const stateNode of nodesOfType(tree, "state_decl")) {
|
|
287
|
+
if (stateNode.hasError) continue;
|
|
288
|
+
const nameNode = stateNode.childForFieldName("name");
|
|
289
|
+
const stateName = nameNode?.text ?? "?";
|
|
290
|
+
const hasInteract = stateNode.descendantsOfType("interact_stmt").length > 0;
|
|
291
|
+
const hasGoal = stateNode.descendantsOfType("goal_stmt").length > 0;
|
|
292
|
+
const intentHandlers = stateNode.descendantsOfType("intent_handler");
|
|
293
|
+
if (hasInteract && !hasGoal) {
|
|
294
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
295
|
+
messages.push({
|
|
296
|
+
file,
|
|
297
|
+
line,
|
|
298
|
+
col,
|
|
299
|
+
severity: "warning",
|
|
300
|
+
code: "W013",
|
|
301
|
+
message: `'interact' without 'goal'. Add 'goal "..."' before 'interact'.`
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (hasGoal && !hasInteract) {
|
|
305
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
306
|
+
messages.push({
|
|
307
|
+
file,
|
|
308
|
+
line,
|
|
309
|
+
col,
|
|
310
|
+
severity: "warning",
|
|
311
|
+
code: "W012",
|
|
312
|
+
message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal'.`
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (hasInteract && intentHandlers.length === 0) {
|
|
316
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
317
|
+
messages.push({
|
|
318
|
+
file,
|
|
319
|
+
line,
|
|
320
|
+
col,
|
|
321
|
+
severity: "error",
|
|
322
|
+
code: "E009",
|
|
323
|
+
message: `Oriented state '${stateName}' has no 'on intent' handlers. At least one is required.`
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const seenIntents = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const handler of intentHandlers) {
|
|
328
|
+
const intentNode = handler.childForFieldName("intent");
|
|
329
|
+
if (!intentNode) continue;
|
|
330
|
+
const label = intentNode.text.replace(/^"|"$/g, "");
|
|
331
|
+
if (seenIntents.has(label)) {
|
|
332
|
+
const { line, col } = nodePosition(handler);
|
|
333
|
+
messages.push({
|
|
334
|
+
file,
|
|
335
|
+
line,
|
|
336
|
+
col,
|
|
337
|
+
severity: "error",
|
|
338
|
+
code: "W008",
|
|
339
|
+
message: `Duplicate 'on intent "${label}"' in state '${stateName}'. The FSM cannot route ambiguous intents — use a unique label for each handler.`
|
|
340
|
+
});
|
|
341
|
+
} else seenIntents.set(label, true);
|
|
342
|
+
}
|
|
343
|
+
for (const handler of intentHandlers) for (const t of handler.descendantsOfType("transition_stmt")) if (t.childForFieldName("state")?.text === stateName) {
|
|
344
|
+
const { line, col } = nodePosition(handler);
|
|
345
|
+
messages.push({
|
|
346
|
+
file,
|
|
347
|
+
line,
|
|
348
|
+
col,
|
|
349
|
+
severity: "warning",
|
|
350
|
+
code: "W011",
|
|
351
|
+
message: `'on intent' handler in state '${stateName}' transitions back to itself. The user expressed an intent but receives no progress — did you mean a different target state?`
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (stateName === "init" || stateName === "end") {
|
|
355
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
356
|
+
if (stateName === "init") messages.push({
|
|
357
|
+
file,
|
|
358
|
+
line,
|
|
359
|
+
col,
|
|
360
|
+
severity: "info",
|
|
361
|
+
code: "I001",
|
|
362
|
+
message: `State 'init' overrides the kernel's default entry lifecycle. The kernel will use this definition instead of its built-in init sequence.`
|
|
363
|
+
});
|
|
364
|
+
else messages.push({
|
|
365
|
+
file,
|
|
366
|
+
line,
|
|
367
|
+
col,
|
|
368
|
+
severity: "info",
|
|
369
|
+
code: "I002",
|
|
370
|
+
message: `State 'end' overrides the kernel's default terminal lifecycle. The kernel will treat this state as the canonical exit point.`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (!KERNEL_LIFECYCLE_NAMES.includes(stateName)) {
|
|
374
|
+
const closest = closestName(stateName, KERNEL_LIFECYCLE_NAMES);
|
|
375
|
+
if (closest) {
|
|
376
|
+
const { line, col } = nodePosition(nameNode ?? stateNode);
|
|
377
|
+
messages.push({
|
|
378
|
+
file,
|
|
379
|
+
line,
|
|
380
|
+
col,
|
|
381
|
+
severity: "hint",
|
|
382
|
+
code: "H001",
|
|
383
|
+
message: `State name '${stateName}' resembles the kernel lifecycle name '${closest}'. If this is intentional, ignore this hint.`
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const parallelNode of nodesOfType(tree, "parallel_stmt")) {
|
|
389
|
+
if (parallelNode.hasError) continue;
|
|
390
|
+
if (parallelNode.descendantsOfType("run_stmt").length === 0) {
|
|
391
|
+
const { line, col } = nodePosition(parallelNode);
|
|
392
|
+
let stateName = "?";
|
|
393
|
+
let ancestor = parallelNode.parent;
|
|
394
|
+
while (ancestor && ancestor.type !== "state_decl") ancestor = ancestor.parent;
|
|
395
|
+
if (ancestor) stateName = ancestor.childForFieldName("name")?.text ?? "?";
|
|
396
|
+
messages.push({
|
|
397
|
+
file,
|
|
398
|
+
line,
|
|
399
|
+
col,
|
|
400
|
+
severity: "warning",
|
|
401
|
+
code: "E010",
|
|
402
|
+
message: `'parallel' block in state '${stateName}' has no 'run' statements and does nothing.`
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
for (const afterNode of nodesOfType(tree, "after_stmt")) {
|
|
407
|
+
if (afterNode.hasError) continue;
|
|
408
|
+
const countNode = afterNode.childForFieldName("count");
|
|
409
|
+
if (countNode?.text === "0") {
|
|
410
|
+
const { line, col } = nodePosition(countNode);
|
|
411
|
+
messages.push({
|
|
412
|
+
file,
|
|
413
|
+
line,
|
|
414
|
+
col,
|
|
415
|
+
severity: "error",
|
|
416
|
+
code: "E011",
|
|
417
|
+
message: `'after 0' is invalid — prompts must be ≥ 1. Use 'after 1' for the next prompt.`
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
for (const stmtNode of nodesOfType(tree, "goal_stmt")) {
|
|
422
|
+
const strNode = stmtNode.descendantsOfType("quoted_string").find((n) => n !== null);
|
|
423
|
+
if (!strNode) continue;
|
|
424
|
+
const content = strNode.text.replace(/^"|"$/g, "");
|
|
425
|
+
if (content.length > 280) {
|
|
426
|
+
const { line, col } = nodePosition(strNode);
|
|
427
|
+
messages.push({
|
|
428
|
+
file,
|
|
429
|
+
line,
|
|
430
|
+
col,
|
|
431
|
+
severity: "warning",
|
|
432
|
+
code: "W002",
|
|
433
|
+
message: `Text block exceeds 280 characters (${content.length}). Consider using 'guide' to load long orientation text from an external text file.`
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const stmtNode of nodesOfType(tree, "guide_stmt")) {
|
|
438
|
+
const strNode = stmtNode.descendantsOfType("quoted_string").find((n) => n !== null);
|
|
439
|
+
if (!strNode) continue;
|
|
440
|
+
const content = strNode.text.replace(/^"|"$/g, "");
|
|
441
|
+
if (content.length > 280) {
|
|
442
|
+
const { line, col } = nodePosition(strNode);
|
|
443
|
+
let p = stmtNode.parent;
|
|
444
|
+
while (p && p.type !== "state_decl") p = p.parent;
|
|
445
|
+
const stateName = p?.childForFieldName("name")?.text ?? "?";
|
|
446
|
+
messages.push({
|
|
447
|
+
file,
|
|
448
|
+
line,
|
|
449
|
+
col,
|
|
450
|
+
severity: "warning",
|
|
451
|
+
code: "W010",
|
|
452
|
+
message: `'guide' text in state '${stateName}' is ${content.length} characters. Consider using an external file.`
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const definedStates = new Set(nodesOfType(tree, "state_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean));
|
|
457
|
+
if (docPath) try {
|
|
458
|
+
collectMergedStates(tree, (0, path.dirname)(docPath), definedStates);
|
|
459
|
+
} catch {}
|
|
460
|
+
if (externalStates) for (const name of externalStates) definedStates.add(name);
|
|
461
|
+
const stateNameList = Array.from(definedStates);
|
|
462
|
+
for (const transitionNode of nodesOfType(tree, "transition_stmt")) {
|
|
463
|
+
const stateNode = transitionNode.childForFieldName("state");
|
|
464
|
+
if (!stateNode) continue;
|
|
465
|
+
const target = stateNode.text;
|
|
466
|
+
if (!definedStates.has(target)) {
|
|
467
|
+
const { line, col } = nodePosition(stateNode);
|
|
468
|
+
const isExternal = target.includes(".");
|
|
469
|
+
const hint = !isExternal ? closestName(target, stateNameList) : void 0;
|
|
470
|
+
messages.push({
|
|
471
|
+
file,
|
|
472
|
+
line,
|
|
473
|
+
col,
|
|
474
|
+
severity: isExternal ? "warning" : "error",
|
|
475
|
+
code: isExternal ? "W005" : "E005",
|
|
476
|
+
message: isExternal ? `State '${target}' is not defined locally (assuming external flow reference).` : `State '${target}' is not defined in this file.`,
|
|
477
|
+
...hint ? { hint: `did you mean '${hint}'?` } : {}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const interactNode of nodesOfType(tree, "interact_stmt")) {
|
|
482
|
+
let ancestor = interactNode.parent;
|
|
483
|
+
while (ancestor && ancestor.type !== "state_decl") ancestor = ancestor.parent;
|
|
484
|
+
if (!ancestor) continue;
|
|
485
|
+
if (!(ancestor.descendantsOfType("intent_handler").length + ancestor.descendantsOfType("offtopic_handler").length > 0)) {
|
|
486
|
+
const { line, col } = nodePosition(interactNode);
|
|
487
|
+
messages.push({
|
|
488
|
+
file,
|
|
489
|
+
line,
|
|
490
|
+
col,
|
|
491
|
+
severity: "warning",
|
|
492
|
+
code: "W006",
|
|
493
|
+
message: "This state calls interact but has no handlers (on intent/offtopic). This will trap the agent."
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const fsmResult = parseBehaviorFile(text);
|
|
498
|
+
if (fsmResult.ok === null) {
|
|
499
|
+
const firstMsg = fsmResult.diagnostics[0]?.message ?? "Parse error";
|
|
500
|
+
messages.push({
|
|
501
|
+
file,
|
|
502
|
+
line: 1,
|
|
503
|
+
col: 1,
|
|
504
|
+
severity: "error",
|
|
505
|
+
code: "E006",
|
|
506
|
+
message: `Parse error: ${firstMsg}`
|
|
507
|
+
});
|
|
508
|
+
return messages;
|
|
509
|
+
}
|
|
510
|
+
const fsm = fsmResult.ok;
|
|
511
|
+
const wholeFsm = mergedText !== void 0 ? parseBehaviorFile(mergedText).ok : null;
|
|
512
|
+
const reachabilityStates = wholeFsm?.states ?? fsm.states;
|
|
513
|
+
const allTargets = new Set(collectTransitionTargets(reachabilityStates.flatMap((s) => s.body)));
|
|
514
|
+
for (const state of fsm.states) {
|
|
515
|
+
const hasOutgoing = collectTransitionTargets(state.body).length > 0;
|
|
516
|
+
if (!allTargets.has(state.name) && !hasOutgoing) messages.push({
|
|
517
|
+
file,
|
|
518
|
+
line: 1,
|
|
519
|
+
col: 1,
|
|
520
|
+
severity: "warning",
|
|
521
|
+
code: "W001",
|
|
522
|
+
message: `State "${state.name}" has no transitions`
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
for (let i = 1; i < fsm.states.length; i++) {
|
|
526
|
+
const state = fsm.states[i];
|
|
527
|
+
if (!allTargets.has(state.name)) {
|
|
528
|
+
const stateNode = nodesOfType(tree, "state_decl").find((n) => n.childForFieldName("name")?.text === state.name);
|
|
529
|
+
const pos = stateNode ? nodePosition(stateNode) : {
|
|
530
|
+
line: 1,
|
|
531
|
+
col: 1
|
|
532
|
+
};
|
|
533
|
+
messages.push({
|
|
534
|
+
file,
|
|
535
|
+
line: pos.line,
|
|
536
|
+
col: pos.col,
|
|
537
|
+
severity: "warning",
|
|
538
|
+
code: "W009",
|
|
539
|
+
message: `State '${state.name}' is unreachable — no other state transitions to it.`
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (consolidated) {
|
|
544
|
+
const wholeTreeFsm = wholeFsm ?? fsm;
|
|
545
|
+
const seenStates = /* @__PURE__ */ new Set();
|
|
546
|
+
for (const state of wholeTreeFsm.states) if (seenStates.has(state.name)) messages.push({
|
|
547
|
+
file,
|
|
548
|
+
line: 1,
|
|
549
|
+
col: 1,
|
|
550
|
+
severity: "error",
|
|
551
|
+
code: "E015",
|
|
552
|
+
message: `Duplicate state '${state.name}' across merged files — each state name must be unique in the consolidated FSM.`
|
|
553
|
+
});
|
|
554
|
+
else seenStates.add(state.name);
|
|
555
|
+
if (!wholeTreeFsm.states.some((s) => s.name === "init")) messages.push({
|
|
556
|
+
file,
|
|
557
|
+
line: 1,
|
|
558
|
+
col: 1,
|
|
559
|
+
severity: "error",
|
|
560
|
+
code: "E016",
|
|
561
|
+
message: `Required 'init' state is missing. Add a state named 'init' as the entry point of your behavior.`
|
|
562
|
+
});
|
|
563
|
+
const seenTriggers = /* @__PURE__ */ new Set();
|
|
564
|
+
for (const trigger of wholeTreeFsm.global_triggers) if (seenTriggers.has(trigger.event)) messages.push({
|
|
565
|
+
file,
|
|
566
|
+
line: 1,
|
|
567
|
+
col: 1,
|
|
568
|
+
severity: "warning",
|
|
569
|
+
code: "W014",
|
|
570
|
+
message: `Duplicate global trigger '${trigger.event}' across merged files — only the last definition takes effect.`
|
|
571
|
+
});
|
|
572
|
+
else seenTriggers.add(trigger.event);
|
|
573
|
+
}
|
|
574
|
+
return messages;
|
|
674
575
|
}
|
|
675
576
|
async function createLinter() {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
var DSL_VERSION = "0.1";
|
|
681
|
-
var COMPILER_VERSION = "0.10.0";
|
|
682
|
-
|
|
683
|
-
// src/manifest.ts
|
|
684
|
-
function parseAboutme(json) {
|
|
685
|
-
if (!json.dslVersion) throw new Error("Missing dslVersion in aboutme.json");
|
|
686
|
-
if (!json.id) throw new Error("Missing id in aboutme.json");
|
|
687
|
-
if (!json.name) throw new Error("Missing name in aboutme.json");
|
|
688
|
-
if (!json.description) throw new Error("Missing description in aboutme.json");
|
|
689
|
-
if (!json.domain) throw new Error("Missing domain in aboutme.json");
|
|
690
|
-
if (!json.compiler) throw new Error("Missing compiler in aboutme.json");
|
|
691
|
-
if (!Array.isArray(json.capabilities)) throw new Error("Missing capabilities array in aboutme.json");
|
|
692
|
-
if (!Array.isArray(json.requires)) throw new Error("Missing requires array in aboutme.json");
|
|
693
|
-
if (!json.integrity) throw new Error("Missing integrity in aboutme.json");
|
|
694
|
-
return {
|
|
695
|
-
dslVersion: json.dslVersion,
|
|
696
|
-
id: json.id,
|
|
697
|
-
name: json.name,
|
|
698
|
-
description: json.description,
|
|
699
|
-
version: json.version ?? "",
|
|
700
|
-
domain: json.domain,
|
|
701
|
-
license: json.license ?? "",
|
|
702
|
-
persona: json.persona,
|
|
703
|
-
purpose: json.purpose ?? "unknown",
|
|
704
|
-
compiler: json.compiler,
|
|
705
|
-
commit: json.commit,
|
|
706
|
-
capabilities: json.capabilities,
|
|
707
|
-
requires: json.requires,
|
|
708
|
-
integrity: json.integrity
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
function buildAboutme(opts) {
|
|
712
|
-
return {
|
|
713
|
-
dslVersion: `dot-agent/${DSL_VERSION}`,
|
|
714
|
-
id: opts.id,
|
|
715
|
-
name: opts.name,
|
|
716
|
-
description: opts.description,
|
|
717
|
-
version: opts.version,
|
|
718
|
-
domain: opts.domain,
|
|
719
|
-
license: opts.license ?? "",
|
|
720
|
-
persona: opts.persona,
|
|
721
|
-
purpose: opts.purpose ?? "unknown",
|
|
722
|
-
compiler: opts.compiler,
|
|
723
|
-
commit: opts.commit,
|
|
724
|
-
capabilities: opts.capabilities ?? [],
|
|
725
|
-
requires: opts.requires ?? [],
|
|
726
|
-
integrity: opts.integrity
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
function aboutmeToJson(aboutme) {
|
|
730
|
-
return JSON.stringify(aboutme, null, 2);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// src/id.ts
|
|
734
|
-
var KNOWN_PLATFORMS = ["github.com", "gitlab.com", "codeberg.org", "sr.ht"];
|
|
735
|
-
function parseNamespaceName(identifier) {
|
|
736
|
-
const atIdx = identifier.indexOf("@");
|
|
737
|
-
if (atIdx !== -1 && (identifier.indexOf("/") === -1 || atIdx < identifier.indexOf("/"))) {
|
|
738
|
-
const slashIdx = identifier.indexOf("/");
|
|
739
|
-
if (slashIdx === -1) {
|
|
740
|
-
throw new Error(`Invalid agent ID: email namespace requires a name after '/' (got '${identifier}')`);
|
|
741
|
-
}
|
|
742
|
-
return {
|
|
743
|
-
namespace: identifier.slice(0, slashIdx),
|
|
744
|
-
name: identifier.slice(slashIdx + 1)
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
for (const platform of KNOWN_PLATFORMS) {
|
|
748
|
-
if (identifier.startsWith(platform + "/")) {
|
|
749
|
-
const afterPlatform = identifier.slice(platform.length + 1);
|
|
750
|
-
const slashIdx = afterPlatform.indexOf("/");
|
|
751
|
-
if (slashIdx === -1) {
|
|
752
|
-
throw new Error(
|
|
753
|
-
`Invalid agent ID: '${platform}' namespace requires a user segment and a name (got '${identifier}')`
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
return {
|
|
757
|
-
namespace: platform + "/" + afterPlatform.slice(0, slashIdx),
|
|
758
|
-
name: afterPlatform.slice(slashIdx + 1)
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
const lastSlash = identifier.lastIndexOf("/");
|
|
763
|
-
if (lastSlash === -1) {
|
|
764
|
-
throw new Error(`Invalid agent ID: missing '/' separator in '${identifier}'`);
|
|
765
|
-
}
|
|
766
|
-
return {
|
|
767
|
-
namespace: identifier.slice(0, lastSlash),
|
|
768
|
-
name: identifier.slice(lastSlash + 1)
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
function parseId(id) {
|
|
772
|
-
if (!id) throw new Error("Invalid agent ID: empty string");
|
|
773
|
-
const colonIdx = id.indexOf(":");
|
|
774
|
-
let identifier;
|
|
775
|
-
let version;
|
|
776
|
-
let digest;
|
|
777
|
-
if (colonIdx === -1) {
|
|
778
|
-
identifier = id;
|
|
779
|
-
} else {
|
|
780
|
-
identifier = id.slice(0, colonIdx);
|
|
781
|
-
const rest = id.slice(colonIdx + 1);
|
|
782
|
-
if (!rest) throw new Error(`Invalid agent ID: empty version after ':' in '${id}'`);
|
|
783
|
-
const tildeIdx = rest.indexOf("~");
|
|
784
|
-
if (tildeIdx === -1) {
|
|
785
|
-
version = rest;
|
|
786
|
-
} else {
|
|
787
|
-
version = rest.slice(0, tildeIdx);
|
|
788
|
-
digest = rest.slice(tildeIdx + 1);
|
|
789
|
-
if (!version) throw new Error(`Invalid agent ID: empty version before '~' in '${id}'`);
|
|
790
|
-
if (!digest) throw new Error(`Invalid agent ID: empty digest after '~' in '${id}'`);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
const { namespace, name } = parseNamespaceName(identifier);
|
|
794
|
-
if (!namespace) throw new Error(`Invalid agent ID: empty namespace in '${id}'`);
|
|
795
|
-
if (!name) throw new Error(`Invalid agent ID: empty name in '${id}'`);
|
|
796
|
-
return { namespace, name, version, digest };
|
|
797
|
-
}
|
|
798
|
-
function buildId(parts) {
|
|
799
|
-
const { namespace, name, version, digest } = parts;
|
|
800
|
-
if (!namespace || !name) {
|
|
801
|
-
throw new Error("Missing required ID parts: namespace and name");
|
|
802
|
-
}
|
|
803
|
-
if (digest && !version) {
|
|
804
|
-
throw new Error("Invalid ID parts: digest requires version");
|
|
805
|
-
}
|
|
806
|
-
let result = `${namespace}/${name}`;
|
|
807
|
-
if (version) {
|
|
808
|
-
result += `:${version}`;
|
|
809
|
-
if (digest) result += `~${digest}`;
|
|
810
|
-
}
|
|
811
|
-
return result.toLowerCase();
|
|
812
|
-
}
|
|
813
|
-
function slugify(name) {
|
|
814
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
815
|
-
}
|
|
816
|
-
var VERSION_RE = /^v?\d+(\.\d+){0,2}(-[0-9A-Za-z.]+)?(\+[0-9A-Za-z.]+)?$/i;
|
|
817
|
-
function isValidVersion(version) {
|
|
818
|
-
return VERSION_RE.test(version);
|
|
819
|
-
}
|
|
820
|
-
function extractDigest(id) {
|
|
821
|
-
try {
|
|
822
|
-
return parseId(id).digest ?? "";
|
|
823
|
-
} catch {
|
|
824
|
-
return "";
|
|
825
|
-
}
|
|
577
|
+
return {
|
|
578
|
+
lintDescription,
|
|
579
|
+
lintBehavior
|
|
580
|
+
};
|
|
826
581
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return parseId(id).name;
|
|
830
|
-
} catch {
|
|
831
|
-
return "";
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// src/zip.ts
|
|
836
|
-
var import_jszip2 = __toESM(require("jszip"), 1);
|
|
837
|
-
var import_promises = require("fs/promises");
|
|
838
|
-
|
|
839
|
-
// src/zip-core.ts
|
|
840
|
-
var import_jszip = __toESM(require("jszip"), 1);
|
|
841
|
-
var MAX_ZIP_SIZE = 500 * 1024 * 1024;
|
|
842
|
-
var MAX_COMPRESSION_RATIO = 100;
|
|
843
|
-
function validateMagicBytes(bytes) {
|
|
844
|
-
if (!(bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4)) {
|
|
845
|
-
throw new Error("Not a valid .agent file (invalid magic bytes)");
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function validateZipBomb(zip, compressedSize) {
|
|
849
|
-
let totalUncompressed = 0;
|
|
850
|
-
zip.forEach((_path, file) => {
|
|
851
|
-
if (!file.dir) totalUncompressed += file._data?.uncompressedSize || 0;
|
|
852
|
-
});
|
|
853
|
-
if (totalUncompressed > MAX_ZIP_SIZE) {
|
|
854
|
-
throw new Error(`Bundle exceeds 500MB uncompressed: ${totalUncompressed}`);
|
|
855
|
-
}
|
|
856
|
-
if (totalUncompressed / compressedSize > MAX_COMPRESSION_RATIO) {
|
|
857
|
-
throw new Error(`Compression ratio exceeds 100x: ${(totalUncompressed / compressedSize).toFixed(1)}x`);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
function createZip() {
|
|
861
|
-
return new import_jszip.default();
|
|
862
|
-
}
|
|
863
|
-
async function extractFiles(zip, filter) {
|
|
864
|
-
const files = /* @__PURE__ */ new Map();
|
|
865
|
-
const promises = [];
|
|
866
|
-
zip.forEach((relativePath, file) => {
|
|
867
|
-
if (file.dir) return;
|
|
868
|
-
if (filter && !filter.some((f) => relativePath.startsWith(f))) return;
|
|
869
|
-
promises.push(
|
|
870
|
-
file.async("text").then((content) => {
|
|
871
|
-
files.set(relativePath, content);
|
|
872
|
-
})
|
|
873
|
-
);
|
|
874
|
-
});
|
|
875
|
-
await Promise.all(promises);
|
|
876
|
-
return files;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// src/zip.ts
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/zip.ts
|
|
880
584
|
async function readZip(filePath) {
|
|
881
|
-
|
|
882
|
-
|
|
585
|
+
const data = await (0, fs_promises.readFile)(filePath);
|
|
586
|
+
return jszip.default.loadAsync(data);
|
|
883
587
|
}
|
|
884
|
-
async function
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return true;
|
|
588
|
+
async function validateZipBomb(filePath) {
|
|
589
|
+
const data = await (0, fs_promises.readFile)(filePath);
|
|
590
|
+
require_zip_core.validateZipBomb(await jszip.default.loadAsync(data), data.length);
|
|
591
|
+
return true;
|
|
889
592
|
}
|
|
890
|
-
async function
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
return true;
|
|
593
|
+
async function validateMagicBytes(filePath) {
|
|
594
|
+
require_zip_core.validateMagicBytes(await (0, fs_promises.readFile)(filePath));
|
|
595
|
+
return true;
|
|
894
596
|
}
|
|
895
597
|
async function writeZip(zip, outPath) {
|
|
896
|
-
|
|
897
|
-
|
|
598
|
+
const data = await zip.generateAsync({ type: "arraybuffer" });
|
|
599
|
+
await (0, fs_promises.writeFile)(outPath, Buffer.from(data));
|
|
898
600
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
var import_path2 = require("path");
|
|
903
|
-
var import_child_process = require("child_process");
|
|
904
|
-
var import_crypto = require("crypto");
|
|
905
|
-
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
906
|
-
|
|
907
|
-
// src/schema.ts
|
|
908
|
-
var STD_BASE = "https://dot-agent.dev/schema/std/v1/";
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/schema.ts
|
|
603
|
+
const STD_BASE = "https://dot-agent.dev/schema/std/v1/";
|
|
909
604
|
function propertyTypeToSchema(pt) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
605
|
+
switch (pt.kind) {
|
|
606
|
+
case "primitive": return { type: pt.value };
|
|
607
|
+
case "reference": return pt.value.startsWith("std.") ? { $ref: `${STD_BASE}${pt.value.slice(4)}.json` } : { $ref: `#/$defs/${pt.value}` };
|
|
608
|
+
case "array": return {
|
|
609
|
+
type: "array",
|
|
610
|
+
items: propertyTypeToSchema(pt.value)
|
|
611
|
+
};
|
|
612
|
+
case "enum": return {
|
|
613
|
+
type: "string",
|
|
614
|
+
enum: pt.value
|
|
615
|
+
};
|
|
616
|
+
}
|
|
920
617
|
}
|
|
921
618
|
function typeDefinitionToSchema(td) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
...required.length ? { required } : {}
|
|
939
|
-
};
|
|
619
|
+
const required = td.properties.filter((p) => !p.is_optional).map((p) => p.name);
|
|
620
|
+
const properties = {};
|
|
621
|
+
for (const p of td.properties) properties[p.name] = {
|
|
622
|
+
...propertyTypeToSchema(p.type),
|
|
623
|
+
...p.description ? { description: p.description } : {}
|
|
624
|
+
};
|
|
625
|
+
return {
|
|
626
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
627
|
+
"x-category": td.category.uri,
|
|
628
|
+
...td.category.label ? { "x-category-label": td.category.label } : {},
|
|
629
|
+
...td.concept ? { "x-concept": td.concept.uri } : {},
|
|
630
|
+
...td.concept?.label ? { "x-concept-label": td.concept.label } : {},
|
|
631
|
+
type: "object",
|
|
632
|
+
properties,
|
|
633
|
+
...required.length ? { required } : {}
|
|
634
|
+
};
|
|
940
635
|
}
|
|
941
636
|
function refFromAnnotatedRef(ref) {
|
|
942
|
-
|
|
637
|
+
return ref.name.startsWith("std.") ? { $ref: `${STD_BASE}${ref.name.slice(4)}.json` } : { $ref: `#/$defs/${ref.name}` };
|
|
943
638
|
}
|
|
639
|
+
/** Returns JSON Schema 2020-12 string for types.json, or null if nothing to write. */
|
|
944
640
|
function buildTypesJson(df) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
...df.output.length ? { output: df.output.map(refFromAnnotatedRef) } : {},
|
|
954
|
-
...Object.keys(defs).length ? { $defs: defs } : {}
|
|
955
|
-
},
|
|
956
|
-
null,
|
|
957
|
-
2
|
|
958
|
-
);
|
|
641
|
+
if (df.types.length === 0 && df.input.length === 0 && df.output.length === 0) return null;
|
|
642
|
+
const defs = {};
|
|
643
|
+
for (const td of df.types) defs[td.name] = typeDefinitionToSchema(td);
|
|
644
|
+
return JSON.stringify({
|
|
645
|
+
...df.input.length ? { input: df.input.map(refFromAnnotatedRef) } : {},
|
|
646
|
+
...df.output.length ? { output: df.output.map(refFromAnnotatedRef) } : {},
|
|
647
|
+
...Object.keys(defs).length ? { $defs: defs } : {}
|
|
648
|
+
}, null, 2);
|
|
959
649
|
}
|
|
960
|
-
|
|
961
|
-
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region src/pack.ts
|
|
962
652
|
function gitRevParseShort() {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
653
|
+
try {
|
|
654
|
+
return (0, child_process.execSync)("git rev-parse --short HEAD", {
|
|
655
|
+
stdio: "pipe",
|
|
656
|
+
encoding: "utf-8"
|
|
657
|
+
}).trim();
|
|
658
|
+
} catch {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
968
661
|
}
|
|
969
662
|
async function resolveCommit(explicit) {
|
|
970
|
-
|
|
971
|
-
|
|
663
|
+
if (explicit) return explicit;
|
|
664
|
+
return gitRevParseShort() ?? void 0;
|
|
972
665
|
}
|
|
973
666
|
function recentTagCandidates(limit = 5) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
667
|
+
let tags;
|
|
668
|
+
try {
|
|
669
|
+
tags = (0, child_process.execSync)("git tag --sort=-creatordate", {
|
|
670
|
+
stdio: "pipe",
|
|
671
|
+
encoding: "utf-8"
|
|
672
|
+
}).trim().split("\n").filter(Boolean);
|
|
673
|
+
} catch {
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
return tags.slice(0, limit).map((tag) => {
|
|
677
|
+
const atIdx = tag.lastIndexOf("@");
|
|
678
|
+
const version = atIdx === -1 ? tag : tag.slice(atIdx + 1);
|
|
679
|
+
return {
|
|
680
|
+
version,
|
|
681
|
+
label: atIdx === -1 ? tag : `${version} (tag: ${tag})`
|
|
682
|
+
};
|
|
683
|
+
}).filter((c) => require_zip_core.isValidVersion(c.version));
|
|
986
684
|
}
|
|
987
|
-
|
|
988
|
-
|
|
685
|
+
const CUSTOM_VERSION = "__dot-agent-custom-version__";
|
|
686
|
+
const BARE_VERSION = "__dot-agent-bare-version__";
|
|
989
687
|
async function resolveVersionInteractive(explicit) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
688
|
+
if (explicit) {
|
|
689
|
+
if (!require_zip_core.isValidVersion(explicit)) throw new Error(`E019: Invalid version format '${explicit}' — expected vX.Y[.Z][-prerelease] or X.Y[.Z][-prerelease]`);
|
|
690
|
+
return explicit;
|
|
691
|
+
}
|
|
692
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
693
|
+
process.stderr.write("[dot-agent] no TTY and no --version — packing without a version\n");
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const candidates = recentTagCandidates();
|
|
697
|
+
const clack = await import("@clack/prompts");
|
|
698
|
+
const choice = await clack.select({
|
|
699
|
+
message: "Choose a version for this package",
|
|
700
|
+
options: [
|
|
701
|
+
...candidates.map((c) => ({
|
|
702
|
+
value: c.version,
|
|
703
|
+
label: c.label
|
|
704
|
+
})),
|
|
705
|
+
{
|
|
706
|
+
value: CUSTOM_VERSION,
|
|
707
|
+
label: "Type a custom version…"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
value: BARE_VERSION,
|
|
711
|
+
label: "None (bare — no version)"
|
|
712
|
+
}
|
|
713
|
+
]
|
|
714
|
+
});
|
|
715
|
+
if (clack.isCancel(choice)) {
|
|
716
|
+
clack.cancel("Pack cancelled.");
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
if (choice === BARE_VERSION) return void 0;
|
|
720
|
+
if (choice === CUSTOM_VERSION) {
|
|
721
|
+
const typed = await clack.text({
|
|
722
|
+
message: "Version",
|
|
723
|
+
validate: (v) => v && require_zip_core.isValidVersion(v) ? void 0 : "Invalid version format — expected vX.Y[.Z][-prerelease] or X.Y[.Z][-prerelease]"
|
|
724
|
+
});
|
|
725
|
+
if (clack.isCancel(typed)) {
|
|
726
|
+
clack.cancel("Pack cancelled.");
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
return typed;
|
|
730
|
+
}
|
|
731
|
+
return choice;
|
|
1027
732
|
}
|
|
1028
733
|
function isExternalPath(agentRoot, declaredPath) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return (0, import_path2.relative)(normRoot, resolved).startsWith("..");
|
|
734
|
+
if ((0, path.isAbsolute)(declaredPath)) return true;
|
|
735
|
+
const normRoot = (0, path.normalize)((0, path.resolve)(agentRoot));
|
|
736
|
+
return (0, path.relative)(normRoot, (0, path.resolve)(normRoot, declaredPath)).startsWith("..");
|
|
1033
737
|
}
|
|
1034
738
|
async function discoverDescriptionFile(dir, explicit) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
if (descFiles.length > 1) {
|
|
1049
|
-
throw new Error(`E003: Multiple .description files found: ${descFiles.join(", ")} \u2014 use PackOptions.description to specify one`);
|
|
1050
|
-
}
|
|
1051
|
-
const name = descFiles[0];
|
|
1052
|
-
if (name !== "agent.description") {
|
|
1053
|
-
console.info(`[dot-agent] using description file: ${name}`);
|
|
1054
|
-
}
|
|
1055
|
-
return name;
|
|
739
|
+
if (explicit) try {
|
|
740
|
+
await (0, fs_promises.stat)((0, path.join)(dir, explicit));
|
|
741
|
+
return explicit;
|
|
742
|
+
} catch {
|
|
743
|
+
throw new Error(`E003: Description file '${explicit}' not found in ${dir}`);
|
|
744
|
+
}
|
|
745
|
+
const descFiles = (await (0, fs_promises.readdir)(dir)).filter((f) => f.endsWith(".description"));
|
|
746
|
+
if (descFiles.length === 0) throw new Error(`E003: No .description file found in ${dir}`);
|
|
747
|
+
if (descFiles.length > 1) throw new Error(`E003: Multiple .description files found: ${descFiles.join(", ")} — use PackOptions.description to specify one`);
|
|
748
|
+
const name = descFiles[0];
|
|
749
|
+
if (name !== "agent.description") console.info(`[dot-agent] using description file: ${name}`);
|
|
750
|
+
return name;
|
|
1056
751
|
}
|
|
1057
752
|
async function consolidate(agentRoot, entryFile) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
order.push(relPath);
|
|
1094
|
-
}
|
|
1095
|
-
await dfs(entryFile);
|
|
1096
|
-
const texts = order.map((relPath) => textCache.get((0, import_path2.resolve)(normRoot, relPath)));
|
|
1097
|
-
return {
|
|
1098
|
-
mergedText: texts.join("\n"),
|
|
1099
|
-
mergeSources: order
|
|
1100
|
-
};
|
|
753
|
+
await initBehaviorParser();
|
|
754
|
+
const normRoot = (0, path.normalize)((0, path.resolve)(agentRoot));
|
|
755
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
756
|
+
const visited = /* @__PURE__ */ new Set();
|
|
757
|
+
const order = [];
|
|
758
|
+
const textCache = /* @__PURE__ */ new Map();
|
|
759
|
+
async function dfs(relPath) {
|
|
760
|
+
const absPath = (0, path.resolve)(normRoot, relPath);
|
|
761
|
+
if (visiting.has(absPath)) throw new Error(`E013: Circular merge dependency involving '${relPath}'`);
|
|
762
|
+
if (visited.has(absPath)) return;
|
|
763
|
+
visiting.add(absPath);
|
|
764
|
+
let fileText;
|
|
765
|
+
try {
|
|
766
|
+
fileText = await (0, fs_promises.readFile)(absPath, "utf-8");
|
|
767
|
+
} catch {
|
|
768
|
+
throw new Error(`E012: Merge target not found: '${relPath}'`);
|
|
769
|
+
}
|
|
770
|
+
textCache.set(absPath, fileText);
|
|
771
|
+
const merges = parseBehaviorFile(fileText).ok?.merges ?? [];
|
|
772
|
+
for (const mergePath of merges) {
|
|
773
|
+
if ((0, path.isAbsolute)(mergePath)) throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' is absolute — only paths within the agent root are allowed`);
|
|
774
|
+
const mergeAbs = (0, path.resolve)((0, path.dirname)(absPath), mergePath);
|
|
775
|
+
const mergeRel = (0, path.relative)(normRoot, mergeAbs);
|
|
776
|
+
if (mergeRel.startsWith("..")) throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' escapes the agent root`);
|
|
777
|
+
await dfs(mergeRel);
|
|
778
|
+
}
|
|
779
|
+
visiting.delete(absPath);
|
|
780
|
+
visited.add(absPath);
|
|
781
|
+
order.push(relPath);
|
|
782
|
+
}
|
|
783
|
+
await dfs(entryFile);
|
|
784
|
+
return {
|
|
785
|
+
mergedText: order.map((relPath) => textCache.get((0, path.resolve)(normRoot, relPath))).join("\n"),
|
|
786
|
+
mergeSources: order
|
|
787
|
+
};
|
|
1101
788
|
}
|
|
1102
|
-
|
|
789
|
+
const FILE_REF_RE = /\.(txt|md)$/i;
|
|
1103
790
|
function collectFileRefs(stmts, out) {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
791
|
+
for (const s of stmts) switch (s.type) {
|
|
792
|
+
case "guide_stmt":
|
|
793
|
+
if (FILE_REF_RE.test(s.text)) out.set(`guide:${s.text}`, {
|
|
794
|
+
kind: "guide",
|
|
795
|
+
text: s.text
|
|
796
|
+
});
|
|
797
|
+
break;
|
|
798
|
+
case "teach_stmt":
|
|
799
|
+
if (FILE_REF_RE.test(s.text)) out.set(`teach:${s.text}`, {
|
|
800
|
+
kind: "teach",
|
|
801
|
+
text: s.text
|
|
802
|
+
});
|
|
803
|
+
break;
|
|
804
|
+
case "interact_stmt":
|
|
805
|
+
collectFileRefs(s.handlers, out);
|
|
806
|
+
break;
|
|
807
|
+
case "intent_handler":
|
|
808
|
+
if (Array.isArray(s.body)) collectFileRefs(s.body, out);
|
|
809
|
+
break;
|
|
810
|
+
case "offtopic_handler":
|
|
811
|
+
case "after_stmt":
|
|
812
|
+
case "parallel_stmt":
|
|
813
|
+
collectFileRefs(s.body, out);
|
|
814
|
+
break;
|
|
815
|
+
case "conditional_stmt":
|
|
816
|
+
collectFileRefs(s.then, out);
|
|
817
|
+
if (s.else) collectFileRefs(s.else, out);
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function namespaceOf(kind) {
|
|
822
|
+
return kind === "guide" ? "guides" : "knowledge";
|
|
823
|
+
}
|
|
824
|
+
async function collectBehaviorFileRefs(mergedBehaviorText) {
|
|
825
|
+
await initBehaviorParser();
|
|
826
|
+
const refs = /* @__PURE__ */ new Map();
|
|
827
|
+
const behaviorAst = parseBehaviorFile(mergedBehaviorText).ok;
|
|
828
|
+
if (!behaviorAst) return refs;
|
|
829
|
+
for (const state of behaviorAst.states) collectFileRefs(state.body, refs);
|
|
830
|
+
for (const trigger of behaviorAst.global_triggers) collectFileRefs(trigger.body, refs);
|
|
831
|
+
return refs;
|
|
832
|
+
}
|
|
833
|
+
async function listContentFiles(dir, ns) {
|
|
834
|
+
const found = [];
|
|
835
|
+
async function walk(subdir, prefix) {
|
|
836
|
+
let entries;
|
|
837
|
+
try {
|
|
838
|
+
entries = await (0, fs_promises.readdir)(subdir);
|
|
839
|
+
} catch {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
for (const entry of entries) {
|
|
843
|
+
if (entry.startsWith(".")) continue;
|
|
844
|
+
const fullPath = (0, path.join)(subdir, entry);
|
|
845
|
+
const relativePath = `${prefix}/${entry}`;
|
|
846
|
+
if ((await (0, fs_promises.stat)(fullPath)).isDirectory()) await walk(fullPath, relativePath);
|
|
847
|
+
else found.push(relativePath);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
await walk((0, path.join)(dir, ns), ns);
|
|
851
|
+
return found;
|
|
852
|
+
}
|
|
853
|
+
async function findOrphanContentFiles(dir, mergedBehaviorText) {
|
|
854
|
+
const refs = await collectBehaviorFileRefs(mergedBehaviorText);
|
|
855
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
856
|
+
for (const { kind, text } of refs.values()) referenced.add(`${namespaceOf(kind)}/${text}`);
|
|
857
|
+
return [...await listContentFiles(dir, "guides"), ...await listContentFiles(dir, "knowledge")].filter((path$4) => !referenced.has(path$4)).map((path$5) => ({
|
|
858
|
+
file: path$5,
|
|
859
|
+
line: 1,
|
|
860
|
+
col: 1,
|
|
861
|
+
severity: "warning",
|
|
862
|
+
code: "W015",
|
|
863
|
+
message: `'${path$5}' is not referenced by any guide or teach statement — it will not be included in the bundle`
|
|
864
|
+
}));
|
|
1129
865
|
}
|
|
1130
866
|
async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSources, personaFile) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
try {
|
|
1161
|
-
personaText = await (0, import_promises2.readFile)((0, import_path2.join)(dir, personaFile), "utf-8");
|
|
1162
|
-
} catch {
|
|
1163
|
-
throw new Error(`E_DESC: persona file '${personaFile}' declared in '${descriptionFile}' not found`);
|
|
1164
|
-
}
|
|
1165
|
-
files.set(personaFile, personaText);
|
|
1166
|
-
}
|
|
1167
|
-
await walk((0, import_path2.join)(dir, "guides"), "guides");
|
|
1168
|
-
await walk((0, import_path2.join)(dir, "knowledge"), "knowledge");
|
|
1169
|
-
await initBehaviorParser();
|
|
1170
|
-
const behaviorAst = parseBehaviorFile(mergedBehaviorText).ok;
|
|
1171
|
-
if (behaviorAst) {
|
|
1172
|
-
const refs = /* @__PURE__ */ new Map();
|
|
1173
|
-
for (const state of behaviorAst.states) collectFileRefs(state.body, refs);
|
|
1174
|
-
for (const trigger of behaviorAst.global_triggers) collectFileRefs(trigger.body, refs);
|
|
1175
|
-
for (const { kind, text } of refs.values()) {
|
|
1176
|
-
if (isExternalPath(dir, text)) {
|
|
1177
|
-
throw new Error(`E014: '${kind}' path '${text}' is absolute or escapes the agent root`);
|
|
1178
|
-
}
|
|
1179
|
-
let content;
|
|
1180
|
-
try {
|
|
1181
|
-
content = await (0, import_promises2.readFile)((0, import_path2.join)(dir, text), "utf-8");
|
|
1182
|
-
} catch {
|
|
1183
|
-
throw new Error(`E018: '${kind}' file '${text}' referenced in agent.behavior not found in ${dir}`);
|
|
1184
|
-
}
|
|
1185
|
-
files.set(`${kind === "guide" ? "guides" : "knowledge"}/${text}`, content);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
return files;
|
|
867
|
+
const files = /* @__PURE__ */ new Map();
|
|
868
|
+
files.set(descriptionFile, await (0, fs_promises.readFile)((0, path.join)(dir, descriptionFile), "utf-8"));
|
|
869
|
+
const flatBehaviorText = mergedBehaviorText.replace(/^\s*merge\s+"[^"]*"\s*$/gm, "");
|
|
870
|
+
files.set("agent.behavior", flatBehaviorText);
|
|
871
|
+
for (const relPath of mergeSources) files.set(`behaviors/${relPath}`, await (0, fs_promises.readFile)((0, path.join)(dir, relPath), "utf-8"));
|
|
872
|
+
if (personaFile) {
|
|
873
|
+
if (isExternalPath(dir, personaFile)) throw new Error(`E014: persona path '${personaFile}' in '${descriptionFile}' is absolute or escapes the agent root`);
|
|
874
|
+
let personaText;
|
|
875
|
+
try {
|
|
876
|
+
personaText = await (0, fs_promises.readFile)((0, path.join)(dir, personaFile), "utf-8");
|
|
877
|
+
} catch {
|
|
878
|
+
throw new Error(`E_DESC: persona file '${personaFile}' declared in '${descriptionFile}' not found`);
|
|
879
|
+
}
|
|
880
|
+
files.set(personaFile, personaText);
|
|
881
|
+
}
|
|
882
|
+
const refs = await collectBehaviorFileRefs(mergedBehaviorText);
|
|
883
|
+
for (const { kind, text } of refs.values()) {
|
|
884
|
+
if (isExternalPath(dir, text)) throw new Error(`E014: '${kind}' path '${text}' is absolute or escapes the agent root`);
|
|
885
|
+
const ns = namespaceOf(kind);
|
|
886
|
+
const nsPath = `${ns}/${text}`;
|
|
887
|
+
let content;
|
|
888
|
+
for (const candidate of [(0, path.join)(dir, ns, text), (0, path.join)(dir, text)]) try {
|
|
889
|
+
content = await (0, fs_promises.readFile)(candidate, "utf-8");
|
|
890
|
+
break;
|
|
891
|
+
} catch {}
|
|
892
|
+
if (content === void 0) throw new Error(`E018: '${kind}' file '${text}' referenced in agent.behavior not found in ${dir} — looked for '${nsPath}' and '${text}'`);
|
|
893
|
+
files.set(nsPath, content);
|
|
894
|
+
}
|
|
895
|
+
return files;
|
|
1189
896
|
}
|
|
1190
|
-
function withAgentExtension(path) {
|
|
1191
|
-
|
|
897
|
+
function withAgentExtension(path$6) {
|
|
898
|
+
return path$6.endsWith(".agent") ? path$6 : `${path$6}.agent`;
|
|
1192
899
|
}
|
|
1193
900
|
async function pack(options = {}) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
};
|
|
1273
|
-
agentFolder.file("files.json", JSON.stringify(filesJson, null, 2));
|
|
1274
|
-
for (const [path, content] of allFiles) {
|
|
1275
|
-
zip.file(path, content);
|
|
1276
|
-
}
|
|
1277
|
-
await writeZip(zip, outPath);
|
|
1278
|
-
return { path: outPath, id, warnings };
|
|
901
|
+
const dir = options.dir ?? process.cwd();
|
|
902
|
+
const outPath = withAgentExtension(options.out ?? (0, path.join)(dir, (0, path.basename)(dir)));
|
|
903
|
+
const descriptionFileName = await discoverDescriptionFile(dir, options.description);
|
|
904
|
+
const descriptionText = await (0, fs_promises.readFile)((0, path.join)(dir, descriptionFileName), "utf-8");
|
|
905
|
+
const descriptionMessages = await lintDescription(descriptionText, descriptionFileName);
|
|
906
|
+
const descErrors = descriptionMessages.filter((m) => m.severity === "error");
|
|
907
|
+
if (descErrors.length > 0) throw new Error(`Lint failed:\n${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`);
|
|
908
|
+
await initBehaviorParser();
|
|
909
|
+
const descResult = parseDescriptionFile(descriptionText);
|
|
910
|
+
if (descResult.ok === null) {
|
|
911
|
+
const firstError = descResult.diagnostics.find((d) => d.severity === "error");
|
|
912
|
+
throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
|
|
913
|
+
}
|
|
914
|
+
const df = descResult.ok;
|
|
915
|
+
if (!df.behavior) throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file — add 'behavior "agent.behavior"' (or the name of your entry .behavior file)`);
|
|
916
|
+
if (isExternalPath(dir, df.behavior)) throw new Error(`E014: behavior path '${df.behavior}' in '${descriptionFileName}' is absolute or escapes the agent root`);
|
|
917
|
+
const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
|
|
918
|
+
const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
|
|
919
|
+
const allMessages = [...descriptionMessages, ...behaviorMessages];
|
|
920
|
+
const errors = allMessages.filter((m) => m.severity === "error");
|
|
921
|
+
const lintWarnings = allMessages.filter((m) => m.severity === "warning");
|
|
922
|
+
if (errors.length > 0) throw new Error(`Lint failed:\n${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`);
|
|
923
|
+
const version = await resolveVersionInteractive(options.version);
|
|
924
|
+
const commit = await resolveCommit(options.commit);
|
|
925
|
+
const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources, df.persona ?? void 0);
|
|
926
|
+
const warnings = [...lintWarnings, ...await findOrphanContentFiles(dir, mergedText)];
|
|
927
|
+
const contentForHash = Array.from(allFiles.values()).join("");
|
|
928
|
+
const sha256 = (0, crypto.createHash)("sha256").update(contentForHash).digest("hex");
|
|
929
|
+
const id = require_zip_core.buildId({
|
|
930
|
+
namespace: df.agent.domain ?? "unknown",
|
|
931
|
+
name: require_zip_core.slugify(df.agent.name),
|
|
932
|
+
version,
|
|
933
|
+
digest: version ? commit : void 0
|
|
934
|
+
});
|
|
935
|
+
const typesJson = buildTypesJson(df);
|
|
936
|
+
const integrity = {
|
|
937
|
+
sha256,
|
|
938
|
+
...typesJson ? { types: ".agent/types.json" } : {},
|
|
939
|
+
files: ".agent/files.json"
|
|
940
|
+
};
|
|
941
|
+
const aboutme = require_zip_core.buildAboutme({
|
|
942
|
+
id,
|
|
943
|
+
name: df.agent.name,
|
|
944
|
+
description: df.description ?? "",
|
|
945
|
+
version: version ?? "",
|
|
946
|
+
domain: df.agent.domain ?? "",
|
|
947
|
+
license: df.agent.license ?? "",
|
|
948
|
+
persona: df.persona ?? void 0,
|
|
949
|
+
purpose: "unknown",
|
|
950
|
+
compiler: `dot-agent/${require_zip_core.COMPILER_VERSION}`,
|
|
951
|
+
commit,
|
|
952
|
+
capabilities: df.capabilities.map((c) => ({
|
|
953
|
+
id: c.name,
|
|
954
|
+
description: c.description ?? ""
|
|
955
|
+
})),
|
|
956
|
+
requires: df.requires,
|
|
957
|
+
integrity
|
|
958
|
+
});
|
|
959
|
+
const zip = new jszip.default();
|
|
960
|
+
const agentFolder = zip.folder(".agent");
|
|
961
|
+
agentFolder.file("aboutme.json", require_zip_core.aboutmeToJson(aboutme));
|
|
962
|
+
if (typesJson) agentFolder.file("types.json", typesJson);
|
|
963
|
+
const filesJson = {
|
|
964
|
+
description: descriptionFileName,
|
|
965
|
+
behavior: "agent.behavior",
|
|
966
|
+
...df.persona ? { persona: df.persona } : {},
|
|
967
|
+
behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/")),
|
|
968
|
+
guides: Array.from(allFiles.keys()).filter((f) => f.startsWith("guides/")),
|
|
969
|
+
knowledge: Array.from(allFiles.keys()).filter((f) => f.startsWith("knowledge/"))
|
|
970
|
+
};
|
|
971
|
+
agentFolder.file("files.json", JSON.stringify(filesJson, null, 2));
|
|
972
|
+
for (const [path$7, content] of allFiles) zip.file(path$7, content);
|
|
973
|
+
await writeZip(zip, outPath);
|
|
974
|
+
return {
|
|
975
|
+
path: outPath,
|
|
976
|
+
id,
|
|
977
|
+
warnings
|
|
978
|
+
};
|
|
1279
979
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
var import_promises3 = require("fs/promises");
|
|
1283
|
-
var import_path3 = require("path");
|
|
1284
|
-
var import_crypto2 = require("crypto");
|
|
1285
|
-
var GITKEEP = /* @__PURE__ */ new Set(["guides/.gitkeep", "knowledge/.gitkeep"]);
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region src/bundle.ts
|
|
1286
982
|
async function bundleFromDir(dir) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
983
|
+
const descriptionFileName = await discoverDescriptionFile(dir);
|
|
984
|
+
const descriptionText = await (0, fs_promises.readFile)((0, path.join)(dir, descriptionFileName), "utf-8");
|
|
985
|
+
const descMessages = await lintDescription(descriptionText, descriptionFileName);
|
|
986
|
+
const descErrors = descMessages.filter((m) => m.severity === "error");
|
|
987
|
+
if (descErrors.length > 0) throw new Error(`Lint failed:\n${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`);
|
|
988
|
+
await initBehaviorParser();
|
|
989
|
+
const descResult = parseDescriptionFile(descriptionText);
|
|
990
|
+
if (descResult.ok === null) {
|
|
991
|
+
const firstError = descResult.diagnostics.find((d) => d.severity === "error");
|
|
992
|
+
throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
|
|
993
|
+
}
|
|
994
|
+
const df = descResult.ok;
|
|
995
|
+
if (!df.behavior) throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file`);
|
|
996
|
+
const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
|
|
997
|
+
const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
|
|
998
|
+
const orphanMessages = await findOrphanContentFiles(dir, mergedText);
|
|
999
|
+
const allMessages = [
|
|
1000
|
+
...descMessages,
|
|
1001
|
+
...behaviorMessages,
|
|
1002
|
+
...orphanMessages
|
|
1003
|
+
];
|
|
1004
|
+
const errors = allMessages.filter((m) => m.severity === "error");
|
|
1005
|
+
const warnings = allMessages.filter((m) => m.severity === "warning");
|
|
1006
|
+
if (errors.length > 0) throw new Error(`Lint failed:\n${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`);
|
|
1007
|
+
for (const w of warnings) process.stderr.write(`\x1b[33m⚠\x1b[0m ${w.file}:${w.line}:${w.col} ${w.code} ${w.message}\n`);
|
|
1008
|
+
const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources, df.persona ?? void 0);
|
|
1009
|
+
const sha256 = (0, crypto.createHash)("sha256").update(Array.from(allFiles.values()).join("")).digest("hex");
|
|
1010
|
+
const id = require_zip_core.buildId({
|
|
1011
|
+
namespace: df.agent.domain ?? "unknown",
|
|
1012
|
+
name: require_zip_core.slugify(df.agent.name),
|
|
1013
|
+
version: "dev"
|
|
1014
|
+
});
|
|
1015
|
+
return {
|
|
1016
|
+
id,
|
|
1017
|
+
aboutme: require_zip_core.buildAboutme({
|
|
1018
|
+
id,
|
|
1019
|
+
name: df.agent.name,
|
|
1020
|
+
description: df.description ?? "",
|
|
1021
|
+
version: "dev",
|
|
1022
|
+
domain: df.agent.domain ?? "",
|
|
1023
|
+
license: df.agent.license ?? "",
|
|
1024
|
+
persona: df.persona ?? void 0,
|
|
1025
|
+
purpose: "development",
|
|
1026
|
+
compiler: `dot-agent/${require_zip_core.COMPILER_VERSION}`,
|
|
1027
|
+
capabilities: df.capabilities.map((c) => ({
|
|
1028
|
+
id: c.name,
|
|
1029
|
+
description: c.description ?? ""
|
|
1030
|
+
})),
|
|
1031
|
+
requires: df.requires,
|
|
1032
|
+
integrity: {
|
|
1033
|
+
sha256,
|
|
1034
|
+
files: ".agent/files.json"
|
|
1035
|
+
}
|
|
1036
|
+
}),
|
|
1037
|
+
files: {
|
|
1038
|
+
description: descriptionText,
|
|
1039
|
+
behavior: allFiles.get("agent.behavior") ?? mergedText,
|
|
1040
|
+
persona: df.persona ? allFiles.get(df.persona) : void 0,
|
|
1041
|
+
guides: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("guides/")).map(([path$1, content]) => ({
|
|
1042
|
+
path: path$1,
|
|
1043
|
+
content
|
|
1044
|
+
})),
|
|
1045
|
+
knowledge: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("knowledge/")).map(([path$2, content]) => ({
|
|
1046
|
+
path: path$2,
|
|
1047
|
+
content
|
|
1048
|
+
})),
|
|
1049
|
+
behaviors: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("behaviors/")).map(([path$3, content]) => ({
|
|
1050
|
+
path: path$3,
|
|
1051
|
+
content
|
|
1052
|
+
}))
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1351
1055
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
});
|
|
1056
|
+
//#endregion
|
|
1057
|
+
exports.aboutmeToJson = require_zip_core.aboutmeToJson;
|
|
1058
|
+
exports.buildAboutme = require_zip_core.buildAboutme;
|
|
1059
|
+
exports.buildId = require_zip_core.buildId;
|
|
1060
|
+
exports.bundleFromDir = bundleFromDir;
|
|
1061
|
+
exports.collectFiles = collectFiles;
|
|
1062
|
+
exports.consolidate = consolidate;
|
|
1063
|
+
exports.createLinter = createLinter;
|
|
1064
|
+
exports.createZip = require_zip_core.createZip;
|
|
1065
|
+
exports.extractDigest = require_zip_core.extractDigest;
|
|
1066
|
+
exports.extractFiles = require_zip_core.extractFiles;
|
|
1067
|
+
exports.extractName = require_zip_core.extractName;
|
|
1068
|
+
exports.getBehaviorScxml = getBehaviorScxml;
|
|
1069
|
+
exports.getContextNode = getContextNode;
|
|
1070
|
+
exports.initBehaviorParser = initBehaviorParser;
|
|
1071
|
+
exports.initParsers = initParsers;
|
|
1072
|
+
exports.lintBehavior = lintBehavior;
|
|
1073
|
+
exports.lintDescription = lintDescription;
|
|
1074
|
+
exports.nodeAtOffset = nodeAtOffset;
|
|
1075
|
+
exports.nodeToRange = nodeToRange;
|
|
1076
|
+
exports.nodesOfType = nodesOfType;
|
|
1077
|
+
exports.pack = pack;
|
|
1078
|
+
exports.parse = parse;
|
|
1079
|
+
exports.parseAboutme = require_zip_core.parseAboutme;
|
|
1080
|
+
exports.parseBehaviorFile = parseBehaviorFile;
|
|
1081
|
+
exports.parseDescriptionFile = parseDescriptionFile;
|
|
1082
|
+
exports.parseId = require_zip_core.parseId;
|
|
1083
|
+
exports.parseSync = parseSync;
|
|
1084
|
+
exports.positionToOffset = positionToOffset;
|
|
1085
|
+
exports.readZip = readZip;
|
|
1086
|
+
exports.validateMagicBytes = validateMagicBytes;
|
|
1087
|
+
exports.validateZipBomb = validateZipBomb;
|
|
1088
|
+
exports.writeZip = writeZip;
|
|
1089
|
+
|
|
1387
1090
|
//# sourceMappingURL=index.cjs.map
|