From 7a3d3afb53ae412177f0f8589d08a48dbfcd1dde Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:00:51 +0000
Subject: [PATCH 1/5] Initial plan


From b4895b9ca8165da4a0fcefe1040d318ff2adf9f6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:07:04 +0000
Subject: [PATCH 2/5] Initial analysis and planning for frontmatter parameter
 interpolation fix

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 packages/core/package.json | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/packages/core/package.json b/packages/core/package.json
index a80cf38b72..719b5508bc 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -4,7 +4,10 @@
   "version": "2.4.0",
   "license": "MIT",
   "type": "module",
-  "repository": "github:microsoft/genaiscript",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/microsoft/genaiscript.git"
+  },
   "bugs": {
     "url": "https://github.com/microsoft/genaiscript/issues"
   },
@@ -175,5 +178,13 @@
   },
   "main": "./dist/commonjs/index.js",
   "types": "./dist/commonjs/index.d.ts",
-  "module": "./dist/esm/index.js"
+  "module": "./dist/esm/index.js",
+  "description": "Core package for the GenAIScript project, which provides the foundational libraries and tools to build and execute GenAIScript code.",
+  "directories": {
+    "doc": "docs",
+    "test": "test"
+  },
+  "keywords": [],
+  "author": "",
+  "homepage": "https://github.com/microsoft/genaiscript#readme"
 }

From 2ca3920b327cb524a8695ce67af13d978f5f4a70 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:10:11 +0000
Subject: [PATCH 3/5] Fix frontmatter parameter interpolation in mustache.ts

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 packages/core/src/mustache.ts       | 32 +++++++++++++----
 packages/core/test/mustache.test.ts | 56 +++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/packages/core/src/mustache.ts b/packages/core/src/mustache.ts
index c6bd476a16..f0f6d817cb 100644
--- a/packages/core/src/mustache.ts
+++ b/packages/core/src/mustache.ts
@@ -1,14 +1,14 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import { splitMarkdown } from "./frontmatter.js";
+import { splitMarkdown, frontmatterTryParse } from "./frontmatter.js";
 import Mustache from "mustache";
 import { jinjaRender } from "./jinja.js";
 import type { ImportTemplateOptions } from "./types.js";
 
 /**
  * Processes a markdown string by applying Mustache or Jinja templating.
- * Removes frontmatter, prompty roles, and XML tags before interpolation.
+ * Extracts frontmatter parameters and merges them with provided data before interpolation.
  * @param md The markdown string to process.
  * @param data The data for variable interpolation.
  * @param options Configuration for templating format, e.g., Mustache or Jinja.
@@ -19,10 +19,30 @@ export async function interpolateVariables(
   data: Record<string, any>,
   options?: ImportTemplateOptions,
 ): Promise<string> {
-  if (!md || !data) return md;
+  if (!md) return md;
   const { format } = options || {};
-  // remove frontmatter
+  
+  // Extract frontmatter and content
   let { content } = splitMarkdown(md);
+  
+  // Extract parameters from frontmatter and merge with provided data
+  const frontmatter = frontmatterTryParse(md);
+  let mergedData = { ...(data ?? {}) };
+  
+  if (frontmatter?.value?.parameters) {
+    // Extract default values from frontmatter parameters
+    const frontmatterDefaults: Record<string, any> = {};
+    for (const [key, param] of Object.entries(frontmatter.value.parameters)) {
+      if (typeof param === 'object' && param !== null && 'default' in param) {
+        // Only use frontmatter default if no data provided for this key
+        if (!(key in mergedData)) {
+          frontmatterDefaults[key] = param.default;
+        }
+      }
+    }
+    // Merge frontmatter defaults with provided data (data takes precedence)
+    mergedData = { ...frontmatterDefaults, ...mergedData };
+  }
 
   // remove prompty roles
   // https://github.com/microsoft/prompty/blob/main/runtime/prompty/prompty/parsers.py#L113C21-L113C77
@@ -31,8 +51,8 @@ export async function interpolateVariables(
   if (content) {
     // remove xml tags
     // https://humanloop.com/docs/prompt-file-format
-    if (format === "jinja") content = jinjaRender(content, data ?? {});
-    else content = Mustache.render(content, data ?? {});
+    if (format === "jinja") content = jinjaRender(content, mergedData);
+    else content = Mustache.render(content, mergedData);
   }
 
   return content;
diff --git a/packages/core/test/mustache.test.ts b/packages/core/test/mustache.test.ts
index 6a788dbd82..c409584324 100644
--- a/packages/core/test/mustache.test.ts
+++ b/packages/core/test/mustache.test.ts
@@ -128,4 +128,60 @@ Hello {{name}}! This message will repeat {{count}} times.`;
         
     assert.strictEqual(output, `Hello GenAI! This message will repeat 5 times.`);
   });
+
+  test("should use frontmatter parameter defaults when no data provided", async () => {
+    const md = `---
+parameters:
+  name:
+    type: string
+    default: "World"
+  count:
+    type: number
+    default: 3
+---
+Hello {{name}}! This message will repeat {{count}} times.`;
+        
+    const output = await interpolateVariables(md, {});
+        
+    assert.strictEqual(output, `Hello World! This message will repeat 3 times.`);
+  });
+
+  test("should handle partial data with frontmatter defaults", async () => {
+    const md = `---
+parameters:
+  name:
+    type: string
+    default: "World"
+  count:
+    type: number
+    default: 3
+---
+Hello {{name}}! This message will repeat {{count}} times.`;
+        
+    const output = await interpolateVariables(md, {
+      name: "GenAI"
+      // count should use default from frontmatter
+    });
+        
+    assert.strictEqual(output, `Hello GenAI! This message will repeat 3 times.`);
+  });
+
+  test("should handle frontmatter parameters with Jinja format", async () => {
+    const md = `---
+parameters:
+  name:
+    type: string
+    default: "World"
+  count:
+    type: number
+    default: 3
+---
+Hello {{ name }}! This message will repeat {{ count }} times.`;
+        
+    const output = await interpolateVariables(md, {
+      name: "GenAI"
+    }, { format: "jinja" });
+        
+    assert.strictEqual(output, `Hello GenAI! This message will repeat 3 times.`);
+  });
 });

From 81fcf53b92e9e123530db6390e04b77d889ea7ff Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 22 Aug 2025 15:13:55 +0000
Subject: [PATCH 4/5] Enhance frontmatter parameter support for prompty format

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 packages/core/src/mustache.ts       | 30 ++++++++++++++++++-------
 packages/core/src/template.ts       |  8 +++++--
 packages/core/test/mustache.test.ts | 34 +++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 10 deletions(-)

diff --git a/packages/core/src/mustache.ts b/packages/core/src/mustache.ts
index f0f6d817cb..19c237409c 100644
--- a/packages/core/src/mustache.ts
+++ b/packages/core/src/mustache.ts
@@ -29,19 +29,33 @@ export async function interpolateVariables(
   const frontmatter = frontmatterTryParse(md);
   let mergedData = { ...(data ?? {}) };
   
-  if (frontmatter?.value?.parameters) {
-    // Extract default values from frontmatter parameters
+  if (frontmatter?.value) {
+    // Extract default values from frontmatter parameters or inputs (prompty format)
     const frontmatterDefaults: Record<string, any> = {};
-    for (const [key, param] of Object.entries(frontmatter.value.parameters)) {
-      if (typeof param === 'object' && param !== null && 'default' in param) {
-        // Only use frontmatter default if no data provided for this key
+    const parameterSource = frontmatter.value.parameters || frontmatter.value.inputs;
+    
+    if (parameterSource) {
+      for (const [key, param] of Object.entries(parameterSource)) {
+        if (typeof param === 'object' && param !== null && 'default' in param) {
+          // Only use frontmatter default if no data provided for this key
+          if (!(key in mergedData)) {
+            frontmatterDefaults[key] = param.default;
+          }
+        }
+      }
+      // Merge frontmatter defaults with provided data (data takes precedence)
+      mergedData = { ...frontmatterDefaults, ...mergedData };
+    }
+    
+    // Handle prompty sample data as defaults
+    if (frontmatter.value.sample && typeof frontmatter.value.sample === 'object') {
+      for (const [key, value] of Object.entries(frontmatter.value.sample)) {
         if (!(key in mergedData)) {
-          frontmatterDefaults[key] = param.default;
+          frontmatterDefaults[key] = value;
         }
       }
+      mergedData = { ...frontmatterDefaults, ...mergedData };
     }
-    // Merge frontmatter defaults with provided data (data takes precedence)
-    mergedData = { ...frontmatterDefaults, ...mergedData };
   }
 
   // remove prompty roles
diff --git a/packages/core/src/template.ts b/packages/core/src/template.ts
index eefabb8526..0c187edd6f 100644
--- a/packages/core/src/template.ts
+++ b/packages/core/src/template.ts
@@ -152,11 +152,15 @@ function parsePromptScriptTools(jsSource: string) {
  */
 function extractFrontmatterParameters(content: string): Record<string, any> | undefined {
   const fm = frontmatterTryParse(content);
-  if (!fm?.value?.parameters) return undefined;
+  if (!fm?.value) return undefined;
+  
+  // Handle both 'parameters' and 'inputs' (prompty format)
+  const parameterSource = fm.value.parameters || fm.value.inputs;
+  if (!parameterSource) return undefined;
   
   // Return the parameters directly - they should already be in the correct format
   // with type definitions like { type: "string", default: "value" }
-  return fm.value.parameters;
+  return parameterSource;
 }
 
 /**
diff --git a/packages/core/test/mustache.test.ts b/packages/core/test/mustache.test.ts
index c409584324..5a36293240 100644
--- a/packages/core/test/mustache.test.ts
+++ b/packages/core/test/mustache.test.ts
@@ -184,4 +184,38 @@ Hello {{ name }}! This message will repeat {{ count }} times.`;
         
     assert.strictEqual(output, `Hello GenAI! This message will repeat 3 times.`);
   });
+
+  test("should handle prompty format with inputs and sample", async () => {
+    const md = `---
+name: Basic Prompt
+description: A basic prompt
+inputs:
+  question:
+    type: string
+sample:
+  "question": "What is the capital of France?"
+---
+Question: {{question}}`;
+        
+    const output = await interpolateVariables(md, {});
+        
+    assert.strictEqual(output, `Question: What is the capital of France?`);
+  });
+
+  test("should handle prompty format with data override", async () => {
+    const md = `---
+inputs:
+  question:
+    type: string
+sample:
+  "question": "Default question"
+---
+Question: {{question}}`;
+        
+    const output = await interpolateVariables(md, {
+      question: "Custom question"
+    });
+        
+    assert.strictEqual(output, `Question: Custom question`);
+  });
 });

From ef12503f97cc4537865b152c12dac10a0779c4cb Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 22 Aug 2025 16:20:18 +0000
Subject: [PATCH 5/5] genai: /docs [skip ci]

---
 packages/core/src/mustache.ts       | 14 +++++-----
 packages/core/src/template.ts       | 40 +++++++++++++++++++----------
 packages/core/test/mustache.test.ts | 38 +++++++++++++++------------
 3 files changed, 55 insertions(+), 37 deletions(-)

diff --git a/packages/core/src/mustache.ts b/packages/core/src/mustache.ts
index 19c237409c..406552b2c1 100644
--- a/packages/core/src/mustache.ts
+++ b/packages/core/src/mustache.ts
@@ -21,22 +21,22 @@ export async function interpolateVariables(
 ): Promise<string> {
   if (!md) return md;
   const { format } = options || {};
-  
+
   // Extract frontmatter and content
   let { content } = splitMarkdown(md);
-  
+
   // Extract parameters from frontmatter and merge with provided data
   const frontmatter = frontmatterTryParse(md);
   let mergedData = { ...(data ?? {}) };
-  
+
   if (frontmatter?.value) {
     // Extract default values from frontmatter parameters or inputs (prompty format)
     const frontmatterDefaults: Record<string, any> = {};
     const parameterSource = frontmatter.value.parameters || frontmatter.value.inputs;
-    
+
     if (parameterSource) {
       for (const [key, param] of Object.entries(parameterSource)) {
-        if (typeof param === 'object' && param !== null && 'default' in param) {
+        if (typeof param === "object" && param !== null && "default" in param) {
           // Only use frontmatter default if no data provided for this key
           if (!(key in mergedData)) {
             frontmatterDefaults[key] = param.default;
@@ -46,9 +46,9 @@ export async function interpolateVariables(
       // Merge frontmatter defaults with provided data (data takes precedence)
       mergedData = { ...frontmatterDefaults, ...mergedData };
     }
-    
+
     // Handle prompty sample data as defaults
-    if (frontmatter.value.sample && typeof frontmatter.value.sample === 'object') {
+    if (frontmatter.value.sample && typeof frontmatter.value.sample === "object") {
       for (const [key, value] of Object.entries(frontmatter.value.sample)) {
         if (!(key in mergedData)) {
           frontmatterDefaults[key] = value;
diff --git a/packages/core/src/template.ts b/packages/core/src/template.ts
index 0c187edd6f..a3b174ddf5 100644
--- a/packages/core/src/template.ts
+++ b/packages/core/src/template.ts
@@ -15,7 +15,14 @@ import { deleteUndefinedValues } from "./cleaners.js";
 import { markdownScriptParse } from "./markdownscript.js";
 import { readJSON } from "./fs.js";
 import { frontmatterTryParse } from "./frontmatter.js";
-import type { PromptArgs, PromptScript, McpServersConfig, McpServerConfig, McpAgentServersConfig, McpAgentServerConfig } from "./types.js";
+import type {
+  PromptArgs,
+  PromptScript,
+  McpServersConfig,
+  McpServerConfig,
+  McpAgentServersConfig,
+  McpAgentServerConfig,
+} from "./types.js";
 import { basename, resolve, dirname } from "node:path";
 import { readText } from "./fs.js";
 
@@ -41,10 +48,10 @@ export function templateIdFromFileName(filename: string) {
  */
 async function resolveMcpServersConfig(
   mcpServers: McpServersConfig | undefined,
-  scriptPath: string
+  scriptPath: string,
 ): Promise<Record<string, Omit<McpServerConfig, "id" | "options">> | undefined> {
   if (!mcpServers) return undefined;
-  
+
   if (typeof mcpServers === "string") {
     // Handle file path - resolve relative to script directory
     const configPath = resolve(dirname(scriptPath), mcpServers);
@@ -55,7 +62,9 @@ async function resolveMcpServersConfig(
         if (config.mcpServers && typeof config.mcpServers === "object") {
           return config.mcpServers as Record<string, Omit<McpServerConfig, "id" | "options">>;
         } else {
-          throw new Error(`Invalid MCP server configuration format in ${configPath}. Configuration must have a root 'mcpServers' field.`);
+          throw new Error(
+            `Invalid MCP server configuration format in ${configPath}. Configuration must have a root 'mcpServers' field.`,
+          );
         }
       } else {
         throw new Error(`Invalid MCP server configuration format in ${configPath}`);
@@ -77,10 +86,10 @@ async function resolveMcpServersConfig(
  */
 async function resolveMcpAgentServersConfig(
   mcpAgentServers: McpAgentServersConfig | undefined,
-  scriptPath: string
+  scriptPath: string,
 ): Promise<Record<string, Omit<McpAgentServerConfig, "id" | "options">> | undefined> {
   if (!mcpAgentServers) return undefined;
-  
+
   if (typeof mcpAgentServers === "string") {
     // Handle file path - resolve relative to script directory
     const configPath = resolve(dirname(scriptPath), mcpAgentServers);
@@ -89,9 +98,14 @@ async function resolveMcpAgentServersConfig(
       if (typeof config === "object" && config !== null) {
         // Require Claude format with root mcpAgentServers field
         if (config.mcpAgentServers && typeof config.mcpAgentServers === "object") {
-          return config.mcpAgentServers as Record<string, Omit<McpAgentServerConfig, "id" | "options">>;
+          return config.mcpAgentServers as Record<
+            string,
+            Omit<McpAgentServerConfig, "id" | "options">
+          >;
         } else {
-          throw new Error(`Invalid MCP agent server configuration format in ${configPath}. Configuration must have a root 'mcpAgentServers' field.`);
+          throw new Error(
+            `Invalid MCP agent server configuration format in ${configPath}. Configuration must have a root 'mcpAgentServers' field.`,
+          );
         }
       } else {
         throw new Error(`Invalid MCP agent server configuration format in ${configPath}`);
@@ -153,11 +167,11 @@ function parsePromptScriptTools(jsSource: string) {
 function extractFrontmatterParameters(content: string): Record<string, any> | undefined {
   const fm = frontmatterTryParse(content);
   if (!fm?.value) return undefined;
-  
+
   // Handle both 'parameters' and 'inputs' (prompty format)
   const parameterSource = fm.value.parameters || fm.value.inputs;
   if (!parameterSource) return undefined;
-  
+
   // Return the parameters directly - they should already be in the correct format
   // with type definitions like { type: "string", default: "value" }
   return parameterSource;
@@ -216,16 +230,16 @@ async function parsePromptTemplateCore(filename: string, content: string) {
  */
 export async function parsePromptScript(filename: string, content: string) {
   const script = await parsePromptTemplateCore(filename, content);
-  
+
   // Extract frontmatter parameters from markdown files and merge them
   // This handles the case where markdown scripts define parameters in frontmatter
   const frontmatterParameters = extractFrontmatterParameters(content);
   if (frontmatterParameters) {
     script.parameters = {
       ...(script.parameters || {}),
-      ...frontmatterParameters
+      ...frontmatterParameters,
     };
   }
-  
+
   return script;
 }
diff --git a/packages/core/test/mustache.test.ts b/packages/core/test/mustache.test.ts
index 5a36293240..89bd04589d 100644
--- a/packages/core/test/mustache.test.ts
+++ b/packages/core/test/mustache.test.ts
@@ -120,12 +120,12 @@ parameters:
     default: 3
 ---
 Hello {{name}}! This message will repeat {{count}} times.`;
-        
+
     const output = await interpolateVariables(md, {
       name: "GenAI",
       count: 5,
     });
-        
+
     assert.strictEqual(output, `Hello GenAI! This message will repeat 5 times.`);
   });
 
@@ -140,9 +140,9 @@ parameters:
     default: 3
 ---
 Hello {{name}}! This message will repeat {{count}} times.`;
-        
+
     const output = await interpolateVariables(md, {});
-        
+
     assert.strictEqual(output, `Hello World! This message will repeat 3 times.`);
   });
 
@@ -157,12 +157,12 @@ parameters:
     default: 3
 ---
 Hello {{name}}! This message will repeat {{count}} times.`;
-        
+
     const output = await interpolateVariables(md, {
-      name: "GenAI"
+      name: "GenAI",
       // count should use default from frontmatter
     });
-        
+
     assert.strictEqual(output, `Hello GenAI! This message will repeat 3 times.`);
   });
 
@@ -177,11 +177,15 @@ parameters:
     default: 3
 ---
 Hello {{ name }}! This message will repeat {{ count }} times.`;
-        
-    const output = await interpolateVariables(md, {
-      name: "GenAI"
-    }, { format: "jinja" });
-        
+
+    const output = await interpolateVariables(
+      md,
+      {
+        name: "GenAI",
+      },
+      { format: "jinja" },
+    );
+
     assert.strictEqual(output, `Hello GenAI! This message will repeat 3 times.`);
   });
 
@@ -196,9 +200,9 @@ sample:
   "question": "What is the capital of France?"
 ---
 Question: {{question}}`;
-        
+
     const output = await interpolateVariables(md, {});
-        
+
     assert.strictEqual(output, `Question: What is the capital of France?`);
   });
 
@@ -211,11 +215,11 @@ sample:
   "question": "Default question"
 ---
 Question: {{question}}`;
-        
+
     const output = await interpolateVariables(md, {
-      question: "Custom question"
+      question: "Custom question",
     });
-        
+
     assert.strictEqual(output, `Question: Custom question`);
   });
 });
