@promptscript/cli 1.18.1 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -113,7 +113,9 @@ var init_target_output_paths = __esm({
113
113
  mimo: "AGENTS.md",
114
114
  "deep-agents": "AGENTS.md",
115
115
  forgecode: "AGENTS.md",
116
- hermes: "AGENTS.md"
116
+ hermes: "AGENTS.md",
117
+ // GitLab Duo (Agent Platform)
118
+ "gitlab-duo": "AGENTS.md"
117
119
  };
118
120
  DEFAULT_OUTPUT_PATHS = DEFAULT_OUTPUT_PATH_VALUES;
119
121
  }
@@ -184,7 +186,9 @@ var init_config = __esm({
184
186
  "mimo",
185
187
  "deep-agents",
186
188
  "forgecode",
187
- "hermes"
189
+ "hermes",
190
+ // GitLab Duo (Agent Platform)
191
+ "gitlab-duo"
188
192
  ];
189
193
  }
190
194
  });
@@ -914,10 +918,12 @@ var init_levenshtein = __esm({
914
918
 
915
919
  // packages/core/src/utils/markers.ts
916
920
  function stripPromptScriptMarkers(content) {
917
- return content.replace(new RegExp(`${HTML_MARKER}\\n*`, "g"), "").replace(new RegExp(`${YAML_MARKER}[^\\n]+\\n*`, "g"), "");
921
+ return content.replace(new RegExp(`${HTML_MARKER}\\n*`, "g"), "").replace(new RegExp(`${TYPESCRIPT_MARKER}[^\\n]+\\n*`, "g"), "").replace(new RegExp(`${LEGACY_TYPESCRIPT_MARKER}\\n*`, "g"), "").replace(new RegExp(`${YAML_MARKER}[^\\n]+\\n*`, "g"), "");
918
922
  }
919
923
  function isGeneratedByPromptScript(content) {
920
- const head = new RegExp(`^\\s*(?:${YAML_MARKER}|${HTML_MARKER})`);
924
+ const head = new RegExp(
925
+ `^\\s*(?:${YAML_MARKER}|${TYPESCRIPT_MARKER}|${LEGACY_TYPESCRIPT_MARKER}|${HTML_MARKER})`
926
+ );
921
927
  return content.split("\n", 5).some((line) => head.test(line));
922
928
  }
923
929
  function stripFrontmatterMarkerLines(frontmatter) {
@@ -927,12 +933,14 @@ function stripFrontmatterMarkerLines(frontmatter) {
927
933
  function stripLeadingHtmlMarker(body) {
928
934
  return body.replace(new RegExp(`^\\s*${HTML_MARKER}\\n*`), "");
929
935
  }
930
- var HTML_MARKER, YAML_MARKER;
936
+ var HTML_MARKER, YAML_MARKER, TYPESCRIPT_MARKER, LEGACY_TYPESCRIPT_MARKER;
931
937
  var init_markers = __esm({
932
938
  "packages/core/src/utils/markers.ts"() {
933
939
  "use strict";
934
940
  HTML_MARKER = "<!--\\s*PromptScript [^>]+-->";
935
941
  YAML_MARKER = "#\\s*promptscript-generated:";
942
+ TYPESCRIPT_MARKER = "//\\s*promptscript-generated:";
943
+ LEGACY_TYPESCRIPT_MARKER = "//\\s*Generated by PromptScript - do not edit\\.";
936
944
  }
937
945
  });
938
946
 
@@ -5092,10 +5100,21 @@ var init_hook_capabilities = __esm({
5092
5100
  }
5093
5101
  },
5094
5102
  opencode: {
5095
- ...UNSUPPORTED_CAPABILITY,
5096
- status: "plugin-only",
5097
- fallback: "OpenCode lifecycle events require a JavaScript or TypeScript plugin; use `prs compile --watch` for regeneration.",
5098
- documentationUrl: "https://opencode.ai/docs/plugins/"
5103
+ status: "native",
5104
+ configPath: ".opencode/plugins/promptscript.ts",
5105
+ events: ["pre-tool-use", "post-tool-use"],
5106
+ commandFormat: "generated TypeScript plugin with bounded JSON payloads",
5107
+ timeoutUnit: "milliseconds",
5108
+ projectRootStrategy: "plugin-context",
5109
+ platforms: ["unix"],
5110
+ nativeVersions: ["multifile", "full"],
5111
+ fallback: "Use OpenCode version 'multifile' or 'full'.",
5112
+ documentationUrl: "https://opencode.ai/docs/plugins/",
5113
+ terminal: {
5114
+ guarantee: "not-guaranteed",
5115
+ toolNames: [],
5116
+ notes: "OpenCode exposes tool.execute.before and tool.execute.after only; a dedicated terminal command event is not guaranteed, and MCP tool calls plus some subagent paths may not fire plugin hooks at all."
5117
+ }
5099
5118
  },
5100
5119
  gemini: {
5101
5120
  status: "native",
@@ -5233,7 +5252,8 @@ var init_hook_capabilities = __esm({
5233
5252
  mimo: UNSUPPORTED_CAPABILITY,
5234
5253
  "deep-agents": UNSUPPORTED_CAPABILITY,
5235
5254
  forgecode: UNSUPPORTED_CAPABILITY,
5236
- hermes: UNSUPPORTED_CAPABILITY
5255
+ hermes: UNSUPPORTED_CAPABILITY,
5256
+ "gitlab-duo": UNSUPPORTED_CAPABILITY
5237
5257
  };
5238
5258
  VSCODE_HOOK_CAPABILITY = {
5239
5259
  status: "compatible",
@@ -6122,12 +6142,12 @@ var init_target_capabilities = __esm({
6122
6142
  simple: { name: "simple", description: "Single OPENCODE.md file", outputPath: "OPENCODE.md" },
6123
6143
  multifile: {
6124
6144
  name: "multifile",
6125
- description: "OPENCODE.md + .opencode/commands/<name>.md (skills via full mode)",
6145
+ description: "OPENCODE.md + .opencode/commands/<name>.md + .opencode/plugins/promptscript.ts (skills via full mode)",
6126
6146
  outputPath: "OPENCODE.md"
6127
6147
  },
6128
6148
  full: {
6129
6149
  name: "full",
6130
- description: "Multifile + .opencode/skills/<name>/SKILL.md + .opencode/commands/<name>.md + .opencode/agents/<name>.md",
6150
+ description: "Multifile + .opencode/skills/<name>/SKILL.md + .opencode/commands/<name>.md + .opencode/agents/<name>.md + .opencode/plugins/promptscript.ts",
6131
6151
  outputPath: "OPENCODE.md"
6132
6152
  }
6133
6153
  }
@@ -6832,6 +6852,21 @@ var init_target_capabilities = __esm({
6832
6852
  },
6833
6853
  full: { name: "full", description: "Single AGENTS.md file", outputPath: "AGENTS.md" }
6834
6854
  }
6855
+ },
6856
+ "gitlab-duo": {
6857
+ versions: {
6858
+ simple: { name: "simple", description: "Single AGENTS.md file", outputPath: "AGENTS.md" },
6859
+ multifile: {
6860
+ name: "multifile",
6861
+ description: "Single AGENTS.md file (skills via full mode)",
6862
+ outputPath: "AGENTS.md"
6863
+ },
6864
+ full: {
6865
+ name: "full",
6866
+ description: "Multifile + skills/<name>/SKILL.md",
6867
+ outputPath: "AGENTS.md"
6868
+ }
6869
+ }
6835
6870
  }
6836
6871
  };
6837
6872
  }
@@ -7383,6 +7418,22 @@ var init_target_catalog = __esm({
7383
7418
  hasCommands: false
7384
7419
  }
7385
7420
  },
7421
+ // GitLab Duo (Agent Platform)
7422
+ "gitlab-duo": {
7423
+ name: "gitlab-duo",
7424
+ outputPath: DEFAULT_OUTPUT_PATHS["gitlab-duo"],
7425
+ family: "simple",
7426
+ // Duo reads Agent Skills from a repository-root `skills/` directory,
7427
+ // so the dot-directory skill layout does not apply to this target.
7428
+ features: {
7429
+ defaultEnabled: false,
7430
+ defaultVersion: "full",
7431
+ hasSkills: true,
7432
+ hasAgents: false,
7433
+ hasCommands: false
7434
+ },
7435
+ skillPath: { basePath: "skills", fileName: "SKILL.md" }
7436
+ },
7386
7437
  // AGENTS.md-only targets
7387
7438
  aider: {
7388
7439
  name: "aider",
@@ -7557,6 +7608,89 @@ var init_target_catalog = __esm({
7557
7608
  }
7558
7609
  });
7559
7610
 
7611
+ // packages/core/src/agent-capabilities.ts
7612
+ function fieldStatus(groups, target) {
7613
+ if (groups.emitted?.includes(target)) return "emitted";
7614
+ if (groups.transformed?.includes(target)) return "transformed";
7615
+ return "not-supported";
7616
+ }
7617
+ function getAgentFieldStatus(target, field) {
7618
+ return fieldStatus(AGENT_FIELD_STATUS_GROUPS[field], target);
7619
+ }
7620
+ function listAgentFieldSupportTargets(field) {
7621
+ const groups = AGENT_FIELD_STATUS_GROUPS[field];
7622
+ return { emitted: groups.emitted ?? [], transformed: groups.transformed ?? [] };
7623
+ }
7624
+ var CANONICAL_AGENT_FIELDS, ALL_NATIVE_AGENT_TARGETS, CLAUDE_CONTRACT_TARGETS, MARKDOWN_CONTRACT_TARGETS, AGENT_FIELD_STATUS_GROUPS;
7625
+ var init_agent_capabilities = __esm({
7626
+ "packages/core/src/agent-capabilities.ts"() {
7627
+ "use strict";
7628
+ init_target_catalog();
7629
+ CANONICAL_AGENT_FIELDS = [
7630
+ "description",
7631
+ "content",
7632
+ "tools",
7633
+ "model",
7634
+ "reasoningEffort",
7635
+ "specModel",
7636
+ "specReasoningEffort",
7637
+ "disallowedTools",
7638
+ "permissionMode",
7639
+ "skills",
7640
+ "mcpServers",
7641
+ "sandboxMode",
7642
+ "nicknameCandidates",
7643
+ "handoffs",
7644
+ "maxTurns",
7645
+ "memory",
7646
+ "background",
7647
+ "isolation"
7648
+ ];
7649
+ ALL_NATIVE_AGENT_TARGETS = [
7650
+ "github",
7651
+ "claude",
7652
+ "cursor",
7653
+ "factory",
7654
+ "codex",
7655
+ "opencode",
7656
+ "augment",
7657
+ "amp",
7658
+ "grok"
7659
+ ];
7660
+ CLAUDE_CONTRACT_TARGETS = ["claude", "grok"];
7661
+ MARKDOWN_CONTRACT_TARGETS = ["opencode", "augment", "amp"];
7662
+ AGENT_FIELD_STATUS_GROUPS = {
7663
+ description: { emitted: ALL_NATIVE_AGENT_TARGETS },
7664
+ content: {
7665
+ emitted: [...MARKDOWN_CONTRACT_TARGETS, "github", "claude", "cursor", "factory", "grok"],
7666
+ transformed: ["codex"]
7667
+ },
7668
+ tools: { emitted: [...CLAUDE_CONTRACT_TARGETS, "factory"], transformed: ["github"] },
7669
+ model: {
7670
+ emitted: [...CLAUDE_CONTRACT_TARGETS, "cursor", "factory", "codex"],
7671
+ transformed: ["github"]
7672
+ },
7673
+ reasoningEffort: { emitted: ["factory"], transformed: ["codex"] },
7674
+ specModel: { emitted: ["factory"], transformed: ["github"] },
7675
+ specReasoningEffort: { emitted: ["factory"] },
7676
+ disallowedTools: { emitted: CLAUDE_CONTRACT_TARGETS },
7677
+ permissionMode: { emitted: CLAUDE_CONTRACT_TARGETS },
7678
+ skills: { emitted: CLAUDE_CONTRACT_TARGETS, transformed: ["codex"] },
7679
+ mcpServers: {
7680
+ emitted: [...CLAUDE_CONTRACT_TARGETS, "cursor", "factory"],
7681
+ transformed: ["github", "codex"]
7682
+ },
7683
+ sandboxMode: { transformed: ["codex"] },
7684
+ nicknameCandidates: { transformed: ["codex"] },
7685
+ handoffs: { emitted: ["github"] },
7686
+ maxTurns: { emitted: CLAUDE_CONTRACT_TARGETS },
7687
+ memory: { emitted: CLAUDE_CONTRACT_TARGETS },
7688
+ background: { emitted: CLAUDE_CONTRACT_TARGETS },
7689
+ isolation: { emitted: CLAUDE_CONTRACT_TARGETS }
7690
+ };
7691
+ }
7692
+ });
7693
+
7560
7694
  // packages/core/src/git-timeout.ts
7561
7695
  function isGitTimeoutError(error) {
7562
7696
  const errorWithCode = error;
@@ -7795,6 +7929,80 @@ var init_output_plan = __esm({
7795
7929
  }
7796
7930
  });
7797
7931
 
7932
+ // packages/core/src/output-resources.ts
7933
+ function isOutputResourceKind(value) {
7934
+ return OUTPUT_RESOURCE_KINDS.includes(value);
7935
+ }
7936
+ function keepBestMatch(current, next) {
7937
+ return current && current.specificity >= next.specificity ? current : next;
7938
+ }
7939
+ function matchResourcePath(resourcePath, normalizedPath) {
7940
+ const nameIndex = resourcePath.indexOf("<name>");
7941
+ if (nameIndex >= 0) {
7942
+ const prefix = resourcePath.slice(0, nameIndex);
7943
+ return normalizedPath.startsWith(prefix) ? prefix.length : 0;
7944
+ }
7945
+ return resourcePath === normalizedPath ? resourcePath.length + 2 : 0;
7946
+ }
7947
+ function normalizeBaseDir(dir) {
7948
+ return dir.replaceAll("\\", "/").split("/").filter(Boolean).join("/");
7949
+ }
7950
+ function hooksSiblingSpecificity(resource, normalizedPath) {
7951
+ const directory = resource.path.slice(0, resource.path.lastIndexOf("/"));
7952
+ if (!directory.endsWith("/hooks")) return 0;
7953
+ if (!normalizedPath.startsWith(`${directory}/`)) return 0;
7954
+ if (normalizedPath.slice(directory.length + 1).includes("/")) return 0;
7955
+ return directory.length + 2;
7956
+ }
7957
+ function matchCatalogResource(target, normalizedPath) {
7958
+ let best;
7959
+ for (const resource of TARGET_CAPABILITIES[target].resources) {
7960
+ let next;
7961
+ if (resource.kind === "main") {
7962
+ const specificity = matchResourcePath(resource.path, normalizedPath);
7963
+ if (specificity > 0) {
7964
+ next = { kind: "main", specificity };
7965
+ }
7966
+ } else {
7967
+ const specificity = matchResourcePath(resource.path, normalizedPath) || (resource.kind === "hooks" ? hooksSiblingSpecificity(resource, normalizedPath) : 0);
7968
+ if (specificity > 0) {
7969
+ next = { kind: resource.kind, specificity };
7970
+ }
7971
+ }
7972
+ if (next) {
7973
+ best = keepBestMatch(best, next);
7974
+ }
7975
+ }
7976
+ return best?.kind;
7977
+ }
7978
+ function classifyOutputResource(target, path2, skillBaseDir) {
7979
+ const normalizedPath = path2.replaceAll("\\", "/");
7980
+ const catalogKind = isKnownTarget(target) ? matchCatalogResource(target, normalizedPath) : void 0;
7981
+ if (catalogKind) return catalogKind;
7982
+ const baseDir = skillBaseDir ? normalizeBaseDir(skillBaseDir) : "";
7983
+ if (baseDir && (normalizedPath === baseDir || normalizedPath.startsWith(`${baseDir}/`))) {
7984
+ return "skills";
7985
+ }
7986
+ return "main";
7987
+ }
7988
+ var OUTPUT_RESOURCE_KINDS;
7989
+ var init_output_resources = __esm({
7990
+ "packages/core/src/output-resources.ts"() {
7991
+ "use strict";
7992
+ init_target_catalog();
7993
+ init_config();
7994
+ OUTPUT_RESOURCE_KINDS = [
7995
+ "agents",
7996
+ "skills",
7997
+ "commands",
7998
+ "mcp",
7999
+ "hooks",
8000
+ "plugins",
8001
+ "main"
8002
+ ];
8003
+ }
8004
+ });
8005
+
7798
8006
  // packages/core/src/index.ts
7799
8007
  var init_src = __esm({
7800
8008
  "packages/core/src/index.ts"() {
@@ -7818,10 +8026,12 @@ var init_src = __esm({
7818
8026
  init_section_registry();
7819
8027
  init_target_catalog();
7820
8028
  init_target_capabilities();
8029
+ init_agent_capabilities();
7821
8030
  init_hook_capabilities();
7822
8031
  init_git_timeout();
7823
8032
  init_structured_output();
7824
8033
  init_output_plan();
8034
+ init_output_resources();
7825
8035
  }
7826
8036
  });
7827
8037
 
@@ -17039,12 +17249,35 @@ var init_visitor2 = __esm({
17039
17249
  return expression ? expression.loc : fallbackLoc;
17040
17250
  }
17041
17251
  parseTextBlock(token) {
17042
- let value = token.image.slice(3, -3).trim();
17252
+ let value = this.normalizeTextBlockIndent(token.image.slice(3, -3));
17043
17253
  if (this.interpolateEnv) {
17044
17254
  value = this.interpolateEnvVars(value);
17045
17255
  }
17046
17256
  return value;
17047
17257
  }
17258
+ /**
17259
+ * Strip the common leading whitespace shared by the non-empty lines of a
17260
+ * text block, then trim. A plain trim() would leave the first line at
17261
+ * column 0 while every following line keeps its source indentation, so
17262
+ * markdown consumers read the block body indent as list nesting. Stripping
17263
+ * the common indent keeps relative (nested) indentation intact.
17264
+ */
17265
+ normalizeTextBlockIndent(raw) {
17266
+ let minIndent = Infinity;
17267
+ for (const line of raw.split("\n")) {
17268
+ if (line.trim().length === 0) continue;
17269
+ const match2 = /^([ \t]*)/.exec(line);
17270
+ const indent = match2?.[1]?.length ?? 0;
17271
+ if (indent < minIndent) {
17272
+ minIndent = indent;
17273
+ if (minIndent === 0) break;
17274
+ }
17275
+ }
17276
+ if (minIndent === Infinity || minIndent === 0) {
17277
+ return raw.trim();
17278
+ }
17279
+ return raw.split("\n").map((line) => line.slice(minIndent)).join("\n").trim();
17280
+ }
17048
17281
  /**
17049
17282
  * Parse a string literal, handling escape sequences.
17050
17283
  */
@@ -18600,6 +18833,52 @@ var init_extractors = __esm({
18600
18833
  }
18601
18834
  });
18602
18835
 
18836
+ // packages/formatters/src/section-title-resolver.ts
18837
+ function presentationSectionId(blockName, entry) {
18838
+ if (entry.sectionId) return getSectionContract(entry.sectionId)?.id ?? entry.sectionId;
18839
+ return getPrimarySectionForBlock(blockName)?.id;
18840
+ }
18841
+ function sourceTitle(ast, section) {
18842
+ const owners = [section.primaryOwner, ...section.fallbackOwners];
18843
+ for (const source of ["explicit", "legacy"]) {
18844
+ for (const owner of owners) {
18845
+ const candidates = ast.blocks.filter((block) => block.name === owner).flatMap(
18846
+ (block) => (block.canonicalBody?.entries ?? []).filter(
18847
+ (entry) => entry.type === "PresentationEntry" && entry.source === source && presentationSectionId(block.name, entry) === section.id
18848
+ )
18849
+ );
18850
+ const selected = candidates.at(-1);
18851
+ if (selected) return selected.title;
18852
+ }
18853
+ }
18854
+ return void 0;
18855
+ }
18856
+ function resolveSourceSectionTitle(ast, sectionIdOrAlias) {
18857
+ const section = getSectionContract(sectionIdOrAlias);
18858
+ return section ? sourceTitle(ast, section) : void 0;
18859
+ }
18860
+ function configuredTitle(section, titles) {
18861
+ if (!titles) return void 0;
18862
+ for (const key of [section.id, ...section.formatterAliases]) {
18863
+ const title = titles[key];
18864
+ if (title !== void 0) return title;
18865
+ }
18866
+ return void 0;
18867
+ }
18868
+ function resolveSectionTitle(ast, sectionIdOrAlias, options = {}) {
18869
+ const section = getSectionContract(sectionIdOrAlias);
18870
+ if (!section) {
18871
+ return options.defaultTitle ?? sectionIdOrAlias;
18872
+ }
18873
+ return (options.sourceOverrides === false ? void 0 : sourceTitle(ast, section)) ?? configuredTitle(section, options.formatterTitles) ?? options.defaultTitle ?? section.defaultTitle;
18874
+ }
18875
+ var init_section_title_resolver = __esm({
18876
+ "packages/formatters/src/section-title-resolver.ts"() {
18877
+ "use strict";
18878
+ init_src();
18879
+ }
18880
+ });
18881
+
18603
18882
  // packages/formatters/src/base-formatter.ts
18604
18883
  import { posix } from "path";
18605
18884
  var BaseFormatter;
@@ -18609,6 +18888,7 @@ var init_base_formatter = __esm({
18609
18888
  init_src();
18610
18889
  init_convention_renderer();
18611
18890
  init_extractors();
18891
+ init_section_title_resolver();
18612
18892
  BaseFormatter = class _BaseFormatter {
18613
18893
  /**
18614
18894
  * Canonical entry point for legacy implementations.
@@ -18793,6 +19073,21 @@ var init_base_formatter = __esm({
18793
19073
  }
18794
19074
  return this.valueToString(value).split("\n")[0] ?? fallback;
18795
19075
  }
19076
+ /**
19077
+ * Whether the `@context` block's text is consumed by the project/intro
19078
+ * fallback. Must mirror the project() and intro() consumption conditions
19079
+ * exactly, otherwise the context section would either duplicate the text
19080
+ * (predicate too narrow) or drop it entirely (predicate too wide).
19081
+ */
19082
+ contextTextConsumedByProject(ast) {
19083
+ const identity = this.findBlock(ast, "identity");
19084
+ if (identity) return false;
19085
+ const context = this.findBlock(ast, "context");
19086
+ if (!context) return false;
19087
+ if (resolveSourceSectionTitle(ast, "context")) return false;
19088
+ const projectTitle = resolveSourceSectionTitle(ast, "project");
19089
+ return context.content.type === "MixedContent" || projectTitle !== void 0;
19090
+ }
18796
19091
  /**
18797
19092
  * `@context` keys that already have a dedicated rendering path. Anything
18798
19093
  * outside this set is generic and must be surfaced by the context section.
@@ -19160,9 +19455,12 @@ var init_base_formatter = __esm({
19160
19455
  return `${skillBasePath}/${skillName}`;
19161
19456
  }
19162
19457
  const normalizedOutputDir = this.normalizeOutputDir(outputDir);
19458
+ if (!normalizedOutputDir) {
19459
+ return `${skillBasePath}/${skillName}`;
19460
+ }
19163
19461
  if (!configuredBaseDir) {
19164
- const targetRoot = defaultSkillBasePath.replace(/\/skills$/, "");
19165
- return `${targetRoot}/${normalizedOutputDir}`;
19462
+ const targetRoot = defaultSkillBasePath.replace(/(?:^|\/)skills$/, "");
19463
+ return targetRoot ? `${targetRoot}/${normalizedOutputDir}` : normalizedOutputDir;
19166
19464
  }
19167
19465
  const outputSegments = normalizedOutputDir.split("/").filter((segment) => segment.length > 0);
19168
19466
  const relativeOutputDir = outputSegments[0] === "skills" ? outputSegments.slice(1).join("/") : outputSegments.join("/");
@@ -19726,7 +20024,7 @@ function getEnabledHookScriptResources(hook) {
19726
20024
  }
19727
20025
  function mapEvent(event, target) {
19728
20026
  if (target === "windsurf") return WINDSURF_EVENT_MAP[event][0] ?? null;
19729
- const map = target === "claude" ? CLAUDE_EVENT_MAP : target === "cursor" ? CURSOR_EVENT_MAP : target === "factory" ? FACTORY_EVENT_MAP : target === "github" ? GITHUB_EVENT_MAP : target === "gemini" ? GEMINI_EVENT_MAP : target === "grok" ? GROK_EVENT_MAP : target === "vscode" ? VSCODE_EVENT_MAP : CODEX_EVENT_MAP;
20027
+ const map = EVENT_MAPS[target] ?? CODEX_EVENT_MAP;
19730
20028
  return map[event] || null;
19731
20029
  }
19732
20030
  function mapEvents(event) {
@@ -19743,6 +20041,7 @@ function getEffectiveMatcher(hook, target) {
19743
20041
  function convertTimeout(timeoutMs, target) {
19744
20042
  if (target === "gemini") return timeoutMs;
19745
20043
  if (target === "windsurf") return timeoutMs;
20044
+ if (target === "opencode") return timeoutMs;
19746
20045
  if (target === "claude" || target === "cursor" || target === "codex" || target === "factory" || target === "github" || target === "vscode" || target === "grok")
19747
20046
  return Math.ceil(timeoutMs / 1e3);
19748
20047
  return timeoutMs;
@@ -20022,7 +20321,37 @@ function generateCodexHookConfig(hooks) {
20022
20321
  }
20023
20322
  return result;
20024
20323
  }
20025
- var HOOK_OWNERSHIP_MARKER, SHELL_SAFE_ARGUMENT, HOST_CWD_STRATEGIES, CLAUDE_EVENT_MAP, CURSOR_EVENT_MAP, CODEX_EVENT_MAP, FACTORY_EVENT_MAP, GITHUB_EVENT_MAP, GEMINI_EVENT_MAP, GROK_EVENT_MAP, WINDSURF_EVENT_MAP, VSCODE_EVENT_MAP, GITHUB_MATCHER_EVENTS;
20324
+ function buildOpenCodeHookRule(hook) {
20325
+ const nativeEvent = mapEvent(hook.event, "opencode");
20326
+ if (nativeEvent !== "tool.execute.before" && nativeEvent !== "tool.execute.after") return null;
20327
+ const command = hook.command ?? (hook.script ? [
20328
+ ...getPosixInterpreter(hook.script.interpreter),
20329
+ getScriptPathFromNativeCwd(hook),
20330
+ ...hook.script.args
20331
+ ] : []);
20332
+ if (command.length === 0) return null;
20333
+ const rule = {
20334
+ id: hook.id.replace(/[^A-Za-z0-9._-]/g, "-"),
20335
+ event: nativeEvent,
20336
+ ...hook.matcher !== void 0 ? { matcher: hook.matcher } : {},
20337
+ command,
20338
+ ...hook.cwd !== void 0 && hook.cwd !== "project" ? { cwd: hook.cwd } : {},
20339
+ ...hook.timeoutMs !== void 0 ? { timeoutMs: hook.timeoutMs } : {}
20340
+ };
20341
+ return rule;
20342
+ }
20343
+ function generateOpenCodePlugin(hooks) {
20344
+ const rules = [];
20345
+ for (const hook of applyHookTargetOverrides(hooks, "opencode")) {
20346
+ if (hook.enabled === false) continue;
20347
+ const rule = buildOpenCodeHookRule(hook);
20348
+ if (rule) rules.push(rule);
20349
+ }
20350
+ if (rules.length === 0) return null;
20351
+ const serializedRules = rules.map((rule) => JSON.stringify(rule));
20352
+ return "// promptscript-generated: opencode-plugin\n// Generated by PromptScript - do not edit.\n// Recompiling the .promptscript sources rewrites this file; manual edits are discarded.\n// Scope: OpenCode fires these hooks for local tool executions only. MCP tool calls,\n// some subagent paths, and failed tool calls have no dedicated plugin hook.\n// OpenCode tool hooks do not expose model or agent context; payloads omit those fields.\n\ninterface OpenCodeHookRule {\n id: string;\n event: 'tool.execute.before' | 'tool.execute.after';\n matcher?: string;\n command: string[];\n cwd?: string;\n timeoutMs?: number;\n}\n\nconst HOOK_RULES: readonly OpenCodeHookRule[] = [\n" + serializedRules.map((rule) => " " + rule).join(",\n") + "\n];\n" + OPENCODE_PLUGIN_RUNTIME;
20353
+ }
20354
+ var HOOK_OWNERSHIP_MARKER, SHELL_SAFE_ARGUMENT, HOST_CWD_STRATEGIES, CLAUDE_EVENT_MAP, CURSOR_EVENT_MAP, CODEX_EVENT_MAP, FACTORY_EVENT_MAP, GITHUB_EVENT_MAP, GEMINI_EVENT_MAP, GROK_EVENT_MAP, WINDSURF_EVENT_MAP, VSCODE_EVENT_MAP, OPENCODE_EVENT_MAP, EVENT_MAPS, GITHUB_MATCHER_EVENTS, OPENCODE_PLUGIN_PATH, OPENCODE_PLUGIN_RUNTIME;
20026
20355
  var init_hook_adapters = __esm({
20027
20356
  "packages/formatters/src/hook-adapters.ts"() {
20028
20357
  "use strict";
@@ -20034,6 +20363,7 @@ var init_hook_adapters = __esm({
20034
20363
  "git-root": false,
20035
20364
  "native-cwd": true,
20036
20365
  "workspace-cwd": true,
20366
+ "plugin-context": false,
20037
20367
  none: false
20038
20368
  };
20039
20369
  CLAUDE_EVENT_MAP = {
@@ -20125,12 +20455,199 @@ var init_hook_adapters = __esm({
20125
20455
  notification: "",
20126
20456
  stop: "Stop"
20127
20457
  };
20458
+ OPENCODE_EVENT_MAP = {
20459
+ "pre-terminal-command": "",
20460
+ "pre-tool-use": "tool.execute.before",
20461
+ "post-tool-use": "tool.execute.after",
20462
+ "session-start": "",
20463
+ setup: "",
20464
+ "subagent-start": "",
20465
+ notification: "",
20466
+ stop: ""
20467
+ };
20468
+ EVENT_MAPS = {
20469
+ claude: CLAUDE_EVENT_MAP,
20470
+ cursor: CURSOR_EVENT_MAP,
20471
+ factory: FACTORY_EVENT_MAP,
20472
+ github: GITHUB_EVENT_MAP,
20473
+ gemini: GEMINI_EVENT_MAP,
20474
+ grok: GROK_EVENT_MAP,
20475
+ vscode: VSCODE_EVENT_MAP,
20476
+ opencode: OPENCODE_EVENT_MAP
20477
+ };
20128
20478
  GITHUB_MATCHER_EVENTS = /* @__PURE__ */ new Set([
20129
20479
  "pre-tool-use",
20130
20480
  "post-tool-use",
20131
20481
  "subagent-start",
20132
20482
  "notification"
20133
20483
  ]);
20484
+ OPENCODE_PLUGIN_PATH = ".opencode/plugins/promptscript.ts";
20485
+ OPENCODE_PLUGIN_RUNTIME = `
20486
+ const PAYLOAD_LIMIT_BYTES = 32768;
20487
+ const DEFAULT_TIMEOUT_MS = 30000;
20488
+ const KILL_GRACE_MS = 1000;
20489
+
20490
+ interface OpenCodePluginContext {
20491
+ directory: string;
20492
+ worktree?: string;
20493
+ }
20494
+
20495
+ interface OpenCodeToolInput {
20496
+ tool?: string;
20497
+ sessionID?: string;
20498
+ callID?: string;
20499
+ args?: unknown;
20500
+ }
20501
+
20502
+ interface OpenCodeToolOutput {
20503
+ args?: unknown;
20504
+ }
20505
+
20506
+ interface OpenCodeToolResult {
20507
+ title?: unknown;
20508
+ output?: unknown;
20509
+ metadata?: unknown;
20510
+ }
20511
+
20512
+ interface OpenCodeCompiledRule {
20513
+ rule: OpenCodeHookRule;
20514
+ matcher: RegExp | null;
20515
+ }
20516
+
20517
+ function safeStringify(value: unknown): string {
20518
+ try {
20519
+ return JSON.stringify(value);
20520
+ } catch {
20521
+ return '{}';
20522
+ }
20523
+ }
20524
+
20525
+ function payloadByteLength(value: string): number {
20526
+ return new TextEncoder().encode(value).byteLength;
20527
+ }
20528
+
20529
+ // Keep the payload bounded: drop oversized tool arguments first, then the
20530
+ // result object, so the command always receives parseable JSON.
20531
+ function boundedPayload(payload: Record<string, unknown>): string {
20532
+ const full = safeStringify(payload);
20533
+ if (payloadByteLength(full) <= PAYLOAD_LIMIT_BYTES) return full;
20534
+ const withoutArgs = { ...payload, args: '[truncated]' };
20535
+ const trimmed = safeStringify(withoutArgs);
20536
+ if (payloadByteLength(trimmed) <= PAYLOAD_LIMIT_BYTES) return trimmed;
20537
+ const withoutResult = safeStringify({ ...withoutArgs, result: '[truncated]' });
20538
+ if (payloadByteLength(withoutResult) <= PAYLOAD_LIMIT_BYTES) return withoutResult;
20539
+ return '{"target":"opencode","args":"[truncated]","result":"[truncated]"}';
20540
+ }
20541
+
20542
+ function buildPayload(
20543
+ rule: OpenCodeHookRule,
20544
+ input: OpenCodeToolInput,
20545
+ args: unknown,
20546
+ result?: unknown
20547
+ ): Record<string, unknown> {
20548
+ const payload: Record<string, unknown> = {
20549
+ target: 'opencode',
20550
+ hook: rule.id,
20551
+ event: rule.event === 'tool.execute.before' ? 'pre-tool-use' : 'post-tool-use',
20552
+ tool: input.tool,
20553
+ args,
20554
+ sessionID: input.sessionID,
20555
+ callID: input.callID,
20556
+ timestamp: new Date().toISOString()
20557
+ };
20558
+ if (result !== undefined) payload.result = result;
20559
+ return payload;
20560
+ }
20561
+
20562
+ // Hooks observe tool execution asynchronously. Failures are logged, and every
20563
+ // process gets a bounded lifetime so a broken hook cannot disrupt the session.
20564
+ async function runRule(
20565
+ rule: OpenCodeHookRule,
20566
+ projectRoot: string,
20567
+ payload: string
20568
+ ): Promise<void> {
20569
+ try {
20570
+ const cwd = rule.cwd ? projectRoot + '/' + rule.cwd : projectRoot;
20571
+ const proc = Bun.spawn({
20572
+ cmd: rule.command,
20573
+ cwd,
20574
+ stdin: 'pipe',
20575
+ stdout: 'inherit',
20576
+ stderr: 'inherit'
20577
+ });
20578
+ const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20579
+ let graceTimer: ReturnType<typeof setTimeout> | undefined;
20580
+ const timer = setTimeout(() => {
20581
+ proc.kill('SIGTERM');
20582
+ graceTimer = setTimeout(() => proc.kill('SIGKILL'), KILL_GRACE_MS);
20583
+ }, timeoutMs);
20584
+ try {
20585
+ proc.stdin.write(payload);
20586
+ proc.stdin.end();
20587
+ const exitCode = await proc.exited;
20588
+ if (exitCode !== 0) {
20589
+ console.error(
20590
+ '[promptscript] opencode hook "' + rule.id + '" exited with code ' + exitCode
20591
+ );
20592
+ }
20593
+ } finally {
20594
+ clearTimeout(timer);
20595
+ if (graceTimer !== undefined) clearTimeout(graceTimer);
20596
+ }
20597
+ } catch (error: unknown) {
20598
+ console.error('[promptscript] opencode hook "' + rule.id + '" failed:', error);
20599
+ }
20600
+ }
20601
+
20602
+ export const PromptScriptHooks = async (context: OpenCodePluginContext) => {
20603
+ const projectRoot = context.worktree || context.directory;
20604
+ const compiled: OpenCodeCompiledRule[] = [];
20605
+ for (const rule of HOOK_RULES) {
20606
+ if (rule.matcher === undefined) {
20607
+ compiled.push({ rule, matcher: null });
20608
+ continue;
20609
+ }
20610
+ try {
20611
+ compiled.push({ rule, matcher: new RegExp(rule.matcher) });
20612
+ } catch (error: unknown) {
20613
+ console.error(
20614
+ '[promptscript] opencode hook "' + rule.id + '" has an invalid matcher:',
20615
+ rule.matcher
20616
+ );
20617
+ }
20618
+ }
20619
+
20620
+ return {
20621
+ 'tool.execute.before': async (input: OpenCodeToolInput, output: OpenCodeToolOutput) => {
20622
+ for (const entry of compiled) {
20623
+ if (entry.rule.event !== 'tool.execute.before') continue;
20624
+ if (entry.matcher !== null && !entry.matcher.test(String(input.tool))) continue;
20625
+ void runRule(
20626
+ entry.rule,
20627
+ projectRoot,
20628
+ boundedPayload(buildPayload(entry.rule, input, output.args))
20629
+ );
20630
+ }
20631
+ },
20632
+ 'tool.execute.after': async (input: OpenCodeToolInput, output: OpenCodeToolResult) => {
20633
+ for (const entry of compiled) {
20634
+ if (entry.rule.event !== 'tool.execute.after') continue;
20635
+ if (entry.matcher !== null && !entry.matcher.test(String(input.tool))) continue;
20636
+ const result = {
20637
+ title: output.title,
20638
+ output: output.output,
20639
+ metadata: output.metadata
20640
+ };
20641
+ void runRule(
20642
+ entry.rule,
20643
+ projectRoot,
20644
+ boundedPayload(buildPayload(entry.rule, input, input.args, result))
20645
+ );
20646
+ }
20647
+ }
20648
+ };
20649
+ };
20650
+ `;
20134
20651
  }
20135
20652
  });
20136
20653
 
@@ -20181,6 +20698,108 @@ var init_hook_capability_warnings = __esm({
20181
20698
  }
20182
20699
  });
20183
20700
 
20701
+ // packages/formatters/src/agent-capability-warnings.ts
20702
+ function collectAgentEntries(ast) {
20703
+ const agentsBlock = ast.blocks.find((block) => block.name === "agents");
20704
+ if (!agentsBlock) return null;
20705
+ const entries = [];
20706
+ if (agentsBlock.content.type === "ObjectContent" || agentsBlock.content.type === "MixedContent") {
20707
+ for (const [name, value] of Object.entries(agentsBlock.content.properties)) {
20708
+ if (value && typeof value === "object" && !Array.isArray(value)) {
20709
+ const fields = Object.fromEntries(
20710
+ Object.entries(value).filter(([key]) => !key.startsWith("__"))
20711
+ );
20712
+ entries.push({ name, fields });
20713
+ }
20714
+ }
20715
+ }
20716
+ return { location: agentsBlock.loc, entries };
20717
+ }
20718
+ function describeFieldSupport(field) {
20719
+ const support = listAgentFieldSupportTargets(field);
20720
+ const parts = [];
20721
+ if (support.emitted.length > 0) {
20722
+ parts.push(`emitted by ${support.emitted.join(", ")}`);
20723
+ }
20724
+ if (support.transformed.length > 0) {
20725
+ parts.push(`transformed by ${support.transformed.join(", ")}`);
20726
+ }
20727
+ return parts.length > 0 ? `Field support: ${parts.join("; ")}.` : "";
20728
+ }
20729
+ function describeBlockOmission(agentsResource, target, version, location, options) {
20730
+ if (!agentsResource) {
20731
+ if (options?.blockWarningHandled) return [];
20732
+ return [
20733
+ {
20734
+ code: AGENT_COMPATIBILITY_CODE,
20735
+ ruleName: "agent-compatibility",
20736
+ message: `Target "${target}" has no native agent output and will omit @agents.`,
20737
+ suggestion: "Compile a target with agent support or move agent guidance into instruction blocks.",
20738
+ location
20739
+ }
20740
+ ];
20741
+ }
20742
+ return [
20743
+ {
20744
+ code: AGENT_COMPATIBILITY_CODE,
20745
+ ruleName: "agent-compatibility",
20746
+ message: `Target "${target}" version "${version}" cannot emit @agents and will omit it.`,
20747
+ suggestion: `Use a version that emits agents: ${agentsResource.versions.join(", ")}.`,
20748
+ location
20749
+ }
20750
+ ];
20751
+ }
20752
+ function describeFieldLoss(agentName, field, target, location) {
20753
+ if (field === "description" || field === "content") return void 0;
20754
+ const isCanonical = CANONICAL_AGENT_FIELDS.includes(field);
20755
+ const status = isCanonical ? getAgentFieldStatus(target, field) : "not-supported";
20756
+ if (status !== "not-supported") return void 0;
20757
+ return {
20758
+ code: AGENT_COMPATIBILITY_CODE,
20759
+ ruleName: "agent-compatibility",
20760
+ message: `Agent "${agentName}": field "${field}" is not supported by target "${target}" and will be omitted.`,
20761
+ suggestion: isCanonical ? describeFieldSupport(field) : "Not a canonical @agents field; remove it or model it through a supported block.",
20762
+ location
20763
+ };
20764
+ }
20765
+ function collectEntryFieldWarnings(entry, target, location) {
20766
+ const warnings = [];
20767
+ for (const field of Object.keys(entry.fields)) {
20768
+ const warning = describeFieldLoss(entry.name, field, target, location);
20769
+ if (warning) warnings.push(warning);
20770
+ }
20771
+ return warnings;
20772
+ }
20773
+ function getAgentCapabilityWarnings(ast, target, version, options) {
20774
+ const agentsBlock = collectAgentEntries(ast);
20775
+ if (!agentsBlock || !isKnownTarget(target)) return [];
20776
+ const agentsResource = TARGET_CAPABILITIES[target].resources.find(
20777
+ (resource) => resource.kind === "agents"
20778
+ );
20779
+ if (!agentsResource?.versions.includes(version)) {
20780
+ return describeBlockOmission(agentsResource, target, version, agentsBlock.location, options);
20781
+ }
20782
+ return agentsBlock.entries.flatMap(
20783
+ (entry) => collectEntryFieldWarnings(entry, target, agentsBlock.location)
20784
+ );
20785
+ }
20786
+ function appendAgentCapabilityWarnings(output, ast, target, version, options) {
20787
+ const warnings = getAgentCapabilityWarnings(ast, target, version, options);
20788
+ if (warnings.length === 0) return output;
20789
+ return {
20790
+ ...output,
20791
+ warnings: [...output.warnings ?? [], ...warnings]
20792
+ };
20793
+ }
20794
+ var AGENT_COMPATIBILITY_CODE;
20795
+ var init_agent_capability_warnings = __esm({
20796
+ "packages/formatters/src/agent-capability-warnings.ts"() {
20797
+ "use strict";
20798
+ init_src();
20799
+ AGENT_COMPATIBILITY_CODE = "PS4003";
20800
+ }
20801
+ });
20802
+
20184
20803
  // packages/formatters/src/mcp-helpers.ts
20185
20804
  function findMcpServersBlock(ast) {
20186
20805
  return ast.blocks.find((b) => b.name === "mcpServers");
@@ -20356,52 +20975,6 @@ var init_mcp_helpers = __esm({
20356
20975
  }
20357
20976
  });
20358
20977
 
20359
- // packages/formatters/src/section-title-resolver.ts
20360
- function presentationSectionId(blockName, entry) {
20361
- if (entry.sectionId) return getSectionContract(entry.sectionId)?.id ?? entry.sectionId;
20362
- return getPrimarySectionForBlock(blockName)?.id;
20363
- }
20364
- function sourceTitle(ast, section) {
20365
- const owners = [section.primaryOwner, ...section.fallbackOwners];
20366
- for (const source of ["explicit", "legacy"]) {
20367
- for (const owner of owners) {
20368
- const candidates = ast.blocks.filter((block) => block.name === owner).flatMap(
20369
- (block) => (block.canonicalBody?.entries ?? []).filter(
20370
- (entry) => entry.type === "PresentationEntry" && entry.source === source && presentationSectionId(block.name, entry) === section.id
20371
- )
20372
- );
20373
- const selected = candidates.at(-1);
20374
- if (selected) return selected.title;
20375
- }
20376
- }
20377
- return void 0;
20378
- }
20379
- function resolveSourceSectionTitle(ast, sectionIdOrAlias) {
20380
- const section = getSectionContract(sectionIdOrAlias);
20381
- return section ? sourceTitle(ast, section) : void 0;
20382
- }
20383
- function configuredTitle(section, titles) {
20384
- if (!titles) return void 0;
20385
- for (const key of [section.id, ...section.formatterAliases]) {
20386
- const title = titles[key];
20387
- if (title !== void 0) return title;
20388
- }
20389
- return void 0;
20390
- }
20391
- function resolveSectionTitle(ast, sectionIdOrAlias, options = {}) {
20392
- const section = getSectionContract(sectionIdOrAlias);
20393
- if (!section) {
20394
- return options.defaultTitle ?? sectionIdOrAlias;
20395
- }
20396
- return (options.sourceOverrides === false ? void 0 : sourceTitle(ast, section)) ?? configuredTitle(section, options.formatterTitles) ?? options.defaultTitle ?? section.defaultTitle;
20397
- }
20398
- var init_section_title_resolver = __esm({
20399
- "packages/formatters/src/section-title-resolver.ts"() {
20400
- "use strict";
20401
- init_src();
20402
- }
20403
- });
20404
-
20405
20978
  // packages/formatters/src/markdown-instruction-formatter.ts
20406
20979
  var MarkdownInstructionFormatter;
20407
20980
  var init_markdown_instruction_formatter = __esm({
@@ -20410,6 +20983,7 @@ var init_markdown_instruction_formatter = __esm({
20410
20983
  init_base_formatter();
20411
20984
  init_hook_adapters();
20412
20985
  init_hook_capability_warnings();
20986
+ init_agent_capability_warnings();
20413
20987
  init_mcp_helpers();
20414
20988
  init_section_title_resolver();
20415
20989
  MarkdownInstructionFormatter = class extends BaseFormatter {
@@ -20428,7 +21002,7 @@ var init_markdown_instruction_formatter = __esm({
20428
21002
  }
20429
21003
  getSkillBasePath() {
20430
21004
  if (!this.config.hasSkills) return null;
20431
- return `${this.config.dotDir}/skills`;
21005
+ return this.config.skillsDir ?? `${this.config.dotDir}/skills`;
20432
21006
  }
20433
21007
  getSkillFileName() {
20434
21008
  if (!this.config.hasSkills) return null;
@@ -20454,7 +21028,10 @@ var init_markdown_instruction_formatter = __esm({
20454
21028
  hookWarnings.length > 0 || !hasEnabledHooks ? /* @__PURE__ */ new Set(["hooks"]) : void 0
20455
21029
  );
20456
21030
  const warnedOutput = unsupportedWarnings.length > 0 ? { ...output, warnings: [...output.warnings ?? [], ...unsupportedWarnings] } : output;
20457
- return appendTargetHookCapabilityWarnings(warnedOutput, ast, this.name, version);
21031
+ const agentOutput = appendAgentCapabilityWarnings(warnedOutput, ast, this.name, version, {
21032
+ blockWarningHandled: (this.config.unsupportedBlocks ?? []).includes("agents")
21033
+ });
21034
+ return appendTargetHookCapabilityWarnings(agentOutput, ast, this.name, version);
20458
21035
  }
20459
21036
  hasEnabledHooks(ast) {
20460
21037
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
@@ -20889,17 +21466,16 @@ var init_markdown_instruction_formatter = __esm({
20889
21466
  context(ast, renderer) {
20890
21467
  const contextBlock = this.findBlock(ast, "context");
20891
21468
  if (!contextBlock) return null;
20892
- const identity = this.findBlock(ast, "identity");
20893
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
21469
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
20894
21470
  const propertyItems = this.contextPropertyItems(ast);
20895
21471
  let body = "";
20896
21472
  if (!textIsConsumedByProject) {
20897
21473
  const text = this.extractText(contextBlock.content);
20898
21474
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
20899
21475
  const strippedText = archMatch ? text.replace(archMatch, "") : text;
20900
- const remainingText = this.dedent(strippedText);
21476
+ const remainingText = strippedText.trim();
20901
21477
  if (remainingText) {
20902
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
21478
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
20903
21479
  body = this.normalizeMarkdownForPrettier(downgradedText).trim();
20904
21480
  }
20905
21481
  }
@@ -21114,24 +21690,28 @@ ${list}` : list;
21114
21690
  });
21115
21691
 
21116
21692
  // packages/formatters/src/create-simple-formatter.ts
21117
- function buildVersions(outputPath, dotDir, hasSkills, hasAgents, hasCommands, skillFileName, mcpConfigPath) {
21693
+ function buildVersions(inputs) {
21694
+ const { outputPath, dotDir, hasSkills, hasAgents, hasCommands } = inputs;
21695
+ const { skillFileName, skillsInMultifile, mcpConfigPath, skillsDir } = inputs;
21118
21696
  const isNested = outputPath.startsWith(dotDir + "/");
21119
21697
  const simpleDesc = `Single ${outputPath} file`;
21120
- const skillPath = `${dotDir}/skills/<name>/${skillFileName}`;
21698
+ const defaultSkillsDir = `${dotDir}/skills`;
21699
+ const resolvedSkillsDir = skillsDir ?? defaultSkillsDir;
21700
+ const skillPath = `${resolvedSkillsDir}/<name>/${skillFileName}`;
21121
21701
  const commandPath = `${dotDir}/commands/<name>.md`;
21122
21702
  const agentPath = `${dotDir}/agents/<name>.md`;
21123
- const describeAdditionalFiles = (version, paths, skillsInFullMode2) => {
21124
- if (paths.length === 0 && skillsInFullMode2) {
21703
+ const skillsFullModeOnly = hasSkills && !skillsInMultifile;
21704
+ const describeAdditionalFiles = (version, paths, fullModeSkillsOnly) => {
21705
+ if (paths.length === 0 && fullModeSkillsOnly) {
21125
21706
  return `Single ${outputPath} file (skills via full mode)`;
21126
21707
  }
21127
21708
  if (paths.length === 0) return `Single ${outputPath} file`;
21128
21709
  const prefix = !isNested && version === "full" && hasSkills ? "Multifile" : outputPath;
21129
- const suffix = skillsInFullMode2 ? " (skills via full mode)" : "";
21710
+ const suffix = fullModeSkillsOnly ? " (skills via full mode)" : "";
21130
21711
  return `${prefix} + ${paths.join(" + ")}${suffix}`;
21131
21712
  };
21132
21713
  const multifilePaths = [];
21133
- const skillsInFullMode = hasSkills && isNested;
21134
- if (hasSkills && !skillsInFullMode) multifilePaths.push(skillPath);
21714
+ if (hasSkills && skillsInMultifile) multifilePaths.push(skillPath);
21135
21715
  if (hasCommands) multifilePaths.push(commandPath);
21136
21716
  if (mcpConfigPath) multifilePaths.push(mcpConfigPath);
21137
21717
  const fullPaths = [];
@@ -21139,7 +21719,7 @@ function buildVersions(outputPath, dotDir, hasSkills, hasAgents, hasCommands, sk
21139
21719
  if (hasCommands) fullPaths.push(commandPath);
21140
21720
  if (hasAgents) fullPaths.push(agentPath);
21141
21721
  if (mcpConfigPath) fullPaths.push(mcpConfigPath);
21142
- const multifileDesc = describeAdditionalFiles("multifile", multifilePaths, skillsInFullMode);
21722
+ const multifileDesc = describeAdditionalFiles("multifile", multifilePaths, skillsFullModeOnly);
21143
21723
  const fullDesc = describeAdditionalFiles("full", fullPaths, false);
21144
21724
  return {
21145
21725
  simple: { name: "simple", description: simpleDesc, outputPath },
@@ -21157,20 +21737,24 @@ function createSimpleMarkdownFormatter(opts) {
21157
21737
  hasAgents = false,
21158
21738
  hasCommands = false,
21159
21739
  hasSkills = true,
21740
+ skillsInMultifile = false,
21160
21741
  skillFileName = "SKILL.md",
21161
21742
  mcpConfigPath,
21162
21743
  mcpConfigFormat,
21163
- unsupportedBlocks
21744
+ unsupportedBlocks,
21745
+ skillsDir
21164
21746
  } = opts;
21165
- const versions = buildVersions(
21747
+ const versions = buildVersions({
21166
21748
  outputPath,
21167
21749
  dotDir,
21168
21750
  hasSkills,
21169
21751
  hasAgents,
21170
21752
  hasCommands,
21753
+ skillsInMultifile,
21171
21754
  skillFileName,
21172
- mcpConfigPath
21173
- );
21755
+ ...mcpConfigPath ? { mcpConfigPath } : {},
21756
+ ...skillsDir ? { skillsDir } : {}
21757
+ });
21174
21758
  class SimpleFormatter extends MarkdownInstructionFormatter {
21175
21759
  constructor() {
21176
21760
  super({
@@ -21184,6 +21768,8 @@ function createSimpleMarkdownFormatter(opts) {
21184
21768
  hasAgents,
21185
21769
  hasCommands,
21186
21770
  hasSkills,
21771
+ skillsInMultifile,
21772
+ ...skillsDir ? { skillsDir } : {},
21187
21773
  ...mcpConfigPath ? { mcpConfigPath } : {},
21188
21774
  ...mcpConfigFormat ? { mcpConfigFormat } : {},
21189
21775
  ...unsupportedBlocks ? { unsupportedBlocks } : {}
@@ -21703,6 +22289,7 @@ var init_github2 = __esm({
21703
22289
  init_mcp_helpers();
21704
22290
  init_hook_adapters();
21705
22291
  init_hook_capability_warnings();
22292
+ init_agent_capability_warnings();
21706
22293
  init_section_title_resolver();
21707
22294
  GITHUB_VERSIONS = {
21708
22295
  simple: {
@@ -21797,6 +22384,7 @@ var init_github2 = __esm({
21797
22384
  output = this.formatSimple(ast, options);
21798
22385
  }
21799
22386
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
22387
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
21800
22388
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
21801
22389
  const hooks = hooksBlock ? extractHooks(hooksBlock) : [];
21802
22390
  const hookWarnings = hooksBlock && version !== "simple" ? [
@@ -22573,8 +23161,7 @@ ${f.content}`
22573
23161
  contextSection(ast, renderer) {
22574
23162
  const contextBlock = this.findBlock(ast, "context");
22575
23163
  if (!contextBlock) return null;
22576
- const identity = this.findBlock(ast, "identity");
22577
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
23164
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
22578
23165
  const propertyItems = this.contextPropertyItems(ast);
22579
23166
  let body = "";
22580
23167
  if (!textIsConsumedByProject) {
@@ -22582,7 +23169,7 @@ ${f.content}`
22582
23169
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
22583
23170
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
22584
23171
  if (remainingText) {
22585
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
23172
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
22586
23173
  body = this.stripAllIndent(downgradedText);
22587
23174
  }
22588
23175
  }
@@ -22868,6 +23455,7 @@ var init_claude2 = __esm({
22868
23455
  init_base_formatter();
22869
23456
  init_hook_adapters();
22870
23457
  init_hook_capability_warnings();
23458
+ init_agent_capability_warnings();
22871
23459
  init_mcp_helpers();
22872
23460
  init_section_title_resolver();
22873
23461
  CLAUDE_VERSIONS = {
@@ -22918,6 +23506,7 @@ var init_claude2 = __esm({
22918
23506
  output = this.formatSimple(ast, options);
22919
23507
  }
22920
23508
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
23509
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
22921
23510
  output = {
22922
23511
  ...output,
22923
23512
  managedOutputFiles: [
@@ -23686,8 +24275,7 @@ ${f.content}`
23686
24275
  contextSection(ast, renderer) {
23687
24276
  const contextBlock = this.findBlock(ast, "context");
23688
24277
  if (!contextBlock) return null;
23689
- const identity = this.findBlock(ast, "identity");
23690
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
24278
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
23691
24279
  const propertyItems = this.contextPropertyItems(ast);
23692
24280
  let body = "";
23693
24281
  if (!textIsConsumedByProject) {
@@ -23695,7 +24283,7 @@ ${f.content}`
23695
24283
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
23696
24284
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
23697
24285
  if (remainingText) {
23698
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
24286
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
23699
24287
  body = this.normalizeMarkdownForPrettier(downgradedText).trim();
23700
24288
  }
23701
24289
  }
@@ -23964,6 +24552,7 @@ var init_cursor2 = __esm({
23964
24552
  init_glob_categorizer();
23965
24553
  init_hook_adapters();
23966
24554
  init_hook_capability_warnings();
24555
+ init_agent_capability_warnings();
23967
24556
  init_mcp_helpers();
23968
24557
  init_plugin_helpers();
23969
24558
  init_section_title_resolver();
@@ -24052,10 +24641,11 @@ var init_cursor2 = __esm({
24052
24641
  const hooks = hooksBlock ? extractHooks(hooksBlock) : [];
24053
24642
  const effectiveHooks = applyHookTargetOverrides(hooks, "cursor");
24054
24643
  if (hooksBlock && effectiveHooks.some((hook) => hook.enabled !== false)) {
24644
+ const withAgentWarnings = appendAgentCapabilityWarnings(output, ast, this.name, version);
24055
24645
  return {
24056
- ...output,
24646
+ ...withAgentWarnings,
24057
24647
  warnings: [
24058
- ...output.warnings ?? [],
24648
+ ...withAgentWarnings.warnings ?? [],
24059
24649
  {
24060
24650
  code: "PS4002",
24061
24651
  message: `Cursor ${version} mode cannot emit @hooks and will omit them.`,
@@ -24070,6 +24660,7 @@ var init_cursor2 = __esm({
24070
24660
  }
24071
24661
  }
24072
24662
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
24663
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
24073
24664
  return {
24074
24665
  ...output,
24075
24666
  managedOutputFiles: [
@@ -24591,10 +25182,14 @@ ${text2.trim()}`;
24591
25182
  }
24592
25183
  }
24593
25184
  const text = this.extractText(context.content);
24594
- if (text?.includes("Architecture")) {
24595
- return text.trim();
24596
- }
24597
- return null;
25185
+ const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
25186
+ if (!archMatch) return null;
25187
+ const content = archMatch.replace("## Architecture", "");
25188
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
25189
+ const title = resolveSectionTitle(ast, "architecture", { defaultTitle: "Architecture" });
25190
+ return `## ${title}
25191
+
25192
+ ${normalizedContent.trim()}`;
24598
25193
  }
24599
25194
  contextSection(ast) {
24600
25195
  const context = this.findBlock(ast, "context");
@@ -24606,13 +25201,13 @@ ${text2.trim()}`;
24606
25201
  consumedByIntro ? ["tech-stack", "project"] : ["tech-stack"]
24607
25202
  );
24608
25203
  let body = "";
24609
- if (resolveSourceSectionTitle(ast, "context")) {
25204
+ if (!this.contextTextConsumedByProject(ast)) {
24610
25205
  const text = this.extractText(context.content);
24611
25206
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
24612
25207
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
24613
25208
  if (remainingText) {
24614
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
24615
- body = this.stripAllIndent(downgradedText);
25209
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
25210
+ body = downgradedText.trim();
24616
25211
  }
24617
25212
  }
24618
25213
  if (propertyItems.length > 0) {
@@ -25480,8 +26075,7 @@ ${this.stripAllIndent(content)}`;
25480
26075
  contextSection(ast, _renderer) {
25481
26076
  const contextBlock = this.findBlock(ast, "context");
25482
26077
  if (!contextBlock) return null;
25483
- const identity = this.findBlock(ast, "identity");
25484
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
26078
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
25485
26079
  const propertyItems = this.contextPropertyItems(ast, ["frameworks"]);
25486
26080
  let body = "";
25487
26081
  if (!textIsConsumedByProject) {
@@ -25489,7 +26083,7 @@ ${this.stripAllIndent(content)}`;
25489
26083
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
25490
26084
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
25491
26085
  if (remainingText) {
25492
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
26086
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
25493
26087
  body = this.stripAllIndent(downgradedText);
25494
26088
  }
25495
26089
  }
@@ -26721,20 +27315,115 @@ ${filteredFrontmatter}
26721
27315
  });
26722
27316
 
26723
27317
  // packages/formatters/src/formatters/opencode.ts
26724
- var OpenCodeFormatter, OPENCODE_VERSIONS;
27318
+ var OPENCODE_VERSIONS, OpenCodeFormatter;
26725
27319
  var init_opencode = __esm({
26726
27320
  "packages/formatters/src/formatters/opencode.ts"() {
26727
27321
  "use strict";
26728
- init_create_simple_formatter();
26729
- ({ Formatter: OpenCodeFormatter, VERSIONS: OPENCODE_VERSIONS } = createSimpleMarkdownFormatter({
26730
- name: "opencode",
26731
- outputPath: "OPENCODE.md",
26732
- description: "OpenCode instructions (Markdown)",
26733
- mainFileHeader: "# OPENCODE.md",
26734
- dotDir: ".opencode",
26735
- hasAgents: true,
26736
- hasCommands: true
26737
- }));
27322
+ init_hook_adapters();
27323
+ init_markdown_instruction_formatter();
27324
+ OPENCODE_VERSIONS = {
27325
+ simple: {
27326
+ name: "simple",
27327
+ description: "Single OPENCODE.md file",
27328
+ outputPath: "OPENCODE.md"
27329
+ },
27330
+ multifile: {
27331
+ name: "multifile",
27332
+ description: "OPENCODE.md + .opencode/commands/<name>.md + .opencode/plugins/promptscript.ts (skills via full mode)",
27333
+ outputPath: "OPENCODE.md"
27334
+ },
27335
+ full: {
27336
+ name: "full",
27337
+ description: "Multifile + .opencode/skills/<name>/SKILL.md + .opencode/commands/<name>.md + .opencode/agents/<name>.md + .opencode/plugins/promptscript.ts",
27338
+ outputPath: "OPENCODE.md"
27339
+ }
27340
+ };
27341
+ OpenCodeFormatter = class extends MarkdownInstructionFormatter {
27342
+ constructor() {
27343
+ super({
27344
+ name: "opencode",
27345
+ outputPath: "OPENCODE.md",
27346
+ description: "OpenCode instructions (Markdown)",
27347
+ defaultConvention: "markdown",
27348
+ mainFileHeader: "# OPENCODE.md",
27349
+ dotDir: ".opencode",
27350
+ skillFileName: "SKILL.md",
27351
+ hasAgents: true,
27352
+ hasCommands: true,
27353
+ hasSkills: true
27354
+ });
27355
+ }
27356
+ /**
27357
+ * Return supported OpenCode output modes.
27358
+ */
27359
+ static getSupportedVersions() {
27360
+ return OPENCODE_VERSIONS;
27361
+ }
27362
+ /**
27363
+ * Format single-file output while retaining stale-plugin cleanup metadata.
27364
+ */
27365
+ formatSimple(ast, options) {
27366
+ const output = super.formatSimple(ast, options);
27367
+ return {
27368
+ ...output,
27369
+ managedOutputFiles: [
27370
+ .../* @__PURE__ */ new Set([...output.managedOutputFiles ?? [], OPENCODE_PLUGIN_PATH])
27371
+ ]
27372
+ };
27373
+ }
27374
+ /**
27375
+ * Format multifile output and attach the generated hook plugin.
27376
+ */
27377
+ formatMultifile(ast, options) {
27378
+ return this.appendHookPlugin(ast, super.formatMultifile(ast, options));
27379
+ }
27380
+ /**
27381
+ * Format full output and attach the generated hook plugin.
27382
+ */
27383
+ formatFull(ast, options) {
27384
+ return this.appendHookPlugin(ast, super.formatFull(ast, options));
27385
+ }
27386
+ /**
27387
+ * Attach the generated OpenCode plugin and its compatibility warnings.
27388
+ *
27389
+ * The plugin path is always registered as managed output so recompiles and
27390
+ * version downgrades remove only PromptScript-owned files; user plugins in
27391
+ * the same directory are never touched.
27392
+ */
27393
+ appendHookPlugin(ast, output) {
27394
+ const managedFiles = [.../* @__PURE__ */ new Set([...output.managedOutputFiles ?? [], OPENCODE_PLUGIN_PATH])];
27395
+ const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
27396
+ if (!hooksBlock) {
27397
+ return { ...output, managedOutputFiles: managedFiles };
27398
+ }
27399
+ const hooks = extractHooks(hooksBlock);
27400
+ const plugin = generateOpenCodePlugin(hooks);
27401
+ const warnings = [
27402
+ ...getHookCompatibilityWarnings(hooks, "opencode"),
27403
+ ...plugin !== null ? [
27404
+ {
27405
+ code: "PS4002",
27406
+ message: "OpenCode tool hooks do not expose model or agent context; the generated payload omits those fields.",
27407
+ suggestion: "Use sessionID and callID for attribution."
27408
+ }
27409
+ ] : []
27410
+ ].map((warning) => ({ ...warning, location: hooksBlock.loc }));
27411
+ return {
27412
+ ...output,
27413
+ ...plugin !== null ? {
27414
+ additionalFiles: [
27415
+ ...output.additionalFiles ?? [],
27416
+ { path: OPENCODE_PLUGIN_PATH, content: plugin }
27417
+ ]
27418
+ } : {},
27419
+ ...warnings.length > 0 ? { warnings: [...output.warnings ?? [], ...warnings] } : {},
27420
+ managedOutputFiles: managedFiles,
27421
+ managedOutputDirectories: [
27422
+ .../* @__PURE__ */ new Set([...output.managedOutputDirectories ?? [], ".opencode/plugins"])
27423
+ ]
27424
+ };
27425
+ }
27426
+ };
26738
27427
  }
26739
27428
  });
26740
27429
 
@@ -27040,20 +27729,54 @@ ${escaped}"""`;
27040
27729
  }
27041
27730
  return '""';
27042
27731
  }
27043
- function serializeAgentToml(agentName, agent) {
27732
+ function mcpServerTomlConfig(server) {
27733
+ const config = {};
27734
+ if (server.command) config["command"] = server.command;
27735
+ if (server.url) config["url"] = server.url;
27736
+ if (server.headers) config["headers"] = server.headers;
27737
+ if (server.env) config["env"] = server.env;
27738
+ return config;
27739
+ }
27740
+ function agentInstructionsLines(content) {
27741
+ if (content !== null && typeof content === "object" && content.type === "TextContent") {
27742
+ const textContent = content.value ?? "";
27743
+ return [`developer_instructions = ${serializeTomlValue(textContent)}`];
27744
+ }
27745
+ if (typeof content === "string") {
27746
+ return [`developer_instructions = ${serializeTomlValue(content)}`];
27747
+ }
27748
+ return [];
27749
+ }
27750
+ function mcpServerTomlTableHeader(serverName) {
27751
+ return /^[A-Za-z0-9_-]+$/.test(serverName) ? `[mcp_servers.${serverName}]` : `[mcp_servers."${escapeTomlString2(serverName)}"]`;
27752
+ }
27753
+ function mcpServerTableLines(serverName, config) {
27754
+ return [
27755
+ mcpServerTomlTableHeader(serverName),
27756
+ ...Object.entries(config).map(([key, val]) => `${key} = ${serializeTomlValue(val)}`)
27757
+ ];
27758
+ }
27759
+ function agentMcpServerLines(mcpServers, resolvedMcpServers) {
27760
+ if (mcpServers !== null && typeof mcpServers === "object" && !Array.isArray(mcpServers)) {
27761
+ const servers = mcpServers;
27762
+ return Object.entries(servers).filter(([, config]) => config !== null && typeof config === "object").flatMap(([name, config]) => mcpServerTableLines(name, config));
27763
+ }
27764
+ if (Array.isArray(mcpServers)) {
27765
+ return mcpServers.filter((serverName) => typeof serverName === "string").flatMap((serverName) => {
27766
+ const config = resolvedMcpServers?.get(serverName);
27767
+ return config ? mcpServerTableLines(serverName, config) : [];
27768
+ });
27769
+ }
27770
+ return [];
27771
+ }
27772
+ function serializeAgentToml(agentName, agent, resolvedMcpServers) {
27044
27773
  const lines = [];
27045
27774
  lines.push(`name = "${escapeTomlString2(agentName)}"`);
27046
27775
  const description = agent["description"];
27047
27776
  if (typeof description === "string") {
27048
27777
  lines.push(`description = "${escapeTomlString2(description)}"`);
27049
27778
  }
27050
- const content = agent["content"];
27051
- if (content !== null && typeof content === "object" && content.type === "TextContent") {
27052
- const textContent = content.value ?? "";
27053
- lines.push(`developer_instructions = ${serializeTomlValue(textContent)}`);
27054
- } else if (typeof content === "string") {
27055
- lines.push(`developer_instructions = ${serializeTomlValue(content)}`);
27056
- }
27779
+ lines.push(...agentInstructionsLines(agent["content"]));
27057
27780
  const model = agent["model"];
27058
27781
  if (typeof model === "string") {
27059
27782
  lines.push(`model = "${escapeTomlString2(model)}"`);
@@ -27071,30 +27794,14 @@ function serializeAgentToml(agentName, agent) {
27071
27794
  const escaped = nicknameCandidates.filter((c) => typeof c === "string").map((c) => `"${escapeTomlString2(c)}"`);
27072
27795
  lines.push(`nickname_candidates = [${escaped.join(", ")}]`);
27073
27796
  }
27074
- const mcpServers = agent["mcpServers"];
27075
- if (mcpServers !== null && typeof mcpServers === "object" && !Array.isArray(mcpServers)) {
27076
- const servers = mcpServers;
27077
- const serverEntries = Object.entries(servers);
27078
- if (serverEntries.length > 0) {
27079
- lines.push("");
27080
- lines.push("[mcp_servers]");
27081
- for (const [serverName, serverConfig] of serverEntries) {
27082
- if (serverConfig !== null && typeof serverConfig === "object") {
27083
- lines.push(`[mcp_servers.${serverName}]`);
27084
- const config = serverConfig;
27085
- for (const [key, val] of Object.entries(config)) {
27086
- lines.push(`${key} = ${serializeTomlValue(val)}`);
27087
- }
27088
- }
27089
- }
27090
- }
27797
+ const serverTables = agentMcpServerLines(agent["mcpServers"], resolvedMcpServers);
27798
+ if (serverTables.length > 0) {
27799
+ lines.push("", "[mcp_servers]", ...serverTables);
27091
27800
  }
27092
27801
  const skills2 = agent["skills"];
27093
27802
  if (Array.isArray(skills2)) {
27094
27803
  const skillNames = skills2.filter((s) => typeof s === "string").map((s) => `"${escapeTomlString2(s)}"`);
27095
- lines.push("");
27096
- lines.push("[skills]");
27097
- lines.push(`config = [${skillNames.join(", ")}]`);
27804
+ lines.push("", "[skills]", `config = [${skillNames.join(", ")}]`);
27098
27805
  }
27099
27806
  return lines.join("\n") + "\n";
27100
27807
  }
@@ -27111,6 +27818,7 @@ var init_codex = __esm({
27111
27818
  init_markdown_instruction_formatter();
27112
27819
  init_hook_adapters();
27113
27820
  init_hook_capability_warnings();
27821
+ init_agent_capability_warnings();
27114
27822
  init_mcp_helpers();
27115
27823
  init_plugin_helpers();
27116
27824
  CODEX_VERSIONS = {
@@ -27209,12 +27917,13 @@ var init_codex = __esm({
27209
27917
  format(ast, options) {
27210
27918
  const version = this.resolveVersion(options?.version);
27211
27919
  if (version === "simple") {
27212
- const output = appendTargetHookCapabilityWarnings(
27920
+ let output = appendTargetHookCapabilityWarnings(
27213
27921
  this.formatSimple(ast, options),
27214
27922
  ast,
27215
27923
  this.name,
27216
27924
  version
27217
27925
  );
27926
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
27218
27927
  return {
27219
27928
  ...output,
27220
27929
  managedOutputFiles: [
@@ -27267,16 +27976,17 @@ var init_codex = __esm({
27267
27976
  if (!managedDirs.includes(".codex")) managedDirs.push(".codex");
27268
27977
  }
27269
27978
  const mcpServersBlock = findMcpServersBlock(ast);
27270
- if (mcpServersBlock) {
27271
- const servers = extractMcpServers(mcpServersBlock);
27272
- if (servers.length > 0) {
27273
- extraFiles.push({
27274
- path: ".codex/mcp.json",
27275
- content: serializeMcpServersToJsonString(servers)
27276
- });
27277
- managedDirs.push(".codex");
27278
- }
27979
+ const definedMcpServers = mcpServersBlock ? extractMcpServers(mcpServersBlock) : [];
27980
+ if (definedMcpServers.length > 0) {
27981
+ extraFiles.push({
27982
+ path: ".codex/mcp.json",
27983
+ content: serializeMcpServersToJsonString(definedMcpServers)
27984
+ });
27985
+ managedDirs.push(".codex");
27279
27986
  }
27987
+ const resolvedMcpServers = new Map(
27988
+ definedMcpServers.map((server) => [server.name, mcpServerTomlConfig(server)])
27989
+ );
27280
27990
  const pluginsBlock = findPluginsBlock(ast);
27281
27991
  if (pluginsBlock) {
27282
27992
  const plugins = extractPlugins(pluginsBlock);
@@ -27297,7 +28007,7 @@ var init_codex = __esm({
27297
28007
  if (!isValidAgentName(agentName)) continue;
27298
28008
  const agent = agentValue;
27299
28009
  const nativeAgentName = nativeNames.get(agentName) ?? agentName;
27300
- const toml = serializeAgentToml(nativeAgentName, agent);
28010
+ const toml = serializeAgentToml(nativeAgentName, agent, resolvedMcpServers);
27301
28011
  extraFiles.push({
27302
28012
  path: `.codex/agents/${nativeAgentName}.toml`,
27303
28013
  content: toml
@@ -28051,6 +28761,7 @@ var init_grok = __esm({
28051
28761
  init_plugin_helpers();
28052
28762
  init_hook_adapters();
28053
28763
  init_hook_capability_warnings();
28764
+ init_agent_capability_warnings();
28054
28765
  GROK_VERSIONS = {
28055
28766
  simple: {
28056
28767
  name: "simple",
@@ -28097,6 +28808,7 @@ var init_grok = __esm({
28097
28808
  output = this.formatFull(ast, options);
28098
28809
  }
28099
28810
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
28811
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
28100
28812
  return {
28101
28813
  ...output,
28102
28814
  managedOutputFiles: [
@@ -28264,6 +28976,25 @@ var init_hermes = __esm({
28264
28976
  }
28265
28977
  });
28266
28978
 
28979
+ // packages/formatters/src/formatters/gitlab-duo.ts
28980
+ var GitlabDuoFormatter, GITLAB_DUO_VERSIONS;
28981
+ var init_gitlab_duo = __esm({
28982
+ "packages/formatters/src/formatters/gitlab-duo.ts"() {
28983
+ "use strict";
28984
+ init_create_simple_formatter();
28985
+ ({ Formatter: GitlabDuoFormatter, VERSIONS: GITLAB_DUO_VERSIONS } = createSimpleMarkdownFormatter({
28986
+ name: "gitlab-duo",
28987
+ outputPath: "AGENTS.md",
28988
+ description: "GitLab Duo instructions (Markdown)",
28989
+ mainFileHeader: "# AGENTS.md",
28990
+ dotDir: ".gitlab-duo",
28991
+ hasSkills: true,
28992
+ skillsInMultifile: false,
28993
+ skillsDir: "skills"
28994
+ }));
28995
+ }
28996
+ });
28997
+
28267
28998
  // packages/formatters/src/formatters/index.ts
28268
28999
  var init_formatters = __esm({
28269
29000
  "packages/formatters/src/formatters/index.ts"() {
@@ -28317,6 +29048,7 @@ var init_formatters = __esm({
28317
29048
  init_deep_agents();
28318
29049
  init_forgecode();
28319
29050
  init_hermes();
29051
+ init_gitlab_duo();
28320
29052
  }
28321
29053
  });
28322
29054
 
@@ -28374,6 +29106,7 @@ var init_builtin_formatters = __esm({
28374
29106
  init_deep_agents();
28375
29107
  init_forgecode();
28376
29108
  init_hermes();
29109
+ init_gitlab_duo();
28377
29110
  BUILTIN_FORMATTERS = {
28378
29111
  // Original 7
28379
29112
  github: GitHubFormatter,
@@ -28429,7 +29162,9 @@ var init_builtin_formatters = __esm({
28429
29162
  mimo: MimoFormatter,
28430
29163
  "deep-agents": DeepAgentsFormatter,
28431
29164
  forgecode: ForgecodeFormatter,
28432
- hermes: HermesFormatter
29165
+ hermes: HermesFormatter,
29166
+ // GitLab Duo (Agent Platform)
29167
+ "gitlab-duo": GitlabDuoFormatter
28433
29168
  };
28434
29169
  }
28435
29170
  });
@@ -30738,6 +31473,7 @@ function userConfigToProjectRegistry(userRegistry) {
30738
31473
  ...userRegistry.git.fallbackUrl ? { fallbackUrl: userRegistry.git.fallbackUrl } : {},
30739
31474
  ...userRegistry.git.ref ? { ref: userRegistry.git.ref } : {},
30740
31475
  ...userRegistry.git.path ? { path: userRegistry.git.path } : {},
31476
+ ...userRegistry.git.timeout !== void 0 ? { timeout: userRegistry.git.timeout } : {},
30741
31477
  ...userRegistry.git.auth ? { auth: userRegistry.git.auth } : {}
30742
31478
  };
30743
31479
  }
@@ -31252,6 +31988,7 @@ async function runInitCommand(options, services) {
31252
31988
  });
31253
31989
  }
31254
31990
  let migrationPrompt;
31991
+ let migrationPromptCopied = false;
31255
31992
  if (migrationMode === "llm") {
31256
31993
  migrationPrompt = prepareLlmMigration(aiToolsDetection.migrationCandidates, options);
31257
31994
  writes.push({ path: ".promptscript/migration-prompt.md", content: migrationPrompt });
@@ -31286,7 +32023,7 @@ async function runInitCommand(options, services) {
31286
32023
  return;
31287
32024
  }
31288
32025
  if (migrationPrompt) {
31289
- deliverMigrationPrompt(migrationPrompt);
32026
+ migrationPromptCopied = deliverMigrationPrompt(migrationPrompt);
31290
32027
  }
31291
32028
  const installedHookTargets = [];
31292
32029
  if (options.hooks !== false) {
@@ -31353,13 +32090,17 @@ async function runInitCommand(options, services) {
31353
32090
  ConsoleOutput.newline();
31354
32091
  ConsoleOutput.stats("Next steps:");
31355
32092
  if (migrationMode === "llm") {
31356
- ConsoleOutput.muted("1. Use the migration skill in your AI tool:");
31357
- const skillInvocations = getSkillInvocationHints(config.targets);
31358
- for (const hint of skillInvocations) {
31359
- ConsoleOutput.muted(` ${hint}`);
32093
+ ConsoleOutput.info("1. Start your AI agent in this project");
32094
+ if (migrationPromptCopied) {
32095
+ ConsoleOutput.info("2. Paste the migration prompt - it is already in your clipboard");
32096
+ } else {
32097
+ ConsoleOutput.info(
32098
+ "2. Copy the migration prompt from the terminal output and paste it into your agent"
32099
+ );
31360
32100
  }
31361
- ConsoleOutput.muted("2. Review generated .promptscript/project.prs");
31362
- ConsoleOutput.muted("3. Run: prs compile");
32101
+ ConsoleOutput.info("3. Let the agent migrate your instructions");
32102
+ ConsoleOutput.info("4. Review the generated files, then run: prs validate --strict");
32103
+ ConsoleOutput.info("5. Run: prs compile");
31363
32104
  } else if (migrationMode === "static") {
31364
32105
  ConsoleOutput.muted("1. Review imported .promptscript/ files");
31365
32106
  ConsoleOutput.muted("2. Run: prs validate --strict");
@@ -31499,6 +32240,7 @@ function deliverMigrationPrompt(prompt) {
31499
32240
  console.log(prompt);
31500
32241
  }
31501
32242
  ConsoleOutput.info("Saved to .promptscript/migration-prompt.md");
32243
+ return copied;
31502
32244
  }
31503
32245
  function getSkillWrites(targets) {
31504
32246
  const writes = [];
@@ -31841,16 +32583,6 @@ function getTargetSkillDir(target, skillName) {
31841
32583
  const dir = `${basePath}/${skillName}`;
31842
32584
  return { dir, path: `${dir}/${fileName}` };
31843
32585
  }
31844
- function getSkillInvocationHints(targets) {
31845
- const hints = [];
31846
- for (const target of targets) {
31847
- const formatter = FormatterRegistry.get(target);
31848
- if (formatter && formatter.getSkillBasePath()) {
31849
- hints.push(`${formatter.description}: /promptscript`);
31850
- }
31851
- }
31852
- return hints;
31853
- }
31854
32586
  function generateConfig(config) {
31855
32587
  const syntaxVersion = getLatestSyntaxVersion();
31856
32588
  const output = {
@@ -35272,9 +36004,9 @@ var require_rate_limit = __commonJS({
35272
36004
  throw new Error("The configured rate-limit store does not implement a `read` method, which is required to use `{ increment: false }`");
35273
36005
  }
35274
36006
  try {
35275
- const res = await new Promise((resolve42, reject) => {
36007
+ const res = await new Promise((resolve43, reject) => {
35276
36008
  store[storeMethod](key, (err, res2) => {
35277
- err ? reject(err) : resolve42(res2);
36009
+ err ? reject(err) : resolve43(res2);
35278
36010
  }, timeWindow, max);
35279
36011
  });
35280
36012
  current = res.current;
@@ -35444,16 +36176,16 @@ var init_config2 = __esm({
35444
36176
  });
35445
36177
 
35446
36178
  // packages/server/src/path-guard.ts
35447
- import { resolve as resolve40, relative as relative19, isAbsolute as isAbsolute17, dirname as dirname25, basename as basename19 } from "path";
36179
+ import { resolve as resolve41, relative as relative21, isAbsolute as isAbsolute19, dirname as dirname25, basename as basename19 } from "path";
35448
36180
  import { realpath as realpath6 } from "fs/promises";
35449
36181
  async function resolveSafePath(workspace, requestedPath) {
35450
36182
  const decoded = decodeURIComponent(requestedPath);
35451
- if (isAbsolute17(decoded)) {
36183
+ if (isAbsolute19(decoded)) {
35452
36184
  throw new PathTraversalError(requestedPath);
35453
36185
  }
35454
- const resolved = resolve40(workspace, decoded);
35455
- const rel = relative19(workspace, resolved);
35456
- if (rel.startsWith("..") || isAbsolute17(rel)) {
36186
+ const resolved = resolve41(workspace, decoded);
36187
+ const rel = relative21(workspace, resolved);
36188
+ if (rel.startsWith("..") || isAbsolute19(rel)) {
35457
36189
  throw new PathTraversalError(requestedPath);
35458
36190
  }
35459
36191
  let realWorkspace;
@@ -35469,7 +36201,7 @@ async function resolveSafePath(workspace, requestedPath) {
35469
36201
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
35470
36202
  try {
35471
36203
  const realParent = await realpath6(dirname25(resolved));
35472
- real = resolve40(realParent, basename19(resolved));
36204
+ real = resolve41(realParent, basename19(resolved));
35473
36205
  } catch {
35474
36206
  return resolved;
35475
36207
  }
@@ -35477,8 +36209,8 @@ async function resolveSafePath(workspace, requestedPath) {
35477
36209
  throw err;
35478
36210
  }
35479
36211
  }
35480
- const realRel = relative19(realWorkspace, real);
35481
- if (realRel.startsWith("..") || isAbsolute17(realRel)) {
36212
+ const realRel = relative21(realWorkspace, real);
36213
+ if (realRel.startsWith("..") || isAbsolute19(realRel)) {
35482
36214
  throw new PathTraversalError(requestedPath);
35483
36215
  }
35484
36216
  return resolved;
@@ -35497,7 +36229,7 @@ var init_path_guard = __esm({
35497
36229
  });
35498
36230
 
35499
36231
  // packages/server/src/routes/files.ts
35500
- import { readFile as readFile32, writeFile as writeFile12, unlink as unlink3, stat as stat4, mkdir as mkdir11 } from "fs/promises";
36232
+ import { readFile as readFile32, writeFile as writeFile12, unlink as unlink3, stat as stat4, mkdir as mkdir12 } from "fs/promises";
35501
36233
  import { dirname as dirname26 } from "path";
35502
36234
  import fg from "fast-glob";
35503
36235
  function registerRoutes(app, workspace, readOnly) {
@@ -35558,7 +36290,7 @@ function registerRoutes(app, workspace, readOnly) {
35558
36290
  return reply.status(409).send({ error: "File already exists" });
35559
36291
  } catch {
35560
36292
  }
35561
- await mkdir11(dirname26(resolved), { recursive: true });
36293
+ await mkdir12(dirname26(resolved), { recursive: true });
35562
36294
  await writeFile12(resolved, request.body.content, "utf-8");
35563
36295
  return reply.status(201).send({ path: filePath, status: "created" });
35564
36296
  } catch (err) {
@@ -35596,7 +36328,7 @@ var init_files = __esm({
35596
36328
 
35597
36329
  // packages/server/src/watcher.ts
35598
36330
  import { watch } from "chokidar";
35599
- import { relative as relative20 } from "path";
36331
+ import { relative as relative22 } from "path";
35600
36332
  function isWatchedFile(filePath) {
35601
36333
  const base = filePath.split("/").pop() ?? "";
35602
36334
  if (CONFIG_FILENAMES2.has(base)) return true;
@@ -35612,15 +36344,15 @@ function createFileWatcher(workspace, watchPaths, onEvent) {
35612
36344
  });
35613
36345
  watcher.on("add", (filePath) => {
35614
36346
  if (!ready || !isWatchedFile(filePath)) return;
35615
- onEvent({ type: "file:created", path: relative20(workspace, filePath) });
36347
+ onEvent({ type: "file:created", path: relative22(workspace, filePath) });
35616
36348
  });
35617
36349
  watcher.on("change", (filePath) => {
35618
36350
  if (!ready || !isWatchedFile(filePath)) return;
35619
- onEvent({ type: "file:changed", path: relative20(workspace, filePath) });
36351
+ onEvent({ type: "file:changed", path: relative22(workspace, filePath) });
35620
36352
  });
35621
36353
  watcher.on("unlink", (filePath) => {
35622
36354
  if (!ready || !isWatchedFile(filePath)) return;
35623
- onEvent({ type: "file:deleted", path: relative20(workspace, filePath) });
36355
+ onEvent({ type: "file:deleted", path: relative22(workspace, filePath) });
35624
36356
  });
35625
36357
  return watcher;
35626
36358
  }
@@ -35787,7 +36519,7 @@ var migrate_exports = {};
35787
36519
  __export(migrate_exports, {
35788
36520
  migrateCommand: () => migrateCommand
35789
36521
  });
35790
- import { dirname as dirname27, relative as relative21, resolve as resolve41 } from "path";
36522
+ import { dirname as dirname27, relative as relative23, resolve as resolve42 } from "path";
35791
36523
  import { parse as parseYaml17 } from "yaml";
35792
36524
  async function migrateCommand(options, services = createDefaultServices()) {
35793
36525
  const previousOutputStream = getContext().outputStream;
@@ -36045,7 +36777,7 @@ function extractTargetNames(targets) {
36045
36777
  }
36046
36778
  async function createStaticMigrationWrites(candidates, config, entryPaths, services) {
36047
36779
  const result = await importMultipleFiles(
36048
- candidates.map((candidate) => resolve41(services.cwd, candidate.path)),
36780
+ candidates.map((candidate) => resolve42(services.cwd, candidate.path)),
36049
36781
  {
36050
36782
  projectName: config.id,
36051
36783
  syntaxVersion: config.syntax || getLatestSyntaxVersion()
@@ -36079,7 +36811,7 @@ async function createStaticMigrationWrites(candidates, config, entryPaths, servi
36079
36811
  const migratedProjectPath = ".promptscript/migrated/project.prs";
36080
36812
  writes.push({ path: migratedProjectPath, content: markMigrationOutput(importedProject) });
36081
36813
  for (const entryPath of entryPaths) {
36082
- const useLine = `@use ${toUsePath(entryPath, migratedProjectPath)}`;
36814
+ const useLine = `@use ${toUsePath2(entryPath, migratedProjectPath)}`;
36083
36815
  if (services.fs.existsSync(entryPath)) {
36084
36816
  const existing = await services.fs.readFile(entryPath, "utf-8");
36085
36817
  const hasUseLine = existing.split(/\r?\n/).some((line) => line.trim() === useLine);
@@ -36137,9 +36869,9 @@ function markMigrationOutput(content) {
36137
36869
  return `# promptscript-generated: migration
36138
36870
  ${content}`;
36139
36871
  }
36140
- function toUsePath(entryPath, targetPath) {
36872
+ function toUsePath2(entryPath, targetPath) {
36141
36873
  const withoutExtension = targetPath.replace(/\.prs$/, "");
36142
- const path2 = relative21(dirname27(entryPath), withoutExtension).replace(/\\/g, "/");
36874
+ const path2 = relative23(dirname27(entryPath), withoutExtension).replace(/\\/g, "/");
36143
36875
  return path2.startsWith("./") || path2.startsWith("../") ? path2 : `./${path2}`;
36144
36876
  }
36145
36877
  function validateProjectPath(path2) {
@@ -36204,12 +36936,13 @@ import { fileURLToPath as fileURLToPath4, pathToFileURL } from "url";
36204
36936
  import { dirname as dirname28 } from "path";
36205
36937
 
36206
36938
  // packages/cli/src/commands/compile.ts
36207
- import { resolve as resolve21, isAbsolute as isAbsolute13, relative as relative14 } from "path";
36208
- import { readFile as readFile16 } from "fs/promises";
36209
- import { existsSync as existsSync14, lstatSync as lstatSync2 } from "fs";
36939
+ import { resolve as resolve22, isAbsolute as isAbsolute14, relative as relative15 } from "node:path";
36940
+ import { homedir as homedir4 } from "node:os";
36941
+ import { readFile as readFile16 } from "node:fs/promises";
36942
+ import { existsSync as existsSync14, lstatSync as lstatSync2 } from "node:fs";
36210
36943
  import chokidar from "chokidar";
36211
36944
 
36212
- // node_modules/.pnpm/balanced-match@4.0.3/node_modules/balanced-match/dist/esm/index.js
36945
+ // node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js
36213
36946
  var balanced = (a, b, str) => {
36214
36947
  const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
36215
36948
  const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
@@ -38149,6 +38882,14 @@ function findFallbackUrl(repoUrl, registries) {
38149
38882
  }
38150
38883
  return void 0;
38151
38884
  }
38885
+ function findRegistryEntry(repoUrl, registries) {
38886
+ for (const entry of Object.values(registries)) {
38887
+ if (typeof entry !== "string" && entry.url === repoUrl) {
38888
+ return entry;
38889
+ }
38890
+ }
38891
+ return void 0;
38892
+ }
38152
38893
 
38153
38894
  // packages/resolver/src/loader.ts
38154
38895
  var REGISTRY_MARKER_PREFIX = "__registry__:";
@@ -39169,7 +39910,8 @@ function parseSkillMd(content, sourceFile = "<skill>") {
39169
39910
  sourceFile,
39170
39911
  frontmatter.location,
39171
39912
  yamlFrontmatter.fieldLocations,
39172
- yamlFrontmatter.fieldItemLocations
39913
+ yamlFrontmatter.fieldItemLocations,
39914
+ yamlFrontmatter.fieldValueLocations
39173
39915
  );
39174
39916
  const bodyContent = stripLeadingHtmlMarker(frontmatter.body).trim();
39175
39917
  const result = {
@@ -39294,11 +40036,11 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39294
40036
  }
39295
40037
  rejectUnsafeYamlNodes(document.contents, sourceFile, frontmatter);
39296
40038
  enforceFrontmatterLimits(document.contents, sourceFile, frontmatter);
39297
- const { fields: fieldLocations, items: fieldItemLocations } = getTopLevelFieldLocations(
39298
- document.contents,
39299
- sourceFile,
39300
- frontmatter
39301
- );
40039
+ const {
40040
+ fields: fieldLocations,
40041
+ items: fieldItemLocations,
40042
+ values: fieldValueLocations
40043
+ } = getTopLevelFieldLocations(document.contents, sourceFile, frontmatter);
39302
40044
  let value;
39303
40045
  try {
39304
40046
  value = document.toJS({ maxAliasCount: 0 });
@@ -39311,7 +40053,7 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39311
40053
  );
39312
40054
  }
39313
40055
  if (document.contents === null) {
39314
- return { fields: {}, fieldLocations, fieldItemLocations };
40056
+ return { fields: {}, fieldLocations, fieldItemLocations, fieldValueLocations };
39315
40057
  }
39316
40058
  if (!isRecord5(value)) {
39317
40059
  throw frontmatterError(
@@ -39320,7 +40062,7 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39320
40062
  frontmatter.location
39321
40063
  );
39322
40064
  }
39323
- return { fields: value, fieldLocations, fieldItemLocations };
40065
+ return { fields: value, fieldLocations, fieldItemLocations, fieldValueLocations };
39324
40066
  }
39325
40067
  function getYamlLocation(frontmatter, sourceFile, offset) {
39326
40068
  const source = frontmatter.raw;
@@ -39339,7 +40081,10 @@ function getYamlLocation(frontmatter, sourceFile, offset) {
39339
40081
  function getTopLevelFieldLocations(node, sourceFile, frontmatter) {
39340
40082
  const fieldLocations = /* @__PURE__ */ new Map();
39341
40083
  const fieldItemLocations = /* @__PURE__ */ new Map();
39342
- if (!isCollection(node)) return { fields: fieldLocations, items: fieldItemLocations };
40084
+ const fieldValueLocations = /* @__PURE__ */ new Map();
40085
+ if (!isCollection(node)) {
40086
+ return { fields: fieldLocations, items: fieldItemLocations, values: fieldValueLocations };
40087
+ }
39343
40088
  for (const item of node.items) {
39344
40089
  if (!isPair(item)) continue;
39345
40090
  const key = getScalarString(item.key);
@@ -39351,9 +40096,21 @@ function getTopLevelFieldLocations(node, sourceFile, frontmatter) {
39351
40096
  key,
39352
40097
  item.value.items.map((value) => getNodeLocation(value, sourceFile, frontmatter))
39353
40098
  );
40099
+ fieldValueLocations.set(key, getNestedFieldLocations(item.value, sourceFile, frontmatter));
39354
40100
  }
39355
40101
  }
39356
- return { fields: fieldLocations, items: fieldItemLocations };
40102
+ return { fields: fieldLocations, items: fieldItemLocations, values: fieldValueLocations };
40103
+ }
40104
+ function getNestedFieldLocations(collection, sourceFile, frontmatter) {
40105
+ const valueLocations = /* @__PURE__ */ new Map();
40106
+ if (!isCollection(collection)) return valueLocations;
40107
+ for (const item of collection.items) {
40108
+ if (!isPair(item)) continue;
40109
+ const key = getScalarString(item.key);
40110
+ if (key === void 0) continue;
40111
+ valueLocations.set(key, getNodeLocation(item.value ?? item.key, sourceFile, frontmatter));
40112
+ }
40113
+ return valueLocations;
39357
40114
  }
39358
40115
  function getScalarString(node) {
39359
40116
  if (!isNode(node) || !("value" in node)) return void 0;
@@ -39468,7 +40225,7 @@ function frontmatterError(message, sourceFile, location) {
39468
40225
  file: sourceFile
39469
40226
  });
39470
40227
  }
39471
- function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocations, fieldItemLocations) {
40228
+ function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocations, fieldItemLocations, fieldValueLocations) {
39472
40229
  const name = readOptionalString(
39473
40230
  fields,
39474
40231
  "name",
@@ -39526,7 +40283,8 @@ function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocat
39526
40283
  const metadata = parseMetadataField(
39527
40284
  fields,
39528
40285
  sourceFile,
39529
- getFieldLocation("metadata", fieldLocations, fallbackLocation)
40286
+ getFieldLocation("metadata", fieldLocations, fallbackLocation),
40287
+ fieldValueLocations.get("metadata")
39530
40288
  );
39531
40289
  const allowedTools = parseAllowedToolsField(
39532
40290
  fields,
@@ -39789,7 +40547,7 @@ function readOptionalStringFromRecord(record, field, context, sourceFile, locati
39789
40547
  }
39790
40548
  return value;
39791
40549
  }
39792
- function parseMetadataField(fields, sourceFile, location) {
40550
+ function parseMetadataField(fields, sourceFile, location, valueLocations) {
39793
40551
  if (!Object.hasOwn(fields, "metadata")) return void 0;
39794
40552
  const value = fields["metadata"];
39795
40553
  if (!isRecord5(value)) {
@@ -39798,7 +40556,11 @@ function parseMetadataField(fields, sourceFile, location) {
39798
40556
  const metadata = createSafeRecord();
39799
40557
  for (const [key, item] of Object.entries(value)) {
39800
40558
  if (typeof item !== "string") {
39801
- throw frontmatterError(`metadata value "${key}" must be a string`, sourceFile, location);
40559
+ throw frontmatterError(
40560
+ `metadata value "${key}" must be a string; quote scalar values (for example, 16 as "16")`,
40561
+ sourceFile,
40562
+ valueLocations?.get(key) ?? location
40563
+ );
39802
40564
  }
39803
40565
  metadata[key] = item;
39804
40566
  }
@@ -40977,26 +41739,29 @@ var VIRTUAL_LOC = {
40977
41739
  column: 1,
40978
41740
  offset: 0
40979
41741
  };
40980
- function makeObjectContent(properties) {
41742
+ function fileLoc(file) {
41743
+ return { file, line: 1, column: 1, offset: 0 };
41744
+ }
41745
+ function makeObjectContent(properties, file) {
40981
41746
  return {
40982
41747
  type: "ObjectContent",
40983
41748
  properties,
40984
- loc: VIRTUAL_LOC
41749
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
40985
41750
  };
40986
41751
  }
40987
41752
  function makeTextContent(value, file) {
40988
41753
  return {
40989
41754
  type: "TextContent",
40990
41755
  value,
40991
- loc: file ? { file, line: 1, column: 1, offset: 0 } : VIRTUAL_LOC
41756
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
40992
41757
  };
40993
41758
  }
40994
- function makeBlock(name, content) {
41759
+ function makeBlock(name, content, file) {
40995
41760
  return {
40996
41761
  type: "Block",
40997
41762
  name,
40998
41763
  content,
40999
- loc: VIRTUAL_LOC
41764
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
41000
41765
  };
41001
41766
  }
41002
41767
 
@@ -41848,9 +42613,29 @@ var GitCacheManager = class {
41848
42613
  // packages/resolver/src/git-registry.ts
41849
42614
  var TAGS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
41850
42615
  var DEFAULT_GIT_TIMEOUT_MS = 6e4;
42616
+ var GIT_TIMEOUT_ENV_VAR = "PROMPTSCRIPT_GIT_TIMEOUT";
41851
42617
  var SEMVER_TAG_RE = /^v?\d+\.\d+\.\d+/;
42618
+ function envGitTimeout() {
42619
+ const raw = process.env[GIT_TIMEOUT_ENV_VAR];
42620
+ if (raw === void 0 || raw.trim() === "") {
42621
+ return void 0;
42622
+ }
42623
+ const parsed = Number(raw);
42624
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
42625
+ }
41852
42626
  function normalizeGitTimeout(timeout) {
41853
- return timeout !== void 0 && Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_GIT_TIMEOUT_MS;
42627
+ if (timeout !== void 0 && Number.isFinite(timeout) && timeout > 0) {
42628
+ return timeout;
42629
+ }
42630
+ const fromEnv = envGitTimeout();
42631
+ if (fromEnv !== void 0) {
42632
+ return fromEnv;
42633
+ }
42634
+ return DEFAULT_GIT_TIMEOUT_MS;
42635
+ }
42636
+ function isPartialCloneUnsupported(error) {
42637
+ const message = error.message.toLowerCase();
42638
+ return message.includes("filtering not recognized") || message.includes("does not support filtering") || message.includes("invalid filter") || message.includes("filter is not supported") || /unknown (option|switch)[`' ]*(sparse|filter)/.test(message);
41854
42639
  }
41855
42640
  var GitCloneError = class extends Error {
41856
42641
  constructor(message, url, cause) {
@@ -42082,17 +42867,20 @@ var GitRegistry = class {
42082
42867
  * @param tag - Git tag to check out, or undefined for the default branch
42083
42868
  * @param targetDir - Directory to clone into
42084
42869
  * @param fallbackRepoUrl - Optional fallback URL to try on auth failure
42870
+ * @param sparsePath - Optional subdirectory to limit the checkout to.
42871
+ * Uses `--filter=blob:none --sparse` + `sparse-checkout` so only the
42872
+ * blobs for that subdirectory are fetched. Falls back to a plain
42873
+ * shallow clone when the server does not support partial clones.
42085
42874
  */
42086
- async cloneAtTag(repoUrl, tag, targetDir, fallbackRepoUrl) {
42875
+ async cloneAtTag(repoUrl, tag, targetDir, fallbackRepoUrl, sparsePath) {
42087
42876
  if (existsSync8(targetDir)) {
42088
42877
  await fs2.rm(targetDir, { recursive: true, force: true });
42089
42878
  }
42090
42879
  await fs2.mkdir(targetDir, { recursive: true });
42091
42880
  const git = this.createGit();
42092
- const cloneOptions = tag ? ["--depth=1", `--branch=${tag}`, "--single-branch"] : ["--depth=1"];
42093
42881
  const cloneUrl = this.auth ? this.transportUrl : repoUrl;
42094
42882
  try {
42095
- await git.clone(cloneUrl, targetDir, cloneOptions);
42883
+ await this.performClone(git, cloneUrl, targetDir, tag, sparsePath);
42096
42884
  } catch (err) {
42097
42885
  const error = err instanceof Error ? err : new Error(String(err));
42098
42886
  if (isGitTimeoutError(error)) {
@@ -42104,24 +42892,15 @@ var GitRegistry = class {
42104
42892
  if (this.isAccessError(error)) {
42105
42893
  if (fallbackRepoUrl) {
42106
42894
  try {
42107
- await git.clone(fallbackRepoUrl, targetDir, cloneOptions);
42895
+ await this.performClone(git, fallbackRepoUrl, targetDir, tag, sparsePath);
42108
42896
  return;
42109
42897
  } catch (fallbackErr) {
42110
- const fbError = fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr));
42111
- if (isGitTimeoutError(fbError)) {
42112
- throw createGitTimeoutError(fallbackRepoUrl, this.timeout, fbError);
42113
- }
42114
- if (this.isAccessError(fbError)) {
42115
- throw new GitAuthError(
42116
- `Authentication failed for both ${repoUrl} and fallback ${fallbackRepoUrl}`,
42117
- fallbackRepoUrl,
42118
- fbError
42119
- );
42120
- }
42121
- throw new GitCloneError(
42122
- `Failed to clone ${fallbackRepoUrl} at ${tag ?? "default branch"}: ${fbError.message}`,
42898
+ throw this.classifyFallbackCloneError(
42899
+ repoUrl,
42123
42900
  fallbackRepoUrl,
42124
- fbError
42901
+ fallbackErr,
42902
+ tag,
42903
+ "clone"
42125
42904
  );
42126
42905
  }
42127
42906
  }
@@ -42134,6 +42913,92 @@ var GitRegistry = class {
42134
42913
  );
42135
42914
  }
42136
42915
  }
42916
+ /**
42917
+ * Clone shallow, optionally partial (`--filter=blob:none --sparse`) with a
42918
+ * sparse-checkout cone of `sparsePath`. Retries without the partial-clone
42919
+ * flags when the server or local Git does not support them.
42920
+ */
42921
+ async performClone(git, url, targetDir, tag, sparsePath) {
42922
+ const tagOptions = tag ? ["--branch=" + tag, "--single-branch"] : [];
42923
+ const baseOptions = ["--depth=1", ...tagOptions];
42924
+ if (!sparsePath) {
42925
+ await git.clone(url, targetDir, baseOptions);
42926
+ return;
42927
+ }
42928
+ try {
42929
+ await git.clone(url, targetDir, [...baseOptions, "--filter=blob:none", "--sparse"]);
42930
+ } catch (err) {
42931
+ const error = err instanceof Error ? err : new Error(String(err));
42932
+ if (!isPartialCloneUnsupported(error)) {
42933
+ throw error;
42934
+ }
42935
+ if (existsSync8(targetDir)) {
42936
+ await fs2.rm(targetDir, { recursive: true, force: true });
42937
+ }
42938
+ await fs2.mkdir(targetDir, { recursive: true });
42939
+ await git.clone(url, targetDir, baseOptions);
42940
+ return;
42941
+ }
42942
+ const repoGit = this.createGit(targetDir);
42943
+ await repoGit.raw(["sparse-checkout", "set", sparsePath]);
42944
+ }
42945
+ /**
42946
+ * Check whether the checkout at `targetDir` uses sparse-checkout.
42947
+ */
42948
+ async isSparseCheckout(targetDir) {
42949
+ const git = this.createGit(targetDir);
42950
+ try {
42951
+ const out = await git.raw(["config", "core.sparseCheckout"]);
42952
+ return out.trim() === "true";
42953
+ } catch {
42954
+ return false;
42955
+ }
42956
+ }
42957
+ /**
42958
+ * Disable sparse-checkout, materializing the full working tree.
42959
+ * Used when a root-level import needs the whole repository but the
42960
+ * cached clone is partial.
42961
+ */
42962
+ async disableSparseCheckout(targetDir) {
42963
+ try {
42964
+ await this.createGit(targetDir).raw(["sparse-checkout", "disable"]);
42965
+ } catch (err) {
42966
+ this.throwIfGitTimeout(err);
42967
+ throw err instanceof Error ? err : new Error(String(err));
42968
+ }
42969
+ }
42970
+ /** Convert a raw Git error into a GitCloneError when it is a timeout. */
42971
+ throwIfGitTimeout(err) {
42972
+ const error = err instanceof Error ? err : new Error(String(err));
42973
+ if (isGitTimeoutError(error)) {
42974
+ throw createGitTimeoutError(this.url, this.timeout, error);
42975
+ }
42976
+ }
42977
+ /**
42978
+ * Add paths to an existing sparse checkout so their blobs are fetched
42979
+ * on demand. Used when a cached partial clone needs a subdirectory that
42980
+ * the original cone did not cover.
42981
+ */
42982
+ async addSparsePaths(targetDir, paths) {
42983
+ const extra = paths.filter((p) => p.length > 0 && p !== ".");
42984
+ if (extra.length === 0) {
42985
+ return;
42986
+ }
42987
+ const git = this.createGit(targetDir);
42988
+ try {
42989
+ await git.raw(["sparse-checkout", "add", ...extra]);
42990
+ } catch (addError) {
42991
+ const original = addError instanceof Error ? addError : new Error(String(addError));
42992
+ try {
42993
+ const listed = await git.raw(["sparse-checkout", "list"]);
42994
+ const current = listed.split("\n").map((line) => line.trim()).filter(Boolean);
42995
+ await git.raw(["sparse-checkout", "set", .../* @__PURE__ */ new Set([...current, ...extra])]);
42996
+ } catch (setErr) {
42997
+ this.throwIfGitTimeout(setErr);
42998
+ throw original;
42999
+ }
43000
+ }
43001
+ }
42137
43002
  /**
42138
43003
  * List all semver tags from a remote repo.
42139
43004
  * Results are cached via RegistryCache to avoid repeated ls-remote calls.
@@ -42199,8 +43064,9 @@ var GitRegistry = class {
42199
43064
  }
42200
43065
  await fs2.mkdir(targetDir, { recursive: true });
42201
43066
  const git = this.createGit();
43067
+ const cloneUrl = this.auth ? this.transportUrl : repoUrl;
42202
43068
  try {
42203
- await this.doCloneSparse(git, repoUrl, ref, targetDir, sparsePath);
43069
+ await this.performClone(git, cloneUrl, targetDir, ref, sparsePath);
42204
43070
  } catch (err) {
42205
43071
  const error = err instanceof Error ? err : new Error(String(err));
42206
43072
  if (isGitTimeoutError(error)) {
@@ -42208,24 +43074,15 @@ var GitRegistry = class {
42208
43074
  }
42209
43075
  if (this.isAccessError(error) && fallbackRepoUrl) {
42210
43076
  try {
42211
- await this.doCloneSparse(git, fallbackRepoUrl, ref, targetDir, sparsePath);
43077
+ await this.performClone(git, fallbackRepoUrl, targetDir, ref, sparsePath);
42212
43078
  return;
42213
43079
  } catch (fallbackErr) {
42214
- const fbError = fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr));
42215
- if (isGitTimeoutError(fbError)) {
42216
- throw createGitTimeoutError(fallbackRepoUrl, this.timeout, fbError);
42217
- }
42218
- if (this.isAccessError(fbError)) {
42219
- throw new GitAuthError(
42220
- `Authentication failed for both ${repoUrl} and fallback ${fallbackRepoUrl}`,
42221
- fallbackRepoUrl,
42222
- fbError
42223
- );
42224
- }
42225
- throw new GitCloneError(
42226
- `Failed to sparse-clone ${fallbackRepoUrl} at ${ref}: ${fbError.message}`,
43080
+ throw this.classifyFallbackCloneError(
43081
+ repoUrl,
42227
43082
  fallbackRepoUrl,
42228
- fbError
43083
+ fallbackErr,
43084
+ ref,
43085
+ "sparse-clone"
42229
43086
  );
42230
43087
  }
42231
43088
  }
@@ -42243,22 +43100,26 @@ var GitRegistry = class {
42243
43100
  }
42244
43101
  }
42245
43102
  /**
42246
- * Internal sparse-clone implementation (no fallback logic).
43103
+ * Classify an error from the fallback clone attempt shared by
43104
+ * `cloneAtTag` and `cloneSparse`: timeout, auth, or a plain clone failure.
42247
43105
  */
42248
- async doCloneSparse(git, repoUrl, ref, targetDir, sparsePath) {
42249
- if (existsSync8(targetDir)) {
42250
- await fs2.rm(targetDir, { recursive: true, force: true });
43106
+ classifyFallbackCloneError(repoUrl, fallbackRepoUrl, err, ref, action) {
43107
+ const fbError = err instanceof Error ? err : new Error(String(err));
43108
+ if (isGitTimeoutError(fbError)) {
43109
+ return createGitTimeoutError(fallbackRepoUrl, this.timeout, fbError);
43110
+ }
43111
+ if (this.isAccessError(fbError)) {
43112
+ return new GitAuthError(
43113
+ `Authentication failed for both ${repoUrl} and fallback ${fallbackRepoUrl}`,
43114
+ fallbackRepoUrl,
43115
+ fbError
43116
+ );
42251
43117
  }
42252
- await fs2.mkdir(targetDir, { recursive: true });
42253
- await git.clone(repoUrl, targetDir, [
42254
- "--depth=1",
42255
- `--branch=${ref}`,
42256
- "--single-branch",
42257
- "--filter=blob:none",
42258
- "--sparse"
42259
- ]);
42260
- const repoGit = this.createGit(targetDir);
42261
- await repoGit.raw(["sparse-checkout", "set", sparsePath]);
43118
+ return new GitCloneError(
43119
+ `Failed to ${action} ${fallbackRepoUrl} at ${ref ?? "default branch"}: ${fbError.message}`,
43120
+ fallbackRepoUrl,
43121
+ fbError
43122
+ );
42262
43123
  }
42263
43124
  /**
42264
43125
  * Ensure the repository is cloned and at the correct ref.
@@ -42657,7 +43518,7 @@ function createGitTimeoutError(url, timeout, cause) {
42657
43518
  return cause;
42658
43519
  }
42659
43520
  return new GitCloneError(
42660
- `Git operation timed out after ${timeout}ms while contacting ${url}`,
43521
+ `Git operation timed out after ${timeout}ms while contacting ${url}. Raise the limit with the 'timeout' field on the registry entry or the ${GIT_TIMEOUT_ENV_VAR} environment variable.`,
42661
43522
  url,
42662
43523
  cause
42663
43524
  );
@@ -43069,7 +43930,7 @@ async function discoverNativeContent(dir, logger) {
43069
43930
  }
43070
43931
  }
43071
43932
  if (Object.keys(mergedSkills).length > 0) {
43072
- blocks.push(makeBlock("skills", makeObjectContent(mergedSkills)));
43933
+ blocks.push(makeBlock("skills", makeObjectContent(mergedSkills, dir), dir));
43073
43934
  }
43074
43935
  const rootAgents = await discoverAgents(dir) ?? {};
43075
43936
  const wrappedAgents = {};
@@ -43082,7 +43943,7 @@ async function discoverNativeContent(dir, logger) {
43082
43943
  }
43083
43944
  const mergedAgents = { ...wrappedAgents, ...rootAgents };
43084
43945
  if (Object.keys(mergedAgents).length > 0) {
43085
- blocks.push(makeBlock("agents", makeObjectContent(mergedAgents)));
43946
+ blocks.push(makeBlock("agents", makeObjectContent(mergedAgents, dir), dir));
43086
43947
  }
43087
43948
  const rootCommands = await discoverCommands(dir) ?? {};
43088
43949
  const wrappedCommands = {};
@@ -43095,11 +43956,11 @@ async function discoverNativeContent(dir, logger) {
43095
43956
  }
43096
43957
  const mergedCommands = { ...wrappedCommands, ...rootCommands };
43097
43958
  if (Object.keys(mergedCommands).length > 0) {
43098
- blocks.push(makeBlock("shortcuts", makeObjectContent(mergedCommands)));
43959
+ blocks.push(makeBlock("shortcuts", makeObjectContent(mergedCommands, dir), dir));
43099
43960
  }
43100
43961
  const contextContent = await discoverContext(dir);
43101
43962
  if (contextContent) {
43102
- blocks.push(makeBlock("context", contextContent));
43963
+ blocks.push(makeBlock("context", contextContent, contextContent.loc.file));
43103
43964
  }
43104
43965
  if (blocks.length === 0) {
43105
43966
  return null;
@@ -43109,7 +43970,7 @@ async function discoverNativeContent(dir, logger) {
43109
43970
  blocks,
43110
43971
  uses: [],
43111
43972
  extends: [],
43112
- loc: VIRTUAL_LOC
43973
+ loc: fileLoc(dir)
43113
43974
  };
43114
43975
  return program2;
43115
43976
  }
@@ -43156,6 +44017,37 @@ async function readBoundedRegularFile(filePath, maxBytes, description) {
43156
44017
  await handle.close();
43157
44018
  }
43158
44019
  }
44020
+ async function readOptionalBoundedRegularFile(filePath, description) {
44021
+ try {
44022
+ return await readBoundedRegularFile(filePath, MAX_GIT_CONFIG_BYTES, description);
44023
+ } catch (error) {
44024
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
44025
+ return Buffer.alloc(0);
44026
+ }
44027
+ throw error;
44028
+ }
44029
+ }
44030
+ async function readGitBooleanConfig(filePath, key, description) {
44031
+ try {
44032
+ const result = await execFileAsync(
44033
+ "git",
44034
+ ["config", "--no-includes", "--file", filePath, "--bool", "--get", key],
44035
+ {
44036
+ env: {
44037
+ PATH: process.env["PATH"],
44038
+ GIT_CONFIG_GLOBAL: NULL_DEVICE,
44039
+ GIT_CONFIG_NOSYSTEM: "1"
44040
+ }
44041
+ }
44042
+ );
44043
+ return result.stdout.trim() === "true";
44044
+ } catch (error) {
44045
+ if (typeof error === "object" && error !== null && "code" in error && error.code === 1) {
44046
+ return false;
44047
+ }
44048
+ throw new Error(`Invalid ${description}: ${filePath}`, { cause: error });
44049
+ }
44050
+ }
43159
44051
  async function resolveSafeRepositorySymlink(symlinkPath, repositoryRoot, gitMetadataRoot) {
43160
44052
  let targetPath;
43161
44053
  try {
@@ -43249,7 +44141,7 @@ async function hashVendorRepository(directory) {
43249
44141
  await visit(directory, "");
43250
44142
  return `sha256-${hash.digest("hex")}`;
43251
44143
  }
43252
- async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expectedCommit, allowedUntrackedFiles = /* @__PURE__ */ new Set()) {
44144
+ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expectedCommit, allowedUntrackedFiles = /* @__PURE__ */ new Set(), options = {}) {
43253
44145
  const gitDir = join9(directory, gitDirectoryName);
43254
44146
  async function rejectMetadataSymlinks(currentDirectory) {
43255
44147
  for (const entry of await readdir4(currentDirectory, { withFileTypes: true })) {
@@ -43287,8 +44179,23 @@ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expected
43287
44179
  "Vendor Git config"
43288
44180
  );
43289
44181
  const localConfig = localConfigContent.toString("utf-8");
43290
- if (/^[ \t]*\[(?:include|includeif)\b/im.test(localConfig) || /\bpromisor\s*=\s*true\b/i.test(localConfig) || /\bpartialclone/i.test(localConfig) || /\bworktreeconfig\s*=\s*true\b/i.test(localConfig)) {
43291
- throw new Error(`External or partial Git object sources are not allowed: ${gitDir}`);
44182
+ const externalGitSourcesError = () => `External or partial Git object sources are not allowed: ${gitDir}. ` + (options.allowPartial ? `Delete the checkout directory ${directory} and re-run this command to re-clone it.` : `Run 'prs vendor sync' to re-vendor the dependency, then re-run this command.`);
44183
+ if (/^[ \t]*\[(?:include|includeif)\b/im.test(localConfig)) {
44184
+ throw new Error(externalGitSourcesError());
44185
+ }
44186
+ const usesWorktreeConfig = await readGitBooleanConfig(
44187
+ join9(gitDir, "config"),
44188
+ "extensions.worktreeConfig",
44189
+ "Git worktree config extension"
44190
+ );
44191
+ if (!options.allowPartial && (usesWorktreeConfig || /\bpromisor\s*=\s*true\b/i.test(localConfig) || /\bpartialclone/i.test(localConfig))) {
44192
+ throw new Error(externalGitSourcesError());
44193
+ }
44194
+ if (options.allowPartial && usesWorktreeConfig) {
44195
+ const worktreeConfig = (await readOptionalBoundedRegularFile(join9(gitDir, "config.worktree"), "Git worktree config")).toString("utf-8");
44196
+ if (/^[ \t]*\[(?:include|includeif)\b/im.test(worktreeConfig)) {
44197
+ throw new Error(externalGitSourcesError());
44198
+ }
43292
44199
  }
43293
44200
  await rejectMetadataPath("commondir", "Git common directories are not allowed");
43294
44201
  await rejectMetadataPath(
@@ -43458,7 +44365,7 @@ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expected
43458
44365
  }
43459
44366
  }
43460
44367
  await collectFiles(directory, "");
43461
- if (worktreeFileCount !== trackedFiles.size || regularFiles.some((path2) => !trackedFiles.has(path2))) {
44368
+ if (!options.allowPartial && worktreeFileCount !== trackedFiles.size || regularFiles.some((path2) => !trackedFiles.has(path2))) {
43462
44369
  throw new Error(`Vendored repository contents do not match commit ${expectedCommit}`);
43463
44370
  }
43464
44371
  const batchSize = 100;
@@ -43742,7 +44649,8 @@ var Resolver = class {
43742
44649
  cacheEnabled;
43743
44650
  logger;
43744
44651
  options;
43745
- gitRegistry;
44652
+ /** Lazy per-repository Git registries (carry per-entry timeout settings). */
44653
+ gitRegistries = /* @__PURE__ */ new Map();
43746
44654
  registryCache;
43747
44655
  constructor(options) {
43748
44656
  this.options = options;
@@ -43750,7 +44658,6 @@ var Resolver = class {
43750
44658
  this.cache = /* @__PURE__ */ new Map();
43751
44659
  this.cacheEnabled = options.cache !== false;
43752
44660
  this.logger = options.logger ?? noopLogger;
43753
- this.gitRegistry = new GitRegistry({ url: "https://github.com/placeholder/placeholder.git" });
43754
44661
  const defaultCacheDir = join10(
43755
44662
  process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp",
43756
44663
  ".promptscript",
@@ -43758,6 +44665,54 @@ var Resolver = class {
43758
44665
  );
43759
44666
  this.registryCache = new RegistryCache(options.cacheDir ?? defaultCacheDir);
43760
44667
  }
44668
+ /**
44669
+ * Get (or lazily create) the Git registry for a repository URL.
44670
+ *
44671
+ * Per-repository instances carry the `timeout` from the matching
44672
+ * registries config entry, so large monorepo registries can raise the
44673
+ * clone timeout per entry (see issue #455). Falls back to
44674
+ * PROMPTSCRIPT_GIT_TIMEOUT / the 60s default inside GitRegistry.
44675
+ */
44676
+ getGitRegistry(repoUrl) {
44677
+ const cached = this.gitRegistries.get(repoUrl);
44678
+ if (cached) {
44679
+ return cached;
44680
+ }
44681
+ const entry = this.options.registries ? findRegistryEntry(repoUrl, this.options.registries) : void 0;
44682
+ const registry2 = new GitRegistry({
44683
+ url: repoUrl,
44684
+ ...entry?.timeout !== void 0 ? { timeout: entry.timeout } : {}
44685
+ });
44686
+ this.gitRegistries.set(repoUrl, registry2);
44687
+ return registry2;
44688
+ }
44689
+ /**
44690
+ * Clone a registry repository into its cache path. Uses the per-entry
44691
+ * Git registry (so the configured timeout applies) and the fallback URL
44692
+ * from the lockfile or the registries config.
44693
+ */
44694
+ async cloneRegistryIntoCache(repoUrl, cachePath, tag, sparseCone, lockEntry) {
44695
+ const gitRegistry = this.getGitRegistry(repoUrl);
44696
+ const fallbackRepoUrl = lockEntry?.gitUrl ?? (this.options.registries ? findFallbackUrl(repoUrl, this.options.registries) : void 0);
44697
+ await gitRegistry.cloneAtTag(repoUrl, tag, cachePath, fallbackRepoUrl, sparseCone);
44698
+ return gitRegistry;
44699
+ }
44700
+ /**
44701
+ * Verify that a cached registry checkout matches a locked commit.
44702
+ * Cache clones may be partial (sparse cones), so partial-clone metadata
44703
+ * and missing out-of-cone files are allowed.
44704
+ */
44705
+ async verifyCacheCommit(cachePath, commit) {
44706
+ await verifyGitRepositoryCheckout(
44707
+ cachePath,
44708
+ ".git",
44709
+ commit,
44710
+ /* @__PURE__ */ new Set([".prs-registry-meta.json"]),
44711
+ {
44712
+ allowPartial: true
44713
+ }
44714
+ );
44715
+ }
43761
44716
  /**
43762
44717
  * Resolve a PromptScript file and all its dependencies.
43763
44718
  *
@@ -44368,10 +45323,12 @@ var Resolver = class {
44368
45323
  }
44369
45324
  const program2 = {
44370
45325
  type: "Program",
44371
- blocks: [makeBlock("skills", makeObjectContent({ [skillName]: skillProps }))],
45326
+ blocks: [
45327
+ makeBlock("skills", makeObjectContent({ [skillName]: skillProps }, absPath), absPath)
45328
+ ],
44372
45329
  uses: [],
44373
45330
  extends: [],
44374
- loc: VIRTUAL_LOC
45331
+ loc: fileLoc(absPath)
44375
45332
  };
44376
45333
  return { ast: program2 };
44377
45334
  }
@@ -44446,7 +45403,8 @@ var Resolver = class {
44446
45403
  }
44447
45404
  errors.push(
44448
45405
  new ResolveError(
44449
- `Failed to resolve parent: ${err instanceof Error ? err.message : String(err)}`
45406
+ `Failed to resolve parent: ${err instanceof Error ? err.message : String(err)}`,
45407
+ err instanceof ResolveError ? err.location : void 0
44450
45408
  )
44451
45409
  );
44452
45410
  }
@@ -44545,7 +45503,8 @@ var Resolver = class {
44545
45503
  }
44546
45504
  errors.push(
44547
45505
  new ResolveError(
44548
- `Failed to resolve import: ${err instanceof Error ? err.message : String(err)}`
45506
+ `Failed to resolve import: ${err instanceof Error ? err.message : String(err)}`,
45507
+ err instanceof ResolveError ? err.location : void 0
44549
45508
  )
44550
45509
  );
44551
45510
  }
@@ -44681,10 +45640,14 @@ var Resolver = class {
44681
45640
  const markerCommit = version && /^[0-9a-f]{40}$/i.test(version) && !/^0{40}$/.test(version) ? version : null;
44682
45641
  const lockedCommit = lockfileCommit ?? markerCommit;
44683
45642
  if (this.options.lockfile && !lockfileCommit) {
44684
- throw new Error(`Remote dependency is not pinned by the lockfile: ${repoUrl}`);
45643
+ throw new Error(
45644
+ `Remote dependency is not pinned by the lockfile: ${repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
45645
+ );
44685
45646
  }
44686
45647
  const requestedRef = lockEntry?.version ?? (version || void 0);
44687
45648
  const tag = requestedRef && requestedRef !== "latest" && !/^[0-9a-f]{40}$/i.test(requestedRef) ? requestedRef : void 0;
45649
+ const subPathLastSlash = subPath.lastIndexOf("/");
45650
+ const sparseCone = subPathLastSlash > 0 ? subPath.slice(0, subPathLastSlash) : void 0;
44688
45651
  let cachePath;
44689
45652
  let vendoredPath = null;
44690
45653
  const vendorManifest = this.options.vendorDir ? await loadVendorManifest(this.options.vendorDir) : null;
@@ -44693,7 +45656,9 @@ var Resolver = class {
44693
45656
  }
44694
45657
  if (this.options.vendorDir && vendorManifest) {
44695
45658
  if (!lockEntry || !lockedCommit) {
44696
- throw new Error(`Vendored dependency is not pinned by the lockfile: ${repoUrl}`);
45659
+ throw new Error(
45660
+ `Vendored dependency is not pinned by the lockfile: ${repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
45661
+ );
44697
45662
  }
44698
45663
  vendoredPath = await resolveVendoredRepository(
44699
45664
  this.options.vendorDir,
@@ -44711,12 +45676,7 @@ var Resolver = class {
44711
45676
  if (lockedCommit) {
44712
45677
  let cacheMatchesLock = false;
44713
45678
  try {
44714
- await verifyGitRepositoryCheckout(
44715
- cachePath,
44716
- ".git",
44717
- lockedCommit,
44718
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
44719
- );
45679
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44720
45680
  cacheMatchesLock = true;
44721
45681
  } catch {
44722
45682
  cacheMatchesLock = false;
@@ -44730,17 +45690,16 @@ var Resolver = class {
44730
45690
  this.logger.verbose(
44731
45691
  `Registry cache does not match locked commit for ${repoUrl}. Re-cloning.`
44732
45692
  );
44733
- const cloneRepoUrl = repoUrl;
44734
- const fallbackRepoUrl = lockEntry?.gitUrl ?? (this.options.registries ? findFallbackUrl(repoUrl, this.options.registries) : void 0);
44735
- await this.gitRegistry.cloneAtTag(cloneRepoUrl, tag, cachePath, fallbackRepoUrl);
44736
- await this.gitRegistry.checkoutCommit(cachePath, lockedCommit);
44737
- await this.registryCache.set(repoUrl, effectiveVersion, lockedCommit);
44738
- await verifyGitRepositoryCheckout(
45693
+ const gitRegistry = await this.cloneRegistryIntoCache(
45694
+ repoUrl,
44739
45695
  cachePath,
44740
- ".git",
44741
- lockedCommit,
44742
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
45696
+ tag,
45697
+ sparseCone,
45698
+ lockEntry
44743
45699
  );
45700
+ await gitRegistry.checkoutCommit(cachePath, lockedCommit);
45701
+ await this.registryCache.set(repoUrl, effectiveVersion, lockedCommit);
45702
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44744
45703
  }
44745
45704
  }
44746
45705
  } else {
@@ -44751,22 +45710,21 @@ var Resolver = class {
44751
45710
  }
44752
45711
  this.logger.verbose(`Registry cache miss, cloning: ${repoUrl}@${tag ?? "default"}`);
44753
45712
  cachePath = this.registryCache.getCachePath(repoUrl, effectiveVersion);
44754
- const cloneRepoUrl = repoUrl;
44755
- const fallbackRepoUrl = lockEntry?.gitUrl ?? (this.options.registries ? findFallbackUrl(repoUrl, this.options.registries) : void 0);
44756
- await this.gitRegistry.cloneAtTag(cloneRepoUrl, tag, cachePath, fallbackRepoUrl);
45713
+ const gitRegistry = await this.cloneRegistryIntoCache(
45714
+ repoUrl,
45715
+ cachePath,
45716
+ tag,
45717
+ sparseCone,
45718
+ lockEntry
45719
+ );
44757
45720
  if (lockedCommit) {
44758
45721
  this.logger.verbose(`Checking out locked commit: ${lockedCommit}`);
44759
- await this.gitRegistry.checkoutCommit(cachePath, lockedCommit);
45722
+ await gitRegistry.checkoutCommit(cachePath, lockedCommit);
44760
45723
  }
44761
45724
  const commitHash = lockedCommit ?? lockEntry?.commit ?? "unknown";
44762
45725
  await this.registryCache.set(repoUrl, effectiveVersion, commitHash);
44763
45726
  if (lockedCommit) {
44764
- await verifyGitRepositoryCheckout(
44765
- cachePath,
44766
- ".git",
44767
- lockedCommit,
44768
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
44769
- );
45727
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44770
45728
  }
44771
45729
  }
44772
45730
  dependencies.add(cachePath);
@@ -44807,6 +45765,26 @@ var Resolver = class {
44807
45765
  };
44808
45766
  }
44809
45767
  }
45768
+ if (!this.options.readOnly && existsSync9(cachePath) && existsSync9(join10(cachePath, ".git")) && !vendoredPath) {
45769
+ const gitRegistry = this.getGitRegistry(repoUrl);
45770
+ if (await gitRegistry.isSparseCheckout(cachePath)) {
45771
+ const fileMissing = !isRoot && !existsSync9(resolvedFullPath);
45772
+ const dirMissing = !isRoot && !existsSync9(join10(cachePath, subPath));
45773
+ if (isRoot) {
45774
+ this.logger.verbose(`Materializing full checkout for root import: ${repoUrl}`);
45775
+ await gitRegistry.disableSparseCheckout(cachePath);
45776
+ } else if (fileMissing || dirMissing) {
45777
+ this.logger.verbose(`Widening sparse checkout for ${repoUrl}: ${subPath}`);
45778
+ const paths = /* @__PURE__ */ new Set();
45779
+ if (sparseCone) {
45780
+ paths.add(sparseCone);
45781
+ } else if (!isMdPath && !subPath.endsWith(".prs")) {
45782
+ paths.add(subPath);
45783
+ }
45784
+ await gitRegistry.addSparsePaths(cachePath, [...paths]);
45785
+ }
45786
+ }
45787
+ }
44810
45788
  let resolvedAST = null;
44811
45789
  if (!isRoot && existsSync9(resolvedFullPath) && isMdPath) {
44812
45790
  this.logger.debug(`Found .md file: ${resolvedFullPath}`);
@@ -44935,10 +45913,10 @@ var Resolver = class {
44935
45913
  this.logger.debug(`Found ${Object.keys(properties).length} skill(s) in directory: ${dirPath}`);
44936
45914
  const program2 = {
44937
45915
  type: "Program",
44938
- blocks: [makeBlock("skills", makeObjectContent(properties))],
45916
+ blocks: [makeBlock("skills", makeObjectContent(properties, dirPath), dirPath)],
44939
45917
  uses: [],
44940
45918
  extends: [],
44941
- loc: VIRTUAL_LOC
45919
+ loc: fileLoc(dirPath)
44942
45920
  };
44943
45921
  return { ast: program2 };
44944
45922
  }
@@ -45303,7 +46281,7 @@ var HttpRegistry = class {
45303
46281
  * Sleep for specified milliseconds.
45304
46282
  */
45305
46283
  sleep(ms) {
45306
- return new Promise((resolve42) => setTimeout(resolve42, ms));
46284
+ return new Promise((resolve43) => setTimeout(resolve43, ms));
45307
46285
  }
45308
46286
  /**
45309
46287
  * Fetch with timeout.
@@ -45448,6 +46426,7 @@ function createHttpRegistry(options) {
45448
46426
  init_src();
45449
46427
 
45450
46428
  // packages/resolver/src/skill-validation.ts
46429
+ init_src();
45451
46430
  import { basename as basename12, dirname as dirname11, join as join12 } from "path";
45452
46431
  import { existsSync as existsSync11 } from "fs";
45453
46432
  function validateSkillFrontmatter(rawContent, options = {}) {
@@ -45464,11 +46443,15 @@ function validateSkillFrontmatter(rawContent, options = {}) {
45464
46443
  try {
45465
46444
  parsed = parseSkillMd(rawContent, options.filePath);
45466
46445
  } catch (error) {
45467
- issues.push({
46446
+ const issue = {
45468
46447
  severity: "error",
45469
46448
  code: "SK000",
45470
46449
  message: error instanceof Error ? error.message : String(error)
45471
- });
46450
+ };
46451
+ if (error instanceof ResolveError && error.location) {
46452
+ issue.location = error.location;
46453
+ }
46454
+ issues.push(issue);
45472
46455
  return { valid: false, issues };
45473
46456
  }
45474
46457
  validateName(parsed, options, issues);
@@ -45648,7 +46631,8 @@ function formatSkillValidationIssues(issues) {
45648
46631
  return issues.map((i) => {
45649
46632
  const tag = i.severity === "error" ? "\u2717" : "\u26A0";
45650
46633
  const field = i.field ? ` [${i.field}]` : "";
45651
- return ` ${tag} ${i.code}${field}: ${i.message}`;
46634
+ const location = i.location ? ` (at ${i.location.file}:${i.location.line}:${i.location.column})` : "";
46635
+ return ` ${tag} ${i.code}${field}: ${i.message}${location}`;
45652
46636
  }).join("\n");
45653
46637
  }
45654
46638
 
@@ -47388,6 +48372,19 @@ var authorityInjection = {
47388
48372
  }
47389
48373
  };
47390
48374
 
48375
+ // packages/validator/src/external-content.ts
48376
+ import { isAbsolute as isAbsolute8, relative as relative9, sep as sep6 } from "node:path";
48377
+ function isPathInside(root, candidate) {
48378
+ const relation = relative9(root, candidate);
48379
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep6}`) && !isAbsolute8(relation);
48380
+ }
48381
+ function isExternalLocation(loc, config) {
48382
+ if (config.scanExternalContent || !loc) return false;
48383
+ const roots = config.externalRoots;
48384
+ if (!roots || roots.length === 0) return false;
48385
+ return roots.some((root) => isPathInside(root, loc.file));
48386
+ }
48387
+
47391
48388
  // packages/validator/src/rules/obfuscated-content.ts
47392
48389
  var MIN_ENCODED_LENGTH = 16;
47393
48390
  var DECODED_CONTENT_SECURITY_PATTERNS = [
@@ -47568,6 +48565,19 @@ function isLikelyTechnicalContent(match2) {
47568
48565
  }
47569
48566
  return false;
47570
48567
  }
48568
+ var NUCLEOTIDE_CHARS = new Set("ACGTUN");
48569
+ var AMINO_ACID_CHARS = new Set("ACDEFGHIKLMNPQRSTVWYBXZJOU");
48570
+ var BIO_CONTEXT_PATTERN = /\b(?:protein|peptide|amino[-\s]?acid|residue|sequence|nucleotide|genome|transcript|fasta|uniprot)\b/i;
48571
+ function isOverAlphabet(match2, alphabet) {
48572
+ for (const char of match2) {
48573
+ if (!alphabet.has(char)) return false;
48574
+ }
48575
+ return match2.length > 0;
48576
+ }
48577
+ function isLikelyBioSequence(text, match2) {
48578
+ if (isOverAlphabet(match2, NUCLEOTIDE_CHARS)) return true;
48579
+ return isOverAlphabet(match2, AMINO_ACID_CHARS) && BIO_CONTEXT_PATTERN.test(text);
48580
+ }
47571
48581
  function isLikelyLegitimateBase64(text, match2) {
47572
48582
  const matchIndex = text.indexOf(match2);
47573
48583
  if (matchIndex === -1) return false;
@@ -47576,6 +48586,7 @@ function isLikelyLegitimateBase64(text, match2) {
47576
48586
  if (precedingText.includes("data:application/json")) return true;
47577
48587
  if (precedingText.includes("data:text/")) return true;
47578
48588
  if (isLikelyTechnicalContent(match2)) return true;
48589
+ if (isLikelyBioSequence(text, match2)) return true;
47579
48590
  return false;
47580
48591
  }
47581
48592
  function isLikelyHashOrUuid(hex) {
@@ -47784,12 +48795,16 @@ var obfuscatedContent = {
47784
48795
  suggestion: "Encoded content is automatically decoded and checked for security patterns. Remove the malicious payload or use plain text."
47785
48796
  });
47786
48797
  }
47787
- const hasLongBase64 = (text.match(BASE64_PATTERN) || []).some(
48798
+ if (isExternalLocation(loc, ctx.config)) {
48799
+ return;
48800
+ }
48801
+ const longBase64Match = (text.match(BASE64_PATTERN) || []).find(
47788
48802
  (m) => m.length >= MIN_ENCODED_LENGTH * 2 && !isLikelyLegitimateBase64(text, m)
47789
48803
  );
47790
- if (hasLongBase64 && encodedMatches.length === 0) {
48804
+ if (longBase64Match && encodedMatches.length === 0) {
48805
+ const snippet = longBase64Match.length > 20 ? `${longBase64Match.substring(0, 20)}...` : longBase64Match;
47791
48806
  ctx.report({
47792
- message: "Long Base64-encoded content detected that may hide payloads",
48807
+ message: `Long Base64-encoded content detected that may hide payloads (starts with "${snippet}")`,
47793
48808
  location: loc,
47794
48809
  suggestion: "If this is intentional, consider using plain text or documenting the purpose"
47795
48810
  });
@@ -48697,11 +49712,14 @@ var safeReferenceContent = {
48697
49712
  const content = res["content"];
48698
49713
  if (typeof relPath !== "string" || typeof content !== "string") continue;
48699
49714
  if (!relPath.startsWith("references/") && !relPath.includes("/references/")) continue;
49715
+ const origin = res["origin"];
49716
+ const location = typeof origin === "string" ? { file: origin, line: 1, column: 1, offset: 0 } : skillsBlock.loc;
49717
+ if (isExternalLocation(location, ctx.config)) continue;
48700
49718
  const match2 = content.match(DIRECTIVE_PATTERN);
48701
49719
  if (match2) {
48702
49720
  ctx.report({
48703
49721
  message: `Reference file "${relPath}" in skill "${skillName}" contains PRS directive '${match2[1]}' \u2014 references should contain data, not instructions`,
48704
- location: skillsBlock.loc,
49722
+ location,
48705
49723
  suggestion: "Remove PRS directives from reference files. References are data files, not PromptScript source."
48706
49724
  });
48707
49725
  }
@@ -50512,18 +51530,18 @@ var SECURITY_STRICT_MULTILINGUAL = {
50512
51530
  };
50513
51531
 
50514
51532
  // packages/compiler/src/compiler.ts
50515
- import { dirname as dirname13, isAbsolute as isAbsolute9, join as join13, relative as relative10, resolve as resolve15, sep as sep7 } from "path";
51533
+ import { dirname as dirname13, isAbsolute as isAbsolute10, join as join13, relative as relative11, resolve as resolve15, sep as sep8 } from "path";
50516
51534
 
50517
51535
  // packages/compiler/src/hook-script-validator.ts
50518
51536
  init_src();
50519
51537
  init_src4();
50520
51538
  import { lstat as lstat7, realpath as realpath4 } from "fs/promises";
50521
51539
  import { realpathSync, statSync } from "fs";
50522
- import { dirname as dirname12, isAbsolute as isAbsolute8, relative as relative9, resolve as resolve14, sep as sep6 } from "path";
51540
+ import { dirname as dirname12, isAbsolute as isAbsolute9, relative as relative10, resolve as resolve14, sep as sep7 } from "path";
50523
51541
  var PROJECT_MARKERS = [".promptscript", ".git", "package.json"];
50524
51542
  function isInside2(root, candidate) {
50525
- const relation = relative9(root, candidate);
50526
- return relation === "" || !relation.startsWith(`..${sep6}`) && relation !== ".." && !isAbsolute8(relation);
51543
+ const relation = relative10(root, candidate);
51544
+ return relation === "" || !relation.startsWith(`..${sep7}`) && relation !== ".." && !isAbsolute9(relation);
50527
51545
  }
50528
51546
  function isProjectMarker(directory, marker) {
50529
51547
  const markerPath = resolve14(directory, marker);
@@ -50561,10 +51579,10 @@ function inferProjectRoot(localPath, configuredProjectRoot, entryPath) {
50561
51579
  if (markedRoot) return markedRoot;
50562
51580
  }
50563
51581
  const local = localPath ? resolve14(localPath) : entryPath ? dirname12(resolve14(entryPath)) : process.cwd();
50564
- const promptScriptDirectory = `${sep6}.promptscript`;
50565
- const nestedMarker = `${promptScriptDirectory}${sep6}`;
51582
+ const promptScriptDirectory = `${sep7}.promptscript`;
51583
+ const nestedMarker = `${promptScriptDirectory}${sep7}`;
50566
51584
  const nestedIndex = local.indexOf(nestedMarker);
50567
- if (nestedIndex >= 0) return local.slice(0, nestedIndex) || sep6;
51585
+ if (nestedIndex >= 0) return local.slice(0, nestedIndex) || sep7;
50568
51586
  return local.endsWith(promptScriptDirectory) ? dirname12(local) : local;
50569
51587
  }
50570
51588
  async function validateHookScriptResources(ast, projectRoot) {
@@ -50632,7 +51650,7 @@ function resolverCacheKey(projectRoot) {
50632
51650
  function resolveEntryPath(entryPath) {
50633
51651
  if (entryPath.startsWith("@")) return entryPath;
50634
51652
  const fileName = entryPath.endsWith(".prs") || entryPath.endsWith(".md") ? entryPath : `${entryPath}.prs`;
50635
- const absolutePath = isAbsolute9(fileName) ? fileName : resolve15(process.cwd(), fileName);
51653
+ const absolutePath = isAbsolute10(fileName) ? fileName : resolve15(process.cwd(), fileName);
50636
51654
  try {
50637
51655
  return realpathSync2.native(absolutePath);
50638
51656
  } catch {
@@ -50686,7 +51704,7 @@ function normalizeOutputDir(dir) {
50686
51704
  }
50687
51705
  function matchesWatchPattern(path2, pattern, baseDir) {
50688
51706
  const normalizedPath = path2.replace(/\\/g, "/");
50689
- const relativePath = relative10(baseDir, path2).replace(/\\/g, "/");
51707
+ const relativePath = relative11(baseDir, path2).replace(/\\/g, "/");
50690
51708
  const candidates = [relativePath, normalizedPath].flatMap((candidate) => [
50691
51709
  candidate,
50692
51710
  candidate.endsWith("/") ? candidate : `${candidate}/`
@@ -50698,7 +51716,7 @@ function matchesWatchPattern(path2, pattern, baseDir) {
50698
51716
  return candidates.some((candidate) => minimatch(candidate, pattern, matcherOptions));
50699
51717
  }
50700
51718
  function absoluteWatchPath(path2, baseDir = process.cwd()) {
50701
- return isAbsolute9(path2) ? resolve15(path2) : resolve15(baseDir, path2);
51719
+ return isAbsolute10(path2) ? resolve15(path2) : resolve15(baseDir, path2);
50702
51720
  }
50703
51721
  function canonicalWatchPath(path2, baseDir = process.cwd()) {
50704
51722
  const absolutePath = absoluteWatchPath(path2, baseDir);
@@ -50721,8 +51739,8 @@ function watchPathKey(path2, baseDir = process.cwd()) {
50721
51739
  return normalizeWatchPath(canonicalWatchPath(path2, baseDir));
50722
51740
  }
50723
51741
  function isPathWithin(path2, root) {
50724
- const relation = relative10(normalizeWatchPath(root), normalizeWatchPath(path2));
50725
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation);
51742
+ const relation = relative11(normalizeWatchPath(root), normalizeWatchPath(path2));
51743
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation);
50726
51744
  }
50727
51745
  function shouldIncludeSkill(name, config) {
50728
51746
  const includeSkills = config?.includeSkills;
@@ -50877,7 +51895,7 @@ var Compiler = class _Compiler {
50877
51895
  formatTime: 0,
50878
51896
  totalTime: 0
50879
51897
  };
50880
- const sourceLabel = isAbsolute9(entryPath) ? relative10(process.cwd(), entryPath) : entryPath;
51898
+ const sourceLabel = isAbsolute10(entryPath) ? relative11(process.cwd(), entryPath) : entryPath;
50881
51899
  this.logger.verbose("=== Stage 1: Resolve ===");
50882
51900
  const startResolve = Date.now();
50883
51901
  let resolved;
@@ -50921,11 +51939,20 @@ var Compiler = class _Compiler {
50921
51939
  };
50922
51940
  }
50923
51941
  const compileErrors = [];
51942
+ const externalRoots = [];
51943
+ const cacheDir = this.options.resolver.cacheDir ?? join13(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp", ".promptscript", "cache");
51944
+ externalRoots.push(cacheDir);
51945
+ if (this.options.resolver.vendorDir) {
51946
+ externalRoots.push(this.options.resolver.vendorDir);
51947
+ }
51948
+ for (const roots of Object.values(this.options.resolver.referenceRoots ?? {})) {
51949
+ externalRoots.push(...roots);
51950
+ }
51951
+ this.validator.updateConfig({ externalRoots });
50924
51952
  if (!this.options.ignoreHashes && this.options.resolver.lockfile) {
50925
51953
  this.logger.verbose("=== Stage 1.5: Reference Integrity ===");
50926
51954
  const registryReferences = /* @__PURE__ */ new Set();
50927
51955
  const registryReferencePaths = /* @__PURE__ */ new Map();
50928
- const cacheDir = this.options.resolver.cacheDir ?? join13(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp", ".promptscript", "cache");
50929
51956
  const registryCache = new RegistryCache(cacheDir);
50930
51957
  const referenceRoots = [];
50931
51958
  for (const [repoUrl, dependency] of Object.entries(
@@ -50964,8 +51991,8 @@ var Compiler = class _Compiler {
50964
51991
  if (block.name !== "skills" || block.content.type !== "ObjectContent") continue;
50965
51992
  const sourceFile = block.loc.file;
50966
51993
  const referenceRoot = sourceFile ? referenceRoots.find((root) => {
50967
- const relation = relative10(resolve15(root.path), resolve15(sourceFile));
50968
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation);
51994
+ const relation = relative11(resolve15(root.path), resolve15(sourceFile));
51995
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation);
50969
51996
  }) : void 0;
50970
51997
  for (const [, skillValue] of Object.entries(block.content.properties)) {
50971
51998
  if (typeof skillValue !== "object" || skillValue === null || !("references" in skillValue))
@@ -50977,8 +52004,8 @@ var Compiler = class _Compiler {
50977
52004
  registryReferences.add(ref);
50978
52005
  if (sourceFile && referenceRoot) {
50979
52006
  const resolvedReference = resolve15(dirname13(sourceFile), ref);
50980
- const relation = relative10(resolve15(referenceRoot.path), resolvedReference);
50981
- if (relation !== "" && relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation)) {
52007
+ const relation = relative11(resolve15(referenceRoot.path), resolvedReference);
52008
+ if (relation !== "" && relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation)) {
50982
52009
  const sourceReferences = registryReferencePaths.get(sourceFile) ?? /* @__PURE__ */ new Map();
50983
52010
  const relativePath = relation.replaceAll("\\", "/");
50984
52011
  const referenceKey = `${referenceRoot.repoUrl}\0${relativePath}\0${referenceRoot.version}`;
@@ -51082,7 +52109,7 @@ var Compiler = class _Compiler {
51082
52109
  for (const warning of output.warnings ?? []) {
51083
52110
  formatWarnings.push({
51084
52111
  ruleId: warning.code,
51085
- ruleName: "target-hook-compatibility",
52112
+ ruleName: warning.ruleName ?? "target-hook-compatibility",
51086
52113
  severity: "warning",
51087
52114
  message: warning.message,
51088
52115
  ...warning.suggestion ? { suggestion: warning.suggestion } : {},
@@ -51284,8 +52311,8 @@ var Compiler = class _Compiler {
51284
52311
  */
51285
52312
  async watch(entryPath, options = {}) {
51286
52313
  const { default: chokidar2 } = await import("chokidar");
51287
- const { dirname: dirname29, resolve: resolve42 } = await import("path");
51288
- const baseDir = dirname29(resolve42(entryPath));
52314
+ const { dirname: dirname29, resolve: resolve43 } = await import("path");
52315
+ const baseDir = dirname29(resolve43(entryPath));
51289
52316
  const includePatterns = options.include ?? ["**/*.prs"];
51290
52317
  const excludePatterns = options.exclude ?? ["**/node_modules/**"];
51291
52318
  const debounceMs = options.debounce ?? 300;
@@ -51336,7 +52363,7 @@ var Compiler = class _Compiler {
51336
52363
  ".promptscriptrc.yaml",
51337
52364
  ".promptscriptrc.yml",
51338
52365
  "promptscript.lock"
51339
- ].map((fileName) => resolve42(this.watchProjectRoot(entryPath), fileName))
52366
+ ].map((fileName) => resolve43(this.watchProjectRoot(entryPath), fileName))
51340
52367
  ].map((path2) => watchPathKey(path2))
51341
52368
  );
51342
52369
  let dynamicDependencyPaths = toDependencyMap(this.resolvedDependencyPaths(entryPath));
@@ -51629,7 +52656,7 @@ var Pager = class {
51629
52656
  }
51630
52657
  return;
51631
52658
  }
51632
- return new Promise((resolve42) => {
52659
+ return new Promise((resolve43) => {
51633
52660
  const pagerCmd = getPagerCommand();
51634
52661
  const [cmd, ...args] = pagerCmd.split(" ");
51635
52662
  if (cmd === "less" && !args.includes("-R")) {
@@ -51637,7 +52664,7 @@ var Pager = class {
51637
52664
  }
51638
52665
  if (!cmd) {
51639
52666
  console.log(content);
51640
- resolve42();
52667
+ resolve43();
51641
52668
  return;
51642
52669
  }
51643
52670
  const pager = spawn2(cmd, args, {
@@ -51650,17 +52677,17 @@ var Pager = class {
51650
52677
  });
51651
52678
  pager.on("error", (_err) => {
51652
52679
  console.log(content);
51653
- resolve42();
52680
+ resolve43();
51654
52681
  });
51655
52682
  pager.on("close", () => {
51656
- resolve42();
52683
+ resolve43();
51657
52684
  });
51658
52685
  if (pager.stdin) {
51659
52686
  pager.stdin.write(content);
51660
52687
  pager.stdin.end();
51661
52688
  } else {
51662
52689
  console.log(content);
51663
- resolve42();
52690
+ resolve43();
51664
52691
  }
51665
52692
  });
51666
52693
  }
@@ -51674,14 +52701,14 @@ init_services();
51674
52701
 
51675
52702
  // packages/cli/src/utils/registry-resolver.ts
51676
52703
  import { existsSync as existsSync12 } from "fs";
51677
- import { isAbsolute as isAbsolute10, join as join14, relative as relative11, resolve as resolve16, sep as sep8 } from "path";
52704
+ import { isAbsolute as isAbsolute11, join as join14, relative as relative12, resolve as resolve16, sep as sep9 } from "path";
51678
52705
  async function resolveRegistrySubPath(repositoryPath, subPath) {
51679
52706
  if (!subPath) {
51680
52707
  return repositoryPath;
51681
52708
  }
51682
52709
  const result = resolve16(repositoryPath, subPath);
51683
- const relation = relative11(resolve16(repositoryPath), result);
51684
- if (relation === ".." || relation.startsWith(`..${sep8}`) || isAbsolute10(relation)) {
52710
+ const relation = relative12(resolve16(repositoryPath), result);
52711
+ if (relation === ".." || relation.startsWith(`..${sep9}`) || isAbsolute11(relation)) {
51685
52712
  throw new Error(`Registry path escapes its repository: ${subPath}`);
51686
52713
  }
51687
52714
  if (existsSync12(result) && !await isRealPathInside(result, repositoryPath)) {
@@ -51706,12 +52733,16 @@ async function resolveRegistryPath(config, options = {}) {
51706
52733
  if (options.vendorDir && existsSync12(options.vendorDir) && !vendorManifest) {
51707
52734
  throw new Error(`Vendor manifest is missing: ${options.vendorDir}`);
51708
52735
  }
51709
- if (options.lockfile && !lockedRepository) {
51710
- throw new Error(`Git registry is not pinned by the lockfile: ${gitConfig.url}`);
52736
+ if (options.lockfile && !lockedRepository && !options.allowMissingLockEntry) {
52737
+ throw new Error(
52738
+ `Git registry is not pinned by the lockfile: ${gitConfig.url}. Run 'prs lock' to pin it, then re-run this command.`
52739
+ );
51711
52740
  }
51712
52741
  if (options.vendorDir && vendorManifest) {
51713
52742
  if (!lockedRepository) {
51714
- throw new Error(`Vendored registry is not pinned by the lockfile: ${gitConfig.url}`);
52743
+ throw new Error(
52744
+ `Vendored registry is not pinned by the lockfile: ${gitConfig.url}. Run 'prs lock' to pin it, then re-run this command.`
52745
+ );
51715
52746
  }
51716
52747
  const [repoUrl, dependency] = lockedRepository;
51717
52748
  const repositoryPath = await resolveVendoredRepository(
@@ -51751,7 +52782,8 @@ async function resolveRegistryPath(config, options = {}) {
51751
52782
  cachePath,
51752
52783
  ".git",
51753
52784
  lockedRepository[1].commit,
51754
- /* @__PURE__ */ new Set([".prs-cache-meta.json"])
52785
+ /* @__PURE__ */ new Set([".prs-cache-meta.json"]),
52786
+ { allowPartial: true }
51755
52787
  );
51756
52788
  return {
51757
52789
  path: await resolveRegistrySubPath(cachePath, gitConfig.path),
@@ -51780,6 +52812,7 @@ async function resolveRegistryPath(config, options = {}) {
51780
52812
  ref,
51781
52813
  path: gitConfig.path,
51782
52814
  auth: gitConfig.auth,
52815
+ timeout: gitConfig.timeout,
51783
52816
  cache: {
51784
52817
  enabled: config.registry.cache?.enabled ?? true,
51785
52818
  ttl: config.registry.cache?.ttl
@@ -51803,7 +52836,8 @@ async function resolveRegistryPath(config, options = {}) {
51803
52836
  cachePath,
51804
52837
  ".git",
51805
52838
  lockedRepository[1].commit,
51806
- /* @__PURE__ */ new Set([".prs-cache-meta.json"])
52839
+ /* @__PURE__ */ new Set([".prs-cache-meta.json"]),
52840
+ { allowPartial: true }
51807
52841
  );
51808
52842
  }
51809
52843
  return {
@@ -51839,7 +52873,7 @@ init_src();
51839
52873
  // packages/cli/src/utils/conflict-detector.ts
51840
52874
  init_src();
51841
52875
  import * as fs4 from "fs";
51842
- import { basename as basename13, dirname as dirname14, resolve as resolve17, relative as relative12, isAbsolute as isAbsolute11, sep as sep9 } from "path";
52876
+ import { basename as basename13, dirname as dirname14, resolve as resolve17, relative as relative13, isAbsolute as isAbsolute12, sep as sep10 } from "path";
51843
52877
  function detectOutputConflicts(targets) {
51844
52878
  const pathMap = /* @__PURE__ */ new Map();
51845
52879
  for (const target of targets) {
@@ -51860,14 +52894,14 @@ function detectOutputConflicts(targets) {
51860
52894
  function validateOutputPath(outputPath, outputRoot) {
51861
52895
  const resolved = resolveOutputPath(outputPath, outputRoot);
51862
52896
  const root = resolve17(outputRoot);
51863
- const rel = relative12(root, resolved);
52897
+ const rel = relative13(root, resolved);
51864
52898
  if (!isPathContained(rel, false)) {
51865
52899
  return `Output path "${outputPath}" escapes the output directory ${outputRoot}`;
51866
52900
  }
51867
52901
  try {
51868
52902
  const realRoot = resolveThroughExistingAncestor(root);
51869
52903
  const realResolved = resolveThroughExistingAncestor(resolved);
51870
- if (!isPathContained(relative12(realRoot, realResolved), false)) {
52904
+ if (!isPathContained(relative13(realRoot, realResolved), false)) {
51871
52905
  return `Output path "${outputPath}" escapes the output directory ${outputRoot}`;
51872
52906
  }
51873
52907
  } catch {
@@ -51876,11 +52910,11 @@ function validateOutputPath(outputPath, outputRoot) {
51876
52910
  return void 0;
51877
52911
  }
51878
52912
  function isPathInsideDir(dir, root) {
51879
- const rel = relative12(resolve17(root), resolveOutputPath(dir, root));
52913
+ const rel = relative13(resolve17(root), resolveOutputPath(dir, root));
51880
52914
  return isPathContained(rel, true);
51881
52915
  }
51882
52916
  function isPathContained(rel, allowEqual) {
51883
- return allowEqual && rel === "" || rel !== "" && rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute11(rel);
52917
+ return allowEqual && rel === "" || rel !== "" && rel !== ".." && !rel.startsWith(`..${sep10}`) && !isAbsolute12(rel);
51884
52918
  }
51885
52919
  function resolveThroughExistingAncestor(path2) {
51886
52920
  const missingSegments = [];
@@ -51904,7 +52938,7 @@ function resolveThroughExistingAncestor(path2) {
51904
52938
  return resolve17(fs4.realpathSync.native(current), ...missingSegments);
51905
52939
  }
51906
52940
  function resolveOutputPath(outputPath, projectRoot) {
51907
- if (isAbsolute11(outputPath)) {
52941
+ if (isAbsolute12(outputPath)) {
51908
52942
  return outputPath;
51909
52943
  }
51910
52944
  return resolve17(projectRoot, outputPath);
@@ -51923,12 +52957,14 @@ import {
51923
52957
  rmdir,
51924
52958
  unlink as unlink2
51925
52959
  } from "node:fs/promises";
51926
- import { basename as basename14, dirname as dirname15, isAbsolute as isAbsolute12, relative as relative13, resolve as resolve18 } from "node:path";
52960
+ import { basename as basename14, dirname as dirname15, isAbsolute as isAbsolute13, relative as relative14, resolve as resolve18 } from "node:path";
51927
52961
  import { promisify as promisify2 } from "node:util";
51928
52962
  var execFileAsync2 = promisify2(execFile2);
51929
52963
  var PROMPTSCRIPT_MARKER_PATTERNS = [
51930
52964
  /^<!-- PromptScript \d{4}-\d{2}-\d{2}T[\d:.]+Z \| source: .+ \| target: .+ - do not edit -->$/,
51931
52965
  /^# promptscript-generated: \d{4}-\d{2}-\d{2}T[\d:.]+Z \| source: .+ \| target: .+$/,
52966
+ /^\/\/ promptscript-generated: opencode-plugin$/,
52967
+ /^\/\/ Generated by PromptScript - do not edit\.$/,
51932
52968
  /^> Auto-generated by PromptScript(?:\s.*)?$/
51933
52969
  ];
51934
52970
  var OWNED_COMMAND_FIELDS = /* @__PURE__ */ new Set([
@@ -52256,7 +53292,7 @@ function collectManagedDirectories(outputs, outputRoot) {
52256
53292
  for (const output of outputs.values()) {
52257
53293
  for (const directory of output.managedOutputDirectories ?? []) {
52258
53294
  const normalized = directory.replace(/\\/g, "/");
52259
- if (!normalized || isAbsolute12(normalized)) continue;
53295
+ if (!normalized || isAbsolute13(normalized)) continue;
52260
53296
  if (normalized.split("/").some((segment) => segment === "..")) continue;
52261
53297
  const absolutePath = resolve18(outputRoot, normalized);
52262
53298
  if (isWithin(outputRoot, absolutePath)) directories.add(absolutePath);
@@ -52277,7 +53313,7 @@ function collectManagedFiles(outputs, outputRoot) {
52277
53313
  for (const output of outputs.values()) {
52278
53314
  for (const file of output.managedOutputFiles ?? []) {
52279
53315
  const normalized = file.replace(/\\/g, "/");
52280
- if (!normalized || isAbsolute12(normalized)) continue;
53316
+ if (!normalized || isAbsolute13(normalized)) continue;
52281
53317
  if (normalized.split("/").some((segment) => segment === "..")) continue;
52282
53318
  const absolutePath = resolve18(outputRoot, normalized);
52283
53319
  if (isWithin(outputRoot, absolutePath)) files.add(absolutePath);
@@ -52540,7 +53576,7 @@ async function removeIfUnchanged(entryPath, fileStat) {
52540
53576
  return true;
52541
53577
  }
52542
53578
  async function openAncestorDirectories(root, directory) {
52543
- const relativePath = relative13(root, directory);
53579
+ const relativePath = relative14(root, directory);
52544
53580
  const segments = relativePath === "" ? [] : relativePath.split(/[\\/]+/);
52545
53581
  const paths = [root];
52546
53582
  let current = root;
@@ -52564,8 +53600,8 @@ async function openAncestorDirectories(root, directory) {
52564
53600
  return guards;
52565
53601
  }
52566
53602
  async function openOrCreateAncestorDirectories(root, file) {
52567
- const relativePath = relative13(root, file);
52568
- if (relativePath.startsWith("..") || isAbsolute12(relativePath)) {
53603
+ const relativePath = relative14(root, file);
53604
+ if (relativePath.startsWith("..") || isAbsolute13(relativePath)) {
52569
53605
  return void 0;
52570
53606
  }
52571
53607
  await mkdir4(root, { recursive: true });
@@ -53015,8 +54051,8 @@ function scanHookOwnership(value) {
53015
54051
  return scanHookOwnership(Object.values(object));
53016
54052
  }
53017
54053
  function isWithin(root, candidate) {
53018
- const relativePath = relative13(root, candidate);
53019
- return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute12(relativePath);
54054
+ const relativePath = relative14(root, candidate);
54055
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute13(relativePath);
53020
54056
  }
53021
54057
  function isNodeError(error) {
53022
54058
  return error instanceof Error && "code" in error;
@@ -53057,6 +54093,8 @@ async function loadBundledSkillContent(logger) {
53057
54093
  var PROMPTSCRIPT_MARKERS2 = [
53058
54094
  "<!-- PromptScript",
53059
54095
  "# promptscript-generated:",
54096
+ "// promptscript-generated:",
54097
+ "// Generated by PromptScript - do not edit.",
53060
54098
  "> Auto-generated by PromptScript"
53061
54099
  ];
53062
54100
  function hasPromptScriptMarker(content) {
@@ -53246,11 +54284,77 @@ async function finalizeOutputPlan(result, options) {
53246
54284
  };
53247
54285
  }
53248
54286
 
54287
+ // packages/cli/src/utils/resource-filter.ts
54288
+ init_src();
54289
+ function collectResourceKinds(value, previous) {
54290
+ return [...previous, ...value.split(",").map((item) => item.trim())].filter(Boolean);
54291
+ }
54292
+ function parseResourceSelection(values) {
54293
+ if (!values || values.length === 0) return { kinds: [], invalid: [] };
54294
+ const kinds = [];
54295
+ const invalid = [];
54296
+ for (const raw of values.flatMap((value) => value.split(","))) {
54297
+ const item = raw.trim();
54298
+ if (!item) continue;
54299
+ if (isOutputResourceKind(item)) {
54300
+ if (!kinds.includes(item)) kinds.push(item);
54301
+ } else if (!invalid.includes(item)) {
54302
+ invalid.push(item);
54303
+ }
54304
+ }
54305
+ return { kinds, invalid };
54306
+ }
54307
+ function filterOutputsByResources(plan, outputs, kinds, skillBaseDirs) {
54308
+ const filtered = /* @__PURE__ */ new Map();
54309
+ for (const file of plan.files) {
54310
+ if (!kinds.has(classifyOutputResource(file.owner, file.path, skillBaseDirs?.get(file.owner)))) {
54311
+ continue;
54312
+ }
54313
+ const existing = outputs.get(file.path);
54314
+ filtered.set(
54315
+ file.path,
54316
+ existing ?? {
54317
+ path: file.path,
54318
+ content: file.content,
54319
+ ...file.mode !== void 0 ? { mode: file.mode } : {},
54320
+ ...file.merge !== void 0 ? { merge: file.merge } : {},
54321
+ ...file.managedOutputDirectories !== void 0 ? { managedOutputDirectories: file.managedOutputDirectories } : {},
54322
+ ...file.managedOutputFiles !== void 0 ? { managedOutputFiles: file.managedOutputFiles } : {}
54323
+ }
54324
+ );
54325
+ }
54326
+ return {
54327
+ outputs: filtered,
54328
+ kept: filtered.size,
54329
+ dropped: plan.files.length - filtered.size
54330
+ };
54331
+ }
54332
+
54333
+ // packages/cli/src/utils/global-output-safety.ts
54334
+ import { homedir as homedir3 } from "node:os";
54335
+ import { resolve as resolve21 } from "node:path";
54336
+ var PROTECTED_USER_FILES = [
54337
+ {
54338
+ // Factory AI contract: ~/.factory/AGENTS.md is a personal override file.
54339
+ path: ".factory/AGENTS.md",
54340
+ reason: "Factory personal override (~/.factory/AGENTS.md is user-owned, never compiler output)"
54341
+ }
54342
+ ];
54343
+ function describeProtectedUserFile(absolutePath) {
54344
+ const home = homedir3();
54345
+ for (const entry of PROTECTED_USER_FILES) {
54346
+ if (absolutePath === resolve21(home, entry.path)) {
54347
+ return { displayPath: `~/${entry.path}`, reason: entry.reason };
54348
+ }
54349
+ }
54350
+ return void 0;
54351
+ }
54352
+
53249
54353
  // packages/cli/src/commands/compile.ts
53250
54354
  init_build_profile();
53251
54355
  init_target_config();
53252
54356
  async function loadCompileLockfile(projectRoot, logger) {
53253
- const lockfilePath = resolve21(projectRoot, "promptscript.lock");
54357
+ const lockfilePath = resolve22(projectRoot, "promptscript.lock");
53254
54358
  if (!existsSync14(lockfilePath)) {
53255
54359
  return void 0;
53256
54360
  }
@@ -53268,7 +54372,7 @@ async function loadCompileLockfile(projectRoot, logger) {
53268
54372
  }
53269
54373
  function findConfigInDir(dir) {
53270
54374
  for (const file of CONFIG_FILES) {
53271
- const fullPath = resolve21(dir, file);
54375
+ const fullPath = resolve22(dir, file);
53272
54376
  if (existsSync14(fullPath)) {
53273
54377
  return fullPath;
53274
54378
  }
@@ -53309,19 +54413,19 @@ function describeEmptyTargets(context) {
53309
54413
  }
53310
54414
  function resolveOutputBase(projectRoot, output) {
53311
54415
  if (!output) return projectRoot;
53312
- return isAbsolute13(output) ? output : resolve21(projectRoot, output);
54416
+ return isAbsolute14(output) ? output : resolve22(projectRoot, output);
53313
54417
  }
53314
54418
  function resolveProjectPath(projectRoot, path2) {
53315
- return isAbsolute13(path2) ? path2 : resolve21(projectRoot, path2);
54419
+ return isAbsolute14(path2) ? path2 : resolve22(projectRoot, path2);
53316
54420
  }
53317
54421
  function validateWritePathSymlinks(outputPath, outputRoot) {
53318
- const root = resolve21(outputRoot);
53319
- const candidate = resolve21(root, outputPath);
53320
- const relativePath = relative14(root, candidate);
53321
- if (relativePath === "" || relativePath.startsWith("..") || isAbsolute13(relativePath)) return;
54422
+ const root = resolve22(outputRoot);
54423
+ const candidate = resolve22(root, outputPath);
54424
+ const relativePath = relative15(root, candidate);
54425
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute14(relativePath)) return;
53322
54426
  let current = root;
53323
54427
  for (const segment of relativePath.split(/[\\/]/)) {
53324
- current = resolve21(current, segment);
54428
+ current = resolve22(current, segment);
53325
54429
  let stat5;
53326
54430
  try {
53327
54431
  stat5 = lstatSync2(current);
@@ -53452,10 +54556,21 @@ ${escaping.map((m) => ` - ${m}`).join("\n")}`,
53452
54556
  "PS1003" /* INVALID_PATH */
53453
54557
  );
53454
54558
  }
54559
+ const protectedHits = plannedOutputs.map((output) => describeProtectedUserFile(resolve22(outputRoot, output.path))).filter((hit) => hit !== void 0);
54560
+ if (protectedHits.length > 0) {
54561
+ const protectedList = protectedHits.map((hit) => ` - ${hit.displayPath} (${hit.reason})`).join("\n");
54562
+ throw new PSError(
54563
+ `Refusing to write protected personal file(s):
54564
+ ${protectedList}
54565
+
54566
+ Personal override files are never compiler output. Use --resources to install generated agent and skill directories only.`,
54567
+ "PS1003" /* INVALID_PATH */
54568
+ );
54569
+ }
53455
54570
  if (!options.dryRun && !options.force && !isTTY()) {
53456
54571
  const preflightConflicts = [];
53457
54572
  for (const output of plannedOutputs) {
53458
- const outputPath = options.output ? resolve21(options.output, output.path) : resolve21(output.path);
54573
+ const outputPath = options.output ? resolve22(options.output, output.path) : resolve22(output.path);
53459
54574
  if (!existsSync14(outputPath)) continue;
53460
54575
  const existingContent = await readFileOrUndefined(outputPath);
53461
54576
  const contentMatches = existingContent !== void 0 && stripPromptScriptMarkers(existingContent) === stripPromptScriptMarkers(output.content);
@@ -53480,7 +54595,7 @@ Use --force to overwrite these files.`
53480
54595
  }
53481
54596
  }
53482
54597
  async function safeOverwrite(output, outputPath, existingContent, content) {
53483
- const outputRoot2 = options.output ? resolve21(options.output) : process.cwd();
54598
+ const outputRoot2 = options.output ? resolve22(options.output) : process.cwd();
53484
54599
  const rewritten = existingContent !== void 0 && await rewriteHookOutputIfUnchanged(
53485
54600
  outputPath,
53486
54601
  outputRoot2,
@@ -53496,7 +54611,7 @@ Use --force to overwrite these files.`
53496
54611
  return rewritten;
53497
54612
  }
53498
54613
  async function safeCreate(output, outputPath, content) {
53499
- const outputRoot2 = options.output ? resolve21(options.output) : process.cwd();
54614
+ const outputRoot2 = options.output ? resolve22(options.output) : process.cwd();
53500
54615
  const created = await createHookOutputSafely(outputPath, outputRoot2, content, output.mode);
53501
54616
  if (!created) {
53502
54617
  const msg = `Skipped '${outputPath}' because its output path was not safe to create.`;
@@ -53506,7 +54621,7 @@ Use --force to overwrite these files.`
53506
54621
  return created;
53507
54622
  }
53508
54623
  for (const output of plannedOutputs) {
53509
- const outputPath = options.output ? resolve21(options.output, output.path) : resolve21(output.path);
54624
+ const outputPath = options.output ? resolve22(options.output, output.path) : resolve22(output.path);
53510
54625
  const fileExists3 = existsSync14(outputPath);
53511
54626
  const existingContent = fileExists3 ? await readFileOrUndefined(outputPath) : void 0;
53512
54627
  const existingHookOutputOwned = existingContent !== void 0 && isPromptScriptOwnedHookOutput(output.path, existingContent);
@@ -53543,7 +54658,7 @@ Use --force to overwrite these files.`
53543
54658
  if (output.mode !== void 0 && existingContent !== void 0) {
53544
54659
  const modeUpdated = await rewriteHookOutputIfUnchanged(
53545
54660
  outputPath,
53546
- options.output ? resolve21(options.output) : process.cwd(),
54661
+ options.output ? resolve22(options.output) : process.cwd(),
53547
54662
  existingContent,
53548
54663
  existingContent,
53549
54664
  output.mode
@@ -53639,7 +54754,7 @@ function printWarnings(warnings) {
53639
54754
  }
53640
54755
  async function compileAllBuilds(options, services) {
53641
54756
  const logger = createCliLogger();
53642
- const projectRoot = options.cwd ? resolve21(options.cwd) : process.cwd();
54757
+ const projectRoot = options.cwd ? resolve22(options.cwd) : process.cwd();
53643
54758
  const configPath = resolveCompileConfigPath(projectRoot, options);
53644
54759
  const config = await loadEffectiveConfig(configPath);
53645
54760
  const buildNames = Object.keys(getBuildProfiles(config));
@@ -53713,7 +54828,7 @@ async function compileCommandWithResult(options, services) {
53713
54828
  const spinner = createSpinner("Loading configuration...").start();
53714
54829
  const logger = createCliLogger();
53715
54830
  try {
53716
- const projectRoot = options.cwd ? resolve21(options.cwd) : process.cwd();
54831
+ const projectRoot = options.cwd ? resolve22(options.cwd) : process.cwd();
53717
54832
  logger.verbose("Loading configuration...");
53718
54833
  const configPath = resolveCompileConfigPath(projectRoot, options);
53719
54834
  const config = await loadEffectiveConfig(configPath);
@@ -53728,6 +54843,17 @@ async function compileCommandWithResult(options, services) {
53728
54843
  } else {
53729
54844
  targets = parsedTargets;
53730
54845
  }
54846
+ const cliResources = options.resources && options.resources.length > 0 ? options.resources : void 0;
54847
+ const resourceSelection = parseResourceSelection(cliResources ?? config.output?.resources);
54848
+ if (resourceSelection.invalid.length > 0) {
54849
+ spinner.fail("Invalid resource selection");
54850
+ ConsoleOutput.error(
54851
+ `Unknown resource kind(s): ${resourceSelection.invalid.join(", ")}. Valid kinds: agents, skills, commands, mcp, hooks, plugins, main.`
54852
+ );
54853
+ process.exitCode = 1;
54854
+ return false;
54855
+ }
54856
+ const resourceKinds = new Set(resourceSelection.kinds);
53731
54857
  if (targets.length === 0) {
53732
54858
  spinner.fail("No compilation targets");
53733
54859
  ConsoleOutput.error(
@@ -53762,7 +54888,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53762
54888
  }
53763
54889
  }
53764
54890
  const lockfile = await loadCompileLockfile(projectRoot, logger);
53765
- const vendorDir = resolve21(projectRoot, ".promptscript/vendor");
54891
+ const vendorDir = resolve22(projectRoot, ".promptscript/vendor");
53766
54892
  let registryPath;
53767
54893
  let referenceRoots;
53768
54894
  if (options.registry) {
@@ -53784,7 +54910,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53784
54910
  if (config.includePromptScriptSkill !== false) {
53785
54911
  skillContent = await loadBundledSkillContent(logger);
53786
54912
  }
53787
- const localPath = resolve21(projectRoot, ".promptscript");
54913
+ const localPath = resolve22(projectRoot, ".promptscript");
53788
54914
  const compiler = new Compiler({
53789
54915
  resolver: {
53790
54916
  registryPath,
@@ -53806,7 +54932,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53806
54932
  ignoreHashes: options.ignoreHashes
53807
54933
  });
53808
54934
  const entry = buildProfile?.entry ?? config.input?.entry;
53809
- const entryPath = entry ? resolve21(projectRoot, entry) : resolve21(localPath, "project.prs");
54935
+ const entryPath = entry ? resolve22(projectRoot, entry) : resolve22(localPath, "project.prs");
53810
54936
  if (!existsSync14(entryPath)) {
53811
54937
  spinner.fail("Entry file not found");
53812
54938
  ConsoleOutput.error(`File not found: ${entryPath}`);
@@ -53838,8 +54964,13 @@ Disable one of the conflicting targets or assign a custom output path.`
53838
54964
  output: resolveOutputBase(projectRoot, configuredOutput),
53839
54965
  force: options.force ?? config.output?.overwrite
53840
54966
  };
54967
+ if (resourceKinds.size === 0 && effectiveOptions.output === homedir4()) {
54968
+ ConsoleOutput.warning(
54969
+ `Output directory is your home directory; root instruction files (for example CLAUDE.md, AGENTS.md) will be written there. Use --resources to install only generated agent and skill directories.`
54970
+ );
54971
+ }
53841
54972
  const hasFactoryTarget = targets.some((target) => target.name === "factory");
53842
- const migrateFactoryHooks = options.migrateFactoryHooks !== false;
54973
+ const migrateFactoryHooks = options.migrateFactoryHooks !== false && resourceKinds.size === 0;
53843
54974
  const legacyMigration = hasFactoryTarget && migrateFactoryHooks ? await prepareLegacyFactoryMigration(result.outputs, effectiveOptions.output) : void 0;
53844
54975
  const legacySettingsPath = hasFactoryTarget && !migrateFactoryHooks ? await detectLegacyFactorySettingsHooks(effectiveOptions.output, true) : void 0;
53845
54976
  const finalized = await finalizeOutputPlan(result, {
@@ -53848,7 +54979,26 @@ Disable one of the conflicting targets or assign a custom output path.`
53848
54979
  logger,
53849
54980
  additionalOutputPaths: legacyMigration ? [".factory/hooks.json"] : []
53850
54981
  });
53851
- const writeResult = await writeOutputs(finalized.outputs, effectiveOptions, config, services);
54982
+ let effectiveOutputs = finalized.outputs;
54983
+ if (resourceKinds.size > 0) {
54984
+ const skillBaseDirs = /* @__PURE__ */ new Map();
54985
+ for (const target of targets) {
54986
+ if (target.config?.skillBaseDir) {
54987
+ skillBaseDirs.set(target.name, target.config.skillBaseDir);
54988
+ }
54989
+ }
54990
+ const filtered = filterOutputsByResources(
54991
+ finalized.outputPlan,
54992
+ finalized.outputs,
54993
+ resourceKinds,
54994
+ skillBaseDirs
54995
+ );
54996
+ effectiveOutputs = filtered.outputs;
54997
+ ConsoleOutput.info(
54998
+ `Resource-only compile: kept ${filtered.kept} file(s), omitted ${filtered.dropped}`
54999
+ );
55000
+ }
55001
+ const writeResult = await writeOutputs(effectiveOutputs, effectiveOptions, config, services);
53852
55002
  if (legacyMigration) {
53853
55003
  if (writeResult.skipped.includes(legacyMigration.hooksPath)) {
53854
55004
  throw new Error(
@@ -53862,30 +55012,38 @@ Disable one of the conflicting targets or assign a custom output path.`
53862
55012
  writeResult.created.includes(legacyMigration.hooksPath)
53863
55013
  );
53864
55014
  }
53865
- const plannedOutputMap = new Map(finalized.outputs);
53866
- const cleanupResult = await cleanupManagedOutputs(plannedOutputMap, {
53867
- outputRoot: effectiveOptions.output,
53868
- dryRun: options.dryRun
53869
- });
53870
- for (const removedPath of cleanupResult.removed) {
55015
+ if (resourceKinds.size > 0) {
53871
55016
  if (options.dryRun) {
53872
- ConsoleOutput.dryRun(`Would remove obsolete generated file: ${removedPath}`);
55017
+ ConsoleOutput.dryRun("Would skip managed cleanup for resource-only compile");
53873
55018
  } else {
53874
- ConsoleOutput.muted(`Removed obsolete generated file: ${removedPath}`);
55019
+ ConsoleOutput.muted("Skipped managed cleanup for resource-only compile");
53875
55020
  }
53876
- }
53877
- for (const removedDirectory of cleanupResult.removedDirectories) {
53878
- if (options.dryRun) {
53879
- ConsoleOutput.dryRun(`Would remove empty managed directory: ${removedDirectory}`);
53880
- } else {
53881
- ConsoleOutput.muted(`Removed empty managed directory: ${removedDirectory}`);
55021
+ } else {
55022
+ const plannedOutputMap = new Map(finalized.outputs);
55023
+ const cleanupResult = await cleanupManagedOutputs(plannedOutputMap, {
55024
+ outputRoot: effectiveOptions.output,
55025
+ dryRun: options.dryRun
55026
+ });
55027
+ for (const removedPath of cleanupResult.removed) {
55028
+ if (options.dryRun) {
55029
+ ConsoleOutput.dryRun(`Would remove obsolete generated file: ${removedPath}`);
55030
+ } else {
55031
+ ConsoleOutput.muted(`Removed obsolete generated file: ${removedPath}`);
55032
+ }
53882
55033
  }
53883
- }
53884
- for (const rewrittenPath of cleanupResult.rewritten ?? []) {
53885
- if (options.dryRun) {
53886
- ConsoleOutput.dryRun(`Would rewrite mixed managed hook file: ${rewrittenPath}`);
53887
- } else {
53888
- ConsoleOutput.muted(`Rewrote mixed managed hook file: ${rewrittenPath}`);
55034
+ for (const removedDirectory of cleanupResult.removedDirectories) {
55035
+ if (options.dryRun) {
55036
+ ConsoleOutput.dryRun(`Would remove empty managed directory: ${removedDirectory}`);
55037
+ } else {
55038
+ ConsoleOutput.muted(`Removed empty managed directory: ${removedDirectory}`);
55039
+ }
55040
+ }
55041
+ for (const rewrittenPath of cleanupResult.rewritten ?? []) {
55042
+ if (options.dryRun) {
55043
+ ConsoleOutput.dryRun(`Would rewrite mixed managed hook file: ${rewrittenPath}`);
55044
+ } else {
55045
+ ConsoleOutput.muted(`Rewrote mixed managed hook file: ${rewrittenPath}`);
55046
+ }
53889
55047
  }
53890
55048
  }
53891
55049
  spinner.succeed("Compilation successful");
@@ -53953,10 +55111,10 @@ function watchForChanges(projectRoot, watchConfig, callback) {
53953
55111
  const includePatterns = watchConfig?.include ?? DEFAULT_WATCH_INCLUDE;
53954
55112
  const excludePatterns = watchConfig?.exclude ?? DEFAULT_WATCH_EXCLUDE;
53955
55113
  const matcherOptions = { dot: true, nocase: process.platform === "win32" };
53956
- const relativePath = (path2) => relative14(projectRoot, path2).replace(/\\/g, "/");
55114
+ const relativePath = (path2) => relative15(projectRoot, path2).replace(/\\/g, "/");
53957
55115
  const matches = (path2, patterns) => {
53958
- const relative22 = relativePath(path2);
53959
- return patterns.some((pattern) => minimatch(relative22, pattern, matcherOptions));
55116
+ const relative24 = relativePath(path2);
55117
+ return patterns.some((pattern) => minimatch(relative24, pattern, matcherOptions));
53960
55118
  };
53961
55119
  const shouldCompile = (path2) => matches(path2, includePatterns) && !matches(path2, excludePatterns);
53962
55120
  const watcher = chokidar.watch(projectRoot, {
@@ -54030,7 +55188,7 @@ function watchForChanges(projectRoot, watchConfig, callback) {
54030
55188
  // packages/cli/src/commands/validate.ts
54031
55189
  init_loader2();
54032
55190
  init_console();
54033
- import { resolve as resolve22, join as join15 } from "path";
55191
+ import { resolve as resolve23, join as join15 } from "path";
54034
55192
  import { existsSync as existsSync15, readFileSync as readFileSync4, writeFileSync, readdirSync } from "fs";
54035
55193
  init_src();
54036
55194
  init_src2();
@@ -54219,7 +55377,7 @@ function discoverPrsFiles(dir, strict = false, includeSymbolicLinks = false) {
54219
55377
  return results;
54220
55378
  }
54221
55379
  function loadValidationLockfile() {
54222
- const lockfilePath = resolve22("promptscript.lock");
55380
+ const lockfilePath = resolve23("promptscript.lock");
54223
55381
  if (!existsSync15(lockfilePath)) {
54224
55382
  return void 0;
54225
55383
  }
@@ -54242,7 +55400,7 @@ async function validateCommand2(options) {
54242
55400
  try {
54243
55401
  const config = await loadConfig();
54244
55402
  const lockfile = loadValidationLockfile();
54245
- const vendorDir = resolve22(".promptscript/vendor");
55403
+ const vendorDir = resolve23(".promptscript/vendor");
54246
55404
  if (!isJsonFormat) spinner.text = "Resolving registry...";
54247
55405
  const registry2 = await resolveRegistryPath(config, { vendorDir, lockfile });
54248
55406
  if (!isJsonFormat) spinner.text = "Validating...";
@@ -54264,7 +55422,7 @@ async function validateCommand2(options) {
54264
55422
  formatters: []
54265
55423
  // No formatters needed for validation only
54266
55424
  });
54267
- const entryPaths = options.files && options.files.length > 0 ? options.files.map((file) => resolve22(file)) : [resolve22(config.input?.entry ?? "./.promptscript/project.prs")];
55425
+ const entryPaths = options.files && options.files.length > 0 ? options.files.map((file) => resolve23(file)) : [resolve23(config.input?.entry ?? "./.promptscript/project.prs")];
54268
55426
  for (const entryPath of entryPaths) {
54269
55427
  if (!existsSync15(entryPath)) {
54270
55428
  handleEntryNotFound(entryPath, isJsonFormat, spinner);
@@ -54317,16 +55475,16 @@ function outputJsonResult(result) {
54317
55475
  console.log(JSON.stringify(result, null, 2));
54318
55476
  }
54319
55477
  async function runFix(requestedFiles) {
54320
- const files = requestedFiles && requestedFiles.length > 0 ? requestedFiles.map((file) => resolve22(file)) : discoverPrsFiles(".promptscript");
55478
+ const files = requestedFiles && requestedFiles.length > 0 ? requestedFiles.map((file) => resolve23(file)) : discoverPrsFiles(".promptscript");
54321
55479
  let fixedCount = 0;
54322
55480
  const configPath = findConfigFile();
54323
55481
  const config = configPath ? await loadConfig(configPath) : void 0;
54324
55482
  const lockfile = loadValidationLockfile();
54325
- const vendorDir = resolve22(".promptscript/vendor");
54326
- const registryPath = config ? (await resolveRegistryPath(config, { vendorDir, lockfile })).path : resolve22("./registry");
55483
+ const vendorDir = resolve23(".promptscript/vendor");
55484
+ const registryPath = config ? (await resolveRegistryPath(config, { vendorDir, lockfile })).path : resolve23("./registry");
54327
55485
  const resolver = new Resolver({
54328
55486
  registryPath,
54329
- localPath: resolve22("./.promptscript"),
55487
+ localPath: resolve23("./.promptscript"),
54330
55488
  vendorDir,
54331
55489
  lockfile,
54332
55490
  registries: config?.registries,
@@ -54340,7 +55498,7 @@ async function runFix(requestedFiles) {
54340
55498
  if (typeof declaredVersion !== "string") continue;
54341
55499
  let ast = parseResult.ast;
54342
55500
  try {
54343
- const resolved = await resolver.resolve(resolve22(filePath));
55501
+ const resolved = await resolver.resolve(resolve23(filePath));
54344
55502
  ast = resolved.ast ?? ast;
54345
55503
  } catch {
54346
55504
  }
@@ -54389,7 +55547,7 @@ init_loader2();
54389
55547
  init_console();
54390
55548
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
54391
55549
  import { existsSync as existsSync16 } from "fs";
54392
- import { resolve as resolve23, dirname as dirname17, relative as relative15 } from "path";
55550
+ import { resolve as resolve24, dirname as dirname17, relative as relative16 } from "path";
54393
55551
  async function pullCommand(options) {
54394
55552
  const spinner = createSpinner("Loading configuration...").start();
54395
55553
  try {
@@ -54409,10 +55567,10 @@ async function pullCommand(options) {
54409
55567
  spinner.text = `Pulling ${config.inherit}...`;
54410
55568
  const registry2 = await createRegistry(config, options, spinner);
54411
55569
  const inheritPath = parseInheritPath(config.inherit);
54412
- const destinationRoot = resolve23("./.promptscript/.inherited");
54413
- const destPath = resolve23(destinationRoot, inheritPath);
54414
- const destinationRelativePath = relative15(destinationRoot, destPath);
54415
- if (destinationRelativePath.startsWith("..") || resolve23(destPath) === resolve23(destinationRoot)) {
55570
+ const destinationRoot = resolve24("./.promptscript/.inherited");
55571
+ const destPath = resolve24(destinationRoot, inheritPath);
55572
+ const destinationRelativePath = relative16(destinationRoot, destPath);
55573
+ if (destinationRelativePath.startsWith("..") || resolve24(destPath) === resolve24(destinationRoot)) {
54416
55574
  throw new Error(`Invalid inheritance path: ${config.inherit}`);
54417
55575
  }
54418
55576
  if (options.dryRun) {
@@ -54460,6 +55618,8 @@ async function createRegistry(config, options, spinner) {
54460
55618
  ref: gitRef ?? gitConfig.ref,
54461
55619
  path: gitConfig.path,
54462
55620
  auth: gitConfig.auth,
55621
+ timeout: gitConfig.timeout,
55622
+ fallbackUrl: gitConfig.fallbackUrl,
54463
55623
  cache: {
54464
55624
  enabled: !options.refresh && (config.registry.cache?.enabled ?? true),
54465
55625
  ttl: config.registry.cache?.ttl
@@ -54487,7 +55647,7 @@ async function createRegistry(config, options, spinner) {
54487
55647
  if (!existsSync16(registryPath)) {
54488
55648
  throw new Error(`Local registry path not found: ${registryPath}`);
54489
55649
  }
54490
- return createFileSystemRegistry(resolve23(registryPath));
55650
+ return createFileSystemRegistry(resolve24(registryPath));
54491
55651
  }
54492
55652
  function parseInheritVersion(inheritPath) {
54493
55653
  const versionIndex = inheritPath.lastIndexOf("@");
@@ -54546,7 +55706,7 @@ function handlePullError(error, spinner) {
54546
55706
  init_src();
54547
55707
  init_loader2();
54548
55708
  init_console();
54549
- import { resolve as resolve25 } from "node:path";
55709
+ import { resolve as resolve26 } from "node:path";
54550
55710
  import { readFile as readFile18 } from "node:fs/promises";
54551
55711
  import { existsSync as existsSync18 } from "node:fs";
54552
55712
  init_loader();
@@ -54555,7 +55715,7 @@ init_loader();
54555
55715
  import { createHash as createHash5 } from "node:crypto";
54556
55716
  import { existsSync as existsSync17 } from "node:fs";
54557
55717
  import { lstat as lstat9, readFile as readFile17 } from "node:fs/promises";
54558
- import { isAbsolute as isAbsolute14, relative as relative16, resolve as resolve24, sep as sep10 } from "node:path";
55718
+ import { isAbsolute as isAbsolute15, relative as relative17, resolve as resolve25, sep as sep11 } from "node:path";
54559
55719
  var DIFF_SCHEMA_VERSION = 1;
54560
55720
  var DIFF_SCHEMA_URL = "https://getpromptscript.dev/schema/diff/v1.json";
54561
55721
  var CHANGE_KIND_ORDER = {
@@ -54574,19 +55734,19 @@ function normalizePath2(path2) {
54574
55734
  return path2.replaceAll("\\", "/");
54575
55735
  }
54576
55736
  function reportPath(outputRoot, path2) {
54577
- const absolutePath = resolve24(outputRoot, path2);
54578
- const relation = normalizePath2(relative16(outputRoot, absolutePath));
55737
+ const absolutePath = resolve25(outputRoot, path2);
55738
+ const relation = normalizePath2(relative17(outputRoot, absolutePath));
54579
55739
  if (!relation || relation === ".") return normalizePath2(path2);
54580
- if (relation === ".." || relation.startsWith("../") || isAbsolute14(relation)) {
55740
+ if (relation === ".." || relation.startsWith("../") || isAbsolute15(relation)) {
54581
55741
  return normalizePath2(path2);
54582
55742
  }
54583
55743
  return relation;
54584
55744
  }
54585
55745
  function reportSource(projectRoot, source) {
54586
55746
  const normalized = normalizePath2(source);
54587
- if (!isAbsolute14(source)) return normalized;
54588
- const relation = normalizePath2(relative16(projectRoot, source));
54589
- if (relation && relation !== ".." && !relation.startsWith("../") && !isAbsolute14(relation)) {
55747
+ if (!isAbsolute15(source)) return normalized;
55748
+ const relation = normalizePath2(relative17(projectRoot, source));
55749
+ if (relation && relation !== ".." && !relation.startsWith("../") && !isAbsolute15(relation)) {
54590
55750
  return relation;
54591
55751
  }
54592
55752
  return normalized;
@@ -54745,15 +55905,15 @@ function createReport(success, changes, warnings, errors, includeContent, unchan
54745
55905
  };
54746
55906
  }
54747
55907
  function findManagedOutputOwner(removedPath, outputs, outputRoot, entryPath, projectRoot) {
54748
- const candidate = resolve24(removedPath);
55908
+ const candidate = resolve25(removedPath);
54749
55909
  for (const output of outputs.values()) {
54750
55910
  const managedFile = (output.managedOutputFiles ?? []).some(
54751
- (file) => resolve24(outputRoot, file) === candidate
55911
+ (file) => resolve25(outputRoot, file) === candidate
54752
55912
  );
54753
55913
  const managedDirectory = (directory) => {
54754
- const directoryPath = resolve24(outputRoot, directory);
54755
- const relation = relative16(directoryPath, candidate);
54756
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep10}`) && !isAbsolute14(relation);
55914
+ const directoryPath = resolve25(outputRoot, directory);
55915
+ const relation = relative17(directoryPath, candidate);
55916
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep11}`) && !isAbsolute15(relation);
54757
55917
  };
54758
55918
  if (managedFile || (output.managedOutputDirectories ?? []).some(managedDirectory)) {
54759
55919
  return outputIdentity(output, entryPath, projectRoot);
@@ -54772,8 +55932,8 @@ function createCompilationDiffErrorReport(errors, warnings, projectRoot, include
54772
55932
  );
54773
55933
  }
54774
55934
  async function buildCompilationDiff(options) {
54775
- const projectRoot = resolve24(options.projectRoot);
54776
- const outputRoot = resolve24(options.outputRoot);
55935
+ const projectRoot = resolve25(options.projectRoot);
55936
+ const outputRoot = resolve25(options.outputRoot);
54777
55937
  const includeContent = options.includeContent === true;
54778
55938
  const changes = [];
54779
55939
  const warnings = options.warnings.map((warning) => normalizeWarning(warning, projectRoot));
@@ -54782,7 +55942,7 @@ async function buildCompilationDiff(options) {
54782
55942
  for (const output of options.outputs.values()) {
54783
55943
  const identity = outputIdentity(output, options.entryPath, projectRoot);
54784
55944
  const path2 = reportPath(outputRoot, output.path);
54785
- const outputPath = resolve24(outputRoot, output.path);
55945
+ const outputPath = resolve25(outputRoot, output.path);
54786
55946
  const desiredContent = output.content;
54787
55947
  const common = {
54788
55948
  target: identity.target,
@@ -54954,7 +56114,7 @@ function resolveUniversalDir2(universalDir) {
54954
56114
  return { universalDir: ".agents" };
54955
56115
  }
54956
56116
  async function loadDiffLockfile(projectRoot) {
54957
- const lockfilePath = resolve25(projectRoot, "promptscript.lock");
56117
+ const lockfilePath = resolve26(projectRoot, "promptscript.lock");
54958
56118
  if (!existsSync18(lockfilePath)) {
54959
56119
  return void 0;
54960
56120
  }
@@ -54976,7 +56136,7 @@ function printNewFilePreview(content, showFull, pager) {
54976
56136
  }
54977
56137
  }
54978
56138
  async function compareOutput(output, outputRoot, showFull, pager) {
54979
- const outputPath = resolve25(outputRoot, output.path);
56139
+ const outputPath = resolve26(outputRoot, output.path);
54980
56140
  const newContent = output.content;
54981
56141
  if (!existsSync18(outputPath)) {
54982
56142
  pager.write(chalk2.green(`+ ${outputPath} (new file)`));
@@ -55049,7 +56209,7 @@ async function diffCommand(options) {
55049
56209
  return;
55050
56210
  }
55051
56211
  const lockfile = await loadDiffLockfile(projectRoot);
55052
- const vendorDir = resolve25(projectRoot, ".promptscript/vendor");
56212
+ const vendorDir = resolve26(projectRoot, ".promptscript/vendor");
55053
56213
  if (!isJsonFormat) spinner.text = "Resolving registry...";
55054
56214
  const registry2 = await resolveRegistryPath(config, {
55055
56215
  vendorDir,
@@ -55060,11 +56220,11 @@ async function diffCommand(options) {
55060
56220
  const logger = createDiffLogger(isJsonFormat);
55061
56221
  const prettierOptions = await resolvePrettierOptions(config, projectRoot);
55062
56222
  const skillContent = config.includePromptScriptSkill === false ? void 0 : await loadBundledSkillContent(logger);
55063
- const outputRoot = resolve25(projectRoot, buildProfile?.output ?? config.output?.baseDir ?? ".");
56223
+ const outputRoot = resolve26(projectRoot, buildProfile?.output ?? config.output?.baseDir ?? ".");
55064
56224
  const compiler = new Compiler({
55065
56225
  resolver: {
55066
56226
  registryPath: registry2.path,
55067
- localPath: resolve25(projectRoot, ".promptscript"),
56227
+ localPath: resolve26(projectRoot, ".promptscript"),
55068
56228
  projectRoot,
55069
56229
  vendorDir,
55070
56230
  readOnly: true,
@@ -55081,7 +56241,7 @@ async function diffCommand(options) {
55081
56241
  logger,
55082
56242
  skillContent
55083
56243
  });
55084
- const entryPath = resolve25(
56244
+ const entryPath = resolve26(
55085
56245
  projectRoot,
55086
56246
  buildProfile?.entry ?? config.input?.entry ?? "./.promptscript/project.prs"
55087
56247
  );
@@ -55234,7 +56394,7 @@ function printSimpleDiff(existingLines, newLines, showFull, pager) {
55234
56394
  // packages/cli/src/commands/check.ts
55235
56395
  import { existsSync as existsSync19 } from "fs";
55236
56396
  import { readFile as readFile19 } from "fs/promises";
55237
- import { resolve as resolve26 } from "path";
56397
+ import { resolve as resolve27 } from "path";
55238
56398
  import { parse as parseYaml8 } from "yaml";
55239
56399
  init_src();
55240
56400
  init_loader2();
@@ -55393,7 +56553,7 @@ async function checkCommand(_options) {
55393
56553
  }
55394
56554
  const effectiveEntries = getEffectiveEntryPaths(config);
55395
56555
  for (const entry of effectiveEntries) {
55396
- const entryPath = resolve26(entry);
56556
+ const entryPath = resolve27(entry);
55397
56557
  const name = entry === (config.input?.entry ?? ".promptscript/project.prs") ? "Entry file" : `Entry file (${entry})`;
55398
56558
  if (existsSync19(entryPath)) {
55399
56559
  results.push({
@@ -55418,7 +56578,7 @@ async function checkCommand(_options) {
55418
56578
  });
55419
56579
  hasWarnings = true;
55420
56580
  } else if (config.registry?.path && !config.registry.git) {
55421
- const registryPath = resolve26(config.registry.path);
56581
+ const registryPath = resolve27(config.registry.path);
55422
56582
  if (existsSync19(registryPath)) {
55423
56583
  results.push({
55424
56584
  name: "Registry path",
@@ -55503,10 +56663,10 @@ async function checkCommand(_options) {
55503
56663
  message: rootTargets.length === 0 ? `${targetNames.size} target(s) configured in build profiles` : `${targetNames.size} target(s) configured`
55504
56664
  });
55505
56665
  }
55506
- const existingEntries = effectiveEntries.filter((entry) => existsSync19(resolve26(entry)));
56666
+ const existingEntries = effectiveEntries.filter((entry) => existsSync19(resolve27(entry)));
55507
56667
  if (existingEntries.length > 0) {
55508
56668
  try {
55509
- const lockfilePath = resolve26("promptscript.lock");
56669
+ const lockfilePath = resolve27("promptscript.lock");
55510
56670
  let lockfile;
55511
56671
  if (existsSync19(lockfilePath)) {
55512
56672
  const parsed = parseYaml8(await readFile19(lockfilePath, "utf-8"), {
@@ -55522,12 +56682,12 @@ async function checkCommand(_options) {
55522
56682
  message: "promptscript.lock"
55523
56683
  });
55524
56684
  }
55525
- const vendorDir = resolve26(".promptscript/vendor");
56685
+ const vendorDir = resolve27(".promptscript/vendor");
55526
56686
  const registry2 = await resolveRegistryPath(config, { vendorDir, lockfile });
55527
56687
  const compiler = new Compiler({
55528
56688
  resolver: {
55529
- registryPath: resolve26(registry2.path),
55530
- localPath: resolve26(".promptscript"),
56689
+ registryPath: resolve27(registry2.path),
56690
+ localPath: resolve27(".promptscript"),
55531
56691
  vendorDir,
55532
56692
  referenceRoots: registry2.repositoryUrl && registry2.repositoryPath ? { [registry2.repositoryUrl]: [registry2.repositoryPath] } : void 0,
55533
56693
  lockfile,
@@ -55542,7 +56702,7 @@ async function checkCommand(_options) {
55542
56702
  for (const entry of existingEntries) {
55543
56703
  const name = entry === (config.input?.entry ?? ".promptscript/project.prs") ? "Project resolution" : `Project resolution (${entry})`;
55544
56704
  try {
55545
- const result = await compiler.compile(resolve26(entry));
56705
+ const result = await compiler.compile(resolve27(entry));
55546
56706
  if (result.errors.length > 0) {
55547
56707
  results.push({
55548
56708
  name,
@@ -55629,7 +56789,7 @@ import { dirname as dirname18 } from "path";
55629
56789
 
55630
56790
  // packages/cli/src/utils/version-check.ts
55631
56791
  init_console();
55632
- import { homedir as homedir3 } from "os";
56792
+ import { homedir as homedir5 } from "os";
55633
56793
  import { join as join16 } from "path";
55634
56794
  import { readFile as readFile20, writeFile as writeFile5, mkdir as mkdir6 } from "fs/promises";
55635
56795
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@promptscript/cli/latest";
@@ -55679,7 +56839,7 @@ function comparePrerelease2(current, latest) {
55679
56839
  return 0;
55680
56840
  }
55681
56841
  function getCacheDir() {
55682
- return join16(homedir3(), ".promptscript", ".cache");
56842
+ return join16(homedir5(), ".promptscript", ".cache");
55683
56843
  }
55684
56844
  function getCachePath() {
55685
56845
  return join16(getCacheDir(), "version.json");
@@ -56107,7 +57267,7 @@ Then reference configs with \`@inherit\` or \`@use\`:
56107
57267
  }
56108
57268
 
56109
57269
  // packages/cli/src/utils/resolve-target-directory.ts
56110
- import { join as join18, resolve as resolve27 } from "path";
57270
+ import { join as join18, resolve as resolve28 } from "path";
56111
57271
 
56112
57272
  // packages/cli/src/utils/slugify.ts
56113
57273
  function slugify(name) {
@@ -56118,7 +57278,7 @@ function slugify(name) {
56118
57278
  async function resolveTargetDirectory(options, services) {
56119
57279
  const { cwd, directoryArg, registryName, nonInteractive } = options;
56120
57280
  if (directoryArg) {
56121
- return resolve27(directoryArg);
57281
+ return resolve28(directoryArg);
56122
57282
  }
56123
57283
  const empty = await isDirectoryEmpty(cwd, services.fs);
56124
57284
  if (empty && nonInteractive) {
@@ -56237,7 +57397,7 @@ async function registryInitCommand(directory, options, services = createDefaultS
56237
57397
  // packages/cli/src/commands/registry/validate.ts
56238
57398
  init_services();
56239
57399
  init_console();
56240
- import { resolve as resolve28 } from "path";
57400
+ import { resolve as resolve29 } from "path";
56241
57401
 
56242
57402
  // packages/cli/src/utils/registry-validator.ts
56243
57403
  import { join as join19 } from "path";
@@ -56442,7 +57602,7 @@ async function registryValidateCommand(path2, options, services = createDefaultS
56442
57602
  process.exitCode = 1;
56443
57603
  return;
56444
57604
  }
56445
- const registryPath = resolve28(path2 ?? ".");
57605
+ const registryPath = resolve29(path2 ?? ".");
56446
57606
  const spinner = createSpinner("Validating registry...").start();
56447
57607
  try {
56448
57608
  const result = await validateRegistry(registryPath, services);
@@ -56494,10 +57654,10 @@ async function registryValidateCommand(path2, options, services = createDefaultS
56494
57654
  // packages/cli/src/commands/registry/publish.ts
56495
57655
  init_services();
56496
57656
  init_console();
56497
- import { resolve as resolve29, join as join20 } from "path";
57657
+ import { resolve as resolve30, join as join20 } from "path";
56498
57658
  import { execFileSync as execFileSync2 } from "child_process";
56499
57659
  async function registryPublishCommand(path2, options, services = createDefaultServices()) {
56500
- const registryPath = resolve29(path2 ?? ".");
57660
+ const registryPath = resolve30(path2 ?? ".");
56501
57661
  try {
56502
57662
  if (!options.force) {
56503
57663
  const spinner2 = createSpinner("Validating registry...").start();
@@ -56535,7 +57695,7 @@ async function registryPublishCommand(path2, options, services = createDefaultSe
56535
57695
  let gitRoot;
56536
57696
  try {
56537
57697
  git("rev-parse", "--is-inside-work-tree");
56538
- gitRoot = resolve29(git("rev-parse", "--show-toplevel"));
57698
+ gitRoot = resolve30(git("rev-parse", "--show-toplevel"));
56539
57699
  } catch {
56540
57700
  spinner.fail("Not a git repository");
56541
57701
  ConsoleOutput.muted("Initialize with: git init");
@@ -56813,7 +57973,7 @@ init_console();
56813
57973
  init_src();
56814
57974
  import { writeFile as writeFile7, readFile as readFile25 } from "fs/promises";
56815
57975
  import { existsSync as existsSync22 } from "fs";
56816
- import { resolve as resolve32 } from "path";
57976
+ import { resolve as resolve33 } from "path";
56817
57977
  import { parse as parseYaml12, stringify as stringifyYaml3 } from "yaml";
56818
57978
 
56819
57979
  // packages/cli/src/commands/lock-scanner.ts
@@ -56836,6 +57996,63 @@ async function collectRemoteImports(entryPath, options) {
56836
57996
  const visited = /* @__PURE__ */ new Set();
56837
57997
  const seen = /* @__PURE__ */ new Set();
56838
57998
  const results = [];
57999
+ function recordRemoteImport(path2, sourceFile, resolved) {
58000
+ const marker = parseRegistryMarker(resolved);
58001
+ if (!marker) {
58002
+ return false;
58003
+ }
58004
+ const key = `${marker.repoUrl}\0${marker.path}\0${marker.version}`;
58005
+ if (options.deduplicate === false || !seen.has(key)) {
58006
+ seen.add(key);
58007
+ results.push({
58008
+ ...marker,
58009
+ ...options.includeLocations ? {
58010
+ sourceFile,
58011
+ sourceLine: path2.loc.line,
58012
+ rawSource: path2.raw
58013
+ } : {}
58014
+ });
58015
+ }
58016
+ return true;
58017
+ }
58018
+ async function scanLocalReference(resolved) {
58019
+ if (!resolved.endsWith(".prs") && !resolved.endsWith(".md")) {
58020
+ return;
58021
+ }
58022
+ if (existsSync21(resolved)) {
58023
+ await scan(resolved);
58024
+ return;
58025
+ }
58026
+ if (resolved.endsWith(".prs")) {
58027
+ const skillDirectory = resolved.slice(0, -".prs".length);
58028
+ if (existsSync21(skillDirectory)) {
58029
+ const skillFile = join21(skillDirectory, "SKILL.md");
58030
+ if (existsSync21(skillFile)) {
58031
+ await scan(skillFile);
58032
+ }
58033
+ return;
58034
+ }
58035
+ }
58036
+ if (options.strict) {
58037
+ throw new Error(`Imported PromptScript file not found: ${resolved}`);
58038
+ }
58039
+ }
58040
+ async function scanReference(path2, sourceFile) {
58041
+ let resolved;
58042
+ try {
58043
+ resolved = loader.resolveRef(path2, sourceFile);
58044
+ } catch (error) {
58045
+ if (options.strict) {
58046
+ throw new Error(`Cannot resolve import '${path2.raw}' in ${sourceFile}`, {
58047
+ cause: error
58048
+ });
58049
+ }
58050
+ return;
58051
+ }
58052
+ if (!recordRemoteImport(path2, sourceFile, resolved)) {
58053
+ await scanLocalReference(resolved);
58054
+ }
58055
+ }
56839
58056
  async function scan(filePath) {
56840
58057
  if (visited.has(filePath)) return;
56841
58058
  visited.add(filePath);
@@ -56856,54 +58073,16 @@ async function collectRemoteImports(entryPath, options) {
56856
58073
  throw new Error(`Cannot scan invalid PromptScript file: ${filePath}: ${errors[0].message}`);
56857
58074
  }
56858
58075
  if (!ast) return;
56859
- for (const use of ast.uses) {
56860
- let resolved;
56861
- try {
56862
- resolved = loader.resolveRef(use.path, filePath);
56863
- } catch (error) {
56864
- if (options.strict) {
56865
- throw new Error(`Cannot resolve import '${use.path.raw}' in ${filePath}`, {
56866
- cause: error
56867
- });
56868
- }
56869
- continue;
56870
- }
56871
- const marker = parseRegistryMarker(resolved);
56872
- if (marker) {
56873
- const key = `${marker.repoUrl}\0${marker.path}\0${marker.version}`;
56874
- if (options.deduplicate === false || !seen.has(key)) {
56875
- seen.add(key);
56876
- results.push({
56877
- ...marker,
56878
- ...options.includeLocations ? {
56879
- sourceFile: filePath,
56880
- sourceLine: use.path.loc.line,
56881
- rawSource: use.path.raw
56882
- } : {}
56883
- });
56884
- }
56885
- } else {
56886
- if (!resolved.endsWith(".prs") && !resolved.endsWith(".md")) {
56887
- continue;
56888
- }
56889
- if (existsSync21(resolved)) {
56890
- await scan(resolved);
56891
- continue;
56892
- }
56893
- if (resolved.endsWith(".prs")) {
56894
- const skillDirectory = resolved.slice(0, -".prs".length);
56895
- if (existsSync21(skillDirectory)) {
56896
- const skillFile = join21(skillDirectory, "SKILL.md");
56897
- if (existsSync21(skillFile)) {
56898
- await scan(skillFile);
56899
- }
56900
- continue;
56901
- }
56902
- }
56903
- if (options.strict) {
56904
- throw new Error(`Imported PromptScript file not found: ${resolved}`);
56905
- }
56906
- }
58076
+ const inlineUses = ast.blocks.flatMap(
58077
+ (block) => block.name === "skills" && (block.content.type === "ObjectContent" || block.content.type === "MixedContent") ? block.content.inlineUses ?? [] : []
58078
+ );
58079
+ const paths = [
58080
+ ...ast.inherit ? [ast.inherit.path] : [],
58081
+ ...ast.uses.map((use) => use.path),
58082
+ ...inlineUses.map((use) => use.path)
58083
+ ];
58084
+ for (const path2 of paths) {
58085
+ await scanReference(path2, filePath);
56907
58086
  }
56908
58087
  }
56909
58088
  await scan(entryPath);
@@ -56911,16 +58090,16 @@ async function collectRemoteImports(entryPath, options) {
56911
58090
  }
56912
58091
 
56913
58092
  // packages/cli/src/commands/lock-references.ts
56914
- import { homedir as homedir4 } from "os";
56915
- import { resolve as resolve31, join as join22 } from "path";
58093
+ import { homedir as homedir6 } from "os";
58094
+ import { resolve as resolve32, join as join22 } from "path";
56916
58095
 
56917
58096
  // packages/cli/src/commands/lock-reference-scanner.ts
56918
58097
  import { readFile as readFile24 } from "fs/promises";
56919
- import { dirname as dirname20, isAbsolute as isAbsolute15, relative as relative17, resolve as resolve30, sep as sep11 } from "path";
58098
+ import { dirname as dirname20, isAbsolute as isAbsolute16, relative as relative18, resolve as resolve31, sep as sep12 } from "path";
56920
58099
  async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (/* @__PURE__ */ new Date()).toISOString()) {
56921
58100
  const references = {};
56922
58101
  const sortedRoots = [...roots].sort(
56923
- (left, right) => resolve30(right.cachePath).length - resolve30(left.cachePath).length
58102
+ (left, right) => resolve31(right.cachePath).length - resolve31(left.cachePath).length
56924
58103
  );
56925
58104
  for (const block of ast.blocks) {
56926
58105
  if (block.name !== "skills" || block.content.type !== "ObjectContent") {
@@ -56934,7 +58113,7 @@ async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (
56934
58113
  if (!Array.isArray(refs)) continue;
56935
58114
  for (const ref of refs) {
56936
58115
  if (typeof ref !== "string") continue;
56937
- const referencePath = isAbsolute15(ref) ? resolve30(ref) : resolve30(dirname20(block.loc.file), ref);
58116
+ const referencePath = isAbsolute16(ref) ? resolve31(ref) : resolve31(dirname20(block.loc.file), ref);
56938
58117
  const root = sortedRoots.find(
56939
58118
  (candidate) => isInsideCachePath(referencePath, candidate.cachePath)
56940
58119
  );
@@ -56942,7 +58121,7 @@ async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (
56942
58121
  if (!await isRealPathInside(referencePath, root.cachePath)) {
56943
58122
  throw new Error(`Registry reference is missing or escapes its repository: ${ref}`);
56944
58123
  }
56945
- const relativePath = relative17(resolve30(root.cachePath), referencePath).split(sep11).join("/");
58124
+ const relativePath = relative18(resolve31(root.cachePath), referencePath).split(sep12).join("/");
56946
58125
  const key = buildReferenceKey(root.repoUrl, relativePath, root.version);
56947
58126
  const hash = hashContent(await readFile24(referencePath));
56948
58127
  const previous = existing[key];
@@ -56962,9 +58141,9 @@ function normalizeRepository(value) {
56962
58141
  }
56963
58142
  async function generateLockfileReferences(config, entryPath, localPath, lockfile, imports) {
56964
58143
  const registry2 = await resolveRegistryPath(config, { lockfile });
56965
- const cacheDir = join22(homedir4(), ".promptscript", "cache");
58144
+ const cacheDir = join22(homedir6(), ".promptscript", "cache");
56966
58145
  const resolver = new Resolver({
56967
- registryPath: resolve31(registry2.path),
58146
+ registryPath: resolve32(registry2.path),
56968
58147
  localPath,
56969
58148
  lockfile,
56970
58149
  registries: config.registries,
@@ -57095,14 +58274,15 @@ async function lockCommand(options) {
57095
58274
  }
57096
58275
  }
57097
58276
  const projectRoot = process.cwd();
57098
- const localPath = resolve32(projectRoot, ".promptscript");
57099
- const entryPath = config.input?.entry ? resolve32(projectRoot, config.input.entry) : resolve32(localPath, "project.prs");
58277
+ const localPath = resolve33(projectRoot, ".promptscript");
58278
+ const entryPath = config.input?.entry ? resolve33(projectRoot, config.input.entry) : resolve33(localPath, "project.prs");
57100
58279
  const registry2 = await resolveRegistryPath(config, {
58280
+ allowMissingLockEntry: true,
57101
58281
  ...!options.update && Object.keys(existing.dependencies).length > 0 ? { lockfile: existing } : {}
57102
58282
  });
57103
58283
  const scannedImports = await collectRemoteImports(entryPath, {
57104
58284
  localPath,
57105
- registryPath: resolve32(registry2.path),
58285
+ registryPath: resolve33(registry2.path),
57106
58286
  registries: config.registries,
57107
58287
  strict: true
57108
58288
  });
@@ -57116,7 +58296,8 @@ async function lockCommand(options) {
57116
58296
  requestedDependencies.set(defaultGitRegistry.url, {
57117
58297
  versions: /* @__PURE__ */ new Set([defaultGitRegistry.ref ?? "main"]),
57118
58298
  ...defaultGitRegistry.fallbackUrl ? { fallbackUrl: defaultGitRegistry.fallbackUrl } : {},
57119
- ...defaultGitRegistry.auth ? { auth: defaultGitRegistry.auth } : {}
58299
+ ...defaultGitRegistry.auth ? { auth: defaultGitRegistry.auth } : {},
58300
+ ...defaultGitRegistry.timeout !== void 0 ? { timeout: defaultGitRegistry.timeout } : {}
57120
58301
  });
57121
58302
  }
57122
58303
  for (const [, entry] of aliasEntries) {
@@ -57128,6 +58309,9 @@ async function lockCommand(options) {
57128
58309
  if (typeof entry !== "string" && entry.fallbackUrl) {
57129
58310
  requested.fallbackUrl = entry.fallbackUrl;
57130
58311
  }
58312
+ if (typeof entry !== "string" && entry.timeout !== void 0) {
58313
+ requested.timeout = entry.timeout;
58314
+ }
57131
58315
  requestedDependencies.set(repoUrl, requested);
57132
58316
  }
57133
58317
  for (const imp of scannedImports) {
@@ -57152,7 +58336,8 @@ async function lockCommand(options) {
57152
58336
  previous,
57153
58337
  options.update === true && matchingRepos.includes(repoUrl),
57154
58338
  requested.fallbackUrl,
57155
- requested.auth
58339
+ requested.auth,
58340
+ { timeout: requested.timeout }
57156
58341
  );
57157
58342
  if (previous?.skills?.length && previous.commit !== resolved.commit) {
57158
58343
  throw new Error(
@@ -57322,7 +58507,7 @@ import {
57322
58507
  writeFile as writeFile8
57323
58508
  } from "fs/promises";
57324
58509
  import { existsSync as existsSync23 } from "fs";
57325
- import { basename as basename15, dirname as dirname21, join as join23, resolve as resolve33 } from "path";
58510
+ import { basename as basename15, dirname as dirname21, join as join23, resolve as resolve34 } from "path";
57326
58511
  import { parse as parseYaml13 } from "yaml";
57327
58512
  var VENDOR_DIR = ".promptscript/vendor";
57328
58513
  async function vendorSyncCommand(options) {
@@ -57343,7 +58528,7 @@ async function vendorSyncCommand(options) {
57343
58528
  spinner.succeed(`Dry run completed (${deps.length} dependencies ready)`);
57344
58529
  return;
57345
58530
  }
57346
- const vendorDir = resolve33(VENDOR_DIR);
58531
+ const vendorDir = resolve34(VENDOR_DIR);
57347
58532
  await validateVendorParentDirectory(vendorDir, true);
57348
58533
  const recoveredBackup = await recoverVendorDirectory(vendorDir);
57349
58534
  if (recoveredBackup) {
@@ -57416,7 +58601,7 @@ async function vendorCheckCommand(_options) {
57416
58601
  }
57417
58602
  const deps = getRepositoryDependencies(lockfile);
57418
58603
  validateDependencyPaths(deps);
57419
- const vendorDir = resolve33(VENDOR_DIR);
58604
+ const vendorDir = resolve34(VENDOR_DIR);
57420
58605
  await validateVendorParentDirectory(vendorDir, false);
57421
58606
  const recoveredBackup = await recoverVendorDirectory(vendorDir);
57422
58607
  if (recoveredBackup) {
@@ -57597,10 +58782,10 @@ init_loader2();
57597
58782
  init_user_config();
57598
58783
  init_console();
57599
58784
  import { existsSync as existsSync24, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
57600
- import { join as join24, resolve as resolve34 } from "path";
57601
- import { homedir as homedir5 } from "os";
58785
+ import { join as join24, resolve as resolve35 } from "path";
58786
+ import { homedir as homedir7 } from "os";
57602
58787
  import { parse as parseYaml14 } from "yaml";
57603
- var CACHE_BASE = join24(homedir5(), ".promptscript", "cache");
58788
+ var CACHE_BASE = join24(homedir7(), ".promptscript", "cache");
57604
58789
  var LOCKFILE_PATH2 = "promptscript.lock";
57605
58790
  var VENDOR_DIR2 = ".promptscript/vendor";
57606
58791
  function loadLockfile2() {
@@ -57609,7 +58794,7 @@ function loadLockfile2() {
57609
58794
  }
57610
58795
  const parsed = parseYaml14(readFileSync5(LOCKFILE_PATH2, "utf-8"), { maxAliasCount: 100 });
57611
58796
  if (!isValidLockfile(parsed)) {
57612
- throw new Error(`Invalid lockfile: ${resolve34(LOCKFILE_PATH2)}`);
58797
+ throw new Error(`Invalid lockfile: ${resolve35(LOCKFILE_PATH2)}`);
57613
58798
  }
57614
58799
  return parsed;
57615
58800
  }
@@ -57710,14 +58895,14 @@ async function resolveCommand(importPath, options) {
57710
58895
  }
57711
58896
  const reference = parseImportReference(importPath);
57712
58897
  const loader = new FileLoader({
57713
- registryPath: resolve34(config.registry?.path ?? "./registry"),
57714
- localPath: resolve34("./.promptscript"),
58898
+ registryPath: resolve35(config.registry?.path ?? "./registry"),
58899
+ localPath: resolve35("./.promptscript"),
57715
58900
  registries
57716
58901
  });
57717
- const resolvedReference = loader.resolveRef(reference, resolve34(".promptscript/project.prs"));
58902
+ const resolvedReference = loader.resolveRef(reference, resolve35(".promptscript/project.prs"));
57718
58903
  const marker = parseRegistryMarker(resolvedReference);
57719
58904
  if (!marker) {
57720
- const localPath = resolve34(resolvedReference);
58905
+ const localPath = resolve35(resolvedReference);
57721
58906
  printResolution(
57722
58907
  {
57723
58908
  import: importPath,
@@ -57738,7 +58923,7 @@ async function resolveCommand(importPath, options) {
57738
58923
  const cacheVersion = marker.version || "latest";
57739
58924
  const registryCache = new RegistryCache(CACHE_BASE);
57740
58925
  const cachePath = registryCache.getCachePath(marker.repoUrl, cacheVersion);
57741
- const vendorDir = resolve34(VENDOR_DIR2);
58926
+ const vendorDir = resolve35(VENDOR_DIR2);
57742
58927
  const manifest = await loadVendorManifest(vendorDir);
57743
58928
  let repositoryPath = cachePath;
57744
58929
  let source = await registryCache.has(marker.repoUrl, cacheVersion) ? "cache" : "missing";
@@ -57747,7 +58932,9 @@ async function resolveCommand(importPath, options) {
57747
58932
  }
57748
58933
  if (manifest) {
57749
58934
  if (!lockKey || !dependency) {
57750
- throw new Error(`Vendored dependency is not pinned by the lockfile: ${marker.repoUrl}`);
58935
+ throw new Error(
58936
+ `Vendored dependency is not pinned by the lockfile: ${marker.repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
58937
+ );
57751
58938
  }
57752
58939
  const vendoredPath = await resolveVendoredRepository(
57753
58940
  vendorDir,
@@ -57766,7 +58953,10 @@ async function resolveCommand(importPath, options) {
57766
58953
  cachePath,
57767
58954
  ".git",
57768
58955
  dependency.commit,
57769
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
58956
+ /* @__PURE__ */ new Set([".prs-registry-meta.json"]),
58957
+ {
58958
+ allowPartial: true
58959
+ }
57770
58960
  );
57771
58961
  } catch {
57772
58962
  source = "stale cache";
@@ -57811,28 +59001,28 @@ async function resolveCommand(importPath, options) {
57811
59001
 
57812
59002
  // packages/cli/src/commands/inspect.ts
57813
59003
  import { existsSync as existsSync25 } from "fs";
57814
- import { resolve as resolve35, basename as basename16 } from "path";
59004
+ import { resolve as resolve36, basename as basename16 } from "path";
57815
59005
  init_loader2();
57816
59006
  init_console();
57817
59007
  async function inspectCommand(skillName, options) {
57818
59008
  const isJson = options.format === "json";
57819
59009
  const spinner = isJson ? createSpinner("").stop() : createSpinner("Resolving...").start();
57820
59010
  try {
57821
- const projectRoot = options.cwd ? resolve35(options.cwd) : process.cwd();
57822
- const configPath = options.config ? resolve35(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve35(projectRoot, file)).find((file) => existsSync25(file)) : void 0;
59011
+ const projectRoot = options.cwd ? resolve36(options.cwd) : process.cwd();
59012
+ const configPath = options.config ? resolve36(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve36(projectRoot, file)).find((file) => existsSync25(file)) : void 0;
57823
59013
  if (options.cwd && !configPath) {
57824
59014
  throw new Error(`No PromptScript configuration found in ${projectRoot}. Run: prs init`);
57825
59015
  }
57826
59016
  const config = await loadConfig(configPath);
57827
59017
  const registry2 = await resolveRegistryPath(config);
57828
- const localPath = resolve35(projectRoot, ".promptscript");
59018
+ const localPath = resolve36(projectRoot, ".promptscript");
57829
59019
  const resolver = new Resolver({
57830
- registryPath: registry2.isRemote ? registry2.path : resolve35(projectRoot, registry2.path),
59020
+ registryPath: registry2.isRemote ? registry2.path : resolve36(projectRoot, registry2.path),
57831
59021
  localPath,
57832
59022
  projectRoot,
57833
59023
  registries: config.registries
57834
59024
  });
57835
- const entryPath = resolve35(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
59025
+ const entryPath = resolve36(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
57836
59026
  if (!existsSync25(entryPath)) {
57837
59027
  spinner.stop();
57838
59028
  ConsoleOutput.error(`Entry file not found: ${entryPath}`);
@@ -58010,7 +59200,7 @@ function outputJson(skillName, skill, trace, sealed, composedFrom, baseSource) {
58010
59200
  // packages/cli/src/commands/explain.ts
58011
59201
  init_src();
58012
59202
  import { existsSync as existsSync26 } from "fs";
58013
- import { relative as relative18, resolve as resolve36 } from "path";
59203
+ import { relative as relative19, resolve as resolve37 } from "path";
58014
59204
  init_loader2();
58015
59205
  init_console();
58016
59206
  function normalizePath3(path2) {
@@ -58091,7 +59281,7 @@ function findMatches(ast, trace, requestedPath) {
58091
59281
  }
58092
59282
  function sanitizePath(path2, options) {
58093
59283
  if (options.absolutePaths || !path2.startsWith("/")) return path2;
58094
- const relativePath = relative18(options.projectRoot, path2);
59284
+ const relativePath = relative19(options.projectRoot, path2);
58095
59285
  return relativePath || ".";
58096
59286
  }
58097
59287
  function sanitizeText(value, options) {
@@ -58221,24 +59411,24 @@ async function explainCommand(targetPath, options) {
58221
59411
  const isJson = options.format === "json";
58222
59412
  const spinner = isJson ? createSpinner("").stop() : createSpinner("Resolving...").start();
58223
59413
  const outputOptions = {
58224
- projectRoot: resolve36(options.cwd ?? process.cwd()),
59414
+ projectRoot: resolve37(options.cwd ?? process.cwd()),
58225
59415
  absolutePaths: options.absolutePaths === true
58226
59416
  };
58227
59417
  try {
58228
59418
  const projectRoot = outputOptions.projectRoot;
58229
- const configPath = options.config ? resolve36(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve36(projectRoot, file)).find((file) => existsSync26(file)) : void 0;
59419
+ const configPath = options.config ? resolve37(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve37(projectRoot, file)).find((file) => existsSync26(file)) : void 0;
58230
59420
  if (options.cwd && !configPath) {
58231
59421
  throw new Error(`No PromptScript configuration found in ${projectRoot}. Run: prs init`);
58232
59422
  }
58233
59423
  const config = await loadConfig(configPath);
58234
59424
  const registry2 = await resolveRegistryPath(config);
58235
59425
  const resolver = new Resolver({
58236
- registryPath: registry2.isRemote ? registry2.path : resolve36(projectRoot, registry2.path),
58237
- localPath: resolve36(projectRoot, ".promptscript"),
59426
+ registryPath: registry2.isRemote ? registry2.path : resolve37(projectRoot, registry2.path),
59427
+ localPath: resolve37(projectRoot, ".promptscript"),
58238
59428
  projectRoot,
58239
59429
  registries: config.registries
58240
59430
  });
58241
- const entryPath = resolve36(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
59431
+ const entryPath = resolve37(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
58242
59432
  if (!existsSync26(entryPath)) {
58243
59433
  spinner.stop();
58244
59434
  ConsoleOutput.error(`Entry file not found: ${sanitizePath(entryPath, outputOptions)}`);
@@ -58300,7 +59490,7 @@ init_console();
58300
59490
  // packages/cli/src/commands/import.ts
58301
59491
  init_src3();
58302
59492
  init_console();
58303
- import { resolve as resolve37, join as join25, basename as basename17 } from "path";
59493
+ import { resolve as resolve38, join as join25, basename as basename17 } from "path";
58304
59494
  import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync27 } from "fs";
58305
59495
  import { stat as stat2, readdir as readdir8, readFile as readFile27, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
58306
59496
  function parseInstructionFrontmatter(content) {
@@ -58355,7 +59545,7 @@ async function importDirectory(dirPath) {
58355
59545
  return lines.join("\n");
58356
59546
  }
58357
59547
  async function importCommand(file, options) {
58358
- const filepath = resolve37(file);
59548
+ const filepath = resolve38(file);
58359
59549
  const inputStat = await stat2(filepath).catch(() => null);
58360
59550
  if (inputStat?.isDirectory()) {
58361
59551
  try {
@@ -58364,7 +59554,7 @@ async function importCommand(file, options) {
58364
59554
  console.log(prsContent);
58365
59555
  return;
58366
59556
  }
58367
- const outputDir = resolve37(options.output ?? ".promptscript");
59557
+ const outputDir = resolve38(options.output ?? ".promptscript");
58368
59558
  const outputPath = join25(outputDir, "imported.prs");
58369
59559
  if (existsSync27(outputPath) && !options.force) {
58370
59560
  reportOutputConflict(outputPath);
@@ -58398,11 +59588,11 @@ async function importCommand(file, options) {
58398
59588
  console.log(result.prsContent);
58399
59589
  return;
58400
59590
  }
58401
- const outputDir = resolve37(options.output ?? ".promptscript");
59591
+ const outputDir = resolve38(options.output ?? ".promptscript");
58402
59592
  if (!existsSync27(outputDir)) {
58403
59593
  mkdirSync(outputDir, { recursive: true });
58404
59594
  }
58405
- const outputPath = resolve37(outputDir, "imported.prs");
59595
+ const outputPath = resolve38(outputDir, "imported.prs");
58406
59596
  if (existsSync27(outputPath) && !options.force) {
58407
59597
  spinner.stop();
58408
59598
  reportOutputConflict(outputPath);
@@ -58772,11 +59962,11 @@ init_console();
58772
59962
  // packages/cli/src/telemetry/config.ts
58773
59963
  import { existsSync as existsSync30 } from "node:fs";
58774
59964
  import { mkdir as mkdir10, readFile as readFile29, rename as rename2, writeFile as writeFile10 } from "node:fs/promises";
58775
- import { dirname as dirname22, isAbsolute as isAbsolute16, join as join29, resolve as resolve38 } from "node:path";
59965
+ import { dirname as dirname22, isAbsolute as isAbsolute17, join as join29, resolve as resolve39 } from "node:path";
58776
59966
  import { parseDocument as parseDocument2 } from "yaml";
58777
59967
 
58778
59968
  // packages/telemetry/src/config.ts
58779
- import { homedir as homedir6 } from "node:os";
59969
+ import { homedir as homedir8 } from "node:os";
58780
59970
  import { join as join27 } from "node:path";
58781
59971
  var DEFAULT_TELEMETRY_ENDPOINT = "https://telemetry.guziak.net/v1/events";
58782
59972
  function isDisabled(value) {
@@ -58825,7 +60015,7 @@ function resolveTelemetryConfig(input2 = {}) {
58825
60015
  return {
58826
60016
  enabled: vetoes.length === 0,
58827
60017
  endpoint,
58828
- cacheDirectory: input2.cacheDirectory ?? join27(homedir6(), ".promptscript", ".cache"),
60018
+ cacheDirectory: input2.cacheDirectory ?? join27(homedir8(), ".promptscript", ".cache"),
58829
60019
  vetoes
58830
60020
  };
58831
60021
  }
@@ -58911,7 +60101,8 @@ var promptscript_v1_default = {
58911
60101
  "mimo",
58912
60102
  "deep-agents",
58913
60103
  "forgecode",
58914
- "hermes"
60104
+ "hermes",
60105
+ "gitlab-duo"
58915
60106
  ],
58916
60107
  limits: {
58917
60108
  maxEvents: 25,
@@ -59433,7 +60624,7 @@ var FLUSH_BUDGET_MS = 5e3;
59433
60624
  var MAX_BATCHES_PER_FLUSH = 3;
59434
60625
  var COMPLETION_RETRIES = 20;
59435
60626
  function delay(milliseconds) {
59436
- return new Promise((resolve42) => setTimeout(resolve42, milliseconds));
60627
+ return new Promise((resolve43) => setTimeout(resolve43, milliseconds));
59437
60628
  }
59438
60629
  async function persistCompletion(cacheDirectory, remainingRecords, rejectedRecords) {
59439
60630
  for (let attempt = 0; attempt < COMPLETION_RETRIES; attempt += 1) {
@@ -59678,7 +60869,7 @@ function runtimeMetadata(appVersion, input2 = {}) {
59678
60869
  init_loader2();
59679
60870
  init_user_config();
59680
60871
  function resolveFrom(baseDirectory, value) {
59681
- return isAbsolute16(value) ? value : resolve38(baseDirectory, value);
60872
+ return isAbsolute17(value) ? value : resolve39(baseDirectory, value);
59682
60873
  }
59683
60874
  function findProjectConfig(cwd, customPath) {
59684
60875
  if (customPath !== void 0) {
@@ -59723,7 +60914,7 @@ async function readUserTelemetrySetting(configPath) {
59723
60914
  }
59724
60915
  }
59725
60916
  async function resolveCliTelemetryConfig(options = {}) {
59726
- const cwd = options.cwd === void 0 ? process.cwd() : resolve38(process.cwd(), options.cwd);
60917
+ const cwd = options.cwd === void 0 ? process.cwd() : resolve39(process.cwd(), options.cwd);
59727
60918
  const userSetting = await readUserTelemetrySetting(options.userConfigPath ?? USER_CONFIG_PATH);
59728
60919
  const projectPath = findProjectConfig(cwd, options.config);
59729
60920
  const environmentConfig = process.env["PROMPTSCRIPT_CONFIG"];
@@ -59937,13 +61128,41 @@ import {
59937
61128
  chmod,
59938
61129
  open as open4,
59939
61130
  lstat as lstat11,
59940
- stat as stat3
59941
- } from "fs/promises";
59942
- import { resolve as resolve39, join as join30, basename as basename18, dirname as dirname23 } from "path";
59943
- import { existsSync as existsSync31 } from "fs";
59944
- import { tmpdir as tmpdir3 } from "os";
61131
+ stat as stat3,
61132
+ cp,
61133
+ mkdir as mkdir11
61134
+ } from "node:fs/promises";
61135
+ import { resolve as resolve40, join as join30, basename as basename18, dirname as dirname23, relative as relative20, isAbsolute as isAbsolute18, sep as sep13 } from "node:path";
61136
+ import { existsSync as existsSync31 } from "node:fs";
61137
+ import { tmpdir as tmpdir3, homedir as homedir9 } from "node:os";
59945
61138
  import { parse as parseYaml15, stringify as stringifyYaml4 } from "yaml";
59946
61139
  var REMOTE_SOURCE_PATTERN = /^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}\/.+/;
61140
+ var MANAGED_SKILLS_DIR = join30(".promptscript", "skills");
61141
+ var USE_PATH_PATTERN = /^\.{1,2}\/[a-zA-Z0-9_/.-]+$/;
61142
+ function expandHomePath(input2) {
61143
+ if (input2 === "~") return homedir9();
61144
+ if (input2.startsWith("~/") || input2.startsWith("~\\")) {
61145
+ return join30(homedir9(), input2.slice(2));
61146
+ }
61147
+ return input2;
61148
+ }
61149
+ function isLocalSkillSource(source) {
61150
+ const trimmed = source.trim();
61151
+ if (trimmed.length === 0) return false;
61152
+ if (trimmed.startsWith("~")) {
61153
+ return trimmed === "~" || trimmed.slice(1, 2) === "/" || trimmed.slice(1, 2) === "\\";
61154
+ }
61155
+ if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return true;
61156
+ if (/^[a-zA-Z]:[\\/]/.test(trimmed)) return true;
61157
+ if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) return true;
61158
+ if (trimmed.startsWith("../") || trimmed.startsWith("..\\")) return true;
61159
+ if (REMOTE_SOURCE_PATTERN.test(trimmed)) return false;
61160
+ return existsSync31(resolve40(process.cwd(), expandHomePath(trimmed)));
61161
+ }
61162
+ function toUsePath(entryDir, skillDir) {
61163
+ const rel = relative20(entryDir, skillDir).split(sep13).join("/");
61164
+ return rel.startsWith("..") ? rel : `./${rel}`;
61165
+ }
59947
61166
  function parseSkillSource(source) {
59948
61167
  const versionSeparator = source.lastIndexOf("@");
59949
61168
  if (versionSeparator === -1) {
@@ -59962,7 +61181,7 @@ function toDependencyPin(dependency) {
59962
61181
  integrity: dependency.integrity
59963
61182
  };
59964
61183
  }
59965
- function resolveSkillDependency(repoUrl, requestedVersions, existing, forceUpdate, gitUrl, timeout = DEFAULT_GIT_TIMEOUT_MS) {
61184
+ function resolveSkillDependency(repoUrl, requestedVersions, existing, forceUpdate, gitUrl, timeout = void 0) {
59966
61185
  return gitUrl ? resolveRemoteDependency(
59967
61186
  repoUrl,
59968
61187
  requestedVersions,
@@ -60006,32 +61225,32 @@ async function collectProjectRemoteImports(entryFile) {
60006
61225
  const projectRoot = process.cwd();
60007
61226
  const entryFiles = /* @__PURE__ */ new Set();
60008
61227
  if (entryFile) {
60009
- entryFiles.add(resolve39(entryFile));
61228
+ entryFiles.add(resolve40(entryFile));
60010
61229
  }
60011
61230
  if (config) {
60012
61231
  if (config.input?.entry) {
60013
- entryFiles.add(resolve39(projectRoot, config.input.entry));
61232
+ entryFiles.add(resolve40(projectRoot, config.input.entry));
60014
61233
  }
60015
61234
  for (const build of Object.values(config.builds ?? {})) {
60016
61235
  if (build.entry) {
60017
- entryFiles.add(resolve39(projectRoot, build.entry));
61236
+ entryFiles.add(resolve40(projectRoot, build.entry));
60018
61237
  }
60019
61238
  }
60020
- const defaultEntry = resolve39(projectRoot, ".promptscript/project.prs");
61239
+ const defaultEntry = resolve40(projectRoot, ".promptscript/project.prs");
60021
61240
  if (!config.input?.entry && (existsSync31(defaultEntry) || entryFiles.size === 0)) {
60022
61241
  entryFiles.add(defaultEntry);
60023
61242
  }
60024
61243
  } else if (!entryFile) {
60025
- const defaultEntry = resolve39(projectRoot, ".promptscript/project.prs");
61244
+ const defaultEntry = resolve40(projectRoot, ".promptscript/project.prs");
60026
61245
  if (existsSync31(defaultEntry)) {
60027
61246
  entryFiles.add(defaultEntry);
60028
61247
  } else {
60029
- const promptscriptDir = resolve39(projectRoot, ".promptscript");
61248
+ const promptscriptDir = resolve40(projectRoot, ".promptscript");
60030
61249
  if (existsSync31(promptscriptDir)) {
60031
61250
  const entries = await readdir9(promptscriptDir);
60032
61251
  const prsFiles = entries.filter((entry) => entry.endsWith(".prs"));
60033
61252
  if (prsFiles.length === 1) {
60034
- entryFiles.add(resolve39(promptscriptDir, prsFiles[0]));
61253
+ entryFiles.add(resolve40(promptscriptDir, prsFiles[0]));
60035
61254
  }
60036
61255
  }
60037
61256
  }
@@ -60040,7 +61259,7 @@ async function collectProjectRemoteImports(entryFile) {
60040
61259
  for (const projectEntry of entryFiles) {
60041
61260
  imports.push(
60042
61261
  ...await collectRemoteImports(projectEntry, {
60043
- localPath: resolve39(projectRoot, ".promptscript"),
61262
+ localPath: resolve40(projectRoot, ".promptscript"),
60044
61263
  registries: config?.registries,
60045
61264
  strict: true,
60046
61265
  deduplicate: false,
@@ -60086,6 +61305,10 @@ function extractSkillFilePath(source) {
60086
61305
  if (!subPath) return "SKILL.md";
60087
61306
  return subPath.toLowerCase().endsWith(".md") ? subPath : `${subPath}/SKILL.md`;
60088
61307
  }
61308
+ function getRemoteSkillDiagnosticPath(source) {
61309
+ const sourcePath = parseSkillSource(source).path.replace(/^https?:\/\//i, "");
61310
+ return sourcePath.toLowerCase().endsWith(".md") ? sourcePath : `${sourcePath}/SKILL.md`;
61311
+ }
60089
61312
  function deriveSkillFolderName(source) {
60090
61313
  const subPath = extractSubPath(source);
60091
61314
  if (!subPath) return basename18(parseSkillSource(source).path);
@@ -60112,13 +61335,15 @@ async function fetchAndValidateRemoteSkill(source, options) {
60112
61335
  const cloneDir = !skillFilePath.includes("/") ? join30(tmp, repoName) : tmp;
60113
61336
  try {
60114
61337
  const gitRegistry = createGitRegistry({
60115
- url: repoUrl,
60116
- timeout: DEFAULT_GIT_TIMEOUT_MS
61338
+ url: repoUrl
60117
61339
  });
60118
61340
  const cloneRef = options.version === "latest" || /^[0-9a-f]{40}$/i.test(options.version) ? void 0 : options.version;
60119
- await gitRegistry.cloneAtTag(repoUrl, cloneRef, cloneDir);
60120
- await gitRegistry.checkoutCommit(cloneDir, options.commit);
60121
61341
  const subPath = extractSubPath(source);
61342
+ const isMdSource = subPath.toLowerCase().endsWith(".md");
61343
+ const coneCandidate = isMdSource ? subPath.slice(0, subPath.lastIndexOf("/")) : subPath;
61344
+ const sparseCone = coneCandidate.length > 0 ? coneCandidate : void 0;
61345
+ await gitRegistry.cloneAtTag(repoUrl, cloneRef, cloneDir, void 0, sparseCone);
61346
+ await gitRegistry.checkoutCommit(cloneDir, options.commit);
60122
61347
  if (!subPath.toLowerCase().endsWith(".md") && existsSync31(join30(cloneDir, `${subPath}.prs`))) {
60123
61348
  return {
60124
61349
  content: "",
@@ -60159,7 +61384,16 @@ async function fetchAndValidateRemoteSkill(source, options) {
60159
61384
  filePath,
60160
61385
  existingNames: options.existingNames
60161
61386
  });
60162
- return { content, integrity, valid: result.valid, issues: result.issues };
61387
+ const diagnosticPath = getRemoteSkillDiagnosticPath(source);
61388
+ const remoteSkillDir = dirname23(diagnosticPath);
61389
+ const issues = result.issues.map((issue) => {
61390
+ const message = issue.message.replaceAll(dirname23(filePath), remoteSkillDir);
61391
+ if (issue.location) {
61392
+ return { ...issue, message, location: { ...issue.location, file: diagnosticPath } };
61393
+ }
61394
+ return { ...issue, message };
61395
+ });
61396
+ return { content, integrity, valid: result.valid, issues };
60163
61397
  } finally {
60164
61398
  await rm2(tmp, { recursive: true, force: true }).catch(() => {
60165
61399
  });
@@ -60225,7 +61459,7 @@ async function isStaleSkillsAddLock(lockPath) {
60225
61459
  }
60226
61460
  }
60227
61461
  async function acquireSkillsAddLock() {
60228
- const lockPath = resolve39(SKILLS_ADD_LOCK_PATH);
61462
+ const lockPath = resolve40(SKILLS_ADD_LOCK_PATH);
60229
61463
  for (let attempt = 0; attempt < 2; attempt++) {
60230
61464
  try {
60231
61465
  const file = await open4(lockPath, "wx", 384);
@@ -60266,7 +61500,7 @@ async function acquireSkillsAddLock() {
60266
61500
  await rm2(quarantinePath, { force: true });
60267
61501
  }
60268
61502
  }
60269
- throw new Error(`Cannot acquire skills add lock: ${resolve39(SKILLS_ADD_LOCK_PATH)}`);
61503
+ throw new Error(`Cannot acquire skills add lock: ${resolve40(SKILLS_ADD_LOCK_PATH)}`);
60270
61504
  }
60271
61505
  async function releaseSkillsAddLock(lock) {
60272
61506
  try {
@@ -60316,7 +61550,7 @@ function validateRemoteSource(source) {
60316
61550
  }
60317
61551
  async function resolveEntryFile(fileFlag) {
60318
61552
  if (fileFlag) {
60319
- const resolved = resolve39(fileFlag);
61553
+ const resolved = resolve40(fileFlag);
60320
61554
  if (!existsSync31(resolved)) {
60321
61555
  throw new Error(`File not found: ${resolved}`);
60322
61556
  }
@@ -60327,7 +61561,7 @@ async function resolveEntryFile(fileFlag) {
60327
61561
  try {
60328
61562
  const config = await loadConfig();
60329
61563
  if (config.input?.entry) {
60330
- const entryPath = resolve39(config.input.entry);
61564
+ const entryPath = resolve40(config.input.entry);
60331
61565
  if (existsSync31(entryPath)) {
60332
61566
  return entryPath;
60333
61567
  }
@@ -60335,16 +61569,16 @@ async function resolveEntryFile(fileFlag) {
60335
61569
  } catch {
60336
61570
  }
60337
61571
  }
60338
- const promptscriptDir = resolve39(".promptscript");
61572
+ const promptscriptDir = resolve40(".promptscript");
60339
61573
  if (existsSync31(promptscriptDir)) {
60340
61574
  const entries = await readdir9(promptscriptDir);
60341
61575
  const prsFiles = entries.filter((f) => f.endsWith(".prs"));
60342
61576
  if (prsFiles.length === 1) {
60343
- return resolve39(promptscriptDir, prsFiles[0]);
61577
+ return resolve40(promptscriptDir, prsFiles[0]);
60344
61578
  }
60345
61579
  if (prsFiles.length > 1) {
60346
61580
  if (prsFiles.includes("project.prs")) {
60347
- return resolve39(promptscriptDir, "project.prs");
61581
+ return resolve40(promptscriptDir, "project.prs");
60348
61582
  }
60349
61583
  throw new Error(
60350
61584
  `Multiple .prs files found in .promptscript/. Use --file to specify which one.`
@@ -60570,6 +61804,306 @@ async function rollbackSkillsAdd(state) {
60570
61804
  }
60571
61805
  return new Error(rollbackErrors.map((error) => error.message).join("; "));
60572
61806
  }
61807
+ function failLocalSkillStep(spinner, step) {
61808
+ spinner.fail(step.message);
61809
+ if (step.hint) {
61810
+ ConsoleOutput.error(step.hint);
61811
+ }
61812
+ process.exitCode = 1;
61813
+ }
61814
+ async function scanManagedSkillNames(managedRoot, skillDir) {
61815
+ if (!existsSync31(managedRoot)) return [];
61816
+ const entries = await readdir9(managedRoot, { withFileTypes: true });
61817
+ const names = [];
61818
+ for (const entry of entries) {
61819
+ if (!entry.isDirectory()) continue;
61820
+ if (resolve40(managedRoot, entry.name) === resolve40(skillDir)) continue;
61821
+ names.push(entry.name);
61822
+ }
61823
+ return names;
61824
+ }
61825
+ function collectLocalUseNames(lines, skillDir, entryDir) {
61826
+ const names = [];
61827
+ for (const line of lines) {
61828
+ const usePath = extractUsePath(line);
61829
+ if (!usePath || !(usePath.startsWith("./") || usePath.startsWith("../"))) continue;
61830
+ const absUsePath = resolve40(entryDir, usePath);
61831
+ const dir = usePath.endsWith(".md") ? dirname23(absUsePath) : absUsePath;
61832
+ if (dir === resolve40(skillDir)) continue;
61833
+ names.push(basename18(dir));
61834
+ }
61835
+ return names;
61836
+ }
61837
+ async function collectLocalExistingNames(lockfile, lines, skillDir, entryDir) {
61838
+ const names = new Set(collectExistingSkillNames(lockfile));
61839
+ const managedRoot = resolve40(process.cwd(), MANAGED_SKILLS_DIR);
61840
+ for (const name of await scanManagedSkillNames(managedRoot, skillDir)) {
61841
+ names.add(name);
61842
+ }
61843
+ for (const name of collectLocalUseNames(lines, skillDir, entryDir)) {
61844
+ names.add(name);
61845
+ }
61846
+ return names;
61847
+ }
61848
+ async function classifyLocalSkillSource(expanded, copyRequested) {
61849
+ const absSource = resolve40(process.cwd(), expanded);
61850
+ if (!existsSync31(absSource)) {
61851
+ return { ok: false, message: `Path not found: ${expanded}` };
61852
+ }
61853
+ const lstatDetails = await lstat11(absSource);
61854
+ if (lstatDetails.isSymbolicLink() && !copyRequested) {
61855
+ return {
61856
+ ok: false,
61857
+ message: "Symbolic-linked skill directories are not supported",
61858
+ hint: "The resolver skips symlinked directories at compile time. Use --copy to install the skill files."
61859
+ };
61860
+ }
61861
+ const details = lstatDetails.isSymbolicLink() ? await stat3(absSource) : lstatDetails;
61862
+ if (details.isFile()) {
61863
+ if (!absSource.toLowerCase().endsWith(".md")) {
61864
+ return {
61865
+ ok: false,
61866
+ message: `Not a SKILL.md: ${expanded}`,
61867
+ hint: "A local file source must be a SKILL.md, or the directory holding it."
61868
+ };
61869
+ }
61870
+ return { ok: true, value: { skillMdPath: absSource, skillDir: dirname23(absSource) } };
61871
+ }
61872
+ if (details.isDirectory()) {
61873
+ const skillMdPath = join30(absSource, "SKILL.md");
61874
+ if (!existsSync31(skillMdPath)) {
61875
+ return {
61876
+ ok: false,
61877
+ message: `No SKILL.md found in: ${expanded}`,
61878
+ hint: "A skill directory must contain a SKILL.md file."
61879
+ };
61880
+ }
61881
+ return { ok: true, value: { skillMdPath, skillDir: absSource } };
61882
+ }
61883
+ return { ok: false, message: `Unsupported file type: ${expanded}` };
61884
+ }
61885
+ function planLocalUsePath(skillDir, entryDir, copyRequested) {
61886
+ const skillName = basename18(skillDir);
61887
+ const managedDir = resolve40(process.cwd(), MANAGED_SKILLS_DIR, skillName);
61888
+ const sourceIsManaged = resolve40(skillDir) === resolve40(managedDir);
61889
+ const willCopy = copyRequested && !sourceIsManaged;
61890
+ if (willCopy) {
61891
+ return { ok: true, value: { usePath: toUsePath(entryDir, managedDir), managedDir, willCopy } };
61892
+ }
61893
+ const relToRoot = relative20(process.cwd(), skillDir);
61894
+ if (relToRoot.startsWith("..") || isAbsolute18(relToRoot)) {
61895
+ return {
61896
+ ok: false,
61897
+ message: "Skill is outside the project root",
61898
+ hint: `"${skillDir}" cannot be referenced in place because it lies outside the project root. Use --copy to install it into ${MANAGED_SKILLS_DIR}/.`
61899
+ };
61900
+ }
61901
+ const usePath = toUsePath(entryDir, skillDir);
61902
+ if (!USE_PATH_PATTERN.test(usePath)) {
61903
+ return {
61904
+ ok: false,
61905
+ message: `Cannot reference this path in a .prs file: ${usePath}`,
61906
+ hint: "@use paths allow only letters, digits, underscores, hyphens, dots and slashes. Use --copy to install the skill under .promptscript/skills/, or rename the folder."
61907
+ };
61908
+ }
61909
+ return { ok: true, value: { usePath, managedDir, willCopy } };
61910
+ }
61911
+ function validateLocalSkillFrontmatter(options, skillMdPath, skillContent, existingNames, usePath, spinner) {
61912
+ if (options.skipValidation) return "pass";
61913
+ const result = validateSkillFrontmatter(skillContent, { filePath: skillMdPath, existingNames });
61914
+ const hasWarnings = result.issues.some((issue) => issue.severity === "warning");
61915
+ if (!result.valid || options.strict && hasWarnings) {
61916
+ spinner.fail("SKILL.md failed validation");
61917
+ ConsoleOutput.error(usePath);
61918
+ printValidationIssues(result.issues);
61919
+ return "fail";
61920
+ }
61921
+ if (hasWarnings) {
61922
+ spinner.warn("SKILL.md has validation warnings");
61923
+ printValidationIssues(result.issues);
61924
+ spinner.start("Adding skill...");
61925
+ }
61926
+ return "pass";
61927
+ }
61928
+ async function commitLocalSkillAdd(plan, spinner) {
61929
+ spinner.text = plan.willCopy ? "Copying skill..." : "Adding skill...";
61930
+ let replacedBackupDir;
61931
+ let copyStarted = false;
61932
+ try {
61933
+ if (plan.willCopy) {
61934
+ await mkdir11(dirname23(plan.managedDir), { recursive: true });
61935
+ if (existsSync31(plan.managedDir)) {
61936
+ replacedBackupDir = `${plan.managedDir}.replaced-${process.pid}-${Date.now()}`;
61937
+ await rename3(plan.managedDir, replacedBackupDir);
61938
+ }
61939
+ copyStarted = true;
61940
+ await cp(plan.skillDir, plan.managedDir, { recursive: true, dereference: true });
61941
+ }
61942
+ await assertFileUnchanged(plan.entryFile, plan.originalEntryContent);
61943
+ await writeFileAtomically(plan.entryFile, plan.updatedEntryContent);
61944
+ } catch (error) {
61945
+ if (copyStarted) {
61946
+ await rm2(plan.managedDir, { recursive: true, force: true }).catch(() => {
61947
+ });
61948
+ }
61949
+ if (replacedBackupDir) {
61950
+ await rename3(replacedBackupDir, plan.managedDir).catch(() => {
61951
+ });
61952
+ }
61953
+ throw error;
61954
+ }
61955
+ if (replacedBackupDir) {
61956
+ await rm2(replacedBackupDir, { recursive: true, force: true }).catch(() => {
61957
+ });
61958
+ }
61959
+ }
61960
+ function checkSkillNameCollision(skillName, willCopy, force, existingNames) {
61961
+ if (existingNames.has(skillName) && willCopy && !force) {
61962
+ return `Skill "${skillName}" already exists in this project`;
61963
+ }
61964
+ if (force) {
61965
+ existingNames.delete(skillName);
61966
+ }
61967
+ return void 0;
61968
+ }
61969
+ async function prepareLocalSkillAdd(expanded, options) {
61970
+ const parsed = parseSkillSource(expanded);
61971
+ if (parsed.version !== "latest") {
61972
+ return {
61973
+ ok: false,
61974
+ message: "Versioned local paths are not supported",
61975
+ hint: "Local skills are referenced in place, so there is no version to pin."
61976
+ };
61977
+ }
61978
+ const classified = await classifyLocalSkillSource(expanded, options.copy === true);
61979
+ if (!classified.ok) {
61980
+ return classified;
61981
+ }
61982
+ const { skillDir, skillMdPath } = classified.value;
61983
+ const entryFile = await resolveEntryFile(options.file);
61984
+ const originalEntryContent = await readFile30(entryFile, "utf-8");
61985
+ const plan = planLocalUsePath(skillDir, dirname23(entryFile), options.copy === true);
61986
+ if (!plan.ok) {
61987
+ return plan;
61988
+ }
61989
+ return {
61990
+ ok: true,
61991
+ value: {
61992
+ skillMdPath,
61993
+ skillDir,
61994
+ skillName: basename18(skillDir),
61995
+ entryFile,
61996
+ entryDir: dirname23(entryFile),
61997
+ originalEntryContent,
61998
+ usePath: plan.value.usePath,
61999
+ managedDir: plan.value.managedDir,
62000
+ willCopy: plan.value.willCopy
62001
+ }
62002
+ };
62003
+ }
62004
+ function printLocalSkillDryRun(ctx, newLine, spinner) {
62005
+ spinner.succeed("Dry run \u2014 no files modified");
62006
+ ConsoleOutput.newline();
62007
+ ConsoleOutput.dryRun(`Would add to ${ctx.entryFile}:`);
62008
+ ConsoleOutput.dryRun(` ${newLine}`);
62009
+ if (ctx.willCopy) {
62010
+ ConsoleOutput.dryRun(`Would copy ${ctx.skillDir} \u2192 ${ctx.managedDir}`);
62011
+ }
62012
+ ConsoleOutput.dryRun("No lockfile entry (local skills are not pinned)");
62013
+ }
62014
+ function reportLocalSkillAdded(ctx, newLine, spinner) {
62015
+ spinner.succeed("Skill added");
62016
+ ConsoleOutput.newline();
62017
+ ConsoleOutput.success(`${newLine} \u2192 ${ctx.entryFile}`);
62018
+ if (ctx.willCopy) {
62019
+ ConsoleOutput.muted(`Copied into ${MANAGED_SKILLS_DIR}/${ctx.skillName}`);
62020
+ }
62021
+ ConsoleOutput.muted("Local skill - not recorded in the lockfile");
62022
+ }
62023
+ function reportLocalSkillError(error, spinner) {
62024
+ spinner.fail("Failed to add skill");
62025
+ ConsoleOutput.error(error instanceof Error ? error.message : String(error));
62026
+ process.exitCode = 1;
62027
+ }
62028
+ async function addLocalSkill(rawSource, options, spinner) {
62029
+ let transactionLock;
62030
+ try {
62031
+ if (!options.dryRun) {
62032
+ transactionLock = await acquireSkillsAddLock();
62033
+ }
62034
+ spinner.text = "Resolving skill source...";
62035
+ const prepared = await prepareLocalSkillAdd(expandHomePath(rawSource.trim()), options);
62036
+ if (!prepared.ok) {
62037
+ failLocalSkillStep(spinner, prepared);
62038
+ return;
62039
+ }
62040
+ const ctx = prepared.value;
62041
+ const lines = ctx.originalEntryContent.split("\n");
62042
+ const alreadyImported = lines.some((line) => extractUsePath(line) === ctx.usePath);
62043
+ if (alreadyImported) {
62044
+ spinner.warn(`Skill already imported: ${ctx.usePath}`);
62045
+ return;
62046
+ }
62047
+ const lockfile = await loadLockfile3();
62048
+ const existingNames = await collectLocalExistingNames(
62049
+ lockfile,
62050
+ lines,
62051
+ ctx.skillDir,
62052
+ ctx.entryDir
62053
+ );
62054
+ const refusal = checkSkillNameCollision(
62055
+ ctx.skillName,
62056
+ ctx.willCopy,
62057
+ options.force === true,
62058
+ existingNames
62059
+ );
62060
+ if (refusal) {
62061
+ spinner.fail(refusal);
62062
+ ConsoleOutput.error("Use --force to replace it.");
62063
+ process.exitCode = 1;
62064
+ return;
62065
+ }
62066
+ spinner.text = options.skipValidation ? "Reading SKILL.md..." : "Validating SKILL.md...";
62067
+ const skillContent = await readFile30(ctx.skillMdPath, "utf-8");
62068
+ const validationOutcome = validateLocalSkillFrontmatter(
62069
+ options,
62070
+ ctx.skillMdPath,
62071
+ skillContent,
62072
+ existingNames,
62073
+ ctx.usePath,
62074
+ spinner
62075
+ );
62076
+ if (validationOutcome === "fail") {
62077
+ process.exitCode = 1;
62078
+ return;
62079
+ }
62080
+ const insertionPoint = findInsertionPoint(lines);
62081
+ const newLine = `@use ${ctx.usePath}`;
62082
+ lines.splice(insertionPoint, 0, newLine);
62083
+ if (options.dryRun) {
62084
+ printLocalSkillDryRun(ctx, newLine, spinner);
62085
+ return;
62086
+ }
62087
+ await commitLocalSkillAdd(
62088
+ {
62089
+ entryFile: ctx.entryFile,
62090
+ originalEntryContent: ctx.originalEntryContent,
62091
+ updatedEntryContent: lines.join("\n"),
62092
+ willCopy: ctx.willCopy,
62093
+ skillDir: ctx.skillDir,
62094
+ managedDir: ctx.managedDir
62095
+ },
62096
+ spinner
62097
+ );
62098
+ reportLocalSkillAdded(ctx, newLine, spinner);
62099
+ } catch (error) {
62100
+ reportLocalSkillError(error, spinner);
62101
+ } finally {
62102
+ if (transactionLock) {
62103
+ await releaseSkillsAddLock(transactionLock);
62104
+ }
62105
+ }
62106
+ }
60573
62107
  async function skillsAddCommand(rawSource, options) {
60574
62108
  const spinner = createSpinner("Adding skill...").start();
60575
62109
  let rollbackState;
@@ -60583,6 +62117,10 @@ async function skillsAddCommand(rawSource, options) {
60583
62117
  process.exitCode = 1;
60584
62118
  return;
60585
62119
  }
62120
+ if (isLocalSkillSource(rawSource)) {
62121
+ await addLocalSkill(rawSource, options, spinner);
62122
+ return;
62123
+ }
60586
62124
  const sshCloneUrl = extractSshCloneUrl(rawSource);
60587
62125
  const source = normalizeSkillSource(rawSource);
60588
62126
  const sourceError = validateRemoteSource(source);
@@ -61052,6 +62590,8 @@ var __filename4 = fileURLToPath4(import.meta.url);
61052
62590
  var __dirname4 = dirname28(__filename4);
61053
62591
  var CLI_VERSION = getPackageVersion(__dirname3, "./package.json");
61054
62592
  var program = new Command();
62593
+ var RESOURCES_FLAG = "--resources <items>";
62594
+ var RESOURCES_DESCRIPTION = "Compile only these resources (agents, skills, commands, mcp, hooks, plugins, main)";
61055
62595
  program.name("prs").description("PromptScript CLI - Standardize AI instructions").version(CLI_VERSION).option("--verbose", "Enable verbose output").option("--debug", "Enable debug output (includes verbose)").option("--quiet", "Suppress non-error output").hook("preAction", async (thisCommand, actionCommand) => {
61056
62596
  if (actionCommand.name() === "__telemetry-flush" && process.env["PROMPTSCRIPT_TELEMETRY_FLUSH"] === "1") {
61057
62597
  return;
@@ -61088,11 +62628,11 @@ program.command("init").description("Initialize PromptScript in current director
61088
62628
  }
61089
62629
  return initCommand(opts);
61090
62630
  });
61091
- program.command("compile").description("Compile PromptScript to target formats").option("-b, --build <name>", "Named build profile from config.builds").option("--all-builds", "Compile all named build profiles in deterministic order").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-a, --all", "All configured targets").option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option("--dry-run", "Preview changes").option(
62631
+ program.command("compile").description("Compile PromptScript to target formats").option("-b, --build <name>", "Named build profile from config.builds").option("--all-builds", "Compile all named build profiles in deterministic order").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-a, --all", "All configured targets").option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option(RESOURCES_FLAG, RESOURCES_DESCRIPTION, collectResourceKinds, []).option("--dry-run", "Preview changes").option(
61092
62632
  "--no-migrate-factory-hooks",
61093
62633
  "Warn instead of migrating legacy .factory/settings.json hooks"
61094
62634
  ).option("--registry <path>", "Registry path (overrides config)").option("-c, --config <path>", "Path to custom config file").option("--force", "Force overwrite existing files without prompts").option("--strict", "Treat output path conflicts as errors").option("--ignore-hashes", "Skip reference integrity hash verification").option("--cwd <dir>", "Working directory (project root)").action((opts) => compileCommand(opts));
61095
- program.command("build <name>").description("Compile a named build profile").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option("--dry-run", "Preview changes").option(
62635
+ program.command("build <name>").description("Compile a named build profile").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option(RESOURCES_FLAG, RESOURCES_DESCRIPTION, collectResourceKinds, []).option("--dry-run", "Preview changes").option(
61096
62636
  "--no-migrate-factory-hooks",
61097
62637
  "Warn instead of migrating legacy .factory/settings.json hooks"
61098
62638
  ).option("--registry <path>", "Registry path (overrides config)").option("-c, --config <path>", "Path to custom config file").option("--force", "Force overwrite existing files without prompts").option("--strict", "Treat output path conflicts as errors").option("--ignore-hashes", "Skip reference integrity hash verification").option("--cwd <dir>", "Working directory (project root)").action((name, opts) => compileCommand({ ...opts, build: name }));
@@ -61124,7 +62664,10 @@ program.command("resolve <import>").description("Show the lock pin and local sou
61124
62664
  program.command("hook").argument("<action>", "Hook action: pre-edit or post-edit").description("Hook handler invoked by AI tools (not for direct use)").action(hookCommand);
61125
62665
  program.command("hooks").argument("<action>", "Action: install or uninstall").argument("[tool]", "Tool: claude, gemini, copilot, cursor, windsurf, factory, cline").option("--all", "Install/uninstall for all detected tools").description("Manage PromptScript hooks for AI tools").action(hooksCommand);
61126
62666
  var skills = program.command("skills").description("Manage PromptScript skills");
61127
- skills.command("add <source>").description("Add a remote skill to the project").option("-f, --file <file>", "Target .prs file to modify").option("--dry-run", "Preview changes without writing").option("--skip-validation", "Skip SKILL.md frontmatter validation (not recommended)").option("--strict", "Treat validation warnings as errors").action((source, opts) => skillsAddCommand(source, opts));
62667
+ skills.command("add <source>").description("Add a remote or local skill to the project").option("-f, --file <file>", "Target .prs file to modify").option("--dry-run", "Preview changes without writing").option("--skip-validation", "Skip SKILL.md frontmatter validation (not recommended)").option("--strict", "Treat validation warnings as errors").option(
62668
+ "--copy",
62669
+ "Local sources: copy into .promptscript/skills/ instead of referencing in place"
62670
+ ).option("--force", "Local sources: replace an existing skill with the same name").action((source, opts) => skillsAddCommand(source, opts));
61128
62671
  skills.command("remove <name>").description("Remove a skill from the project").option("--dry-run", "Preview changes without writing").action((name, opts) => skillsRemoveCommand(name, opts));
61129
62672
  skills.command("list").description("List skills imported in the current project").action(() => skillsListCommand());
61130
62673
  skills.command("update [name]").description("Update lock entries for markdown-sourced skills").option("--dry-run", "Preview changes without writing").option("--skip-validation", "Skip SKILL.md frontmatter validation (not recommended)").option("--strict", "Treat validation warnings as errors").action((name, opts) => skillsUpdateCommand(name, opts));