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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,11 +7,13 @@ import {
7
7
  extractDigest,
8
8
  extractFiles,
9
9
  extractName,
10
+ isValidVersion,
10
11
  parseAboutme,
11
12
  parseId,
13
+ slugify,
12
14
  validateMagicBytes,
13
15
  validateZipBomb
14
- } from "./chunk-P5LFGJ6P.js";
16
+ } from "./chunk-AYXHOOUX.js";
15
17
 
16
18
  // src/parser.ts
17
19
  import { createRequire } from "module";
@@ -705,13 +707,6 @@ function buildTypesJson(df) {
705
707
  }
706
708
 
707
709
  // src/pack.ts
708
- function gitDescribeTags() {
709
- try {
710
- return execSync("git describe --tags --abbrev=0", { stdio: "pipe", encoding: "utf-8" }).trim();
711
- } catch {
712
- return null;
713
- }
714
- }
715
710
  function gitRevParseShort() {
716
711
  try {
717
712
  return execSync("git rev-parse --short HEAD", { stdio: "pipe", encoding: "utf-8" }).trim();
@@ -719,14 +714,65 @@ function gitRevParseShort() {
719
714
  return null;
720
715
  }
721
716
  }
722
- async function resolveVersion(explicit) {
723
- if (explicit) return explicit;
724
- return gitDescribeTags() ?? "v1.0.0";
725
- }
726
717
  async function resolveCommit(explicit) {
727
718
  if (explicit) return explicit;
728
719
  return gitRevParseShort() ?? void 0;
729
720
  }
721
+ function recentTagCandidates(limit = 5) {
722
+ let tags;
723
+ try {
724
+ tags = execSync("git tag --sort=-creatordate", { stdio: "pipe", encoding: "utf-8" }).trim().split("\n").filter(Boolean);
725
+ } catch {
726
+ return [];
727
+ }
728
+ return tags.slice(0, limit).map((tag) => {
729
+ const atIdx = tag.lastIndexOf("@");
730
+ const version = atIdx === -1 ? tag : tag.slice(atIdx + 1);
731
+ const label = atIdx === -1 ? tag : `${version} (tag: ${tag})`;
732
+ return { version, label };
733
+ }).filter((c) => isValidVersion(c.version));
734
+ }
735
+ var CUSTOM_VERSION = "__dot-agent-custom-version__";
736
+ var BARE_VERSION = "__dot-agent-bare-version__";
737
+ async function resolveVersionInteractive(explicit) {
738
+ if (explicit) {
739
+ if (!isValidVersion(explicit)) {
740
+ throw new Error(`E019: Invalid version format '${explicit}' \u2014 expected vX.Y[.Z][-prerelease] or X.Y[.Z][-prerelease]`);
741
+ }
742
+ return explicit;
743
+ }
744
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
745
+ process.stderr.write("[dot-agent] no TTY and no --version \u2014 packing without a version\n");
746
+ return void 0;
747
+ }
748
+ const candidates = recentTagCandidates();
749
+ const clack = await import("@clack/prompts");
750
+ const choice = await clack.select({
751
+ message: "Choose a version for this package",
752
+ options: [
753
+ ...candidates.map((c) => ({ value: c.version, label: c.label })),
754
+ { value: CUSTOM_VERSION, label: "Type a custom version\u2026" },
755
+ { value: BARE_VERSION, label: "None (bare \u2014 no version)" }
756
+ ]
757
+ });
758
+ if (clack.isCancel(choice)) {
759
+ clack.cancel("Pack cancelled.");
760
+ process.exit(1);
761
+ }
762
+ if (choice === BARE_VERSION) return void 0;
763
+ if (choice === CUSTOM_VERSION) {
764
+ const typed = await clack.text({
765
+ message: "Version",
766
+ validate: (v) => v && isValidVersion(v) ? void 0 : "Invalid version format \u2014 expected vX.Y[.Z][-prerelease] or X.Y[.Z][-prerelease]"
767
+ });
768
+ if (clack.isCancel(typed)) {
769
+ clack.cancel("Pack cancelled.");
770
+ process.exit(1);
771
+ }
772
+ return typed;
773
+ }
774
+ return choice;
775
+ }
730
776
  function isExternalPath(agentRoot, declaredPath) {
731
777
  if (isAbsolute(declaredPath)) return true;
732
778
  const normRoot = normalize(resolve2(agentRoot));
@@ -801,7 +847,35 @@ async function consolidate(agentRoot, entryFile) {
801
847
  mergeSources: order
802
848
  };
803
849
  }
804
- async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSources) {
850
+ var FILE_REF_RE = /\.(txt|md)$/i;
851
+ function collectFileRefs(stmts, out) {
852
+ for (const s of stmts) {
853
+ switch (s.type) {
854
+ case "guide_stmt":
855
+ if (FILE_REF_RE.test(s.text)) out.set(`guide:${s.text}`, { kind: "guide", text: s.text });
856
+ break;
857
+ case "teach_stmt":
858
+ if (FILE_REF_RE.test(s.text)) out.set(`teach:${s.text}`, { kind: "teach", text: s.text });
859
+ break;
860
+ case "interact_stmt":
861
+ collectFileRefs(s.handlers, out);
862
+ break;
863
+ case "intent_handler":
864
+ if (Array.isArray(s.body)) collectFileRefs(s.body, out);
865
+ break;
866
+ case "offtopic_handler":
867
+ case "after_stmt":
868
+ case "parallel_stmt":
869
+ collectFileRefs(s.body, out);
870
+ break;
871
+ case "conditional_stmt":
872
+ collectFileRefs(s.then, out);
873
+ if (s.else) collectFileRefs(s.else, out);
874
+ break;
875
+ }
876
+ }
877
+ }
878
+ async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSources, personaFile) {
805
879
  const files = /* @__PURE__ */ new Map();
806
880
  async function walk(subdir, prefix = "") {
807
881
  try {
@@ -826,17 +900,47 @@ async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSourc
826
900
  for (const relPath of mergeSources) {
827
901
  files.set(`behaviors/${relPath}`, await readFile2(join(dir, relPath), "utf-8"));
828
902
  }
829
- try {
830
- files.set("SOUL.md", await readFile2(join(dir, "SOUL.md"), "utf-8"));
831
- } catch {
903
+ if (personaFile) {
904
+ if (isExternalPath(dir, personaFile)) {
905
+ throw new Error(`E014: persona path '${personaFile}' in '${descriptionFile}' is absolute or escapes the agent root`);
906
+ }
907
+ let personaText;
908
+ try {
909
+ personaText = await readFile2(join(dir, personaFile), "utf-8");
910
+ } catch {
911
+ throw new Error(`E_DESC: persona file '${personaFile}' declared in '${descriptionFile}' not found`);
912
+ }
913
+ files.set(personaFile, personaText);
832
914
  }
833
915
  await walk(join(dir, "guides"), "guides");
834
916
  await walk(join(dir, "knowledge"), "knowledge");
917
+ await initBehaviorParser();
918
+ const behaviorAst = parseBehaviorFile(mergedBehaviorText).ok;
919
+ if (behaviorAst) {
920
+ const refs = /* @__PURE__ */ new Map();
921
+ for (const state of behaviorAst.states) collectFileRefs(state.body, refs);
922
+ for (const trigger of behaviorAst.global_triggers) collectFileRefs(trigger.body, refs);
923
+ for (const { kind, text } of refs.values()) {
924
+ if (isExternalPath(dir, text)) {
925
+ throw new Error(`E014: '${kind}' path '${text}' is absolute or escapes the agent root`);
926
+ }
927
+ let content;
928
+ try {
929
+ content = await readFile2(join(dir, text), "utf-8");
930
+ } catch {
931
+ throw new Error(`E018: '${kind}' file '${text}' referenced in agent.behavior not found in ${dir}`);
932
+ }
933
+ files.set(`${kind === "guide" ? "guides" : "knowledge"}/${text}`, content);
934
+ }
935
+ }
835
936
  return files;
836
937
  }
938
+ function withAgentExtension(path) {
939
+ return path.endsWith(".agent") ? path : `${path}.agent`;
940
+ }
837
941
  async function pack(options = {}) {
838
942
  const dir = options.dir ?? process.cwd();
839
- const outPath = options.out ?? join(dir, `${basename(dir)}.agent`);
943
+ const outPath = withAgentExtension(options.out ?? join(dir, basename(dir)));
840
944
  const descriptionFileName = await discoverDescriptionFile(dir, options.description);
841
945
  const descriptionText = await readFile2(join(dir, descriptionFileName), "utf-8");
842
946
  const descriptionMessages = await lintDescription(descriptionText, descriptionFileName);
@@ -871,13 +975,13 @@ ${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`)
871
975
  ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
872
976
  );
873
977
  }
874
- const version = await resolveVersion(options.version);
978
+ const version = await resolveVersionInteractive(options.version);
875
979
  const commit = await resolveCommit(options.commit);
876
- const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
980
+ const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources, df.persona ?? void 0);
877
981
  const contentForHash = Array.from(allFiles.values()).join("");
878
982
  const sha256 = createHash("sha256").update(contentForHash).digest("hex");
879
983
  const namespace = df.agent.domain ?? "unknown";
880
- const id = buildId({ namespace, name: df.agent.name, version, digest: commit });
984
+ const id = buildId({ namespace, name: slugify(df.agent.name), version, digest: version ? commit : void 0 });
881
985
  const typesJson = buildTypesJson(df);
882
986
  const integrity = {
883
987
  sha256,
@@ -888,10 +992,10 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
888
992
  id,
889
993
  name: df.agent.name,
890
994
  description: df.description ?? "",
891
- version,
995
+ version: version ?? "",
892
996
  domain: df.agent.domain ?? "",
893
997
  license: df.agent.license ?? "",
894
- persona: df.persona ?? "SOUL.md",
998
+ persona: df.persona ?? void 0,
895
999
  purpose: "unknown",
896
1000
  compiler: `dot-agent/${COMPILER_VERSION}`,
897
1001
  commit,
@@ -909,6 +1013,7 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
909
1013
  const filesJson = {
910
1014
  description: descriptionFileName,
911
1015
  behavior: "agent.behavior",
1016
+ ...df.persona ? { persona: df.persona } : {},
912
1017
  behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/")),
913
1018
  guides: Array.from(allFiles.keys()).filter((f) => f.startsWith("guides/") && !gitkeepPaths.has(f)),
914
1019
  knowledge: Array.from(allFiles.keys()).filter((f) => f.startsWith("knowledge/") && !gitkeepPaths.has(f))
@@ -962,9 +1067,9 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
962
1067
  process.stderr.write(`\x1B[33m\u26A0\x1B[0m ${w.file}:${w.line}:${w.col} ${w.code} ${w.message}
963
1068
  `);
964
1069
  }
965
- const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
1070
+ const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources, df.persona ?? void 0);
966
1071
  const sha256 = createHash2("sha256").update(Array.from(allFiles.values()).join("")).digest("hex");
967
- const id = buildId({ namespace: df.agent.domain ?? "local", name: df.agent.name, version: "dev" });
1072
+ const id = buildId({ namespace: df.agent.domain ?? "unknown", name: slugify(df.agent.name), version: "dev" });
968
1073
  const aboutme = buildAboutme({
969
1074
  id,
970
1075
  name: df.agent.name,
@@ -972,7 +1077,7 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
972
1077
  version: "dev",
973
1078
  domain: df.agent.domain ?? "",
974
1079
  license: df.agent.license ?? "",
975
- persona: df.persona ?? "SOUL.md",
1080
+ persona: df.persona ?? void 0,
976
1081
  purpose: "development",
977
1082
  compiler: `dot-agent/${COMPILER_VERSION}`,
978
1083
  capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
@@ -985,7 +1090,7 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
985
1090
  // mergedText — it has redundant `merge "..."` lines stripped (see
986
1091
  // collectFiles() for why they're dead weight once flattened).
987
1092
  behavior: allFiles.get("agent.behavior") ?? mergedText,
988
- soul: allFiles.get("SOUL.md"),
1093
+ persona: df.persona ? allFiles.get(df.persona) : void 0,
989
1094
  guides: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("guides/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
990
1095
  knowledge: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("knowledge/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
991
1096
  behaviors: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("behaviors/")).map(([path, content]) => ({ path, content }))