promptopskit 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +88 -15
  2. package/dist/{chunk-X64JII57.js → chunk-HMYPMHTG.js} +91 -4
  3. package/dist/chunk-HMYPMHTG.js.map +1 -0
  4. package/dist/cli/index.js +593 -35
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/index.cjs +1049 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +191 -0
  9. package/dist/index.d.ts +18 -6
  10. package/dist/index.js +28 -4
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/anthropic.cjs +151 -0
  13. package/dist/providers/anthropic.cjs.map +1 -0
  14. package/dist/providers/anthropic.d.cts +11 -0
  15. package/dist/providers/anthropic.d.ts +2 -2
  16. package/dist/providers/gemini.cjs +155 -0
  17. package/dist/providers/gemini.cjs.map +1 -0
  18. package/dist/providers/gemini.d.cts +11 -0
  19. package/dist/providers/gemini.d.ts +2 -2
  20. package/dist/providers/openai.cjs +146 -0
  21. package/dist/providers/openai.cjs.map +1 -0
  22. package/dist/providers/openai.d.cts +11 -0
  23. package/dist/providers/openai.d.ts +2 -2
  24. package/dist/providers/openrouter.cjs +161 -0
  25. package/dist/providers/openrouter.cjs.map +1 -0
  26. package/dist/providers/openrouter.d.cts +13 -0
  27. package/dist/providers/openrouter.d.ts +2 -2
  28. package/dist/{schema-DHRI5Mzl.d.ts → schema-C6smABrt.d.cts} +3 -3
  29. package/dist/schema-C6smABrt.d.ts +695 -0
  30. package/dist/testing.cjs +236 -0
  31. package/dist/testing.cjs.map +1 -0
  32. package/dist/testing.d.cts +17 -0
  33. package/dist/testing.d.ts +1 -1
  34. package/dist/testing.js +1 -1
  35. package/dist/{types-D9hquVja.d.ts → types-D7lW5IYT.d.ts} +1 -1
  36. package/dist/types-S_c-ZEfK.d.cts +40 -0
  37. package/package.json +54 -16
  38. package/dist/chunk-X64JII57.js.map +0 -1
package/README.md CHANGED
@@ -4,21 +4,23 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
5
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
6
6
 
7
- Open-source developer toolkit for managing prompts, system instructions, tools, and model settings **as code**. One `npm install` gives you both a runtime library and a CLI.
7
+ **Centralize prompts, system instructions, tools, and model settings without leaving your codebase.**
8
8
 
9
- PromptOpsKit treats every prompt as a Markdown file YAML front matter defines model settings, sampling parameters, reasoning levels, tool bindings, and override rules, while H1 headings separate system instructions from prompt templates. Variables use `{{ mustache }}` syntax, shared instructions are composed via `includes`, and environment/tier overrides let the same prompt target dev vs. prod or free vs. pro without duplication.
9
+ Your prompts are already in Git. PromptOpsKit makes them manageable. It replaces hardcoded strings and scattered provider-specific glue with structured Markdown files where prompt text, model settings, sampling parameters, tool bindings, environment overrides, and composable shared instructions all live together diffable, reviewable, and release-aware.
10
10
 
11
11
  Provider adapters for OpenAI, Anthropic, Gemini, and OpenRouter produce a ready-to-send **request body only** — no HTTP client, no auth, no headers. Your application owns transport, so PromptOpsKit slots into any stack without opinions about how you call the API.
12
12
 
13
13
  ### Why PromptOpsKit?
14
14
 
15
- - **Version-control your prompts** — plain Markdown files that diff, review, and merge like any other code.
16
- - **One prompt, many providers** — write once, render for OpenAI, Anthropic, Gemini, or OpenRouter with correct body shapes.
17
- - **Override without forking** — environment and tier overrides swap models and parameters without duplicating prompt files.
18
- - **Compose and share** — `includes` lets you define shared tone, policy, or safety instructions once and reuse them everywhere.
15
+ - **Centralized, not scattered** — each prompt is a single Markdown file that captures prompt text, model config, tool bindings, and context rules together.
16
+ - **Operational, not just templated** — model name, temperature, reasoning effort, tools, and response format are declared alongside the prompt they govern.
17
+ - **Reusable, not duplicated** — `includes` lets you define shared tone, policy, or safety instructions once and compose them into any prompt.
18
+ - **Layered defaults, not repetition** — `defaults.md` in any folder sets shared `metadata` and `# System instructions` for that subtree, with nearest-folder override behavior.
19
+ - **Release-aware, not ad hoc** — environment and tier overrides swap models and parameters without forking prompt files.
20
+ - **Provider-portable** — write once, render for OpenAI, Anthropic, Gemini, or OpenRouter with correct body shapes.
19
21
  - **Validate early** — Zod schema validation, Levenshtein-based "did you mean?" suggestions for typos, and variable usage checks catch mistakes before runtime.
20
22
  - **Compile for production** — pre-compile `.md` to JSON or ESM so deployments skip parsing entirely.
21
- - **No lock-in** — body-only output means you choose the HTTP client, auth strategy, and infrastructure.
23
+ - **Repo-native, not dashboard-native** — no hosted service, no external admin tool. Everything lives in source control.
22
24
 
23
25
  ## Install
24
26
 
@@ -59,13 +61,14 @@ sampling:
59
61
  context:
60
62
  inputs:
61
63
  - user_message
64
+ - app_context
62
65
  includes:
63
66
  - ./shared/tone.md
64
67
  ---
65
68
 
66
69
  # System instructions
67
70
 
68
- You are a helpful support assistant.
71
+ You are a helpful support assistant working in {{ app_context }}.
69
72
 
70
73
  # Prompt template
71
74
 
@@ -82,7 +85,10 @@ const kit = createPromptOpsKit({ sourceDir: './prompts' });
82
85
  const result = await kit.renderPrompt({
83
86
  path: 'support/reply',
84
87
  provider: 'openai',
85
- variables: { user_message: 'How do I reset my password?' },
88
+ variables: {
89
+ user_message: 'How do I reset my password?',
90
+ app_context: 'Account settings page',
91
+ },
86
92
  });
87
93
 
88
94
  // result.request.body is ready for fetch()
@@ -101,11 +107,12 @@ const response = await fetch('https://api.openai.com/v1/chat/completions', {
101
107
  - **Prompts as Markdown** — YAML front matter for settings, H1 headings for sections (`# System instructions`, `# Prompt template`, `# Notes`)
102
108
  - **Variable interpolation** — `{{ variable }}` syntax with strict and permissive modes
103
109
  - **Composition** — `includes` to share system instructions across prompts, with circular detection
110
+ - **Folder defaults** — `defaults.md` inheritance for shared metadata and system instructions
104
111
  - **Overrides** — Environment and tier-based overrides (base → env → tier → runtime)
105
112
  - **4 provider adapters** — OpenAI, Anthropic, Gemini, OpenRouter — body-only output
106
113
  - **Validation** — Zod schema validation, Levenshtein-based "did you mean?" for typos, variable usage checks
107
114
  - **Caching** — LRU cache with mtime-based invalidation
108
- - **CLI** — init, validate, compile, render, inspect
115
+ - **CLI** — init, validate, compile, render, inspect, skill
109
116
  - **Compiled artifacts** — Pre-compile `.md` → JSON or ESM for production
110
117
 
111
118
  ## Provider Adapters
@@ -116,19 +123,35 @@ Each adapter produces a `{ body, provider, model }` object shaped for the target
116
123
  // OpenAI
117
124
  import { createPromptOpsKit } from 'promptopskit';
118
125
  const kit = createPromptOpsKit({ sourceDir: './prompts' });
119
- const { request } = await kit.renderPrompt({ path: 'hello', provider: 'openai', variables: { name: 'World' } });
126
+ const { request } = await kit.renderPrompt({
127
+ path: 'hello',
128
+ provider: 'openai',
129
+ variables: { name: 'World', app_context: 'Welcome screen' },
130
+ });
120
131
  // request.body → { model, messages, temperature, reasoning_effort, ... }
121
132
 
122
133
  // Anthropic — system is a top-level field, max_tokens defaults to 4096
123
- const { request } = await kit.renderPrompt({ path: 'hello', provider: 'anthropic', variables: { name: 'World' } });
134
+ const { request } = await kit.renderPrompt({
135
+ path: 'hello',
136
+ provider: 'anthropic',
137
+ variables: { name: 'World', app_context: 'Welcome screen' },
138
+ });
124
139
  // request.body → { model, messages, system, max_tokens, ... }
125
140
 
126
141
  // Gemini — contents/systemInstruction/generationConfig structure
127
- const { request } = await kit.renderPrompt({ path: 'hello', provider: 'gemini', variables: { name: 'World' } });
142
+ const { request } = await kit.renderPrompt({
143
+ path: 'hello',
144
+ provider: 'gemini',
145
+ variables: { name: 'World', app_context: 'Welcome screen' },
146
+ });
128
147
  // request.body → { contents, systemInstruction, generationConfig, ... }
129
148
 
130
149
  // OpenRouter — same shape as OpenAI, different provider label
131
- const { request } = await kit.renderPrompt({ path: 'hello', provider: 'openrouter', variables: { name: 'World' } });
150
+ const { request } = await kit.renderPrompt({
151
+ path: 'hello',
152
+ provider: 'openrouter',
153
+ variables: { name: 'World', app_context: 'Welcome screen' },
154
+ });
132
155
  ```
133
156
 
134
157
  Provider adapters are also available as direct imports:
@@ -194,6 +217,26 @@ includes:
194
217
  Handle support requests carefully.
195
218
  ```
196
219
 
220
+ ## Folder defaults
221
+
222
+ Define a `defaults.md` file in `prompts/` (and optional subfolders) to provide inherited defaults for prompts:
223
+
224
+ - Shared `metadata` defaults in front matter
225
+ - Shared `# System instructions` in body
226
+ - Nearest subfolder `defaults.md` overrides parent defaults
227
+ - Prompt-local values always win over defaults
228
+ - Included files (`includes`) are not affected by folder defaults
229
+
230
+ > `promptopskit init` scaffolds a starter `defaults.md` in the prompts root.
231
+
232
+ ```text
233
+ prompts/
234
+ ├── defaults.md
235
+ └── support/
236
+ ├── defaults.md
237
+ └── reply.md
238
+ ```
239
+
197
240
  ## CLI
198
241
 
199
242
  ```bash
@@ -211,8 +254,34 @@ promptopskit render <file> [--env <name>] [--tier <name>] [--vars <file>] [--jso
211
254
 
212
255
  # Print normalized asset as JSON
213
256
  promptopskit inspect <file>
257
+
258
+ # Deploy AI agent instructions into your project
259
+ promptopskit skill [--target copilot|cursor|generic] [--force]
260
+ ```
261
+
262
+ ## AI Agent Instructions
263
+
264
+ The `skill` command deploys a comprehensive instructions file so AI coding assistants (GitHub Copilot, Cursor, etc.) automatically understand how to create and manage prompts with promptopskit.
265
+
266
+ ```bash
267
+ # Deploy for GitHub Copilot (default)
268
+ promptopskit skill
269
+ # → .github/instructions/promptopskit.instructions.md
270
+
271
+ # Deploy for Cursor
272
+ promptopskit skill --target cursor
273
+ # → .cursor/rules/promptopskit.mdc
274
+
275
+ # Deploy a generic instructions file
276
+ promptopskit skill --target generic
277
+ # → .ai/promptopskit-skill.md
278
+
279
+ # Overwrite an existing instructions file
280
+ promptopskit skill --force
214
281
  ```
215
282
 
283
+ The deployed file covers the prompt format, front matter schema, variable interpolation, includes, overrides, the TypeScript API, provider adapters, and project conventions — everything an AI agent needs to write correct prompts on the first try.
284
+
216
285
  ## Inline Source
217
286
 
218
287
  Render prompts from strings without files:
@@ -290,7 +359,7 @@ Prompt files use YAML front matter with these fields:
290
359
  |-------|------|-------------|
291
360
  | `id` | `string` | Unique prompt identifier (required) |
292
361
  | `schema_version` | `number` | Schema version, currently `1` |
293
- | `provider` | `string` | `openai`, `anthropic`, `google`, `openrouter`, `any` |
362
+ | `provider` | `string` | `openai`, `anthropic`, `gemini` (or `google`), `openrouter`, `any` |
294
363
  | `model` | `string` | Model name |
295
364
  | `fallback_models` | `string[]` | Fallback model list |
296
365
  | `reasoning` | `object` | `{ effort, budget_tokens }` |
@@ -304,6 +373,10 @@ Prompt files use YAML front matter with these fields:
304
373
  | `tiers` | `object` | Named tier overrides |
305
374
  | `metadata` | `object` | `{ owner, tags, review_required, stable }` |
306
375
 
376
+ ## Website
377
+
378
+ The `website/` directory contains a standalone marketing website for PromptOpsKit.
379
+
307
380
  ## License
308
381
 
309
382
  [MIT](LICENSE)
@@ -62,11 +62,17 @@ var SectionsSchema = z.object({
62
62
  prompt_template: z.string().optional(),
63
63
  notes: z.string().optional()
64
64
  });
65
+ var PromptDefaultsSchema = z.object({
66
+ metadata: MetadataSchema.optional(),
67
+ sections: z.object({
68
+ system_instructions: z.string().optional()
69
+ }).optional()
70
+ });
65
71
  var PromptAssetSchema = z.object({
66
72
  id: z.string(),
67
73
  schema_version: z.number().int().positive().default(1),
68
74
  description: z.string().optional(),
69
- provider: z.enum(["openai", "anthropic", "google", "openrouter", "any"]).optional(),
75
+ provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
70
76
  model: z.string().optional(),
71
77
  fallback_models: z.array(z.string()).optional(),
72
78
  reasoning: ReasoningSchema.optional(),
@@ -144,9 +150,90 @@ function parsePrompt(content, filePath) {
144
150
 
145
151
  // src/parser/loader.ts
146
152
  import { readFile } from "fs/promises";
147
- async function loadPromptFile(filePath) {
153
+ import { dirname, join, resolve } from "path";
154
+ import matter2 from "gray-matter";
155
+ var DEFAULTS_FILE_NAME = "defaults.md";
156
+ async function loadPromptFile(filePath, options = {}) {
148
157
  const content = await readFile(filePath, "utf-8");
149
- return parsePrompt(content, filePath);
158
+ const parsed = parsePrompt(content, filePath);
159
+ const root = options.defaultsRoot ?? dirname(filePath);
160
+ const defaults = await loadDefaultsForPath(filePath, root);
161
+ const asset = applyDefaults(parsed.asset, defaults);
162
+ return {
163
+ ...parsed,
164
+ asset
165
+ };
166
+ }
167
+ async function loadDefaultsForPath(filePath, defaultsRoot) {
168
+ const directories = getDirectoriesToCheck(filePath, defaultsRoot);
169
+ let merged = {};
170
+ for (const dir of directories) {
171
+ const defaultsPath = join(dir, DEFAULTS_FILE_NAME);
172
+ try {
173
+ const defaultsContent = await readFile(defaultsPath, "utf-8");
174
+ const defaults = parseDefaults(defaultsContent);
175
+ merged = mergeDefaults(merged, defaults);
176
+ } catch (error) {
177
+ if (error.code !== "ENOENT") {
178
+ throw error;
179
+ }
180
+ }
181
+ }
182
+ return merged;
183
+ }
184
+ function getDirectoriesToCheck(filePath, defaultsRoot) {
185
+ const dirs = [];
186
+ let current = resolve(dirname(filePath));
187
+ const boundary = defaultsRoot ? resolve(defaultsRoot) : void 0;
188
+ while (true) {
189
+ dirs.unshift(current);
190
+ if (boundary && current === boundary || current === dirname(current)) {
191
+ break;
192
+ }
193
+ current = dirname(current);
194
+ }
195
+ return dirs;
196
+ }
197
+ function parseDefaults(content) {
198
+ const { data: frontMatter, content: body } = matter2(content);
199
+ const sections = extractSections(body);
200
+ return PromptDefaultsSchema.parse({
201
+ ...frontMatter,
202
+ sections: {
203
+ system_instructions: sections.system_instructions
204
+ }
205
+ });
206
+ }
207
+ function mergeDefaults(base, local) {
208
+ return {
209
+ metadata: {
210
+ ...base.metadata ?? {},
211
+ ...local.metadata ?? {}
212
+ },
213
+ sections: {
214
+ ...base.sections ?? {},
215
+ ...local.sections ?? {}
216
+ }
217
+ };
218
+ }
219
+ function applyDefaults(asset, defaults) {
220
+ const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
221
+ const hasDefaultSystem = !!defaults.sections?.system_instructions;
222
+ if (!hasDefaultMetadata && !hasDefaultSystem) {
223
+ return asset;
224
+ }
225
+ const mergedMetadata = {
226
+ ...defaults.metadata ?? {},
227
+ ...asset.metadata ?? {}
228
+ };
229
+ const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
230
+ const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;
231
+ const sections = asset.sections ? { ...asset.sections, system_instructions: systemInstructions } : systemInstructions ? { system_instructions: systemInstructions } : void 0;
232
+ return {
233
+ ...asset,
234
+ metadata,
235
+ sections
236
+ };
150
237
  }
151
238
 
152
239
  export {
@@ -156,4 +243,4 @@ export {
156
243
  parsePrompt,
157
244
  loadPromptFile
158
245
  };
159
- //# sourceMappingURL=chunk-X64JII57.js.map
246
+ //# sourceMappingURL=chunk-HMYPMHTG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/schema.ts","../src/parser/sections.ts","../src/parser/parser.ts","../src/parser/loader.ts"],"sourcesContent":["import { z } from 'zod';\r\n\r\n// --- Tool definitions ---\r\n\r\nexport const InlineToolDefSchema = z.object({\r\n name: z.string(),\r\n description: z.string().optional(),\r\n input_schema: z.record(z.unknown()).optional(),\r\n});\r\n\r\nexport type InlineToolDef = z.infer<typeof InlineToolDefSchema>;\r\n\r\nexport const ToolRefSchema = z.union([z.string(), InlineToolDefSchema]);\r\n\r\n// --- MCP ---\r\n\r\nexport const MCPServerRefSchema = z.union([\r\n z.string(),\r\n z.object({\r\n name: z.string(),\r\n config: z.record(z.unknown()).optional(),\r\n }),\r\n]);\r\n\r\nexport type MCPServerRef = z.infer<typeof MCPServerRefSchema>;\r\n\r\n// --- Reasoning ---\r\n\r\nexport const ReasoningSchema = z.object({\r\n effort: z.enum(['low', 'medium', 'high']).optional(),\r\n budget_tokens: z.number().int().positive().optional(),\r\n});\r\n\r\n// --- Sampling ---\r\n\r\nexport const SamplingSchema = z.object({\r\n temperature: z.number().min(0).max(2).optional(),\r\n top_p: z.number().min(0).max(1).optional(),\r\n frequency_penalty: z.number().optional(),\r\n presence_penalty: z.number().optional(),\r\n stop: z.array(z.string()).optional(),\r\n max_output_tokens: z.number().int().positive().optional(),\r\n});\r\n\r\n// --- Response ---\r\n\r\nexport const ResponseSchema = z.object({\r\n format: z.enum(['text', 'json', 'markdown']).optional(),\r\n stream: z.boolean().optional(),\r\n});\r\n\r\n// --- Context ---\r\n\r\nexport const HistorySchema = z.object({\r\n max_items: z.number().int().positive().optional(),\r\n});\r\n\r\nexport const ContextSchema = z.object({\r\n inputs: z.array(z.string()).optional(),\r\n history: HistorySchema.optional(),\r\n});\r\n\r\n// --- Metadata ---\r\n\r\nexport const MetadataSchema = z.object({\r\n owner: z.string().optional(),\r\n tags: z.array(z.string()).optional(),\r\n review_required: z.boolean().optional(),\r\n stable: z.boolean().optional(),\r\n});\r\n\r\n// --- MCP block ---\r\n\r\nexport const MCPSchema = z.object({\r\n servers: z.array(MCPServerRefSchema).optional(),\r\n});\r\n\r\n// --- Overrides (subset allowed in environments/tiers) ---\r\n\r\nexport const PromptAssetOverridesSchema = z.object({\r\n model: z.string().optional(),\r\n fallback_models: z.array(z.string()).optional(),\r\n reasoning: ReasoningSchema.optional(),\r\n sampling: SamplingSchema.optional(),\r\n response: ResponseSchema.optional(),\r\n tools: z.array(ToolRefSchema).optional(),\r\n});\r\n\r\nexport type PromptAssetOverrides = z.infer<typeof PromptAssetOverridesSchema>;\r\n\r\n// --- Source tracking ---\r\n\r\nexport const SourceSchema = z.object({\r\n file_path: z.string().optional(),\r\n checksum: z.string().optional(),\r\n});\r\n\r\n// --- Sections (populated by parser) ---\r\n\r\nexport const SectionsSchema = z.object({\r\n system_instructions: z.string().optional(),\r\n prompt_template: z.string().optional(),\r\n notes: z.string().optional(),\r\n});\r\n\r\n// --- Defaults files (folder-level inheritance) ---\r\n\r\nexport const PromptDefaultsSchema = z.object({\r\n metadata: MetadataSchema.optional(),\r\n sections: z.object({\r\n system_instructions: z.string().optional(),\r\n }).optional(),\r\n});\r\n\r\nexport type PromptDefaults = z.infer<typeof PromptDefaultsSchema>;\r\n\r\n// --- Top-level PromptAsset ---\r\n\r\nexport const PromptAssetSchema = z.object({\r\n id: z.string(),\r\n schema_version: z.number().int().positive().default(1),\r\n description: z.string().optional(),\r\n\r\n provider: z.enum(['openai', 'anthropic', 'google', 'gemini', 'openrouter', 'any']).optional(),\r\n model: z.string().optional(),\r\n fallback_models: z.array(z.string()).optional(),\r\n\r\n reasoning: ReasoningSchema.optional(),\r\n sampling: SamplingSchema.optional(),\r\n response: ResponseSchema.optional(),\r\n\r\n tools: z.array(ToolRefSchema).optional(),\r\n mcp: MCPSchema.optional(),\r\n\r\n context: ContextSchema.optional(),\r\n\r\n includes: z.array(z.string()).optional(),\r\n\r\n environments: z.record(PromptAssetOverridesSchema).optional(),\r\n tiers: z.record(PromptAssetOverridesSchema).optional(),\r\n\r\n metadata: MetadataSchema.optional(),\r\n\r\n // Populated by parser, not authored in YAML\r\n sections: SectionsSchema.optional(),\r\n source: SourceSchema.optional(),\r\n});\r\n\r\nexport type PromptAsset = z.infer<typeof PromptAssetSchema>;\r\n\r\n// --- Resolved asset (after includes, overrides applied) ---\r\n\r\nexport interface ResolvedPromptAsset extends PromptAsset {\r\n sections: {\r\n system_instructions?: string;\r\n prompt_template?: string;\r\n notes?: string;\r\n };\r\n source: {\r\n file_path?: string;\r\n checksum?: string;\r\n };\r\n}\r\n","export interface Sections {\r\n system_instructions?: string;\r\n prompt_template?: string;\r\n notes?: string;\r\n}\r\n\r\nconst SECTION_MAP: Record<string, keyof Sections> = {\r\n 'system instructions': 'system_instructions',\r\n 'prompt template': 'prompt_template',\r\n 'notes': 'notes',\r\n};\r\n\r\n/**\r\n * Extract named sections from the markdown body using H1 headings.\r\n * Case-insensitive. H2+ within a section is treated as content.\r\n * If no H1 headings are found, the entire body is treated as prompt_template.\r\n */\r\nexport function extractSections(body: string): Sections {\r\n const lines = body.split('\\n');\r\n const sections: Sections = {};\r\n\r\n let currentKey: keyof Sections | null = null;\r\n let currentLines: string[] = [];\r\n let foundAnyH1 = false;\r\n\r\n for (const line of lines) {\r\n const h1Match = line.match(/^#\\s+(.+)$/);\r\n if (h1Match) {\r\n // Flush previous section\r\n if (currentKey) {\r\n sections[currentKey] = currentLines.join('\\n').trim();\r\n }\r\n\r\n foundAnyH1 = true;\r\n const heading = h1Match[1].trim().toLowerCase();\r\n currentKey = SECTION_MAP[heading] ?? null;\r\n currentLines = [];\r\n } else {\r\n currentLines.push(line);\r\n }\r\n }\r\n\r\n // Flush last section\r\n if (currentKey) {\r\n sections[currentKey] = currentLines.join('\\n').trim();\r\n }\r\n\r\n // If no H1 headings found, treat entire body as prompt_template\r\n if (!foundAnyH1) {\r\n const trimmed = body.trim();\r\n if (trimmed) {\r\n sections.prompt_template = trimmed;\r\n }\r\n }\r\n\r\n return sections;\r\n}\r\n","import matter from 'gray-matter';\r\nimport { PromptAssetSchema } from '../schema/index.js';\r\nimport type { PromptAsset } from '../schema/index.js';\r\nimport { extractSections } from './sections.js';\r\n\r\nexport interface ParseResult {\r\n asset: PromptAsset;\r\n raw: {\r\n frontMatter: Record<string, unknown>;\r\n body: string;\r\n };\r\n}\r\n\r\n/**\r\n * Parse a prompt markdown string (YAML front matter + markdown body)\r\n * into a validated PromptAsset.\r\n */\r\nexport function parsePrompt(content: string, filePath?: string): ParseResult {\r\n const { data: frontMatter, content: body } = matter(content);\r\n\r\n const sections = extractSections(body);\r\n\r\n const raw = {\r\n ...frontMatter,\r\n sections,\r\n source: filePath ? { file_path: filePath } : undefined,\r\n };\r\n\r\n const asset = PromptAssetSchema.parse(raw);\r\n\r\n return {\r\n asset,\r\n raw: {\r\n frontMatter: frontMatter as Record<string, unknown>,\r\n body,\r\n },\r\n };\r\n}\r\n","import { readFile } from 'node:fs/promises';\r\nimport { dirname, join, resolve } from 'node:path';\r\nimport matter from 'gray-matter';\r\nimport { parsePrompt } from './parser.js';\r\nimport type { ParseResult } from './parser.js';\r\nimport { extractSections } from './sections.js';\r\nimport { PromptDefaultsSchema } from '../schema/index.js';\r\nimport type { PromptDefaults } from '../schema/index.js';\r\n\r\nconst DEFAULTS_FILE_NAME = 'defaults.md';\r\n\r\nexport interface LoadPromptOptions {\r\n /**\r\n * Optional boundary directory for defaults discovery.\r\n * If provided, defaults are loaded from this directory down to the prompt directory.\r\n */\r\n defaultsRoot?: string;\r\n}\r\n\r\n/**\r\n * Load and parse a prompt file from disk.\r\n */\r\nexport async function loadPromptFile(filePath: string, options: LoadPromptOptions = {}): Promise<ParseResult> {\r\n const content = await readFile(filePath, 'utf-8');\r\n const parsed = parsePrompt(content, filePath);\r\n // Default the boundary to the file's own directory so traversal never\r\n // walks above the prompt tree when no explicit root is provided.\r\n const root = options.defaultsRoot ?? dirname(filePath);\r\n const defaults = await loadDefaultsForPath(filePath, root);\r\n const asset = applyDefaults(parsed.asset, defaults);\r\n\r\n return {\r\n ...parsed,\r\n asset,\r\n };\r\n}\r\n\r\nasync function loadDefaultsForPath(filePath: string, defaultsRoot?: string): Promise<PromptDefaults> {\r\n const directories = getDirectoriesToCheck(filePath, defaultsRoot);\r\n let merged: PromptDefaults = {};\r\n\r\n for (const dir of directories) {\r\n const defaultsPath = join(dir, DEFAULTS_FILE_NAME);\r\n try {\r\n const defaultsContent = await readFile(defaultsPath, 'utf-8');\r\n const defaults = parseDefaults(defaultsContent);\r\n merged = mergeDefaults(merged, defaults);\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n return merged;\r\n}\r\n\r\nfunction getDirectoriesToCheck(filePath: string, defaultsRoot?: string): string[] {\r\n const dirs: string[] = [];\r\n let current = resolve(dirname(filePath));\r\n const boundary = defaultsRoot ? resolve(defaultsRoot) : undefined;\r\n\r\n while (true) {\r\n dirs.unshift(current);\r\n if ((boundary && current === boundary) || current === dirname(current)) {\r\n break;\r\n }\r\n current = dirname(current);\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\nfunction parseDefaults(content: string): PromptDefaults {\r\n const { data: frontMatter, content: body } = matter(content);\r\n const sections = extractSections(body);\r\n\r\n return PromptDefaultsSchema.parse({\r\n ...frontMatter,\r\n sections: {\r\n system_instructions: sections.system_instructions,\r\n },\r\n });\r\n}\r\n\r\nfunction mergeDefaults(base: PromptDefaults, local: PromptDefaults): PromptDefaults {\r\n return {\r\n metadata: {\r\n ...(base.metadata ?? {}),\r\n ...(local.metadata ?? {}),\r\n },\r\n sections: {\r\n ...(base.sections ?? {}),\r\n ...(local.sections ?? {}),\r\n },\r\n };\r\n}\r\n\r\nfunction applyDefaults(asset: ParseResult['asset'], defaults: PromptDefaults): ParseResult['asset'] {\r\n const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;\r\n const hasDefaultSystem = !!defaults.sections?.system_instructions;\r\n\r\n // Short-circuit: nothing to merge\r\n if (!hasDefaultMetadata && !hasDefaultSystem) {\r\n return asset;\r\n }\r\n\r\n const mergedMetadata = {\r\n ...(defaults.metadata ?? {}),\r\n ...(asset.metadata ?? {}),\r\n };\r\n const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : undefined;\r\n\r\n const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;\r\n\r\n const sections = asset.sections\r\n ? { ...asset.sections, system_instructions: systemInstructions }\r\n : systemInstructions\r\n ? { system_instructions: systemInstructions }\r\n : undefined;\r\n\r\n return {\r\n ...asset,\r\n metadata,\r\n sections,\r\n };\r\n}\r\n"],"mappings":"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","names":["matter","matter"]}