@promptscript/cli 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -311,6 +311,13 @@ var init_manifest = __esm({
311
311
  }
312
312
  });
313
313
 
314
+ // packages/core/src/types/models.ts
315
+ var init_models = __esm({
316
+ "packages/core/src/types/models.ts"() {
317
+ "use strict";
318
+ }
319
+ });
320
+
314
321
  // packages/core/src/types/policy.ts
315
322
  var init_policy = __esm({
316
323
  "packages/core/src/types/policy.ts"() {
@@ -362,6 +369,7 @@ var init_types = __esm({
362
369
  init_convention();
363
370
  init_lockfile();
364
371
  init_manifest();
372
+ init_models();
365
373
  init_policy();
366
374
  init_prettier();
367
375
  init_provenance();
@@ -7649,6 +7657,651 @@ var init_agent_capabilities = __esm({
7649
7657
  }
7650
7658
  });
7651
7659
 
7660
+ // packages/core/src/model-profiles.ts
7661
+ function release(provider, family, version, id, displayName, fields = {}) {
7662
+ return {
7663
+ id,
7664
+ provider,
7665
+ family,
7666
+ version,
7667
+ displayName,
7668
+ apiId: id,
7669
+ aliases: [],
7670
+ // A release with a successor is legacy unless it says otherwise.
7671
+ status: fields.successor === void 0 ? "current" : "legacy",
7672
+ targets: {},
7673
+ ...fields
7674
+ };
7675
+ }
7676
+ function claude(line, version, fields = {}) {
7677
+ const { id = `claude-${line}-${version.replaceAll(".", "-")}`, displayName, ...rest } = fields;
7678
+ const name = `${line.charAt(0).toUpperCase()}${line.slice(1)}`;
7679
+ return release(
7680
+ "anthropic",
7681
+ `claude-${line}`,
7682
+ version,
7683
+ id,
7684
+ displayName ?? `Claude ${name} ${version}`,
7685
+ {
7686
+ aliases: [`${line}-${version}`],
7687
+ ...rest
7688
+ }
7689
+ );
7690
+ }
7691
+ var openai, google, xai, MODEL_PROFILES;
7692
+ var init_model_profiles = __esm({
7693
+ "packages/core/src/model-profiles.ts"() {
7694
+ "use strict";
7695
+ openai = (family, version, id, displayName, fields) => release("openai", family, version, id, displayName, fields);
7696
+ google = (family, version, id, displayName, fields) => release("google", family, version, id, displayName, fields);
7697
+ xai = (family, version, id, displayName, fields) => release("xai", family, version, id, displayName, fields);
7698
+ MODEL_PROFILES = [
7699
+ // Anthropic
7700
+ claude("opus", "4", {
7701
+ apiId: "claude-opus-4-20250514",
7702
+ status: "retired",
7703
+ successor: "claude-opus-4-1",
7704
+ releaseDate: "2025-05-22",
7705
+ retirementDate: "2026-06-15"
7706
+ }),
7707
+ claude("opus", "4.1", {
7708
+ apiId: "claude-opus-4-1-20250805",
7709
+ status: "retired",
7710
+ successor: "claude-opus-4-5",
7711
+ releaseDate: "2025-08-05",
7712
+ retirementDate: "2026-08-05"
7713
+ }),
7714
+ claude("opus", "4.5", {
7715
+ apiId: "claude-opus-4-5-20251101",
7716
+ successor: "claude-opus-4-6",
7717
+ releaseDate: "2025-11-24"
7718
+ }),
7719
+ claude("opus", "4.6", { successor: "claude-opus-4-7" }),
7720
+ claude("opus", "4.7", { successor: "claude-opus-4-8" }),
7721
+ claude("opus", "4.8", { successor: "claude-opus-5" }),
7722
+ claude("opus", "5", { successor: "claude-opus-5-5", releaseDate: "2026-07-24" }),
7723
+ claude("opus", "5.5", { releaseDate: "2026-09-22" }),
7724
+ claude("sonnet", "3.5", {
7725
+ id: "claude-3-5-sonnet",
7726
+ apiId: "claude-3-5-sonnet-20241022",
7727
+ displayName: "Claude 3.5 Sonnet",
7728
+ aliases: ["sonnet-3.5", "claude-3-5-sonnet-latest"],
7729
+ status: "retired",
7730
+ successor: "claude-3-7-sonnet",
7731
+ releaseDate: "2024-10-22",
7732
+ retirementDate: "2025-10-28"
7733
+ }),
7734
+ claude("sonnet", "3.7", {
7735
+ id: "claude-3-7-sonnet",
7736
+ apiId: "claude-3-7-sonnet-20250219",
7737
+ displayName: "Claude 3.7 Sonnet",
7738
+ aliases: ["sonnet-3.7", "claude-3-7-sonnet-latest"],
7739
+ status: "retired",
7740
+ successor: "claude-sonnet-4",
7741
+ releaseDate: "2025-02-24",
7742
+ retirementDate: "2026-02-19"
7743
+ }),
7744
+ claude("sonnet", "4", {
7745
+ apiId: "claude-sonnet-4-20250514",
7746
+ status: "retired",
7747
+ successor: "claude-sonnet-4-5",
7748
+ releaseDate: "2025-05-22",
7749
+ retirementDate: "2026-06-15"
7750
+ }),
7751
+ claude("sonnet", "4.5", {
7752
+ apiId: "claude-sonnet-4-5-20250929",
7753
+ successor: "claude-sonnet-4-6",
7754
+ releaseDate: "2025-09-29"
7755
+ }),
7756
+ claude("sonnet", "4.6", { successor: "claude-sonnet-5" }),
7757
+ claude("sonnet", "5", { releaseDate: "2026-06-30" }),
7758
+ claude("haiku", "3.5", {
7759
+ id: "claude-3-5-haiku",
7760
+ apiId: "claude-3-5-haiku-20241022",
7761
+ displayName: "Claude 3.5 Haiku",
7762
+ aliases: ["haiku-3.5", "claude-3-5-haiku-latest"],
7763
+ status: "retired",
7764
+ successor: "claude-haiku-4-5",
7765
+ releaseDate: "2024-10-22",
7766
+ retirementDate: "2026-02-19"
7767
+ }),
7768
+ claude("haiku", "4.5", { apiId: "claude-haiku-4-5-20251001", releaseDate: "2025-10-15" }),
7769
+ claude("fable", "5", { successor: "claude-fable-5-1", releaseDate: "2026-06-09" }),
7770
+ claude("fable", "5.1", { releaseDate: "2026-09-01" }),
7771
+ claude("mythos", "5", { successor: "claude-mythos-5-1", releaseDate: "2026-06-09" }),
7772
+ claude("mythos", "5.1", { releaseDate: "2026-09-01" }),
7773
+ // OpenAI
7774
+ openai("gpt-4o", "4o", "gpt-4o", "GPT-4o", { successor: "gpt-4.1", releaseDate: "2024-05-13" }),
7775
+ openai("gpt", "4.1", "gpt-4.1", "GPT-4.1", { successor: "gpt-5.5", releaseDate: "2025-04-14" }),
7776
+ openai("gpt", "5", "gpt-5", "GPT-5", {
7777
+ status: "deprecated",
7778
+ successor: "gpt-5.1",
7779
+ releaseDate: "2025-08-07",
7780
+ retirementDate: "2026-12-11"
7781
+ }),
7782
+ openai("gpt", "5.1", "gpt-5.1", "GPT-5.1", {
7783
+ status: "deprecated",
7784
+ successor: "gpt-5.2",
7785
+ releaseDate: "2025-11-13"
7786
+ }),
7787
+ openai("gpt", "5.2", "gpt-5.2", "GPT-5.2", { successor: "gpt-5.4", releaseDate: "2025-12-11" }),
7788
+ openai("gpt", "5.4", "gpt-5.4", "GPT-5.4", { successor: "gpt-5.5", releaseDate: "2026-03-05" }),
7789
+ openai("gpt", "5.5", "gpt-5.5", "GPT-5.5", {
7790
+ successor: "gpt-5.6-sol",
7791
+ releaseDate: "2026-04-24"
7792
+ }),
7793
+ openai("gpt-mini", "5", "gpt-5-mini", "GPT-5 mini", {
7794
+ status: "deprecated",
7795
+ successor: "gpt-5.4-mini",
7796
+ releaseDate: "2025-08-07",
7797
+ retirementDate: "2026-12-11"
7798
+ }),
7799
+ openai("gpt-mini", "5.4", "gpt-5.4-mini", "GPT-5.4 mini", {
7800
+ successor: "gpt-5.6-terra",
7801
+ releaseDate: "2026-03-17"
7802
+ }),
7803
+ openai("gpt-nano", "5", "gpt-5-nano", "GPT-5 nano", {
7804
+ status: "deprecated",
7805
+ successor: "gpt-5.4-nano",
7806
+ releaseDate: "2025-08-07",
7807
+ retirementDate: "2026-12-11"
7808
+ }),
7809
+ openai("gpt-nano", "5.4", "gpt-5.4-nano", "GPT-5.4 nano", {
7810
+ successor: "gpt-5.6-luna",
7811
+ releaseDate: "2026-03-17"
7812
+ }),
7813
+ openai("gpt-codex", "5", "gpt-5-codex", "GPT-5-Codex", { successor: "gpt-5.1-codex" }),
7814
+ openai("gpt-codex", "5.1", "gpt-5.1-codex", "GPT-5.1-Codex", {
7815
+ successor: "gpt-5.2-codex",
7816
+ releaseDate: "2025-11-13"
7817
+ }),
7818
+ openai("gpt-codex", "5.2", "gpt-5.2-codex", "GPT-5.2-Codex", {
7819
+ status: "deprecated",
7820
+ successor: "gpt-5.3-codex",
7821
+ releaseDate: "2026-01-14"
7822
+ }),
7823
+ openai("gpt-codex", "5.3", "gpt-5.3-codex", "GPT-5.3-Codex", { releaseDate: "2026-02-24" }),
7824
+ openai("gpt-codex-max", "5.1", "gpt-5.1-codex-max", "GPT-5.1-Codex-Max", {
7825
+ successor: "gpt-5.2-codex",
7826
+ releaseDate: "2025-12-04"
7827
+ }),
7828
+ openai("gpt-codex-mini", "5.1", "gpt-5.1-codex-mini", "GPT-5.1-Codex-Mini", {
7829
+ successor: "gpt-5.3-codex",
7830
+ releaseDate: "2025-11-13"
7831
+ }),
7832
+ openai("gpt-sol", "5.6", "gpt-5.6-sol", "GPT-5.6 Sol", {
7833
+ successor: "gpt-6-astra",
7834
+ releaseDate: "2026-07-09"
7835
+ }),
7836
+ openai("gpt-sol", "6", "gpt-6-sol", "GPT-6 Sol", { releaseDate: "2026-09-22" }),
7837
+ openai("gpt-terra", "5.6", "gpt-5.6-terra", "GPT-5.6 Terra", {
7838
+ successor: "gpt-6-sol",
7839
+ releaseDate: "2026-07-09"
7840
+ }),
7841
+ openai("gpt-luna", "5.6", "gpt-5.6-luna", "GPT-5.6 Luna", {
7842
+ successor: "gpt-6-luna",
7843
+ releaseDate: "2026-07-09"
7844
+ }),
7845
+ openai("gpt-luna", "6", "gpt-6-luna", "GPT-6 Luna", { releaseDate: "2026-09-22" }),
7846
+ openai("gpt-astra", "6", "gpt-6-astra", "GPT-6 Astra", { releaseDate: "2026-09-03" }),
7847
+ openai("o3", "3", "o3", "o3", {
7848
+ status: "deprecated",
7849
+ successor: "gpt-5.5",
7850
+ releaseDate: "2025-04-16",
7851
+ retirementDate: "2026-12-11"
7852
+ }),
7853
+ openai("o4-mini", "4", "o4-mini", "o4-mini", {
7854
+ status: "deprecated",
7855
+ successor: "gpt-5.4-mini",
7856
+ releaseDate: "2025-04-16",
7857
+ retirementDate: "2026-10-23"
7858
+ }),
7859
+ // Google
7860
+ google("gemini-pro", "2.5", "gemini-2.5-pro", "Gemini 2.5 Pro", {
7861
+ successor: "gemini-3.1-pro-preview",
7862
+ releaseDate: "2025-06-17"
7863
+ }),
7864
+ google("gemini-pro", "3.1", "gemini-3.1-pro-preview", "Gemini 3.1 Pro", {
7865
+ aliases: ["Gemini 3.1 Pro Preview"],
7866
+ releaseDate: "2026-02-19"
7867
+ }),
7868
+ google("gemini-flash", "2.5", "gemini-2.5-flash", "Gemini 2.5 Flash", {
7869
+ successor: "gemini-3.5-flash",
7870
+ releaseDate: "2025-06-17"
7871
+ }),
7872
+ google("gemini-flash", "3.5", "gemini-3.5-flash", "Gemini 3.5 Flash", {
7873
+ successor: "gemini-3.6-flash",
7874
+ releaseDate: "2026-05-19"
7875
+ }),
7876
+ google("gemini-flash", "3.6", "gemini-3.6-flash", "Gemini 3.6 Flash", {
7877
+ successor: "gemini-3.7-flash",
7878
+ releaseDate: "2026-07-21"
7879
+ }),
7880
+ google("gemini-flash", "3.7", "gemini-3.7-flash", "Gemini 3.7 Flash", {
7881
+ successor: "gemini-3.8-flash",
7882
+ releaseDate: "2026-08-13"
7883
+ }),
7884
+ google("gemini-flash", "3.8", "gemini-3.8-flash", "Gemini 3.8 Flash", {
7885
+ releaseDate: "2026-09-02"
7886
+ }),
7887
+ google("gemini-flash-lite", "2.5", "gemini-2.5-flash-lite", "Gemini 2.5 Flash-Lite", {
7888
+ successor: "gemini-3.1-flash-lite",
7889
+ releaseDate: "2025-07-22"
7890
+ }),
7891
+ google("gemini-flash-lite", "3.1", "gemini-3.1-flash-lite", "Gemini 3.1 Flash-Lite", {
7892
+ status: "deprecated",
7893
+ successor: "gemini-3.5-flash-lite",
7894
+ releaseDate: "2026-05-07",
7895
+ retirementDate: "2027-05-07"
7896
+ }),
7897
+ google("gemini-flash-lite", "3.5", "gemini-3.5-flash-lite", "Gemini 3.5 Flash-Lite", {
7898
+ releaseDate: "2026-07-21"
7899
+ }),
7900
+ // xAI
7901
+ xai("grok", "4.5", "grok-4.5", "Grok 4.5", { successor: "grok-4.6" }),
7902
+ xai("grok", "4.6", "grok-4.6", "Grok 4.6", { successor: "grok-4.7" }),
7903
+ xai("grok", "4.7", "grok-4.7", "Grok 4.7")
7904
+ ];
7905
+ }
7906
+ });
7907
+
7908
+ // packages/core/src/model-catalog.ts
7909
+ function normalizeModelName(name) {
7910
+ return name.trim().toLowerCase();
7911
+ }
7912
+ function ownValue(record, key) {
7913
+ return Object.hasOwn(record, key) ? record[key] : void 0;
7914
+ }
7915
+ function isRecord4(value) {
7916
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7917
+ }
7918
+ function nonEmptyString(value) {
7919
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
7920
+ }
7921
+ function stringList(value) {
7922
+ if (!Array.isArray(value)) return void 0;
7923
+ return value.flatMap((item) => {
7924
+ const name = nonEmptyString(item);
7925
+ return name ? [name] : [];
7926
+ });
7927
+ }
7928
+ function targetNames(value) {
7929
+ if (!isRecord4(value)) return {};
7930
+ return Object.fromEntries(
7931
+ Object.entries(value).flatMap(([key, item]) => {
7932
+ const target = key.trim().toLowerCase();
7933
+ const name = nonEmptyString(item);
7934
+ return target && name ? [[target, name]] : [];
7935
+ })
7936
+ );
7937
+ }
7938
+ function modelStatus(value) {
7939
+ return MODEL_STATUSES.find((status) => status === value);
7940
+ }
7941
+ function optionalFields(input2) {
7942
+ const fields = {};
7943
+ const provider = nonEmptyString(input2.provider);
7944
+ if (provider) fields.provider = provider;
7945
+ const family = nonEmptyString(input2.family);
7946
+ if (family) fields.family = family;
7947
+ const version = nonEmptyString(input2.version);
7948
+ if (version) fields.version = version;
7949
+ const displayName = nonEmptyString(input2.displayName);
7950
+ if (displayName) fields.displayName = displayName;
7951
+ const apiId = nonEmptyString(input2.apiId);
7952
+ if (apiId) fields.apiId = apiId;
7953
+ const aliases = stringList(input2.aliases);
7954
+ if (aliases) fields.aliases = aliases;
7955
+ const status = modelStatus(input2.status);
7956
+ if (status) fields.status = status;
7957
+ const successor = nonEmptyString(input2.successor);
7958
+ if (successor) fields.successor = successor;
7959
+ const releaseDate = nonEmptyString(input2.releaseDate);
7960
+ if (releaseDate) fields.releaseDate = releaseDate;
7961
+ const retirementDate = nonEmptyString(input2.retirementDate);
7962
+ if (retirementDate) fields.retirementDate = retirementDate;
7963
+ return fields;
7964
+ }
7965
+ function applyProfileInput(base, input2) {
7966
+ return {
7967
+ ...base,
7968
+ ...optionalFields(input2),
7969
+ // Per-target names merge so one override keeps the other targets.
7970
+ targets: { ...base.targets, ...targetNames(input2.targets) }
7971
+ };
7972
+ }
7973
+ function createCustomProfile(id, input2) {
7974
+ const fields = optionalFields(input2);
7975
+ return {
7976
+ id,
7977
+ provider: "custom",
7978
+ family: id,
7979
+ version: "",
7980
+ displayName: id,
7981
+ apiId: id,
7982
+ aliases: [],
7983
+ status: "current",
7984
+ ...fields,
7985
+ targets: targetNames(input2.targets)
7986
+ };
7987
+ }
7988
+ function mergeProfiles(builtIns, overrides) {
7989
+ if (!isRecord4(overrides)) return builtIns;
7990
+ const pending = /* @__PURE__ */ new Map();
7991
+ for (const [key, input2] of Object.entries(overrides)) {
7992
+ const id = nonEmptyString(key);
7993
+ if (id && isRecord4(input2)) {
7994
+ pending.set(normalizeModelName(id), { id, input: input2 });
7995
+ }
7996
+ }
7997
+ const merged = builtIns.map((profile) => {
7998
+ const key = normalizeModelName(profile.id);
7999
+ const override = pending.get(key);
8000
+ if (!override) return profile;
8001
+ pending.delete(key);
8002
+ return applyProfileInput(profile, override.input);
8003
+ });
8004
+ for (const { id, input: input2 } of pending.values()) {
8005
+ merged.push(createCustomProfile(id, input2));
8006
+ }
8007
+ return merged;
8008
+ }
8009
+ function splitVersionPart(part) {
8010
+ let end = 0;
8011
+ while (end < part.length) {
8012
+ const character = part[end];
8013
+ if (character === void 0 || character < "0" || character > "9") break;
8014
+ end++;
8015
+ }
8016
+ const digits = part.slice(0, end);
8017
+ return { number: digits.length > 0 ? Number(digits) : 0, suffix: part.slice(end) };
8018
+ }
8019
+ function compareVersionParts(leftPart, rightPart) {
8020
+ const left = splitVersionPart(leftPart);
8021
+ const right = splitVersionPart(rightPart);
8022
+ if (left.number !== right.number) return left.number - right.number;
8023
+ const leftSuffix = left.suffix;
8024
+ const rightSuffix = right.suffix;
8025
+ if (leftSuffix === rightSuffix) return 0;
8026
+ if (leftSuffix === "") return 1;
8027
+ if (rightSuffix === "") return -1;
8028
+ return leftSuffix.localeCompare(rightSuffix);
8029
+ }
8030
+ function compareModelVersions(a, b) {
8031
+ const left = a.split(".");
8032
+ const right = b.split(".");
8033
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
8034
+ const diff = compareVersionParts(left[index] ?? "0", right[index] ?? "0");
8035
+ if (diff !== 0) return diff;
8036
+ }
8037
+ return 0;
8038
+ }
8039
+ function compareModelReleases(a, b) {
8040
+ return compareModelVersions(a.version, b.version) || (a.releaseDate ?? "").localeCompare(b.releaseDate ?? "");
8041
+ }
8042
+ function createModelCatalog(config, builtIns = MODEL_PROFILES) {
8043
+ const profiles = mergeProfiles(builtIns, config?.profiles);
8044
+ const byId = /* @__PURE__ */ new Map();
8045
+ const byName = /* @__PURE__ */ new Map();
8046
+ for (const profile of profiles) {
8047
+ byId.set(normalizeModelName(profile.id), profile);
8048
+ for (const name of [profile.apiId, profile.displayName, ...profile.aliases]) {
8049
+ byName.set(normalizeModelName(name), profile);
8050
+ }
8051
+ }
8052
+ const getProfile = (id) => byId.get(normalizeModelName(id));
8053
+ const getLatest = (family) => {
8054
+ const members = profiles.filter((profile) => profile.family === family);
8055
+ const served = members.filter((profile) => profile.status !== "retired");
8056
+ const pool = served.length > 0 ? served : members;
8057
+ return pool.reduce(
8058
+ (newest, profile) => newest === void 0 || compareModelReleases(profile, newest) > 0 ? profile : newest,
8059
+ void 0
8060
+ );
8061
+ };
8062
+ const resolve45 = (reference) => {
8063
+ const name = normalizeModelName(reference);
8064
+ if (!name) return void 0;
8065
+ if (name === INHERIT_MODEL) return { kind: "inherit" };
8066
+ const exact = byId.get(name);
8067
+ if (exact) return { kind: "profile", profile: exact };
8068
+ const family = ownValue(FLOATING_MODEL_ALIASES, name);
8069
+ const latest = family ? getLatest(family) : void 0;
8070
+ if (latest) return { kind: "floating", alias: name, profile: latest };
8071
+ const named = byName.get(name);
8072
+ return named ? { kind: "profile", profile: named } : void 0;
8073
+ };
8074
+ const getReplacement = (id) => {
8075
+ const visited = /* @__PURE__ */ new Set();
8076
+ let current = getProfile(id);
8077
+ while (current?.successor && !visited.has(current.id)) {
8078
+ visited.add(current.id);
8079
+ const next = getProfile(current.successor);
8080
+ if (!next) break;
8081
+ if (next.status === "current") return next;
8082
+ current = next;
8083
+ }
8084
+ return void 0;
8085
+ };
8086
+ return { profiles, getProfile, getLatest, resolve: resolve45, getReplacement };
8087
+ }
8088
+ function getModelCatalog(config) {
8089
+ if (!config?.profiles) {
8090
+ defaultCatalog ??= createModelCatalog();
8091
+ return defaultCatalog;
8092
+ }
8093
+ let catalog = catalogCache.get(config);
8094
+ if (!catalog) {
8095
+ catalog = createModelCatalog(config);
8096
+ catalogCache.set(config, catalog);
8097
+ }
8098
+ return catalog;
8099
+ }
8100
+ function duplicateIdIssues(profiles) {
8101
+ const issues = [];
8102
+ const ids = /* @__PURE__ */ new Set();
8103
+ for (const profile of profiles) {
8104
+ const id = normalizeModelName(profile.id);
8105
+ if (ids.has(id)) issues.push(`duplicate model id "${profile.id}"`);
8106
+ ids.add(id);
8107
+ }
8108
+ return issues;
8109
+ }
8110
+ function nameIssues(profile, owners) {
8111
+ const issues = [];
8112
+ const names = new Set(
8113
+ [profile.id, profile.apiId, profile.displayName, ...profile.aliases].map(normalizeModelName)
8114
+ );
8115
+ for (const name of names) {
8116
+ const owner = owners.get(name);
8117
+ if (Object.hasOwn(FLOATING_MODEL_ALIASES, name) || name === INHERIT_MODEL) {
8118
+ issues.push(`model "${profile.id}" uses reserved name "${name}"`);
8119
+ } else if (owner === void 0) {
8120
+ owners.set(name, profile.id);
8121
+ } else if (owner !== profile.id) {
8122
+ issues.push(`model name "${name}" is used by "${owner}" and "${profile.id}"`);
8123
+ }
8124
+ }
8125
+ return issues;
8126
+ }
8127
+ function successorIssues(profile, ids) {
8128
+ if (profile.successor === void 0) return [];
8129
+ const successor = normalizeModelName(profile.successor);
8130
+ if (successor === normalizeModelName(profile.id)) {
8131
+ return [`model "${profile.id}" names itself as successor`];
8132
+ }
8133
+ if (ids.has(successor)) return [];
8134
+ return [`model "${profile.id}" names unknown successor "${profile.successor}"`];
8135
+ }
8136
+ function dateIssues(profile) {
8137
+ const dates = [
8138
+ ["release date", profile.releaseDate],
8139
+ ["retirement date", profile.retirementDate]
8140
+ ];
8141
+ return dates.flatMap(
8142
+ ([label, date]) => date === void 0 || DATE_PATTERN.test(date) ? [] : [`model "${profile.id}" has invalid ${label} "${date}", expected YYYY-MM-DD`]
8143
+ );
8144
+ }
8145
+ function targetIssues(profile) {
8146
+ const writers = Object.keys(MODEL_TARGET_SCHEMES).join(", ");
8147
+ return Object.keys(profile.targets).filter((target) => !getModelTargetScheme(target)).map(
8148
+ (target) => `model "${profile.id}" sets a name for "${target}", which is not a target that writes model names (${writers})`
8149
+ );
8150
+ }
8151
+ function successorLoopIssues(profiles) {
8152
+ const byId = new Map(profiles.map((profile) => [normalizeModelName(profile.id), profile]));
8153
+ const successorOf = (profile) => profile.successor ? byId.get(normalizeModelName(profile.successor)) : void 0;
8154
+ const issues = [];
8155
+ for (const profile of profiles) {
8156
+ const visited = /* @__PURE__ */ new Set([normalizeModelName(profile.id)]);
8157
+ let next = successorOf(profile);
8158
+ while (next) {
8159
+ const id = normalizeModelName(next.id);
8160
+ if (visited.has(id)) {
8161
+ issues.push(`successor chain of "${profile.id}" loops`);
8162
+ break;
8163
+ }
8164
+ visited.add(id);
8165
+ next = successorOf(next);
8166
+ }
8167
+ }
8168
+ return issues;
8169
+ }
8170
+ function floatingAliasIssues(profiles) {
8171
+ return Object.entries(FLOATING_MODEL_ALIASES).filter(([, family]) => !profiles.some((profile) => profile.family === family)).map(([alias, family]) => `floating alias "${alias}" names unknown family "${family}"`);
8172
+ }
8173
+ function validateModelCatalog(profiles = MODEL_PROFILES) {
8174
+ const ids = new Set(profiles.map((profile) => normalizeModelName(profile.id)));
8175
+ const owners = /* @__PURE__ */ new Map();
8176
+ const profileIssues = profiles.flatMap((profile) => [
8177
+ ...nameIssues(profile, owners),
8178
+ ...successorIssues(profile, ids),
8179
+ ...dateIssues(profile),
8180
+ ...targetIssues(profile)
8181
+ ]);
8182
+ return [
8183
+ ...duplicateIdIssues(profiles),
8184
+ ...profileIssues,
8185
+ ...successorLoopIssues(profiles),
8186
+ ...floatingAliasIssues(profiles)
8187
+ ];
8188
+ }
8189
+ function resolveModelSet(entries, catalog) {
8190
+ const profileIds = /* @__PURE__ */ new Set();
8191
+ const floatingAliases = /* @__PURE__ */ new Set();
8192
+ const unknown = [];
8193
+ for (const entry of entries) {
8194
+ const resolution = catalog.resolve(entry);
8195
+ if (!resolution) {
8196
+ unknown.push(entry);
8197
+ continue;
8198
+ }
8199
+ if (resolution.kind === "inherit") continue;
8200
+ if (resolution.kind === "floating") floatingAliases.add(resolution.alias);
8201
+ profileIds.add(resolution.profile.id);
8202
+ }
8203
+ return { profileIds, floatingAliases, unknown };
8204
+ }
8205
+ function isInModelSet(resolution, set) {
8206
+ if (resolution.kind === "inherit") return true;
8207
+ if (resolution.kind === "floating" && set.floatingAliases.has(resolution.alias)) return true;
8208
+ return set.profileIds.has(resolution.profile.id);
8209
+ }
8210
+ function getModelTargetScheme(target) {
8211
+ return Object.hasOwn(MODEL_TARGET_SCHEMES, target) ? MODEL_TARGET_SCHEMES[target] : void 0;
8212
+ }
8213
+ function profileName(value, profile) {
8214
+ return UNSAFE_MODEL_NAME.test(value) ? { issue: "invalid-name", profile } : { value, profile };
8215
+ }
8216
+ function mapProfileToTarget(resolution, target, scheme) {
8217
+ const { profile } = resolution;
8218
+ const explicit = ownValue(profile.targets, target);
8219
+ if (explicit !== void 0) return profileName(explicit, profile);
8220
+ if (resolution.kind === "floating" && scheme.keepsFloatingAliases) {
8221
+ return { value: resolution.alias, profile };
8222
+ }
8223
+ if (scheme.providers && !scheme.providers.includes(profile.provider)) {
8224
+ return { issue: "unsupported-provider", profile };
8225
+ }
8226
+ return profileName(profile[scheme.naming], profile);
8227
+ }
8228
+ function mapModelToTarget(reference, target, catalog = getModelCatalog()) {
8229
+ const written = reference.trim();
8230
+ if (!written) return {};
8231
+ if (UNSAFE_MODEL_NAME.test(written)) return { issue: "invalid-name" };
8232
+ const scheme = getModelTargetScheme(target);
8233
+ if (!scheme) return { value: written };
8234
+ const resolution = catalog.resolve(written);
8235
+ if (!resolution) {
8236
+ const native = ownValue(scheme.nativeValues ?? {}, normalizeModelName(written));
8237
+ return native !== void 0 ? { value: native } : { value: written, issue: "unknown-model" };
8238
+ }
8239
+ if (resolution.kind === "inherit") {
8240
+ return scheme.writesInherit ? { value: INHERIT_MODEL } : {};
8241
+ }
8242
+ return mapProfileToTarget(resolution, target, scheme);
8243
+ }
8244
+ var FLOATING_MODEL_ALIASES, INHERIT_MODEL, MODEL_STATUSES, defaultCatalog, catalogCache, DATE_PATTERN, CLAUDE_MODEL_SCHEME, MODEL_TARGET_SCHEMES, UNSAFE_MODEL_NAME;
8245
+ var init_model_catalog = __esm({
8246
+ "packages/core/src/model-catalog.ts"() {
8247
+ "use strict";
8248
+ init_model_profiles();
8249
+ FLOATING_MODEL_ALIASES = {
8250
+ opus: "claude-opus",
8251
+ sonnet: "claude-sonnet",
8252
+ haiku: "claude-haiku",
8253
+ fable: "claude-fable"
8254
+ };
8255
+ INHERIT_MODEL = "inherit";
8256
+ MODEL_STATUSES = ["current", "legacy", "deprecated", "retired"];
8257
+ catalogCache = /* @__PURE__ */ new WeakMap();
8258
+ DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
8259
+ CLAUDE_MODEL_SCHEME = {
8260
+ providers: ["anthropic"],
8261
+ naming: "apiId",
8262
+ keepsFloatingAliases: true,
8263
+ writesInherit: true
8264
+ };
8265
+ MODEL_TARGET_SCHEMES = {
8266
+ claude: CLAUDE_MODEL_SCHEME,
8267
+ // Grok delegates agent files to the Claude formatter.
8268
+ grok: CLAUDE_MODEL_SCHEME,
8269
+ github: {
8270
+ naming: "displayName",
8271
+ keepsFloatingAliases: false,
8272
+ writesInherit: false,
8273
+ nativeValues: { auto: "Auto" }
8274
+ },
8275
+ factory: {
8276
+ naming: "apiId",
8277
+ keepsFloatingAliases: false,
8278
+ writesInherit: true
8279
+ },
8280
+ codex: {
8281
+ providers: ["openai"],
8282
+ naming: "apiId",
8283
+ keepsFloatingAliases: false,
8284
+ writesInherit: false
8285
+ },
8286
+ // Cursor has its own model ids and documents them without snapshot dates.
8287
+ cursor: {
8288
+ naming: "id",
8289
+ keepsFloatingAliases: false,
8290
+ writesInherit: true
8291
+ }
8292
+ };
8293
+ UNSAFE_MODEL_NAME = /[\p{Cc}\u2028\u2029]/u;
8294
+ }
8295
+ });
8296
+
8297
+ // packages/core/src/model-drift.ts
8298
+ var init_model_drift = __esm({
8299
+ "packages/core/src/model-drift.ts"() {
8300
+ "use strict";
8301
+ init_model_catalog();
8302
+ }
8303
+ });
8304
+
7652
8305
  // packages/core/src/git-timeout.ts
7653
8306
  function isGitTimeoutError(error) {
7654
8307
  const errorWithCode = error;
@@ -7727,7 +8380,7 @@ function normalizeManagedPaths(output) {
7727
8380
  ...files.length > 0 ? { files } : {}
7728
8381
  };
7729
8382
  }
7730
- function isRecord4(value) {
8383
+ function isRecord5(value) {
7731
8384
  return typeof value === "object" && value !== null;
7732
8385
  }
7733
8386
  function valuesEqual(left, right) {
@@ -7739,7 +8392,7 @@ function valuesEqual(left, right) {
7739
8392
  }
7740
8393
  return left.every((value, index) => valuesEqual(value, right[index]));
7741
8394
  }
7742
- if (!isRecord4(left) || !isRecord4(right)) return false;
8395
+ if (!isRecord5(left) || !isRecord5(right)) return false;
7743
8396
  const leftKeys = Object.keys(left).sort();
7744
8397
  const rightKeys = Object.keys(right).sort();
7745
8398
  if (leftKeys.length !== rightKeys.length) return false;
@@ -7985,6 +8638,9 @@ var init_src = __esm({
7985
8638
  init_target_catalog();
7986
8639
  init_target_capabilities();
7987
8640
  init_agent_capabilities();
8641
+ init_model_catalog();
8642
+ init_model_drift();
8643
+ init_model_profiles();
7988
8644
  init_hook_capabilities();
7989
8645
  init_git_timeout();
7990
8646
  init_structured_output();
@@ -8044,7 +8700,7 @@ var PROMPTSCRIPT_SKILL_CONTENT;
8044
8700
  var init_promptscript_skill = __esm({
8045
8701
  "packages/cli/src/generated/promptscript-skill.ts"() {
8046
8702
  "use strict";
8047
- PROMPTSCRIPT_SKILL_CONTENT = '---\nname: promptscript\ndescription: >-\n PromptScript language expert for reading, writing, modifying, and\n troubleshooting .prs files. Use when working with PromptScript syntax,\n creating or editing .prs files, adding blocks like @identity, @standards,\n @restrictions, @shortcuts, @skills, or @agents, configuring\n promptscript.yaml, resolving compilation errors, understanding inheritance\n (@inherit), composition (@use, @extend, @override), contextual @header\n metadata, or migrating AI instructions\n to PromptScript. Also use when asked about the 50 built-in compilation\n targets, including GitHub Copilot, Claude Code, Cursor, Antigravity,\n Factory AI, and AGENTS.md-based platforms.\nlicense: MIT\nmetadata:\n author: PromptScript\n homepage: https://getpromptscript.dev\ncompatibility:\n - claude-code\n - github-copilot\n - cursor\n - factory-ai\n - gemini-cli\n - opencode\n - windsurf\n - cline\n - roo\n - codex\n - continue\n - augment\n - goose\n - kilo\n - amp\n - trae\n - junie\n - kiro-cli\nallowed-tools:\n - Read\n - Write\n - Glob\n - Grep\n - Bash\nuser-invocable: true\n---\n\n# PromptScript Language Guide\n\nPromptScript is a domain-specific language that compiles `.prs` files into native instruction formats for AI coding assistants (GitHub Copilot, Claude Code, Cursor, Antigravity, Factory AI, OpenCode, Gemini CLI). One source of truth, multiple outputs.\n\n## File Structure\n\nA `.prs` file contains ordered declarations. Syntax `1.5.0` applies `@inherit`,\n`@use`, local blocks, `@extend`, and `@override` in source order. Put `@meta`\nfirst.\n\n```\n# Comments start with #\n\n@meta { ... } # Required metadata\n@inherit @path # Single inheritance (optional)\n@use @path [as alias] # Imports/mixins (optional, multiple)\n\n@identity { ... } # AI persona\n@context { ... } # Project context\n@standards { ... } # Coding conventions\n@restrictions { ... } # Hard rules\n@shortcuts { ... } # Command aliases\n@knowledge { ... } # Reference documentation\n@skills { ... } # Reusable skill definitions\n@agents { ... } # Subagent definitions\n@workflows { ... } # Repeatable agent procedures\n@examples { ... } # Few-shot input/output examples (syntax 1.2.0+)\n@params { ... } # Template parameters\n@guards { ... } # File globs and priorities\n@hooks { ... } # Portable lifecycle hooks (syntax 1.4.0+)\n@mcpServers { ... } # MCP server configurations (syntax 1.4.0+)\n@plugins { ... } # Capability bundles (syntax 1.4.0+)\n@local { ... } # Private config (not committed)\n@extend path { ... } # Modify imported blocks\n@override path { ... } # Replace one complete existing target (syntax 1.5.0+)\n@custom-name { ... } # Arbitrary named blocks\n```\n\nContextual `@header` entries appear inside supported owner blocks, not at the\ntop level.\n\n## Content Types\n\nPromptScript has four canonical content shapes inside blocks:\n\n### Text Content\n\nUse triple quotes (three double-quote characters) to wrap multiline text.\nText is automatically dedented - leading whitespace from source indentation is stripped.\nUse for prose, markdown, or freeform content.\n\nExample: `@identity` with a text block describing an AI persona starting with "You are..."\n\n### Object Content (key-value pairs)\n\n```\n@context {\n project: "My App"\n team: "Frontend"\n monorepo: {\n tool: "Nx"\n packageManager: "pnpm"\n }\n}\n```\n\nValues can be strings (quoted or unquoted), numbers, booleans, nested objects, or arrays.\n\n### Array Content\n\n```\n@standards {\n code: [\n "Use strict TypeScript",\n "Named exports only"\n ]\n}\n\n@restrictions {\n - "Never use any type"\n - "Never commit secrets"\n}\n```\n\n### Mixed Content\n\nBlocks can contain both object properties and text in the same block.\nPlace the triple-quoted text block alongside key-value pairs.\n\n## Block Reference\n\n### @meta (required)\n\n```\n@meta {\n id: "project-id" # Required: unique identifier\n syntax: "1.0.0" # Required: syntax version (semver)\n org: "Company Name" # Optional\n team: "Frontend" # Optional\n tags: [react, ts] # Optional\n params: { # Optional: template parameters\n projectName: string\n port: number = 3000\n debug?: boolean\n framework: enum("react", "vue") = "react"\n }\n}\n```\n\n### @identity\n\nDefines AI persona. Start with "You are..." for consistent output across all formatters.\nContains a triple-quoted text block with the persona description.\n\n### @context\n\nProject context with structured properties (project, team, languages, runtime)\nplus optional triple-quoted text for architecture details, diagrams, etc.\n\n### @standards\n\nCategory-based conventions. Any category name is valid:\n\n```\n@standards {\n typescript: ["Strict mode", "No any type"]\n naming: ["Files: kebab-case.ts", "Classes: PascalCase"]\n git: {\n format: "Conventional Commits"\n types: [feat, fix, docs, refactor, test, chore]\n }\n}\n```\n\nCategory names are arbitrary. `@standards` can also contain free-form text:\n\n```\n@standards {\n """\n ## Formatting\n Preserve heading structure and use four-space indentation.\n\n ## Testing\n Add regression coverage for every behavior change.\n """\n typescript: ["Strict mode", "Named exports only"]\n git: {\n format: "Conventional Commits"\n }\n}\n```\n\nFree-form text is dedented and rendered with its Markdown heading structure. Factory\nmonolith output nests it under `Conventions & Patterns`; split Factory rules adjust\nheading levels relative to the generated section. Custom structured categories remain\navailable to formatters that support them.\n\n### @restrictions\n\nHard rules as a list of dash-prefixed strings:\n\n```\n@restrictions {\n - "Never expose API keys"\n - "Never commit secrets to version control"\n - "Always validate user input"\n}\n```\n\n### @shortcuts\n\nSimple strings appear as documentation. Objects with `prompt: true` generate\nexecutable prompt/command files for GitHub Copilot and Cursor:\n\n```\n@shortcuts {\n "/review": "Review code for quality"\n "/test": {\n prompt: true\n description: "Write unit tests"\n content: (triple-quoted text with instructions)\n }\n}\n```\n\n> `@commands` is a backwards-compatible alias for `@shortcuts` \u2014 prefer `@shortcuts` in new files.\n\n### @skills\n\nReusable skill definitions with metadata:\n\n```\n@skills {\n commit: {\n description: "Create git commits"\n trigger: "commit, git commit"\n disableModelInvocation: true\n userInvocable: true\n allowedTools: ["Bash", "Read"]\n content: (triple-quoted text with skill instructions)\n }\n}\n```\n\nProperties: description (required), content (required), trigger, disableModelInvocation,\nuserInvocable, allowedTools, context ("fork" or "inherit"), agent, requires, references, inputs, outputs.\n\nThe `references` property attaches external files to the skill\'s context:\n\n```\n@skills {\n architecture-review: {\n description: "Review architecture decisions"\n references: [\n ./references/architecture.md\n ./references/modules.md\n ]\n content: (triple-quoted text)\n }\n}\n```\n\nAllowed file types: `.md`, `.json`, `.yaml`, `.yml`, `.txt`, `.csv`. Paths are resolved relative\nto the `.prs` file. Formatters emit referenced files alongside SKILL.md in the output directory.\n\n### Parameterized Skills\n\nSkills in `.promptscript/skills/<name>/SKILL.md` support template parameters via\nYAML frontmatter. Define `params` in frontmatter and use `{{variable}}` in content:\n\n```yaml\n---\nname: review\ndescription: \'Review {{language}} code for {{standard}}\'\nparams:\n language:\n type: string\n standard:\n type: string\n default: \'best practices\'\nreferences:\n - references/architecture.md\n---\nReview the code using {{language}} conventions following {{standard}}.\n```\n\nThe `references` field in SKILL.md frontmatter lists files to attach to the skill\'s context.\nPaths are relative to the SKILL.md file.\n\nImported SKILL.md frontmatter is bounded: 256 KiB per document, 10,000 YAML nodes, 32 nesting\nlevels, 2,000 entries per mapping or sequence, and 64 KiB per string value. Documents over any\nlimit are rejected before their YAML values are converted.\n\nPass values in `@skills` block:\n\n```\n@skills {\n review: {\n description: "Review code"\n language: "typescript"\n standard: "strict mode"\n }\n}\n```\n\nNon-reserved properties (anything other than description, content, trigger,\nuserInvocable, allowedTools, disableModelInvocation, context, agent, requires,\ninputs, outputs) are treated as skill parameter arguments.\n\n### Skill Dependencies\n\nSkills can declare dependencies on other skills via `requires`:\n\n```\n@skills {\n deploy: {\n description: "Deploy service"\n requires: ["lint-check", "test-suite"]\n content: (triple-quoted text)\n }\n}\n```\n\nThe validator (PS016) checks that required skills exist, detects self-references,\nand catches circular dependency chains.\n\n### Skill Contracts (Inputs/Outputs)\n\nSkills can declare typed inputs and outputs in SKILL.md frontmatter:\n\n```yaml\n---\nname: security-scan\ndescription: \'Scan for vulnerabilities\'\ninputs:\n files:\n description: \'Files to scan\'\n type: string\n severity:\n description: \'Minimum severity\'\n type: enum\n options: [low, medium, high]\n default: medium\noutputs:\n report:\n description: \'Scan report\'\n type: string\n passed:\n description: \'Whether scan passed\'\n type: boolean\n---\n```\n\nField types: `string`, `number`, `boolean`, `enum` (with `options` list).\nThe validator (PS017) checks field types, ensures enum fields have options,\nand warns if param names collide with input names.\n\n### Shared Resources\n\nSkills in a folder can share common resources via `.promptscript/shared/`:\n\n```\n.promptscript/\n shared/\n templates.md # Shared across all skills\n style-guide.md\n skills/\n review/\n SKILL.md # Gets @shared/templates.md, @shared/style-guide.md\n deploy/\n SKILL.md # Also gets shared resources\n```\n\nFiles in `shared/` are automatically included in every skill with `@shared/` prefix.\n\n### @agents\n\nCustom subagent definitions. Compiles to `.claude/agents/` for Claude Code,\n`.github/agents/` for GitHub Copilot, `.factory/droids/` for Factory AI, etc.\n\n```\n@agents {\n code-reviewer: {\n description: "Reviews code quality"\n tools: ["Read", "Grep", "Glob", "Bash"]\n model: "sonnet"\n permissionMode: "default"\n content: (triple-quoted text with agent instructions)\n }\n}\n```\n\nImported agent definitions are qualified by an aliased `@use`:\n\n```\n@use ./frontend-team as frontend\n@use ./backend-team as backend\n```\n\nIf both imports define `reviewer`, the resolved names are `frontend.reviewer` and\n`backend.reviewer`. Unique unaliased imports keep their original names. Conflicting unaliased\ndefinitions stop compilation with source and import diagnostics instead of silently overwriting\none another. Native targets map dots to hyphens, so `frontend.reviewer` becomes\n`frontend-reviewer`.\n\nSupports mixed models per agent: `specModel` sets a different model for\nSpecification/planning mode (GitHub, Factory), `specReasoningEffort` sets reasoning\neffort for the spec model (Factory only, values: "low", "medium", "high").\n\nFactory AI droids support additional properties: `model` (any model ID or "inherit"),\n`reasoningEffort` ("low", "medium", "high"), and `tools` (category name like "read-only"\nor array of tool IDs).\n\n### @workflows\n\nRepeatable multi-step agent procedures. Requires syntax `1.1.0`.\n\n```\n@workflows {\n release: {\n description: "Prepare a validated release"\n content: """\n 1. Run formatting, linting, type checks, and tests.\n 2. Validate compiled output.\n 3. Stop before publishing and request approval.\n """\n }\n}\n```\n\nTargets with native workflow discovery emit dedicated workflow files. Other targets\nretain workflow instructions in their main output when supported.\n\n### @examples\n\nStructured few-shot examples for AI assistants (requires syntax `1.2.0`):\n\n```\n@meta {\n id: "commit-style"\n syntax: "1.2.0"\n}\n\n@examples {\n feat-commit: {\n description: "Feature commit with scope"\n input: "Added user authentication with JWT tokens"\n output: "feat(auth): add JWT-based user authentication"\n }\n}\n```\n\nEach entry is a named example with `input` and `output` (both required),\nplus optional `description`. Multi-line content uses triple-quoted strings.\n\nExamples can also be attached to skills via the `examples` property:\n\n```\n@skills {\n commit: {\n description: "Create conventional commits"\n examples: {\n basic: {\n input: "Added dark mode toggle"\n output: "feat(settings): add dark mode toggle"\n }\n }\n content: (triple-quoted text)\n }\n}\n```\n\n### @knowledge\n\nReference documentation as triple-quoted text. Used for command references,\nAPI docs, and other material that should appear in the output.\n\n### @params\n\nTemplate parameter definitions with types: string, number, boolean, enum("a", "b").\nOptional parameters use `?` suffix. Defaults use `= value`.\n\n### @guards\n\nFile glob patterns and priority rules for path-specific instructions.\n\n### @hooks\n\nPortable lifecycle hooks. Requires syntax `1.4.0`. Each hook needs exactly one of\n`command` or `script`.\n\n```\n@hooks {\n validate-types: {\n event: "post-tool-use"\n matcher: "Edit|Write"\n script: {\n path: ".promptscript/scripts/validate.py"\n interpreter: "python3"\n args: ["--strict"]\n }\n cwd: "project"\n timeoutMs: 120000\n statusMessage: "Checking TypeScript"\n continueOnFailure: false\n enabled: true\n targets: {\n factory: { matcher: "Execute" }\n vscode: { matcher: "run_in_terminal" }\n github: { enabled: false }\n }\n }\n}\n```\n\nPortable events:\n\n| Event | Meaning |\n| ---------------------- | ------------------------- |\n| `pre-terminal-command` | Before a terminal command |\n| `pre-tool-use` | Before a tool invocation |\n| `post-tool-use` | After a tool invocation |\n| `session-start` | Agent session start |\n| `setup` | Session setup |\n| `subagent-start` | Subagent start |\n| `notification` | Agent notification |\n| `stop` | Agent stop |\n\n`command` is a non-empty string array. Shell interpolation (`$()`, backticks,\n`${...}`) is forbidden. `script` requires:\n\n- `path` under `.promptscript/scripts/`, using forward slashes.\n- Existing regular file at compile time.\n- No traversal, absolute path, invalid segment, or symlink escape.\n- Explicit interpreter: `python3`, `python`, `node`, `deno`, `bun`, `ruby`, `php`,\n `perl`, `bash`, `sh`, `zsh`, `pwsh`, or `powershell`.\n- Optional `args` string array; each argument remains one argument.\n\n`cwd: "project"` runs from project root. Other values are portable forward-slash\npaths relative to project root. Hook config file location does not set command cwd.\nEnvironment-root and Git-root wrappers exit before script or command execution when\nthe required root is unavailable. Native-cwd and workspace-cwd targets retain host\ncwd fields and report `PS4002` when PromptScript cannot verify that cwd.\n`timeoutMs` range is 100-600000. `matcher` uses target-native tool names, so a\nmatcher valid for one target may match nothing on another.\n\n`pre-terminal-command` supplies native defaults: Factory `Execute`, Claude and\nCodex `Bash`, Windsurf `pre_run_command`, Cursor `run_terminal_cmd`, Gemini\n`run_shell_command`, and VS Code `run_in_terminal`. Override a native tool name\nwith `targets.<name>.matcher`. Cursor, Gemini, and VS Code report best-effort\n`PS4002` warnings. GitHub and Grok omit the event with `PS4002`.\n\nTarget overrides may change `event`, `matcher`, `timeoutMs`, `statusMessage`,\n`continueOnFailure`, `enabled`, or `cwd`. Native hook files are emitted only in\ntarget modes that support additional files:\n\n| Target | Hook output | Mode |\n| -------------- | ---------------------------------------------------------------------- | ------------------- |\n| Claude Code | `.claude/settings.json` | `full` |\n| Factory AI | `.factory/hooks.json` | `multifile`, `full` |\n| GitHub Copilot | `.github/hooks/promptscript.json` | `multifile`, `full` |\n| Cursor | `.cursor/hooks.json` | `full` |\n| Codex | `.codex/hooks.json` | `multifile`, `full` |\n| Gemini CLI | `.gemini/settings.json` | `multifile`, `full` |\n| Windsurf | `.windsurf/hooks.json` | `multifile`, `full` |\n| Grok Build | `.grok/hooks/promptscript.json` | `full` |\n| OpenCode | `.opencode/plugins/promptscript.ts` (generated plugin) | `multifile`, `full` |\n| VS Code Agent | `.github/hooks/promptscript-vscode.json` when `vscode` override exists | target-specific |\n\nOpenCode starts generated hook commands asynchronously. It enforces authored\ntimeouts or a 30-second default, then escalates from `SIGTERM` to `SIGKILL`.\nPayloads are bounded by UTF-8 byte length. OpenCode tool hooks expose tool\narguments, session ID, and call ID, but no model or agent context; compilation\nreports that limitation with `PS4002`.\n\nSimple mode and targets without native project hooks report `PS4002` instead of\nsilently dropping hooks. Use `prs compile --watch` as fallback. Plugin-only and\nagent-scoped integrations are not emitted as universal project hooks.\n\nEach generated command carries a PromptScript ownership marker. CLI cleanup removes\nonly marked entries and preserves user hooks/settings. Removing `@hooks` removes a\nfully owned generated hook file and prunes directories left empty. `prs hooks install factory`\nmigrates unambiguous legacy hooks from `.factory/settings.json`; ambiguous\nentries remain for manual review.\n\nFactory compilation performs the same migration when `.factory/hooks.json` is\nabsent. Use `prs compile --dry-run` to preview the changes or\n`--no-migrate-factory-hooks` to keep warning-only behavior. Unknown events,\nmalformed entries, and mixed ownership abort without a partial migration.\n\n`@hooks` compilation is separate from `prs hooks install`. The latter installs\nauto-compilation and generated-output protection for supported AI tools. Copilot VS\nCode Agent hooks use `promptscript-vscode.json`; GitHub Copilot repository hooks use\n`promptscript.json`.\n\n### @mcpServers\n\nProject-local Model Context Protocol servers. Requires syntax `1.4.0`.\n\n```\n@mcpServers {\n issue-tracker: {\n transport: "stdio"\n command: ["node", "./tools/issues.mjs"]\n env: { LOG_LEVEL: "info" }\n }\n}\n```\n\nUse `stdio` with `command`, or `http`/`sse` with `url`. Keep credentials out of\n`.prs` files and provide them through target-native secret management.\n\n### @plugins\n\nPortable capability bundles. Requires syntax `1.4.0`.\n\n```\n@plugins {\n security-suite: {\n description: "Security review tooling"\n version: "1.0.0"\n skills: ["security-review"]\n hooks: ["validate-types"]\n mcpServers: ["issue-tracker"]\n }\n}\n```\n\n### @local\n\nPrivate local configuration. Not included in compiled output or committed to git.\n\n## Inheritance and Composition\n\n### @inherit (single, linear)\n\nOne per file. Child blocks merge on top of parent:\n\n```\n@inherit @company/frontend-team\n@inherit ./parent\n@inherit @stacks/react-app(projectName: "my-app", port: 3000)\n```\n\n### @use (multiple, mixins)\n\nImport and merge fragments:\n\n```\n@use @core/security\n@use @core/quality\n@use ./local-config\n@use @core/typescript as ts # alias enables @extend access\n```\n\n#### URL imports (Go-module style)\n\nImport directly from any Git repository by host path - no alias required:\n\n```\n@use github.com/acme/shared-standards/@fragments/security\n@use gitlab.com/myorg/prompts/@stacks/python\n```\n\nVersion pinning with `@`:\n\n```\n@use github.com/acme/shared-standards/@org/base@1.2.0 # exact version\n@use github.com/acme/shared-standards/@org/base@^1.0.0 # semver range\n@use github.com/acme/shared-standards/@org/base@main # branch\n```\n\n#### Registry aliases\n\nShort names for Git repository URLs, configured in `promptscript.yaml`:\n\n```yaml\nregistries:\n company:\n url: github.com/acme/promptscript-registry\n```\n\nThen use the alias as scope prefix:\n\n```\n@use @company/security\n@inherit @company/base-config\n```\n\nMerge rules:\n\n- Text: concatenated with deduplication\n- Objects: deep merged (imported source wins same-shape conflicts)\n- Arrays: unique concatenation\n- Shape mismatch: existing target body wins\n\nUnder syntax `1.5.0`, later local blocks, `@extend`, and `@override`\noperations apply to the accumulated import result in declaration order.\n\n### Block Filtering\n\nControl which blocks are imported using the reserved `only` and `exclude` parameters:\n\n```\n@use ./shared-config(only: ["skills", "context"])\n@use ./shared-config(exclude: ["knowledge"])\n@use ./shared-config(exclude: ["knowledge"], mode: "strict")\n```\n\nRules:\n\n- `only` and `exclude` are mutually exclusive \u2014 using both is a validation error (PS021)\n- Values are block type names: `identity`, `context`, `standards`, `knowledge`, `skills`, `shortcuts`, `agents`, etc.\n- Block filtering does not apply to `@inherit` directives\n\n### Markdown Imports\n\nImport skills directly from `.md` files (v1.8+). No external tools needed:\n\n```\n@use ./skills/frontend-design.md\n@use ./shared/commit.md as commit\n@use github.com/anthropics/skills/commit@1.0.0\n@use github.com/repo/skills/gitnexus # directory \u2192 SKILL.md\n```\n\nContent detection: PromptScript blocks in `.md` are parsed as a `.prs` fragment;\nYAML frontmatter with `name`/`description` is loaded as a skill definition;\notherwise content is treated as free-form knowledge.\n\nCLI management:\n\n```\nprs skills add github.com/anthropics/skills/commit@1.0.0\nprs skills remove commit\nprs skills list\nprs skills update\n```\n\n### @extend (modify existing or imported blocks)\n\nUse a direct path for inherited or local blocks:\n\n```\n@extend standards.testing {\n coverage: 95\n}\n```\n\nUse an alias when targeting a specific imported block:\n\n```\n@use @core/typescript as ts\n\n@extend ts.standards {\n testing: { coverage: 95 }\n}\n```\n\n#### Replacing regular block fields\n\nSyntax `1.3.0` supports explicit replacement of complete regular block field values:\n\n```\n@meta { id: "project" syntax: "1.3.0" }\n\n@inherit ./company-base\n\n@extend standards {\n testing!: ["Use Vitest"]\n linting: ["Use ESLint"]\n}\n```\n\n`testing!` replaces the inherited value. Fields without `!` keep normal merge behavior.\nReplacement works after `@inherit` and `@use`, including aliases and nested target paths.\nA missing field is set. The modifier is rejected for `@skills`, which retain their dedicated\nmerge and sealing semantics.\n\n#### Replacing complete targets with @override\n\nSyntax `1.5.0` adds atomic replacement for an existing block or nested value:\n\n```\n@meta { id: "project" syntax: "1.5.0" }\n\n@standards {\n testing: ["Use Jest", "Use Mocha"]\n}\n\n@override standards.testing {\n ["Use Vitest"]\n}\n```\n\n`@override` requires the complete target path to exist, applies in declaration\norder, and cannot bypass sealed skill properties. Later `@extend` declarations\nmerge into the replacement. Use `@extend` for additive changes, `field!` for\ncompatibility replacement of one direct regular field, and `@override` for\nintentional complete replacement.\n\n#### Skill-aware @extend semantics\n\nWhen extending a skill definition via `@extend`, individual skill properties follow specific merge\nstrategies rather than the generic block merge rules:\n\n| Strategy | Properties |\n| ----------------- | ----------------------------------------------------------------------------------------------------------- |\n| **Replace** | content, description, trigger, userInvocable, allowedTools, disableModelInvocation, context, agent, license |\n| **Append** | references, examples, requires |\n| **Shallow merge** | params, inputs, outputs |\n\nExample \u2014 extending a base skill to add references and override content:\n\n```\n@use @company/skills as skills\n\n@extend skills.code-review {\n content: (triple-quoted text with overridden instructions)\n references: [\n ./extra-context.md\n ]\n}\n```\n\nThe `references` array from the base skill and the overlay are combined (append). The `content`\nfield from the overlay replaces the base (replace).\n\n#### Reference negation\n\nUse `!` prefix in `@extend` to remove entries from a lower layer\'s append-strategy arrays:\n\n```\n@extend skills.code-review {\n references: [\n "!references/deprecated.md"\n "references/replacement.md"\n ]\n}\n```\n\nPath matching is normalized (`"!./foo.md"` matches `"foo.md"`). Only works in `@extend` blocks\non `references` and `requires`. Validator PS028 warns about `!` in base definitions.\n\n#### Overlay consistency warnings\n\nThe resolver emits warnings during compile when an overlay drifts from its base. Always shown\n(not gated by `--verbose`):\n\n- **Orphaned extend** \u2014 `@extend target "X" not found \u2014 overlay will be ignored.` Triggered when\n the targeted block doesn\'t exist (base removed or renamed).\n- **Stale skill target** \u2014 `@extend creates new skill "X" \u2014 base does not define it.` Triggered\n when an `@extend` inside `@skills` would create a new skill instead of extending an existing one.\n- **Negation orphan** \u2014 `Negation "!path" did not match any base entry \u2014 it may be stale.`\n Triggered when a `!entry` in references/requires doesn\'t match anything in the base.\n\nThese come from the resolver, not the validator (PS0XX rules). They appear during `prs compile`,\nnot `prs validate`.\n\n#### Sealed properties\n\nPrevent `@extend` from overriding specified replace-strategy properties:\n\n```\n@skills {\n deploy: {\n content: (triple-quoted text with critical workflow)\n sealed: ["content", "description"]\n }\n}\n```\n\n`sealed: true` seals all replace-strategy properties. Attempting to override a sealed\nproperty is a hard compilation error. Only the base skill author can set `sealed` \u2014\noverlays cannot add or modify it. Append-strategy properties remain extendable.\nValidator PS029 warns about invalid entries in `sealed`.\n\n#### Skill composition (inline @use)\n\nImport sub-skills within a `@skills` block to compose multi-phase workflows:\n\n```\n@skills {\n ops: {\n description: "Production triage"\n content: (triple-quoted text with orchestrator instructions)\n }\n @use ./phases/health-scan\n @use ./phases/triage\n @use ./phases/code-fix as autofix\n}\n```\n\nEach `@use` resolves the referenced `.prs` file, extracts its skill definition and context\nblocks, and flattens them as numbered phase sections into the parent skill\'s content. The\n`as alias` form controls the phase display name. Validator PS027 checks composition validity.\n\n### Parameterized Inheritance (Template Variables)\n\nUse `{{variable}}` placeholders in a **parent/template** file, and pass values\nfrom the **child** file via `@inherit` or `@use` with `(key: value)` syntax.\n\n**IMPORTANT:** Variables are NOT set from `promptscript.yaml` or CLI. They are\npassed from one `.prs` file to another through `@inherit` or `@use`.\n\n**Step 1: Create the template** (parent file with `params` in `@meta`):\n\n```\n# base.prs - reusable template\n@meta {\n id: "service-template"\n syntax: "1.0.0"\n params: {\n serviceName: string\n port?: number = 3000\n }\n}\n\n@identity {\n """\n You are working on {{serviceName}} running on port {{port}}.\n """\n}\n```\n\n**Step 2: Inherit with values** (child file passes params):\n\n```\n# project.prs - concrete project\n@meta { id: "user-api" syntax: "1.0.0" }\n\n@inherit ./base(serviceName: "user-api", port: 8080)\n```\n\nAfter compilation, `{{serviceName}}` becomes `user-api` and `{{port}}` becomes `8080`.\n\nThe same works with `@use`:\n\n```\n@use ./base(serviceName: "auth-service") as auth\n```\n\n**Parameter types:** `string`, `number`, `boolean`, `enum("a", "b")`.\nOptional params use `?` suffix. Defaults use `= value`.\nMissing required params produce a compile error.\n\n**Multi-service pattern** - reuse one template across many projects:\n\n```\nservices/\n base.prs # template with params\n user-api/\n promptscript.yaml # source: project.prs\n project.prs # @inherit ../base(serviceName: "user-api")\n auth-service/\n promptscript.yaml\n project.prs # @inherit ../base(serviceName: "auth-service")\n```\n\n## Configuration: promptscript.yaml\n\n### Auto-injection\n\nThis skill is automatically included when compiling with `prs compile`. No manual copying needed.\nTo disable, set `includePromptScriptSkill: false` in your `promptscript.yaml`.\n\n```\nid: my-project\nsyntax: "1.1.0"\ndescription: "My project description"\ninput:\n entry: .promptscript/project.prs\n include: [\'.promptscript/**/*.prs\']\ntargets:\n github:\n version: full # simple | multifile | full\n claude:\n version: full\n cursor:\n version: standard\n antigravity:\n version: frontmatter\n factory:\n version: full\n windsurf: # 41 additional targets supported\n version: simple\n cline:\n version: simple\nregistry:\n git: https://github.com/org/registry.git\n ref: main\nregistries:\n company:\n url: github.com/acme/promptscript-registry\n oss:\n url: github.com/prscrpt/community-registry\n ref: v2\npolicies:\n - name: adjacent-layers-only\n kind: layer-boundary\n severity: error\n layers: [\'@core\', \'@team\', \'@project\']\n maxDistance: 1\n```\n\n### Lockfile: `promptscript.lock`\n\nWhen remote imports are used, run `prs lock` to generate or update the lockfile\nbefore compilation. It records the exact resolved commit for each dependency.\nIntegrity hashes (SHA-256) are included for registry references to detect\ntampering or drift. This enables reproducible builds across machines and CI.\nCommit `promptscript.lock` to version control.\n\nUse `--ignore-hashes` on `prs compile` or `prs validate` to skip integrity\nhash verification when needed.\n\n### Policy Engine\n\nDefine organizational policies in `promptscript.yaml` to validate skill extensions:\n\n```yaml\npolicies:\n - name: adjacent-layers-only\n kind: layer-boundary\n description: \'Only adjacent layers can extend each other\'\n severity: error\n layers: [\'@core\', \'@team\', \'@project\']\n maxDistance: 1\n\n - name: protect-content\n kind: property-protection\n description: \'Content override requires explicit approval\'\n severity: warning\n properties: [\'content\', \'description\']\n\n - name: approved-registries\n kind: registry-allowlist\n description: \'Extensions must come from approved registries\'\n severity: error\n allowed: [\'@core\', \'@team\']\n```\n\nPolicy kinds: `layer-boundary` (controls layer distance), `property-protection`\n(prevents overriding specific properties), `registry-allowlist` (restricts extension sources).\nSeverity: `error` (fails validation) or `warning` (reported only).\nSkip with `--skip-policies` during development (never in CI).\n\n## Syntax Version Validation\n\nThe `syntax` field in `@meta` declares the PromptScript language version (semver).\n\n### Known Versions\n\n| Version | What it adds |\n| ------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `1.0.0` | Core blocks (identity, context, standards, restrictions, knowledge, shortcuts, commands, guards, params, skills, local) |\n| `1.1.0` | Adds `@agents` and `@workflows`; reserves internal `@prompts` |\n| `1.2.0` | Adds `@examples` (few-shot input/output pairs) |\n| `1.3.0` | Adds explicit regular block field replacement in `@extend` |\n| `1.4.0` | Adds `@hooks`, `@mcpServers`, and `@plugins` |\n| `1.5.0` | Adds `@header` section titles, `@override` replacement, and unquoted `${VAR}` values |\n\n### Block Version Requirements\n\n| Block | Minimum Syntax Version |\n| ------------- | ---------------------- |\n| `@agents` | `1.1.0` |\n| `@workflows` | `1.1.0` |\n| `@examples` | `1.2.0` |\n| `@hooks` | `1.4.0` |\n| `@mcpServers` | `1.4.0` |\n| `@plugins` | `1.4.0` |\n\nAll other built-in blocks are available from `1.0.0`.\nRegular block field replacement with `field!: value` requires syntax `1.3.0`.\nGenerated section title overrides with `@header` require syntax `1.5.0`.\nAtomic replacement with `@override` requires syntax `1.5.0`.\nUnquoted `${VAR}` references as values require syntax `1.5.0`.\n\n### Generated Section Headers\n\nUse `@header` inside a registered owner block to rename human-readable output\nsections without changing filenames, frontmatter, XML tags, or structured keys:\n\n```promptscript\n@meta { id: "localized" syntax: "1.5.0" }\n\n@standards {\n @header "Coding Rules"\n @header git-commits "Commit Rules"\n code: ["Use strict TypeScript"]\n}\n```\n\n- `@header "Title"` targets the block\'s primary section.\n- `@header <section-key> "Title"` targets an owned derived section.\n- Titles must be non-empty, single-line strings.\n- Source overrides take precedence over formatter configuration and target defaults.\n- Child inheritance, imported source, and the latest root extension take precedence.\n- An initial `## Heading` in a registered text-only primary owner is a syntax\n `1.5.0` compatibility fallback. Explicit `@header` metadata wins.\n- Ordinary `header` and `headers` fields remain domain data.\n\n### Validation Rules\n\n- **PS018 (`syntax-version-compat`)**: warns when resolved blocks or syntax features require a higher version than declared. Requirements from inheritance, imports, and skill composition are included. Suggestion: run `prs validate --fix`.\n- **PS019 (`unknown-block-name`)**: warns when a block name is not a known PromptScript type, with fuzzy-match suggestions for typos.\n- **PS037 (`valid-section-headers`)**: rejects invalid titles, unknown or unowned section keys, duplicate overrides, and nested extension overrides.\n- **PS038 (`valid-block-shape`)**: rejects unsupported built-in block shapes and warns about formatter-sensitive legacy shapes or multiline shortcut scalars.\n- **PS039 (`agent-namespaces`)**: validates qualified agent name segments and checks them against recorded import provenance.\n- **PS040 (`import-excludes`)**: errors when a `validation.excludes` entry for an import does not record the commit pinned in promptscript.lock, so consumers re-review imports whose pinned commit changed.\n- **PS021 (`use-block-filter`)**: errors when `only` and `exclude` are both specified in `@use` parameters.\n- **PS025 (`valid-skill-references`)**: errors when a `references` entry points to a file with a disallowed extension or a path that cannot be resolved.\n- **PS026 (`safe-reference-content`)**: warns when a referenced file contains potentially sensitive content (e.g., secrets, credentials).\n- **PS027 (`valid-skill-composition`)**: warns about conflicting phase names or excessive phases in composed skills.\n- **PS028 (`valid-append-negation`)**: warns when negation prefix `!` appears in base skill definitions (only effective in `@extend`).\n- **PS029 (`valid-sealed-property`)**: warns when `sealed` contains non-replace-strategy property names.\n- **PS030 (`policy-compliance`)**: validates skill extensions against organizational policies defined in `promptscript.yaml`.\n- **PS034 (`valid-hooks`)**: validates portable hook events, commands/scripts, paths, interpreters, timeouts, cwd, and target overrides.\n\nTarget formatters report **PS4002** when a hook event or field has no native equivalent,\nwhen a target cannot guarantee project-root execution, or when output mode cannot emit\nthe additional hook file.\n\n### Fixing Syntax Versions\n\n```\nprs validate --fix # Auto-fix syntax versions in .prs files\nprs upgrade # Upgrade all .prs files to the latest version\n```\n\n`--fix` rewrites the `syntax: "..."` line in each file\'s `@meta` block to match the minimum version required by resolved blocks and syntax features. It follows inheritance, imports, and skill composition. It only upgrades, never downgrades.\n\n`prs upgrade` upgrades all files to the latest known syntax version regardless of what blocks they use.\n\n## CLI Commands\n\n```\nprs init # Initialize project (auto-detects existing files)\nprs init --yes --targets claude factory\nprs init --dry-run # Preview initialization\nprs init --auto-import # Initialize + static import of existing files\nprs migrate # Interactive migration flow\nprs migrate --static # Non-interactive static import\nprs migrate --llm # Generate AI-assisted migration prompt\nprs migrate --static --dry-run\nprs compile # Compile to all targets\nprs compile --watch # Watch mode\nprs compile --ignore-hashes # Skip integrity hash verification\nprs build <name> # Compile a named build profile\nprs validate --strict # Validate syntax\nprs validate --fix # Auto-fix syntax version declarations\nprs validate --skip-policies # Skip policy engine evaluation\nprs upgrade # Upgrade all .prs files to latest syntax version\nprs import CLAUDE.md # Import existing AI instructions\nprs import CLAUDE.md --dry-run # Preview import conversion\nprs inspect <skill> # Show skill composition provenance\nprs inspect <skill> --layers # Show layer-level breakdown\nprs explain <path> # Explain source and composition provenance\nprs explain <path> --format json # Machine-readable provenance history\nprs hooks install # Install auto-compilation hooks for AI tools\nprs hooks install claude # Install hooks for a specific tool\nprs hooks uninstall # Remove installed auto-compilation hooks\nprs hooks uninstall claude # Remove hooks for a specific tool\nprs skills add <source> # Add a remote skill (@use + lock update + SKILL.md validation)\nprs skills add <source> --strict # Treat validation warnings as errors\nprs skills add <source> --skip-validation # Bypass Agent Skills spec checks (not recommended)\nprs skills remove <name> # Remove a skill (@use line + lock entry)\nprs skills list # List all imported skills\nprs skills update # Re-resolve markdown-imported skills (re-validates + re-hashes)\nprs pull # Update registry\nprs diff --target claude # Show compilation diff\nprs diff --all --format json # Machine-readable diff report for CI\nprs diff --format json --include-content # Include generated content in the report\nprs lock # Generate/update promptscript.lock\nprs lock --dry-run # Preview lockfile changes\nprs update # Re-resolve all remote imports to latest\nprs update <url> # Update a specific registry\nprs vendor sync # Copy cached deps to .promptscript/vendor/\nprs vendor check # Verify vendor matches lockfile\nprs resolve @alias/path # Debug: show how an import resolves\nprs registry list # Show configured registries and aliases\nprs registry add <alias> <url> # Add a registry alias\n```\n\n`prs init --yes` requires explicit, detected, or user-configured targets. It does not invent\ndefault tools. For existing projects, `prs migrate` preserves `promptscript.yaml`, isolates static\noutput under `.promptscript/migrated/`, leaves source instructions untouched, and performs no\nwrites when no candidates are detected.\n\n## Output Targets\n\n50 supported targets. Key examples:\n\n| Target | Main File | Skills |\n| ----------- | ------------------------------- | -------------------------------------------------- |\n| GitHub | .github/copilot-instructions.md | .github/skills/\\*/SKILL.md |\n| Claude | CLAUDE.md | .claude/skills/\\*/SKILL.md |\n| Cursor | .cursor/rules/project.mdc | .agents/skills/\\*/SKILL.md |\n| Antigravity | .agent/rules/project.md | - |\n| Factory | AGENTS.md | .factory/skills/\\*/SKILL.md, .factory/droids/\\*.md |\n| OpenCode | OPENCODE.md | .opencode/skills/\\*/SKILL.md |\n| Gemini | GEMINI.md | .agents/skills/\\*/skill.md |\n| Windsurf | .windsurf/rules/project.md | .windsurf/skills/\\*/SKILL.md |\n| Cline | .clinerules | - |\n| Roo Code | .roorules | - |\n| Codex | AGENTS.md | .agents/skills/\\*/SKILL.md |\n| Continue | .continue/rules/project.md | - |\n| Hermes | AGENTS.md | - |\n| + 37 more | | See full list in documentation |\n\nTargets that share an output path (for example Factory, Codex, and every AGENTS.md target) are\nreconciled in one output plan before anything is written. Identical content merges silently;\ndiffering content reports `PS4001`, where a formatter output replaces an earlier one and a\nresource or the auto-injected PromptScript skill keeps the file already planned. Paths are compared\ncase-insensitively and NFC-normalized on every platform.\n\n### Formatter Documentation\n\nFor detailed information about each formatter\'s output paths, supported features, quirks, and example outputs:\n\n- **Full formatter reference:** `docs/reference/formatters/` (7 dedicated pages + index of all 50)\n- **llms-full.txt:** Available at the docs site root - contains all documentation in a single file for LLM consumption\n- **Dedicated pages exist for:** Claude Code, GitHub Copilot, Cursor, Antigravity, Factory AI, Gemini CLI, OpenCode\n- **All 50 formatters indexed at:** `docs/reference/formatters/index.md` with output paths, tier, and feature flags\n\n### Auto-Compilation Hooks\n\nInstead of running `prs compile --watch` manually, install hooks so your AI tool\ntriggers compilation automatically when you edit `.prs` files:\n\n```\nprs hooks install # Auto-detect and install for all detected tools\nprs hooks install claude # Install for a specific tool\n```\n\nHooks also protect generated files from direct edits \u2014 when an AI agent tries\nto edit a compiled output (e.g., CLAUDE.md), the write is blocked with a message\npointing to the source `.prs` file. Supported tools: Claude Code, Factory AI,\nCursor, Windsurf, Cline, GitHub Copilot, Gemini CLI.\n\n## Project Organization\n\nTypical modular structure:\n\n```\n.promptscript/\n project.prs # Entry: @meta, @inherit, @use, @identity, @agents\n context.prs # @context (architecture, tech stack)\n standards.prs # @standards (coding conventions)\n restrictions.prs # @restrictions (hard rules)\n commands.prs # @shortcuts and @knowledge\n```\n\nThe entry file uses `@use ./context`, `@use ./standards`, etc. to compose them.\n\n## Common Mistakes\n\n1. Missing @meta block - every .prs file needs `@meta` with `id` and `syntax`\n2. Multiple @inherit - only one per file; use `@use` for additional imports\n3. Extending an unknown path - target an inherited or local block, or use an imported alias\n4. Unquoted strings with special chars - quote strings containing `:`, `#`, `{`, `}`\n5. Forgetting to compile - `.prs` changes need `prs compile` to take effect\n6. Triple quotes inside triple quotes - not supported; describe content textually instead\n7. Using `{{var}}` in the root file without `@inherit` - template variables only work\n in a parent file that defines `params` in `@meta`, with values passed by the child\n via `@inherit ./parent(key: value)` or `@use ./fragment(key: value)`. They are NOT\n set from `promptscript.yaml` or CLI flags\n8. Using `@examples` with `syntax: "1.0.0"` or `"1.1.0"` - `@examples` requires\n syntax version `1.2.0`. Run `prs validate --fix` to auto-upgrade\n\n## Migrating Existing AI Instructions to PromptScript\n\n### Automated: `prs import`\n\nThe fastest way to convert existing AI instructions to PromptScript:\n\n```\nprs import CLAUDE.md # Convert a single file\nprs import .github/copilot-instructions.md\nprs import AGENTS.md --output ./imported\nprs import --dry-run CLAUDE.md # Preview without writing\n```\n\n`prs import` automatically:\n\n- Detects the source format (Claude, GitHub Copilot, Cursor, Factory, etc.)\n- Maps content to appropriate PromptScript blocks (@identity, @standards, etc.)\n- Generates a valid `.prs` file with `@meta` block\n- Preserves the original intent and structure\n\nSupported source formats:\n\n- `CLAUDE.md` (Claude Code)\n- `.github/copilot-instructions.md` (GitHub Copilot)\n- `.cursorrules` or `.cursor/rules/*.mdc` (Cursor)\n- `AGENTS.md` (Factory AI / Codex)\n- `.clinerules` (Cline), `.roorules` (Roo Code)\n- `.windsurf/rules/*.md` (Windsurf)\n- Any Markdown-based AI instruction file\n\n### Manual Migration\n\nFor complex migrations or when `prs import` needs refinement:\n\n| Source Pattern | PromptScript Block |\n| ----------------------------------- | ------------------ |\n| "You are..." persona text | `@identity` |\n| Project description, tech stack | `@context` |\n| Coding conventions, style rules | `@standards` |\n| "Never...", "Always...", hard rules | `@restrictions` |\n| `/command` definitions | `@shortcuts` |\n| Skill/tool definitions | `@skills` |\n| Agent/subagent configs | `@agents` |\n| Reference docs, API specs | `@knowledge` |\n\nAfter import, split into modular files (`context.prs`, `standards.prs`, etc.)\nand compose with `@use` in `project.prs`. Run `prs validate --strict` then\n`prs compile` to verify output matches the original.\n';
8703
+ PROMPTSCRIPT_SKILL_CONTENT = '---\nname: promptscript\ndescription: >-\n PromptScript language expert for reading, writing, modifying, and\n troubleshooting .prs files. Use when working with PromptScript syntax,\n creating or editing .prs files, adding blocks like @identity, @standards,\n @restrictions, @shortcuts, @skills, or @agents, configuring\n promptscript.yaml, resolving compilation errors, understanding inheritance\n (@inherit), composition (@use, @extend, @override), contextual @header\n metadata, or migrating AI instructions\n to PromptScript. Also use when asked about the 50 built-in compilation\n targets, including GitHub Copilot, Claude Code, Cursor, Antigravity,\n Factory AI, and AGENTS.md-based platforms.\nlicense: MIT\nmetadata:\n author: PromptScript\n homepage: https://getpromptscript.dev\ncompatibility:\n - claude-code\n - github-copilot\n - cursor\n - factory-ai\n - gemini-cli\n - opencode\n - windsurf\n - cline\n - roo\n - codex\n - continue\n - augment\n - goose\n - kilo\n - amp\n - trae\n - junie\n - kiro-cli\nallowed-tools:\n - Read\n - Write\n - Glob\n - Grep\n - Bash\nuser-invocable: true\n---\n\n# PromptScript Language Guide\n\nPromptScript is a domain-specific language that compiles `.prs` files into native instruction formats for AI coding assistants (GitHub Copilot, Claude Code, Cursor, Antigravity, Factory AI, OpenCode, Gemini CLI). One source of truth, multiple outputs.\n\n## File Structure\n\nA `.prs` file contains ordered declarations. Syntax `1.5.0` applies `@inherit`,\n`@use`, local blocks, `@extend`, and `@override` in source order. Put `@meta`\nfirst.\n\n```\n# Comments start with #\n\n@meta { ... } # Required metadata\n@inherit @path # Single inheritance (optional)\n@use @path [as alias] # Imports/mixins (optional, multiple)\n\n@identity { ... } # AI persona\n@context { ... } # Project context\n@standards { ... } # Coding conventions\n@restrictions { ... } # Hard rules\n@shortcuts { ... } # Command aliases\n@knowledge { ... } # Reference documentation\n@skills { ... } # Reusable skill definitions\n@agents { ... } # Subagent definitions\n@workflows { ... } # Repeatable agent procedures\n@examples { ... } # Few-shot input/output examples (syntax 1.2.0+)\n@params { ... } # Template parameters\n@guards { ... } # File globs and priorities\n@hooks { ... } # Portable lifecycle hooks (syntax 1.4.0+)\n@mcpServers { ... } # MCP server configurations (syntax 1.4.0+)\n@plugins { ... } # Capability bundles (syntax 1.4.0+)\n@local { ... } # Private config (not committed)\n@extend path { ... } # Modify imported blocks\n@override path { ... } # Replace one complete existing target (syntax 1.5.0+)\n@custom-name { ... } # Arbitrary named blocks\n```\n\nContextual `@header` entries appear inside supported owner blocks, not at the\ntop level.\n\n## Content Types\n\nPromptScript has four canonical content shapes inside blocks:\n\n### Text Content\n\nUse triple quotes (three double-quote characters) to wrap multiline text.\nText is automatically dedented - leading whitespace from source indentation is stripped.\nUse for prose, markdown, or freeform content.\n\nExample: `@identity` with a text block describing an AI persona starting with "You are..."\n\n### Object Content (key-value pairs)\n\n```\n@context {\n project: "My App"\n team: "Frontend"\n monorepo: {\n tool: "Nx"\n packageManager: "pnpm"\n }\n}\n```\n\nValues can be strings (quoted or unquoted), numbers, booleans, nested objects, or arrays.\n\n### Array Content\n\n```\n@standards {\n code: [\n "Use strict TypeScript",\n "Named exports only"\n ]\n}\n\n@restrictions {\n - "Never use any type"\n - "Never commit secrets"\n}\n```\n\n### Mixed Content\n\nBlocks can contain both object properties and text in the same block.\nPlace the triple-quoted text block alongside key-value pairs.\n\n## Block Reference\n\n### @meta (required)\n\n```\n@meta {\n id: "project-id" # Required: unique identifier\n syntax: "1.0.0" # Required: syntax version (semver)\n org: "Company Name" # Optional\n team: "Frontend" # Optional\n tags: [react, ts] # Optional\n params: { # Optional: template parameters\n projectName: string\n port: number = 3000\n debug?: boolean\n framework: enum("react", "vue") = "react"\n }\n}\n```\n\n### @identity\n\nDefines AI persona. Start with "You are..." for consistent output across all formatters.\nContains a triple-quoted text block with the persona description.\n\n### @context\n\nProject context with structured properties (project, team, languages, runtime)\nplus optional triple-quoted text for architecture details, diagrams, etc.\n\n### @standards\n\nCategory-based conventions. Any category name is valid:\n\n```\n@standards {\n typescript: ["Strict mode", "No any type"]\n naming: ["Files: kebab-case.ts", "Classes: PascalCase"]\n git: {\n format: "Conventional Commits"\n types: [feat, fix, docs, refactor, test, chore]\n }\n}\n```\n\nCategory names are arbitrary. `@standards` can also contain free-form text:\n\n```\n@standards {\n """\n ## Formatting\n Preserve heading structure and use four-space indentation.\n\n ## Testing\n Add regression coverage for every behavior change.\n """\n typescript: ["Strict mode", "Named exports only"]\n git: {\n format: "Conventional Commits"\n }\n}\n```\n\nFree-form text is dedented and rendered with its Markdown heading structure. Factory\nmonolith output nests it under `Conventions & Patterns`; split Factory rules adjust\nheading levels relative to the generated section. Custom structured categories remain\navailable to formatters that support them.\n\n### @restrictions\n\nHard rules as a list of dash-prefixed strings:\n\n```\n@restrictions {\n - "Never expose API keys"\n - "Never commit secrets to version control"\n - "Always validate user input"\n}\n```\n\n### @shortcuts\n\nSimple strings appear as documentation. Objects with `prompt: true` generate\nexecutable prompt/command files for GitHub Copilot and Cursor:\n\n```\n@shortcuts {\n "/review": "Review code for quality"\n "/test": {\n prompt: true\n description: "Write unit tests"\n content: (triple-quoted text with instructions)\n }\n}\n```\n\n> `@commands` is a backwards-compatible alias for `@shortcuts` \u2014 prefer `@shortcuts` in new files.\n\n### @skills\n\nReusable skill definitions with metadata:\n\n```\n@skills {\n commit: {\n description: "Create git commits"\n trigger: "commit, git commit"\n disableModelInvocation: true\n userInvocable: true\n allowedTools: ["Bash", "Read"]\n content: (triple-quoted text with skill instructions)\n }\n}\n```\n\nProperties: description (required), content (required), trigger, disableModelInvocation,\nuserInvocable, allowedTools, context ("fork" or "inherit"), agent, model (Claude Code and Grok\nBuild, mapped through the model catalog), requires, references, inputs, outputs.\n\nThe `references` property attaches external files to the skill\'s context:\n\n```\n@skills {\n architecture-review: {\n description: "Review architecture decisions"\n references: [\n ./references/architecture.md\n ./references/modules.md\n ]\n content: (triple-quoted text)\n }\n}\n```\n\nAllowed file types: `.md`, `.json`, `.yaml`, `.yml`, `.txt`, `.csv`. Paths are resolved relative\nto the `.prs` file. Formatters emit referenced files alongside SKILL.md in the output directory.\n\n### Parameterized Skills\n\nSkills in `.promptscript/skills/<name>/SKILL.md` support template parameters via\nYAML frontmatter. Define `params` in frontmatter and use `{{variable}}` in content:\n\n```yaml\n---\nname: review\ndescription: \'Review {{language}} code for {{standard}}\'\nparams:\n language:\n type: string\n standard:\n type: string\n default: \'best practices\'\nreferences:\n - references/architecture.md\n---\nReview the code using {{language}} conventions following {{standard}}.\n```\n\nThe `references` field in SKILL.md frontmatter lists files to attach to the skill\'s context.\nPaths are relative to the SKILL.md file.\n\nImported SKILL.md frontmatter is bounded: 256 KiB per document, 10,000 YAML nodes, 32 nesting\nlevels, 2,000 entries per mapping or sequence, and 64 KiB per string value. Documents over any\nlimit are rejected before their YAML values are converted.\n\nPass values in `@skills` block:\n\n```\n@skills {\n review: {\n description: "Review code"\n language: "typescript"\n standard: "strict mode"\n }\n}\n```\n\nNon-reserved properties (anything other than description, content, trigger,\nuserInvocable, allowedTools, disableModelInvocation, context, agent, requires,\ninputs, outputs, model) are treated as skill parameter arguments.\n\n### Skill Dependencies\n\nSkills can declare dependencies on other skills via `requires`:\n\n```\n@skills {\n deploy: {\n description: "Deploy service"\n requires: ["lint-check", "test-suite"]\n content: (triple-quoted text)\n }\n}\n```\n\nThe validator (PS016) checks that required skills exist, detects self-references,\nand catches circular dependency chains.\n\n### Skill Contracts (Inputs/Outputs)\n\nSkills can declare typed inputs and outputs in SKILL.md frontmatter:\n\n```yaml\n---\nname: security-scan\ndescription: \'Scan for vulnerabilities\'\ninputs:\n files:\n description: \'Files to scan\'\n type: string\n severity:\n description: \'Minimum severity\'\n type: enum\n options: [low, medium, high]\n default: medium\noutputs:\n report:\n description: \'Scan report\'\n type: string\n passed:\n description: \'Whether scan passed\'\n type: boolean\n---\n```\n\nField types: `string`, `number`, `boolean`, `enum` (with `options` list).\nThe validator (PS017) checks field types, ensures enum fields have options,\nand warns if param names collide with input names.\n\n### Shared Resources\n\nSkills in a folder can share common resources via `.promptscript/shared/`:\n\n```\n.promptscript/\n shared/\n templates.md # Shared across all skills\n style-guide.md\n skills/\n review/\n SKILL.md # Gets @shared/templates.md, @shared/style-guide.md\n deploy/\n SKILL.md # Also gets shared resources\n```\n\nFiles in `shared/` are automatically included in every skill with `@shared/` prefix.\n\n### @agents\n\nCustom subagent definitions. Compiles to `.claude/agents/` for Claude Code,\n`.github/agents/` for GitHub Copilot, `.factory/droids/` for Factory AI, etc.\n\n```\n@agents {\n code-reviewer: {\n description: "Reviews code quality"\n tools: ["Read", "Grep", "Glob", "Bash"]\n model: "sonnet"\n permissionMode: "default"\n content: (triple-quoted text with agent instructions)\n }\n}\n```\n\nImported agent definitions are qualified by an aliased `@use`:\n\n```\n@use ./frontend-team as frontend\n@use ./backend-team as backend\n```\n\nIf both imports define `reviewer`, the resolved names are `frontend.reviewer` and\n`backend.reviewer`. Unique unaliased imports keep their original names. Conflicting unaliased\ndefinitions stop compilation with source and import diagnostics instead of silently overwriting\none another. Native targets map dots to hyphens, so `frontend.reviewer` becomes\n`frontend-reviewer`.\n\nSupports mixed models per agent: `specModel` sets a different model for\nSpecification/planning mode (GitHub, Factory), `specReasoningEffort` sets reasoning\neffort for the spec model (Factory only, values: "low", "medium", "high").\n\nFactory AI droids support additional properties: `model` (any model ID or "inherit"),\n`reasoningEffort` ("low", "medium", "high"), and `tools` (category name like "read-only"\nor array of tool IDs).\n\nAgent `model`/`specModel` and skill `model` values resolve against the model catalog\n(built-in profiles plus `models.profiles` in promptscript.yaml). Write a floating alias\n(`sonnet`, `opus`, `haiku`, `fable` - newest Claude release), a pinned model (profile id,\nalias, API id, or display name such as `claude-opus-5-5` or `Claude Opus 5.5`), or\n`inherit`. Each target gets its native name: Claude Code keeps aliases and uses API ids\nfor pinned Claude models, GitHub Copilot gets display names (`Claude Sonnet 5`), Factory\nAI and Codex get API ids, and Cursor gets dateless ids. Names outside the catalog pass\nthrough unchanged, unless the target has its own spelling for them (GitHub Copilot writes\n`auto` as `Auto`). A model from a provider the target cannot run (a GPT model on Claude\nCode, a Claude model on Codex), or a name with a line break or control character, is\nomitted with a PS4004 warning.\n\n### @workflows\n\nRepeatable multi-step agent procedures. Requires syntax `1.1.0`.\n\n```\n@workflows {\n release: {\n description: "Prepare a validated release"\n content: """\n 1. Run formatting, linting, type checks, and tests.\n 2. Validate compiled output.\n 3. Stop before publishing and request approval.\n """\n }\n}\n```\n\nTargets with native workflow discovery emit dedicated workflow files. Other targets\nretain workflow instructions in their main output when supported.\n\n### @examples\n\nStructured few-shot examples for AI assistants (requires syntax `1.2.0`):\n\n```\n@meta {\n id: "commit-style"\n syntax: "1.2.0"\n}\n\n@examples {\n feat-commit: {\n description: "Feature commit with scope"\n input: "Added user authentication with JWT tokens"\n output: "feat(auth): add JWT-based user authentication"\n }\n}\n```\n\nEach entry is a named example with `input` and `output` (both required),\nplus optional `description`. Multi-line content uses triple-quoted strings.\n\nExamples can also be attached to skills via the `examples` property:\n\n```\n@skills {\n commit: {\n description: "Create conventional commits"\n examples: {\n basic: {\n input: "Added dark mode toggle"\n output: "feat(settings): add dark mode toggle"\n }\n }\n content: (triple-quoted text)\n }\n}\n```\n\n### @knowledge\n\nReference documentation as triple-quoted text. Used for command references,\nAPI docs, and other material that should appear in the output.\n\n### @params\n\nTemplate parameter definitions with types: string, number, boolean, enum("a", "b").\nOptional parameters use `?` suffix. Defaults use `= value`.\n\n### @guards\n\nFile glob patterns and priority rules for path-specific instructions.\n\n### @hooks\n\nPortable lifecycle hooks. Requires syntax `1.4.0`. Each hook needs exactly one of\n`command` or `script`.\n\n```\n@hooks {\n validate-types: {\n event: "post-tool-use"\n matcher: "Edit|Write"\n script: {\n path: ".promptscript/scripts/validate.py"\n interpreter: "python3"\n args: ["--strict"]\n }\n cwd: "project"\n timeoutMs: 120000\n statusMessage: "Checking TypeScript"\n continueOnFailure: false\n enabled: true\n targets: {\n factory: { matcher: "Execute" }\n vscode: { matcher: "run_in_terminal" }\n github: { enabled: false }\n }\n }\n}\n```\n\nPortable events:\n\n| Event | Meaning |\n| ---------------------- | ------------------------- |\n| `pre-terminal-command` | Before a terminal command |\n| `pre-tool-use` | Before a tool invocation |\n| `post-tool-use` | After a tool invocation |\n| `session-start` | Agent session start |\n| `setup` | Session setup |\n| `subagent-start` | Subagent start |\n| `notification` | Agent notification |\n| `stop` | Agent stop |\n\n`command` is a non-empty string array. Shell interpolation (`$()`, backticks,\n`${...}`) is forbidden. `script` requires:\n\n- `path` under `.promptscript/scripts/`, using forward slashes.\n- Existing regular file at compile time.\n- No traversal, absolute path, invalid segment, or symlink escape.\n- Explicit interpreter: `python3`, `python`, `node`, `deno`, `bun`, `ruby`, `php`,\n `perl`, `bash`, `sh`, `zsh`, `pwsh`, or `powershell`.\n- Optional `args` string array; each argument remains one argument.\n\n`cwd: "project"` runs from project root. Other values are portable forward-slash\npaths relative to project root. Hook config file location does not set command cwd.\nEnvironment-root and Git-root wrappers exit before script or command execution when\nthe required root is unavailable. Native-cwd and workspace-cwd targets retain host\ncwd fields and report `PS4002` when PromptScript cannot verify that cwd.\n`timeoutMs` range is 100-600000. `matcher` uses target-native tool names, so a\nmatcher valid for one target may match nothing on another.\n\n`pre-terminal-command` supplies native defaults: Factory `Execute`, Claude and\nCodex `Bash`, Windsurf `pre_run_command`, Cursor `run_terminal_cmd`, Gemini\n`run_shell_command`, and VS Code `run_in_terminal`. Override a native tool name\nwith `targets.<name>.matcher`. Cursor, Gemini, and VS Code report best-effort\n`PS4002` warnings. GitHub and Grok omit the event with `PS4002`.\n\nTarget overrides may change `event`, `matcher`, `timeoutMs`, `statusMessage`,\n`continueOnFailure`, `enabled`, or `cwd`. Native hook files are emitted only in\ntarget modes that support additional files:\n\n| Target | Hook output | Mode |\n| -------------- | ---------------------------------------------------------------------- | ------------------- |\n| Claude Code | `.claude/settings.json` | `full` |\n| Factory AI | `.factory/hooks.json` | `multifile`, `full` |\n| GitHub Copilot | `.github/hooks/promptscript.json` | `multifile`, `full` |\n| Cursor | `.cursor/hooks.json` | `full` |\n| Codex | `.codex/hooks.json` | `multifile`, `full` |\n| Gemini CLI | `.gemini/settings.json` | `multifile`, `full` |\n| Windsurf | `.windsurf/hooks.json` | `multifile`, `full` |\n| Grok Build | `.grok/hooks/promptscript.json` | `full` |\n| OpenCode | `.opencode/plugins/promptscript.ts` (generated plugin) | `multifile`, `full` |\n| VS Code Agent | `.github/hooks/promptscript-vscode.json` when `vscode` override exists | target-specific |\n\nOpenCode starts generated hook commands asynchronously. It enforces authored\ntimeouts or a 30-second default, then escalates from `SIGTERM` to `SIGKILL`.\nPayloads are bounded by UTF-8 byte length. OpenCode tool hooks expose tool\narguments, session ID, and call ID, but no model or agent context; compilation\nreports that limitation with `PS4002`.\n\nSimple mode and targets without native project hooks report `PS4002` instead of\nsilently dropping hooks. Use `prs compile --watch` as fallback. Plugin-only and\nagent-scoped integrations are not emitted as universal project hooks.\n\nEach generated command carries a PromptScript ownership marker. CLI cleanup removes\nonly marked entries and preserves user hooks/settings. Removing `@hooks` removes a\nfully owned generated hook file and prunes directories left empty. `prs hooks install factory`\nmigrates unambiguous legacy hooks from `.factory/settings.json`; ambiguous\nentries remain for manual review.\n\nFactory compilation performs the same migration when `.factory/hooks.json` is\nabsent. Use `prs compile --dry-run` to preview the changes or\n`--no-migrate-factory-hooks` to keep warning-only behavior. Unknown events,\nmalformed entries, and mixed ownership abort without a partial migration.\n\n`@hooks` compilation is separate from `prs hooks install`. The latter installs\nauto-compilation and generated-output protection for supported AI tools. Copilot VS\nCode Agent hooks use `promptscript-vscode.json`; GitHub Copilot repository hooks use\n`promptscript.json`.\n\n### @mcpServers\n\nProject-local Model Context Protocol servers. Requires syntax `1.4.0`.\n\n```\n@mcpServers {\n issue-tracker: {\n transport: "stdio"\n command: ["node", "./tools/issues.mjs"]\n env: { LOG_LEVEL: "info" }\n }\n}\n```\n\nUse `stdio` with `command`, or `http`/`sse` with `url`. Keep credentials out of\n`.prs` files and provide them through target-native secret management.\n\n### @plugins\n\nPortable capability bundles. Requires syntax `1.4.0`.\n\n```\n@plugins {\n security-suite: {\n description: "Security review tooling"\n version: "1.0.0"\n skills: ["security-review"]\n hooks: ["validate-types"]\n mcpServers: ["issue-tracker"]\n }\n}\n```\n\n### @local\n\nPrivate local configuration. Not included in compiled output or committed to git.\n\n## Inheritance and Composition\n\n### @inherit (single, linear)\n\nOne per file. Child blocks merge on top of parent:\n\n```\n@inherit @company/frontend-team\n@inherit ./parent\n@inherit @stacks/react-app(projectName: "my-app", port: 3000)\n```\n\n### @use (multiple, mixins)\n\nImport and merge fragments:\n\n```\n@use @core/security\n@use @core/quality\n@use ./local-config\n@use @core/typescript as ts # alias enables @extend access\n```\n\n#### URL imports (Go-module style)\n\nImport directly from any Git repository by host path - no alias required:\n\n```\n@use github.com/acme/shared-standards/@fragments/security\n@use gitlab.com/myorg/prompts/@stacks/python\n```\n\nVersion pinning with `@`:\n\n```\n@use github.com/acme/shared-standards/@org/base@1.2.0 # exact version\n@use github.com/acme/shared-standards/@org/base@^1.0.0 # semver range\n@use github.com/acme/shared-standards/@org/base@main # branch\n```\n\n#### Registry aliases\n\nShort names for Git repository URLs, configured in `promptscript.yaml`:\n\n```yaml\nregistries:\n company:\n url: github.com/acme/promptscript-registry\n```\n\nThen use the alias as scope prefix:\n\n```\n@use @company/security\n@inherit @company/base-config\n```\n\nMerge rules:\n\n- Text: concatenated with deduplication\n- Objects: deep merged (imported source wins same-shape conflicts)\n- Arrays: unique concatenation\n- Shape mismatch: existing target body wins\n\nUnder syntax `1.5.0`, later local blocks, `@extend`, and `@override`\noperations apply to the accumulated import result in declaration order.\n\n### Block Filtering\n\nControl which blocks are imported using the reserved `only` and `exclude` parameters:\n\n```\n@use ./shared-config(only: ["skills", "context"])\n@use ./shared-config(exclude: ["knowledge"])\n@use ./shared-config(exclude: ["knowledge"], mode: "strict")\n```\n\nRules:\n\n- `only` and `exclude` are mutually exclusive \u2014 using both is a validation error (PS021)\n- Values are block type names: `identity`, `context`, `standards`, `knowledge`, `skills`, `shortcuts`, `agents`, etc.\n- Block filtering does not apply to `@inherit` directives\n\n### Markdown Imports\n\nImport skills directly from `.md` files (v1.8+). No external tools needed:\n\n```\n@use ./skills/frontend-design.md\n@use ./shared/commit.md as commit\n@use github.com/anthropics/skills/commit@1.0.0\n@use github.com/repo/skills/gitnexus # directory \u2192 SKILL.md\n```\n\nContent detection: PromptScript blocks in `.md` are parsed as a `.prs` fragment;\nYAML frontmatter with `name`/`description` is loaded as a skill definition;\notherwise content is treated as free-form knowledge.\n\nCLI management:\n\n```\nprs skills add github.com/anthropics/skills/commit@1.0.0\nprs skills remove commit\nprs skills list\nprs skills update\n```\n\n### @extend (modify existing or imported blocks)\n\nUse a direct path for inherited or local blocks:\n\n```\n@extend standards.testing {\n coverage: 95\n}\n```\n\nUse an alias when targeting a specific imported block:\n\n```\n@use @core/typescript as ts\n\n@extend ts.standards {\n testing: { coverage: 95 }\n}\n```\n\n#### Replacing regular block fields\n\nSyntax `1.3.0` supports explicit replacement of complete regular block field values:\n\n```\n@meta { id: "project" syntax: "1.3.0" }\n\n@inherit ./company-base\n\n@extend standards {\n testing!: ["Use Vitest"]\n linting: ["Use ESLint"]\n}\n```\n\n`testing!` replaces the inherited value. Fields without `!` keep normal merge behavior.\nReplacement works after `@inherit` and `@use`, including aliases and nested target paths.\nA missing field is set. The modifier is rejected for `@skills`, which retain their dedicated\nmerge and sealing semantics.\n\n#### Replacing complete targets with @override\n\nSyntax `1.5.0` adds atomic replacement for an existing block or nested value:\n\n```\n@meta { id: "project" syntax: "1.5.0" }\n\n@standards {\n testing: ["Use Jest", "Use Mocha"]\n}\n\n@override standards.testing {\n ["Use Vitest"]\n}\n```\n\n`@override` requires the complete target path to exist, applies in declaration\norder, and cannot bypass sealed skill properties. Later `@extend` declarations\nmerge into the replacement. Use `@extend` for additive changes, `field!` for\ncompatibility replacement of one direct regular field, and `@override` for\nintentional complete replacement.\n\n#### Skill-aware @extend semantics\n\nWhen extending a skill definition via `@extend`, individual skill properties follow specific merge\nstrategies rather than the generic block merge rules:\n\n| Strategy | Properties |\n| ----------------- | ----------------------------------------------------------------------------------------------------------- |\n| **Replace** | content, description, trigger, userInvocable, allowedTools, disableModelInvocation, context, agent, license |\n| **Append** | references, examples, requires |\n| **Shallow merge** | params, inputs, outputs |\n\nExample \u2014 extending a base skill to add references and override content:\n\n```\n@use @company/skills as skills\n\n@extend skills.code-review {\n content: (triple-quoted text with overridden instructions)\n references: [\n ./extra-context.md\n ]\n}\n```\n\nThe `references` array from the base skill and the overlay are combined (append). The `content`\nfield from the overlay replaces the base (replace).\n\n#### Reference negation\n\nUse `!` prefix in `@extend` to remove entries from a lower layer\'s append-strategy arrays:\n\n```\n@extend skills.code-review {\n references: [\n "!references/deprecated.md"\n "references/replacement.md"\n ]\n}\n```\n\nPath matching is normalized (`"!./foo.md"` matches `"foo.md"`). Only works in `@extend` blocks\non `references` and `requires`. Validator PS028 warns about `!` in base definitions.\n\n#### Overlay consistency warnings\n\nThe resolver emits warnings during compile when an overlay drifts from its base. Always shown\n(not gated by `--verbose`):\n\n- **Orphaned extend** \u2014 `@extend target "X" not found \u2014 overlay will be ignored.` Triggered when\n the targeted block doesn\'t exist (base removed or renamed).\n- **Stale skill target** \u2014 `@extend creates new skill "X" \u2014 base does not define it.` Triggered\n when an `@extend` inside `@skills` would create a new skill instead of extending an existing one.\n- **Negation orphan** \u2014 `Negation "!path" did not match any base entry \u2014 it may be stale.`\n Triggered when a `!entry` in references/requires doesn\'t match anything in the base.\n\nThese come from the resolver, not the validator (PS0XX rules). They appear during `prs compile`,\nnot `prs validate`.\n\n#### Sealed properties\n\nPrevent `@extend` from overriding specified replace-strategy properties:\n\n```\n@skills {\n deploy: {\n content: (triple-quoted text with critical workflow)\n sealed: ["content", "description"]\n }\n}\n```\n\n`sealed: true` seals all replace-strategy properties. Attempting to override a sealed\nproperty is a hard compilation error. Only the base skill author can set `sealed` \u2014\noverlays cannot add or modify it. Append-strategy properties remain extendable.\nValidator PS029 warns about invalid entries in `sealed`.\n\n#### Skill composition (inline @use)\n\nImport sub-skills within a `@skills` block to compose multi-phase workflows:\n\n```\n@skills {\n ops: {\n description: "Production triage"\n content: (triple-quoted text with orchestrator instructions)\n }\n @use ./phases/health-scan\n @use ./phases/triage\n @use ./phases/code-fix as autofix\n}\n```\n\nEach `@use` resolves the referenced `.prs` file, extracts its skill definition and context\nblocks, and flattens them as numbered phase sections into the parent skill\'s content. The\n`as alias` form controls the phase display name. Validator PS027 checks composition validity.\n\n### Parameterized Inheritance (Template Variables)\n\nUse `{{variable}}` placeholders in a **parent/template** file, and pass values\nfrom the **child** file via `@inherit` or `@use` with `(key: value)` syntax.\n\n**IMPORTANT:** Variables are NOT set from `promptscript.yaml` or CLI. They are\npassed from one `.prs` file to another through `@inherit` or `@use`.\n\n**Step 1: Create the template** (parent file with `params` in `@meta`):\n\n```\n# base.prs - reusable template\n@meta {\n id: "service-template"\n syntax: "1.0.0"\n params: {\n serviceName: string\n port?: number = 3000\n }\n}\n\n@identity {\n """\n You are working on {{serviceName}} running on port {{port}}.\n """\n}\n```\n\n**Step 2: Inherit with values** (child file passes params):\n\n```\n# project.prs - concrete project\n@meta { id: "user-api" syntax: "1.0.0" }\n\n@inherit ./base(serviceName: "user-api", port: 8080)\n```\n\nAfter compilation, `{{serviceName}}` becomes `user-api` and `{{port}}` becomes `8080`.\n\nThe same works with `@use`:\n\n```\n@use ./base(serviceName: "auth-service") as auth\n```\n\n**Parameter types:** `string`, `number`, `boolean`, `enum("a", "b")`.\nOptional params use `?` suffix. Defaults use `= value`.\nMissing required params produce a compile error.\n\n**Multi-service pattern** - reuse one template across many projects:\n\n```\nservices/\n base.prs # template with params\n user-api/\n promptscript.yaml # source: project.prs\n project.prs # @inherit ../base(serviceName: "user-api")\n auth-service/\n promptscript.yaml\n project.prs # @inherit ../base(serviceName: "auth-service")\n```\n\n## Configuration: promptscript.yaml\n\n### Auto-injection\n\nThis skill is automatically included when compiling with `prs compile`. No manual copying needed.\nTo disable, set `includePromptScriptSkill: false` in your `promptscript.yaml`.\n\n```\nid: my-project\nsyntax: "1.1.0"\ndescription: "My project description"\ninput:\n entry: .promptscript/project.prs\n include: [\'.promptscript/**/*.prs\']\ntargets:\n github:\n version: full # simple | multifile | full\n claude:\n version: full\n cursor:\n version: standard\n antigravity:\n version: frontmatter\n factory:\n version: full\n windsurf: # 41 additional targets supported\n version: simple\n cline:\n version: simple\nregistry:\n git: https://github.com/org/registry.git\n ref: main\nregistries:\n company:\n url: github.com/acme/promptscript-registry\n oss:\n url: github.com/prscrpt/community-registry\n ref: v2\npolicies:\n - name: adjacent-layers-only\n kind: layer-boundary\n severity: error\n layers: [\'@core\', \'@team\', \'@project\']\n maxDistance: 1\nmodels:\n supported: [opus, sonnet, gpt-5.3-codex] # PS041 reports models outside this set\n profiles: # add models or override built-in profiles\n claude-opus-9:\n provider: anthropic\n family: claude-opus # joins the family, so `opus` now resolves here\n version: \'9\'\n displayName: Claude Opus 9\n targets:\n github: Claude Opus 9 (Preview) # per-target name always wins\n```\n\n### Lockfile: `promptscript.lock`\n\nWhen remote imports are used, run `prs lock` to generate or update the lockfile\nbefore compilation. It records the exact resolved commit for each dependency.\nIntegrity hashes (SHA-256) are included for registry references to detect\ntampering or drift. This enables reproducible builds across machines and CI.\nCommit `promptscript.lock` to version control.\n\nUse `--ignore-hashes` on `prs compile` or `prs validate` to skip integrity\nhash verification when needed.\n\n### Policy Engine\n\nDefine organizational policies in `promptscript.yaml` to validate skill extensions:\n\n```yaml\npolicies:\n - name: adjacent-layers-only\n kind: layer-boundary\n description: \'Only adjacent layers can extend each other\'\n severity: error\n layers: [\'@core\', \'@team\', \'@project\']\n maxDistance: 1\n\n - name: protect-content\n kind: property-protection\n description: \'Content override requires explicit approval\'\n severity: warning\n properties: [\'content\', \'description\']\n\n - name: approved-registries\n kind: registry-allowlist\n description: \'Extensions must come from approved registries\'\n severity: error\n allowed: [\'@core\', \'@team\']\n```\n\nPolicy kinds: `layer-boundary` (controls layer distance), `property-protection`\n(prevents overriding specific properties), `registry-allowlist` (restricts extension sources).\nSeverity: `error` (fails validation) or `warning` (reported only).\nSkip with `--skip-policies` during development (never in CI).\n\n## Syntax Version Validation\n\nThe `syntax` field in `@meta` declares the PromptScript language version (semver).\n\n### Known Versions\n\n| Version | What it adds |\n| ------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `1.0.0` | Core blocks (identity, context, standards, restrictions, knowledge, shortcuts, commands, guards, params, skills, local) |\n| `1.1.0` | Adds `@agents` and `@workflows`; reserves internal `@prompts` |\n| `1.2.0` | Adds `@examples` (few-shot input/output pairs) |\n| `1.3.0` | Adds explicit regular block field replacement in `@extend` |\n| `1.4.0` | Adds `@hooks`, `@mcpServers`, and `@plugins` |\n| `1.5.0` | Adds `@header` section titles, `@override` replacement, and unquoted `${VAR}` values |\n\n### Block Version Requirements\n\n| Block | Minimum Syntax Version |\n| ------------- | ---------------------- |\n| `@agents` | `1.1.0` |\n| `@workflows` | `1.1.0` |\n| `@examples` | `1.2.0` |\n| `@hooks` | `1.4.0` |\n| `@mcpServers` | `1.4.0` |\n| `@plugins` | `1.4.0` |\n\nAll other built-in blocks are available from `1.0.0`.\nRegular block field replacement with `field!: value` requires syntax `1.3.0`.\nGenerated section title overrides with `@header` require syntax `1.5.0`.\nAtomic replacement with `@override` requires syntax `1.5.0`.\nUnquoted `${VAR}` references as values require syntax `1.5.0`.\n\n### Generated Section Headers\n\nUse `@header` inside a registered owner block to rename human-readable output\nsections without changing filenames, frontmatter, XML tags, or structured keys:\n\n```promptscript\n@meta { id: "localized" syntax: "1.5.0" }\n\n@standards {\n @header "Coding Rules"\n @header git-commits "Commit Rules"\n code: ["Use strict TypeScript"]\n}\n```\n\n- `@header "Title"` targets the block\'s primary section.\n- `@header <section-key> "Title"` targets an owned derived section.\n- Titles must be non-empty, single-line strings.\n- Source overrides take precedence over formatter configuration and target defaults.\n- Child inheritance, imported source, and the latest root extension take precedence.\n- An initial `## Heading` in a registered text-only primary owner is a syntax\n `1.5.0` compatibility fallback. Explicit `@header` metadata wins.\n- Ordinary `header` and `headers` fields remain domain data.\n\n### Validation Rules\n\n- **PS018 (`syntax-version-compat`)**: warns when resolved blocks or syntax features require a higher version than declared. Requirements from inheritance, imports, and skill composition are included. Suggestion: run `prs validate --fix`.\n- **PS019 (`unknown-block-name`)**: warns when a block name is not a known PromptScript type, with fuzzy-match suggestions for typos.\n- **PS037 (`valid-section-headers`)**: rejects invalid titles, unknown or unowned section keys, duplicate overrides, and nested extension overrides.\n- **PS038 (`valid-block-shape`)**: rejects unsupported built-in block shapes and warns about formatter-sensitive legacy shapes or multiline shortcut scalars.\n- **PS039 (`agent-namespaces`)**: validates qualified agent name segments and checks them against recorded import provenance.\n- **PS040 (`import-excludes`)**: errors when a `validation.excludes` entry for an import does not record the commit pinned in promptscript.lock, so consumers re-review imports whose pinned commit changed.\n- **PS041 (`valid-model-reference`)**: warns when an agent `model`/`specModel` or skill `model` resolves to a deprecated or retired model, suggesting the successor. With `models.supported` set, it also warns about models outside the set, models missing from the catalog, and unknown `models.supported` entries. It also reports `models.profiles` problems: a name shared by two profiles or taken from a floating alias or `inherit`, unknown or looping successors, dates not in `YYYY-MM-DD`, and `targets` keys for targets that write no model names.\n- **PS021 (`use-block-filter`)**: errors when `only` and `exclude` are both specified in `@use` parameters.\n- **PS025 (`valid-skill-references`)**: errors when a `references` entry points to a file with a disallowed extension or a path that cannot be resolved.\n- **PS026 (`safe-reference-content`)**: warns when a referenced file contains potentially sensitive content (e.g., secrets, credentials).\n- **PS027 (`valid-skill-composition`)**: warns about conflicting phase names or excessive phases in composed skills.\n- **PS028 (`valid-append-negation`)**: warns when negation prefix `!` appears in base skill definitions (only effective in `@extend`).\n- **PS029 (`valid-sealed-property`)**: warns when `sealed` contains non-replace-strategy property names.\n- **PS030 (`policy-compliance`)**: validates skill extensions against organizational policies defined in `promptscript.yaml`.\n- **PS034 (`valid-hooks`)**: validates portable hook events, commands/scripts, paths, interpreters, timeouts, cwd, and target overrides.\n\nTarget formatters report **PS4002** when a hook event or field has no native equivalent,\nwhen a target cannot guarantee project-root execution, or when output mode cannot emit\nthe additional hook file. They report **PS4004** when an agent or skill model comes from\na provider the target cannot run, or when its name has a line break or control character;\nthe model field is omitted for that target.\n\n### Fixing Syntax Versions\n\n```\nprs validate --fix # Auto-fix syntax versions in .prs files\nprs upgrade # Upgrade all .prs files to the latest version\n```\n\n`--fix` rewrites the `syntax: "..."` line in each file\'s `@meta` block to match the minimum version required by resolved blocks and syntax features. It follows inheritance, imports, and skill composition. It only upgrades, never downgrades.\n\n`prs upgrade` upgrades all files to the latest known syntax version regardless of what blocks they use.\n\n## CLI Commands\n\n```\nprs init # Initialize project (auto-detects existing files)\nprs init --yes --targets claude factory\nprs init --dry-run # Preview initialization\nprs init --auto-import # Initialize + static import of existing files\nprs migrate # Interactive migration flow\nprs migrate --static # Non-interactive static import\nprs migrate --llm # Generate AI-assisted migration prompt\nprs migrate --static --dry-run\nprs compile # Compile to all targets\nprs compile --watch # Watch mode\nprs compile --ignore-hashes # Skip integrity hash verification\nprs build <name> # Compile a named build profile\nprs validate --strict # Validate syntax\nprs validate --fix # Auto-fix syntax version declarations\nprs validate --skip-policies # Skip policy engine evaluation\nprs upgrade # Upgrade all .prs files to latest syntax version\nprs import CLAUDE.md # Import existing AI instructions\nprs import CLAUDE.md --dry-run # Preview import conversion\nprs inspect <skill> # Show skill composition provenance\nprs inspect <skill> --layers # Show layer-level breakdown\nprs explain <path> # Explain source and composition provenance\nprs explain <path> --format json # Machine-readable provenance history\nprs hooks install # Install auto-compilation hooks for AI tools\nprs hooks install claude # Install hooks for a specific tool\nprs hooks uninstall # Remove installed auto-compilation hooks\nprs hooks uninstall claude # Remove hooks for a specific tool\nprs skills add <source> # Add a remote skill (@use + lock update + SKILL.md validation)\nprs skills add <source> --strict # Treat validation warnings as errors\nprs skills add <source> --skip-validation # Bypass Agent Skills spec checks (not recommended)\nprs skills remove <name> # Remove a skill (@use line + lock entry)\nprs skills list # List all imported skills\nprs skills update # Re-resolve markdown-imported skills (re-validates + re-hashes)\nprs pull # Update registry\nprs diff --target claude # Show compilation diff\nprs diff --all --format json # Machine-readable diff report for CI\nprs diff --format json --include-content # Include generated content in the report\nprs lock # Generate/update promptscript.lock\nprs lock --dry-run # Preview lockfile changes\nprs update # Re-resolve all remote imports to latest\nprs update <url> # Update a specific registry\nprs vendor sync # Copy cached deps to .promptscript/vendor/\nprs vendor check # Verify vendor matches lockfile\nprs resolve @alias/path # Debug: show how an import resolves\nprs registry list # Show configured registries and aliases\nprs registry add <alias> <url> # Add a registry alias\n```\n\n`prs init --yes` requires explicit, detected, or user-configured targets. It does not invent\ndefault tools. For existing projects, `prs migrate` preserves `promptscript.yaml`, isolates static\noutput under `.promptscript/migrated/`, leaves source instructions untouched, and performs no\nwrites when no candidates are detected.\n\n## Output Targets\n\n50 supported targets. Key examples:\n\n| Target | Main File | Skills |\n| ----------- | ------------------------------- | -------------------------------------------------- |\n| GitHub | .github/copilot-instructions.md | .github/skills/\\*/SKILL.md |\n| Claude | CLAUDE.md | .claude/skills/\\*/SKILL.md |\n| Cursor | .cursor/rules/project.mdc | .agents/skills/\\*/SKILL.md |\n| Antigravity | .agent/rules/project.md | - |\n| Factory | AGENTS.md | .factory/skills/\\*/SKILL.md, .factory/droids/\\*.md |\n| OpenCode | OPENCODE.md | .opencode/skills/\\*/SKILL.md |\n| Gemini | GEMINI.md | .agents/skills/\\*/skill.md |\n| Windsurf | .windsurf/rules/project.md | .windsurf/skills/\\*/SKILL.md |\n| Cline | .clinerules | - |\n| Roo Code | .roorules | - |\n| Codex | AGENTS.md | .agents/skills/\\*/SKILL.md |\n| Continue | .continue/rules/project.md | - |\n| Hermes | AGENTS.md | - |\n| + 37 more | | See full list in documentation |\n\nTargets that share an output path (for example Factory, Codex, and every AGENTS.md target) are\nreconciled in one output plan before anything is written. Identical content merges silently;\ndiffering content reports `PS4001`, where a formatter output replaces an earlier one and a\nresource or the auto-injected PromptScript skill keeps the file already planned. Paths are compared\ncase-insensitively and NFC-normalized on every platform.\n\n### Formatter Documentation\n\nFor detailed information about each formatter\'s output paths, supported features, quirks, and example outputs:\n\n- **Full formatter reference:** `docs/reference/formatters/` (7 dedicated pages + index of all 50)\n- **llms-full.txt:** Available at the docs site root - contains all documentation in a single file for LLM consumption\n- **Dedicated pages exist for:** Claude Code, GitHub Copilot, Cursor, Antigravity, Factory AI, Gemini CLI, OpenCode\n- **All 50 formatters indexed at:** `docs/reference/formatters/index.md` with output paths, tier, and feature flags\n\n### Auto-Compilation Hooks\n\nInstead of running `prs compile --watch` manually, install hooks so your AI tool\ntriggers compilation automatically when you edit `.prs` files:\n\n```\nprs hooks install # Auto-detect and install for all detected tools\nprs hooks install claude # Install for a specific tool\n```\n\nHooks also protect generated files from direct edits \u2014 when an AI agent tries\nto edit a compiled output (e.g., CLAUDE.md), the write is blocked with a message\npointing to the source `.prs` file. Supported tools: Claude Code, Factory AI,\nCursor, Windsurf, Cline, GitHub Copilot, Gemini CLI.\n\n## Project Organization\n\nTypical modular structure:\n\n```\n.promptscript/\n project.prs # Entry: @meta, @inherit, @use, @identity, @agents\n context.prs # @context (architecture, tech stack)\n standards.prs # @standards (coding conventions)\n restrictions.prs # @restrictions (hard rules)\n commands.prs # @shortcuts and @knowledge\n```\n\nThe entry file uses `@use ./context`, `@use ./standards`, etc. to compose them.\n\n## Common Mistakes\n\n1. Missing @meta block - every .prs file needs `@meta` with `id` and `syntax`\n2. Multiple @inherit - only one per file; use `@use` for additional imports\n3. Extending an unknown path - target an inherited or local block, or use an imported alias\n4. Unquoted strings with special chars - quote strings containing `:`, `#`, `{`, `}`\n5. Forgetting to compile - `.prs` changes need `prs compile` to take effect\n6. Triple quotes inside triple quotes - not supported; describe content textually instead\n7. Using `{{var}}` in the root file without `@inherit` - template variables only work\n in a parent file that defines `params` in `@meta`, with values passed by the child\n via `@inherit ./parent(key: value)` or `@use ./fragment(key: value)`. They are NOT\n set from `promptscript.yaml` or CLI flags\n8. Using `@examples` with `syntax: "1.0.0"` or `"1.1.0"` - `@examples` requires\n syntax version `1.2.0`. Run `prs validate --fix` to auto-upgrade\n\n## Migrating Existing AI Instructions to PromptScript\n\n### Automated: `prs import`\n\nThe fastest way to convert existing AI instructions to PromptScript:\n\n```\nprs import CLAUDE.md # Convert a single file\nprs import .github/copilot-instructions.md\nprs import AGENTS.md --output ./imported\nprs import --dry-run CLAUDE.md # Preview without writing\n```\n\n`prs import` automatically:\n\n- Detects the source format (Claude, GitHub Copilot, Cursor, Factory, etc.)\n- Maps content to appropriate PromptScript blocks (@identity, @standards, etc.)\n- Generates a valid `.prs` file with `@meta` block\n- Preserves the original intent and structure\n\nSupported source formats:\n\n- `CLAUDE.md` (Claude Code)\n- `.github/copilot-instructions.md` (GitHub Copilot)\n- `.cursorrules` or `.cursor/rules/*.mdc` (Cursor)\n- `AGENTS.md` (Factory AI / Codex)\n- `.clinerules` (Cline), `.roorules` (Roo Code)\n- `.windsurf/rules/*.md` (Windsurf)\n- Any Markdown-based AI instruction file\n\n### Manual Migration\n\nFor complex migrations or when `prs import` needs refinement:\n\n| Source Pattern | PromptScript Block |\n| ----------------------------------- | ------------------ |\n| "You are..." persona text | `@identity` |\n| Project description, tech stack | `@context` |\n| Coding conventions, style rules | `@standards` |\n| "Never...", "Always...", hard rules | `@restrictions` |\n| `/command` definitions | `@shortcuts` |\n| Skill/tool definitions | `@skills` |\n| Agent/subagent configs | `@agents` |\n| Reference docs, API specs | `@knowledge` |\n\nAfter import, split into modular files (`context.prs`, `standards.prs`, etc.)\nand compose with `@use` in `project.prs`. Run `prs validate --strict` then\n`prs compile` to verify output matches the original.\n';
8048
8704
  }
8049
8705
  });
8050
8706
 
@@ -20767,6 +21423,214 @@ var init_agent_capability_warnings = __esm({
20767
21423
  }
20768
21424
  });
20769
21425
 
21426
+ // packages/formatters/src/model-mapping.ts
21427
+ function unquoteYamlToken(token) {
21428
+ const quote = token[0];
21429
+ if ((quote === "'" || quote === '"') && token.at(-1) === quote && token.length >= 2) {
21430
+ return token.slice(1, -1);
21431
+ }
21432
+ return token;
21433
+ }
21434
+ function stripYamlComment(value) {
21435
+ let quote;
21436
+ for (let i = 0; i < value.length; i++) {
21437
+ const char = value[i];
21438
+ if (quote === void 0) {
21439
+ if (char === "'" || char === '"') {
21440
+ quote = char;
21441
+ } else if (char === "#" && (i === 0 || value[i - 1] === " " || value[i - 1] === " ")) {
21442
+ return value.slice(0, i);
21443
+ }
21444
+ } else if (char === quote) {
21445
+ quote = void 0;
21446
+ }
21447
+ }
21448
+ return value;
21449
+ }
21450
+ function splitYamlMapping(line) {
21451
+ if (line.startsWith(" ") || line.startsWith(" ")) return void 0;
21452
+ const colon = line.indexOf(":");
21453
+ if (colon < 0) return void 0;
21454
+ const rest = line.slice(colon + 1);
21455
+ if (rest !== "" && !rest.startsWith(" ") && !rest.startsWith(" ")) return void 0;
21456
+ return [unquoteYamlToken(line.slice(0, colon).trim()), rest];
21457
+ }
21458
+ function isFrontmatterModelLine(line) {
21459
+ return splitYamlMapping(line)?.[0] === MODEL_KEY;
21460
+ }
21461
+ function blockScalarKind(value) {
21462
+ const first2 = value[0];
21463
+ if (first2 !== "|" && first2 !== ">") return void 0;
21464
+ for (const char of value.slice(1)) {
21465
+ if (char !== "-" && char !== "+" && (char < "1" || char > "9")) return void 0;
21466
+ }
21467
+ return first2 === "|" ? "literal" : "folded";
21468
+ }
21469
+ function blockScalarLines(lines, keyIndex) {
21470
+ const block = [];
21471
+ for (const line of lines.slice(keyIndex + 1)) {
21472
+ if (line === "" || line.startsWith(" ") || line.startsWith(" ")) {
21473
+ block.push(line);
21474
+ } else {
21475
+ break;
21476
+ }
21477
+ }
21478
+ while (block.length > 0 && block.at(-1) === "") block.pop();
21479
+ return block;
21480
+ }
21481
+ function blockScalarValue(block, kind) {
21482
+ const indents = block.filter((line) => line !== "").map((line) => line.length - line.trimStart().length);
21483
+ const indent = indents.length > 0 ? Math.min(...indents) : 0;
21484
+ const content = block.map((line) => line === "" ? "" : line.slice(indent));
21485
+ if (kind === "literal") {
21486
+ return content.join("\n").trim();
21487
+ }
21488
+ const paragraphs = [[]];
21489
+ for (const line of content) {
21490
+ if (line === "") {
21491
+ paragraphs.push([]);
21492
+ } else {
21493
+ paragraphs.at(-1)?.push(line.trim());
21494
+ }
21495
+ }
21496
+ return paragraphs.map((lines) => lines.join(" ")).join("\n").trim();
21497
+ }
21498
+ function findRawFrontmatterModel(frontmatter) {
21499
+ const lines = frontmatter.split(/\r?\n/);
21500
+ for (let i = 0; i < lines.length; i++) {
21501
+ const line = lines[i];
21502
+ if (line === void 0 || !isFrontmatterModelLine(line)) continue;
21503
+ const valuePart = stripYamlComment(line.slice(line.indexOf(":") + 1)).trim();
21504
+ if (!valuePart) {
21505
+ return { keyIndex: i, lineCount: 1, value: void 0 };
21506
+ }
21507
+ const kind = blockScalarKind(valuePart);
21508
+ if (kind === void 0) {
21509
+ return { keyIndex: i, lineCount: 1, value: unquoteYamlToken(valuePart) };
21510
+ }
21511
+ const block = blockScalarLines(lines, i);
21512
+ const value = blockScalarValue(block, kind);
21513
+ return { keyIndex: i, lineCount: 1 + block.length, value: value === "" ? void 0 : value };
21514
+ }
21515
+ return void 0;
21516
+ }
21517
+ function extractRawFrontmatterModel(frontmatter) {
21518
+ return findRawFrontmatterModel(frontmatter)?.value;
21519
+ }
21520
+ function toTargetModel(model, target, models) {
21521
+ if (model === void 0) return void 0;
21522
+ return mapModelToTarget(model, target, getModelCatalog(models)).value;
21523
+ }
21524
+ function describeInvalidName(label, target, profile) {
21525
+ if (!profile) {
21526
+ return {
21527
+ message: `${label} has a line break or control character, so it is omitted.`,
21528
+ suggestion: "Write the model name on one line, without control characters."
21529
+ };
21530
+ }
21531
+ return {
21532
+ message: `${label} maps to a name with a line break or control character on target "${target}", so it is omitted.`,
21533
+ suggestion: `Remove line breaks and control characters from models.profiles.${profile.id} in promptscript.yaml.`
21534
+ };
21535
+ }
21536
+ function describeOmission(label, target, mapped) {
21537
+ if (mapped.issue === "invalid-name") return describeInvalidName(label, target, mapped.profile);
21538
+ if (mapped.issue !== "unsupported-provider" || !mapped.profile) return void 0;
21539
+ const providers = (getModelTargetScheme(target)?.providers ?? []).map((provider) => `"${provider}"`).join(" or ");
21540
+ return {
21541
+ message: `${label} comes from provider "${mapped.profile.provider}", which target "${target}" cannot run, so it is omitted.`,
21542
+ suggestion: `Use a model from provider ${providers}, or set models.profiles.${mapped.profile.id}.targets.${target} in promptscript.yaml.`
21543
+ };
21544
+ }
21545
+ function blockEntries(ast, name) {
21546
+ const block = ast.blocks.find((candidate) => candidate.name === name);
21547
+ if (!block) return [];
21548
+ if (block.content.type !== "ObjectContent" && block.content.type !== "MixedContent") return [];
21549
+ return Object.entries(block.content.properties).flatMap(
21550
+ ([entryName, value]) => value && typeof value === "object" && !Array.isArray(value) ? [
21551
+ [entryName, value, block]
21552
+ ] : []
21553
+ );
21554
+ }
21555
+ function emitsResource(target, kind, version) {
21556
+ return TARGET_CAPABILITIES[target].resources.some(
21557
+ (resource) => resource.kind === kind && resource.versions.includes(version)
21558
+ );
21559
+ }
21560
+ function modelReferenceWarning(owner, field, value, block, target, catalog) {
21561
+ if (typeof value !== "string") return void 0;
21562
+ const described = describeOmission(
21563
+ `${owner}: ${field} ${JSON.stringify(value.trim())}`,
21564
+ target,
21565
+ mapModelToTarget(value, target, catalog)
21566
+ );
21567
+ return described && {
21568
+ code: MODEL_COMPATIBILITY_CODE,
21569
+ ruleName: "model-compatibility",
21570
+ ...described,
21571
+ location: block.loc
21572
+ };
21573
+ }
21574
+ function agentModelWarnings(ast, target, version, catalog) {
21575
+ if (!emitsResource(target, "agents", version)) return [];
21576
+ const fields = AGENT_MODEL_FIELDS.filter(
21577
+ (field) => getAgentFieldStatus(target, field) !== "not-supported"
21578
+ );
21579
+ return blockEntries(ast, "agents").flatMap(
21580
+ ([name, entry, block]) => fields.flatMap((field) => {
21581
+ const warning = modelReferenceWarning(
21582
+ `Agent "${name}"`,
21583
+ field,
21584
+ entry[field],
21585
+ block,
21586
+ target,
21587
+ catalog
21588
+ );
21589
+ return warning ? [warning] : [];
21590
+ })
21591
+ );
21592
+ }
21593
+ function frontmatterModelWarning(name, entry, block, target, catalog) {
21594
+ if (entry["model"] !== void 0) return void 0;
21595
+ const frontmatter = typeof entry["__rawFrontmatter"] === "string" ? entry["__rawFrontmatter"] : void 0;
21596
+ const rawModel = frontmatter !== void 0 ? extractRawFrontmatterModel(frontmatter) : void 0;
21597
+ return rawModel === void 0 ? void 0 : modelReferenceWarning(`Skill "${name}"`, "model", rawModel, block, target, catalog);
21598
+ }
21599
+ function skillModelWarnings(ast, target, version, catalog) {
21600
+ if (!SKILL_MODEL_TARGETS.has(target) || !emitsResource(target, "skills", version)) return [];
21601
+ return blockEntries(ast, "skills").flatMap(([name, entry, block]) => {
21602
+ const warnings = [
21603
+ modelReferenceWarning(`Skill "${name}"`, "model", entry["model"], block, target, catalog),
21604
+ frontmatterModelWarning(name, entry, block, target, catalog)
21605
+ ].flatMap((warning) => warning ? [warning] : []);
21606
+ return warnings;
21607
+ });
21608
+ }
21609
+ function getModelCompatibilityWarnings(ast, target, version, models) {
21610
+ if (!isKnownTarget(target) || !getModelTargetScheme(target)) return [];
21611
+ const catalog = getModelCatalog(models);
21612
+ return [
21613
+ ...agentModelWarnings(ast, target, version, catalog),
21614
+ ...skillModelWarnings(ast, target, version, catalog)
21615
+ ];
21616
+ }
21617
+ function appendModelCompatibilityWarnings(output, ast, target, version, models) {
21618
+ const warnings = getModelCompatibilityWarnings(ast, target, version, models);
21619
+ if (warnings.length === 0) return output;
21620
+ return { ...output, warnings: [...output.warnings ?? [], ...warnings] };
21621
+ }
21622
+ var MODEL_COMPATIBILITY_CODE, AGENT_MODEL_FIELDS, SKILL_MODEL_TARGETS, MODEL_KEY;
21623
+ var init_model_mapping = __esm({
21624
+ "packages/formatters/src/model-mapping.ts"() {
21625
+ "use strict";
21626
+ init_src();
21627
+ MODEL_COMPATIBILITY_CODE = "PS4004";
21628
+ AGENT_MODEL_FIELDS = ["model", "specModel"];
21629
+ SKILL_MODEL_TARGETS = /* @__PURE__ */ new Set(["claude", "grok"]);
21630
+ MODEL_KEY = "model";
21631
+ }
21632
+ });
21633
+
20770
21634
  // packages/formatters/src/mcp-helpers.ts
20771
21635
  function findMcpServersBlock(ast) {
20772
21636
  return ast.blocks.find((b) => b.name === "mcpServers");
@@ -20951,6 +21815,7 @@ var init_markdown_instruction_formatter = __esm({
20951
21815
  init_hook_adapters();
20952
21816
  init_hook_capability_warnings();
20953
21817
  init_agent_capability_warnings();
21818
+ init_model_mapping();
20954
21819
  init_mcp_helpers();
20955
21820
  init_section_title_resolver();
20956
21821
  MarkdownInstructionFormatter = class extends BaseFormatter {
@@ -20998,7 +21863,14 @@ var init_markdown_instruction_formatter = __esm({
20998
21863
  const agentOutput = appendAgentCapabilityWarnings(warnedOutput, ast, this.name, version, {
20999
21864
  blockWarningHandled: (this.config.unsupportedBlocks ?? []).includes("agents")
21000
21865
  });
21001
- return appendTargetHookCapabilityWarnings(agentOutput, ast, this.name, version);
21866
+ const modelOutput = appendModelCompatibilityWarnings(
21867
+ agentOutput,
21868
+ ast,
21869
+ this.name,
21870
+ version,
21871
+ options?.models
21872
+ );
21873
+ return appendTargetHookCapabilityWarnings(modelOutput, ast, this.name, version);
21002
21874
  }
21003
21875
  hasEnabledHooks(ast) {
21004
21876
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
@@ -21138,7 +22010,7 @@ var init_markdown_instruction_formatter = __esm({
21138
22010
  }
21139
22011
  }
21140
22012
  if (this.config.hasAgents) {
21141
- const agents = this.extractAgents(ast);
22013
+ const agents = this.extractAgents(ast, options);
21142
22014
  for (const agent of agents) {
21143
22015
  additionalFiles.push(this.generateAgentFile(agent));
21144
22016
  }
@@ -21278,27 +22150,32 @@ var init_markdown_instruction_formatter = __esm({
21278
22150
  // ============================================================
21279
22151
  // Agent Extraction & File Generation
21280
22152
  // ============================================================
21281
- extractAgents(ast) {
22153
+ extractAgents(ast, _options) {
21282
22154
  const agentsBlock = this.findBlock(ast, "agents");
21283
22155
  if (!agentsBlock) return [];
21284
22156
  const agents = [];
21285
22157
  const props = this.getProps(agentsBlock.content);
21286
22158
  const nativeNames = this.getNativeAgentNameMap(ast);
21287
22159
  for (const [name, value] of Object.entries(props)) {
21288
- if (value && typeof value === "object" && !Array.isArray(value)) {
21289
- if (!this.isSafeAgentName(name)) continue;
21290
- const obj = value;
21291
- const description = obj["description"] ? this.valueToString(obj["description"]) : "";
21292
- if (!description) continue;
21293
- agents.push({
21294
- name: nativeNames.get(name) ?? name,
21295
- description,
21296
- content: obj["content"] ? this.valueToString(obj["content"]) : ""
21297
- });
21298
- }
22160
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
22161
+ if (!this.isSafeAgentName(name)) continue;
22162
+ const agent = this.parseAgent(nativeNames.get(name) ?? name, value);
22163
+ if (agent) agents.push(agent);
21299
22164
  }
21300
22165
  return agents;
21301
22166
  }
22167
+ /**
22168
+ * Build one agent config, or undefined when it has no description.
22169
+ */
22170
+ parseAgent(nativeName, obj) {
22171
+ const description = obj["description"] ? this.valueToString(obj["description"]) : "";
22172
+ if (!description) return void 0;
22173
+ return {
22174
+ name: nativeName,
22175
+ description,
22176
+ content: obj["content"] ? this.valueToString(obj["content"]) : ""
22177
+ };
22178
+ }
21302
22179
  generateAgentFile(config) {
21303
22180
  const lines = [];
21304
22181
  lines.push("---");
@@ -22247,7 +23124,7 @@ var init_feature_matrix = __esm({
22247
23124
  });
22248
23125
 
22249
23126
  // packages/formatters/src/formatters/github.ts
22250
- var GITHUB_VERSIONS, TOOL_NAME_MAPPING, MODEL_NAME_MAPPING, GitHubFormatter;
23127
+ var GITHUB_VERSIONS, TOOL_NAME_MAPPING, GitHubFormatter;
22251
23128
  var init_github2 = __esm({
22252
23129
  "packages/formatters/src/formatters/github.ts"() {
22253
23130
  "use strict";
@@ -22257,6 +23134,7 @@ var init_github2 = __esm({
22257
23134
  init_hook_adapters();
22258
23135
  init_hook_capability_warnings();
22259
23136
  init_agent_capability_warnings();
23137
+ init_model_mapping();
22260
23138
  init_section_title_resolver();
22261
23139
  GITHUB_VERSIONS = {
22262
23140
  simple: {
@@ -22298,28 +23176,6 @@ var init_github2 = __esm({
22298
23176
  TodoWrite: "todo",
22299
23177
  TodoRead: "todo"
22300
23178
  };
22301
- MODEL_NAME_MAPPING = {
22302
- // Claude models - default aliases map to latest versions (4.5)
22303
- sonnet: "Claude Sonnet 4.5",
22304
- opus: "Claude Opus 4.5",
22305
- haiku: "Claude Haiku 4.5",
22306
- // Explicit version mappings
22307
- "sonnet-4": "Claude Sonnet 4",
22308
- "sonnet-4.5": "Claude Sonnet 4.5",
22309
- "opus-4": "Claude Opus 4",
22310
- "opus-4.5": "Claude Opus 4.5",
22311
- "haiku-4": "Claude Haiku 4",
22312
- "haiku-4.5": "Claude Haiku 4.5",
22313
- // OpenAI models (pass through if already in correct format)
22314
- "gpt-4o": "GPT-4o",
22315
- "gpt-4.1": "GPT-4.1",
22316
- "gpt-5": "GPT-5",
22317
- "gpt-5-mini": "GPT-5 mini",
22318
- // Special values
22319
- inherit: "",
22320
- // Empty string means omit the model property
22321
- auto: "Auto"
22322
- };
22323
23179
  GitHubFormatter = class extends BaseFormatter {
22324
23180
  name = "github";
22325
23181
  outputPath = ".github/copilot-instructions.md";
@@ -22352,6 +23208,7 @@ var init_github2 = __esm({
22352
23208
  }
22353
23209
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
22354
23210
  output = appendAgentCapabilityWarnings(output, ast, this.name, version);
23211
+ output = appendModelCompatibilityWarnings(output, ast, this.name, version, options?.models);
22355
23212
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
22356
23213
  const hooks = hooksBlock ? extractHooks(hooksBlock) : [];
22357
23214
  const hookWarnings = hooksBlock && version !== "simple" ? [
@@ -22450,7 +23307,7 @@ var init_github2 = __esm({
22450
23307
  for (const skill of skills2) {
22451
23308
  additionalFiles.push(this.generateCopilotSkillFile(skill, options));
22452
23309
  }
22453
- const customAgents = this.extractCustomAgents(ast);
23310
+ const customAgents = this.extractCustomAgents(ast, options);
22454
23311
  for (const agent of customAgents) {
22455
23312
  additionalFiles.push(this.generateCustomAgentFile(agent));
22456
23313
  }
@@ -22824,7 +23681,7 @@ ${f.content}`
22824
23681
  *
22825
23682
  * @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-custom-agents
22826
23683
  */
22827
- extractCustomAgents(ast) {
23684
+ extractCustomAgents(ast, options) {
22828
23685
  const agentsBlock = this.findBlock(ast, "agents");
22829
23686
  if (!agentsBlock) return [];
22830
23687
  const agents = [];
@@ -22833,36 +23690,43 @@ ${f.content}`
22833
23690
  const mcpServersBlock = findMcpServersBlock(ast);
22834
23691
  const availableMcpServers = mcpServersBlock ? extractMcpServers(mcpServersBlock) : [];
22835
23692
  for (const [name, value] of Object.entries(props)) {
22836
- if (value && typeof value === "object" && !Array.isArray(value)) {
22837
- if (!this.isSafeAgentName(name)) continue;
22838
- const obj = value;
22839
- const description = obj["description"] ? this.valueToString(obj["description"]) : "";
22840
- if (!description) continue;
22841
- const handoffs = this.extractHandoffs(obj["handoffs"]).map((handoff) => ({
22842
- ...handoff,
22843
- agent: this.getNativeAgentName(ast, handoff.agent)
22844
- }));
22845
- const mcpServersValue = obj["mcpServers"];
22846
- const mcpServerNames = Array.isArray(mcpServersValue) ? mcpServersValue.filter(
22847
- (serverName) => typeof serverName === "string"
22848
- ) : [];
22849
- const mcpServers = availableMcpServers.filter(
22850
- (server) => mcpServerNames.includes(server.name)
22851
- );
22852
- agents.push({
22853
- name: nativeNames.get(name) ?? name,
22854
- description,
22855
- tools: this.parseToolsArray(obj["tools"]),
22856
- model: obj["model"] ? this.valueToString(obj["model"]) : void 0,
22857
- specModel: obj["specModel"] ? this.valueToString(obj["specModel"]) : void 0,
22858
- handoffs: handoffs.length > 0 ? handoffs : void 0,
22859
- mcpServers: mcpServers.length > 0 ? mcpServers : void 0,
22860
- content: obj["content"] ? this.valueToString(obj["content"]) : ""
22861
- });
22862
- }
23693
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
23694
+ if (!this.isSafeAgentName(name)) continue;
23695
+ const agent = this.parseCustomAgent(
23696
+ ast,
23697
+ nativeNames.get(name) ?? name,
23698
+ value,
23699
+ availableMcpServers,
23700
+ options
23701
+ );
23702
+ if (agent) agents.push(agent);
22863
23703
  }
22864
23704
  return agents;
22865
23705
  }
23706
+ /**
23707
+ * Build one custom agent config, or undefined when it has no description.
23708
+ */
23709
+ parseCustomAgent(ast, nativeName, obj, availableMcpServers, options) {
23710
+ const description = obj["description"] ? this.valueToString(obj["description"]) : "";
23711
+ if (!description) return void 0;
23712
+ const handoffs = this.extractHandoffs(obj["handoffs"]).map((handoff) => ({
23713
+ ...handoff,
23714
+ agent: this.getNativeAgentName(ast, handoff.agent)
23715
+ }));
23716
+ const mcpServersValue = obj["mcpServers"];
23717
+ const mcpServerNames = Array.isArray(mcpServersValue) ? mcpServersValue.filter((serverName) => typeof serverName === "string") : [];
23718
+ const mcpServers = availableMcpServers.filter((server) => mcpServerNames.includes(server.name));
23719
+ return {
23720
+ name: nativeName,
23721
+ description,
23722
+ tools: this.parseToolsArray(obj["tools"]),
23723
+ model: this.copilotModel(obj["model"], options),
23724
+ specModel: this.copilotModel(obj["specModel"], options),
23725
+ handoffs: handoffs.length > 0 ? handoffs : void 0,
23726
+ mcpServers: mcpServers.length > 0 ? mcpServers : void 0,
23727
+ content: obj["content"] ? this.valueToString(obj["content"]) : ""
23728
+ };
23729
+ }
22866
23730
  /**
22867
23731
  * Parse tools array from a Value and map to GitHub Copilot canonical names.
22868
23732
  *
@@ -22919,15 +23783,14 @@ ${f.content}`
22919
23783
  return lines;
22920
23784
  }
22921
23785
  /**
22922
- * Map a model name from PromptScript/Claude Code format to GitHub Copilot format.
23786
+ * Map a model reference to its Copilot model name through the model catalog.
23787
+ * Names missing from the catalog pass through, so Copilot names work as-is.
22923
23788
  *
22924
- * @returns The mapped model name, or undefined if the model should be omitted (e.g., "inherit")
23789
+ * @returns The Copilot model name, or undefined when the model is omitted (e.g., "inherit")
22925
23790
  */
22926
- mapModelName(model) {
22927
- if (!model) return void 0;
22928
- const mapped = MODEL_NAME_MAPPING[model.toLowerCase()];
22929
- if (mapped === "") return void 0;
22930
- return mapped ?? model;
23791
+ copilotModel(value, options) {
23792
+ if (!value) return void 0;
23793
+ return toTargetModel(this.valueToString(value), this.name, options?.models);
22931
23794
  }
22932
23795
  /**
22933
23796
  * Generate a .github/agents/<name>.md file.
@@ -22943,13 +23806,11 @@ ${f.content}`
22943
23806
  const toolsArray = config.tools.map((t) => `'${t}'`).join(", ");
22944
23807
  lines.push(`tools: [${toolsArray}]`);
22945
23808
  }
22946
- const mappedModel = this.mapModelName(config.model);
22947
- if (mappedModel) {
22948
- lines.push(`model: ${mappedModel}`);
23809
+ if (config.model) {
23810
+ lines.push(`model: ${this.yamlString(config.model)}`);
22949
23811
  }
22950
- const mappedSpecModel = this.mapModelName(config.specModel);
22951
- if (mappedSpecModel) {
22952
- lines.push(`specModel: ${mappedSpecModel}`);
23812
+ if (config.specModel) {
23813
+ lines.push(`specModel: ${this.yamlString(config.specModel)}`);
22953
23814
  }
22954
23815
  if (config.handoffs && config.handoffs.length > 0) {
22955
23816
  lines.push(...this.renderHandoffsYaml(config.handoffs));
@@ -23423,6 +24284,7 @@ var init_claude2 = __esm({
23423
24284
  init_hook_adapters();
23424
24285
  init_hook_capability_warnings();
23425
24286
  init_agent_capability_warnings();
24287
+ init_model_mapping();
23426
24288
  init_mcp_helpers();
23427
24289
  init_section_title_resolver();
23428
24290
  CLAUDE_VERSIONS = {
@@ -23443,6 +24305,16 @@ var init_claude2 = __esm({
23443
24305
  }
23444
24306
  };
23445
24307
  ClaudeFormatter = class extends BaseFormatter {
24308
+ /**
24309
+ * @param modelTarget - Target whose model names agent and skill files use.
24310
+ * Grok writes its agent and skill files through this formatter, so its
24311
+ * `models.profiles` names must apply to them.
24312
+ */
24313
+ constructor(modelTarget = "claude") {
24314
+ super();
24315
+ this.modelTarget = modelTarget;
24316
+ }
24317
+ modelTarget;
23446
24318
  name = "claude";
23447
24319
  outputPath = "CLAUDE.md";
23448
24320
  description = "Claude Code instructions (concise Markdown)";
@@ -23474,6 +24346,7 @@ var init_claude2 = __esm({
23474
24346
  }
23475
24347
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
23476
24348
  output = appendAgentCapabilityWarnings(output, ast, this.name, version);
24349
+ output = appendModelCompatibilityWarnings(output, ast, this.name, version, options?.models);
23477
24350
  output = {
23478
24351
  ...output,
23479
24352
  managedOutputFiles: [
@@ -23549,7 +24422,7 @@ var init_claude2 = __esm({
23549
24422
  for (const skill of skills2) {
23550
24423
  additionalFiles.push(this.generateClaudeSkillFile(skill, options));
23551
24424
  }
23552
- const agents = this.extractAgents(ast);
24425
+ const agents = this.extractAgents(ast, options);
23553
24426
  for (const agent of agents) {
23554
24427
  additionalFiles.push(this.generateAgentFile(agent));
23555
24428
  }
@@ -23790,7 +24663,7 @@ var init_claude2 = __esm({
23790
24663
  userInvocable: obj["userInvocable"] === true,
23791
24664
  disableModelInvocation: obj["disableModelInvocation"] === true,
23792
24665
  argumentHint: obj["argumentHint"] ? this.valueToString(obj["argumentHint"]) : void 0,
23793
- model: obj["model"] ? this.valueToString(obj["model"]) : void 0,
24666
+ model: this.claudeModel(obj["model"], options),
23794
24667
  content: obj["content"] ? this.valueToString(obj["content"]) : "",
23795
24668
  resources: obj["resources"] && Array.isArray(obj["resources"]) ? obj["resources"].map((r) => ({
23796
24669
  relativePath: r["relativePath"],
@@ -23804,6 +24677,31 @@ var init_claude2 = __esm({
23804
24677
  }
23805
24678
  return skills2;
23806
24679
  }
24680
+ /**
24681
+ * Apply the model field to raw SKILL.md frontmatter.
24682
+ *
24683
+ * A `.prs` model wins over the frontmatter one; without it, the
24684
+ * frontmatter value is mapped through the model catalog for this target.
24685
+ * Unmappable values drop the whole property, block scalars included,
24686
+ * and PS4004 reports why; frontmatter without a model line gains one
24687
+ * from `.prs`. Empty values are left untouched.
24688
+ */
24689
+ applyRawFrontmatterModel(rawFrontmatter, prsModel, options) {
24690
+ const field = findRawFrontmatterModel(rawFrontmatter);
24691
+ if (field === void 0) {
24692
+ return prsModel === void 0 ? rawFrontmatter : `${rawFrontmatter}
24693
+ model: ${this.yamlString(prsModel)}`;
24694
+ }
24695
+ if (field.value === void 0) return rawFrontmatter;
24696
+ const lines = rawFrontmatter.split(/\r?\n/);
24697
+ const mapped = prsModel ?? this.claudeModel(field.value, options);
24698
+ if (mapped === void 0) {
24699
+ lines.splice(field.keyIndex, field.lineCount);
24700
+ } else {
24701
+ lines.splice(field.keyIndex, field.lineCount, `model: ${this.yamlString(mapped)}`);
24702
+ }
24703
+ return lines.join("\n");
24704
+ }
23807
24705
  /**
23808
24706
  * Generate a .claude/skills/<name>/SKILL.md file.
23809
24707
  */
@@ -23811,7 +24709,7 @@ var init_claude2 = __esm({
23811
24709
  const lines = [];
23812
24710
  lines.push("---");
23813
24711
  if (config.rawFrontmatter) {
23814
- lines.push(config.rawFrontmatter);
24712
+ lines.push(this.applyRawFrontmatterModel(config.rawFrontmatter, config.model, options));
23815
24713
  } else {
23816
24714
  lines.push(`name: '${config.name}'`);
23817
24715
  const descQuote = config.description.includes("'") ? '"' : "'";
@@ -23838,7 +24736,7 @@ var init_claude2 = __esm({
23838
24736
  lines.push(`argument-hint: '${config.argumentHint}'`);
23839
24737
  }
23840
24738
  if (config.model) {
23841
- lines.push(`model: ${config.model}`);
24739
+ lines.push(`model: ${this.yamlString(config.model)}`);
23842
24740
  }
23843
24741
  }
23844
24742
  lines.push("---");
@@ -23936,7 +24834,7 @@ ${f.content}`
23936
24834
  /**
23937
24835
  * Extract agent configurations from @agents block.
23938
24836
  */
23939
- extractAgents(ast) {
24837
+ extractAgents(ast, options) {
23940
24838
  const agentsBlock = this.findBlock(ast, "agents");
23941
24839
  if (!agentsBlock) return [];
23942
24840
  const agents = [];
@@ -23946,7 +24844,7 @@ ${f.content}`
23946
24844
  if (value && typeof value === "object" && !Array.isArray(value)) {
23947
24845
  if (!this.isSafeAgentName(name)) continue;
23948
24846
  const obj = value;
23949
- const agent = this.parseAgentConfig(nativeNames.get(name) ?? name, obj);
24847
+ const agent = this.parseAgentConfig(nativeNames.get(name) ?? name, obj, options);
23950
24848
  if (agent) {
23951
24849
  agents.push(agent);
23952
24850
  }
@@ -23957,7 +24855,7 @@ ${f.content}`
23957
24855
  /**
23958
24856
  * Parse a single agent configuration from object properties.
23959
24857
  */
23960
- parseAgentConfig(name, obj) {
24858
+ parseAgentConfig(name, obj, options) {
23961
24859
  const description = obj["description"] ? this.valueToString(obj["description"]) : "";
23962
24860
  if (!description) return null;
23963
24861
  return {
@@ -23965,7 +24863,7 @@ ${f.content}`
23965
24863
  description,
23966
24864
  tools: this.parseStringArray(obj["tools"]),
23967
24865
  disallowedTools: this.parseStringArray(obj["disallowedTools"]),
23968
- model: this.parseAgentModel(obj["model"]),
24866
+ model: this.claudeModel(obj["model"], options),
23969
24867
  permissionMode: this.parsePermissionMode(obj["permissionMode"]),
23970
24868
  skills: this.parseStringArray(obj["skills"]),
23971
24869
  maxTurns: obj["maxTurns"] !== void 0 && typeof obj["maxTurns"] === "number" ? obj["maxTurns"] : void 0,
@@ -23985,13 +24883,13 @@ ${f.content}`
23985
24883
  return arr.length > 0 ? arr : void 0;
23986
24884
  }
23987
24885
  /**
23988
- * Parse model value, validating it's a known model.
24886
+ * Map a model reference to a Claude Code model value through the model
24887
+ * catalog. Floating aliases, inherit, and names missing from the catalog
24888
+ * stay as written; models from other providers are omitted (PS4004).
23989
24889
  */
23990
- parseAgentModel(value) {
24890
+ claudeModel(value, options) {
23991
24891
  if (!value) return void 0;
23992
- const str = this.valueToString(value);
23993
- const validModels = ["sonnet", "opus", "haiku", "inherit"];
23994
- return validModels.includes(str) ? str : void 0;
24892
+ return toTargetModel(this.valueToString(value), this.modelTarget, options?.models);
23995
24893
  }
23996
24894
  /**
23997
24895
  * Parse permission mode value.
@@ -24036,7 +24934,7 @@ ${f.content}`
24036
24934
  lines.push(`disallowedTools: [${arr}]`);
24037
24935
  }
24038
24936
  if (config.model) {
24039
- lines.push(`model: ${config.model}`);
24937
+ lines.push(`model: ${this.yamlString(config.model)}`);
24040
24938
  }
24041
24939
  if (config.permissionMode) {
24042
24940
  lines.push(`permissionMode: ${config.permissionMode}`);
@@ -24520,6 +25418,7 @@ var init_cursor2 = __esm({
24520
25418
  init_hook_adapters();
24521
25419
  init_hook_capability_warnings();
24522
25420
  init_agent_capability_warnings();
25421
+ init_model_mapping();
24523
25422
  init_mcp_helpers();
24524
25423
  init_plugin_helpers();
24525
25424
  init_section_title_resolver();
@@ -24628,6 +25527,7 @@ var init_cursor2 = __esm({
24628
25527
  }
24629
25528
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
24630
25529
  output = appendAgentCapabilityWarnings(output, ast, this.name, version);
25530
+ output = appendModelCompatibilityWarnings(output, ast, this.name, version, options?.models);
24631
25531
  return {
24632
25532
  ...output,
24633
25533
  managedOutputFiles: [
@@ -24797,7 +25697,8 @@ ${content}`);
24797
25697
  agentLines.push(`name: ${nativeAgentName}`);
24798
25698
  if (description) agentLines.push(`description: ${JSON.stringify(description)}`);
24799
25699
  const model = obj["model"];
24800
- if (typeof model === "string") agentLines.push(`model: ${model}`);
25700
+ const cursorModel = typeof model === "string" ? toTargetModel(model, this.name, options?.models) : void 0;
25701
+ if (cursorModel) agentLines.push(`model: ${this.yamlString(cursorModel)}`);
24801
25702
  const agentMcp = obj["mcpServers"];
24802
25703
  if (agentMcp && typeof agentMcp === "object" && !Array.isArray(agentMcp)) {
24803
25704
  const mcpNames = Object.keys(agentMcp);
@@ -26355,6 +27256,7 @@ var init_factory = __esm({
26355
27256
  init_mcp_helpers();
26356
27257
  init_plugin_helpers();
26357
27258
  init_section_title_resolver();
27259
+ init_model_mapping();
26358
27260
  FACTORY_VERSIONS = {
26359
27261
  simple: {
26360
27262
  name: "simple",
@@ -26702,32 +27604,45 @@ ${f.content}`
26702
27604
  // ============================================================
26703
27605
  // Droid File Generation (Factory-specific)
26704
27606
  // ============================================================
26705
- extractAgents(ast) {
27607
+ extractAgents(ast, options) {
26706
27608
  const agentsBlock = this.findBlock(ast, "agents");
26707
27609
  if (!agentsBlock) return [];
26708
27610
  const droids = [];
26709
27611
  const props = this.getProps(agentsBlock.content);
26710
27612
  for (const [name, value] of Object.entries(props)) {
26711
27613
  if (!this.isSafeAgentName(name)) continue;
26712
- if (value && typeof value === "object" && !Array.isArray(value)) {
26713
- const obj = value;
26714
- const description = obj["description"] ? this.valueToString(obj["description"]) : "";
26715
- if (!description) continue;
26716
- droids.push({
26717
- name: this.getNativeAgentName(ast, name),
26718
- description,
26719
- content: obj["content"] ? this.valueToString(obj["content"]) : "",
26720
- model: obj["model"] ? this.valueToString(obj["model"]) : void 0,
26721
- reasoningEffort: this.parseReasoningEffort(obj["reasoningEffort"]),
26722
- specModel: obj["specModel"] ? this.valueToString(obj["specModel"]) : void 0,
26723
- specReasoningEffort: this.parseReasoningEffort(obj["specReasoningEffort"]),
26724
- tools: this.parseDroidTools(obj["tools"]),
26725
- mcpServers: this.parseMcpServerNames(obj["mcpServers"])
26726
- });
26727
- }
27614
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
27615
+ const droid = this.parseDroid(ast, name, value, options);
27616
+ if (droid) droids.push(droid);
26728
27617
  }
26729
27618
  return droids;
26730
27619
  }
27620
+ /**
27621
+ * Build one droid config, or undefined when it has no description.
27622
+ */
27623
+ parseDroid(ast, name, obj, options) {
27624
+ const description = obj["description"] ? this.valueToString(obj["description"]) : "";
27625
+ if (!description) return void 0;
27626
+ return {
27627
+ name: this.getNativeAgentName(ast, name),
27628
+ description,
27629
+ content: obj["content"] ? this.valueToString(obj["content"]) : "",
27630
+ model: this.droidModel(obj["model"], options),
27631
+ reasoningEffort: this.parseReasoningEffort(obj["reasoningEffort"]),
27632
+ specModel: this.droidModel(obj["specModel"], options),
27633
+ specReasoningEffort: this.parseReasoningEffort(obj["specReasoningEffort"]),
27634
+ tools: this.parseDroidTools(obj["tools"]),
27635
+ mcpServers: this.parseMcpServerNames(obj["mcpServers"])
27636
+ };
27637
+ }
27638
+ /**
27639
+ * Map a model reference to a Factory model ID through the model catalog.
27640
+ * Names missing from the catalog pass through unchanged.
27641
+ */
27642
+ droidModel(value, options) {
27643
+ if (!value) return void 0;
27644
+ return toTargetModel(this.valueToString(value), this.name, options?.models);
27645
+ }
26731
27646
  generateAgentFile(config) {
26732
27647
  const droidConfig = config;
26733
27648
  const lines = [];
@@ -26737,13 +27652,13 @@ ${f.content}`
26737
27652
  lines.push(`description: ${this.yamlString(droidConfig.description)}`);
26738
27653
  }
26739
27654
  if (droidConfig.model) {
26740
- lines.push(`model: ${droidConfig.model}`);
27655
+ lines.push(`model: ${this.yamlString(droidConfig.model)}`);
26741
27656
  }
26742
27657
  if (droidConfig.reasoningEffort) {
26743
27658
  lines.push(`reasoningEffort: ${droidConfig.reasoningEffort}`);
26744
27659
  }
26745
27660
  if (droidConfig.specModel) {
26746
- lines.push(`specModel: ${droidConfig.specModel}`);
27661
+ lines.push(`specModel: ${this.yamlString(droidConfig.specModel)}`);
26747
27662
  }
26748
27663
  if (droidConfig.specReasoningEffort) {
26749
27664
  lines.push(`specReasoningEffort: ${droidConfig.specReasoningEffort}`);
@@ -27078,7 +27993,7 @@ ${trimmed}`;
27078
27993
  additionalFiles.push(this.generateSkillFile(skill, options));
27079
27994
  }
27080
27995
  if (this.config.hasAgents) {
27081
- const agents = this.extractAgents(ast);
27996
+ const agents = this.extractAgents(ast, options);
27082
27997
  for (const agent of agents) {
27083
27998
  additionalFiles.push(this.generateAgentFile(agent));
27084
27999
  }
@@ -27736,7 +28651,7 @@ function agentMcpServerLines(mcpServers, resolvedMcpServers) {
27736
28651
  }
27737
28652
  return [];
27738
28653
  }
27739
- function serializeAgentToml(agentName, agent, resolvedMcpServers) {
28654
+ function serializeAgentToml(agentName, agent, resolvedMcpServers, models) {
27740
28655
  const lines = [];
27741
28656
  lines.push(`name = "${escapeTomlString2(agentName)}"`);
27742
28657
  const description = agent["description"];
@@ -27745,8 +28660,9 @@ function serializeAgentToml(agentName, agent, resolvedMcpServers) {
27745
28660
  }
27746
28661
  lines.push(...agentInstructionsLines(agent["content"]));
27747
28662
  const model = agent["model"];
27748
- if (typeof model === "string") {
27749
- lines.push(`model = "${escapeTomlString2(model)}"`);
28663
+ const codexModel = typeof model === "string" ? toTargetModel(model, "codex", models) : void 0;
28664
+ if (codexModel) {
28665
+ lines.push(`model = "${escapeTomlString2(codexModel)}"`);
27750
28666
  }
27751
28667
  const reasoningEffort = agent["reasoningEffort"];
27752
28668
  if (typeof reasoningEffort === "string") {
@@ -27786,6 +28702,7 @@ var init_codex = __esm({
27786
28702
  init_hook_adapters();
27787
28703
  init_hook_capability_warnings();
27788
28704
  init_agent_capability_warnings();
28705
+ init_model_mapping();
27789
28706
  init_mcp_helpers();
27790
28707
  init_plugin_helpers();
27791
28708
  CODEX_VERSIONS = {
@@ -27974,7 +28891,12 @@ var init_codex = __esm({
27974
28891
  if (!isValidAgentName(agentName)) continue;
27975
28892
  const agent = agentValue;
27976
28893
  const nativeAgentName = nativeNames.get(agentName) ?? agentName;
27977
- const toml = serializeAgentToml(nativeAgentName, agent, resolvedMcpServers);
28894
+ const toml = serializeAgentToml(
28895
+ nativeAgentName,
28896
+ agent,
28897
+ resolvedMcpServers,
28898
+ options?.models
28899
+ );
27978
28900
  extraFiles.push({
27979
28901
  path: `.codex/agents/${nativeAgentName}.toml`,
27980
28902
  content: toml
@@ -28729,6 +29651,7 @@ var init_grok = __esm({
28729
29651
  init_hook_adapters();
28730
29652
  init_hook_capability_warnings();
28731
29653
  init_agent_capability_warnings();
29654
+ init_model_mapping();
28732
29655
  GROK_VERSIONS = {
28733
29656
  simple: {
28734
29657
  name: "simple",
@@ -28751,7 +29674,7 @@ var init_grok = __esm({
28751
29674
  outputPath = "AGENTS.md";
28752
29675
  description = "Grok Build instructions (AGENTS.md + Claude delegation)";
28753
29676
  defaultConvention = "markdown";
28754
- claudeFormatter = new ClaudeFormatter();
29677
+ claudeFormatter = new ClaudeFormatter("grok");
28755
29678
  static getSupportedVersions() {
28756
29679
  return GROK_VERSIONS;
28757
29680
  }
@@ -28776,6 +29699,7 @@ var init_grok = __esm({
28776
29699
  }
28777
29700
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
28778
29701
  output = appendAgentCapabilityWarnings(output, ast, this.name, version);
29702
+ output = appendModelCompatibilityWarnings(output, ast, this.name, version, options?.models);
28779
29703
  return {
28780
29704
  ...output,
28781
29705
  managedOutputFiles: [
@@ -28809,7 +29733,7 @@ var init_grok = __esm({
28809
29733
  });
28810
29734
  const additionalFiles = [];
28811
29735
  if (claudeMultifile.path !== "AGENTS.md") {
28812
- additionalFiles.push(claudeMultifile);
29736
+ additionalFiles.push({ ...claudeMultifile, warnings: void 0 });
28813
29737
  }
28814
29738
  if (claudeMultifile.additionalFiles) {
28815
29739
  additionalFiles.push(...claudeMultifile.additionalFiles);
@@ -28833,7 +29757,7 @@ var init_grok = __esm({
28833
29757
  });
28834
29758
  const additionalFiles = [];
28835
29759
  if (claudeFull.path !== "AGENTS.md") {
28836
- additionalFiles.push({ ...claudeFull, additionalFiles: void 0 });
29760
+ additionalFiles.push({ ...claudeFull, additionalFiles: void 0, warnings: void 0 });
28837
29761
  }
28838
29762
  if (claudeFull.additionalFiles) {
28839
29763
  additionalFiles.push(
@@ -29341,7 +30265,7 @@ function formatMigrationHint(detection) {
29341
30265
  lines.push("");
29342
30266
  lines.push(" These can be migrated to PromptScript for unified management.");
29343
30267
  lines.push(" Run: prs migrate");
29344
- lines.push(" See: https://getpromptscript.dev/latest/guides/ai-migration-best-practices");
30268
+ lines.push(" See: https://getpromptscript.dev/guides/ai-migration-best-practices");
29345
30269
  return lines;
29346
30270
  }
29347
30271
  var PROMPTSCRIPT_MARKERS, INSTRUCTION_FILES, FILE_TOOL_NAMES, AI_TOOL_PATTERNS, INSTRUCTION_DIRECTORY_PATTERNS;
@@ -33577,7 +34501,7 @@ import { pathToFileURL as pathToFileURL2 } from "node:url";
33577
34501
  // packages/cli/package.json
33578
34502
  var package_default = {
33579
34503
  name: "@promptscript/cli",
33580
- version: "1.20.0",
34504
+ version: "1.21.0",
33581
34505
  description: "CLI for PromptScript - standardize AI instructions across GitHub Copilot, Claude, Cursor and other AI tools",
33582
34506
  keywords: [
33583
34507
  "cli",
@@ -33629,12 +34553,12 @@ var package_default = {
33629
34553
  commander: "^15.0.0",
33630
34554
  minimatch: "^10.2.6",
33631
34555
  ora: "^9.4.1",
33632
- prettier: "^3.9.8",
34556
+ prettier: "^3.9.9",
33633
34557
  yaml: "^2.9.1"
33634
34558
  },
33635
34559
  devDependencies: {
33636
34560
  "@codecov/bundle-analyzer": "^2.0.1",
33637
- memfs: "^4.78.1"
34561
+ memfs: "^4.79.0"
33638
34562
  }
33639
34563
  };
33640
34564
 
@@ -36977,7 +37901,7 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
36977
37901
  if (document.contents === null) {
36978
37902
  return { fields: {}, fieldLocations, fieldItemLocations, fieldValueLocations };
36979
37903
  }
36980
- if (!isRecord5(value)) {
37904
+ if (!isRecord6(value)) {
36981
37905
  throw frontmatterError(
36982
37906
  "top-level frontmatter value must be a YAML mapping",
36983
37907
  sourceFile,
@@ -37135,7 +38059,7 @@ function enforceFrontmatterLimits(value, sourceFile, frontmatter, depth = 0, sta
37135
38059
  }
37136
38060
  }
37137
38061
  }
37138
- function isRecord5(value) {
38062
+ function isRecord6(value) {
37139
38063
  return typeof value === "object" && value !== null && !Array.isArray(value);
37140
38064
  }
37141
38065
  function createSafeRecord() {
@@ -37278,7 +38202,7 @@ function parseParamsField(fields, sourceFile, location) {
37278
38202
  if (!Object.hasOwn(fields, "params")) return void 0;
37279
38203
  const value = fields["params"];
37280
38204
  if (value === null) return [];
37281
- if (!isRecord5(value)) {
38205
+ if (!isRecord6(value)) {
37282
38206
  throw frontmatterError(
37283
38207
  'field "params" must be a mapping of parameter names',
37284
38208
  sourceFile,
@@ -37294,7 +38218,7 @@ function parseParamsField(fields, sourceFile, location) {
37294
38218
  location
37295
38219
  );
37296
38220
  }
37297
- if (!isRecord5(definition)) {
38221
+ if (!isRecord6(definition)) {
37298
38222
  throw frontmatterError(`parameter "${name}" must be a mapping`, sourceFile, location);
37299
38223
  }
37300
38224
  const paramType = parseTypedFieldType(definition, `parameter "${name}"`, sourceFile, location);
@@ -37333,7 +38257,7 @@ function parseContractFieldsField(fields, fieldName, sourceFile, location) {
37333
38257
  if (!Object.hasOwn(fields, fieldName)) return void 0;
37334
38258
  const value = fields[fieldName];
37335
38259
  if (value === null) return createSafeRecord();
37336
- if (!isRecord5(value)) {
38260
+ if (!isRecord6(value)) {
37337
38261
  throw frontmatterError(
37338
38262
  `field "${fieldName}" must be a mapping of fields`,
37339
38263
  sourceFile,
@@ -37342,7 +38266,7 @@ function parseContractFieldsField(fields, fieldName, sourceFile, location) {
37342
38266
  }
37343
38267
  const result = createSafeRecord();
37344
38268
  for (const [name, definition] of Object.entries(value)) {
37345
- if (!isRecord5(definition)) {
38269
+ if (!isRecord6(definition)) {
37346
38270
  throw frontmatterError(
37347
38271
  `${fieldName} field "${name}" must be a mapping`,
37348
38272
  sourceFile,
@@ -37446,7 +38370,7 @@ function toPromptValue(value, context, sourceFile, location) {
37446
38370
  if (Array.isArray(value)) {
37447
38371
  return value.map((item) => toPromptValue(item, context, sourceFile, location));
37448
38372
  }
37449
- if (isRecord5(value)) {
38373
+ if (isRecord6(value)) {
37450
38374
  const result = createSafeRecord();
37451
38375
  for (const [key, item] of Object.entries(value)) {
37452
38376
  result[key] = toPromptValue(item, context, sourceFile, location);
@@ -37472,7 +38396,7 @@ function readOptionalStringFromRecord(record, field, context, sourceFile, locati
37472
38396
  function parseMetadataField(fields, sourceFile, location, valueLocations) {
37473
38397
  if (!Object.hasOwn(fields, "metadata")) return void 0;
37474
38398
  const value = fields["metadata"];
37475
- if (!isRecord5(value)) {
38399
+ if (!isRecord6(value)) {
37476
38400
  throw frontmatterError('field "metadata" must be a mapping', sourceFile, location);
37477
38401
  }
37478
38402
  const metadata = createSafeRecord();
@@ -41537,7 +42461,7 @@ function createResolutionContext() {
41537
42461
  parsedFiles: /* @__PURE__ */ new Map()
41538
42462
  };
41539
42463
  }
41540
- function isRecord6(value) {
42464
+ function isRecord7(value) {
41541
42465
  return typeof value === "object" && value !== null && !Array.isArray(value);
41542
42466
  }
41543
42467
  function collectDependencyPaths(value, dependencies) {
@@ -41547,9 +42471,9 @@ function collectDependencyPaths(value, dependencies) {
41547
42471
  }
41548
42472
  return;
41549
42473
  }
41550
- if (!isRecord6(value)) return;
42474
+ if (!isRecord7(value)) return;
41551
42475
  const loc = value["loc"];
41552
- if (isRecord6(loc) && typeof loc["file"] === "string") {
42476
+ if (isRecord7(loc) && typeof loc["file"] === "string") {
41553
42477
  const file = loc["file"];
41554
42478
  if (isAbsolute7(file)) dependencies.add(file);
41555
42479
  }
@@ -46921,7 +47845,7 @@ var VALID_KINDS = [
46921
47845
  "registry-allowlist"
46922
47846
  ];
46923
47847
  var VALID_SEVERITIES = ["error", "warning"];
46924
- function isRecord7(value) {
47848
+ function isRecord8(value) {
46925
47849
  return typeof value === "object" && value !== null && !Array.isArray(value);
46926
47850
  }
46927
47851
  function isStringArray(value) {
@@ -46998,7 +47922,7 @@ function validateRegistryAllowlist(raw, name) {
46998
47922
  }
46999
47923
  function parseOnePolicy(raw, index, seenNames) {
47000
47924
  const errors = [];
47001
- if (!isRecord7(raw)) {
47925
+ if (!isRecord8(raw)) {
47002
47926
  errors.push(`Policy at index ${index}: must be an object`);
47003
47927
  return { policy: null, errors };
47004
47928
  }
@@ -47051,15 +47975,15 @@ function parsePolicies(input2) {
47051
47975
  }
47052
47976
 
47053
47977
  // packages/validator/src/policy/evaluator.ts
47054
- function isRecord8(value) {
47978
+ function isRecord9(value) {
47055
47979
  return typeof value === "object" && value !== null && !Array.isArray(value);
47056
47980
  }
47057
47981
  function isLayerTraceEntry(value) {
47058
- if (!isRecord8(value)) return false;
47982
+ if (!isRecord9(value)) return false;
47059
47983
  return typeof value["property"] === "string" && typeof value["source"] === "string" && typeof value["strategy"] === "string" && typeof value["action"] === "string";
47060
47984
  }
47061
47985
  function isComposedFromEntry(value) {
47062
- if (!isRecord8(value)) return false;
47986
+ if (!isRecord9(value)) return false;
47063
47987
  return typeof value["source"] === "string" && typeof value["phase"] === "string";
47064
47988
  }
47065
47989
  function getLayerTrace(skill) {
@@ -47167,7 +48091,7 @@ function evaluatePolicies(policies, ast) {
47167
48091
  const content = skillsBlock.content;
47168
48092
  const violations = [];
47169
48093
  for (const [skillName, skillValue] of Object.entries(content.properties)) {
47170
- if (!isRecord8(skillValue)) continue;
48094
+ if (!isRecord9(skillValue)) continue;
47171
48095
  const skill = skillValue;
47172
48096
  const baseSource = typeof skill["__baseSource"] === "string" ? skill["__baseSource"] : null;
47173
48097
  for (const policy of policies) {
@@ -48209,7 +49133,7 @@ function isShortcutObject(value) {
48209
49133
  function isShortcutNameConflict(existing, blockName, sourceName) {
48210
49134
  return existing.sourceName !== sourceName || existing.blockName === blockName;
48211
49135
  }
48212
- function validateShortcutValues(ctx, block, normalizedNames, targetNames) {
49136
+ function validateShortcutValues(ctx, block, normalizedNames, targetNames2) {
48213
49137
  if (block.content.type !== "ObjectContent") return;
48214
49138
  for (const [name, value] of Object.entries(block.content.properties)) {
48215
49139
  const normalizedName = name.replace(/^\/+/, "");
@@ -48229,7 +49153,7 @@ function validateShortcutValues(ctx, block, normalizedNames, targetNames) {
48229
49153
  });
48230
49154
  }
48231
49155
  const targetName = normalizedName.toLowerCase().replace(/\s+/g, "-");
48232
- const existingTargetName = targetNames.get(targetName);
49156
+ const existingTargetName = targetNames2.get(targetName);
48233
49157
  if (!hasNormalizedConflict && existingTargetName && isShortcutNameConflict(existingTargetName, block.name, name)) {
48234
49158
  ctx.report({
48235
49159
  message: `@${block.name} entry "${name}" resolves to the same target-normalized file name as @${existingTargetName.blockName} entry "${existingTargetName.sourceName}".`,
@@ -48238,7 +49162,7 @@ function validateShortcutValues(ctx, block, normalizedNames, targetNames) {
48238
49162
  severity: "error"
48239
49163
  });
48240
49164
  } else if (!existingTargetName) {
48241
- targetNames.set(targetName, {
49165
+ targetNames2.set(targetName, {
48242
49166
  blockName: block.name,
48243
49167
  sourceName: name
48244
49168
  });
@@ -48386,6 +49310,108 @@ var importExcludes = {
48386
49310
  }
48387
49311
  };
48388
49312
 
49313
+ // packages/validator/src/rules/valid-model-reference.ts
49314
+ init_src();
49315
+ var MODEL_FIELDS = {
49316
+ agents: { label: "Agent", fields: ["model", "specModel"] },
49317
+ skills: { label: "Skill", fields: ["model"] }
49318
+ };
49319
+ function blockEntries2(block) {
49320
+ if (block.content.type !== "ObjectContent" && block.content.type !== "MixedContent") return [];
49321
+ return Object.entries(block.content.properties).flatMap(
49322
+ ([name, value]) => value && typeof value === "object" && !Array.isArray(value) ? [[name, value]] : []
49323
+ );
49324
+ }
49325
+ function collectReferences(ctx) {
49326
+ const references = [];
49327
+ for (const block of ctx.ast.blocks) {
49328
+ const spec = Object.hasOwn(MODEL_FIELDS, block.name) ? MODEL_FIELDS[block.name] : void 0;
49329
+ if (!spec) continue;
49330
+ for (const [name, entry] of blockEntries2(block)) {
49331
+ for (const field of spec.fields) {
49332
+ const value = entry[field];
49333
+ if (typeof value === "string" && value.trim().length > 0) {
49334
+ references.push({
49335
+ owner: `${spec.label} "${name}"`,
49336
+ field,
49337
+ value: value.trim(),
49338
+ location: block.loc
49339
+ });
49340
+ }
49341
+ }
49342
+ }
49343
+ }
49344
+ return references;
49345
+ }
49346
+ var PROFILES_HINT = "or declare it under models.profiles in promptscript.yaml";
49347
+ var MODELS_DOC_LINK = "https://getpromptscript.dev/reference/models/";
49348
+ function readSupportedModels(config, catalog) {
49349
+ if (!Array.isArray(config?.supported)) return void 0;
49350
+ const entries = config.supported.filter((entry) => typeof entry === "string");
49351
+ return { entries, set: resolveModelSet(entries, catalog) };
49352
+ }
49353
+ function reportProfileIssues(ctx, catalog) {
49354
+ for (const issue of validateModelCatalog(catalog.profiles)) {
49355
+ ctx.report({
49356
+ message: `models.profiles: ${issue}`,
49357
+ suggestion: "Update the profile under models.profiles in promptscript.yaml."
49358
+ });
49359
+ }
49360
+ }
49361
+ function reportLifecycle(ctx, label, location, profile, catalog) {
49362
+ if (profile.status !== "retired" && profile.status !== "deprecated") return;
49363
+ const replacement = catalog.getReplacement(profile.id);
49364
+ ctx.report({
49365
+ message: `${label} resolves to ${profile.displayName}, which is ${profile.status}`,
49366
+ location,
49367
+ suggestion: replacement ? `Switch to ${replacement.id} (${replacement.displayName}).` : "Switch to a current model."
49368
+ });
49369
+ }
49370
+ function checkReference(ctx, reference, catalog, supported) {
49371
+ const label = `${reference.owner}: ${reference.field} "${reference.value}"`;
49372
+ const resolution = catalog.resolve(reference.value);
49373
+ if (!resolution) {
49374
+ if (supported) {
49375
+ ctx.report({
49376
+ message: `${label} is not in the model catalog`,
49377
+ location: reference.location,
49378
+ suggestion: `Use a model from models.supported, ${PROFILES_HINT}. See the model list: ${MODELS_DOC_LINK}`
49379
+ });
49380
+ }
49381
+ return;
49382
+ }
49383
+ if (resolution.kind === "inherit") return;
49384
+ reportLifecycle(ctx, label, reference.location, resolution.profile, catalog);
49385
+ if (supported && !isInModelSet(resolution, supported.set)) {
49386
+ ctx.report({
49387
+ message: `${label} is outside the supported model set`,
49388
+ location: reference.location,
49389
+ suggestion: `Use one of: ${supported.entries.join(", ")}; or add it to models.supported.`
49390
+ });
49391
+ }
49392
+ }
49393
+ var validModelReference = {
49394
+ id: "PS041",
49395
+ name: "valid-model-reference",
49396
+ description: "Model references must name served models from the project model set",
49397
+ defaultSeverity: "warning",
49398
+ validate: (ctx) => {
49399
+ const config = ctx.config.models;
49400
+ const catalog = getModelCatalog(config);
49401
+ const supported = readSupportedModels(config, catalog);
49402
+ if (config?.profiles !== void 0) reportProfileIssues(ctx, catalog);
49403
+ for (const entry of supported?.set.unknown ?? []) {
49404
+ ctx.report({
49405
+ message: `models.supported entry "${entry}" is not in the model catalog`,
49406
+ suggestion: `Use a catalog model id or alias, ${PROFILES_HINT}. See the model list: ${MODELS_DOC_LINK}`
49407
+ });
49408
+ }
49409
+ for (const reference of collectReferences(ctx)) {
49410
+ checkReference(ctx, reference, catalog, supported);
49411
+ }
49412
+ }
49413
+ };
49414
+
48389
49415
  // packages/validator/src/rules/index.ts
48390
49416
  var allRules = [
48391
49417
  // Required meta rules (PS001, PS002)
@@ -48457,6 +49483,8 @@ var allRules = [
48457
49483
  agentNamespaces,
48458
49484
  // Import validation excludes bound to lockfile commits (PS040)
48459
49485
  importExcludes,
49486
+ // Valid model references (PS041)
49487
+ validModelReference,
48460
49488
  // Security rules (PS010, PS011, PS012, PS013, PS014)
48461
49489
  suspiciousUrls,
48462
49490
  authorityInjection,
@@ -48857,8 +49885,13 @@ var Compiler = class _Compiler {
48857
49885
  constructor(options) {
48858
49886
  this.options = options;
48859
49887
  this.logger = options.logger ?? noopLogger;
49888
+ this.models = options.validator?.models ?? options.models;
48860
49889
  this.resolver = new Resolver({ ...options.resolver, logger: this.logger });
48861
- this.validator = new Validator({ ...options.validator, logger: this.logger });
49890
+ this.validator = new Validator({
49891
+ ...options.validator,
49892
+ models: this.models,
49893
+ logger: this.logger
49894
+ });
48862
49895
  this.loadedFormatters = this.loadFormatters(options.formatters);
48863
49896
  this.logger.debug(`Compiler initialized with ${this.loadedFormatters.length} formatters`);
48864
49897
  }
@@ -48868,6 +49901,9 @@ var Compiler = class _Compiler {
48868
49901
  validator;
48869
49902
  loadedFormatters;
48870
49903
  logger;
49904
+ // Validation and formatting share one catalog, so PS041 and the target
49905
+ // model mapping agree about every name.
49906
+ models;
48871
49907
  resolvedDependencies = /* @__PURE__ */ new Map();
48872
49908
  hasExplicitResolverScope() {
48873
49909
  return Boolean(this.options.resolver.localPath || this.options.resolver.projectRoot);
@@ -49669,7 +50705,8 @@ var Compiler = class _Compiler {
49669
50705
  outputPath: config?.output,
49670
50706
  version: config?.version,
49671
50707
  prettier: prettierOptions,
49672
- targetConfig: config
50708
+ targetConfig: config,
50709
+ models: this.models
49673
50710
  };
49674
50711
  const conventionName = config?.convention;
49675
50712
  if (conventionName && customConventions?.[conventionName]) {
@@ -50108,11 +51145,11 @@ import { fileURLToPath } from "node:url";
50108
51145
  import { promisify as promisify2 } from "node:util";
50109
51146
 
50110
51147
  // packages/cli/src/runtime/runtime-info.ts
50111
- function isRecord9(value) {
51148
+ function isRecord10(value) {
50112
51149
  return typeof value === "object" && value !== null;
50113
51150
  }
50114
51151
  function isDenoNamespace(value) {
50115
- return isRecord9(value) && isRecord9(value["build"]);
51152
+ return isRecord10(value) && isRecord10(value["build"]);
50116
51153
  }
50117
51154
  function getDenoNamespace() {
50118
51155
  const denoCandidate = "Deno" in globalThis ? globalThis.Deno : void 0;
@@ -50130,7 +51167,7 @@ function getRuntimeVersion() {
50130
51167
  if (denoNamespace === void 0) {
50131
51168
  return process.versions.node;
50132
51169
  }
50133
- return isRecord9(denoNamespace.version) && typeof denoNamespace.version["deno"] === "string" ? denoNamespace.version["deno"] : "0";
51170
+ return isRecord10(denoNamespace.version) && typeof denoNamespace.version["deno"] === "string" ? denoNamespace.version["deno"] : "0";
50134
51171
  }
50135
51172
 
50136
51173
  // packages/cli/src/runtime/self-invocation.ts
@@ -51835,6 +52872,9 @@ function printWarnings(warnings) {
51835
52872
  console.log(`Warnings (${warnings.length}):`);
51836
52873
  for (const warn of warnings) {
51837
52874
  ConsoleOutput.warning(`${warn.ruleId}: ${warn.message}`);
52875
+ if (warn.suggestion) {
52876
+ ConsoleOutput.muted(`suggestion: ${warn.suggestion}`);
52877
+ }
51838
52878
  }
51839
52879
  }
51840
52880
  }
@@ -52013,6 +53053,7 @@ Disable one of the conflicting targets or assign a custom output path.`
52013
53053
  formatters: targets,
52014
53054
  customConventions: config.customConventions,
52015
53055
  prettier: prettierOptions,
53056
+ models: config.models,
52016
53057
  logger,
52017
53058
  skillContent,
52018
53059
  ignoreHashes: options.ignoreHashes
@@ -52521,6 +53562,7 @@ async function validateCommand2(options) {
52521
53562
  policies: options.skipPolicies ? void 0 : config.policies,
52522
53563
  skipPolicies: options.skipPolicies
52523
53564
  },
53565
+ models: config.models,
52524
53566
  ignoreHashes: options.ignoreHashes,
52525
53567
  formatters: []
52526
53568
  // No formatters needed for validation only
@@ -53341,6 +54383,7 @@ async function diffCommand(options) {
53341
54383
  formatters: targets,
53342
54384
  customConventions: config.customConventions,
53343
54385
  prettier: prettierOptions,
54386
+ models: config.models,
53344
54387
  logger,
53345
54388
  skillContent
53346
54389
  });
@@ -53697,8 +54740,8 @@ async function checkCommand(_options) {
53697
54740
  hasErrors = true;
53698
54741
  }
53699
54742
  }
53700
- const targetIssues = [];
53701
- const targetNames = /* @__PURE__ */ new Set();
54743
+ const targetIssues2 = [];
54744
+ const targetNames2 = /* @__PURE__ */ new Set();
53702
54745
  const rootTargetValue = config.targets;
53703
54746
  const rootTargets = Array.isArray(rootTargetValue) ? rootTargetValue : [];
53704
54747
  const buildsValue = config.builds;
@@ -53706,50 +54749,50 @@ async function checkCommand(_options) {
53706
54749
  const appendTargetAnalysis = (location, entries) => {
53707
54750
  const analysis = analyzeTargetEntries(entries);
53708
54751
  for (const name of analysis.names) {
53709
- targetNames.add(name);
54752
+ targetNames2.add(name);
53710
54753
  }
53711
- targetIssues.push(...analysis.errors.map((error) => `${location}: ${error}`));
54754
+ targetIssues2.push(...analysis.errors.map((error) => `${location}: ${error}`));
53712
54755
  if (entries.length > 0 && analysis.names.length === 0 && analysis.errors.length === 0) {
53713
- targetIssues.push(`${location}: no enabled targets`);
54756
+ targetIssues2.push(`${location}: no enabled targets`);
53714
54757
  }
53715
54758
  };
53716
54759
  if (rootTargetValue !== void 0 && !Array.isArray(rootTargetValue)) {
53717
- targetIssues.push("config.targets must be an array");
54760
+ targetIssues2.push("config.targets must be an array");
53718
54761
  } else if (rootTargets.length > 0) {
53719
54762
  appendTargetAnalysis("config.targets", rootTargets);
53720
54763
  }
53721
54764
  if (buildsValue !== void 0 && (buildsValue === null || typeof buildsValue !== "object" || Array.isArray(buildsValue))) {
53722
- targetIssues.push("config.builds must be an object");
54765
+ targetIssues2.push("config.builds must be an object");
53723
54766
  }
53724
54767
  for (const [buildName, profile] of builds) {
53725
54768
  if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
53726
- targetIssues.push(`config.builds.${buildName} must be an object`);
54769
+ targetIssues2.push(`config.builds.${buildName} must be an object`);
53727
54770
  continue;
53728
54771
  }
53729
54772
  const profileTargetValue = profile.targets;
53730
54773
  if (profileTargetValue === void 0) {
53731
54774
  if (rootTargets.length === 0) {
53732
- targetIssues.push(
54775
+ targetIssues2.push(
53733
54776
  `config.builds.${buildName}.targets is missing and config.targets is empty`
53734
54777
  );
53735
54778
  }
53736
54779
  continue;
53737
54780
  }
53738
54781
  if (!Array.isArray(profileTargetValue)) {
53739
- targetIssues.push(`config.builds.${buildName}.targets must be an array`);
54782
+ targetIssues2.push(`config.builds.${buildName}.targets must be an array`);
53740
54783
  continue;
53741
54784
  }
53742
54785
  if (profileTargetValue.length === 0) {
53743
- targetIssues.push(`config.builds.${buildName}.targets is empty`);
54786
+ targetIssues2.push(`config.builds.${buildName}.targets is empty`);
53744
54787
  continue;
53745
54788
  }
53746
54789
  appendTargetAnalysis(`config.builds.${buildName}.targets`, profileTargetValue);
53747
54790
  }
53748
- if (targetIssues.length > 0) {
54791
+ if (targetIssues2.length > 0) {
53749
54792
  results.push({
53750
54793
  name: "Targets",
53751
54794
  status: "error",
53752
- message: targetIssues.join("; ")
54795
+ message: targetIssues2.join("; ")
53753
54796
  });
53754
54797
  hasErrors = true;
53755
54798
  } else if (rootTargets.length === 0 && builds.length === 0) {
@@ -53763,7 +54806,7 @@ async function checkCommand(_options) {
53763
54806
  results.push({
53764
54807
  name: "Targets",
53765
54808
  status: "ok",
53766
- message: rootTargets.length === 0 ? `${targetNames.size} target(s) configured in build profiles` : `${targetNames.size} target(s) configured`
54809
+ message: rootTargets.length === 0 ? `${targetNames2.size} target(s) configured in build profiles` : `${targetNames2.size} target(s) configured`
53767
54810
  });
53768
54811
  }
53769
54812
  const existingEntries = effectiveEntries.filter((entry) => existsSync18(resolve28(entry)));
@@ -53800,6 +54843,7 @@ async function checkCommand(_options) {
53800
54843
  ...config.validation,
53801
54844
  policies: config.policies
53802
54845
  },
54846
+ models: config.models,
53803
54847
  formatters: []
53804
54848
  });
53805
54849
  for (const entry of existingEntries) {
@@ -56318,7 +57362,7 @@ function tokenizePath(path2) {
56318
57362
  }
56319
57363
  return tokens;
56320
57364
  }
56321
- function isRecord10(value) {
57365
+ function isRecord11(value) {
56322
57366
  return typeof value === "object" && value !== null && !Array.isArray(value);
56323
57367
  }
56324
57368
  function readValueAtPath(ast, path2) {
@@ -56329,7 +57373,7 @@ function readValueAtPath(ast, path2) {
56329
57373
  if (!block) return void 0;
56330
57374
  let value = block.content;
56331
57375
  for (const token of tokens) {
56332
- if (token === "text" && isRecord10(value)) {
57376
+ if (token === "text" && isRecord11(value)) {
56333
57377
  const contentType = value["type"];
56334
57378
  if (contentType === "TextContent") {
56335
57379
  value = value["value"];
@@ -56343,21 +57387,21 @@ function readValueAtPath(ast, path2) {
56343
57387
  value = value[token];
56344
57388
  } else if (typeof value === "string") {
56345
57389
  value = value[token];
56346
- } else if (isRecord10(value) && value["type"] === "TextContent") {
57390
+ } else if (isRecord11(value) && value["type"] === "TextContent") {
56347
57391
  value = value["value"];
56348
57392
  } else {
56349
57393
  return void 0;
56350
57394
  }
56351
57395
  continue;
56352
57396
  }
56353
- if (isRecord10(value)) {
57397
+ if (isRecord11(value)) {
56354
57398
  if (value["type"] === "TextContent" && token === "value") {
56355
57399
  value = value["value"];
56356
57400
  } else if (Object.hasOwn(value, token)) {
56357
57401
  value = value[token];
56358
- } else if (value["type"] === "ObjectContent" && isRecord10(value["properties"])) {
57402
+ } else if (value["type"] === "ObjectContent" && isRecord11(value["properties"])) {
56359
57403
  value = value["properties"][token];
56360
- } else if (value["type"] === "MixedContent" && isRecord10(value["properties"])) {
57404
+ } else if (value["type"] === "MixedContent" && isRecord11(value["properties"])) {
56361
57405
  value = value["properties"][token];
56362
57406
  } else {
56363
57407
  return void 0;
@@ -56398,7 +57442,7 @@ function sanitizeValue(value, options, seen = /* @__PURE__ */ new WeakSet()) {
56398
57442
  seen.add(value);
56399
57443
  return value.map((item) => sanitizeValue(item, options, seen));
56400
57444
  }
56401
- if (isRecord10(value)) {
57445
+ if (isRecord11(value)) {
56402
57446
  if (seen.has(value)) return value;
56403
57447
  seen.add(value);
56404
57448
  return Object.fromEntries(