@promptscript/cli 1.0.0-alpha.9 → 1.0.0-rc.1

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 (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/index.js +710 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.0-rc.1](https://github.com/mrwogu/promptscript/compare/v1.0.0-alpha.10...v1.0.0-rc.1) (2026-02-01)
9
+
10
+
11
+ ### chore
12
+
13
+ * prepare rc release ([756f820](https://github.com/mrwogu/promptscript/commit/756f82096a2a511e6a88aa3b45a56c92c3fab68c))
14
+
15
+ ## [1.0.0-alpha.10](https://github.com/mrwogu/promptscript/compare/v1.0.0-alpha.9...v1.0.0-alpha.10) (2026-01-31)
16
+
17
+
18
+ ### chore
19
+
20
+ * prepare alpha release ([c6595b6](https://github.com/mrwogu/promptscript/commit/c6595b6fd639f93f0093c503e8468fbbbf057cf9))
21
+
8
22
  ## [1.0.0-alpha.9](https://github.com/mrwogu/promptscript/compare/v1.0.0-alpha.8...v1.0.0-alpha.9) (2026-01-30)
9
23
 
10
24
 
package/index.js CHANGED
@@ -171,6 +171,79 @@ var CircularDependencyError = class extends ResolveError {
171
171
  }
172
172
  };
173
173
 
174
+ // packages/core/src/errors/template.ts
175
+ var MissingParamError = class extends PSError {
176
+ /** Name of the missing parameter */
177
+ paramName;
178
+ /** Path of the template file */
179
+ templatePath;
180
+ constructor(paramName, templatePath, options) {
181
+ super(
182
+ `Missing required parameter '${paramName}' for template '${templatePath}'`,
183
+ "PS2010" /* MISSING_PARAM */,
184
+ options
185
+ );
186
+ this.name = "MissingParamError";
187
+ this.paramName = paramName;
188
+ this.templatePath = templatePath;
189
+ }
190
+ };
191
+ var UnknownParamError = class extends PSError {
192
+ /** Name of the unknown parameter */
193
+ paramName;
194
+ /** Path of the template file */
195
+ templatePath;
196
+ /** Available parameter names */
197
+ availableParams;
198
+ constructor(paramName, templatePath, availableParams, options) {
199
+ const suggestion = availableParams.length > 0 ? `. Available parameters: ${availableParams.join(", ")}` : ". This template has no parameters.";
200
+ super(
201
+ `Unknown parameter '${paramName}' for template '${templatePath}'${suggestion}`,
202
+ "PS2011" /* UNKNOWN_PARAM */,
203
+ options
204
+ );
205
+ this.name = "UnknownParamError";
206
+ this.paramName = paramName;
207
+ this.templatePath = templatePath;
208
+ this.availableParams = availableParams;
209
+ }
210
+ };
211
+ var ParamTypeMismatchError = class extends PSError {
212
+ /** Name of the parameter */
213
+ paramName;
214
+ /** Expected type */
215
+ expectedType;
216
+ /** Actual type */
217
+ actualType;
218
+ constructor(paramName, expectedType, actualType, options) {
219
+ super(
220
+ `Type mismatch for parameter '${paramName}': expected ${expectedType}, got ${actualType}`,
221
+ "PS2012" /* PARAM_TYPE_MISMATCH */,
222
+ options
223
+ );
224
+ this.name = "ParamTypeMismatchError";
225
+ this.paramName = paramName;
226
+ this.expectedType = expectedType;
227
+ this.actualType = actualType;
228
+ }
229
+ };
230
+ var UndefinedVariableError = class extends PSError {
231
+ /** Name of the undefined variable */
232
+ variableName;
233
+ /** File where the variable was used */
234
+ sourceFile;
235
+ constructor(variableName, sourceFile, options) {
236
+ super(
237
+ `Undefined template variable '{{${variableName}}}' in '${sourceFile}'`,
238
+ "PS2013" /* UNDEFINED_VARIABLE */,
239
+ options
240
+ );
241
+ this.name = "UndefinedVariableError";
242
+ this.variableName = variableName;
243
+ this.sourceFile = sourceFile;
244
+ }
245
+ };
246
+
174
247
  // packages/core/src/utils/merge.ts
175
248
  var DEFAULT_MERGE_OPTIONS = {
176
249
  arrayStrategy: "unique",
@@ -304,6 +377,219 @@ var noopLogger = {
304
377
  }
305
378
  };
306
379
 
380
+ // packages/core/src/template.ts
381
+ function paramTypeToString(paramType) {
382
+ switch (paramType.kind) {
383
+ case "string":
384
+ return "string";
385
+ case "number":
386
+ return "number";
387
+ case "boolean":
388
+ return "boolean";
389
+ case "enum":
390
+ return `enum(${paramType.options.map((o) => `"${o}"`).join(", ")})`;
391
+ }
392
+ }
393
+ function getValueType(value) {
394
+ if (value === null) return "null";
395
+ if (typeof value === "string") return "string";
396
+ if (typeof value === "number") return "number";
397
+ if (typeof value === "boolean") return "boolean";
398
+ if (Array.isArray(value)) return "array";
399
+ if (typeof value === "object" && "type" in value) {
400
+ const typed = value;
401
+ if (typed.type === "TextContent") return "string";
402
+ if (typed.type === "TemplateExpression") return "template";
403
+ }
404
+ return "object";
405
+ }
406
+ function validateParamType(paramName, value, expectedType, location) {
407
+ const actualType = getValueType(value);
408
+ const expected = paramTypeToString(expectedType);
409
+ switch (expectedType.kind) {
410
+ case "string":
411
+ if (actualType !== "string") {
412
+ throw new ParamTypeMismatchError(paramName, expected, actualType, { location });
413
+ }
414
+ break;
415
+ case "number":
416
+ if (actualType !== "number") {
417
+ throw new ParamTypeMismatchError(paramName, expected, actualType, { location });
418
+ }
419
+ break;
420
+ case "boolean":
421
+ if (actualType !== "boolean") {
422
+ throw new ParamTypeMismatchError(paramName, expected, actualType, { location });
423
+ }
424
+ break;
425
+ case "enum":
426
+ if (actualType !== "string" || !expectedType.options.includes(value)) {
427
+ throw new ParamTypeMismatchError(paramName, expected, String(value), { location });
428
+ }
429
+ break;
430
+ }
431
+ }
432
+ function bindParams(args, defs, templatePath, callLocation) {
433
+ const result = /* @__PURE__ */ new Map();
434
+ if (!defs || defs.length === 0) {
435
+ if (args && args.length > 0 && args[0] !== void 0) {
436
+ throw new UnknownParamError(args[0].name, templatePath, [], { location: callLocation });
437
+ }
438
+ return result;
439
+ }
440
+ const defMap = /* @__PURE__ */ new Map();
441
+ for (const def of defs) {
442
+ defMap.set(def.name, def);
443
+ }
444
+ const providedNames = /* @__PURE__ */ new Set();
445
+ if (args) {
446
+ for (const arg of args) {
447
+ const def = defMap.get(arg.name);
448
+ if (!def) {
449
+ throw new UnknownParamError(
450
+ arg.name,
451
+ templatePath,
452
+ defs.map((d) => d.name),
453
+ { location: arg.loc }
454
+ );
455
+ }
456
+ validateParamType(arg.name, arg.value, def.paramType, arg.loc);
457
+ result.set(arg.name, arg.value);
458
+ providedNames.add(arg.name);
459
+ }
460
+ }
461
+ for (const def of defs) {
462
+ if (!providedNames.has(def.name)) {
463
+ if (def.defaultValue !== void 0) {
464
+ result.set(def.name, def.defaultValue);
465
+ } else if (!def.optional) {
466
+ throw new MissingParamError(def.name, templatePath, { location: callLocation });
467
+ }
468
+ }
469
+ }
470
+ return result;
471
+ }
472
+ function valueToString(value) {
473
+ if (value === null) return "null";
474
+ if (typeof value === "string") return value;
475
+ if (typeof value === "number") return String(value);
476
+ if (typeof value === "boolean") return String(value);
477
+ if (Array.isArray(value)) return JSON.stringify(value);
478
+ if (typeof value === "object" && "type" in value) {
479
+ const typed = value;
480
+ if (typed.type === "TextContent") {
481
+ return value.value;
482
+ }
483
+ }
484
+ return JSON.stringify(value);
485
+ }
486
+ function interpolateText(text, ctx) {
487
+ const pattern = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
488
+ return text.replace(pattern, (_match, varName) => {
489
+ const value = ctx.params.get(varName);
490
+ if (value === void 0) {
491
+ throw new UndefinedVariableError(varName, ctx.sourceFile);
492
+ }
493
+ return valueToString(value);
494
+ });
495
+ }
496
+ function interpolateValue(value, ctx) {
497
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
498
+ return value;
499
+ }
500
+ if (Array.isArray(value)) {
501
+ return value.map((v) => interpolateValue(v, ctx));
502
+ }
503
+ if (typeof value === "object" && "type" in value) {
504
+ const typed = value;
505
+ if (typed.type === "TemplateExpression") {
506
+ const expr = value;
507
+ const resolved = ctx.params.get(expr.name);
508
+ if (resolved === void 0) {
509
+ throw new UndefinedVariableError(expr.name, ctx.sourceFile, { location: expr.loc });
510
+ }
511
+ return resolved;
512
+ }
513
+ if (typed.type === "TextContent") {
514
+ const text = value;
515
+ return {
516
+ ...text,
517
+ value: interpolateText(text.value, ctx)
518
+ };
519
+ }
520
+ if (typed.type === "TypeExpression") {
521
+ return value;
522
+ }
523
+ }
524
+ if (typeof value === "object") {
525
+ const result = {};
526
+ for (const [key, val] of Object.entries(value)) {
527
+ result[key] = interpolateValue(val, ctx);
528
+ }
529
+ return result;
530
+ }
531
+ return value;
532
+ }
533
+ function interpolateContent(content, ctx) {
534
+ switch (content.type) {
535
+ case "TextContent":
536
+ return {
537
+ ...content,
538
+ value: interpolateText(content.value, ctx)
539
+ };
540
+ case "ObjectContent":
541
+ return {
542
+ ...content,
543
+ properties: interpolateProperties(content.properties, ctx)
544
+ };
545
+ case "ArrayContent":
546
+ return {
547
+ ...content,
548
+ elements: content.elements.map((e) => interpolateValue(e, ctx))
549
+ };
550
+ case "MixedContent":
551
+ return {
552
+ ...content,
553
+ text: content.text ? { ...content.text, value: interpolateText(content.text.value, ctx) } : void 0,
554
+ properties: interpolateProperties(content.properties, ctx)
555
+ };
556
+ }
557
+ }
558
+ function interpolateProperties(properties, ctx) {
559
+ const result = {};
560
+ for (const [key, value] of Object.entries(properties)) {
561
+ result[key] = interpolateValue(value, ctx);
562
+ }
563
+ return result;
564
+ }
565
+ function interpolateBlock(block, ctx) {
566
+ return {
567
+ ...block,
568
+ content: interpolateContent(block.content, ctx)
569
+ };
570
+ }
571
+ function interpolateAST(ast, ctx) {
572
+ if (ctx.params.size === 0) {
573
+ return ast;
574
+ }
575
+ return {
576
+ ...ast,
577
+ // Interpolate meta fields (but not params definitions)
578
+ meta: ast.meta ? {
579
+ ...ast.meta,
580
+ fields: interpolateProperties(ast.meta.fields, ctx)
581
+ // Keep params as-is (they're definitions, not values to interpolate)
582
+ } : void 0,
583
+ // Interpolate blocks
584
+ blocks: ast.blocks.map((b) => interpolateBlock(b, ctx)),
585
+ // Interpolate extend blocks
586
+ extends: ast.extends.map((e) => ({
587
+ ...e,
588
+ content: interpolateContent(e.content, ctx)
589
+ }))
590
+ };
591
+ }
592
+
307
593
  // packages/cli/src/commands/init.ts
308
594
  import { fileURLToPath } from "url";
309
595
  import { dirname as dirname3 } from "path";
@@ -15215,6 +15501,29 @@ var Enum = createToken({
15215
15501
  pattern: /enum/,
15216
15502
  longer_alt: Identifier
15217
15503
  });
15504
+ var StringType = createToken({
15505
+ name: "StringType",
15506
+ pattern: /string/,
15507
+ longer_alt: Identifier
15508
+ });
15509
+ var NumberType = createToken({
15510
+ name: "NumberType",
15511
+ pattern: /number/,
15512
+ longer_alt: Identifier
15513
+ });
15514
+ var BooleanType = createToken({
15515
+ name: "BooleanType",
15516
+ pattern: /boolean/,
15517
+ longer_alt: Identifier
15518
+ });
15519
+ var TemplateOpen = createToken({
15520
+ name: "TemplateOpen",
15521
+ pattern: /\{\{/
15522
+ });
15523
+ var TemplateClose = createToken({
15524
+ name: "TemplateClose",
15525
+ pattern: /\}\}/
15526
+ });
15218
15527
  var At = createToken({
15219
15528
  name: "At",
15220
15529
  pattern: /@/
@@ -15288,6 +15597,9 @@ var allTokens = [
15288
15597
  // Paths (must be before DotDot to handle ../)
15289
15598
  PathReference,
15290
15599
  RelativePath,
15600
+ // Template expression tokens (multi-char, before single braces)
15601
+ TemplateOpen,
15602
+ TemplateClose,
15291
15603
  // Multi-char symbols before single-char
15292
15604
  DotDot,
15293
15605
  // Keywords before Identifier
@@ -15301,6 +15613,9 @@ var allTokens = [
15301
15613
  Null,
15302
15614
  Range,
15303
15615
  Enum,
15616
+ StringType,
15617
+ NumberType,
15618
+ BooleanType,
15304
15619
  // Single-char symbols
15305
15620
  At,
15306
15621
  LBrace,
@@ -15366,22 +15681,24 @@ var PromptScriptParser = class extends CstParser {
15366
15681
  });
15367
15682
  /**
15368
15683
  * inheritDecl
15369
- * : '@' 'inherit' pathRef
15684
+ * : '@' 'inherit' pathRef paramCallList?
15370
15685
  */
15371
15686
  inheritDecl = this.RULE("inheritDecl", () => {
15372
15687
  this.CONSUME(At);
15373
15688
  this.CONSUME(Inherit);
15374
15689
  this.SUBRULE(this.pathRef);
15690
+ this.OPTION(() => this.SUBRULE(this.paramCallList));
15375
15691
  });
15376
15692
  /**
15377
15693
  * useDecl
15378
- * : '@' 'use' pathRef ('as' Identifier)?
15694
+ * : '@' 'use' pathRef paramCallList? ('as' Identifier)?
15379
15695
  */
15380
15696
  useDecl = this.RULE("useDecl", () => {
15381
15697
  this.CONSUME(At);
15382
15698
  this.CONSUME(Use);
15383
15699
  this.SUBRULE(this.pathRef);
15384
- this.OPTION(() => {
15700
+ this.OPTION(() => this.SUBRULE(this.paramCallList));
15701
+ this.OPTION2(() => {
15385
15702
  this.CONSUME(As);
15386
15703
  this.CONSUME(Identifier);
15387
15704
  });
@@ -15432,10 +15749,18 @@ var PromptScriptParser = class extends CstParser {
15432
15749
  });
15433
15750
  /**
15434
15751
  * field
15435
- * : (Identifier | StringLiteral) '?'? ':' value ('=' value)?
15752
+ * : (Identifier | StringLiteral | StringType | NumberType | BooleanType) '?'? ':' value ('=' value)?
15753
+ *
15754
+ * Note: StringType/NumberType/BooleanType are also valid field keys (e.g., { string: "value" })
15436
15755
  */
15437
15756
  field = this.RULE("field", () => {
15438
- this.OR([{ ALT: () => this.CONSUME(Identifier) }, { ALT: () => this.CONSUME(StringLiteral) }]);
15757
+ this.OR([
15758
+ { ALT: () => this.CONSUME(Identifier) },
15759
+ { ALT: () => this.CONSUME(StringLiteral) },
15760
+ { ALT: () => this.CONSUME(StringType) },
15761
+ { ALT: () => this.CONSUME(NumberType) },
15762
+ { ALT: () => this.CONSUME(BooleanType) }
15763
+ ]);
15439
15764
  this.OPTION(() => this.CONSUME(Question));
15440
15765
  this.CONSUME(Colon);
15441
15766
  this.SUBRULE(this.value);
@@ -15447,7 +15772,10 @@ var PromptScriptParser = class extends CstParser {
15447
15772
  /**
15448
15773
  * value
15449
15774
  * : StringLiteral | NumberLiteral | True | False | Null
15450
- * | TextBlock | array | object | typeExpr | Identifier
15775
+ * | TextBlock | array | paramDefList | object | typeExpr | templateExpr | Identifier
15776
+ *
15777
+ * Note: paramDefList must come before object since both start with LBrace,
15778
+ * and paramDefList is more specific (contains paramType like 'string', 'number', 'boolean').
15451
15779
  */
15452
15780
  value = this.RULE("value", () => {
15453
15781
  this.OR([
@@ -15458,11 +15786,42 @@ var PromptScriptParser = class extends CstParser {
15458
15786
  { ALT: () => this.CONSUME(Null) },
15459
15787
  { ALT: () => this.CONSUME(TextBlock) },
15460
15788
  { ALT: () => this.SUBRULE(this.array) },
15789
+ { ALT: () => this.SUBRULE(this.paramDefList), GATE: () => this.isParamDefListAhead() },
15461
15790
  { ALT: () => this.SUBRULE(this.object) },
15462
15791
  { ALT: () => this.SUBRULE(this.typeExpr) },
15792
+ { ALT: () => this.SUBRULE(this.templateExpr) },
15463
15793
  { ALT: () => this.CONSUME(Identifier) }
15464
15794
  ]);
15465
15795
  });
15796
+ /**
15797
+ * Check if we're about to parse a paramDefList.
15798
+ * A paramDefList starts with '{' and contains paramDef entries like 'name: string'.
15799
+ */
15800
+ isParamDefListAhead() {
15801
+ const tokens = this.LA(1);
15802
+ if (!tokens || tokens.tokenType?.name !== "LBrace") return false;
15803
+ const second = this.LA(2);
15804
+ if (!second || second.tokenType?.name !== "Identifier") return false;
15805
+ const third = this.LA(3);
15806
+ if (!third) return false;
15807
+ if (third.tokenType?.name === "Colon") {
15808
+ const fourth = this.LA(4);
15809
+ if (!fourth) return false;
15810
+ return ["StringType", "NumberType", "BooleanType", "Enum"].includes(
15811
+ fourth.tokenType?.name ?? ""
15812
+ );
15813
+ }
15814
+ if (third.tokenType?.name === "Question") {
15815
+ const fourth = this.LA(4);
15816
+ if (!fourth || fourth.tokenType?.name !== "Colon") return false;
15817
+ const fifth = this.LA(5);
15818
+ if (!fifth) return false;
15819
+ return ["StringType", "NumberType", "BooleanType", "Enum"].includes(
15820
+ fifth.tokenType?.name ?? ""
15821
+ );
15822
+ }
15823
+ return false;
15824
+ }
15466
15825
  /**
15467
15826
  * array
15468
15827
  * : '[' (value (',' value?)*)? ']'
@@ -15547,14 +15906,90 @@ var PromptScriptParser = class extends CstParser {
15547
15906
  this.CONSUME2(Identifier);
15548
15907
  });
15549
15908
  });
15909
+ // ============================================================
15910
+ // Template Parameter Rules
15911
+ // ============================================================
15912
+ /**
15913
+ * paramCallList (for @inherit/@use calls)
15914
+ * : '(' (paramArg (',' paramArg)*)? ')'
15915
+ */
15916
+ paramCallList = this.RULE("paramCallList", () => {
15917
+ this.CONSUME(LParen);
15918
+ this.OPTION(() => {
15919
+ this.SUBRULE(this.paramArg);
15920
+ this.MANY(() => {
15921
+ this.CONSUME(Comma);
15922
+ this.SUBRULE2(this.paramArg);
15923
+ });
15924
+ });
15925
+ this.CONSUME(RParen);
15926
+ });
15927
+ /**
15928
+ * paramArg
15929
+ * : Identifier ':' value
15930
+ */
15931
+ paramArg = this.RULE("paramArg", () => {
15932
+ this.CONSUME(Identifier);
15933
+ this.CONSUME(Colon);
15934
+ this.SUBRULE(this.value);
15935
+ });
15936
+ /**
15937
+ * paramDefList (for @meta { params: {...} })
15938
+ * : '{' (paramDef ','?)* '}'
15939
+ */
15940
+ paramDefList = this.RULE("paramDefList", () => {
15941
+ this.CONSUME(LBrace);
15942
+ this.MANY(() => {
15943
+ this.SUBRULE(this.paramDef);
15944
+ this.OPTION(() => this.CONSUME(Comma));
15945
+ });
15946
+ this.CONSUME(RBrace);
15947
+ });
15948
+ /**
15949
+ * paramDef
15950
+ * : Identifier '?'? ':' paramType ('=' value)?
15951
+ */
15952
+ paramDef = this.RULE("paramDef", () => {
15953
+ this.CONSUME(Identifier);
15954
+ this.OPTION(() => this.CONSUME(Question));
15955
+ this.CONSUME(Colon);
15956
+ this.SUBRULE(this.paramType);
15957
+ this.OPTION2(() => {
15958
+ this.CONSUME(Equals);
15959
+ this.SUBRULE(this.value);
15960
+ });
15961
+ });
15962
+ /**
15963
+ * paramType
15964
+ * : 'string' | 'number' | 'boolean' | enumType
15965
+ */
15966
+ paramType = this.RULE("paramType", () => {
15967
+ this.OR([
15968
+ { ALT: () => this.CONSUME(StringType) },
15969
+ { ALT: () => this.CONSUME(NumberType) },
15970
+ { ALT: () => this.CONSUME(BooleanType) },
15971
+ { ALT: () => this.SUBRULE(this.enumType) }
15972
+ ]);
15973
+ });
15974
+ /**
15975
+ * templateExpr
15976
+ * : '{{' Identifier '}}'
15977
+ */
15978
+ templateExpr = this.RULE("templateExpr", () => {
15979
+ this.CONSUME(TemplateOpen);
15980
+ this.CONSUME(Identifier);
15981
+ this.CONSUME(TemplateClose);
15982
+ });
15550
15983
  };
15551
15984
  var parser = new PromptScriptParser();
15552
15985
 
15553
15986
  // packages/parser/src/grammar/visitor.ts
15554
15987
  var BaseVisitor = parser.getBaseCstVisitorConstructor();
15988
+ var defaultEnvProvider = (name) => process.env[name];
15555
15989
  var PromptScriptVisitor = class extends BaseVisitor {
15556
15990
  filename = "<unknown>";
15557
15991
  interpolateEnv = false;
15992
+ envProvider = defaultEnvProvider;
15558
15993
  constructor() {
15559
15994
  super();
15560
15995
  this.validateVisitor();
@@ -15571,6 +16006,19 @@ var PromptScriptVisitor = class extends BaseVisitor {
15571
16006
  setInterpolateEnv(enabled) {
15572
16007
  this.interpolateEnv = enabled;
15573
16008
  }
16009
+ /**
16010
+ * Set a custom environment provider function.
16011
+ * Use this to provide environment variables from sources other than process.env.
16012
+ */
16013
+ setEnvProvider(provider) {
16014
+ this.envProvider = provider;
16015
+ }
16016
+ /**
16017
+ * Reset the environment provider to the default (process.env).
16018
+ */
16019
+ resetEnvProvider() {
16020
+ this.envProvider = defaultEnvProvider;
16021
+ }
15574
16022
  /**
15575
16023
  * Create source location from a token.
15576
16024
  */
@@ -15617,27 +16065,40 @@ var PromptScriptVisitor = class extends BaseVisitor {
15617
16065
  */
15618
16066
  metaBlock(ctx) {
15619
16067
  const fields = {};
16068
+ let params;
15620
16069
  if (ctx.field) {
15621
16070
  for (const fieldNode of ctx.field) {
15622
- const { name, value } = this.visit(fieldNode);
15623
- fields[name] = value;
16071
+ const { name, value, isParamsDef, paramsDefs } = this.visit(fieldNode);
16072
+ if (isParamsDef && paramsDefs) {
16073
+ params = paramsDefs;
16074
+ } else {
16075
+ fields[name] = value;
16076
+ }
15624
16077
  }
15625
16078
  }
15626
- return {
16079
+ const meta = {
15627
16080
  type: "MetaBlock",
15628
16081
  fields,
15629
16082
  loc: this.loc(ctx.At[0])
15630
16083
  };
16084
+ if (params) {
16085
+ meta.params = params;
16086
+ }
16087
+ return meta;
15631
16088
  }
15632
16089
  /**
15633
16090
  * inheritDecl → InheritDeclaration
15634
16091
  */
15635
16092
  inheritDecl(ctx) {
15636
- return {
16093
+ const inherit = {
15637
16094
  type: "InheritDeclaration",
15638
16095
  path: this.visit(ctx.pathRef[0]),
15639
16096
  loc: this.loc(ctx.At[0])
15640
16097
  };
16098
+ if (ctx.paramCallList) {
16099
+ inherit.params = this.visit(ctx.paramCallList[0]);
16100
+ }
16101
+ return inherit;
15641
16102
  }
15642
16103
  /**
15643
16104
  * useDecl → UseDeclaration
@@ -15648,6 +16109,9 @@ var PromptScriptVisitor = class extends BaseVisitor {
15648
16109
  path: this.visit(ctx.pathRef[0]),
15649
16110
  loc: this.loc(ctx.At[0])
15650
16111
  };
16112
+ if (ctx.paramCallList) {
16113
+ use.params = this.visit(ctx.paramCallList[0]);
16114
+ }
15651
16115
  if (ctx.Identifier) {
15652
16116
  use.alias = ctx.Identifier[0].image;
15653
16117
  }
@@ -15769,20 +16233,31 @@ var PromptScriptVisitor = class extends BaseVisitor {
15769
16233
  return this.parseStringLiteral(token.image);
15770
16234
  }
15771
16235
  /**
15772
- * field → { name, value, optional, defaultValue }
16236
+ * field → { name, value, optional, defaultValue, isParamsDef?, paramsDefs? }
15773
16237
  */
15774
16238
  field(ctx) {
15775
16239
  let name;
15776
16240
  if (ctx.Identifier) {
15777
16241
  name = ctx.Identifier[0].image;
15778
- } else {
16242
+ } else if (ctx.StringLiteral) {
15779
16243
  name = this.parseStringLiteral(ctx.StringLiteral[0].image);
16244
+ } else if (ctx.StringType) {
16245
+ name = "string";
16246
+ } else if (ctx.NumberType) {
16247
+ name = "number";
16248
+ } else if (ctx.BooleanType) {
16249
+ name = "boolean";
16250
+ } else {
16251
+ throw new Error("Unknown field key type");
15780
16252
  }
15781
16253
  const optional = ctx.Question ? true : void 0;
15782
16254
  const values2 = ctx.value;
15783
- const value = this.visit(values2[0]);
16255
+ const valueResult = this.visit(values2[0]);
15784
16256
  const defaultValue = values2.length > 1 ? this.visit(values2[1]) : void 0;
15785
- return { name, value, optional, defaultValue };
16257
+ if (name === "params" && Array.isArray(valueResult) && valueResult.length > 0 && valueResult[0]?.type === "ParamDefinition") {
16258
+ return { name, value: {}, isParamsDef: true, paramsDefs: valueResult };
16259
+ }
16260
+ return { name, value: valueResult, optional, defaultValue };
15786
16261
  }
15787
16262
  /**
15788
16263
  * value → Value
@@ -15819,12 +16294,18 @@ var PromptScriptVisitor = class extends BaseVisitor {
15819
16294
  if (ctx.array) {
15820
16295
  return this.visit(ctx.array[0]);
15821
16296
  }
16297
+ if (ctx.paramDefList) {
16298
+ return this.visit(ctx.paramDefList[0]);
16299
+ }
15822
16300
  if (ctx.object) {
15823
16301
  return this.visit(ctx.object[0]);
15824
16302
  }
15825
16303
  if (ctx.typeExpr) {
15826
16304
  return this.visit(ctx.typeExpr[0]);
15827
16305
  }
16306
+ if (ctx.templateExpr) {
16307
+ return this.visit(ctx.templateExpr[0]);
16308
+ }
15828
16309
  if (ctx.Identifier) {
15829
16310
  return ctx.Identifier[0].image;
15830
16311
  }
@@ -15903,6 +16384,87 @@ var PromptScriptVisitor = class extends BaseVisitor {
15903
16384
  return ctx.Identifier.map((token) => token.image).join(".");
15904
16385
  }
15905
16386
  // ============================================================
16387
+ // Template Parameter Visitor Methods
16388
+ // ============================================================
16389
+ /**
16390
+ * paramCallList → ParamArgument[]
16391
+ */
16392
+ paramCallList(ctx) {
16393
+ if (!ctx.paramArg) {
16394
+ return [];
16395
+ }
16396
+ return ctx.paramArg.map((node) => this.visit(node));
16397
+ }
16398
+ /**
16399
+ * paramArg → ParamArgument
16400
+ */
16401
+ paramArg(ctx) {
16402
+ return {
16403
+ type: "ParamArgument",
16404
+ name: ctx.Identifier[0].image,
16405
+ value: this.visit(ctx.value[0]),
16406
+ loc: this.loc(ctx.Identifier[0])
16407
+ };
16408
+ }
16409
+ /**
16410
+ * paramDefList → ParamDefinition[]
16411
+ */
16412
+ paramDefList(ctx) {
16413
+ if (!ctx.paramDef) {
16414
+ return [];
16415
+ }
16416
+ return ctx.paramDef.map((node) => this.visit(node));
16417
+ }
16418
+ /**
16419
+ * paramDef → ParamDefinition
16420
+ */
16421
+ paramDef(ctx) {
16422
+ const name = ctx.Identifier[0].image;
16423
+ const optional = ctx.Question !== void 0;
16424
+ const paramType = this.visit(ctx.paramType[0]);
16425
+ const defaultValue = ctx.value ? this.visit(ctx.value[0]) : void 0;
16426
+ return {
16427
+ type: "ParamDefinition",
16428
+ name,
16429
+ paramType,
16430
+ optional: optional || defaultValue !== void 0,
16431
+ defaultValue,
16432
+ loc: this.loc(ctx.Identifier[0])
16433
+ };
16434
+ }
16435
+ /**
16436
+ * paramType → ParamType
16437
+ */
16438
+ paramType(ctx) {
16439
+ if (ctx.StringType) {
16440
+ return { kind: "string" };
16441
+ }
16442
+ if (ctx.NumberType) {
16443
+ return { kind: "number" };
16444
+ }
16445
+ if (ctx.BooleanType) {
16446
+ return { kind: "boolean" };
16447
+ }
16448
+ if (ctx.enumType) {
16449
+ const enumExpr = this.visit(ctx.enumType[0]);
16450
+ return {
16451
+ kind: "enum",
16452
+ options: enumExpr.constraints?.options ?? []
16453
+ };
16454
+ }
16455
+ throw new Error("Unknown param type");
16456
+ }
16457
+ /**
16458
+ * templateExpr → TemplateExpression
16459
+ */
16460
+ templateExpr(ctx) {
16461
+ return {
16462
+ type: "TemplateExpression",
16463
+ name: ctx.Identifier[0].image,
16464
+ loc: this.loc(ctx.TemplateOpen[0])
16465
+ };
16466
+ }
16467
+ // ============================================================
15906
16468
  // Helper Methods
15907
16469
  // ============================================================
15908
16470
  /**
@@ -15924,7 +16486,7 @@ var PromptScriptVisitor = class extends BaseVisitor {
15924
16486
  interpolateEnvVars(text) {
15925
16487
  const envVarPattern = /\$\{([A-Za-z_]\w*)(?::-([^}]*))?\}/g;
15926
16488
  return text.replace(envVarPattern, (_match, varName, defaultValue) => {
15927
- const envValue = process.env[varName];
16489
+ const envValue = this.envProvider(varName);
15928
16490
  if (envValue !== void 0) {
15929
16491
  return envValue;
15930
16492
  }
@@ -15983,7 +16545,8 @@ function parse(source, options = {}) {
15983
16545
  filename = "<unknown>",
15984
16546
  tolerant = false,
15985
16547
  recovery = false,
15986
- interpolateEnv = false
16548
+ interpolateEnv = false,
16549
+ envProvider
15987
16550
  } = options;
15988
16551
  const isRecoveryMode = tolerant || recovery;
15989
16552
  const errors = [];
@@ -16018,6 +16581,11 @@ function parse(source, options = {}) {
16018
16581
  }
16019
16582
  try {
16020
16583
  visitor.setInterpolateEnv(interpolateEnv);
16584
+ if (envProvider) {
16585
+ visitor.setEnvProvider(envProvider);
16586
+ } else {
16587
+ visitor.resetEnvProvider();
16588
+ }
16021
16589
  const ast = visitor.visit(cst, filename);
16022
16590
  return { ast, errors };
16023
16591
  } catch (err) {
@@ -16987,8 +17555,28 @@ var Resolver = class {
16987
17555
  sources.push(...parent.sources);
16988
17556
  errors.push(...parent.errors);
16989
17557
  if (parent.ast) {
17558
+ let resolvedParent = parent.ast;
17559
+ if (parent.ast.meta?.params || ast.inherit.params) {
17560
+ this.logger.debug(`Binding template parameters for ${parentPath}`);
17561
+ try {
17562
+ const params = bindParams(
17563
+ ast.inherit.params,
17564
+ parent.ast.meta?.params,
17565
+ parentPath,
17566
+ ast.inherit.loc
17567
+ );
17568
+ if (params.size > 0) {
17569
+ const ctx = { params, sourceFile: parentPath };
17570
+ resolvedParent = interpolateAST(parent.ast, ctx);
17571
+ this.logger.debug(`Interpolated ${params.size} parameter(s)`);
17572
+ }
17573
+ } catch (err) {
17574
+ errors.push(new ResolveError(err.message, ast.inherit.loc));
17575
+ return ast;
17576
+ }
17577
+ }
16990
17578
  this.logger.debug(`Merging with parent AST`);
16991
- return resolveInheritance(parent.ast, ast);
17579
+ return resolveInheritance(resolvedParent, ast);
16992
17580
  }
16993
17581
  } catch (err) {
16994
17582
  if (err instanceof CircularDependencyError) {
@@ -17012,8 +17600,23 @@ var Resolver = class {
17012
17600
  sources.push(...imported.sources);
17013
17601
  errors.push(...imported.errors);
17014
17602
  if (imported.ast) {
17603
+ let resolvedImport = imported.ast;
17604
+ if (imported.ast.meta?.params || use.params) {
17605
+ this.logger.debug(`Binding template parameters for ${importPath}`);
17606
+ try {
17607
+ const params = bindParams(use.params, imported.ast.meta?.params, importPath, use.loc);
17608
+ if (params.size > 0) {
17609
+ const ctx = { params, sourceFile: importPath };
17610
+ resolvedImport = interpolateAST(imported.ast, ctx);
17611
+ this.logger.debug(`Interpolated ${params.size} parameter(s)`);
17612
+ }
17613
+ } catch (err) {
17614
+ errors.push(new ResolveError(err.message, use.loc));
17615
+ continue;
17616
+ }
17617
+ }
17015
17618
  this.logger.debug(`Merging import${use.alias ? ` as "${use.alias}"` : ""}`);
17016
- result = resolveUses(result, use, imported.ast);
17619
+ result = resolveUses(result, use, resolvedImport);
17017
17620
  }
17018
17621
  } catch (err) {
17019
17622
  if (err instanceof CircularDependencyError) {
@@ -18262,6 +18865,92 @@ var emptyBlock = {
18262
18865
  }
18263
18866
  };
18264
18867
 
18868
+ // packages/validator/src/rules/valid-params.ts
18869
+ function paramTypeToString2(paramType) {
18870
+ switch (paramType.kind) {
18871
+ case "string":
18872
+ return "string";
18873
+ case "number":
18874
+ return "number";
18875
+ case "boolean":
18876
+ return "boolean";
18877
+ case "enum":
18878
+ return `enum(${paramType.options.map((o) => `"${o}"`).join(", ")})`;
18879
+ }
18880
+ }
18881
+ function getValueType2(value) {
18882
+ if (value === null) return "null";
18883
+ if (typeof value === "string") return "string";
18884
+ if (typeof value === "number") return "number";
18885
+ if (typeof value === "boolean") return "boolean";
18886
+ if (Array.isArray(value)) return "array";
18887
+ if (typeof value === "object" && "type" in value) {
18888
+ const typed = value;
18889
+ if (typed.type === "TextContent") return "string";
18890
+ if (typed.type === "TemplateExpression") return "template";
18891
+ if (typed.type === "TypeExpression") return "type";
18892
+ }
18893
+ return "object";
18894
+ }
18895
+ function valueMatchesType(value, paramType) {
18896
+ const actualType = getValueType2(value);
18897
+ switch (paramType.kind) {
18898
+ case "string":
18899
+ return actualType === "string";
18900
+ case "number":
18901
+ return actualType === "number";
18902
+ case "boolean":
18903
+ return actualType === "boolean";
18904
+ case "enum":
18905
+ return actualType === "string" && paramType.options.includes(value);
18906
+ }
18907
+ }
18908
+ var validParams = {
18909
+ id: "PS009",
18910
+ name: "valid-params",
18911
+ description: "Validate parameter definitions in @meta",
18912
+ defaultSeverity: "error",
18913
+ validate: (ctx) => {
18914
+ const params = ctx.ast.meta?.params;
18915
+ if (!params || params.length === 0) {
18916
+ return;
18917
+ }
18918
+ const seen = /* @__PURE__ */ new Set();
18919
+ for (const param of params) {
18920
+ if (seen.has(param.name)) {
18921
+ ctx.report({
18922
+ message: `Duplicate parameter definition: '${param.name}'`,
18923
+ location: param.loc,
18924
+ suggestion: "Remove the duplicate parameter definition"
18925
+ });
18926
+ }
18927
+ seen.add(param.name);
18928
+ }
18929
+ for (const param of params) {
18930
+ if (param.defaultValue !== void 0) {
18931
+ if (!valueMatchesType(param.defaultValue, param.paramType)) {
18932
+ const expectedType = paramTypeToString2(param.paramType);
18933
+ const actualType = getValueType2(param.defaultValue);
18934
+ ctx.report({
18935
+ message: `Default value for '${param.name}' has wrong type: expected ${expectedType}, got ${actualType}`,
18936
+ location: param.loc,
18937
+ suggestion: `Change the default value to a ${expectedType}`
18938
+ });
18939
+ }
18940
+ }
18941
+ }
18942
+ for (const param of params) {
18943
+ if (param.optional && param.defaultValue === void 0) {
18944
+ ctx.report({
18945
+ message: `Optional parameter '${param.name}' has no default value`,
18946
+ location: param.loc,
18947
+ suggestion: "Consider adding a default value or marking as required"
18948
+ });
18949
+ }
18950
+ }
18951
+ }
18952
+ };
18953
+
18265
18954
  // packages/validator/src/rules/index.ts
18266
18955
  var allRules = [
18267
18956
  // Required meta rules (PS001, PS002)
@@ -18278,7 +18967,9 @@ var allRules = [
18278
18967
  // Deprecated features (PS007)
18279
18968
  deprecated,
18280
18969
  // Empty blocks (PS008)
18281
- emptyBlock
18970
+ emptyBlock,
18971
+ // Valid params (PS009)
18972
+ validParams
18282
18973
  ];
18283
18974
 
18284
18975
  // packages/validator/src/validator.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptscript/cli",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "CLI for PromptScript - standardize AI instructions across GitHub Copilot, Claude, Cursor and other AI tools",
5
5
  "keywords": [
6
6
  "cli",