promptopskit 0.4.2 → 0.6.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.
Files changed (82) hide show
  1. package/README.md +105 -62
  2. package/SKILL.md +376 -16
  3. package/dist/{chunk-7MRMN3SW.js → chunk-55B7QQBK.js} +6 -4
  4. package/dist/chunk-55B7QQBK.js.map +1 -0
  5. package/dist/{chunk-HWEVRBKZ.js → chunk-AGTGIZMR.js} +6 -2
  6. package/dist/chunk-AGTGIZMR.js.map +1 -0
  7. package/dist/chunk-FNCIBZJF.js +130 -0
  8. package/dist/chunk-FNCIBZJF.js.map +1 -0
  9. package/dist/{chunk-XL76NHZR.js → chunk-GLT3YYWK.js} +6 -4
  10. package/dist/chunk-GLT3YYWK.js.map +1 -0
  11. package/dist/{chunk-5AAYDOPU.js → chunk-IPFD5Y3O.js} +4 -3
  12. package/dist/chunk-IPFD5Y3O.js.map +1 -0
  13. package/dist/{chunk-YI4LILPY.js → chunk-JR7252DG.js} +584 -520
  14. package/dist/chunk-JR7252DG.js.map +1 -0
  15. package/dist/{chunk-JKBWC5WJ.js → chunk-UY5OKMVP.js} +302 -21
  16. package/dist/chunk-UY5OKMVP.js.map +1 -0
  17. package/dist/{chunk-MI3M6KDD.js → chunk-VMLGUI4F.js} +6 -4
  18. package/dist/chunk-VMLGUI4F.js.map +1 -0
  19. package/dist/{chunk-Z4IPWEUU.js → chunk-XEZJZ3IG.js} +6 -4
  20. package/dist/chunk-XEZJZ3IG.js.map +1 -0
  21. package/dist/cli/index.js +435 -106
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/index.cjs +1159 -636
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +10 -9
  26. package/dist/index.d.ts +10 -9
  27. package/dist/index.js +79 -17
  28. package/dist/index.js.map +1 -1
  29. package/dist/providers/anthropic.cjs +875 -533
  30. package/dist/providers/anthropic.cjs.map +1 -1
  31. package/dist/providers/anthropic.d.cts +2 -2
  32. package/dist/providers/anthropic.d.ts +2 -2
  33. package/dist/providers/anthropic.js +3 -3
  34. package/dist/providers/gemini.cjs +875 -533
  35. package/dist/providers/gemini.cjs.map +1 -1
  36. package/dist/providers/gemini.d.cts +2 -2
  37. package/dist/providers/gemini.d.ts +2 -2
  38. package/dist/providers/gemini.js +3 -3
  39. package/dist/providers/llmasaservice.cjs +1735 -0
  40. package/dist/providers/llmasaservice.cjs.map +1 -0
  41. package/dist/providers/llmasaservice.d.cts +28 -0
  42. package/dist/providers/llmasaservice.d.ts +28 -0
  43. package/dist/providers/llmasaservice.js +20 -0
  44. package/dist/providers/llmasaservice.js.map +1 -0
  45. package/dist/providers/openai-responses.cjs +875 -533
  46. package/dist/providers/openai-responses.cjs.map +1 -1
  47. package/dist/providers/openai-responses.d.cts +2 -2
  48. package/dist/providers/openai-responses.d.ts +2 -2
  49. package/dist/providers/openai-responses.js +3 -3
  50. package/dist/providers/openai.cjs +875 -533
  51. package/dist/providers/openai.cjs.map +1 -1
  52. package/dist/providers/openai.d.cts +2 -2
  53. package/dist/providers/openai.d.ts +2 -2
  54. package/dist/providers/openai.js +3 -3
  55. package/dist/providers/openrouter.cjs +876 -533
  56. package/dist/providers/openrouter.cjs.map +1 -1
  57. package/dist/providers/openrouter.d.cts +2 -2
  58. package/dist/providers/openrouter.d.ts +2 -2
  59. package/dist/providers/openrouter.js +4 -4
  60. package/dist/{schema-B9A1zLRk.d.cts → schema-CuYsv8JA.d.cts} +853 -7
  61. package/dist/{schema-B9A1zLRk.d.ts → schema-CuYsv8JA.d.ts} +853 -7
  62. package/dist/testing.cjs +64 -9
  63. package/dist/testing.cjs.map +1 -1
  64. package/dist/testing.d.cts +1 -1
  65. package/dist/testing.d.ts +1 -1
  66. package/dist/testing.js +1 -1
  67. package/dist/{types-DjGAwJxC.d.ts → types-DpK0mTHL.d.ts} +18 -6
  68. package/dist/{types-DpbzzZO2.d.cts → types-UA_p9uJy.d.cts} +18 -6
  69. package/dist/usagetap/index.cjs +6 -1
  70. package/dist/usagetap/index.cjs.map +1 -1
  71. package/dist/usagetap/index.d.cts +4 -3
  72. package/dist/usagetap/index.d.ts +4 -3
  73. package/dist/usagetap/index.js +3 -1
  74. package/package.json +19 -2
  75. package/dist/chunk-5AAYDOPU.js.map +0 -1
  76. package/dist/chunk-7MRMN3SW.js.map +0 -1
  77. package/dist/chunk-HWEVRBKZ.js.map +0 -1
  78. package/dist/chunk-JKBWC5WJ.js.map +0 -1
  79. package/dist/chunk-MI3M6KDD.js.map +0 -1
  80. package/dist/chunk-XL76NHZR.js.map +0 -1
  81. package/dist/chunk-YI4LILPY.js.map +0 -1
  82. package/dist/chunk-Z4IPWEUU.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -5,7 +5,7 @@ import { readFileSync as readFileSync2 } from "fs";
5
5
 
6
6
  // src/cli/commands/validate.ts
7
7
  import { readdir, stat } from "fs/promises";
8
- import { join as join3, extname } from "path";
8
+ import { join as join3, extname as extname2 } from "path";
9
9
 
10
10
  // src/parser/parser.ts
11
11
  import matter from "gray-matter";
@@ -41,10 +41,26 @@ var ResponseSchema = z.object({
41
41
  format: z.enum(["text", "json", "markdown"]).optional(),
42
42
  stream: z.boolean().optional(),
43
43
  schema: z.record(z.unknown()).optional(),
44
+ schema_ref: z.string().min(1).optional(),
45
+ schema_source: z.object({
46
+ mode: z.enum(["inline", "schema_ref_json", "schema_ref_zod_module"]).optional(),
47
+ ref: z.string().optional(),
48
+ resolved_path: z.string().optional(),
49
+ hash: z.string().optional()
50
+ }).optional(),
44
51
  schema_name: z.string().optional(),
45
52
  schema_description: z.string().optional(),
46
53
  schema_strict: z.boolean().optional()
47
54
  });
55
+ var ResponseSchemaWithValidation = ResponseSchema.superRefine((value, ctx) => {
56
+ if (value.schema !== void 0 && value.schema_ref !== void 0) {
57
+ ctx.addIssue({
58
+ code: z.ZodIssueCode.custom,
59
+ message: "response.schema and response.schema_ref are mutually exclusive",
60
+ path: ["schema_ref"]
61
+ });
62
+ }
63
+ });
48
64
  var AnthropicProviderOptionsSchema = z.object({
49
65
  top_k: z.number().int().min(0).optional(),
50
66
  tool_choice: z.record(z.unknown()).optional(),
@@ -65,10 +81,26 @@ var OpenRouterProviderOptionsSchema = z.object({
65
81
  plugins: z.array(z.record(z.unknown())).optional(),
66
82
  models: z.array(z.string()).optional()
67
83
  });
84
+ var LLMAsAServiceCustomerSchema = z.object({
85
+ customer_id: z.string(),
86
+ customer_name: z.string().optional(),
87
+ customer_user_id: z.string().optional(),
88
+ customer_user_name: z.string().optional(),
89
+ customer_user_email: z.string().optional()
90
+ });
91
+ var LLMAsAServiceProviderOptionsSchema = z.object({
92
+ base_url: z.string().url().optional(),
93
+ project_id: z.string().optional(),
94
+ customer: LLMAsAServiceCustomerSchema.optional(),
95
+ conversationId: z.string().optional(),
96
+ conversationTitle: z.string().optional(),
97
+ projectId: z.string().optional()
98
+ });
68
99
  var ProviderOptionsSchema = z.object({
69
100
  anthropic: AnthropicProviderOptionsSchema.optional(),
70
101
  gemini: GeminiProviderOptionsSchema.optional(),
71
- openrouter: OpenRouterProviderOptionsSchema.optional()
102
+ openrouter: OpenRouterProviderOptionsSchema.optional(),
103
+ llmasaservice: LLMAsAServiceProviderOptionsSchema.optional()
72
104
  });
73
105
  var RawProviderBodySchema = z.object({
74
106
  openai: z.record(z.unknown()).optional(),
@@ -77,7 +109,8 @@ var RawProviderBodySchema = z.object({
77
109
  anthropic: z.record(z.unknown()).optional(),
78
110
  gemini: z.record(z.unknown()).optional(),
79
111
  google: z.record(z.unknown()).optional(),
80
- openrouter: z.record(z.unknown()).optional()
112
+ openrouter: z.record(z.unknown()).optional(),
113
+ llmasaservice: z.record(z.unknown()).optional()
81
114
  });
82
115
  var OpenAICacheSchema = z.object({
83
116
  prompt_cache_key: z.string().min(1).optional(),
@@ -119,6 +152,8 @@ var ContextBuiltInValidatorSchema = z.union([
119
152
  ]);
120
153
  var ContextInputDefinitionObjectSchema = z.object({
121
154
  name: z.string(),
155
+ optional: z.boolean().optional(),
156
+ warnings: z.boolean().optional(),
122
157
  max_size: z.number().int().positive().optional(),
123
158
  trim: z.union([z.boolean(), z.enum(["start", "end", "both"])]).optional(),
124
159
  allow_regex: ContextRegexSchema.optional(),
@@ -148,7 +183,7 @@ var PromptAssetOverridesSchema = z.object({
148
183
  fallback_models: z.array(z.string()).optional(),
149
184
  reasoning: ReasoningSchema.optional(),
150
185
  sampling: SamplingSchema.optional(),
151
- response: ResponseSchema.optional(),
186
+ response: ResponseSchemaWithValidation.optional(),
152
187
  cache: CacheSchema.optional(),
153
188
  raw: RawProviderBodySchema.optional(),
154
189
  tools: z.array(ToolRefSchema).optional(),
@@ -164,9 +199,21 @@ var SectionsSchema = z.object({
164
199
  notes: z.string().optional()
165
200
  });
166
201
  var PromptDefaultsSchema = z.object({
167
- provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
202
+ provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "llmasaservice", "any"]).optional(),
168
203
  model: z.string().optional(),
204
+ fallback_models: z.array(z.string()).optional(),
205
+ reasoning: ReasoningSchema.optional(),
206
+ sampling: SamplingSchema.optional(),
207
+ response: ResponseSchema.optional(),
169
208
  cache: CacheSchema.optional(),
209
+ raw: RawProviderBodySchema.optional(),
210
+ tools: z.array(ToolRefSchema).optional(),
211
+ provider_options: ProviderOptionsSchema.optional(),
212
+ mcp: MCPSchema.optional(),
213
+ context: ContextSchema.optional(),
214
+ includes: z.array(z.string()).optional(),
215
+ environments: z.record(PromptAssetOverridesSchema).optional(),
216
+ tiers: z.record(PromptAssetOverridesSchema).optional(),
170
217
  metadata: MetadataSchema.optional(),
171
218
  sections: z.object({
172
219
  system_instructions: z.string().optional()
@@ -176,12 +223,12 @@ var PromptAssetSchema = z.object({
176
223
  id: z.string(),
177
224
  schema_version: z.number().int().positive().default(1),
178
225
  description: z.string().optional(),
179
- provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
226
+ provider: z.enum(["openai", "openai-responses", "anthropic", "google", "gemini", "openrouter", "llmasaservice", "any"]).optional(),
180
227
  model: z.string().optional(),
181
228
  fallback_models: z.array(z.string()).optional(),
182
229
  reasoning: ReasoningSchema.optional(),
183
230
  sampling: SamplingSchema.optional(),
184
- response: ResponseSchema.optional(),
231
+ response: ResponseSchemaWithValidation.optional(),
185
232
  cache: CacheSchema.optional(),
186
233
  raw: RawProviderBodySchema.optional(),
187
234
  tools: z.array(ToolRefSchema).optional(),
@@ -373,16 +420,122 @@ function parsePrompt(content, filePath) {
373
420
  }
374
421
 
375
422
  // src/parser/loader.ts
376
- import { readFile } from "fs/promises";
377
- import { dirname, join, resolve } from "path";
423
+ import { readFile as readFile2 } from "fs/promises";
424
+ import { dirname as dirname2, join, relative, resolve as resolve2 } from "path";
378
425
  import matter2 from "gray-matter";
426
+
427
+ // src/parser/response-schema-ref.ts
428
+ import { readFile } from "fs/promises";
429
+ import { createHash } from "crypto";
430
+ import { dirname, extname, resolve } from "path";
431
+ import { pathToFileURL } from "url";
432
+ import { z as z2 } from "zod";
433
+ import { zodToJsonSchema } from "zod-to-json-schema";
434
+ var SUPPORTED_SCHEMA_REF_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
435
+ async function resolveJsonSchemaRef(schemaRef, promptFilePath) {
436
+ const resolvedPath = resolve(dirname(promptFilePath), schemaRef);
437
+ let raw;
438
+ try {
439
+ raw = await readFile(resolvedPath, "utf-8");
440
+ } catch (error) {
441
+ const code = error.code;
442
+ if (code === "ENOENT") {
443
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
444
+ }
445
+ throw error;
446
+ }
447
+ let parsed;
448
+ try {
449
+ parsed = JSON.parse(raw);
450
+ } catch {
451
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" is not valid JSON`);
452
+ }
453
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
454
+ throw new Error(`POK050: response.schema_ref "${schemaRef}" must resolve to a JSON object schema`);
455
+ }
456
+ return {
457
+ schema: parsed,
458
+ resolvedPath,
459
+ hash: createHash("sha256").update(raw).digest("hex")
460
+ };
461
+ }
462
+ async function resolveZodSchemaRef(schemaRef, promptFilePath) {
463
+ const resolvedPath = resolve(dirname(promptFilePath), schemaRef);
464
+ let moduleSource;
465
+ try {
466
+ moduleSource = await readFile(resolvedPath, "utf-8");
467
+ } catch (error) {
468
+ const code = error.code;
469
+ if (code === "ENOENT") {
470
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" not found (resolved from ${promptFilePath})`);
471
+ }
472
+ throw error;
473
+ }
474
+ let imported;
475
+ try {
476
+ imported = await import(pathToFileURL(resolvedPath).href);
477
+ } catch (error) {
478
+ const message = error instanceof Error ? error.message : String(error);
479
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" could not be imported as a module (${message})`);
480
+ }
481
+ const mod = imported;
482
+ const candidate = mod.default ?? mod.schema;
483
+ if (!(candidate instanceof z2.ZodType)) {
484
+ throw new Error('POK051: zod schema modules must export a Zod schema as default export or named export "schema"');
485
+ }
486
+ const jsonSchema = zodToJsonSchema(candidate, {
487
+ target: "jsonSchema7",
488
+ $refStrategy: "none"
489
+ });
490
+ if (!jsonSchema || typeof jsonSchema !== "object" || Array.isArray(jsonSchema)) {
491
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" did not produce a valid JSON schema object`);
492
+ }
493
+ return {
494
+ schema: jsonSchema,
495
+ resolvedPath,
496
+ hash: createHash("sha256").update(moduleSource).digest("hex")
497
+ };
498
+ }
499
+ async function resolveResponseSchemaRef(asset, promptFilePath) {
500
+ const schemaRef = asset.response?.schema_ref;
501
+ if (!schemaRef) return asset;
502
+ const ext = extname(schemaRef).toLowerCase();
503
+ if (!SUPPORTED_SCHEMA_REF_EXTENSIONS.has(ext)) {
504
+ throw new Error(`POK051: response.schema_ref "${schemaRef}" has unsupported extension "${ext}". Use .json, .js, .mjs, or .cjs`);
505
+ }
506
+ const resolved = ext === ".json" ? await resolveJsonSchemaRef(schemaRef, promptFilePath) : await resolveZodSchemaRef(schemaRef, promptFilePath);
507
+ return {
508
+ ...asset,
509
+ response: {
510
+ ...asset.response,
511
+ schema: resolved.schema,
512
+ schema_ref: void 0,
513
+ schema_source: {
514
+ mode: ext === ".json" ? "schema_ref_json" : "schema_ref_zod_module",
515
+ ref: schemaRef,
516
+ resolved_path: resolved.resolvedPath,
517
+ hash: resolved.hash
518
+ }
519
+ }
520
+ };
521
+ }
522
+
523
+ // src/parser/loader.ts
379
524
  var DEFAULTS_FILE_NAME = "defaults.md";
380
525
  async function loadPromptFile(filePath, options = {}) {
381
- const content = await readFile(filePath, "utf-8");
526
+ const content = await readFile2(filePath, "utf-8");
382
527
  const parsed = parsePrompt(content, filePath);
383
- const root = options.defaultsRoot ?? dirname(filePath);
528
+ const root = options.defaultsRoot ?? dirname2(filePath);
384
529
  const defaults = await loadDefaultsForPath(filePath, root);
385
- const asset = applyDefaults(parsed.asset, defaults);
530
+ const withDefaults = applyDefaults(parsed.asset, defaults);
531
+ const resolved = await resolveResponseSchemaRef(withDefaults, filePath);
532
+ const asset = resolved.response?.schema !== void 0 && !resolved.response?.schema_source ? {
533
+ ...resolved,
534
+ response: {
535
+ ...resolved.response,
536
+ schema_source: { mode: "inline" }
537
+ }
538
+ } : resolved;
386
539
  return {
387
540
  ...parsed,
388
541
  asset
@@ -394,8 +547,8 @@ async function loadDefaultsForPath(filePath, defaultsRoot) {
394
547
  for (const dir of directories) {
395
548
  const defaultsPath = join(dir, DEFAULTS_FILE_NAME);
396
549
  try {
397
- const defaultsContent = await readFile(defaultsPath, "utf-8");
398
- const defaults = parseDefaults(defaultsContent);
550
+ const defaultsContent = await readFile2(defaultsPath, "utf-8");
551
+ const defaults = normalizeDefaultIncludes(parseDefaults(defaultsContent), defaultsPath, filePath);
399
552
  merged = mergeDefaults(merged, defaults);
400
553
  } catch (error) {
401
554
  if (error.code !== "ENOENT") {
@@ -407,14 +560,14 @@ async function loadDefaultsForPath(filePath, defaultsRoot) {
407
560
  }
408
561
  function getDirectoriesToCheck(filePath, defaultsRoot) {
409
562
  const dirs = [];
410
- let current = resolve(dirname(filePath));
411
- const boundary = defaultsRoot ? resolve(defaultsRoot) : void 0;
563
+ let current = resolve2(dirname2(filePath));
564
+ const boundary = defaultsRoot ? resolve2(defaultsRoot) : void 0;
412
565
  while (true) {
413
566
  dirs.unshift(current);
414
- if (boundary && current === boundary || current === dirname(current)) {
567
+ if (boundary && current === boundary || current === dirname2(current)) {
415
568
  break;
416
569
  }
417
- current = dirname(current);
570
+ current = dirname2(current);
418
571
  }
419
572
  return dirs;
420
573
  }
@@ -432,7 +585,19 @@ function mergeDefaults(base, local) {
432
585
  return {
433
586
  provider: local.provider ?? base.provider,
434
587
  model: local.model ?? base.model,
588
+ fallback_models: local.fallback_models ?? base.fallback_models,
589
+ reasoning: mergeRecordBlock(base.reasoning, local.reasoning),
590
+ sampling: mergeRecordBlock(base.sampling, local.sampling),
591
+ response: mergeRecordBlock(base.response, local.response),
435
592
  cache: mergeCache(base.cache, local.cache),
593
+ raw: mergeRaw(base.raw, local.raw),
594
+ tools: local.tools ?? base.tools,
595
+ provider_options: mergeProviderOptions(base.provider_options, local.provider_options),
596
+ mcp: mergeMcp(base.mcp, local.mcp),
597
+ context: mergeContext(base.context, local.context),
598
+ includes: local.includes ?? base.includes,
599
+ environments: mergeOverrideMap(base.environments, local.environments),
600
+ tiers: mergeOverrideMap(base.tiers, local.tiers),
436
601
  metadata: {
437
602
  ...base.metadata ?? {},
438
603
  ...local.metadata ?? {}
@@ -445,11 +610,20 @@ function mergeDefaults(base, local) {
445
610
  }
446
611
  function applyDefaults(asset, defaults) {
447
612
  const cache = mergeCache(defaults.cache, asset.cache);
613
+ const raw = mergeRaw(defaults.raw, asset.raw);
614
+ const providerOptions = mergeProviderOptions(defaults.provider_options, asset.provider_options);
615
+ const reasoning = mergeRecordBlock(defaults.reasoning, asset.reasoning);
616
+ const sampling = mergeRecordBlock(defaults.sampling, asset.sampling);
617
+ const response = mergeRecordBlock(defaults.response, asset.response);
618
+ const mcp = mergeMcp(defaults.mcp, asset.mcp);
619
+ const context = mergeContext(defaults.context, asset.context);
620
+ const environments = mergeOverrideMap(defaults.environments, asset.environments);
621
+ const tiers = mergeOverrideMap(defaults.tiers, asset.tiers);
448
622
  const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
449
623
  const hasDefaultSystem = !!defaults.sections?.system_instructions;
450
- const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0;
451
- const hasDefaultCache = cache !== void 0;
452
- if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultCache) {
624
+ const hasDefaultScalars = defaults.provider !== void 0 || defaults.model !== void 0 || defaults.fallback_models !== void 0 || defaults.tools !== void 0 || defaults.includes !== void 0;
625
+ const hasDefaultObjects = cache !== void 0 || raw !== void 0 || providerOptions !== void 0 || reasoning !== void 0 || sampling !== void 0 || response !== void 0 || mcp !== void 0 || context !== void 0 || environments !== void 0 || tiers !== void 0;
626
+ if (!hasDefaultMetadata && !hasDefaultSystem && !hasDefaultScalars && !hasDefaultObjects) {
453
627
  return asset;
454
628
  }
455
629
  const mergedMetadata = {
@@ -463,11 +637,52 @@ function applyDefaults(asset, defaults) {
463
637
  ...asset,
464
638
  provider: asset.provider ?? defaults.provider,
465
639
  model: asset.model ?? defaults.model,
640
+ fallback_models: asset.fallback_models ?? defaults.fallback_models,
641
+ reasoning,
642
+ sampling,
643
+ response,
466
644
  cache,
645
+ raw,
646
+ tools: asset.tools ?? defaults.tools,
647
+ provider_options: providerOptions,
648
+ mcp,
649
+ context,
650
+ includes: asset.includes ?? defaults.includes,
651
+ environments,
652
+ tiers,
467
653
  metadata,
468
654
  sections
469
655
  };
470
656
  }
657
+ function normalizeDefaultIncludes(defaults, defaultsPath, filePath) {
658
+ if (!defaults.includes) return defaults;
659
+ const promptDir = dirname2(filePath);
660
+ const defaultsDir = dirname2(defaultsPath);
661
+ const includes = defaults.includes.map((includePath) => {
662
+ const relativePath = relative(promptDir, resolve2(defaultsDir, includePath)).replace(/\\/g, "/");
663
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
664
+ });
665
+ return {
666
+ ...defaults,
667
+ includes
668
+ };
669
+ }
670
+ function mergeRecordBlock(base, local) {
671
+ if (base === void 0) return local;
672
+ if (local === void 0) return base;
673
+ return { ...base, ...local };
674
+ }
675
+ function mergeMcp(base, local) {
676
+ return mergeRecordBlock(base, local);
677
+ }
678
+ function mergeContext(base, local) {
679
+ const merged = {
680
+ ...base ?? {},
681
+ ...local ?? {},
682
+ history: mergeRecordBlock(base?.history, local?.history)
683
+ };
684
+ return Object.keys(merged).length > 0 ? merged : void 0;
685
+ }
471
686
  function mergeCache(base, local) {
472
687
  const merged = {
473
688
  ...base ?? {},
@@ -495,6 +710,72 @@ function mergeCache(base, local) {
495
710
  if (merged.google && Object.keys(merged.google).length === 0) delete merged.google;
496
711
  return Object.keys(merged).length > 0 ? merged : void 0;
497
712
  }
713
+ function mergeRaw(base, local) {
714
+ const merged = {
715
+ ...base ?? {},
716
+ ...local ?? {},
717
+ openai: mergeRecordBlock(base?.openai, local?.openai),
718
+ "openai-responses": mergeRecordBlock(base?.["openai-responses"], local?.["openai-responses"]),
719
+ openai_responses: mergeRecordBlock(base?.openai_responses, local?.openai_responses),
720
+ anthropic: mergeRecordBlock(base?.anthropic, local?.anthropic),
721
+ gemini: mergeRecordBlock(base?.gemini, local?.gemini),
722
+ google: mergeRecordBlock(base?.google, local?.google),
723
+ openrouter: mergeRecordBlock(base?.openrouter, local?.openrouter),
724
+ llmasaservice: mergeRecordBlock(base?.llmasaservice, local?.llmasaservice)
725
+ };
726
+ removeEmptyProviderBlocks(merged);
727
+ return Object.keys(merged).length > 0 ? merged : void 0;
728
+ }
729
+ function mergeProviderOptions(base, local) {
730
+ const merged = {
731
+ ...base ?? {},
732
+ ...local ?? {},
733
+ anthropic: mergeRecordBlock(base?.anthropic, local?.anthropic),
734
+ gemini: mergeRecordBlock(base?.gemini, local?.gemini),
735
+ openrouter: mergeRecordBlock(base?.openrouter, local?.openrouter),
736
+ llmasaservice: mergeRecordBlock(base?.llmasaservice, local?.llmasaservice)
737
+ };
738
+ removeEmptyProviderBlocks(merged);
739
+ return Object.keys(merged).length > 0 ? merged : void 0;
740
+ }
741
+ function mergeOverrideMap(base, local) {
742
+ const merged = { ...base ?? {} };
743
+ for (const [name, localOverride] of Object.entries(local ?? {})) {
744
+ merged[name] = mergeOverrideConfig(merged[name], localOverride);
745
+ }
746
+ return Object.keys(merged).length > 0 ? merged : void 0;
747
+ }
748
+ function mergeOverrideConfig(base, local) {
749
+ const merged = {
750
+ ...base ?? {},
751
+ ...local
752
+ };
753
+ const reasoning = mergeRecordBlock(base?.reasoning, local.reasoning);
754
+ const sampling = mergeRecordBlock(base?.sampling, local.sampling);
755
+ const response = mergeRecordBlock(base?.response, local.response);
756
+ const cache = mergeCache(base?.cache, local.cache);
757
+ const raw = mergeRaw(base?.raw, local.raw);
758
+ const providerOptions = mergeProviderOptions(base?.provider_options, local.provider_options);
759
+ if (reasoning !== void 0) merged.reasoning = reasoning;
760
+ if (sampling !== void 0) merged.sampling = sampling;
761
+ if (response !== void 0) merged.response = response;
762
+ if (cache !== void 0) merged.cache = cache;
763
+ if (raw !== void 0) merged.raw = raw;
764
+ if (providerOptions !== void 0) merged.provider_options = providerOptions;
765
+ return merged;
766
+ }
767
+ function removeEmptyProviderBlocks(value) {
768
+ for (const key of Object.keys(value)) {
769
+ const block = value[key];
770
+ if (block === void 0) {
771
+ delete value[key];
772
+ continue;
773
+ }
774
+ if (block !== null && typeof block === "object" && !Array.isArray(block) && Object.keys(block).length === 0) {
775
+ delete value[key];
776
+ }
777
+ }
778
+ }
498
779
 
499
780
  // src/renderer/interpolate.ts
500
781
  var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
@@ -502,12 +783,13 @@ var ESCAPED_OPEN = /\\\{\\\{/g;
502
783
  var ESCAPE_PLACEHOLDER = "\0ESCAPED_OPEN\0";
503
784
  function interpolate(template, variables, options = {}) {
504
785
  const { strict = false } = options;
786
+ const optionalVariables = new Set(options.optionalVariables ?? []);
505
787
  let result = template.replace(ESCAPED_OPEN, ESCAPE_PLACEHOLDER);
506
788
  result = result.replace(VARIABLE_RE, (match, name) => {
507
789
  if (name in variables) {
508
790
  return variables[name];
509
791
  }
510
- if (strict) {
792
+ if (strict && !optionalVariables.has(name)) {
511
793
  throw new Error(`Missing required variable: "${name}"`);
512
794
  }
513
795
  return match;
@@ -525,59 +807,19 @@ function extractVariables(template) {
525
807
  return [...vars];
526
808
  }
527
809
 
528
- // src/composition/resolve-includes.ts
529
- import { readFile as readFile2 } from "fs/promises";
530
- import { resolve as resolve2, dirname as dirname2 } from "path";
531
- async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
532
- if (!asset.includes || asset.includes.length === 0) {
533
- return asset;
534
- }
535
- const baseDir = dirname2(basePath);
536
- const resolvedPath = resolve2(basePath);
537
- if (visited.has(resolvedPath)) {
538
- throw new Error(`Circular include detected: ${resolvedPath}`);
539
- }
540
- visited.add(resolvedPath);
541
- let mergedSystemInstructions = "";
542
- for (const includePath of asset.includes) {
543
- const fullPath = resolve2(baseDir, includePath);
544
- if (visited.has(fullPath)) {
545
- throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
546
- }
547
- const content = await readFile2(fullPath, "utf-8");
548
- const { asset: includedAsset } = parsePrompt(content, fullPath);
549
- const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
550
- if (resolved.sections?.system_instructions) {
551
- mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
552
- }
553
- }
554
- const localSystem = asset.sections?.system_instructions ?? "";
555
- const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
556
- return {
557
- ...asset,
558
- sections: {
559
- ...asset.sections,
560
- system_instructions: combinedSystem
561
- },
562
- // Drop includes from the resolved asset — they've been inlined
563
- includes: void 0
564
- };
565
- }
566
-
567
810
  // src/context.ts
568
811
  var textEncoder = new TextEncoder();
569
812
  function getContextInputs(asset) {
570
813
  return (asset.context?.inputs ?? []).map(normalizeContextInput);
571
814
  }
572
- function getContextInputNames(asset) {
573
- return getContextInputs(asset).map((input) => input.name);
574
- }
575
815
  function normalizeContextInput(input) {
576
816
  if (typeof input === "string") {
577
817
  return { name: input };
578
818
  }
579
819
  return {
580
820
  name: input.name,
821
+ optional: input.optional,
822
+ warnings: input.warnings,
581
823
  max_size: input.max_size,
582
824
  trim: input.trim,
583
825
  allow_regex: normalizeContextRegex(input.allow_regex),
@@ -586,6 +828,9 @@ function normalizeContextInput(input) {
586
828
  reject_secrets: normalizeBuiltInValidator(input.reject_secrets)
587
829
  };
588
830
  }
831
+ function areContextInputWarningsEnabled(input) {
832
+ return input.warnings !== false;
833
+ }
589
834
  function normalizeContextRegex(value) {
590
835
  if (value === void 0) {
591
836
  return void 0;
@@ -672,6 +917,45 @@ function compileContextRegex(regex, details) {
672
917
  }
673
918
  }
674
919
 
920
+ // src/composition/resolve-includes.ts
921
+ import { readFile as readFile3 } from "fs/promises";
922
+ import { resolve as resolve3, dirname as dirname3 } from "path";
923
+ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
924
+ if (!asset.includes || asset.includes.length === 0) {
925
+ return asset;
926
+ }
927
+ const baseDir = dirname3(basePath);
928
+ const resolvedPath = resolve3(basePath);
929
+ if (visited.has(resolvedPath)) {
930
+ throw new Error(`Circular include detected: ${resolvedPath}`);
931
+ }
932
+ visited.add(resolvedPath);
933
+ let mergedSystemInstructions = "";
934
+ for (const includePath of asset.includes) {
935
+ const fullPath = resolve3(baseDir, includePath);
936
+ if (visited.has(fullPath)) {
937
+ throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
938
+ }
939
+ const content = await readFile3(fullPath, "utf-8");
940
+ const { asset: includedAsset } = parsePrompt(content, fullPath);
941
+ const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
942
+ if (resolved.sections?.system_instructions) {
943
+ mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
944
+ }
945
+ }
946
+ const localSystem = asset.sections?.system_instructions ?? "";
947
+ const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
948
+ return {
949
+ ...asset,
950
+ sections: {
951
+ ...asset.sections,
952
+ system_instructions: combinedSystem
953
+ },
954
+ // Drop includes from the resolved asset — they've been inlined
955
+ includes: void 0
956
+ };
957
+ }
958
+
675
959
  // src/validation/levenshtein.ts
676
960
  function levenshtein(a, b) {
677
961
  const m = a.length;
@@ -716,6 +1000,18 @@ var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
716
1000
  "provider_options",
717
1001
  "raw"
718
1002
  ]);
1003
+ function collectSchemaKeywords(value, acc) {
1004
+ if (!value || typeof value !== "object") return;
1005
+ if (Array.isArray(value)) {
1006
+ for (const item of value) collectSchemaKeywords(item, acc);
1007
+ return;
1008
+ }
1009
+ const record = value;
1010
+ for (const [key, child] of Object.entries(record)) {
1011
+ acc.add(key);
1012
+ collectSchemaKeywords(child, acc);
1013
+ }
1014
+ }
719
1015
  var RISKY_UNBOUNDED_INPUT_NAMES = [
720
1016
  "message",
721
1017
  "prompt",
@@ -767,7 +1063,9 @@ function validateAsset(asset, frontMatterKeys, filePath) {
767
1063
  }
768
1064
  }
769
1065
  }
770
- const declaredInputs = new Set(getContextInputNames(asset));
1066
+ const contextInputs = getContextInputs(asset);
1067
+ const declaredInputs = new Set(contextInputs.map((input) => input.name));
1068
+ const declaredInputsByName = new Map(contextInputs.map((input) => [input.name, input]));
771
1069
  const usedVars = /* @__PURE__ */ new Set();
772
1070
  if (asset.sections?.system_instructions) {
773
1071
  for (const v of extractVariables(asset.sections.system_instructions)) {
@@ -789,7 +1087,8 @@ function validateAsset(asset, frontMatterKeys, filePath) {
789
1087
  }
790
1088
  }
791
1089
  for (const v of declaredInputs) {
792
- if (!usedVars.has(v)) {
1090
+ const input = declaredInputsByName.get(v);
1091
+ if (!usedVars.has(v) && input?.optional !== true && areContextInputWarningsEnabled(input ?? {})) {
793
1092
  warnings.push({
794
1093
  code: "POK012",
795
1094
  message: `Variable "${v}" is declared in context.inputs but never used`,
@@ -805,9 +1104,10 @@ function validateAsset(asset, frontMatterKeys, filePath) {
805
1104
  suggestion: "Declare context.inputs to enable input policy validation."
806
1105
  });
807
1106
  }
808
- for (const input of getContextInputs(asset)) {
1107
+ for (const input of contextInputs) {
809
1108
  const lowerName = input.name.toLowerCase();
810
- if (input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
1109
+ const inputWarningsEnabled = areContextInputWarningsEnabled(input);
1110
+ if (inputWarningsEnabled && input.max_size === void 0 && RISKY_UNBOUNDED_INPUT_NAMES.some((needle) => lowerName.includes(needle))) {
811
1111
  warnings.push({
812
1112
  code: "POK040",
813
1113
  message: `Context input "${input.name}" has no max_size and appears unbounded.`,
@@ -815,7 +1115,7 @@ function validateAsset(asset, frontMatterKeys, filePath) {
815
1115
  suggestion: "Add max_size to constrain prompt payload growth."
816
1116
  });
817
1117
  }
818
- if (input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
1118
+ if (inputWarningsEnabled && input.optional !== true && input.allow_regex === void 0 && input.deny_regex === void 0 && input.non_empty === void 0 && input.reject_secrets === void 0) {
819
1119
  warnings.push({
820
1120
  code: "POK041",
821
1121
  message: `Context input "${input.name}" has no input hardening validators.`,
@@ -823,7 +1123,7 @@ function validateAsset(asset, frontMatterKeys, filePath) {
823
1123
  suggestion: "Consider non_empty/reject_secrets and allow/deny regex validators."
824
1124
  });
825
1125
  }
826
- if (input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
1126
+ if (inputWarningsEnabled && input.trim !== void 0 && input.trim !== false && input.max_size === void 0) {
827
1127
  warnings.push({
828
1128
  code: "POK014",
829
1129
  message: `Context input "${input.name}" sets trim but has no max_size; trim-to-budget will be skipped.`,
@@ -850,6 +1150,30 @@ function validateAsset(asset, frontMatterKeys, filePath) {
850
1150
  }
851
1151
  }
852
1152
  }
1153
+ if (asset.response?.schema && asset.provider) {
1154
+ const keywords = /* @__PURE__ */ new Set();
1155
+ collectSchemaKeywords(asset.response.schema, keywords);
1156
+ const openAIUnsupported = ["patternProperties", "unevaluatedProperties", "$dynamicRef"];
1157
+ const geminiUnsupported = ["patternProperties", "$dynamicRef", "dependentSchemas"];
1158
+ const anthropicCaution = ["if", "then", "else"];
1159
+ const checks = [
1160
+ { provider: ["openai", "openai-responses", "openrouter", "llmasaservice"], unsupported: openAIUnsupported, code: "POK052" },
1161
+ { provider: ["gemini", "google"], unsupported: geminiUnsupported, code: "POK053" },
1162
+ { provider: ["anthropic"], unsupported: anthropicCaution, code: "POK054" }
1163
+ ];
1164
+ for (const check of checks) {
1165
+ if (!check.provider.includes(asset.provider)) continue;
1166
+ const hits = check.unsupported.filter((k) => keywords.has(k));
1167
+ if (hits.length > 0) {
1168
+ warnings.push({
1169
+ code: check.code,
1170
+ message: `response.schema includes provider-sensitive JSON Schema keywords for "${asset.provider}": ${hits.join(", ")}`,
1171
+ filePath,
1172
+ suggestion: "Validate schema compatibility with your provider docs or simplify the schema dialect."
1173
+ });
1174
+ }
1175
+ }
1176
+ }
853
1177
  if (asset.provider) {
854
1178
  let providerCache;
855
1179
  let cacheSuggestionField;
@@ -976,9 +1300,9 @@ function isRecord(value) {
976
1300
  }
977
1301
 
978
1302
  // src/prompt-resolution.ts
979
- import { readFile as readFile3 } from "fs/promises";
1303
+ import { readFile as readFile4 } from "fs/promises";
980
1304
  import { existsSync, statSync as statSync2 } from "fs";
981
- import { resolve as resolve3 } from "path";
1305
+ import { resolve as resolve4 } from "path";
982
1306
 
983
1307
  // src/cache.ts
984
1308
  import { statSync } from "fs";
@@ -1058,23 +1382,24 @@ function mergeOverride(base, override) {
1058
1382
  result.cache = {
1059
1383
  ...result.cache,
1060
1384
  ...override.cache,
1061
- openai: mergeRecordBlock(result.cache?.openai, override.cache.openai),
1062
- anthropic: mergeRecordBlock(result.cache?.anthropic, override.cache.anthropic),
1063
- gemini: mergeRecordBlock(result.cache?.gemini, override.cache.gemini),
1064
- google: mergeRecordBlock(result.cache?.google, override.cache.google)
1385
+ openai: mergeRecordBlock2(result.cache?.openai, override.cache.openai),
1386
+ anthropic: mergeRecordBlock2(result.cache?.anthropic, override.cache.anthropic),
1387
+ gemini: mergeRecordBlock2(result.cache?.gemini, override.cache.gemini),
1388
+ google: mergeRecordBlock2(result.cache?.google, override.cache.google)
1065
1389
  };
1066
1390
  }
1067
1391
  if (override.raw !== void 0) {
1068
1392
  result.raw = {
1069
1393
  ...result.raw,
1070
1394
  ...override.raw,
1071
- openai: mergeRecordBlock(result.raw?.openai, override.raw.openai),
1072
- "openai-responses": mergeRecordBlock(result.raw?.["openai-responses"], override.raw["openai-responses"]),
1073
- openai_responses: mergeRecordBlock(result.raw?.openai_responses, override.raw.openai_responses),
1074
- anthropic: mergeRecordBlock(result.raw?.anthropic, override.raw.anthropic),
1075
- gemini: mergeRecordBlock(result.raw?.gemini, override.raw.gemini),
1076
- google: mergeRecordBlock(result.raw?.google, override.raw.google),
1077
- openrouter: mergeRecordBlock(result.raw?.openrouter, override.raw.openrouter)
1395
+ openai: mergeRecordBlock2(result.raw?.openai, override.raw.openai),
1396
+ "openai-responses": mergeRecordBlock2(result.raw?.["openai-responses"], override.raw["openai-responses"]),
1397
+ openai_responses: mergeRecordBlock2(result.raw?.openai_responses, override.raw.openai_responses),
1398
+ anthropic: mergeRecordBlock2(result.raw?.anthropic, override.raw.anthropic),
1399
+ gemini: mergeRecordBlock2(result.raw?.gemini, override.raw.gemini),
1400
+ google: mergeRecordBlock2(result.raw?.google, override.raw.google),
1401
+ openrouter: mergeRecordBlock2(result.raw?.openrouter, override.raw.openrouter),
1402
+ llmasaservice: mergeRecordBlock2(result.raw?.llmasaservice, override.raw.llmasaservice)
1078
1403
  };
1079
1404
  }
1080
1405
  if (override.provider_options !== void 0) {
@@ -1092,12 +1417,16 @@ function mergeOverride(base, override) {
1092
1417
  openrouter: {
1093
1418
  ...result.provider_options?.openrouter,
1094
1419
  ...override.provider_options.openrouter
1420
+ },
1421
+ llmasaservice: {
1422
+ ...result.provider_options?.llmasaservice,
1423
+ ...override.provider_options.llmasaservice
1095
1424
  }
1096
1425
  };
1097
1426
  }
1098
1427
  return result;
1099
1428
  }
1100
- function mergeRecordBlock(base, override) {
1429
+ function mergeRecordBlock2(base, override) {
1101
1430
  if (override === void 0) return base;
1102
1431
  return { ...base, ...override };
1103
1432
  }
@@ -1113,9 +1442,9 @@ var sharedPromptCache = new PromptCache();
1113
1442
 
1114
1443
  // src/cli/commands/defaults-root.ts
1115
1444
  import { existsSync as existsSync2 } from "fs";
1116
- import { dirname as dirname3, join as join2, resolve as resolve4 } from "path";
1445
+ import { dirname as dirname4, join as join2, resolve as resolve5 } from "path";
1117
1446
  function findDefaultsRoot(filePath) {
1118
- let current = resolve4(dirname3(filePath));
1447
+ let current = resolve5(dirname4(filePath));
1119
1448
  let root = current;
1120
1449
  let foundDefaults = false;
1121
1450
  while (true) {
@@ -1125,7 +1454,7 @@ function findDefaultsRoot(filePath) {
1125
1454
  } else if (foundDefaults) {
1126
1455
  return root;
1127
1456
  }
1128
- const parent = dirname3(current);
1457
+ const parent = dirname4(current);
1129
1458
  if (parent === current) {
1130
1459
  return root;
1131
1460
  }
@@ -1220,7 +1549,7 @@ function getFlag(args, ...flags) {
1220
1549
  async function collectPromptFiles(target) {
1221
1550
  const targetStats = await stat(target);
1222
1551
  if (targetStats.isFile()) {
1223
- if (extname(target) !== ".md" || target.endsWith("defaults.md") || target.endsWith(".test.md")) {
1552
+ if (extname2(target) !== ".md" || target.endsWith("defaults.md") || target.endsWith(".test.md")) {
1224
1553
  return [];
1225
1554
  }
1226
1555
  return [target];
@@ -1228,7 +1557,7 @@ async function collectPromptFiles(target) {
1228
1557
  const results = [];
1229
1558
  const entries = await readdir(target, { withFileTypes: true, recursive: true });
1230
1559
  for (const entry of entries) {
1231
- if (entry.isFile() && extname(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
1560
+ if (entry.isFile() && extname2(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
1232
1561
  results.push(join3(entry.parentPath ?? target, entry.name));
1233
1562
  }
1234
1563
  }
@@ -1237,7 +1566,7 @@ async function collectPromptFiles(target) {
1237
1566
 
1238
1567
  // src/cli/commands/compile.ts
1239
1568
  import { readdir as readdir2, writeFile, mkdir, rm } from "fs/promises";
1240
- import { join as join4, extname as extname2, relative, dirname as dirname5 } from "path";
1569
+ import { join as join4, extname as extname3, relative as relative2, dirname as dirname6 } from "path";
1241
1570
  var HELP2 = `
1242
1571
  promptopskit compile [sourceDir] [outputDir] [options]
1243
1572
 
@@ -1277,7 +1606,7 @@ async function compile(args) {
1277
1606
  let compiled = 0;
1278
1607
  let errors = 0;
1279
1608
  for (const file of files) {
1280
- const rel = relative(sourceDir, file).replace(/\.md$/, "");
1609
+ const rel = relative2(sourceDir, file).replace(/\.md$/, "");
1281
1610
  const outExt = format === "esm" ? ".mjs" : ".json";
1282
1611
  const outPath = join4(outputDir, rel + outExt);
1283
1612
  try {
@@ -1290,7 +1619,7 @@ async function compile(args) {
1290
1619
  if (dryRun) {
1291
1620
  console.log(` Would create: ${outPath}`);
1292
1621
  } else {
1293
- await mkdir(dirname5(outPath), { recursive: true });
1622
+ await mkdir(dirname6(outPath), { recursive: true });
1294
1623
  if (format === "esm") {
1295
1624
  const esmContent = `export default ${JSON.stringify(asset, null, 2)};
1296
1625
  `;
@@ -1346,7 +1675,7 @@ async function collectPromptFiles2(dir) {
1346
1675
  const results = [];
1347
1676
  const entries = await readdir2(dir, { withFileTypes: true, recursive: true });
1348
1677
  for (const entry of entries) {
1349
- if (entry.isFile() && extname2(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
1678
+ if (entry.isFile() && extname3(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
1350
1679
  results.push(join4(entry.parentPath ?? dir, entry.name));
1351
1680
  }
1352
1681
  }
@@ -1354,7 +1683,7 @@ async function collectPromptFiles2(dir) {
1354
1683
  }
1355
1684
 
1356
1685
  // src/cli/commands/render.ts
1357
- import { readFile as readFile4 } from "fs/promises";
1686
+ import { readFile as readFile5 } from "fs/promises";
1358
1687
  import { existsSync as existsSync3 } from "fs";
1359
1688
  var HELP3 = `
1360
1689
  promptopskit render <file> [options]
@@ -1385,13 +1714,13 @@ async function render(args) {
1385
1714
  const jsonOutput = args.includes("--json");
1386
1715
  let variables = {};
1387
1716
  if (varsFile) {
1388
- const varsContent = await readFile4(varsFile, "utf-8");
1717
+ const varsContent = await readFile5(varsFile, "utf-8");
1389
1718
  variables = JSON.parse(varsContent);
1390
1719
  } else {
1391
1720
  const sidecarPath = file.replace(/\.md$/, ".test.yaml");
1392
1721
  if (existsSync3(sidecarPath)) {
1393
1722
  const { default: yaml } = await import("gray-matter");
1394
- const sidecarContent = await readFile4(sidecarPath, "utf-8");
1723
+ const sidecarContent = await readFile5(sidecarPath, "utf-8");
1395
1724
  const parsed2 = yaml(`---
1396
1725
  ${sidecarContent}---
1397
1726
  `);
@@ -1490,7 +1819,7 @@ async function inspect(args) {
1490
1819
 
1491
1820
  // src/cli/commands/init.ts
1492
1821
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1493
- import { join as join5, dirname as dirname6, relative as relative2 } from "path";
1822
+ import { join as join5, dirname as dirname7, relative as relative3 } from "path";
1494
1823
  import { existsSync as existsSync4, readFileSync } from "fs";
1495
1824
  var HELP5 = `
1496
1825
  promptopskit init [dir]
@@ -1641,8 +1970,8 @@ async function init(args) {
1641
1970
  return;
1642
1971
  }
1643
1972
  const dir = args.find((a) => !a.startsWith("--")) ?? "./prompts";
1644
- const testFilePath = join5(dirname6(dir), "tests", "hello.prompt.test.mjs");
1645
- const promptsDirFromTest = relative2(dirname6(testFilePath), dir) || ".";
1973
+ const testFilePath = join5(dirname7(dir), "tests", "hello.prompt.test.mjs");
1974
+ const promptsDirFromTest = relative3(dirname7(testFilePath), dir) || ".";
1646
1975
  const helloPromptTest = createHelloPromptTest(promptsDirFromTest);
1647
1976
  const files = [
1648
1977
  { path: join5(dir, "defaults.md"), content: DEFAULTS },
@@ -1660,7 +1989,7 @@ async function init(args) {
1660
1989
  skipped++;
1661
1990
  continue;
1662
1991
  }
1663
- await mkdir2(dirname6(file.path), { recursive: true });
1992
+ await mkdir2(dirname7(file.path), { recursive: true });
1664
1993
  await writeFile2(file.path, file.content, "utf-8");
1665
1994
  console.log(` \u2713 ${file.path}`);
1666
1995
  created++;
@@ -1728,8 +2057,8 @@ test('hello prompt can return a deterministic response without calling a model',
1728
2057
  }
1729
2058
 
1730
2059
  // src/cli/commands/skill.ts
1731
- import { writeFile as writeFile3, readFile as readFile5, mkdir as mkdir3 } from "fs/promises";
1732
- import { dirname as dirname7 } from "path";
2060
+ import { writeFile as writeFile3, readFile as readFile6, mkdir as mkdir3 } from "fs/promises";
2061
+ import { dirname as dirname8 } from "path";
1733
2062
  import { existsSync as existsSync5 } from "fs";
1734
2063
  var MARKER_START = "<!-- promptopskit:start -->";
1735
2064
  var MARKER_END = "<!-- promptopskit:end -->";
@@ -1817,7 +2146,7 @@ async function skill(args) {
1817
2146
  }
1818
2147
  const markedContent = config.wrap(wrapMarkers(STUB_CONTENT));
1819
2148
  if (existsSync5(filePath) && !force) {
1820
- const existing = await readFile5(filePath, "utf-8");
2149
+ const existing = await readFile6(filePath, "utf-8");
1821
2150
  const merged = mergeContent(existing, markedContent);
1822
2151
  if (merged === existing) {
1823
2152
  console.log(` skip ${filePath} (already up to date)`);
@@ -1828,7 +2157,7 @@ async function skill(args) {
1828
2157
  written++;
1829
2158
  continue;
1830
2159
  }
1831
- await mkdir3(dirname7(filePath), { recursive: true });
2160
+ await mkdir3(dirname8(filePath), { recursive: true });
1832
2161
  await writeFile3(filePath, markedContent, "utf-8");
1833
2162
  console.log(` \u2713 ${filePath}`);
1834
2163
  written++;
@@ -1841,12 +2170,12 @@ async function skill(args) {
1841
2170
  async function deployClaude(filePath, force) {
1842
2171
  const content = CLAUDE_LINE + "\n";
1843
2172
  if (!existsSync5(filePath) || force) {
1844
- await mkdir3(dirname7(filePath), { recursive: true });
2173
+ await mkdir3(dirname8(filePath), { recursive: true });
1845
2174
  await writeFile3(filePath, content, "utf-8");
1846
2175
  console.log(` \u2713 ${filePath}`);
1847
2176
  return 1;
1848
2177
  }
1849
- const existing = await readFile5(filePath, "utf-8");
2178
+ const existing = await readFile6(filePath, "utf-8");
1850
2179
  if (existing.split("\n").some((line) => line.trim() === CLAUDE_LINE)) {
1851
2180
  console.log(` skip ${filePath} (already up to date)`);
1852
2181
  return 0;