@dot-agent/compiler 0.1.0 → 0.5.0-alpha.2

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/index.cjs CHANGED
@@ -33,7 +33,9 @@ __export(index_exports, {
33
33
  aboutmeToJson: () => aboutmeToJson,
34
34
  buildAboutme: () => buildAboutme,
35
35
  buildId: () => buildId,
36
+ bundleFromDir: () => bundleFromDir,
36
37
  collectFiles: () => collectFiles,
38
+ consolidate: () => consolidate,
37
39
  createLinter: () => createLinter,
38
40
  createZip: () => createZip,
39
41
  extractDigest: () => extractDigest,
@@ -57,8 +59,8 @@ __export(index_exports, {
57
59
  parseSync: () => parseSync,
58
60
  positionToOffset: () => positionToOffset,
59
61
  readZip: () => readZip,
60
- validateMagicBytes: () => validateMagicBytes,
61
- validateZipBomb: () => validateZipBomb,
62
+ validateMagicBytes: () => validateMagicBytes2,
63
+ validateZipBomb: () => validateZipBomb2,
62
64
  writeZip: () => writeZip
63
65
  });
64
66
  module.exports = __toCommonJS(index_exports);
@@ -70,7 +72,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
70
72
  // src/parser.ts
71
73
  var import_module = require("module");
72
74
  var import_web_tree_sitter = require("web-tree-sitter");
73
- var import_parser_dsl = __toESM(require("@dot-agent/parser-dsl"), 1);
75
+ var import_parser_dsl = require("@dot-agent/parser-dsl");
74
76
  var require2 = (0, import_module.createRequire)(importMetaUrl);
75
77
  var { descriptionWasmPath, behaviorWasmPath } = require2("@dot-agent/tree-sitter");
76
78
  var _initialized = false;
@@ -79,7 +81,7 @@ var _behaviorParser;
79
81
  var _bpInitialized = false;
80
82
  async function initBehaviorParser() {
81
83
  if (_bpInitialized) return;
82
- await (0, import_parser_dsl.default)();
84
+ await (0, import_parser_dsl.init)();
83
85
  _bpInitialized = true;
84
86
  }
85
87
  function parseBehaviorFile(text) {
@@ -234,6 +236,18 @@ async function lintDescription(text, file = "agent.description") {
234
236
  message: "No domain declared \u2014 agent will be packaged as unknown/name. Interoperability between runtimes is not guaranteed."
235
237
  });
236
238
  }
239
+ const behaviorBlocks = nodesOfType(tree, "behavior_block");
240
+ if (behaviorBlocks.length > 1) {
241
+ const { line, col } = nodePosition(behaviorBlocks[1]);
242
+ messages.push({
243
+ file,
244
+ line,
245
+ col,
246
+ severity: "error",
247
+ code: "E017",
248
+ message: "Multiple 'behavior' declarations \u2014 only one is allowed. To combine multiple files, use 'merge' in your .behavior file."
249
+ });
250
+ }
237
251
  const declaredTypes = new Set(
238
252
  nodesOfType(tree, "type_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean)
239
253
  );
@@ -332,7 +346,7 @@ function collectMergedStates(tree, docDir, states, visited = /* @__PURE__ */ new
332
346
  collectMergedStates(mergedTree, (0, import_path.dirname)(absPath), states, visited);
333
347
  }
334
348
  }
335
- async function lintBehavior(text, file = "agent.behavior", docPath) {
349
+ async function lintBehavior(text, file = "agent.behavior", docPath, consolidated = false, externalStates, mergedText) {
336
350
  await initBehaviorParser();
337
351
  if (!text.endsWith("\n")) text = text + "\n";
338
352
  const tree = await parse("behavior", text);
@@ -353,7 +367,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
353
367
  col,
354
368
  severity: "warning",
355
369
  code: "W013",
356
- message: `'interact' without 'goal' \u2014 the prettifier will insert one. To set it explicitly, add 'goal "..."' before 'interact'.`
370
+ message: `'interact' without 'goal'. Add 'goal "..."' before 'interact'.`
357
371
  });
358
372
  }
359
373
  if (hasGoal && !hasInteract) {
@@ -364,7 +378,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
364
378
  col,
365
379
  severity: "warning",
366
380
  code: "W012",
367
- message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal' \u2014 the prettifier can adjust this.`
381
+ message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal'.`
368
382
  });
369
383
  }
370
384
  if (hasInteract && intentHandlers.length === 0) {
@@ -495,7 +509,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
495
509
  col,
496
510
  severity: "warning",
497
511
  code: "W002",
498
- message: `Text block exceeds 280 characters (${content.length}). Consider using 'teach' to load long goal text from an external file.`
512
+ message: `Text block exceeds 280 characters (${content.length}). Consider using 'guide' to load long orientation text from an external text file.`
499
513
  });
500
514
  }
501
515
  }
@@ -514,7 +528,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
514
528
  col,
515
529
  severity: "warning",
516
530
  code: "W010",
517
- message: `'guide' text in state '${stateName}' is ${content.length} characters (limit: 280). Consider using an external file.`
531
+ message: `'guide' text in state '${stateName}' is ${content.length} characters. Consider using an external file.`
518
532
  });
519
533
  }
520
534
  }
@@ -527,6 +541,9 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
527
541
  } catch {
528
542
  }
529
543
  }
544
+ if (externalStates) {
545
+ for (const name of externalStates) definedStates.add(name);
546
+ }
530
547
  const stateNameList = Array.from(definedStates);
531
548
  for (const transitionNode of nodesOfType(tree, "transition_stmt")) {
532
549
  const stateNode = transitionNode.childForFieldName("state");
@@ -578,7 +595,9 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
578
595
  return messages;
579
596
  }
580
597
  const fsm = fsmResult.ok;
581
- const allTargets = new Set(collectTransitionTargets(fsm.states.flatMap((s) => s.body)));
598
+ const wholeFsm = mergedText !== void 0 ? parseBehaviorFile(mergedText).ok : null;
599
+ const reachabilityStates = wholeFsm?.states ?? fsm.states;
600
+ const allTargets = new Set(collectTransitionTargets(reachabilityStates.flatMap((s) => s.body)));
582
601
  for (const state of fsm.states) {
583
602
  const hasOutgoing = collectTransitionTargets(state.body).length > 0;
584
603
  const hasIncoming = allTargets.has(state.name);
@@ -608,15 +627,62 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
608
627
  });
609
628
  }
610
629
  }
630
+ if (consolidated) {
631
+ const wholeTreeFsm = wholeFsm ?? fsm;
632
+ const seenStates = /* @__PURE__ */ new Set();
633
+ for (const state of wholeTreeFsm.states) {
634
+ if (seenStates.has(state.name)) {
635
+ messages.push({
636
+ file,
637
+ line: 1,
638
+ col: 1,
639
+ severity: "error",
640
+ code: "E015",
641
+ message: `Duplicate state '${state.name}' across merged files \u2014 each state name must be unique in the consolidated FSM.`
642
+ });
643
+ } else {
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
+ }
611
673
  return messages;
612
674
  }
613
675
  async function createLinter() {
614
676
  return { lintDescription, lintBehavior };
615
677
  }
616
678
 
679
+ // src/generated-version.ts
680
+ var DSL_VERSION = "0.1-alpha";
681
+ var COMPILER_VERSION = "0.5.0-alpha.2";
682
+
617
683
  // src/manifest.ts
618
684
  function parseAboutme(json) {
619
- if (!json.schemaVersion) throw new Error("Missing schemaVersion in aboutme.json");
685
+ if (!json.dslVersion) throw new Error("Missing dslVersion in aboutme.json");
620
686
  if (!json.id) throw new Error("Missing id in aboutme.json");
621
687
  if (!json.name) throw new Error("Missing name in aboutme.json");
622
688
  if (!json.description) throw new Error("Missing description in aboutme.json");
@@ -628,7 +694,7 @@ function parseAboutme(json) {
628
694
  if (!Array.isArray(json.requires)) throw new Error("Missing requires array in aboutme.json");
629
695
  if (!json.integrity) throw new Error("Missing integrity in aboutme.json");
630
696
  return {
631
- schemaVersion: json.schemaVersion,
697
+ dslVersion: json.dslVersion,
632
698
  id: json.id,
633
699
  name: json.name,
634
700
  description: json.description,
@@ -646,7 +712,7 @@ function parseAboutme(json) {
646
712
  }
647
713
  function buildAboutme(opts) {
648
714
  return {
649
- schemaVersion: "dot-agent/1.0",
715
+ dslVersion: `dot-agent/${DSL_VERSION}`,
650
716
  id: opts.id,
651
717
  name: opts.name,
652
718
  description: opts.description,
@@ -767,6 +833,25 @@ var import_promises = require("fs/promises");
767
833
 
768
834
  // src/zip-core.ts
769
835
  var import_jszip = __toESM(require("jszip"), 1);
836
+ var MAX_ZIP_SIZE = 500 * 1024 * 1024;
837
+ var MAX_COMPRESSION_RATIO = 100;
838
+ function validateMagicBytes(bytes) {
839
+ if (!(bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4)) {
840
+ throw new Error("Not a valid .agent file (invalid magic bytes)");
841
+ }
842
+ }
843
+ function validateZipBomb(zip, compressedSize) {
844
+ let totalUncompressed = 0;
845
+ zip.forEach((_path, file) => {
846
+ if (!file.dir) totalUncompressed += file._data?.uncompressedSize || 0;
847
+ });
848
+ if (totalUncompressed > MAX_ZIP_SIZE) {
849
+ throw new Error(`Bundle exceeds 500MB uncompressed: ${totalUncompressed}`);
850
+ }
851
+ if (totalUncompressed / compressedSize > MAX_COMPRESSION_RATIO) {
852
+ throw new Error(`Compression ratio exceeds 100x: ${(totalUncompressed / compressedSize).toFixed(1)}x`);
853
+ }
854
+ }
770
855
  function createZip() {
771
856
  return new import_jszip.default();
772
857
  }
@@ -787,37 +872,19 @@ async function extractFiles(zip, filter) {
787
872
  }
788
873
 
789
874
  // src/zip.ts
790
- var MAX_ZIP_SIZE = 500 * 1024 * 1024;
791
- var MAX_COMPRESSION_RATIO = 100;
792
875
  async function readZip(filePath) {
793
876
  const data = await (0, import_promises.readFile)(filePath);
794
877
  return import_jszip2.default.loadAsync(data);
795
878
  }
796
- async function validateZipBomb(filePath) {
879
+ async function validateZipBomb2(filePath) {
797
880
  const data = await (0, import_promises.readFile)(filePath);
798
881
  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
- }
882
+ validateZipBomb(zip, data.length);
813
883
  return true;
814
884
  }
815
- async function validateMagicBytes(filePath) {
885
+ async function validateMagicBytes2(filePath) {
816
886
  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
- }
887
+ validateMagicBytes(data);
821
888
  return true;
822
889
  }
823
890
  async function writeZip(zip, outPath) {
@@ -909,7 +976,81 @@ async function resolveCommit(explicit) {
909
976
  if (explicit) return explicit;
910
977
  return gitRevParseShort() ?? void 0;
911
978
  }
912
- async function collectFiles(dir) {
979
+ function isExternalPath(agentRoot, declaredPath) {
980
+ if ((0, import_path2.isAbsolute)(declaredPath)) return true;
981
+ const normRoot = (0, import_path2.normalize)((0, import_path2.resolve)(agentRoot));
982
+ const resolved = (0, import_path2.resolve)(normRoot, declaredPath);
983
+ return (0, import_path2.relative)(normRoot, resolved).startsWith("..");
984
+ }
985
+ async function discoverDescriptionFile(dir, explicit) {
986
+ if (explicit) {
987
+ try {
988
+ await (0, import_promises2.stat)((0, import_path2.join)(dir, explicit));
989
+ return explicit;
990
+ } catch {
991
+ throw new Error(`E003: Description file '${explicit}' not found in ${dir}`);
992
+ }
993
+ }
994
+ const entries = await (0, import_promises2.readdir)(dir);
995
+ const descFiles = entries.filter((f) => f.endsWith(".description"));
996
+ if (descFiles.length === 0) {
997
+ throw new Error(`E003: No .description file found in ${dir}`);
998
+ }
999
+ if (descFiles.length > 1) {
1000
+ throw new Error(`E003: Multiple .description files found: ${descFiles.join(", ")} \u2014 use PackOptions.description to specify one`);
1001
+ }
1002
+ const name = descFiles[0];
1003
+ if (name !== "agent.description") {
1004
+ console.info(`[dot-agent] using description file: ${name}`);
1005
+ }
1006
+ return name;
1007
+ }
1008
+ async function consolidate(agentRoot, entryFile) {
1009
+ await initBehaviorParser();
1010
+ const normRoot = (0, import_path2.normalize)((0, import_path2.resolve)(agentRoot));
1011
+ const visiting = /* @__PURE__ */ new Set();
1012
+ const visited = /* @__PURE__ */ new Set();
1013
+ const order = [];
1014
+ const textCache = /* @__PURE__ */ new Map();
1015
+ async function dfs(relPath) {
1016
+ const absPath = (0, import_path2.resolve)(normRoot, relPath);
1017
+ if (visiting.has(absPath)) {
1018
+ throw new Error(`E013: Circular merge dependency involving '${relPath}'`);
1019
+ }
1020
+ if (visited.has(absPath)) return;
1021
+ visiting.add(absPath);
1022
+ let fileText;
1023
+ try {
1024
+ fileText = await (0, import_promises2.readFile)(absPath, "utf-8");
1025
+ } catch {
1026
+ throw new Error(`E012: Merge target not found: '${relPath}'`);
1027
+ }
1028
+ textCache.set(absPath, fileText);
1029
+ const result = parseBehaviorFile(fileText);
1030
+ const merges = result.ok?.merges ?? [];
1031
+ for (const mergePath of merges) {
1032
+ if ((0, import_path2.isAbsolute)(mergePath)) {
1033
+ throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' is absolute \u2014 only paths within the agent root are allowed`);
1034
+ }
1035
+ const mergeAbs = (0, import_path2.resolve)((0, import_path2.dirname)(absPath), mergePath);
1036
+ const mergeRel = (0, import_path2.relative)(normRoot, mergeAbs);
1037
+ if (mergeRel.startsWith("..")) {
1038
+ throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' escapes the agent root`);
1039
+ }
1040
+ await dfs(mergeRel);
1041
+ }
1042
+ visiting.delete(absPath);
1043
+ visited.add(absPath);
1044
+ order.push(relPath);
1045
+ }
1046
+ await dfs(entryFile);
1047
+ const texts = order.map((relPath) => textCache.get((0, import_path2.resolve)(normRoot, relPath)));
1048
+ return {
1049
+ mergedText: texts.join("\n"),
1050
+ mergeSources: order
1051
+ };
1052
+ }
1053
+ async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSources) {
913
1054
  const files = /* @__PURE__ */ new Map();
914
1055
  async function walk(subdir, prefix = "") {
915
1056
  try {
@@ -928,13 +1069,16 @@ async function collectFiles(dir) {
928
1069
  } catch {
929
1070
  }
930
1071
  }
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"));
1072
+ files.set(descriptionFile, await (0, import_promises2.readFile)((0, import_path2.join)(dir, descriptionFile), "utf-8"));
1073
+ const flatBehaviorText = mergedBehaviorText.replace(/^\s*merge\s+"[^"]*"\s*$/gm, "");
1074
+ files.set("agent.behavior", flatBehaviorText);
1075
+ for (const relPath of mergeSources) {
1076
+ files.set(`behaviors/${relPath}`, await (0, import_promises2.readFile)((0, import_path2.join)(dir, relPath), "utf-8"));
1077
+ }
933
1078
  try {
934
1079
  files.set("SOUL.md", await (0, import_promises2.readFile)((0, import_path2.join)(dir, "SOUL.md"), "utf-8"));
935
1080
  } catch {
936
1081
  }
937
- await walk((0, import_path2.join)(dir, "behaviors"), "behaviors");
938
1082
  await walk((0, import_path2.join)(dir, "guides"), "guides");
939
1083
  await walk((0, import_path2.join)(dir, "knowledge"), "knowledge");
940
1084
  return files;
@@ -942,29 +1086,14 @@ async function collectFiles(dir) {
942
1086
  async function pack(options = {}) {
943
1087
  const dir = options.dir ?? process.cwd();
944
1088
  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) {
1089
+ const descriptionFileName = await discoverDescriptionFile(dir, options.description);
1090
+ const descriptionText = await (0, import_promises2.readFile)((0, import_path2.join)(dir, descriptionFileName), "utf-8");
1091
+ const descriptionMessages = await lintDescription(descriptionText, descriptionFileName);
1092
+ const descErrors = descriptionMessages.filter((m) => m.severity === "error");
1093
+ if (descErrors.length > 0) {
965
1094
  throw new Error(
966
1095
  `Lint failed:
967
- ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
1096
+ ${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
968
1097
  );
969
1098
  }
970
1099
  await initBehaviorParser();
@@ -974,9 +1103,26 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
974
1103
  throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
975
1104
  }
976
1105
  const df = descResult.ok;
1106
+ if (!df.behavior) {
1107
+ throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file \u2014 add 'behavior "agent.behavior"' (or the name of your entry .behavior file)`);
1108
+ }
1109
+ if (isExternalPath(dir, df.behavior)) {
1110
+ throw new Error(`E014: behavior path '${df.behavior}' in '${descriptionFileName}' is absolute or escapes the agent root`);
1111
+ }
1112
+ const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
1113
+ const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
1114
+ const allMessages = [...descriptionMessages, ...behaviorMessages];
1115
+ const errors = allMessages.filter((m) => m.severity === "error");
1116
+ const warnings = allMessages.filter((m) => m.severity === "warning");
1117
+ if (errors.length > 0) {
1118
+ throw new Error(
1119
+ `Lint failed:
1120
+ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
1121
+ );
1122
+ }
977
1123
  const version = await resolveVersion(options.version);
978
1124
  const commit = await resolveCommit(options.commit);
979
- const allFiles = await collectFiles(dir);
1125
+ const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
980
1126
  const contentForHash = Array.from(allFiles.values()).join("");
981
1127
  const sha256 = (0, import_crypto.createHash)("sha256").update(contentForHash).digest("hex");
982
1128
  const namespace = df.agent.domain ?? "unknown";
@@ -996,7 +1142,7 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
996
1142
  license: df.agent.license ?? "",
997
1143
  persona: df.persona ?? "SOUL.md",
998
1144
  purpose: "unknown",
999
- compiler: "dot-agent/1.0.0",
1145
+ compiler: `dot-agent/${COMPILER_VERSION}`,
1000
1146
  commit,
1001
1147
  capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
1002
1148
  requires: df.requires,
@@ -1008,27 +1154,101 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
1008
1154
  if (typesJson) {
1009
1155
  agentFolder.file("types.json", typesJson);
1010
1156
  }
1011
- const gitkeepPaths = /* @__PURE__ */ new Set(["behaviors/.gitkeep", "guides/.gitkeep", "knowledge/.gitkeep"]);
1157
+ const gitkeepPaths = /* @__PURE__ */ new Set(["guides/.gitkeep", "knowledge/.gitkeep"]);
1012
1158
  const filesJson = {
1013
- description: "agent.description",
1159
+ description: descriptionFileName,
1014
1160
  behavior: "agent.behavior",
1015
- behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/") && !gitkeepPaths.has(f)),
1161
+ behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/")),
1016
1162
  guides: Array.from(allFiles.keys()).filter((f) => f.startsWith("guides/") && !gitkeepPaths.has(f)),
1017
1163
  knowledge: Array.from(allFiles.keys()).filter((f) => f.startsWith("knowledge/") && !gitkeepPaths.has(f))
1018
1164
  };
1019
1165
  agentFolder.file("files.json", JSON.stringify(filesJson, null, 2));
1020
1166
  for (const [path, content] of allFiles) {
1021
- if (!gitkeepPaths.has(path)) zip.file(path, content);
1167
+ zip.file(path, content);
1022
1168
  }
1023
1169
  await writeZip(zip, outPath);
1024
1170
  return { path: outPath, id, warnings };
1025
1171
  }
1172
+
1173
+ // src/bundle.ts
1174
+ var import_promises3 = require("fs/promises");
1175
+ var import_path3 = require("path");
1176
+ var import_crypto2 = require("crypto");
1177
+ var GITKEEP = /* @__PURE__ */ new Set(["guides/.gitkeep", "knowledge/.gitkeep"]);
1178
+ async function bundleFromDir(dir) {
1179
+ const descriptionFileName = await discoverDescriptionFile(dir);
1180
+ const descriptionText = await (0, import_promises3.readFile)((0, import_path3.join)(dir, descriptionFileName), "utf-8");
1181
+ const descMessages = await lintDescription(descriptionText, descriptionFileName);
1182
+ const descErrors = descMessages.filter((m) => m.severity === "error");
1183
+ if (descErrors.length > 0) {
1184
+ throw new Error(
1185
+ `Lint failed:
1186
+ ${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
1187
+ );
1188
+ }
1189
+ await initBehaviorParser();
1190
+ const descResult = parseDescriptionFile(descriptionText);
1191
+ if (descResult.ok === null) {
1192
+ const firstError = descResult.diagnostics.find((d) => d.severity === "error");
1193
+ throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
1194
+ }
1195
+ const df = descResult.ok;
1196
+ if (!df.behavior) {
1197
+ throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file`);
1198
+ }
1199
+ const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
1200
+ const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
1201
+ const allMessages = [...descMessages, ...behaviorMessages];
1202
+ const errors = allMessages.filter((m) => m.severity === "error");
1203
+ const warnings = allMessages.filter((m) => m.severity === "warning");
1204
+ if (errors.length > 0) {
1205
+ throw new Error(
1206
+ `Lint failed:
1207
+ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
1208
+ );
1209
+ }
1210
+ for (const w of warnings) {
1211
+ process.stderr.write(`\x1B[33m\u26A0\x1B[0m ${w.file}:${w.line}:${w.col} ${w.code} ${w.message}
1212
+ `);
1213
+ }
1214
+ const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
1215
+ const sha256 = (0, import_crypto2.createHash)("sha256").update(Array.from(allFiles.values()).join("")).digest("hex");
1216
+ const id = buildId({ namespace: df.agent.domain ?? "local", name: df.agent.name, version: "dev" });
1217
+ const aboutme = buildAboutme({
1218
+ id,
1219
+ name: df.agent.name,
1220
+ description: df.description ?? "",
1221
+ version: "dev",
1222
+ domain: df.agent.domain ?? "",
1223
+ license: df.agent.license ?? "",
1224
+ persona: df.persona ?? "SOUL.md",
1225
+ purpose: "development",
1226
+ compiler: `dot-agent/${COMPILER_VERSION}`,
1227
+ capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
1228
+ requires: df.requires,
1229
+ integrity: { sha256, files: ".agent/files.json" }
1230
+ });
1231
+ const files = {
1232
+ description: descriptionText,
1233
+ // Reuse collectFiles()'s 'agent.behavior' entry rather than the raw
1234
+ // mergedText — it has redundant `merge "..."` lines stripped (see
1235
+ // collectFiles() for why they're dead weight once flattened).
1236
+ behavior: allFiles.get("agent.behavior") ?? mergedText,
1237
+ soul: allFiles.get("SOUL.md"),
1238
+ guides: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("guides/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
1239
+ knowledge: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("knowledge/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
1240
+ behaviors: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("behaviors/")).map(([path, content]) => ({ path, content }))
1241
+ };
1242
+ return { id, aboutme, files };
1243
+ }
1026
1244
  // Annotate the CommonJS export names for ESM import in node:
1027
1245
  0 && (module.exports = {
1028
1246
  aboutmeToJson,
1029
1247
  buildAboutme,
1030
1248
  buildId,
1249
+ bundleFromDir,
1031
1250
  collectFiles,
1251
+ consolidate,
1032
1252
  createLinter,
1033
1253
  createZip,
1034
1254
  extractDigest,