@promptscript/cli 1.18.1 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -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
 
@@ -19160,9 +19370,12 @@ var init_base_formatter = __esm({
19160
19370
  return `${skillBasePath}/${skillName}`;
19161
19371
  }
19162
19372
  const normalizedOutputDir = this.normalizeOutputDir(outputDir);
19373
+ if (!normalizedOutputDir) {
19374
+ return `${skillBasePath}/${skillName}`;
19375
+ }
19163
19376
  if (!configuredBaseDir) {
19164
- const targetRoot = defaultSkillBasePath.replace(/\/skills$/, "");
19165
- return `${targetRoot}/${normalizedOutputDir}`;
19377
+ const targetRoot = defaultSkillBasePath.replace(/(?:^|\/)skills$/, "");
19378
+ return targetRoot ? `${targetRoot}/${normalizedOutputDir}` : normalizedOutputDir;
19166
19379
  }
19167
19380
  const outputSegments = normalizedOutputDir.split("/").filter((segment) => segment.length > 0);
19168
19381
  const relativeOutputDir = outputSegments[0] === "skills" ? outputSegments.slice(1).join("/") : outputSegments.join("/");
@@ -19726,7 +19939,7 @@ function getEnabledHookScriptResources(hook) {
19726
19939
  }
19727
19940
  function mapEvent(event, target) {
19728
19941
  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;
19942
+ const map = EVENT_MAPS[target] ?? CODEX_EVENT_MAP;
19730
19943
  return map[event] || null;
19731
19944
  }
19732
19945
  function mapEvents(event) {
@@ -19743,6 +19956,7 @@ function getEffectiveMatcher(hook, target) {
19743
19956
  function convertTimeout(timeoutMs, target) {
19744
19957
  if (target === "gemini") return timeoutMs;
19745
19958
  if (target === "windsurf") return timeoutMs;
19959
+ if (target === "opencode") return timeoutMs;
19746
19960
  if (target === "claude" || target === "cursor" || target === "codex" || target === "factory" || target === "github" || target === "vscode" || target === "grok")
19747
19961
  return Math.ceil(timeoutMs / 1e3);
19748
19962
  return timeoutMs;
@@ -20022,7 +20236,37 @@ function generateCodexHookConfig(hooks) {
20022
20236
  }
20023
20237
  return result;
20024
20238
  }
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;
20239
+ function buildOpenCodeHookRule(hook) {
20240
+ const nativeEvent = mapEvent(hook.event, "opencode");
20241
+ if (nativeEvent !== "tool.execute.before" && nativeEvent !== "tool.execute.after") return null;
20242
+ const command = hook.command ?? (hook.script ? [
20243
+ ...getPosixInterpreter(hook.script.interpreter),
20244
+ getScriptPathFromNativeCwd(hook),
20245
+ ...hook.script.args
20246
+ ] : []);
20247
+ if (command.length === 0) return null;
20248
+ const rule = {
20249
+ id: hook.id.replace(/[^A-Za-z0-9._-]/g, "-"),
20250
+ event: nativeEvent,
20251
+ ...hook.matcher !== void 0 ? { matcher: hook.matcher } : {},
20252
+ command,
20253
+ ...hook.cwd !== void 0 && hook.cwd !== "project" ? { cwd: hook.cwd } : {},
20254
+ ...hook.timeoutMs !== void 0 ? { timeoutMs: hook.timeoutMs } : {}
20255
+ };
20256
+ return rule;
20257
+ }
20258
+ function generateOpenCodePlugin(hooks) {
20259
+ const rules = [];
20260
+ for (const hook of applyHookTargetOverrides(hooks, "opencode")) {
20261
+ if (hook.enabled === false) continue;
20262
+ const rule = buildOpenCodeHookRule(hook);
20263
+ if (rule) rules.push(rule);
20264
+ }
20265
+ if (rules.length === 0) return null;
20266
+ const serializedRules = rules.map((rule) => JSON.stringify(rule));
20267
+ 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;
20268
+ }
20269
+ 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
20270
  var init_hook_adapters = __esm({
20027
20271
  "packages/formatters/src/hook-adapters.ts"() {
20028
20272
  "use strict";
@@ -20034,6 +20278,7 @@ var init_hook_adapters = __esm({
20034
20278
  "git-root": false,
20035
20279
  "native-cwd": true,
20036
20280
  "workspace-cwd": true,
20281
+ "plugin-context": false,
20037
20282
  none: false
20038
20283
  };
20039
20284
  CLAUDE_EVENT_MAP = {
@@ -20125,12 +20370,199 @@ var init_hook_adapters = __esm({
20125
20370
  notification: "",
20126
20371
  stop: "Stop"
20127
20372
  };
20373
+ OPENCODE_EVENT_MAP = {
20374
+ "pre-terminal-command": "",
20375
+ "pre-tool-use": "tool.execute.before",
20376
+ "post-tool-use": "tool.execute.after",
20377
+ "session-start": "",
20378
+ setup: "",
20379
+ "subagent-start": "",
20380
+ notification: "",
20381
+ stop: ""
20382
+ };
20383
+ EVENT_MAPS = {
20384
+ claude: CLAUDE_EVENT_MAP,
20385
+ cursor: CURSOR_EVENT_MAP,
20386
+ factory: FACTORY_EVENT_MAP,
20387
+ github: GITHUB_EVENT_MAP,
20388
+ gemini: GEMINI_EVENT_MAP,
20389
+ grok: GROK_EVENT_MAP,
20390
+ vscode: VSCODE_EVENT_MAP,
20391
+ opencode: OPENCODE_EVENT_MAP
20392
+ };
20128
20393
  GITHUB_MATCHER_EVENTS = /* @__PURE__ */ new Set([
20129
20394
  "pre-tool-use",
20130
20395
  "post-tool-use",
20131
20396
  "subagent-start",
20132
20397
  "notification"
20133
20398
  ]);
20399
+ OPENCODE_PLUGIN_PATH = ".opencode/plugins/promptscript.ts";
20400
+ OPENCODE_PLUGIN_RUNTIME = `
20401
+ const PAYLOAD_LIMIT_BYTES = 32768;
20402
+ const DEFAULT_TIMEOUT_MS = 30000;
20403
+ const KILL_GRACE_MS = 1000;
20404
+
20405
+ interface OpenCodePluginContext {
20406
+ directory: string;
20407
+ worktree?: string;
20408
+ }
20409
+
20410
+ interface OpenCodeToolInput {
20411
+ tool?: string;
20412
+ sessionID?: string;
20413
+ callID?: string;
20414
+ args?: unknown;
20415
+ }
20416
+
20417
+ interface OpenCodeToolOutput {
20418
+ args?: unknown;
20419
+ }
20420
+
20421
+ interface OpenCodeToolResult {
20422
+ title?: unknown;
20423
+ output?: unknown;
20424
+ metadata?: unknown;
20425
+ }
20426
+
20427
+ interface OpenCodeCompiledRule {
20428
+ rule: OpenCodeHookRule;
20429
+ matcher: RegExp | null;
20430
+ }
20431
+
20432
+ function safeStringify(value: unknown): string {
20433
+ try {
20434
+ return JSON.stringify(value);
20435
+ } catch {
20436
+ return '{}';
20437
+ }
20438
+ }
20439
+
20440
+ function payloadByteLength(value: string): number {
20441
+ return new TextEncoder().encode(value).byteLength;
20442
+ }
20443
+
20444
+ // Keep the payload bounded: drop oversized tool arguments first, then the
20445
+ // result object, so the command always receives parseable JSON.
20446
+ function boundedPayload(payload: Record<string, unknown>): string {
20447
+ const full = safeStringify(payload);
20448
+ if (payloadByteLength(full) <= PAYLOAD_LIMIT_BYTES) return full;
20449
+ const withoutArgs = { ...payload, args: '[truncated]' };
20450
+ const trimmed = safeStringify(withoutArgs);
20451
+ if (payloadByteLength(trimmed) <= PAYLOAD_LIMIT_BYTES) return trimmed;
20452
+ const withoutResult = safeStringify({ ...withoutArgs, result: '[truncated]' });
20453
+ if (payloadByteLength(withoutResult) <= PAYLOAD_LIMIT_BYTES) return withoutResult;
20454
+ return '{"target":"opencode","args":"[truncated]","result":"[truncated]"}';
20455
+ }
20456
+
20457
+ function buildPayload(
20458
+ rule: OpenCodeHookRule,
20459
+ input: OpenCodeToolInput,
20460
+ args: unknown,
20461
+ result?: unknown
20462
+ ): Record<string, unknown> {
20463
+ const payload: Record<string, unknown> = {
20464
+ target: 'opencode',
20465
+ hook: rule.id,
20466
+ event: rule.event === 'tool.execute.before' ? 'pre-tool-use' : 'post-tool-use',
20467
+ tool: input.tool,
20468
+ args,
20469
+ sessionID: input.sessionID,
20470
+ callID: input.callID,
20471
+ timestamp: new Date().toISOString()
20472
+ };
20473
+ if (result !== undefined) payload.result = result;
20474
+ return payload;
20475
+ }
20476
+
20477
+ // Hooks observe tool execution asynchronously. Failures are logged, and every
20478
+ // process gets a bounded lifetime so a broken hook cannot disrupt the session.
20479
+ async function runRule(
20480
+ rule: OpenCodeHookRule,
20481
+ projectRoot: string,
20482
+ payload: string
20483
+ ): Promise<void> {
20484
+ try {
20485
+ const cwd = rule.cwd ? projectRoot + '/' + rule.cwd : projectRoot;
20486
+ const proc = Bun.spawn({
20487
+ cmd: rule.command,
20488
+ cwd,
20489
+ stdin: 'pipe',
20490
+ stdout: 'inherit',
20491
+ stderr: 'inherit'
20492
+ });
20493
+ const timeoutMs = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20494
+ let graceTimer: ReturnType<typeof setTimeout> | undefined;
20495
+ const timer = setTimeout(() => {
20496
+ proc.kill('SIGTERM');
20497
+ graceTimer = setTimeout(() => proc.kill('SIGKILL'), KILL_GRACE_MS);
20498
+ }, timeoutMs);
20499
+ try {
20500
+ proc.stdin.write(payload);
20501
+ proc.stdin.end();
20502
+ const exitCode = await proc.exited;
20503
+ if (exitCode !== 0) {
20504
+ console.error(
20505
+ '[promptscript] opencode hook "' + rule.id + '" exited with code ' + exitCode
20506
+ );
20507
+ }
20508
+ } finally {
20509
+ clearTimeout(timer);
20510
+ if (graceTimer !== undefined) clearTimeout(graceTimer);
20511
+ }
20512
+ } catch (error: unknown) {
20513
+ console.error('[promptscript] opencode hook "' + rule.id + '" failed:', error);
20514
+ }
20515
+ }
20516
+
20517
+ export const PromptScriptHooks = async (context: OpenCodePluginContext) => {
20518
+ const projectRoot = context.worktree || context.directory;
20519
+ const compiled: OpenCodeCompiledRule[] = [];
20520
+ for (const rule of HOOK_RULES) {
20521
+ if (rule.matcher === undefined) {
20522
+ compiled.push({ rule, matcher: null });
20523
+ continue;
20524
+ }
20525
+ try {
20526
+ compiled.push({ rule, matcher: new RegExp(rule.matcher) });
20527
+ } catch (error: unknown) {
20528
+ console.error(
20529
+ '[promptscript] opencode hook "' + rule.id + '" has an invalid matcher:',
20530
+ rule.matcher
20531
+ );
20532
+ }
20533
+ }
20534
+
20535
+ return {
20536
+ 'tool.execute.before': async (input: OpenCodeToolInput, output: OpenCodeToolOutput) => {
20537
+ for (const entry of compiled) {
20538
+ if (entry.rule.event !== 'tool.execute.before') continue;
20539
+ if (entry.matcher !== null && !entry.matcher.test(String(input.tool))) continue;
20540
+ void runRule(
20541
+ entry.rule,
20542
+ projectRoot,
20543
+ boundedPayload(buildPayload(entry.rule, input, output.args))
20544
+ );
20545
+ }
20546
+ },
20547
+ 'tool.execute.after': async (input: OpenCodeToolInput, output: OpenCodeToolResult) => {
20548
+ for (const entry of compiled) {
20549
+ if (entry.rule.event !== 'tool.execute.after') continue;
20550
+ if (entry.matcher !== null && !entry.matcher.test(String(input.tool))) continue;
20551
+ const result = {
20552
+ title: output.title,
20553
+ output: output.output,
20554
+ metadata: output.metadata
20555
+ };
20556
+ void runRule(
20557
+ entry.rule,
20558
+ projectRoot,
20559
+ boundedPayload(buildPayload(entry.rule, input, input.args, result))
20560
+ );
20561
+ }
20562
+ }
20563
+ };
20564
+ };
20565
+ `;
20134
20566
  }
20135
20567
  });
20136
20568
 
@@ -20181,6 +20613,108 @@ var init_hook_capability_warnings = __esm({
20181
20613
  }
20182
20614
  });
20183
20615
 
20616
+ // packages/formatters/src/agent-capability-warnings.ts
20617
+ function collectAgentEntries(ast) {
20618
+ const agentsBlock = ast.blocks.find((block) => block.name === "agents");
20619
+ if (!agentsBlock) return null;
20620
+ const entries = [];
20621
+ if (agentsBlock.content.type === "ObjectContent" || agentsBlock.content.type === "MixedContent") {
20622
+ for (const [name, value] of Object.entries(agentsBlock.content.properties)) {
20623
+ if (value && typeof value === "object" && !Array.isArray(value)) {
20624
+ const fields = Object.fromEntries(
20625
+ Object.entries(value).filter(([key]) => !key.startsWith("__"))
20626
+ );
20627
+ entries.push({ name, fields });
20628
+ }
20629
+ }
20630
+ }
20631
+ return { location: agentsBlock.loc, entries };
20632
+ }
20633
+ function describeFieldSupport(field) {
20634
+ const support = listAgentFieldSupportTargets(field);
20635
+ const parts = [];
20636
+ if (support.emitted.length > 0) {
20637
+ parts.push(`emitted by ${support.emitted.join(", ")}`);
20638
+ }
20639
+ if (support.transformed.length > 0) {
20640
+ parts.push(`transformed by ${support.transformed.join(", ")}`);
20641
+ }
20642
+ return parts.length > 0 ? `Field support: ${parts.join("; ")}.` : "";
20643
+ }
20644
+ function describeBlockOmission(agentsResource, target, version, location, options) {
20645
+ if (!agentsResource) {
20646
+ if (options?.blockWarningHandled) return [];
20647
+ return [
20648
+ {
20649
+ code: AGENT_COMPATIBILITY_CODE,
20650
+ ruleName: "agent-compatibility",
20651
+ message: `Target "${target}" has no native agent output and will omit @agents.`,
20652
+ suggestion: "Compile a target with agent support or move agent guidance into instruction blocks.",
20653
+ location
20654
+ }
20655
+ ];
20656
+ }
20657
+ return [
20658
+ {
20659
+ code: AGENT_COMPATIBILITY_CODE,
20660
+ ruleName: "agent-compatibility",
20661
+ message: `Target "${target}" version "${version}" cannot emit @agents and will omit it.`,
20662
+ suggestion: `Use a version that emits agents: ${agentsResource.versions.join(", ")}.`,
20663
+ location
20664
+ }
20665
+ ];
20666
+ }
20667
+ function describeFieldLoss(agentName, field, target, location) {
20668
+ if (field === "description" || field === "content") return void 0;
20669
+ const isCanonical = CANONICAL_AGENT_FIELDS.includes(field);
20670
+ const status = isCanonical ? getAgentFieldStatus(target, field) : "not-supported";
20671
+ if (status !== "not-supported") return void 0;
20672
+ return {
20673
+ code: AGENT_COMPATIBILITY_CODE,
20674
+ ruleName: "agent-compatibility",
20675
+ message: `Agent "${agentName}": field "${field}" is not supported by target "${target}" and will be omitted.`,
20676
+ suggestion: isCanonical ? describeFieldSupport(field) : "Not a canonical @agents field; remove it or model it through a supported block.",
20677
+ location
20678
+ };
20679
+ }
20680
+ function collectEntryFieldWarnings(entry, target, location) {
20681
+ const warnings = [];
20682
+ for (const field of Object.keys(entry.fields)) {
20683
+ const warning = describeFieldLoss(entry.name, field, target, location);
20684
+ if (warning) warnings.push(warning);
20685
+ }
20686
+ return warnings;
20687
+ }
20688
+ function getAgentCapabilityWarnings(ast, target, version, options) {
20689
+ const agentsBlock = collectAgentEntries(ast);
20690
+ if (!agentsBlock || !isKnownTarget(target)) return [];
20691
+ const agentsResource = TARGET_CAPABILITIES[target].resources.find(
20692
+ (resource) => resource.kind === "agents"
20693
+ );
20694
+ if (!agentsResource?.versions.includes(version)) {
20695
+ return describeBlockOmission(agentsResource, target, version, agentsBlock.location, options);
20696
+ }
20697
+ return agentsBlock.entries.flatMap(
20698
+ (entry) => collectEntryFieldWarnings(entry, target, agentsBlock.location)
20699
+ );
20700
+ }
20701
+ function appendAgentCapabilityWarnings(output, ast, target, version, options) {
20702
+ const warnings = getAgentCapabilityWarnings(ast, target, version, options);
20703
+ if (warnings.length === 0) return output;
20704
+ return {
20705
+ ...output,
20706
+ warnings: [...output.warnings ?? [], ...warnings]
20707
+ };
20708
+ }
20709
+ var AGENT_COMPATIBILITY_CODE;
20710
+ var init_agent_capability_warnings = __esm({
20711
+ "packages/formatters/src/agent-capability-warnings.ts"() {
20712
+ "use strict";
20713
+ init_src();
20714
+ AGENT_COMPATIBILITY_CODE = "PS4003";
20715
+ }
20716
+ });
20717
+
20184
20718
  // packages/formatters/src/mcp-helpers.ts
20185
20719
  function findMcpServersBlock(ast) {
20186
20720
  return ast.blocks.find((b) => b.name === "mcpServers");
@@ -20410,6 +20944,7 @@ var init_markdown_instruction_formatter = __esm({
20410
20944
  init_base_formatter();
20411
20945
  init_hook_adapters();
20412
20946
  init_hook_capability_warnings();
20947
+ init_agent_capability_warnings();
20413
20948
  init_mcp_helpers();
20414
20949
  init_section_title_resolver();
20415
20950
  MarkdownInstructionFormatter = class extends BaseFormatter {
@@ -20428,7 +20963,7 @@ var init_markdown_instruction_formatter = __esm({
20428
20963
  }
20429
20964
  getSkillBasePath() {
20430
20965
  if (!this.config.hasSkills) return null;
20431
- return `${this.config.dotDir}/skills`;
20966
+ return this.config.skillsDir ?? `${this.config.dotDir}/skills`;
20432
20967
  }
20433
20968
  getSkillFileName() {
20434
20969
  if (!this.config.hasSkills) return null;
@@ -20454,7 +20989,10 @@ var init_markdown_instruction_formatter = __esm({
20454
20989
  hookWarnings.length > 0 || !hasEnabledHooks ? /* @__PURE__ */ new Set(["hooks"]) : void 0
20455
20990
  );
20456
20991
  const warnedOutput = unsupportedWarnings.length > 0 ? { ...output, warnings: [...output.warnings ?? [], ...unsupportedWarnings] } : output;
20457
- return appendTargetHookCapabilityWarnings(warnedOutput, ast, this.name, version);
20992
+ const agentOutput = appendAgentCapabilityWarnings(warnedOutput, ast, this.name, version, {
20993
+ blockWarningHandled: (this.config.unsupportedBlocks ?? []).includes("agents")
20994
+ });
20995
+ return appendTargetHookCapabilityWarnings(agentOutput, ast, this.name, version);
20458
20996
  }
20459
20997
  hasEnabledHooks(ast) {
20460
20998
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
@@ -21114,24 +21652,28 @@ ${list}` : list;
21114
21652
  });
21115
21653
 
21116
21654
  // packages/formatters/src/create-simple-formatter.ts
21117
- function buildVersions(outputPath, dotDir, hasSkills, hasAgents, hasCommands, skillFileName, mcpConfigPath) {
21655
+ function buildVersions(inputs) {
21656
+ const { outputPath, dotDir, hasSkills, hasAgents, hasCommands } = inputs;
21657
+ const { skillFileName, skillsInMultifile, mcpConfigPath, skillsDir } = inputs;
21118
21658
  const isNested = outputPath.startsWith(dotDir + "/");
21119
21659
  const simpleDesc = `Single ${outputPath} file`;
21120
- const skillPath = `${dotDir}/skills/<name>/${skillFileName}`;
21660
+ const defaultSkillsDir = `${dotDir}/skills`;
21661
+ const resolvedSkillsDir = skillsDir ?? defaultSkillsDir;
21662
+ const skillPath = `${resolvedSkillsDir}/<name>/${skillFileName}`;
21121
21663
  const commandPath = `${dotDir}/commands/<name>.md`;
21122
21664
  const agentPath = `${dotDir}/agents/<name>.md`;
21123
- const describeAdditionalFiles = (version, paths, skillsInFullMode2) => {
21124
- if (paths.length === 0 && skillsInFullMode2) {
21665
+ const skillsFullModeOnly = hasSkills && !skillsInMultifile;
21666
+ const describeAdditionalFiles = (version, paths, fullModeSkillsOnly) => {
21667
+ if (paths.length === 0 && fullModeSkillsOnly) {
21125
21668
  return `Single ${outputPath} file (skills via full mode)`;
21126
21669
  }
21127
21670
  if (paths.length === 0) return `Single ${outputPath} file`;
21128
21671
  const prefix = !isNested && version === "full" && hasSkills ? "Multifile" : outputPath;
21129
- const suffix = skillsInFullMode2 ? " (skills via full mode)" : "";
21672
+ const suffix = fullModeSkillsOnly ? " (skills via full mode)" : "";
21130
21673
  return `${prefix} + ${paths.join(" + ")}${suffix}`;
21131
21674
  };
21132
21675
  const multifilePaths = [];
21133
- const skillsInFullMode = hasSkills && isNested;
21134
- if (hasSkills && !skillsInFullMode) multifilePaths.push(skillPath);
21676
+ if (hasSkills && skillsInMultifile) multifilePaths.push(skillPath);
21135
21677
  if (hasCommands) multifilePaths.push(commandPath);
21136
21678
  if (mcpConfigPath) multifilePaths.push(mcpConfigPath);
21137
21679
  const fullPaths = [];
@@ -21139,7 +21681,7 @@ function buildVersions(outputPath, dotDir, hasSkills, hasAgents, hasCommands, sk
21139
21681
  if (hasCommands) fullPaths.push(commandPath);
21140
21682
  if (hasAgents) fullPaths.push(agentPath);
21141
21683
  if (mcpConfigPath) fullPaths.push(mcpConfigPath);
21142
- const multifileDesc = describeAdditionalFiles("multifile", multifilePaths, skillsInFullMode);
21684
+ const multifileDesc = describeAdditionalFiles("multifile", multifilePaths, skillsFullModeOnly);
21143
21685
  const fullDesc = describeAdditionalFiles("full", fullPaths, false);
21144
21686
  return {
21145
21687
  simple: { name: "simple", description: simpleDesc, outputPath },
@@ -21157,20 +21699,24 @@ function createSimpleMarkdownFormatter(opts) {
21157
21699
  hasAgents = false,
21158
21700
  hasCommands = false,
21159
21701
  hasSkills = true,
21702
+ skillsInMultifile = false,
21160
21703
  skillFileName = "SKILL.md",
21161
21704
  mcpConfigPath,
21162
21705
  mcpConfigFormat,
21163
- unsupportedBlocks
21706
+ unsupportedBlocks,
21707
+ skillsDir
21164
21708
  } = opts;
21165
- const versions = buildVersions(
21709
+ const versions = buildVersions({
21166
21710
  outputPath,
21167
21711
  dotDir,
21168
21712
  hasSkills,
21169
21713
  hasAgents,
21170
21714
  hasCommands,
21715
+ skillsInMultifile,
21171
21716
  skillFileName,
21172
- mcpConfigPath
21173
- );
21717
+ ...mcpConfigPath ? { mcpConfigPath } : {},
21718
+ ...skillsDir ? { skillsDir } : {}
21719
+ });
21174
21720
  class SimpleFormatter extends MarkdownInstructionFormatter {
21175
21721
  constructor() {
21176
21722
  super({
@@ -21184,6 +21730,8 @@ function createSimpleMarkdownFormatter(opts) {
21184
21730
  hasAgents,
21185
21731
  hasCommands,
21186
21732
  hasSkills,
21733
+ skillsInMultifile,
21734
+ ...skillsDir ? { skillsDir } : {},
21187
21735
  ...mcpConfigPath ? { mcpConfigPath } : {},
21188
21736
  ...mcpConfigFormat ? { mcpConfigFormat } : {},
21189
21737
  ...unsupportedBlocks ? { unsupportedBlocks } : {}
@@ -21703,6 +22251,7 @@ var init_github2 = __esm({
21703
22251
  init_mcp_helpers();
21704
22252
  init_hook_adapters();
21705
22253
  init_hook_capability_warnings();
22254
+ init_agent_capability_warnings();
21706
22255
  init_section_title_resolver();
21707
22256
  GITHUB_VERSIONS = {
21708
22257
  simple: {
@@ -21797,6 +22346,7 @@ var init_github2 = __esm({
21797
22346
  output = this.formatSimple(ast, options);
21798
22347
  }
21799
22348
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
22349
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
21800
22350
  const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
21801
22351
  const hooks = hooksBlock ? extractHooks(hooksBlock) : [];
21802
22352
  const hookWarnings = hooksBlock && version !== "simple" ? [
@@ -22868,6 +23418,7 @@ var init_claude2 = __esm({
22868
23418
  init_base_formatter();
22869
23419
  init_hook_adapters();
22870
23420
  init_hook_capability_warnings();
23421
+ init_agent_capability_warnings();
22871
23422
  init_mcp_helpers();
22872
23423
  init_section_title_resolver();
22873
23424
  CLAUDE_VERSIONS = {
@@ -22918,6 +23469,7 @@ var init_claude2 = __esm({
22918
23469
  output = this.formatSimple(ast, options);
22919
23470
  }
22920
23471
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
23472
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
22921
23473
  output = {
22922
23474
  ...output,
22923
23475
  managedOutputFiles: [
@@ -23964,6 +24516,7 @@ var init_cursor2 = __esm({
23964
24516
  init_glob_categorizer();
23965
24517
  init_hook_adapters();
23966
24518
  init_hook_capability_warnings();
24519
+ init_agent_capability_warnings();
23967
24520
  init_mcp_helpers();
23968
24521
  init_plugin_helpers();
23969
24522
  init_section_title_resolver();
@@ -24052,10 +24605,11 @@ var init_cursor2 = __esm({
24052
24605
  const hooks = hooksBlock ? extractHooks(hooksBlock) : [];
24053
24606
  const effectiveHooks = applyHookTargetOverrides(hooks, "cursor");
24054
24607
  if (hooksBlock && effectiveHooks.some((hook) => hook.enabled !== false)) {
24608
+ const withAgentWarnings = appendAgentCapabilityWarnings(output, ast, this.name, version);
24055
24609
  return {
24056
- ...output,
24610
+ ...withAgentWarnings,
24057
24611
  warnings: [
24058
- ...output.warnings ?? [],
24612
+ ...withAgentWarnings.warnings ?? [],
24059
24613
  {
24060
24614
  code: "PS4002",
24061
24615
  message: `Cursor ${version} mode cannot emit @hooks and will omit them.`,
@@ -24070,6 +24624,7 @@ var init_cursor2 = __esm({
24070
24624
  }
24071
24625
  }
24072
24626
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
24627
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
24073
24628
  return {
24074
24629
  ...output,
24075
24630
  managedOutputFiles: [
@@ -26721,20 +27276,115 @@ ${filteredFrontmatter}
26721
27276
  });
26722
27277
 
26723
27278
  // packages/formatters/src/formatters/opencode.ts
26724
- var OpenCodeFormatter, OPENCODE_VERSIONS;
27279
+ var OPENCODE_VERSIONS, OpenCodeFormatter;
26725
27280
  var init_opencode = __esm({
26726
27281
  "packages/formatters/src/formatters/opencode.ts"() {
26727
27282
  "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
- }));
27283
+ init_hook_adapters();
27284
+ init_markdown_instruction_formatter();
27285
+ OPENCODE_VERSIONS = {
27286
+ simple: {
27287
+ name: "simple",
27288
+ description: "Single OPENCODE.md file",
27289
+ outputPath: "OPENCODE.md"
27290
+ },
27291
+ multifile: {
27292
+ name: "multifile",
27293
+ description: "OPENCODE.md + .opencode/commands/<name>.md + .opencode/plugins/promptscript.ts (skills via full mode)",
27294
+ outputPath: "OPENCODE.md"
27295
+ },
27296
+ full: {
27297
+ name: "full",
27298
+ description: "Multifile + .opencode/skills/<name>/SKILL.md + .opencode/commands/<name>.md + .opencode/agents/<name>.md + .opencode/plugins/promptscript.ts",
27299
+ outputPath: "OPENCODE.md"
27300
+ }
27301
+ };
27302
+ OpenCodeFormatter = class extends MarkdownInstructionFormatter {
27303
+ constructor() {
27304
+ super({
27305
+ name: "opencode",
27306
+ outputPath: "OPENCODE.md",
27307
+ description: "OpenCode instructions (Markdown)",
27308
+ defaultConvention: "markdown",
27309
+ mainFileHeader: "# OPENCODE.md",
27310
+ dotDir: ".opencode",
27311
+ skillFileName: "SKILL.md",
27312
+ hasAgents: true,
27313
+ hasCommands: true,
27314
+ hasSkills: true
27315
+ });
27316
+ }
27317
+ /**
27318
+ * Return supported OpenCode output modes.
27319
+ */
27320
+ static getSupportedVersions() {
27321
+ return OPENCODE_VERSIONS;
27322
+ }
27323
+ /**
27324
+ * Format single-file output while retaining stale-plugin cleanup metadata.
27325
+ */
27326
+ formatSimple(ast, options) {
27327
+ const output = super.formatSimple(ast, options);
27328
+ return {
27329
+ ...output,
27330
+ managedOutputFiles: [
27331
+ .../* @__PURE__ */ new Set([...output.managedOutputFiles ?? [], OPENCODE_PLUGIN_PATH])
27332
+ ]
27333
+ };
27334
+ }
27335
+ /**
27336
+ * Format multifile output and attach the generated hook plugin.
27337
+ */
27338
+ formatMultifile(ast, options) {
27339
+ return this.appendHookPlugin(ast, super.formatMultifile(ast, options));
27340
+ }
27341
+ /**
27342
+ * Format full output and attach the generated hook plugin.
27343
+ */
27344
+ formatFull(ast, options) {
27345
+ return this.appendHookPlugin(ast, super.formatFull(ast, options));
27346
+ }
27347
+ /**
27348
+ * Attach the generated OpenCode plugin and its compatibility warnings.
27349
+ *
27350
+ * The plugin path is always registered as managed output so recompiles and
27351
+ * version downgrades remove only PromptScript-owned files; user plugins in
27352
+ * the same directory are never touched.
27353
+ */
27354
+ appendHookPlugin(ast, output) {
27355
+ const managedFiles = [.../* @__PURE__ */ new Set([...output.managedOutputFiles ?? [], OPENCODE_PLUGIN_PATH])];
27356
+ const hooksBlock = ast.blocks.find((block) => block.name === "hooks");
27357
+ if (!hooksBlock) {
27358
+ return { ...output, managedOutputFiles: managedFiles };
27359
+ }
27360
+ const hooks = extractHooks(hooksBlock);
27361
+ const plugin = generateOpenCodePlugin(hooks);
27362
+ const warnings = [
27363
+ ...getHookCompatibilityWarnings(hooks, "opencode"),
27364
+ ...plugin !== null ? [
27365
+ {
27366
+ code: "PS4002",
27367
+ message: "OpenCode tool hooks do not expose model or agent context; the generated payload omits those fields.",
27368
+ suggestion: "Use sessionID and callID for attribution."
27369
+ }
27370
+ ] : []
27371
+ ].map((warning) => ({ ...warning, location: hooksBlock.loc }));
27372
+ return {
27373
+ ...output,
27374
+ ...plugin !== null ? {
27375
+ additionalFiles: [
27376
+ ...output.additionalFiles ?? [],
27377
+ { path: OPENCODE_PLUGIN_PATH, content: plugin }
27378
+ ]
27379
+ } : {},
27380
+ ...warnings.length > 0 ? { warnings: [...output.warnings ?? [], ...warnings] } : {},
27381
+ managedOutputFiles: managedFiles,
27382
+ managedOutputDirectories: [
27383
+ .../* @__PURE__ */ new Set([...output.managedOutputDirectories ?? [], ".opencode/plugins"])
27384
+ ]
27385
+ };
27386
+ }
27387
+ };
26738
27388
  }
26739
27389
  });
26740
27390
 
@@ -27040,20 +27690,54 @@ ${escaped}"""`;
27040
27690
  }
27041
27691
  return '""';
27042
27692
  }
27043
- function serializeAgentToml(agentName, agent) {
27693
+ function mcpServerTomlConfig(server) {
27694
+ const config = {};
27695
+ if (server.command) config["command"] = server.command;
27696
+ if (server.url) config["url"] = server.url;
27697
+ if (server.headers) config["headers"] = server.headers;
27698
+ if (server.env) config["env"] = server.env;
27699
+ return config;
27700
+ }
27701
+ function agentInstructionsLines(content) {
27702
+ if (content !== null && typeof content === "object" && content.type === "TextContent") {
27703
+ const textContent = content.value ?? "";
27704
+ return [`developer_instructions = ${serializeTomlValue(textContent)}`];
27705
+ }
27706
+ if (typeof content === "string") {
27707
+ return [`developer_instructions = ${serializeTomlValue(content)}`];
27708
+ }
27709
+ return [];
27710
+ }
27711
+ function mcpServerTomlTableHeader(serverName) {
27712
+ return /^[A-Za-z0-9_-]+$/.test(serverName) ? `[mcp_servers.${serverName}]` : `[mcp_servers."${escapeTomlString2(serverName)}"]`;
27713
+ }
27714
+ function mcpServerTableLines(serverName, config) {
27715
+ return [
27716
+ mcpServerTomlTableHeader(serverName),
27717
+ ...Object.entries(config).map(([key, val]) => `${key} = ${serializeTomlValue(val)}`)
27718
+ ];
27719
+ }
27720
+ function agentMcpServerLines(mcpServers, resolvedMcpServers) {
27721
+ if (mcpServers !== null && typeof mcpServers === "object" && !Array.isArray(mcpServers)) {
27722
+ const servers = mcpServers;
27723
+ return Object.entries(servers).filter(([, config]) => config !== null && typeof config === "object").flatMap(([name, config]) => mcpServerTableLines(name, config));
27724
+ }
27725
+ if (Array.isArray(mcpServers)) {
27726
+ return mcpServers.filter((serverName) => typeof serverName === "string").flatMap((serverName) => {
27727
+ const config = resolvedMcpServers?.get(serverName);
27728
+ return config ? mcpServerTableLines(serverName, config) : [];
27729
+ });
27730
+ }
27731
+ return [];
27732
+ }
27733
+ function serializeAgentToml(agentName, agent, resolvedMcpServers) {
27044
27734
  const lines = [];
27045
27735
  lines.push(`name = "${escapeTomlString2(agentName)}"`);
27046
27736
  const description = agent["description"];
27047
27737
  if (typeof description === "string") {
27048
27738
  lines.push(`description = "${escapeTomlString2(description)}"`);
27049
27739
  }
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
- }
27740
+ lines.push(...agentInstructionsLines(agent["content"]));
27057
27741
  const model = agent["model"];
27058
27742
  if (typeof model === "string") {
27059
27743
  lines.push(`model = "${escapeTomlString2(model)}"`);
@@ -27071,30 +27755,14 @@ function serializeAgentToml(agentName, agent) {
27071
27755
  const escaped = nicknameCandidates.filter((c) => typeof c === "string").map((c) => `"${escapeTomlString2(c)}"`);
27072
27756
  lines.push(`nickname_candidates = [${escaped.join(", ")}]`);
27073
27757
  }
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
- }
27758
+ const serverTables = agentMcpServerLines(agent["mcpServers"], resolvedMcpServers);
27759
+ if (serverTables.length > 0) {
27760
+ lines.push("", "[mcp_servers]", ...serverTables);
27091
27761
  }
27092
27762
  const skills2 = agent["skills"];
27093
27763
  if (Array.isArray(skills2)) {
27094
27764
  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(", ")}]`);
27765
+ lines.push("", "[skills]", `config = [${skillNames.join(", ")}]`);
27098
27766
  }
27099
27767
  return lines.join("\n") + "\n";
27100
27768
  }
@@ -27111,6 +27779,7 @@ var init_codex = __esm({
27111
27779
  init_markdown_instruction_formatter();
27112
27780
  init_hook_adapters();
27113
27781
  init_hook_capability_warnings();
27782
+ init_agent_capability_warnings();
27114
27783
  init_mcp_helpers();
27115
27784
  init_plugin_helpers();
27116
27785
  CODEX_VERSIONS = {
@@ -27209,12 +27878,13 @@ var init_codex = __esm({
27209
27878
  format(ast, options) {
27210
27879
  const version = this.resolveVersion(options?.version);
27211
27880
  if (version === "simple") {
27212
- const output = appendTargetHookCapabilityWarnings(
27881
+ let output = appendTargetHookCapabilityWarnings(
27213
27882
  this.formatSimple(ast, options),
27214
27883
  ast,
27215
27884
  this.name,
27216
27885
  version
27217
27886
  );
27887
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
27218
27888
  return {
27219
27889
  ...output,
27220
27890
  managedOutputFiles: [
@@ -27267,16 +27937,17 @@ var init_codex = __esm({
27267
27937
  if (!managedDirs.includes(".codex")) managedDirs.push(".codex");
27268
27938
  }
27269
27939
  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
- }
27940
+ const definedMcpServers = mcpServersBlock ? extractMcpServers(mcpServersBlock) : [];
27941
+ if (definedMcpServers.length > 0) {
27942
+ extraFiles.push({
27943
+ path: ".codex/mcp.json",
27944
+ content: serializeMcpServersToJsonString(definedMcpServers)
27945
+ });
27946
+ managedDirs.push(".codex");
27279
27947
  }
27948
+ const resolvedMcpServers = new Map(
27949
+ definedMcpServers.map((server) => [server.name, mcpServerTomlConfig(server)])
27950
+ );
27280
27951
  const pluginsBlock = findPluginsBlock(ast);
27281
27952
  if (pluginsBlock) {
27282
27953
  const plugins = extractPlugins(pluginsBlock);
@@ -27297,7 +27968,7 @@ var init_codex = __esm({
27297
27968
  if (!isValidAgentName(agentName)) continue;
27298
27969
  const agent = agentValue;
27299
27970
  const nativeAgentName = nativeNames.get(agentName) ?? agentName;
27300
- const toml = serializeAgentToml(nativeAgentName, agent);
27971
+ const toml = serializeAgentToml(nativeAgentName, agent, resolvedMcpServers);
27301
27972
  extraFiles.push({
27302
27973
  path: `.codex/agents/${nativeAgentName}.toml`,
27303
27974
  content: toml
@@ -28051,6 +28722,7 @@ var init_grok = __esm({
28051
28722
  init_plugin_helpers();
28052
28723
  init_hook_adapters();
28053
28724
  init_hook_capability_warnings();
28725
+ init_agent_capability_warnings();
28054
28726
  GROK_VERSIONS = {
28055
28727
  simple: {
28056
28728
  name: "simple",
@@ -28097,6 +28769,7 @@ var init_grok = __esm({
28097
28769
  output = this.formatFull(ast, options);
28098
28770
  }
28099
28771
  output = appendTargetHookCapabilityWarnings(output, ast, this.name, version);
28772
+ output = appendAgentCapabilityWarnings(output, ast, this.name, version);
28100
28773
  return {
28101
28774
  ...output,
28102
28775
  managedOutputFiles: [
@@ -28264,6 +28937,25 @@ var init_hermes = __esm({
28264
28937
  }
28265
28938
  });
28266
28939
 
28940
+ // packages/formatters/src/formatters/gitlab-duo.ts
28941
+ var GitlabDuoFormatter, GITLAB_DUO_VERSIONS;
28942
+ var init_gitlab_duo = __esm({
28943
+ "packages/formatters/src/formatters/gitlab-duo.ts"() {
28944
+ "use strict";
28945
+ init_create_simple_formatter();
28946
+ ({ Formatter: GitlabDuoFormatter, VERSIONS: GITLAB_DUO_VERSIONS } = createSimpleMarkdownFormatter({
28947
+ name: "gitlab-duo",
28948
+ outputPath: "AGENTS.md",
28949
+ description: "GitLab Duo instructions (Markdown)",
28950
+ mainFileHeader: "# AGENTS.md",
28951
+ dotDir: ".gitlab-duo",
28952
+ hasSkills: true,
28953
+ skillsInMultifile: false,
28954
+ skillsDir: "skills"
28955
+ }));
28956
+ }
28957
+ });
28958
+
28267
28959
  // packages/formatters/src/formatters/index.ts
28268
28960
  var init_formatters = __esm({
28269
28961
  "packages/formatters/src/formatters/index.ts"() {
@@ -28317,6 +29009,7 @@ var init_formatters = __esm({
28317
29009
  init_deep_agents();
28318
29010
  init_forgecode();
28319
29011
  init_hermes();
29012
+ init_gitlab_duo();
28320
29013
  }
28321
29014
  });
28322
29015
 
@@ -28374,6 +29067,7 @@ var init_builtin_formatters = __esm({
28374
29067
  init_deep_agents();
28375
29068
  init_forgecode();
28376
29069
  init_hermes();
29070
+ init_gitlab_duo();
28377
29071
  BUILTIN_FORMATTERS = {
28378
29072
  // Original 7
28379
29073
  github: GitHubFormatter,
@@ -28429,7 +29123,9 @@ var init_builtin_formatters = __esm({
28429
29123
  mimo: MimoFormatter,
28430
29124
  "deep-agents": DeepAgentsFormatter,
28431
29125
  forgecode: ForgecodeFormatter,
28432
- hermes: HermesFormatter
29126
+ hermes: HermesFormatter,
29127
+ // GitLab Duo (Agent Platform)
29128
+ "gitlab-duo": GitlabDuoFormatter
28433
29129
  };
28434
29130
  }
28435
29131
  });
@@ -30738,6 +31434,7 @@ function userConfigToProjectRegistry(userRegistry) {
30738
31434
  ...userRegistry.git.fallbackUrl ? { fallbackUrl: userRegistry.git.fallbackUrl } : {},
30739
31435
  ...userRegistry.git.ref ? { ref: userRegistry.git.ref } : {},
30740
31436
  ...userRegistry.git.path ? { path: userRegistry.git.path } : {},
31437
+ ...userRegistry.git.timeout !== void 0 ? { timeout: userRegistry.git.timeout } : {},
30741
31438
  ...userRegistry.git.auth ? { auth: userRegistry.git.auth } : {}
30742
31439
  };
30743
31440
  }
@@ -31252,6 +31949,7 @@ async function runInitCommand(options, services) {
31252
31949
  });
31253
31950
  }
31254
31951
  let migrationPrompt;
31952
+ let migrationPromptCopied = false;
31255
31953
  if (migrationMode === "llm") {
31256
31954
  migrationPrompt = prepareLlmMigration(aiToolsDetection.migrationCandidates, options);
31257
31955
  writes.push({ path: ".promptscript/migration-prompt.md", content: migrationPrompt });
@@ -31286,7 +31984,7 @@ async function runInitCommand(options, services) {
31286
31984
  return;
31287
31985
  }
31288
31986
  if (migrationPrompt) {
31289
- deliverMigrationPrompt(migrationPrompt);
31987
+ migrationPromptCopied = deliverMigrationPrompt(migrationPrompt);
31290
31988
  }
31291
31989
  const installedHookTargets = [];
31292
31990
  if (options.hooks !== false) {
@@ -31353,13 +32051,17 @@ async function runInitCommand(options, services) {
31353
32051
  ConsoleOutput.newline();
31354
32052
  ConsoleOutput.stats("Next steps:");
31355
32053
  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}`);
32054
+ ConsoleOutput.info("1. Start your AI agent in this project");
32055
+ if (migrationPromptCopied) {
32056
+ ConsoleOutput.info("2. Paste the migration prompt - it is already in your clipboard");
32057
+ } else {
32058
+ ConsoleOutput.info(
32059
+ "2. Copy the migration prompt from the terminal output and paste it into your agent"
32060
+ );
31360
32061
  }
31361
- ConsoleOutput.muted("2. Review generated .promptscript/project.prs");
31362
- ConsoleOutput.muted("3. Run: prs compile");
32062
+ ConsoleOutput.info("3. Let the agent migrate your instructions");
32063
+ ConsoleOutput.info("4. Review the generated files, then run: prs validate --strict");
32064
+ ConsoleOutput.info("5. Run: prs compile");
31363
32065
  } else if (migrationMode === "static") {
31364
32066
  ConsoleOutput.muted("1. Review imported .promptscript/ files");
31365
32067
  ConsoleOutput.muted("2. Run: prs validate --strict");
@@ -31499,6 +32201,7 @@ function deliverMigrationPrompt(prompt) {
31499
32201
  console.log(prompt);
31500
32202
  }
31501
32203
  ConsoleOutput.info("Saved to .promptscript/migration-prompt.md");
32204
+ return copied;
31502
32205
  }
31503
32206
  function getSkillWrites(targets) {
31504
32207
  const writes = [];
@@ -31841,16 +32544,6 @@ function getTargetSkillDir(target, skillName) {
31841
32544
  const dir = `${basePath}/${skillName}`;
31842
32545
  return { dir, path: `${dir}/${fileName}` };
31843
32546
  }
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
32547
  function generateConfig(config) {
31855
32548
  const syntaxVersion = getLatestSyntaxVersion();
31856
32549
  const output = {
@@ -35272,9 +35965,9 @@ var require_rate_limit = __commonJS({
35272
35965
  throw new Error("The configured rate-limit store does not implement a `read` method, which is required to use `{ increment: false }`");
35273
35966
  }
35274
35967
  try {
35275
- const res = await new Promise((resolve42, reject) => {
35968
+ const res = await new Promise((resolve43, reject) => {
35276
35969
  store[storeMethod](key, (err, res2) => {
35277
- err ? reject(err) : resolve42(res2);
35970
+ err ? reject(err) : resolve43(res2);
35278
35971
  }, timeWindow, max);
35279
35972
  });
35280
35973
  current = res.current;
@@ -35444,16 +36137,16 @@ var init_config2 = __esm({
35444
36137
  });
35445
36138
 
35446
36139
  // 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";
36140
+ import { resolve as resolve41, relative as relative21, isAbsolute as isAbsolute19, dirname as dirname25, basename as basename19 } from "path";
35448
36141
  import { realpath as realpath6 } from "fs/promises";
35449
36142
  async function resolveSafePath(workspace, requestedPath) {
35450
36143
  const decoded = decodeURIComponent(requestedPath);
35451
- if (isAbsolute17(decoded)) {
36144
+ if (isAbsolute19(decoded)) {
35452
36145
  throw new PathTraversalError(requestedPath);
35453
36146
  }
35454
- const resolved = resolve40(workspace, decoded);
35455
- const rel = relative19(workspace, resolved);
35456
- if (rel.startsWith("..") || isAbsolute17(rel)) {
36147
+ const resolved = resolve41(workspace, decoded);
36148
+ const rel = relative21(workspace, resolved);
36149
+ if (rel.startsWith("..") || isAbsolute19(rel)) {
35457
36150
  throw new PathTraversalError(requestedPath);
35458
36151
  }
35459
36152
  let realWorkspace;
@@ -35469,7 +36162,7 @@ async function resolveSafePath(workspace, requestedPath) {
35469
36162
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
35470
36163
  try {
35471
36164
  const realParent = await realpath6(dirname25(resolved));
35472
- real = resolve40(realParent, basename19(resolved));
36165
+ real = resolve41(realParent, basename19(resolved));
35473
36166
  } catch {
35474
36167
  return resolved;
35475
36168
  }
@@ -35477,8 +36170,8 @@ async function resolveSafePath(workspace, requestedPath) {
35477
36170
  throw err;
35478
36171
  }
35479
36172
  }
35480
- const realRel = relative19(realWorkspace, real);
35481
- if (realRel.startsWith("..") || isAbsolute17(realRel)) {
36173
+ const realRel = relative21(realWorkspace, real);
36174
+ if (realRel.startsWith("..") || isAbsolute19(realRel)) {
35482
36175
  throw new PathTraversalError(requestedPath);
35483
36176
  }
35484
36177
  return resolved;
@@ -35497,7 +36190,7 @@ var init_path_guard = __esm({
35497
36190
  });
35498
36191
 
35499
36192
  // 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";
36193
+ import { readFile as readFile32, writeFile as writeFile12, unlink as unlink3, stat as stat4, mkdir as mkdir12 } from "fs/promises";
35501
36194
  import { dirname as dirname26 } from "path";
35502
36195
  import fg from "fast-glob";
35503
36196
  function registerRoutes(app, workspace, readOnly) {
@@ -35558,7 +36251,7 @@ function registerRoutes(app, workspace, readOnly) {
35558
36251
  return reply.status(409).send({ error: "File already exists" });
35559
36252
  } catch {
35560
36253
  }
35561
- await mkdir11(dirname26(resolved), { recursive: true });
36254
+ await mkdir12(dirname26(resolved), { recursive: true });
35562
36255
  await writeFile12(resolved, request.body.content, "utf-8");
35563
36256
  return reply.status(201).send({ path: filePath, status: "created" });
35564
36257
  } catch (err) {
@@ -35596,7 +36289,7 @@ var init_files = __esm({
35596
36289
 
35597
36290
  // packages/server/src/watcher.ts
35598
36291
  import { watch } from "chokidar";
35599
- import { relative as relative20 } from "path";
36292
+ import { relative as relative22 } from "path";
35600
36293
  function isWatchedFile(filePath) {
35601
36294
  const base = filePath.split("/").pop() ?? "";
35602
36295
  if (CONFIG_FILENAMES2.has(base)) return true;
@@ -35612,15 +36305,15 @@ function createFileWatcher(workspace, watchPaths, onEvent) {
35612
36305
  });
35613
36306
  watcher.on("add", (filePath) => {
35614
36307
  if (!ready || !isWatchedFile(filePath)) return;
35615
- onEvent({ type: "file:created", path: relative20(workspace, filePath) });
36308
+ onEvent({ type: "file:created", path: relative22(workspace, filePath) });
35616
36309
  });
35617
36310
  watcher.on("change", (filePath) => {
35618
36311
  if (!ready || !isWatchedFile(filePath)) return;
35619
- onEvent({ type: "file:changed", path: relative20(workspace, filePath) });
36312
+ onEvent({ type: "file:changed", path: relative22(workspace, filePath) });
35620
36313
  });
35621
36314
  watcher.on("unlink", (filePath) => {
35622
36315
  if (!ready || !isWatchedFile(filePath)) return;
35623
- onEvent({ type: "file:deleted", path: relative20(workspace, filePath) });
36316
+ onEvent({ type: "file:deleted", path: relative22(workspace, filePath) });
35624
36317
  });
35625
36318
  return watcher;
35626
36319
  }
@@ -35787,7 +36480,7 @@ var migrate_exports = {};
35787
36480
  __export(migrate_exports, {
35788
36481
  migrateCommand: () => migrateCommand
35789
36482
  });
35790
- import { dirname as dirname27, relative as relative21, resolve as resolve41 } from "path";
36483
+ import { dirname as dirname27, relative as relative23, resolve as resolve42 } from "path";
35791
36484
  import { parse as parseYaml17 } from "yaml";
35792
36485
  async function migrateCommand(options, services = createDefaultServices()) {
35793
36486
  const previousOutputStream = getContext().outputStream;
@@ -36045,7 +36738,7 @@ function extractTargetNames(targets) {
36045
36738
  }
36046
36739
  async function createStaticMigrationWrites(candidates, config, entryPaths, services) {
36047
36740
  const result = await importMultipleFiles(
36048
- candidates.map((candidate) => resolve41(services.cwd, candidate.path)),
36741
+ candidates.map((candidate) => resolve42(services.cwd, candidate.path)),
36049
36742
  {
36050
36743
  projectName: config.id,
36051
36744
  syntaxVersion: config.syntax || getLatestSyntaxVersion()
@@ -36079,7 +36772,7 @@ async function createStaticMigrationWrites(candidates, config, entryPaths, servi
36079
36772
  const migratedProjectPath = ".promptscript/migrated/project.prs";
36080
36773
  writes.push({ path: migratedProjectPath, content: markMigrationOutput(importedProject) });
36081
36774
  for (const entryPath of entryPaths) {
36082
- const useLine = `@use ${toUsePath(entryPath, migratedProjectPath)}`;
36775
+ const useLine = `@use ${toUsePath2(entryPath, migratedProjectPath)}`;
36083
36776
  if (services.fs.existsSync(entryPath)) {
36084
36777
  const existing = await services.fs.readFile(entryPath, "utf-8");
36085
36778
  const hasUseLine = existing.split(/\r?\n/).some((line) => line.trim() === useLine);
@@ -36137,9 +36830,9 @@ function markMigrationOutput(content) {
36137
36830
  return `# promptscript-generated: migration
36138
36831
  ${content}`;
36139
36832
  }
36140
- function toUsePath(entryPath, targetPath) {
36833
+ function toUsePath2(entryPath, targetPath) {
36141
36834
  const withoutExtension = targetPath.replace(/\.prs$/, "");
36142
- const path2 = relative21(dirname27(entryPath), withoutExtension).replace(/\\/g, "/");
36835
+ const path2 = relative23(dirname27(entryPath), withoutExtension).replace(/\\/g, "/");
36143
36836
  return path2.startsWith("./") || path2.startsWith("../") ? path2 : `./${path2}`;
36144
36837
  }
36145
36838
  function validateProjectPath(path2) {
@@ -36204,12 +36897,13 @@ import { fileURLToPath as fileURLToPath4, pathToFileURL } from "url";
36204
36897
  import { dirname as dirname28 } from "path";
36205
36898
 
36206
36899
  // 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";
36900
+ import { resolve as resolve22, isAbsolute as isAbsolute14, relative as relative15 } from "node:path";
36901
+ import { homedir as homedir4 } from "node:os";
36902
+ import { readFile as readFile16 } from "node:fs/promises";
36903
+ import { existsSync as existsSync14, lstatSync as lstatSync2 } from "node:fs";
36210
36904
  import chokidar from "chokidar";
36211
36905
 
36212
- // node_modules/.pnpm/balanced-match@4.0.3/node_modules/balanced-match/dist/esm/index.js
36906
+ // node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js
36213
36907
  var balanced = (a, b, str) => {
36214
36908
  const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
36215
36909
  const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
@@ -38149,6 +38843,14 @@ function findFallbackUrl(repoUrl, registries) {
38149
38843
  }
38150
38844
  return void 0;
38151
38845
  }
38846
+ function findRegistryEntry(repoUrl, registries) {
38847
+ for (const entry of Object.values(registries)) {
38848
+ if (typeof entry !== "string" && entry.url === repoUrl) {
38849
+ return entry;
38850
+ }
38851
+ }
38852
+ return void 0;
38853
+ }
38152
38854
 
38153
38855
  // packages/resolver/src/loader.ts
38154
38856
  var REGISTRY_MARKER_PREFIX = "__registry__:";
@@ -39169,7 +39871,8 @@ function parseSkillMd(content, sourceFile = "<skill>") {
39169
39871
  sourceFile,
39170
39872
  frontmatter.location,
39171
39873
  yamlFrontmatter.fieldLocations,
39172
- yamlFrontmatter.fieldItemLocations
39874
+ yamlFrontmatter.fieldItemLocations,
39875
+ yamlFrontmatter.fieldValueLocations
39173
39876
  );
39174
39877
  const bodyContent = stripLeadingHtmlMarker(frontmatter.body).trim();
39175
39878
  const result = {
@@ -39294,11 +39997,11 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39294
39997
  }
39295
39998
  rejectUnsafeYamlNodes(document.contents, sourceFile, frontmatter);
39296
39999
  enforceFrontmatterLimits(document.contents, sourceFile, frontmatter);
39297
- const { fields: fieldLocations, items: fieldItemLocations } = getTopLevelFieldLocations(
39298
- document.contents,
39299
- sourceFile,
39300
- frontmatter
39301
- );
40000
+ const {
40001
+ fields: fieldLocations,
40002
+ items: fieldItemLocations,
40003
+ values: fieldValueLocations
40004
+ } = getTopLevelFieldLocations(document.contents, sourceFile, frontmatter);
39302
40005
  let value;
39303
40006
  try {
39304
40007
  value = document.toJS({ maxAliasCount: 0 });
@@ -39311,7 +40014,7 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39311
40014
  );
39312
40015
  }
39313
40016
  if (document.contents === null) {
39314
- return { fields: {}, fieldLocations, fieldItemLocations };
40017
+ return { fields: {}, fieldLocations, fieldItemLocations, fieldValueLocations };
39315
40018
  }
39316
40019
  if (!isRecord5(value)) {
39317
40020
  throw frontmatterError(
@@ -39320,7 +40023,7 @@ function parseYamlFrontmatter(frontmatter, sourceFile) {
39320
40023
  frontmatter.location
39321
40024
  );
39322
40025
  }
39323
- return { fields: value, fieldLocations, fieldItemLocations };
40026
+ return { fields: value, fieldLocations, fieldItemLocations, fieldValueLocations };
39324
40027
  }
39325
40028
  function getYamlLocation(frontmatter, sourceFile, offset) {
39326
40029
  const source = frontmatter.raw;
@@ -39339,7 +40042,10 @@ function getYamlLocation(frontmatter, sourceFile, offset) {
39339
40042
  function getTopLevelFieldLocations(node, sourceFile, frontmatter) {
39340
40043
  const fieldLocations = /* @__PURE__ */ new Map();
39341
40044
  const fieldItemLocations = /* @__PURE__ */ new Map();
39342
- if (!isCollection(node)) return { fields: fieldLocations, items: fieldItemLocations };
40045
+ const fieldValueLocations = /* @__PURE__ */ new Map();
40046
+ if (!isCollection(node)) {
40047
+ return { fields: fieldLocations, items: fieldItemLocations, values: fieldValueLocations };
40048
+ }
39343
40049
  for (const item of node.items) {
39344
40050
  if (!isPair(item)) continue;
39345
40051
  const key = getScalarString(item.key);
@@ -39351,9 +40057,21 @@ function getTopLevelFieldLocations(node, sourceFile, frontmatter) {
39351
40057
  key,
39352
40058
  item.value.items.map((value) => getNodeLocation(value, sourceFile, frontmatter))
39353
40059
  );
40060
+ fieldValueLocations.set(key, getNestedFieldLocations(item.value, sourceFile, frontmatter));
39354
40061
  }
39355
40062
  }
39356
- return { fields: fieldLocations, items: fieldItemLocations };
40063
+ return { fields: fieldLocations, items: fieldItemLocations, values: fieldValueLocations };
40064
+ }
40065
+ function getNestedFieldLocations(collection, sourceFile, frontmatter) {
40066
+ const valueLocations = /* @__PURE__ */ new Map();
40067
+ if (!isCollection(collection)) return valueLocations;
40068
+ for (const item of collection.items) {
40069
+ if (!isPair(item)) continue;
40070
+ const key = getScalarString(item.key);
40071
+ if (key === void 0) continue;
40072
+ valueLocations.set(key, getNodeLocation(item.value ?? item.key, sourceFile, frontmatter));
40073
+ }
40074
+ return valueLocations;
39357
40075
  }
39358
40076
  function getScalarString(node) {
39359
40077
  if (!isNode(node) || !("value" in node)) return void 0;
@@ -39468,7 +40186,7 @@ function frontmatterError(message, sourceFile, location) {
39468
40186
  file: sourceFile
39469
40187
  });
39470
40188
  }
39471
- function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocations, fieldItemLocations) {
40189
+ function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocations, fieldItemLocations, fieldValueLocations) {
39472
40190
  const name = readOptionalString(
39473
40191
  fields,
39474
40192
  "name",
@@ -39526,7 +40244,8 @@ function parseFrontmatterFields(fields, sourceFile, fallbackLocation, fieldLocat
39526
40244
  const metadata = parseMetadataField(
39527
40245
  fields,
39528
40246
  sourceFile,
39529
- getFieldLocation("metadata", fieldLocations, fallbackLocation)
40247
+ getFieldLocation("metadata", fieldLocations, fallbackLocation),
40248
+ fieldValueLocations.get("metadata")
39530
40249
  );
39531
40250
  const allowedTools = parseAllowedToolsField(
39532
40251
  fields,
@@ -39789,7 +40508,7 @@ function readOptionalStringFromRecord(record, field, context, sourceFile, locati
39789
40508
  }
39790
40509
  return value;
39791
40510
  }
39792
- function parseMetadataField(fields, sourceFile, location) {
40511
+ function parseMetadataField(fields, sourceFile, location, valueLocations) {
39793
40512
  if (!Object.hasOwn(fields, "metadata")) return void 0;
39794
40513
  const value = fields["metadata"];
39795
40514
  if (!isRecord5(value)) {
@@ -39798,7 +40517,11 @@ function parseMetadataField(fields, sourceFile, location) {
39798
40517
  const metadata = createSafeRecord();
39799
40518
  for (const [key, item] of Object.entries(value)) {
39800
40519
  if (typeof item !== "string") {
39801
- throw frontmatterError(`metadata value "${key}" must be a string`, sourceFile, location);
40520
+ throw frontmatterError(
40521
+ `metadata value "${key}" must be a string; quote scalar values (for example, 16 as "16")`,
40522
+ sourceFile,
40523
+ valueLocations?.get(key) ?? location
40524
+ );
39802
40525
  }
39803
40526
  metadata[key] = item;
39804
40527
  }
@@ -40977,26 +41700,29 @@ var VIRTUAL_LOC = {
40977
41700
  column: 1,
40978
41701
  offset: 0
40979
41702
  };
40980
- function makeObjectContent(properties) {
41703
+ function fileLoc(file) {
41704
+ return { file, line: 1, column: 1, offset: 0 };
41705
+ }
41706
+ function makeObjectContent(properties, file) {
40981
41707
  return {
40982
41708
  type: "ObjectContent",
40983
41709
  properties,
40984
- loc: VIRTUAL_LOC
41710
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
40985
41711
  };
40986
41712
  }
40987
41713
  function makeTextContent(value, file) {
40988
41714
  return {
40989
41715
  type: "TextContent",
40990
41716
  value,
40991
- loc: file ? { file, line: 1, column: 1, offset: 0 } : VIRTUAL_LOC
41717
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
40992
41718
  };
40993
41719
  }
40994
- function makeBlock(name, content) {
41720
+ function makeBlock(name, content, file) {
40995
41721
  return {
40996
41722
  type: "Block",
40997
41723
  name,
40998
41724
  content,
40999
- loc: VIRTUAL_LOC
41725
+ loc: file ? fileLoc(file) : VIRTUAL_LOC
41000
41726
  };
41001
41727
  }
41002
41728
 
@@ -41848,9 +42574,29 @@ var GitCacheManager = class {
41848
42574
  // packages/resolver/src/git-registry.ts
41849
42575
  var TAGS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
41850
42576
  var DEFAULT_GIT_TIMEOUT_MS = 6e4;
42577
+ var GIT_TIMEOUT_ENV_VAR = "PROMPTSCRIPT_GIT_TIMEOUT";
41851
42578
  var SEMVER_TAG_RE = /^v?\d+\.\d+\.\d+/;
42579
+ function envGitTimeout() {
42580
+ const raw = process.env[GIT_TIMEOUT_ENV_VAR];
42581
+ if (raw === void 0 || raw.trim() === "") {
42582
+ return void 0;
42583
+ }
42584
+ const parsed = Number(raw);
42585
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
42586
+ }
41852
42587
  function normalizeGitTimeout(timeout) {
41853
- return timeout !== void 0 && Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_GIT_TIMEOUT_MS;
42588
+ if (timeout !== void 0 && Number.isFinite(timeout) && timeout > 0) {
42589
+ return timeout;
42590
+ }
42591
+ const fromEnv = envGitTimeout();
42592
+ if (fromEnv !== void 0) {
42593
+ return fromEnv;
42594
+ }
42595
+ return DEFAULT_GIT_TIMEOUT_MS;
42596
+ }
42597
+ function isPartialCloneUnsupported(error) {
42598
+ const message = error.message.toLowerCase();
42599
+ 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
42600
  }
41855
42601
  var GitCloneError = class extends Error {
41856
42602
  constructor(message, url, cause) {
@@ -42082,17 +42828,20 @@ var GitRegistry = class {
42082
42828
  * @param tag - Git tag to check out, or undefined for the default branch
42083
42829
  * @param targetDir - Directory to clone into
42084
42830
  * @param fallbackRepoUrl - Optional fallback URL to try on auth failure
42831
+ * @param sparsePath - Optional subdirectory to limit the checkout to.
42832
+ * Uses `--filter=blob:none --sparse` + `sparse-checkout` so only the
42833
+ * blobs for that subdirectory are fetched. Falls back to a plain
42834
+ * shallow clone when the server does not support partial clones.
42085
42835
  */
42086
- async cloneAtTag(repoUrl, tag, targetDir, fallbackRepoUrl) {
42836
+ async cloneAtTag(repoUrl, tag, targetDir, fallbackRepoUrl, sparsePath) {
42087
42837
  if (existsSync8(targetDir)) {
42088
42838
  await fs2.rm(targetDir, { recursive: true, force: true });
42089
42839
  }
42090
42840
  await fs2.mkdir(targetDir, { recursive: true });
42091
42841
  const git = this.createGit();
42092
- const cloneOptions = tag ? ["--depth=1", `--branch=${tag}`, "--single-branch"] : ["--depth=1"];
42093
42842
  const cloneUrl = this.auth ? this.transportUrl : repoUrl;
42094
42843
  try {
42095
- await git.clone(cloneUrl, targetDir, cloneOptions);
42844
+ await this.performClone(git, cloneUrl, targetDir, tag, sparsePath);
42096
42845
  } catch (err) {
42097
42846
  const error = err instanceof Error ? err : new Error(String(err));
42098
42847
  if (isGitTimeoutError(error)) {
@@ -42104,24 +42853,15 @@ var GitRegistry = class {
42104
42853
  if (this.isAccessError(error)) {
42105
42854
  if (fallbackRepoUrl) {
42106
42855
  try {
42107
- await git.clone(fallbackRepoUrl, targetDir, cloneOptions);
42856
+ await this.performClone(git, fallbackRepoUrl, targetDir, tag, sparsePath);
42108
42857
  return;
42109
42858
  } 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}`,
42859
+ throw this.classifyFallbackCloneError(
42860
+ repoUrl,
42123
42861
  fallbackRepoUrl,
42124
- fbError
42862
+ fallbackErr,
42863
+ tag,
42864
+ "clone"
42125
42865
  );
42126
42866
  }
42127
42867
  }
@@ -42134,6 +42874,92 @@ var GitRegistry = class {
42134
42874
  );
42135
42875
  }
42136
42876
  }
42877
+ /**
42878
+ * Clone shallow, optionally partial (`--filter=blob:none --sparse`) with a
42879
+ * sparse-checkout cone of `sparsePath`. Retries without the partial-clone
42880
+ * flags when the server or local Git does not support them.
42881
+ */
42882
+ async performClone(git, url, targetDir, tag, sparsePath) {
42883
+ const tagOptions = tag ? ["--branch=" + tag, "--single-branch"] : [];
42884
+ const baseOptions = ["--depth=1", ...tagOptions];
42885
+ if (!sparsePath) {
42886
+ await git.clone(url, targetDir, baseOptions);
42887
+ return;
42888
+ }
42889
+ try {
42890
+ await git.clone(url, targetDir, [...baseOptions, "--filter=blob:none", "--sparse"]);
42891
+ } catch (err) {
42892
+ const error = err instanceof Error ? err : new Error(String(err));
42893
+ if (!isPartialCloneUnsupported(error)) {
42894
+ throw error;
42895
+ }
42896
+ if (existsSync8(targetDir)) {
42897
+ await fs2.rm(targetDir, { recursive: true, force: true });
42898
+ }
42899
+ await fs2.mkdir(targetDir, { recursive: true });
42900
+ await git.clone(url, targetDir, baseOptions);
42901
+ return;
42902
+ }
42903
+ const repoGit = this.createGit(targetDir);
42904
+ await repoGit.raw(["sparse-checkout", "set", sparsePath]);
42905
+ }
42906
+ /**
42907
+ * Check whether the checkout at `targetDir` uses sparse-checkout.
42908
+ */
42909
+ async isSparseCheckout(targetDir) {
42910
+ const git = this.createGit(targetDir);
42911
+ try {
42912
+ const out = await git.raw(["config", "core.sparseCheckout"]);
42913
+ return out.trim() === "true";
42914
+ } catch {
42915
+ return false;
42916
+ }
42917
+ }
42918
+ /**
42919
+ * Disable sparse-checkout, materializing the full working tree.
42920
+ * Used when a root-level import needs the whole repository but the
42921
+ * cached clone is partial.
42922
+ */
42923
+ async disableSparseCheckout(targetDir) {
42924
+ try {
42925
+ await this.createGit(targetDir).raw(["sparse-checkout", "disable"]);
42926
+ } catch (err) {
42927
+ this.throwIfGitTimeout(err);
42928
+ throw err instanceof Error ? err : new Error(String(err));
42929
+ }
42930
+ }
42931
+ /** Convert a raw Git error into a GitCloneError when it is a timeout. */
42932
+ throwIfGitTimeout(err) {
42933
+ const error = err instanceof Error ? err : new Error(String(err));
42934
+ if (isGitTimeoutError(error)) {
42935
+ throw createGitTimeoutError(this.url, this.timeout, error);
42936
+ }
42937
+ }
42938
+ /**
42939
+ * Add paths to an existing sparse checkout so their blobs are fetched
42940
+ * on demand. Used when a cached partial clone needs a subdirectory that
42941
+ * the original cone did not cover.
42942
+ */
42943
+ async addSparsePaths(targetDir, paths) {
42944
+ const extra = paths.filter((p) => p.length > 0 && p !== ".");
42945
+ if (extra.length === 0) {
42946
+ return;
42947
+ }
42948
+ const git = this.createGit(targetDir);
42949
+ try {
42950
+ await git.raw(["sparse-checkout", "add", ...extra]);
42951
+ } catch (addError) {
42952
+ const original = addError instanceof Error ? addError : new Error(String(addError));
42953
+ try {
42954
+ const listed = await git.raw(["sparse-checkout", "list"]);
42955
+ const current = listed.split("\n").map((line) => line.trim()).filter(Boolean);
42956
+ await git.raw(["sparse-checkout", "set", .../* @__PURE__ */ new Set([...current, ...extra])]);
42957
+ } catch (setErr) {
42958
+ this.throwIfGitTimeout(setErr);
42959
+ throw original;
42960
+ }
42961
+ }
42962
+ }
42137
42963
  /**
42138
42964
  * List all semver tags from a remote repo.
42139
42965
  * Results are cached via RegistryCache to avoid repeated ls-remote calls.
@@ -42199,8 +43025,9 @@ var GitRegistry = class {
42199
43025
  }
42200
43026
  await fs2.mkdir(targetDir, { recursive: true });
42201
43027
  const git = this.createGit();
43028
+ const cloneUrl = this.auth ? this.transportUrl : repoUrl;
42202
43029
  try {
42203
- await this.doCloneSparse(git, repoUrl, ref, targetDir, sparsePath);
43030
+ await this.performClone(git, cloneUrl, targetDir, ref, sparsePath);
42204
43031
  } catch (err) {
42205
43032
  const error = err instanceof Error ? err : new Error(String(err));
42206
43033
  if (isGitTimeoutError(error)) {
@@ -42208,24 +43035,15 @@ var GitRegistry = class {
42208
43035
  }
42209
43036
  if (this.isAccessError(error) && fallbackRepoUrl) {
42210
43037
  try {
42211
- await this.doCloneSparse(git, fallbackRepoUrl, ref, targetDir, sparsePath);
43038
+ await this.performClone(git, fallbackRepoUrl, targetDir, ref, sparsePath);
42212
43039
  return;
42213
43040
  } 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}`,
43041
+ throw this.classifyFallbackCloneError(
43042
+ repoUrl,
42227
43043
  fallbackRepoUrl,
42228
- fbError
43044
+ fallbackErr,
43045
+ ref,
43046
+ "sparse-clone"
42229
43047
  );
42230
43048
  }
42231
43049
  }
@@ -42243,22 +43061,26 @@ var GitRegistry = class {
42243
43061
  }
42244
43062
  }
42245
43063
  /**
42246
- * Internal sparse-clone implementation (no fallback logic).
43064
+ * Classify an error from the fallback clone attempt shared by
43065
+ * `cloneAtTag` and `cloneSparse`: timeout, auth, or a plain clone failure.
42247
43066
  */
42248
- async doCloneSparse(git, repoUrl, ref, targetDir, sparsePath) {
42249
- if (existsSync8(targetDir)) {
42250
- await fs2.rm(targetDir, { recursive: true, force: true });
43067
+ classifyFallbackCloneError(repoUrl, fallbackRepoUrl, err, ref, action) {
43068
+ const fbError = err instanceof Error ? err : new Error(String(err));
43069
+ if (isGitTimeoutError(fbError)) {
43070
+ return createGitTimeoutError(fallbackRepoUrl, this.timeout, fbError);
43071
+ }
43072
+ if (this.isAccessError(fbError)) {
43073
+ return new GitAuthError(
43074
+ `Authentication failed for both ${repoUrl} and fallback ${fallbackRepoUrl}`,
43075
+ fallbackRepoUrl,
43076
+ fbError
43077
+ );
42251
43078
  }
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]);
43079
+ return new GitCloneError(
43080
+ `Failed to ${action} ${fallbackRepoUrl} at ${ref ?? "default branch"}: ${fbError.message}`,
43081
+ fallbackRepoUrl,
43082
+ fbError
43083
+ );
42262
43084
  }
42263
43085
  /**
42264
43086
  * Ensure the repository is cloned and at the correct ref.
@@ -42657,7 +43479,7 @@ function createGitTimeoutError(url, timeout, cause) {
42657
43479
  return cause;
42658
43480
  }
42659
43481
  return new GitCloneError(
42660
- `Git operation timed out after ${timeout}ms while contacting ${url}`,
43482
+ `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
43483
  url,
42662
43484
  cause
42663
43485
  );
@@ -43069,7 +43891,7 @@ async function discoverNativeContent(dir, logger) {
43069
43891
  }
43070
43892
  }
43071
43893
  if (Object.keys(mergedSkills).length > 0) {
43072
- blocks.push(makeBlock("skills", makeObjectContent(mergedSkills)));
43894
+ blocks.push(makeBlock("skills", makeObjectContent(mergedSkills, dir), dir));
43073
43895
  }
43074
43896
  const rootAgents = await discoverAgents(dir) ?? {};
43075
43897
  const wrappedAgents = {};
@@ -43082,7 +43904,7 @@ async function discoverNativeContent(dir, logger) {
43082
43904
  }
43083
43905
  const mergedAgents = { ...wrappedAgents, ...rootAgents };
43084
43906
  if (Object.keys(mergedAgents).length > 0) {
43085
- blocks.push(makeBlock("agents", makeObjectContent(mergedAgents)));
43907
+ blocks.push(makeBlock("agents", makeObjectContent(mergedAgents, dir), dir));
43086
43908
  }
43087
43909
  const rootCommands = await discoverCommands(dir) ?? {};
43088
43910
  const wrappedCommands = {};
@@ -43095,11 +43917,11 @@ async function discoverNativeContent(dir, logger) {
43095
43917
  }
43096
43918
  const mergedCommands = { ...wrappedCommands, ...rootCommands };
43097
43919
  if (Object.keys(mergedCommands).length > 0) {
43098
- blocks.push(makeBlock("shortcuts", makeObjectContent(mergedCommands)));
43920
+ blocks.push(makeBlock("shortcuts", makeObjectContent(mergedCommands, dir), dir));
43099
43921
  }
43100
43922
  const contextContent = await discoverContext(dir);
43101
43923
  if (contextContent) {
43102
- blocks.push(makeBlock("context", contextContent));
43924
+ blocks.push(makeBlock("context", contextContent, contextContent.loc.file));
43103
43925
  }
43104
43926
  if (blocks.length === 0) {
43105
43927
  return null;
@@ -43109,7 +43931,7 @@ async function discoverNativeContent(dir, logger) {
43109
43931
  blocks,
43110
43932
  uses: [],
43111
43933
  extends: [],
43112
- loc: VIRTUAL_LOC
43934
+ loc: fileLoc(dir)
43113
43935
  };
43114
43936
  return program2;
43115
43937
  }
@@ -43249,7 +44071,7 @@ async function hashVendorRepository(directory) {
43249
44071
  await visit(directory, "");
43250
44072
  return `sha256-${hash.digest("hex")}`;
43251
44073
  }
43252
- async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expectedCommit, allowedUntrackedFiles = /* @__PURE__ */ new Set()) {
44074
+ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expectedCommit, allowedUntrackedFiles = /* @__PURE__ */ new Set(), options = {}) {
43253
44075
  const gitDir = join9(directory, gitDirectoryName);
43254
44076
  async function rejectMetadataSymlinks(currentDirectory) {
43255
44077
  for (const entry of await readdir4(currentDirectory, { withFileTypes: true })) {
@@ -43287,7 +44109,7 @@ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expected
43287
44109
  "Vendor Git config"
43288
44110
  );
43289
44111
  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)) {
44112
+ if (/^[ \t]*\[(?:include|includeif)\b/im.test(localConfig) || /\bworktreeconfig\s*=\s*true\b/i.test(localConfig) || !options.allowPartial && (/\bpromisor\s*=\s*true\b/i.test(localConfig) || /\bpartialclone/i.test(localConfig))) {
43291
44113
  throw new Error(`External or partial Git object sources are not allowed: ${gitDir}`);
43292
44114
  }
43293
44115
  await rejectMetadataPath("commondir", "Git common directories are not allowed");
@@ -43458,7 +44280,7 @@ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expected
43458
44280
  }
43459
44281
  }
43460
44282
  await collectFiles(directory, "");
43461
- if (worktreeFileCount !== trackedFiles.size || regularFiles.some((path2) => !trackedFiles.has(path2))) {
44283
+ if (!options.allowPartial && worktreeFileCount !== trackedFiles.size || regularFiles.some((path2) => !trackedFiles.has(path2))) {
43462
44284
  throw new Error(`Vendored repository contents do not match commit ${expectedCommit}`);
43463
44285
  }
43464
44286
  const batchSize = 100;
@@ -43742,7 +44564,8 @@ var Resolver = class {
43742
44564
  cacheEnabled;
43743
44565
  logger;
43744
44566
  options;
43745
- gitRegistry;
44567
+ /** Lazy per-repository Git registries (carry per-entry timeout settings). */
44568
+ gitRegistries = /* @__PURE__ */ new Map();
43746
44569
  registryCache;
43747
44570
  constructor(options) {
43748
44571
  this.options = options;
@@ -43750,7 +44573,6 @@ var Resolver = class {
43750
44573
  this.cache = /* @__PURE__ */ new Map();
43751
44574
  this.cacheEnabled = options.cache !== false;
43752
44575
  this.logger = options.logger ?? noopLogger;
43753
- this.gitRegistry = new GitRegistry({ url: "https://github.com/placeholder/placeholder.git" });
43754
44576
  const defaultCacheDir = join10(
43755
44577
  process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp",
43756
44578
  ".promptscript",
@@ -43758,6 +44580,54 @@ var Resolver = class {
43758
44580
  );
43759
44581
  this.registryCache = new RegistryCache(options.cacheDir ?? defaultCacheDir);
43760
44582
  }
44583
+ /**
44584
+ * Get (or lazily create) the Git registry for a repository URL.
44585
+ *
44586
+ * Per-repository instances carry the `timeout` from the matching
44587
+ * registries config entry, so large monorepo registries can raise the
44588
+ * clone timeout per entry (see issue #455). Falls back to
44589
+ * PROMPTSCRIPT_GIT_TIMEOUT / the 60s default inside GitRegistry.
44590
+ */
44591
+ getGitRegistry(repoUrl) {
44592
+ const cached = this.gitRegistries.get(repoUrl);
44593
+ if (cached) {
44594
+ return cached;
44595
+ }
44596
+ const entry = this.options.registries ? findRegistryEntry(repoUrl, this.options.registries) : void 0;
44597
+ const registry2 = new GitRegistry({
44598
+ url: repoUrl,
44599
+ ...entry?.timeout !== void 0 ? { timeout: entry.timeout } : {}
44600
+ });
44601
+ this.gitRegistries.set(repoUrl, registry2);
44602
+ return registry2;
44603
+ }
44604
+ /**
44605
+ * Clone a registry repository into its cache path. Uses the per-entry
44606
+ * Git registry (so the configured timeout applies) and the fallback URL
44607
+ * from the lockfile or the registries config.
44608
+ */
44609
+ async cloneRegistryIntoCache(repoUrl, cachePath, tag, sparseCone, lockEntry) {
44610
+ const gitRegistry = this.getGitRegistry(repoUrl);
44611
+ const fallbackRepoUrl = lockEntry?.gitUrl ?? (this.options.registries ? findFallbackUrl(repoUrl, this.options.registries) : void 0);
44612
+ await gitRegistry.cloneAtTag(repoUrl, tag, cachePath, fallbackRepoUrl, sparseCone);
44613
+ return gitRegistry;
44614
+ }
44615
+ /**
44616
+ * Verify that a cached registry checkout matches a locked commit.
44617
+ * Cache clones may be partial (sparse cones), so partial-clone metadata
44618
+ * and missing out-of-cone files are allowed.
44619
+ */
44620
+ async verifyCacheCommit(cachePath, commit) {
44621
+ await verifyGitRepositoryCheckout(
44622
+ cachePath,
44623
+ ".git",
44624
+ commit,
44625
+ /* @__PURE__ */ new Set([".prs-registry-meta.json"]),
44626
+ {
44627
+ allowPartial: true
44628
+ }
44629
+ );
44630
+ }
43761
44631
  /**
43762
44632
  * Resolve a PromptScript file and all its dependencies.
43763
44633
  *
@@ -44368,10 +45238,12 @@ var Resolver = class {
44368
45238
  }
44369
45239
  const program2 = {
44370
45240
  type: "Program",
44371
- blocks: [makeBlock("skills", makeObjectContent({ [skillName]: skillProps }))],
45241
+ blocks: [
45242
+ makeBlock("skills", makeObjectContent({ [skillName]: skillProps }, absPath), absPath)
45243
+ ],
44372
45244
  uses: [],
44373
45245
  extends: [],
44374
- loc: VIRTUAL_LOC
45246
+ loc: fileLoc(absPath)
44375
45247
  };
44376
45248
  return { ast: program2 };
44377
45249
  }
@@ -44446,7 +45318,8 @@ var Resolver = class {
44446
45318
  }
44447
45319
  errors.push(
44448
45320
  new ResolveError(
44449
- `Failed to resolve parent: ${err instanceof Error ? err.message : String(err)}`
45321
+ `Failed to resolve parent: ${err instanceof Error ? err.message : String(err)}`,
45322
+ err instanceof ResolveError ? err.location : void 0
44450
45323
  )
44451
45324
  );
44452
45325
  }
@@ -44545,7 +45418,8 @@ var Resolver = class {
44545
45418
  }
44546
45419
  errors.push(
44547
45420
  new ResolveError(
44548
- `Failed to resolve import: ${err instanceof Error ? err.message : String(err)}`
45421
+ `Failed to resolve import: ${err instanceof Error ? err.message : String(err)}`,
45422
+ err instanceof ResolveError ? err.location : void 0
44549
45423
  )
44550
45424
  );
44551
45425
  }
@@ -44685,6 +45559,8 @@ var Resolver = class {
44685
45559
  }
44686
45560
  const requestedRef = lockEntry?.version ?? (version || void 0);
44687
45561
  const tag = requestedRef && requestedRef !== "latest" && !/^[0-9a-f]{40}$/i.test(requestedRef) ? requestedRef : void 0;
45562
+ const subPathLastSlash = subPath.lastIndexOf("/");
45563
+ const sparseCone = subPathLastSlash > 0 ? subPath.slice(0, subPathLastSlash) : void 0;
44688
45564
  let cachePath;
44689
45565
  let vendoredPath = null;
44690
45566
  const vendorManifest = this.options.vendorDir ? await loadVendorManifest(this.options.vendorDir) : null;
@@ -44711,12 +45587,7 @@ var Resolver = class {
44711
45587
  if (lockedCommit) {
44712
45588
  let cacheMatchesLock = false;
44713
45589
  try {
44714
- await verifyGitRepositoryCheckout(
44715
- cachePath,
44716
- ".git",
44717
- lockedCommit,
44718
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
44719
- );
45590
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44720
45591
  cacheMatchesLock = true;
44721
45592
  } catch {
44722
45593
  cacheMatchesLock = false;
@@ -44730,17 +45601,16 @@ var Resolver = class {
44730
45601
  this.logger.verbose(
44731
45602
  `Registry cache does not match locked commit for ${repoUrl}. Re-cloning.`
44732
45603
  );
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(
45604
+ const gitRegistry = await this.cloneRegistryIntoCache(
45605
+ repoUrl,
44739
45606
  cachePath,
44740
- ".git",
44741
- lockedCommit,
44742
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
45607
+ tag,
45608
+ sparseCone,
45609
+ lockEntry
44743
45610
  );
45611
+ await gitRegistry.checkoutCommit(cachePath, lockedCommit);
45612
+ await this.registryCache.set(repoUrl, effectiveVersion, lockedCommit);
45613
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44744
45614
  }
44745
45615
  }
44746
45616
  } else {
@@ -44751,22 +45621,21 @@ var Resolver = class {
44751
45621
  }
44752
45622
  this.logger.verbose(`Registry cache miss, cloning: ${repoUrl}@${tag ?? "default"}`);
44753
45623
  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);
45624
+ const gitRegistry = await this.cloneRegistryIntoCache(
45625
+ repoUrl,
45626
+ cachePath,
45627
+ tag,
45628
+ sparseCone,
45629
+ lockEntry
45630
+ );
44757
45631
  if (lockedCommit) {
44758
45632
  this.logger.verbose(`Checking out locked commit: ${lockedCommit}`);
44759
- await this.gitRegistry.checkoutCommit(cachePath, lockedCommit);
45633
+ await gitRegistry.checkoutCommit(cachePath, lockedCommit);
44760
45634
  }
44761
45635
  const commitHash = lockedCommit ?? lockEntry?.commit ?? "unknown";
44762
45636
  await this.registryCache.set(repoUrl, effectiveVersion, commitHash);
44763
45637
  if (lockedCommit) {
44764
- await verifyGitRepositoryCheckout(
44765
- cachePath,
44766
- ".git",
44767
- lockedCommit,
44768
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
44769
- );
45638
+ await this.verifyCacheCommit(cachePath, lockedCommit);
44770
45639
  }
44771
45640
  }
44772
45641
  dependencies.add(cachePath);
@@ -44807,6 +45676,26 @@ var Resolver = class {
44807
45676
  };
44808
45677
  }
44809
45678
  }
45679
+ if (!this.options.readOnly && existsSync9(cachePath) && existsSync9(join10(cachePath, ".git")) && !vendoredPath) {
45680
+ const gitRegistry = this.getGitRegistry(repoUrl);
45681
+ if (await gitRegistry.isSparseCheckout(cachePath)) {
45682
+ const fileMissing = !isRoot && !existsSync9(resolvedFullPath);
45683
+ const dirMissing = !isRoot && !existsSync9(join10(cachePath, subPath));
45684
+ if (isRoot) {
45685
+ this.logger.verbose(`Materializing full checkout for root import: ${repoUrl}`);
45686
+ await gitRegistry.disableSparseCheckout(cachePath);
45687
+ } else if (fileMissing || dirMissing) {
45688
+ this.logger.verbose(`Widening sparse checkout for ${repoUrl}: ${subPath}`);
45689
+ const paths = /* @__PURE__ */ new Set();
45690
+ if (sparseCone) {
45691
+ paths.add(sparseCone);
45692
+ } else if (!isMdPath && !subPath.endsWith(".prs")) {
45693
+ paths.add(subPath);
45694
+ }
45695
+ await gitRegistry.addSparsePaths(cachePath, [...paths]);
45696
+ }
45697
+ }
45698
+ }
44810
45699
  let resolvedAST = null;
44811
45700
  if (!isRoot && existsSync9(resolvedFullPath) && isMdPath) {
44812
45701
  this.logger.debug(`Found .md file: ${resolvedFullPath}`);
@@ -44935,10 +45824,10 @@ var Resolver = class {
44935
45824
  this.logger.debug(`Found ${Object.keys(properties).length} skill(s) in directory: ${dirPath}`);
44936
45825
  const program2 = {
44937
45826
  type: "Program",
44938
- blocks: [makeBlock("skills", makeObjectContent(properties))],
45827
+ blocks: [makeBlock("skills", makeObjectContent(properties, dirPath), dirPath)],
44939
45828
  uses: [],
44940
45829
  extends: [],
44941
- loc: VIRTUAL_LOC
45830
+ loc: fileLoc(dirPath)
44942
45831
  };
44943
45832
  return { ast: program2 };
44944
45833
  }
@@ -45303,7 +46192,7 @@ var HttpRegistry = class {
45303
46192
  * Sleep for specified milliseconds.
45304
46193
  */
45305
46194
  sleep(ms) {
45306
- return new Promise((resolve42) => setTimeout(resolve42, ms));
46195
+ return new Promise((resolve43) => setTimeout(resolve43, ms));
45307
46196
  }
45308
46197
  /**
45309
46198
  * Fetch with timeout.
@@ -45448,6 +46337,7 @@ function createHttpRegistry(options) {
45448
46337
  init_src();
45449
46338
 
45450
46339
  // packages/resolver/src/skill-validation.ts
46340
+ init_src();
45451
46341
  import { basename as basename12, dirname as dirname11, join as join12 } from "path";
45452
46342
  import { existsSync as existsSync11 } from "fs";
45453
46343
  function validateSkillFrontmatter(rawContent, options = {}) {
@@ -45464,11 +46354,15 @@ function validateSkillFrontmatter(rawContent, options = {}) {
45464
46354
  try {
45465
46355
  parsed = parseSkillMd(rawContent, options.filePath);
45466
46356
  } catch (error) {
45467
- issues.push({
46357
+ const issue = {
45468
46358
  severity: "error",
45469
46359
  code: "SK000",
45470
46360
  message: error instanceof Error ? error.message : String(error)
45471
- });
46361
+ };
46362
+ if (error instanceof ResolveError && error.location) {
46363
+ issue.location = error.location;
46364
+ }
46365
+ issues.push(issue);
45472
46366
  return { valid: false, issues };
45473
46367
  }
45474
46368
  validateName(parsed, options, issues);
@@ -45648,7 +46542,8 @@ function formatSkillValidationIssues(issues) {
45648
46542
  return issues.map((i) => {
45649
46543
  const tag = i.severity === "error" ? "\u2717" : "\u26A0";
45650
46544
  const field = i.field ? ` [${i.field}]` : "";
45651
- return ` ${tag} ${i.code}${field}: ${i.message}`;
46545
+ const location = i.location ? ` (at ${i.location.file}:${i.location.line}:${i.location.column})` : "";
46546
+ return ` ${tag} ${i.code}${field}: ${i.message}${location}`;
45652
46547
  }).join("\n");
45653
46548
  }
45654
46549
 
@@ -47388,6 +48283,19 @@ var authorityInjection = {
47388
48283
  }
47389
48284
  };
47390
48285
 
48286
+ // packages/validator/src/external-content.ts
48287
+ import { isAbsolute as isAbsolute8, relative as relative9, sep as sep6 } from "node:path";
48288
+ function isPathInside(root, candidate) {
48289
+ const relation = relative9(root, candidate);
48290
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep6}`) && !isAbsolute8(relation);
48291
+ }
48292
+ function isExternalLocation(loc, config) {
48293
+ if (config.scanExternalContent || !loc) return false;
48294
+ const roots = config.externalRoots;
48295
+ if (!roots || roots.length === 0) return false;
48296
+ return roots.some((root) => isPathInside(root, loc.file));
48297
+ }
48298
+
47391
48299
  // packages/validator/src/rules/obfuscated-content.ts
47392
48300
  var MIN_ENCODED_LENGTH = 16;
47393
48301
  var DECODED_CONTENT_SECURITY_PATTERNS = [
@@ -47568,6 +48476,19 @@ function isLikelyTechnicalContent(match2) {
47568
48476
  }
47569
48477
  return false;
47570
48478
  }
48479
+ var NUCLEOTIDE_CHARS = new Set("ACGTUN");
48480
+ var AMINO_ACID_CHARS = new Set("ACDEFGHIKLMNPQRSTVWYBXZJOU");
48481
+ var BIO_CONTEXT_PATTERN = /\b(?:protein|peptide|amino[-\s]?acid|residue|sequence|nucleotide|genome|transcript|fasta|uniprot)\b/i;
48482
+ function isOverAlphabet(match2, alphabet) {
48483
+ for (const char of match2) {
48484
+ if (!alphabet.has(char)) return false;
48485
+ }
48486
+ return match2.length > 0;
48487
+ }
48488
+ function isLikelyBioSequence(text, match2) {
48489
+ if (isOverAlphabet(match2, NUCLEOTIDE_CHARS)) return true;
48490
+ return isOverAlphabet(match2, AMINO_ACID_CHARS) && BIO_CONTEXT_PATTERN.test(text);
48491
+ }
47571
48492
  function isLikelyLegitimateBase64(text, match2) {
47572
48493
  const matchIndex = text.indexOf(match2);
47573
48494
  if (matchIndex === -1) return false;
@@ -47576,6 +48497,7 @@ function isLikelyLegitimateBase64(text, match2) {
47576
48497
  if (precedingText.includes("data:application/json")) return true;
47577
48498
  if (precedingText.includes("data:text/")) return true;
47578
48499
  if (isLikelyTechnicalContent(match2)) return true;
48500
+ if (isLikelyBioSequence(text, match2)) return true;
47579
48501
  return false;
47580
48502
  }
47581
48503
  function isLikelyHashOrUuid(hex) {
@@ -47784,12 +48706,16 @@ var obfuscatedContent = {
47784
48706
  suggestion: "Encoded content is automatically decoded and checked for security patterns. Remove the malicious payload or use plain text."
47785
48707
  });
47786
48708
  }
47787
- const hasLongBase64 = (text.match(BASE64_PATTERN) || []).some(
48709
+ if (isExternalLocation(loc, ctx.config)) {
48710
+ return;
48711
+ }
48712
+ const longBase64Match = (text.match(BASE64_PATTERN) || []).find(
47788
48713
  (m) => m.length >= MIN_ENCODED_LENGTH * 2 && !isLikelyLegitimateBase64(text, m)
47789
48714
  );
47790
- if (hasLongBase64 && encodedMatches.length === 0) {
48715
+ if (longBase64Match && encodedMatches.length === 0) {
48716
+ const snippet = longBase64Match.length > 20 ? `${longBase64Match.substring(0, 20)}...` : longBase64Match;
47791
48717
  ctx.report({
47792
- message: "Long Base64-encoded content detected that may hide payloads",
48718
+ message: `Long Base64-encoded content detected that may hide payloads (starts with "${snippet}")`,
47793
48719
  location: loc,
47794
48720
  suggestion: "If this is intentional, consider using plain text or documenting the purpose"
47795
48721
  });
@@ -48697,11 +49623,14 @@ var safeReferenceContent = {
48697
49623
  const content = res["content"];
48698
49624
  if (typeof relPath !== "string" || typeof content !== "string") continue;
48699
49625
  if (!relPath.startsWith("references/") && !relPath.includes("/references/")) continue;
49626
+ const origin = res["origin"];
49627
+ const location = typeof origin === "string" ? { file: origin, line: 1, column: 1, offset: 0 } : skillsBlock.loc;
49628
+ if (isExternalLocation(location, ctx.config)) continue;
48700
49629
  const match2 = content.match(DIRECTIVE_PATTERN);
48701
49630
  if (match2) {
48702
49631
  ctx.report({
48703
49632
  message: `Reference file "${relPath}" in skill "${skillName}" contains PRS directive '${match2[1]}' \u2014 references should contain data, not instructions`,
48704
- location: skillsBlock.loc,
49633
+ location,
48705
49634
  suggestion: "Remove PRS directives from reference files. References are data files, not PromptScript source."
48706
49635
  });
48707
49636
  }
@@ -50512,18 +51441,18 @@ var SECURITY_STRICT_MULTILINGUAL = {
50512
51441
  };
50513
51442
 
50514
51443
  // 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";
51444
+ import { dirname as dirname13, isAbsolute as isAbsolute10, join as join13, relative as relative11, resolve as resolve15, sep as sep8 } from "path";
50516
51445
 
50517
51446
  // packages/compiler/src/hook-script-validator.ts
50518
51447
  init_src();
50519
51448
  init_src4();
50520
51449
  import { lstat as lstat7, realpath as realpath4 } from "fs/promises";
50521
51450
  import { realpathSync, statSync } from "fs";
50522
- import { dirname as dirname12, isAbsolute as isAbsolute8, relative as relative9, resolve as resolve14, sep as sep6 } from "path";
51451
+ import { dirname as dirname12, isAbsolute as isAbsolute9, relative as relative10, resolve as resolve14, sep as sep7 } from "path";
50523
51452
  var PROJECT_MARKERS = [".promptscript", ".git", "package.json"];
50524
51453
  function isInside2(root, candidate) {
50525
- const relation = relative9(root, candidate);
50526
- return relation === "" || !relation.startsWith(`..${sep6}`) && relation !== ".." && !isAbsolute8(relation);
51454
+ const relation = relative10(root, candidate);
51455
+ return relation === "" || !relation.startsWith(`..${sep7}`) && relation !== ".." && !isAbsolute9(relation);
50527
51456
  }
50528
51457
  function isProjectMarker(directory, marker) {
50529
51458
  const markerPath = resolve14(directory, marker);
@@ -50561,10 +51490,10 @@ function inferProjectRoot(localPath, configuredProjectRoot, entryPath) {
50561
51490
  if (markedRoot) return markedRoot;
50562
51491
  }
50563
51492
  const local = localPath ? resolve14(localPath) : entryPath ? dirname12(resolve14(entryPath)) : process.cwd();
50564
- const promptScriptDirectory = `${sep6}.promptscript`;
50565
- const nestedMarker = `${promptScriptDirectory}${sep6}`;
51493
+ const promptScriptDirectory = `${sep7}.promptscript`;
51494
+ const nestedMarker = `${promptScriptDirectory}${sep7}`;
50566
51495
  const nestedIndex = local.indexOf(nestedMarker);
50567
- if (nestedIndex >= 0) return local.slice(0, nestedIndex) || sep6;
51496
+ if (nestedIndex >= 0) return local.slice(0, nestedIndex) || sep7;
50568
51497
  return local.endsWith(promptScriptDirectory) ? dirname12(local) : local;
50569
51498
  }
50570
51499
  async function validateHookScriptResources(ast, projectRoot) {
@@ -50632,7 +51561,7 @@ function resolverCacheKey(projectRoot) {
50632
51561
  function resolveEntryPath(entryPath) {
50633
51562
  if (entryPath.startsWith("@")) return entryPath;
50634
51563
  const fileName = entryPath.endsWith(".prs") || entryPath.endsWith(".md") ? entryPath : `${entryPath}.prs`;
50635
- const absolutePath = isAbsolute9(fileName) ? fileName : resolve15(process.cwd(), fileName);
51564
+ const absolutePath = isAbsolute10(fileName) ? fileName : resolve15(process.cwd(), fileName);
50636
51565
  try {
50637
51566
  return realpathSync2.native(absolutePath);
50638
51567
  } catch {
@@ -50686,7 +51615,7 @@ function normalizeOutputDir(dir) {
50686
51615
  }
50687
51616
  function matchesWatchPattern(path2, pattern, baseDir) {
50688
51617
  const normalizedPath = path2.replace(/\\/g, "/");
50689
- const relativePath = relative10(baseDir, path2).replace(/\\/g, "/");
51618
+ const relativePath = relative11(baseDir, path2).replace(/\\/g, "/");
50690
51619
  const candidates = [relativePath, normalizedPath].flatMap((candidate) => [
50691
51620
  candidate,
50692
51621
  candidate.endsWith("/") ? candidate : `${candidate}/`
@@ -50698,7 +51627,7 @@ function matchesWatchPattern(path2, pattern, baseDir) {
50698
51627
  return candidates.some((candidate) => minimatch(candidate, pattern, matcherOptions));
50699
51628
  }
50700
51629
  function absoluteWatchPath(path2, baseDir = process.cwd()) {
50701
- return isAbsolute9(path2) ? resolve15(path2) : resolve15(baseDir, path2);
51630
+ return isAbsolute10(path2) ? resolve15(path2) : resolve15(baseDir, path2);
50702
51631
  }
50703
51632
  function canonicalWatchPath(path2, baseDir = process.cwd()) {
50704
51633
  const absolutePath = absoluteWatchPath(path2, baseDir);
@@ -50721,8 +51650,8 @@ function watchPathKey(path2, baseDir = process.cwd()) {
50721
51650
  return normalizeWatchPath(canonicalWatchPath(path2, baseDir));
50722
51651
  }
50723
51652
  function isPathWithin(path2, root) {
50724
- const relation = relative10(normalizeWatchPath(root), normalizeWatchPath(path2));
50725
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation);
51653
+ const relation = relative11(normalizeWatchPath(root), normalizeWatchPath(path2));
51654
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation);
50726
51655
  }
50727
51656
  function shouldIncludeSkill(name, config) {
50728
51657
  const includeSkills = config?.includeSkills;
@@ -50877,7 +51806,7 @@ var Compiler = class _Compiler {
50877
51806
  formatTime: 0,
50878
51807
  totalTime: 0
50879
51808
  };
50880
- const sourceLabel = isAbsolute9(entryPath) ? relative10(process.cwd(), entryPath) : entryPath;
51809
+ const sourceLabel = isAbsolute10(entryPath) ? relative11(process.cwd(), entryPath) : entryPath;
50881
51810
  this.logger.verbose("=== Stage 1: Resolve ===");
50882
51811
  const startResolve = Date.now();
50883
51812
  let resolved;
@@ -50921,11 +51850,20 @@ var Compiler = class _Compiler {
50921
51850
  };
50922
51851
  }
50923
51852
  const compileErrors = [];
51853
+ const externalRoots = [];
51854
+ const cacheDir = this.options.resolver.cacheDir ?? join13(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp", ".promptscript", "cache");
51855
+ externalRoots.push(cacheDir);
51856
+ if (this.options.resolver.vendorDir) {
51857
+ externalRoots.push(this.options.resolver.vendorDir);
51858
+ }
51859
+ for (const roots of Object.values(this.options.resolver.referenceRoots ?? {})) {
51860
+ externalRoots.push(...roots);
51861
+ }
51862
+ this.validator.updateConfig({ externalRoots });
50924
51863
  if (!this.options.ignoreHashes && this.options.resolver.lockfile) {
50925
51864
  this.logger.verbose("=== Stage 1.5: Reference Integrity ===");
50926
51865
  const registryReferences = /* @__PURE__ */ new Set();
50927
51866
  const registryReferencePaths = /* @__PURE__ */ new Map();
50928
- const cacheDir = this.options.resolver.cacheDir ?? join13(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp", ".promptscript", "cache");
50929
51867
  const registryCache = new RegistryCache(cacheDir);
50930
51868
  const referenceRoots = [];
50931
51869
  for (const [repoUrl, dependency] of Object.entries(
@@ -50964,8 +51902,8 @@ var Compiler = class _Compiler {
50964
51902
  if (block.name !== "skills" || block.content.type !== "ObjectContent") continue;
50965
51903
  const sourceFile = block.loc.file;
50966
51904
  const referenceRoot = sourceFile ? referenceRoots.find((root) => {
50967
- const relation = relative10(resolve15(root.path), resolve15(sourceFile));
50968
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation);
51905
+ const relation = relative11(resolve15(root.path), resolve15(sourceFile));
51906
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation);
50969
51907
  }) : void 0;
50970
51908
  for (const [, skillValue] of Object.entries(block.content.properties)) {
50971
51909
  if (typeof skillValue !== "object" || skillValue === null || !("references" in skillValue))
@@ -50977,8 +51915,8 @@ var Compiler = class _Compiler {
50977
51915
  registryReferences.add(ref);
50978
51916
  if (sourceFile && referenceRoot) {
50979
51917
  const resolvedReference = resolve15(dirname13(sourceFile), ref);
50980
- const relation = relative10(resolve15(referenceRoot.path), resolvedReference);
50981
- if (relation !== "" && relation !== ".." && !relation.startsWith(`..${sep7}`) && !isAbsolute9(relation)) {
51918
+ const relation = relative11(resolve15(referenceRoot.path), resolvedReference);
51919
+ if (relation !== "" && relation !== ".." && !relation.startsWith(`..${sep8}`) && !isAbsolute10(relation)) {
50982
51920
  const sourceReferences = registryReferencePaths.get(sourceFile) ?? /* @__PURE__ */ new Map();
50983
51921
  const relativePath = relation.replaceAll("\\", "/");
50984
51922
  const referenceKey = `${referenceRoot.repoUrl}\0${relativePath}\0${referenceRoot.version}`;
@@ -51082,7 +52020,7 @@ var Compiler = class _Compiler {
51082
52020
  for (const warning of output.warnings ?? []) {
51083
52021
  formatWarnings.push({
51084
52022
  ruleId: warning.code,
51085
- ruleName: "target-hook-compatibility",
52023
+ ruleName: warning.ruleName ?? "target-hook-compatibility",
51086
52024
  severity: "warning",
51087
52025
  message: warning.message,
51088
52026
  ...warning.suggestion ? { suggestion: warning.suggestion } : {},
@@ -51284,8 +52222,8 @@ var Compiler = class _Compiler {
51284
52222
  */
51285
52223
  async watch(entryPath, options = {}) {
51286
52224
  const { default: chokidar2 } = await import("chokidar");
51287
- const { dirname: dirname29, resolve: resolve42 } = await import("path");
51288
- const baseDir = dirname29(resolve42(entryPath));
52225
+ const { dirname: dirname29, resolve: resolve43 } = await import("path");
52226
+ const baseDir = dirname29(resolve43(entryPath));
51289
52227
  const includePatterns = options.include ?? ["**/*.prs"];
51290
52228
  const excludePatterns = options.exclude ?? ["**/node_modules/**"];
51291
52229
  const debounceMs = options.debounce ?? 300;
@@ -51336,7 +52274,7 @@ var Compiler = class _Compiler {
51336
52274
  ".promptscriptrc.yaml",
51337
52275
  ".promptscriptrc.yml",
51338
52276
  "promptscript.lock"
51339
- ].map((fileName) => resolve42(this.watchProjectRoot(entryPath), fileName))
52277
+ ].map((fileName) => resolve43(this.watchProjectRoot(entryPath), fileName))
51340
52278
  ].map((path2) => watchPathKey(path2))
51341
52279
  );
51342
52280
  let dynamicDependencyPaths = toDependencyMap(this.resolvedDependencyPaths(entryPath));
@@ -51629,7 +52567,7 @@ var Pager = class {
51629
52567
  }
51630
52568
  return;
51631
52569
  }
51632
- return new Promise((resolve42) => {
52570
+ return new Promise((resolve43) => {
51633
52571
  const pagerCmd = getPagerCommand();
51634
52572
  const [cmd, ...args] = pagerCmd.split(" ");
51635
52573
  if (cmd === "less" && !args.includes("-R")) {
@@ -51637,7 +52575,7 @@ var Pager = class {
51637
52575
  }
51638
52576
  if (!cmd) {
51639
52577
  console.log(content);
51640
- resolve42();
52578
+ resolve43();
51641
52579
  return;
51642
52580
  }
51643
52581
  const pager = spawn2(cmd, args, {
@@ -51650,17 +52588,17 @@ var Pager = class {
51650
52588
  });
51651
52589
  pager.on("error", (_err) => {
51652
52590
  console.log(content);
51653
- resolve42();
52591
+ resolve43();
51654
52592
  });
51655
52593
  pager.on("close", () => {
51656
- resolve42();
52594
+ resolve43();
51657
52595
  });
51658
52596
  if (pager.stdin) {
51659
52597
  pager.stdin.write(content);
51660
52598
  pager.stdin.end();
51661
52599
  } else {
51662
52600
  console.log(content);
51663
- resolve42();
52601
+ resolve43();
51664
52602
  }
51665
52603
  });
51666
52604
  }
@@ -51674,14 +52612,14 @@ init_services();
51674
52612
 
51675
52613
  // packages/cli/src/utils/registry-resolver.ts
51676
52614
  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";
52615
+ import { isAbsolute as isAbsolute11, join as join14, relative as relative12, resolve as resolve16, sep as sep9 } from "path";
51678
52616
  async function resolveRegistrySubPath(repositoryPath, subPath) {
51679
52617
  if (!subPath) {
51680
52618
  return repositoryPath;
51681
52619
  }
51682
52620
  const result = resolve16(repositoryPath, subPath);
51683
- const relation = relative11(resolve16(repositoryPath), result);
51684
- if (relation === ".." || relation.startsWith(`..${sep8}`) || isAbsolute10(relation)) {
52621
+ const relation = relative12(resolve16(repositoryPath), result);
52622
+ if (relation === ".." || relation.startsWith(`..${sep9}`) || isAbsolute11(relation)) {
51685
52623
  throw new Error(`Registry path escapes its repository: ${subPath}`);
51686
52624
  }
51687
52625
  if (existsSync12(result) && !await isRealPathInside(result, repositoryPath)) {
@@ -51780,6 +52718,7 @@ async function resolveRegistryPath(config, options = {}) {
51780
52718
  ref,
51781
52719
  path: gitConfig.path,
51782
52720
  auth: gitConfig.auth,
52721
+ timeout: gitConfig.timeout,
51783
52722
  cache: {
51784
52723
  enabled: config.registry.cache?.enabled ?? true,
51785
52724
  ttl: config.registry.cache?.ttl
@@ -51839,7 +52778,7 @@ init_src();
51839
52778
  // packages/cli/src/utils/conflict-detector.ts
51840
52779
  init_src();
51841
52780
  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";
52781
+ import { basename as basename13, dirname as dirname14, resolve as resolve17, relative as relative13, isAbsolute as isAbsolute12, sep as sep10 } from "path";
51843
52782
  function detectOutputConflicts(targets) {
51844
52783
  const pathMap = /* @__PURE__ */ new Map();
51845
52784
  for (const target of targets) {
@@ -51860,14 +52799,14 @@ function detectOutputConflicts(targets) {
51860
52799
  function validateOutputPath(outputPath, outputRoot) {
51861
52800
  const resolved = resolveOutputPath(outputPath, outputRoot);
51862
52801
  const root = resolve17(outputRoot);
51863
- const rel = relative12(root, resolved);
52802
+ const rel = relative13(root, resolved);
51864
52803
  if (!isPathContained(rel, false)) {
51865
52804
  return `Output path "${outputPath}" escapes the output directory ${outputRoot}`;
51866
52805
  }
51867
52806
  try {
51868
52807
  const realRoot = resolveThroughExistingAncestor(root);
51869
52808
  const realResolved = resolveThroughExistingAncestor(resolved);
51870
- if (!isPathContained(relative12(realRoot, realResolved), false)) {
52809
+ if (!isPathContained(relative13(realRoot, realResolved), false)) {
51871
52810
  return `Output path "${outputPath}" escapes the output directory ${outputRoot}`;
51872
52811
  }
51873
52812
  } catch {
@@ -51876,11 +52815,11 @@ function validateOutputPath(outputPath, outputRoot) {
51876
52815
  return void 0;
51877
52816
  }
51878
52817
  function isPathInsideDir(dir, root) {
51879
- const rel = relative12(resolve17(root), resolveOutputPath(dir, root));
52818
+ const rel = relative13(resolve17(root), resolveOutputPath(dir, root));
51880
52819
  return isPathContained(rel, true);
51881
52820
  }
51882
52821
  function isPathContained(rel, allowEqual) {
51883
- return allowEqual && rel === "" || rel !== "" && rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute11(rel);
52822
+ return allowEqual && rel === "" || rel !== "" && rel !== ".." && !rel.startsWith(`..${sep10}`) && !isAbsolute12(rel);
51884
52823
  }
51885
52824
  function resolveThroughExistingAncestor(path2) {
51886
52825
  const missingSegments = [];
@@ -51904,7 +52843,7 @@ function resolveThroughExistingAncestor(path2) {
51904
52843
  return resolve17(fs4.realpathSync.native(current), ...missingSegments);
51905
52844
  }
51906
52845
  function resolveOutputPath(outputPath, projectRoot) {
51907
- if (isAbsolute11(outputPath)) {
52846
+ if (isAbsolute12(outputPath)) {
51908
52847
  return outputPath;
51909
52848
  }
51910
52849
  return resolve17(projectRoot, outputPath);
@@ -51923,12 +52862,14 @@ import {
51923
52862
  rmdir,
51924
52863
  unlink as unlink2
51925
52864
  } from "node:fs/promises";
51926
- import { basename as basename14, dirname as dirname15, isAbsolute as isAbsolute12, relative as relative13, resolve as resolve18 } from "node:path";
52865
+ import { basename as basename14, dirname as dirname15, isAbsolute as isAbsolute13, relative as relative14, resolve as resolve18 } from "node:path";
51927
52866
  import { promisify as promisify2 } from "node:util";
51928
52867
  var execFileAsync2 = promisify2(execFile2);
51929
52868
  var PROMPTSCRIPT_MARKER_PATTERNS = [
51930
52869
  /^<!-- PromptScript \d{4}-\d{2}-\d{2}T[\d:.]+Z \| source: .+ \| target: .+ - do not edit -->$/,
51931
52870
  /^# promptscript-generated: \d{4}-\d{2}-\d{2}T[\d:.]+Z \| source: .+ \| target: .+$/,
52871
+ /^\/\/ promptscript-generated: opencode-plugin$/,
52872
+ /^\/\/ Generated by PromptScript - do not edit\.$/,
51932
52873
  /^> Auto-generated by PromptScript(?:\s.*)?$/
51933
52874
  ];
51934
52875
  var OWNED_COMMAND_FIELDS = /* @__PURE__ */ new Set([
@@ -52256,7 +53197,7 @@ function collectManagedDirectories(outputs, outputRoot) {
52256
53197
  for (const output of outputs.values()) {
52257
53198
  for (const directory of output.managedOutputDirectories ?? []) {
52258
53199
  const normalized = directory.replace(/\\/g, "/");
52259
- if (!normalized || isAbsolute12(normalized)) continue;
53200
+ if (!normalized || isAbsolute13(normalized)) continue;
52260
53201
  if (normalized.split("/").some((segment) => segment === "..")) continue;
52261
53202
  const absolutePath = resolve18(outputRoot, normalized);
52262
53203
  if (isWithin(outputRoot, absolutePath)) directories.add(absolutePath);
@@ -52277,7 +53218,7 @@ function collectManagedFiles(outputs, outputRoot) {
52277
53218
  for (const output of outputs.values()) {
52278
53219
  for (const file of output.managedOutputFiles ?? []) {
52279
53220
  const normalized = file.replace(/\\/g, "/");
52280
- if (!normalized || isAbsolute12(normalized)) continue;
53221
+ if (!normalized || isAbsolute13(normalized)) continue;
52281
53222
  if (normalized.split("/").some((segment) => segment === "..")) continue;
52282
53223
  const absolutePath = resolve18(outputRoot, normalized);
52283
53224
  if (isWithin(outputRoot, absolutePath)) files.add(absolutePath);
@@ -52540,7 +53481,7 @@ async function removeIfUnchanged(entryPath, fileStat) {
52540
53481
  return true;
52541
53482
  }
52542
53483
  async function openAncestorDirectories(root, directory) {
52543
- const relativePath = relative13(root, directory);
53484
+ const relativePath = relative14(root, directory);
52544
53485
  const segments = relativePath === "" ? [] : relativePath.split(/[\\/]+/);
52545
53486
  const paths = [root];
52546
53487
  let current = root;
@@ -52564,8 +53505,8 @@ async function openAncestorDirectories(root, directory) {
52564
53505
  return guards;
52565
53506
  }
52566
53507
  async function openOrCreateAncestorDirectories(root, file) {
52567
- const relativePath = relative13(root, file);
52568
- if (relativePath.startsWith("..") || isAbsolute12(relativePath)) {
53508
+ const relativePath = relative14(root, file);
53509
+ if (relativePath.startsWith("..") || isAbsolute13(relativePath)) {
52569
53510
  return void 0;
52570
53511
  }
52571
53512
  await mkdir4(root, { recursive: true });
@@ -53015,8 +53956,8 @@ function scanHookOwnership(value) {
53015
53956
  return scanHookOwnership(Object.values(object));
53016
53957
  }
53017
53958
  function isWithin(root, candidate) {
53018
- const relativePath = relative13(root, candidate);
53019
- return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute12(relativePath);
53959
+ const relativePath = relative14(root, candidate);
53960
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute13(relativePath);
53020
53961
  }
53021
53962
  function isNodeError(error) {
53022
53963
  return error instanceof Error && "code" in error;
@@ -53057,6 +53998,8 @@ async function loadBundledSkillContent(logger) {
53057
53998
  var PROMPTSCRIPT_MARKERS2 = [
53058
53999
  "<!-- PromptScript",
53059
54000
  "# promptscript-generated:",
54001
+ "// promptscript-generated:",
54002
+ "// Generated by PromptScript - do not edit.",
53060
54003
  "> Auto-generated by PromptScript"
53061
54004
  ];
53062
54005
  function hasPromptScriptMarker(content) {
@@ -53246,11 +54189,77 @@ async function finalizeOutputPlan(result, options) {
53246
54189
  };
53247
54190
  }
53248
54191
 
54192
+ // packages/cli/src/utils/resource-filter.ts
54193
+ init_src();
54194
+ function collectResourceKinds(value, previous) {
54195
+ return [...previous, ...value.split(",").map((item) => item.trim())].filter(Boolean);
54196
+ }
54197
+ function parseResourceSelection(values) {
54198
+ if (!values || values.length === 0) return { kinds: [], invalid: [] };
54199
+ const kinds = [];
54200
+ const invalid = [];
54201
+ for (const raw of values.flatMap((value) => value.split(","))) {
54202
+ const item = raw.trim();
54203
+ if (!item) continue;
54204
+ if (isOutputResourceKind(item)) {
54205
+ if (!kinds.includes(item)) kinds.push(item);
54206
+ } else if (!invalid.includes(item)) {
54207
+ invalid.push(item);
54208
+ }
54209
+ }
54210
+ return { kinds, invalid };
54211
+ }
54212
+ function filterOutputsByResources(plan, outputs, kinds, skillBaseDirs) {
54213
+ const filtered = /* @__PURE__ */ new Map();
54214
+ for (const file of plan.files) {
54215
+ if (!kinds.has(classifyOutputResource(file.owner, file.path, skillBaseDirs?.get(file.owner)))) {
54216
+ continue;
54217
+ }
54218
+ const existing = outputs.get(file.path);
54219
+ filtered.set(
54220
+ file.path,
54221
+ existing ?? {
54222
+ path: file.path,
54223
+ content: file.content,
54224
+ ...file.mode !== void 0 ? { mode: file.mode } : {},
54225
+ ...file.merge !== void 0 ? { merge: file.merge } : {},
54226
+ ...file.managedOutputDirectories !== void 0 ? { managedOutputDirectories: file.managedOutputDirectories } : {},
54227
+ ...file.managedOutputFiles !== void 0 ? { managedOutputFiles: file.managedOutputFiles } : {}
54228
+ }
54229
+ );
54230
+ }
54231
+ return {
54232
+ outputs: filtered,
54233
+ kept: filtered.size,
54234
+ dropped: plan.files.length - filtered.size
54235
+ };
54236
+ }
54237
+
54238
+ // packages/cli/src/utils/global-output-safety.ts
54239
+ import { homedir as homedir3 } from "node:os";
54240
+ import { resolve as resolve21 } from "node:path";
54241
+ var PROTECTED_USER_FILES = [
54242
+ {
54243
+ // Factory AI contract: ~/.factory/AGENTS.md is a personal override file.
54244
+ path: ".factory/AGENTS.md",
54245
+ reason: "Factory personal override (~/.factory/AGENTS.md is user-owned, never compiler output)"
54246
+ }
54247
+ ];
54248
+ function describeProtectedUserFile(absolutePath) {
54249
+ const home = homedir3();
54250
+ for (const entry of PROTECTED_USER_FILES) {
54251
+ if (absolutePath === resolve21(home, entry.path)) {
54252
+ return { displayPath: `~/${entry.path}`, reason: entry.reason };
54253
+ }
54254
+ }
54255
+ return void 0;
54256
+ }
54257
+
53249
54258
  // packages/cli/src/commands/compile.ts
53250
54259
  init_build_profile();
53251
54260
  init_target_config();
53252
54261
  async function loadCompileLockfile(projectRoot, logger) {
53253
- const lockfilePath = resolve21(projectRoot, "promptscript.lock");
54262
+ const lockfilePath = resolve22(projectRoot, "promptscript.lock");
53254
54263
  if (!existsSync14(lockfilePath)) {
53255
54264
  return void 0;
53256
54265
  }
@@ -53268,7 +54277,7 @@ async function loadCompileLockfile(projectRoot, logger) {
53268
54277
  }
53269
54278
  function findConfigInDir(dir) {
53270
54279
  for (const file of CONFIG_FILES) {
53271
- const fullPath = resolve21(dir, file);
54280
+ const fullPath = resolve22(dir, file);
53272
54281
  if (existsSync14(fullPath)) {
53273
54282
  return fullPath;
53274
54283
  }
@@ -53309,19 +54318,19 @@ function describeEmptyTargets(context) {
53309
54318
  }
53310
54319
  function resolveOutputBase(projectRoot, output) {
53311
54320
  if (!output) return projectRoot;
53312
- return isAbsolute13(output) ? output : resolve21(projectRoot, output);
54321
+ return isAbsolute14(output) ? output : resolve22(projectRoot, output);
53313
54322
  }
53314
54323
  function resolveProjectPath(projectRoot, path2) {
53315
- return isAbsolute13(path2) ? path2 : resolve21(projectRoot, path2);
54324
+ return isAbsolute14(path2) ? path2 : resolve22(projectRoot, path2);
53316
54325
  }
53317
54326
  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;
54327
+ const root = resolve22(outputRoot);
54328
+ const candidate = resolve22(root, outputPath);
54329
+ const relativePath = relative15(root, candidate);
54330
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute14(relativePath)) return;
53322
54331
  let current = root;
53323
54332
  for (const segment of relativePath.split(/[\\/]/)) {
53324
- current = resolve21(current, segment);
54333
+ current = resolve22(current, segment);
53325
54334
  let stat5;
53326
54335
  try {
53327
54336
  stat5 = lstatSync2(current);
@@ -53452,10 +54461,21 @@ ${escaping.map((m) => ` - ${m}`).join("\n")}`,
53452
54461
  "PS1003" /* INVALID_PATH */
53453
54462
  );
53454
54463
  }
54464
+ const protectedHits = plannedOutputs.map((output) => describeProtectedUserFile(resolve22(outputRoot, output.path))).filter((hit) => hit !== void 0);
54465
+ if (protectedHits.length > 0) {
54466
+ const protectedList = protectedHits.map((hit) => ` - ${hit.displayPath} (${hit.reason})`).join("\n");
54467
+ throw new PSError(
54468
+ `Refusing to write protected personal file(s):
54469
+ ${protectedList}
54470
+
54471
+ Personal override files are never compiler output. Use --resources to install generated agent and skill directories only.`,
54472
+ "PS1003" /* INVALID_PATH */
54473
+ );
54474
+ }
53455
54475
  if (!options.dryRun && !options.force && !isTTY()) {
53456
54476
  const preflightConflicts = [];
53457
54477
  for (const output of plannedOutputs) {
53458
- const outputPath = options.output ? resolve21(options.output, output.path) : resolve21(output.path);
54478
+ const outputPath = options.output ? resolve22(options.output, output.path) : resolve22(output.path);
53459
54479
  if (!existsSync14(outputPath)) continue;
53460
54480
  const existingContent = await readFileOrUndefined(outputPath);
53461
54481
  const contentMatches = existingContent !== void 0 && stripPromptScriptMarkers(existingContent) === stripPromptScriptMarkers(output.content);
@@ -53480,7 +54500,7 @@ Use --force to overwrite these files.`
53480
54500
  }
53481
54501
  }
53482
54502
  async function safeOverwrite(output, outputPath, existingContent, content) {
53483
- const outputRoot2 = options.output ? resolve21(options.output) : process.cwd();
54503
+ const outputRoot2 = options.output ? resolve22(options.output) : process.cwd();
53484
54504
  const rewritten = existingContent !== void 0 && await rewriteHookOutputIfUnchanged(
53485
54505
  outputPath,
53486
54506
  outputRoot2,
@@ -53496,7 +54516,7 @@ Use --force to overwrite these files.`
53496
54516
  return rewritten;
53497
54517
  }
53498
54518
  async function safeCreate(output, outputPath, content) {
53499
- const outputRoot2 = options.output ? resolve21(options.output) : process.cwd();
54519
+ const outputRoot2 = options.output ? resolve22(options.output) : process.cwd();
53500
54520
  const created = await createHookOutputSafely(outputPath, outputRoot2, content, output.mode);
53501
54521
  if (!created) {
53502
54522
  const msg = `Skipped '${outputPath}' because its output path was not safe to create.`;
@@ -53506,7 +54526,7 @@ Use --force to overwrite these files.`
53506
54526
  return created;
53507
54527
  }
53508
54528
  for (const output of plannedOutputs) {
53509
- const outputPath = options.output ? resolve21(options.output, output.path) : resolve21(output.path);
54529
+ const outputPath = options.output ? resolve22(options.output, output.path) : resolve22(output.path);
53510
54530
  const fileExists3 = existsSync14(outputPath);
53511
54531
  const existingContent = fileExists3 ? await readFileOrUndefined(outputPath) : void 0;
53512
54532
  const existingHookOutputOwned = existingContent !== void 0 && isPromptScriptOwnedHookOutput(output.path, existingContent);
@@ -53543,7 +54563,7 @@ Use --force to overwrite these files.`
53543
54563
  if (output.mode !== void 0 && existingContent !== void 0) {
53544
54564
  const modeUpdated = await rewriteHookOutputIfUnchanged(
53545
54565
  outputPath,
53546
- options.output ? resolve21(options.output) : process.cwd(),
54566
+ options.output ? resolve22(options.output) : process.cwd(),
53547
54567
  existingContent,
53548
54568
  existingContent,
53549
54569
  output.mode
@@ -53639,7 +54659,7 @@ function printWarnings(warnings) {
53639
54659
  }
53640
54660
  async function compileAllBuilds(options, services) {
53641
54661
  const logger = createCliLogger();
53642
- const projectRoot = options.cwd ? resolve21(options.cwd) : process.cwd();
54662
+ const projectRoot = options.cwd ? resolve22(options.cwd) : process.cwd();
53643
54663
  const configPath = resolveCompileConfigPath(projectRoot, options);
53644
54664
  const config = await loadEffectiveConfig(configPath);
53645
54665
  const buildNames = Object.keys(getBuildProfiles(config));
@@ -53713,7 +54733,7 @@ async function compileCommandWithResult(options, services) {
53713
54733
  const spinner = createSpinner("Loading configuration...").start();
53714
54734
  const logger = createCliLogger();
53715
54735
  try {
53716
- const projectRoot = options.cwd ? resolve21(options.cwd) : process.cwd();
54736
+ const projectRoot = options.cwd ? resolve22(options.cwd) : process.cwd();
53717
54737
  logger.verbose("Loading configuration...");
53718
54738
  const configPath = resolveCompileConfigPath(projectRoot, options);
53719
54739
  const config = await loadEffectiveConfig(configPath);
@@ -53728,6 +54748,17 @@ async function compileCommandWithResult(options, services) {
53728
54748
  } else {
53729
54749
  targets = parsedTargets;
53730
54750
  }
54751
+ const cliResources = options.resources && options.resources.length > 0 ? options.resources : void 0;
54752
+ const resourceSelection = parseResourceSelection(cliResources ?? config.output?.resources);
54753
+ if (resourceSelection.invalid.length > 0) {
54754
+ spinner.fail("Invalid resource selection");
54755
+ ConsoleOutput.error(
54756
+ `Unknown resource kind(s): ${resourceSelection.invalid.join(", ")}. Valid kinds: agents, skills, commands, mcp, hooks, plugins, main.`
54757
+ );
54758
+ process.exitCode = 1;
54759
+ return false;
54760
+ }
54761
+ const resourceKinds = new Set(resourceSelection.kinds);
53731
54762
  if (targets.length === 0) {
53732
54763
  spinner.fail("No compilation targets");
53733
54764
  ConsoleOutput.error(
@@ -53762,7 +54793,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53762
54793
  }
53763
54794
  }
53764
54795
  const lockfile = await loadCompileLockfile(projectRoot, logger);
53765
- const vendorDir = resolve21(projectRoot, ".promptscript/vendor");
54796
+ const vendorDir = resolve22(projectRoot, ".promptscript/vendor");
53766
54797
  let registryPath;
53767
54798
  let referenceRoots;
53768
54799
  if (options.registry) {
@@ -53784,7 +54815,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53784
54815
  if (config.includePromptScriptSkill !== false) {
53785
54816
  skillContent = await loadBundledSkillContent(logger);
53786
54817
  }
53787
- const localPath = resolve21(projectRoot, ".promptscript");
54818
+ const localPath = resolve22(projectRoot, ".promptscript");
53788
54819
  const compiler = new Compiler({
53789
54820
  resolver: {
53790
54821
  registryPath,
@@ -53806,7 +54837,7 @@ Disable one of the conflicting targets or assign a custom output path.`
53806
54837
  ignoreHashes: options.ignoreHashes
53807
54838
  });
53808
54839
  const entry = buildProfile?.entry ?? config.input?.entry;
53809
- const entryPath = entry ? resolve21(projectRoot, entry) : resolve21(localPath, "project.prs");
54840
+ const entryPath = entry ? resolve22(projectRoot, entry) : resolve22(localPath, "project.prs");
53810
54841
  if (!existsSync14(entryPath)) {
53811
54842
  spinner.fail("Entry file not found");
53812
54843
  ConsoleOutput.error(`File not found: ${entryPath}`);
@@ -53838,8 +54869,13 @@ Disable one of the conflicting targets or assign a custom output path.`
53838
54869
  output: resolveOutputBase(projectRoot, configuredOutput),
53839
54870
  force: options.force ?? config.output?.overwrite
53840
54871
  };
54872
+ if (resourceKinds.size === 0 && effectiveOptions.output === homedir4()) {
54873
+ ConsoleOutput.warning(
54874
+ `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.`
54875
+ );
54876
+ }
53841
54877
  const hasFactoryTarget = targets.some((target) => target.name === "factory");
53842
- const migrateFactoryHooks = options.migrateFactoryHooks !== false;
54878
+ const migrateFactoryHooks = options.migrateFactoryHooks !== false && resourceKinds.size === 0;
53843
54879
  const legacyMigration = hasFactoryTarget && migrateFactoryHooks ? await prepareLegacyFactoryMigration(result.outputs, effectiveOptions.output) : void 0;
53844
54880
  const legacySettingsPath = hasFactoryTarget && !migrateFactoryHooks ? await detectLegacyFactorySettingsHooks(effectiveOptions.output, true) : void 0;
53845
54881
  const finalized = await finalizeOutputPlan(result, {
@@ -53848,7 +54884,26 @@ Disable one of the conflicting targets or assign a custom output path.`
53848
54884
  logger,
53849
54885
  additionalOutputPaths: legacyMigration ? [".factory/hooks.json"] : []
53850
54886
  });
53851
- const writeResult = await writeOutputs(finalized.outputs, effectiveOptions, config, services);
54887
+ let effectiveOutputs = finalized.outputs;
54888
+ if (resourceKinds.size > 0) {
54889
+ const skillBaseDirs = /* @__PURE__ */ new Map();
54890
+ for (const target of targets) {
54891
+ if (target.config?.skillBaseDir) {
54892
+ skillBaseDirs.set(target.name, target.config.skillBaseDir);
54893
+ }
54894
+ }
54895
+ const filtered = filterOutputsByResources(
54896
+ finalized.outputPlan,
54897
+ finalized.outputs,
54898
+ resourceKinds,
54899
+ skillBaseDirs
54900
+ );
54901
+ effectiveOutputs = filtered.outputs;
54902
+ ConsoleOutput.info(
54903
+ `Resource-only compile: kept ${filtered.kept} file(s), omitted ${filtered.dropped}`
54904
+ );
54905
+ }
54906
+ const writeResult = await writeOutputs(effectiveOutputs, effectiveOptions, config, services);
53852
54907
  if (legacyMigration) {
53853
54908
  if (writeResult.skipped.includes(legacyMigration.hooksPath)) {
53854
54909
  throw new Error(
@@ -53862,30 +54917,38 @@ Disable one of the conflicting targets or assign a custom output path.`
53862
54917
  writeResult.created.includes(legacyMigration.hooksPath)
53863
54918
  );
53864
54919
  }
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) {
54920
+ if (resourceKinds.size > 0) {
53871
54921
  if (options.dryRun) {
53872
- ConsoleOutput.dryRun(`Would remove obsolete generated file: ${removedPath}`);
54922
+ ConsoleOutput.dryRun("Would skip managed cleanup for resource-only compile");
53873
54923
  } else {
53874
- ConsoleOutput.muted(`Removed obsolete generated file: ${removedPath}`);
54924
+ ConsoleOutput.muted("Skipped managed cleanup for resource-only compile");
53875
54925
  }
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}`);
54926
+ } else {
54927
+ const plannedOutputMap = new Map(finalized.outputs);
54928
+ const cleanupResult = await cleanupManagedOutputs(plannedOutputMap, {
54929
+ outputRoot: effectiveOptions.output,
54930
+ dryRun: options.dryRun
54931
+ });
54932
+ for (const removedPath of cleanupResult.removed) {
54933
+ if (options.dryRun) {
54934
+ ConsoleOutput.dryRun(`Would remove obsolete generated file: ${removedPath}`);
54935
+ } else {
54936
+ ConsoleOutput.muted(`Removed obsolete generated file: ${removedPath}`);
54937
+ }
53882
54938
  }
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}`);
54939
+ for (const removedDirectory of cleanupResult.removedDirectories) {
54940
+ if (options.dryRun) {
54941
+ ConsoleOutput.dryRun(`Would remove empty managed directory: ${removedDirectory}`);
54942
+ } else {
54943
+ ConsoleOutput.muted(`Removed empty managed directory: ${removedDirectory}`);
54944
+ }
54945
+ }
54946
+ for (const rewrittenPath of cleanupResult.rewritten ?? []) {
54947
+ if (options.dryRun) {
54948
+ ConsoleOutput.dryRun(`Would rewrite mixed managed hook file: ${rewrittenPath}`);
54949
+ } else {
54950
+ ConsoleOutput.muted(`Rewrote mixed managed hook file: ${rewrittenPath}`);
54951
+ }
53889
54952
  }
53890
54953
  }
53891
54954
  spinner.succeed("Compilation successful");
@@ -53953,10 +55016,10 @@ function watchForChanges(projectRoot, watchConfig, callback) {
53953
55016
  const includePatterns = watchConfig?.include ?? DEFAULT_WATCH_INCLUDE;
53954
55017
  const excludePatterns = watchConfig?.exclude ?? DEFAULT_WATCH_EXCLUDE;
53955
55018
  const matcherOptions = { dot: true, nocase: process.platform === "win32" };
53956
- const relativePath = (path2) => relative14(projectRoot, path2).replace(/\\/g, "/");
55019
+ const relativePath = (path2) => relative15(projectRoot, path2).replace(/\\/g, "/");
53957
55020
  const matches = (path2, patterns) => {
53958
- const relative22 = relativePath(path2);
53959
- return patterns.some((pattern) => minimatch(relative22, pattern, matcherOptions));
55021
+ const relative24 = relativePath(path2);
55022
+ return patterns.some((pattern) => minimatch(relative24, pattern, matcherOptions));
53960
55023
  };
53961
55024
  const shouldCompile = (path2) => matches(path2, includePatterns) && !matches(path2, excludePatterns);
53962
55025
  const watcher = chokidar.watch(projectRoot, {
@@ -54030,7 +55093,7 @@ function watchForChanges(projectRoot, watchConfig, callback) {
54030
55093
  // packages/cli/src/commands/validate.ts
54031
55094
  init_loader2();
54032
55095
  init_console();
54033
- import { resolve as resolve22, join as join15 } from "path";
55096
+ import { resolve as resolve23, join as join15 } from "path";
54034
55097
  import { existsSync as existsSync15, readFileSync as readFileSync4, writeFileSync, readdirSync } from "fs";
54035
55098
  init_src();
54036
55099
  init_src2();
@@ -54219,7 +55282,7 @@ function discoverPrsFiles(dir, strict = false, includeSymbolicLinks = false) {
54219
55282
  return results;
54220
55283
  }
54221
55284
  function loadValidationLockfile() {
54222
- const lockfilePath = resolve22("promptscript.lock");
55285
+ const lockfilePath = resolve23("promptscript.lock");
54223
55286
  if (!existsSync15(lockfilePath)) {
54224
55287
  return void 0;
54225
55288
  }
@@ -54242,7 +55305,7 @@ async function validateCommand2(options) {
54242
55305
  try {
54243
55306
  const config = await loadConfig();
54244
55307
  const lockfile = loadValidationLockfile();
54245
- const vendorDir = resolve22(".promptscript/vendor");
55308
+ const vendorDir = resolve23(".promptscript/vendor");
54246
55309
  if (!isJsonFormat) spinner.text = "Resolving registry...";
54247
55310
  const registry2 = await resolveRegistryPath(config, { vendorDir, lockfile });
54248
55311
  if (!isJsonFormat) spinner.text = "Validating...";
@@ -54264,7 +55327,7 @@ async function validateCommand2(options) {
54264
55327
  formatters: []
54265
55328
  // No formatters needed for validation only
54266
55329
  });
54267
- const entryPaths = options.files && options.files.length > 0 ? options.files.map((file) => resolve22(file)) : [resolve22(config.input?.entry ?? "./.promptscript/project.prs")];
55330
+ const entryPaths = options.files && options.files.length > 0 ? options.files.map((file) => resolve23(file)) : [resolve23(config.input?.entry ?? "./.promptscript/project.prs")];
54268
55331
  for (const entryPath of entryPaths) {
54269
55332
  if (!existsSync15(entryPath)) {
54270
55333
  handleEntryNotFound(entryPath, isJsonFormat, spinner);
@@ -54317,16 +55380,16 @@ function outputJsonResult(result) {
54317
55380
  console.log(JSON.stringify(result, null, 2));
54318
55381
  }
54319
55382
  async function runFix(requestedFiles) {
54320
- const files = requestedFiles && requestedFiles.length > 0 ? requestedFiles.map((file) => resolve22(file)) : discoverPrsFiles(".promptscript");
55383
+ const files = requestedFiles && requestedFiles.length > 0 ? requestedFiles.map((file) => resolve23(file)) : discoverPrsFiles(".promptscript");
54321
55384
  let fixedCount = 0;
54322
55385
  const configPath = findConfigFile();
54323
55386
  const config = configPath ? await loadConfig(configPath) : void 0;
54324
55387
  const lockfile = loadValidationLockfile();
54325
- const vendorDir = resolve22(".promptscript/vendor");
54326
- const registryPath = config ? (await resolveRegistryPath(config, { vendorDir, lockfile })).path : resolve22("./registry");
55388
+ const vendorDir = resolve23(".promptscript/vendor");
55389
+ const registryPath = config ? (await resolveRegistryPath(config, { vendorDir, lockfile })).path : resolve23("./registry");
54327
55390
  const resolver = new Resolver({
54328
55391
  registryPath,
54329
- localPath: resolve22("./.promptscript"),
55392
+ localPath: resolve23("./.promptscript"),
54330
55393
  vendorDir,
54331
55394
  lockfile,
54332
55395
  registries: config?.registries,
@@ -54340,7 +55403,7 @@ async function runFix(requestedFiles) {
54340
55403
  if (typeof declaredVersion !== "string") continue;
54341
55404
  let ast = parseResult.ast;
54342
55405
  try {
54343
- const resolved = await resolver.resolve(resolve22(filePath));
55406
+ const resolved = await resolver.resolve(resolve23(filePath));
54344
55407
  ast = resolved.ast ?? ast;
54345
55408
  } catch {
54346
55409
  }
@@ -54389,7 +55452,7 @@ init_loader2();
54389
55452
  init_console();
54390
55453
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
54391
55454
  import { existsSync as existsSync16 } from "fs";
54392
- import { resolve as resolve23, dirname as dirname17, relative as relative15 } from "path";
55455
+ import { resolve as resolve24, dirname as dirname17, relative as relative16 } from "path";
54393
55456
  async function pullCommand(options) {
54394
55457
  const spinner = createSpinner("Loading configuration...").start();
54395
55458
  try {
@@ -54409,10 +55472,10 @@ async function pullCommand(options) {
54409
55472
  spinner.text = `Pulling ${config.inherit}...`;
54410
55473
  const registry2 = await createRegistry(config, options, spinner);
54411
55474
  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)) {
55475
+ const destinationRoot = resolve24("./.promptscript/.inherited");
55476
+ const destPath = resolve24(destinationRoot, inheritPath);
55477
+ const destinationRelativePath = relative16(destinationRoot, destPath);
55478
+ if (destinationRelativePath.startsWith("..") || resolve24(destPath) === resolve24(destinationRoot)) {
54416
55479
  throw new Error(`Invalid inheritance path: ${config.inherit}`);
54417
55480
  }
54418
55481
  if (options.dryRun) {
@@ -54460,6 +55523,8 @@ async function createRegistry(config, options, spinner) {
54460
55523
  ref: gitRef ?? gitConfig.ref,
54461
55524
  path: gitConfig.path,
54462
55525
  auth: gitConfig.auth,
55526
+ timeout: gitConfig.timeout,
55527
+ fallbackUrl: gitConfig.fallbackUrl,
54463
55528
  cache: {
54464
55529
  enabled: !options.refresh && (config.registry.cache?.enabled ?? true),
54465
55530
  ttl: config.registry.cache?.ttl
@@ -54487,7 +55552,7 @@ async function createRegistry(config, options, spinner) {
54487
55552
  if (!existsSync16(registryPath)) {
54488
55553
  throw new Error(`Local registry path not found: ${registryPath}`);
54489
55554
  }
54490
- return createFileSystemRegistry(resolve23(registryPath));
55555
+ return createFileSystemRegistry(resolve24(registryPath));
54491
55556
  }
54492
55557
  function parseInheritVersion(inheritPath) {
54493
55558
  const versionIndex = inheritPath.lastIndexOf("@");
@@ -54546,7 +55611,7 @@ function handlePullError(error, spinner) {
54546
55611
  init_src();
54547
55612
  init_loader2();
54548
55613
  init_console();
54549
- import { resolve as resolve25 } from "node:path";
55614
+ import { resolve as resolve26 } from "node:path";
54550
55615
  import { readFile as readFile18 } from "node:fs/promises";
54551
55616
  import { existsSync as existsSync18 } from "node:fs";
54552
55617
  init_loader();
@@ -54555,7 +55620,7 @@ init_loader();
54555
55620
  import { createHash as createHash5 } from "node:crypto";
54556
55621
  import { existsSync as existsSync17 } from "node:fs";
54557
55622
  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";
55623
+ import { isAbsolute as isAbsolute15, relative as relative17, resolve as resolve25, sep as sep11 } from "node:path";
54559
55624
  var DIFF_SCHEMA_VERSION = 1;
54560
55625
  var DIFF_SCHEMA_URL = "https://getpromptscript.dev/schema/diff/v1.json";
54561
55626
  var CHANGE_KIND_ORDER = {
@@ -54574,19 +55639,19 @@ function normalizePath2(path2) {
54574
55639
  return path2.replaceAll("\\", "/");
54575
55640
  }
54576
55641
  function reportPath(outputRoot, path2) {
54577
- const absolutePath = resolve24(outputRoot, path2);
54578
- const relation = normalizePath2(relative16(outputRoot, absolutePath));
55642
+ const absolutePath = resolve25(outputRoot, path2);
55643
+ const relation = normalizePath2(relative17(outputRoot, absolutePath));
54579
55644
  if (!relation || relation === ".") return normalizePath2(path2);
54580
- if (relation === ".." || relation.startsWith("../") || isAbsolute14(relation)) {
55645
+ if (relation === ".." || relation.startsWith("../") || isAbsolute15(relation)) {
54581
55646
  return normalizePath2(path2);
54582
55647
  }
54583
55648
  return relation;
54584
55649
  }
54585
55650
  function reportSource(projectRoot, source) {
54586
55651
  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)) {
55652
+ if (!isAbsolute15(source)) return normalized;
55653
+ const relation = normalizePath2(relative17(projectRoot, source));
55654
+ if (relation && relation !== ".." && !relation.startsWith("../") && !isAbsolute15(relation)) {
54590
55655
  return relation;
54591
55656
  }
54592
55657
  return normalized;
@@ -54745,15 +55810,15 @@ function createReport(success, changes, warnings, errors, includeContent, unchan
54745
55810
  };
54746
55811
  }
54747
55812
  function findManagedOutputOwner(removedPath, outputs, outputRoot, entryPath, projectRoot) {
54748
- const candidate = resolve24(removedPath);
55813
+ const candidate = resolve25(removedPath);
54749
55814
  for (const output of outputs.values()) {
54750
55815
  const managedFile = (output.managedOutputFiles ?? []).some(
54751
- (file) => resolve24(outputRoot, file) === candidate
55816
+ (file) => resolve25(outputRoot, file) === candidate
54752
55817
  );
54753
55818
  const managedDirectory = (directory) => {
54754
- const directoryPath = resolve24(outputRoot, directory);
54755
- const relation = relative16(directoryPath, candidate);
54756
- return relation === "" || relation !== ".." && !relation.startsWith(`..${sep10}`) && !isAbsolute14(relation);
55819
+ const directoryPath = resolve25(outputRoot, directory);
55820
+ const relation = relative17(directoryPath, candidate);
55821
+ return relation === "" || relation !== ".." && !relation.startsWith(`..${sep11}`) && !isAbsolute15(relation);
54757
55822
  };
54758
55823
  if (managedFile || (output.managedOutputDirectories ?? []).some(managedDirectory)) {
54759
55824
  return outputIdentity(output, entryPath, projectRoot);
@@ -54772,8 +55837,8 @@ function createCompilationDiffErrorReport(errors, warnings, projectRoot, include
54772
55837
  );
54773
55838
  }
54774
55839
  async function buildCompilationDiff(options) {
54775
- const projectRoot = resolve24(options.projectRoot);
54776
- const outputRoot = resolve24(options.outputRoot);
55840
+ const projectRoot = resolve25(options.projectRoot);
55841
+ const outputRoot = resolve25(options.outputRoot);
54777
55842
  const includeContent = options.includeContent === true;
54778
55843
  const changes = [];
54779
55844
  const warnings = options.warnings.map((warning) => normalizeWarning(warning, projectRoot));
@@ -54782,7 +55847,7 @@ async function buildCompilationDiff(options) {
54782
55847
  for (const output of options.outputs.values()) {
54783
55848
  const identity = outputIdentity(output, options.entryPath, projectRoot);
54784
55849
  const path2 = reportPath(outputRoot, output.path);
54785
- const outputPath = resolve24(outputRoot, output.path);
55850
+ const outputPath = resolve25(outputRoot, output.path);
54786
55851
  const desiredContent = output.content;
54787
55852
  const common = {
54788
55853
  target: identity.target,
@@ -54954,7 +56019,7 @@ function resolveUniversalDir2(universalDir) {
54954
56019
  return { universalDir: ".agents" };
54955
56020
  }
54956
56021
  async function loadDiffLockfile(projectRoot) {
54957
- const lockfilePath = resolve25(projectRoot, "promptscript.lock");
56022
+ const lockfilePath = resolve26(projectRoot, "promptscript.lock");
54958
56023
  if (!existsSync18(lockfilePath)) {
54959
56024
  return void 0;
54960
56025
  }
@@ -54976,7 +56041,7 @@ function printNewFilePreview(content, showFull, pager) {
54976
56041
  }
54977
56042
  }
54978
56043
  async function compareOutput(output, outputRoot, showFull, pager) {
54979
- const outputPath = resolve25(outputRoot, output.path);
56044
+ const outputPath = resolve26(outputRoot, output.path);
54980
56045
  const newContent = output.content;
54981
56046
  if (!existsSync18(outputPath)) {
54982
56047
  pager.write(chalk2.green(`+ ${outputPath} (new file)`));
@@ -55049,7 +56114,7 @@ async function diffCommand(options) {
55049
56114
  return;
55050
56115
  }
55051
56116
  const lockfile = await loadDiffLockfile(projectRoot);
55052
- const vendorDir = resolve25(projectRoot, ".promptscript/vendor");
56117
+ const vendorDir = resolve26(projectRoot, ".promptscript/vendor");
55053
56118
  if (!isJsonFormat) spinner.text = "Resolving registry...";
55054
56119
  const registry2 = await resolveRegistryPath(config, {
55055
56120
  vendorDir,
@@ -55060,11 +56125,11 @@ async function diffCommand(options) {
55060
56125
  const logger = createDiffLogger(isJsonFormat);
55061
56126
  const prettierOptions = await resolvePrettierOptions(config, projectRoot);
55062
56127
  const skillContent = config.includePromptScriptSkill === false ? void 0 : await loadBundledSkillContent(logger);
55063
- const outputRoot = resolve25(projectRoot, buildProfile?.output ?? config.output?.baseDir ?? ".");
56128
+ const outputRoot = resolve26(projectRoot, buildProfile?.output ?? config.output?.baseDir ?? ".");
55064
56129
  const compiler = new Compiler({
55065
56130
  resolver: {
55066
56131
  registryPath: registry2.path,
55067
- localPath: resolve25(projectRoot, ".promptscript"),
56132
+ localPath: resolve26(projectRoot, ".promptscript"),
55068
56133
  projectRoot,
55069
56134
  vendorDir,
55070
56135
  readOnly: true,
@@ -55081,7 +56146,7 @@ async function diffCommand(options) {
55081
56146
  logger,
55082
56147
  skillContent
55083
56148
  });
55084
- const entryPath = resolve25(
56149
+ const entryPath = resolve26(
55085
56150
  projectRoot,
55086
56151
  buildProfile?.entry ?? config.input?.entry ?? "./.promptscript/project.prs"
55087
56152
  );
@@ -55234,7 +56299,7 @@ function printSimpleDiff(existingLines, newLines, showFull, pager) {
55234
56299
  // packages/cli/src/commands/check.ts
55235
56300
  import { existsSync as existsSync19 } from "fs";
55236
56301
  import { readFile as readFile19 } from "fs/promises";
55237
- import { resolve as resolve26 } from "path";
56302
+ import { resolve as resolve27 } from "path";
55238
56303
  import { parse as parseYaml8 } from "yaml";
55239
56304
  init_src();
55240
56305
  init_loader2();
@@ -55393,7 +56458,7 @@ async function checkCommand(_options) {
55393
56458
  }
55394
56459
  const effectiveEntries = getEffectiveEntryPaths(config);
55395
56460
  for (const entry of effectiveEntries) {
55396
- const entryPath = resolve26(entry);
56461
+ const entryPath = resolve27(entry);
55397
56462
  const name = entry === (config.input?.entry ?? ".promptscript/project.prs") ? "Entry file" : `Entry file (${entry})`;
55398
56463
  if (existsSync19(entryPath)) {
55399
56464
  results.push({
@@ -55418,7 +56483,7 @@ async function checkCommand(_options) {
55418
56483
  });
55419
56484
  hasWarnings = true;
55420
56485
  } else if (config.registry?.path && !config.registry.git) {
55421
- const registryPath = resolve26(config.registry.path);
56486
+ const registryPath = resolve27(config.registry.path);
55422
56487
  if (existsSync19(registryPath)) {
55423
56488
  results.push({
55424
56489
  name: "Registry path",
@@ -55503,10 +56568,10 @@ async function checkCommand(_options) {
55503
56568
  message: rootTargets.length === 0 ? `${targetNames.size} target(s) configured in build profiles` : `${targetNames.size} target(s) configured`
55504
56569
  });
55505
56570
  }
55506
- const existingEntries = effectiveEntries.filter((entry) => existsSync19(resolve26(entry)));
56571
+ const existingEntries = effectiveEntries.filter((entry) => existsSync19(resolve27(entry)));
55507
56572
  if (existingEntries.length > 0) {
55508
56573
  try {
55509
- const lockfilePath = resolve26("promptscript.lock");
56574
+ const lockfilePath = resolve27("promptscript.lock");
55510
56575
  let lockfile;
55511
56576
  if (existsSync19(lockfilePath)) {
55512
56577
  const parsed = parseYaml8(await readFile19(lockfilePath, "utf-8"), {
@@ -55522,12 +56587,12 @@ async function checkCommand(_options) {
55522
56587
  message: "promptscript.lock"
55523
56588
  });
55524
56589
  }
55525
- const vendorDir = resolve26(".promptscript/vendor");
56590
+ const vendorDir = resolve27(".promptscript/vendor");
55526
56591
  const registry2 = await resolveRegistryPath(config, { vendorDir, lockfile });
55527
56592
  const compiler = new Compiler({
55528
56593
  resolver: {
55529
- registryPath: resolve26(registry2.path),
55530
- localPath: resolve26(".promptscript"),
56594
+ registryPath: resolve27(registry2.path),
56595
+ localPath: resolve27(".promptscript"),
55531
56596
  vendorDir,
55532
56597
  referenceRoots: registry2.repositoryUrl && registry2.repositoryPath ? { [registry2.repositoryUrl]: [registry2.repositoryPath] } : void 0,
55533
56598
  lockfile,
@@ -55542,7 +56607,7 @@ async function checkCommand(_options) {
55542
56607
  for (const entry of existingEntries) {
55543
56608
  const name = entry === (config.input?.entry ?? ".promptscript/project.prs") ? "Project resolution" : `Project resolution (${entry})`;
55544
56609
  try {
55545
- const result = await compiler.compile(resolve26(entry));
56610
+ const result = await compiler.compile(resolve27(entry));
55546
56611
  if (result.errors.length > 0) {
55547
56612
  results.push({
55548
56613
  name,
@@ -55629,7 +56694,7 @@ import { dirname as dirname18 } from "path";
55629
56694
 
55630
56695
  // packages/cli/src/utils/version-check.ts
55631
56696
  init_console();
55632
- import { homedir as homedir3 } from "os";
56697
+ import { homedir as homedir5 } from "os";
55633
56698
  import { join as join16 } from "path";
55634
56699
  import { readFile as readFile20, writeFile as writeFile5, mkdir as mkdir6 } from "fs/promises";
55635
56700
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@promptscript/cli/latest";
@@ -55679,7 +56744,7 @@ function comparePrerelease2(current, latest) {
55679
56744
  return 0;
55680
56745
  }
55681
56746
  function getCacheDir() {
55682
- return join16(homedir3(), ".promptscript", ".cache");
56747
+ return join16(homedir5(), ".promptscript", ".cache");
55683
56748
  }
55684
56749
  function getCachePath() {
55685
56750
  return join16(getCacheDir(), "version.json");
@@ -56107,7 +57172,7 @@ Then reference configs with \`@inherit\` or \`@use\`:
56107
57172
  }
56108
57173
 
56109
57174
  // packages/cli/src/utils/resolve-target-directory.ts
56110
- import { join as join18, resolve as resolve27 } from "path";
57175
+ import { join as join18, resolve as resolve28 } from "path";
56111
57176
 
56112
57177
  // packages/cli/src/utils/slugify.ts
56113
57178
  function slugify(name) {
@@ -56118,7 +57183,7 @@ function slugify(name) {
56118
57183
  async function resolveTargetDirectory(options, services) {
56119
57184
  const { cwd, directoryArg, registryName, nonInteractive } = options;
56120
57185
  if (directoryArg) {
56121
- return resolve27(directoryArg);
57186
+ return resolve28(directoryArg);
56122
57187
  }
56123
57188
  const empty = await isDirectoryEmpty(cwd, services.fs);
56124
57189
  if (empty && nonInteractive) {
@@ -56237,7 +57302,7 @@ async function registryInitCommand(directory, options, services = createDefaultS
56237
57302
  // packages/cli/src/commands/registry/validate.ts
56238
57303
  init_services();
56239
57304
  init_console();
56240
- import { resolve as resolve28 } from "path";
57305
+ import { resolve as resolve29 } from "path";
56241
57306
 
56242
57307
  // packages/cli/src/utils/registry-validator.ts
56243
57308
  import { join as join19 } from "path";
@@ -56442,7 +57507,7 @@ async function registryValidateCommand(path2, options, services = createDefaultS
56442
57507
  process.exitCode = 1;
56443
57508
  return;
56444
57509
  }
56445
- const registryPath = resolve28(path2 ?? ".");
57510
+ const registryPath = resolve29(path2 ?? ".");
56446
57511
  const spinner = createSpinner("Validating registry...").start();
56447
57512
  try {
56448
57513
  const result = await validateRegistry(registryPath, services);
@@ -56494,10 +57559,10 @@ async function registryValidateCommand(path2, options, services = createDefaultS
56494
57559
  // packages/cli/src/commands/registry/publish.ts
56495
57560
  init_services();
56496
57561
  init_console();
56497
- import { resolve as resolve29, join as join20 } from "path";
57562
+ import { resolve as resolve30, join as join20 } from "path";
56498
57563
  import { execFileSync as execFileSync2 } from "child_process";
56499
57564
  async function registryPublishCommand(path2, options, services = createDefaultServices()) {
56500
- const registryPath = resolve29(path2 ?? ".");
57565
+ const registryPath = resolve30(path2 ?? ".");
56501
57566
  try {
56502
57567
  if (!options.force) {
56503
57568
  const spinner2 = createSpinner("Validating registry...").start();
@@ -56535,7 +57600,7 @@ async function registryPublishCommand(path2, options, services = createDefaultSe
56535
57600
  let gitRoot;
56536
57601
  try {
56537
57602
  git("rev-parse", "--is-inside-work-tree");
56538
- gitRoot = resolve29(git("rev-parse", "--show-toplevel"));
57603
+ gitRoot = resolve30(git("rev-parse", "--show-toplevel"));
56539
57604
  } catch {
56540
57605
  spinner.fail("Not a git repository");
56541
57606
  ConsoleOutput.muted("Initialize with: git init");
@@ -56813,7 +57878,7 @@ init_console();
56813
57878
  init_src();
56814
57879
  import { writeFile as writeFile7, readFile as readFile25 } from "fs/promises";
56815
57880
  import { existsSync as existsSync22 } from "fs";
56816
- import { resolve as resolve32 } from "path";
57881
+ import { resolve as resolve33 } from "path";
56817
57882
  import { parse as parseYaml12, stringify as stringifyYaml3 } from "yaml";
56818
57883
 
56819
57884
  // packages/cli/src/commands/lock-scanner.ts
@@ -56911,16 +57976,16 @@ async function collectRemoteImports(entryPath, options) {
56911
57976
  }
56912
57977
 
56913
57978
  // packages/cli/src/commands/lock-references.ts
56914
- import { homedir as homedir4 } from "os";
56915
- import { resolve as resolve31, join as join22 } from "path";
57979
+ import { homedir as homedir6 } from "os";
57980
+ import { resolve as resolve32, join as join22 } from "path";
56916
57981
 
56917
57982
  // packages/cli/src/commands/lock-reference-scanner.ts
56918
57983
  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";
57984
+ import { dirname as dirname20, isAbsolute as isAbsolute16, relative as relative18, resolve as resolve31, sep as sep12 } from "path";
56920
57985
  async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (/* @__PURE__ */ new Date()).toISOString()) {
56921
57986
  const references = {};
56922
57987
  const sortedRoots = [...roots].sort(
56923
- (left, right) => resolve30(right.cachePath).length - resolve30(left.cachePath).length
57988
+ (left, right) => resolve31(right.cachePath).length - resolve31(left.cachePath).length
56924
57989
  );
56925
57990
  for (const block of ast.blocks) {
56926
57991
  if (block.name !== "skills" || block.content.type !== "ObjectContent") {
@@ -56934,7 +57999,7 @@ async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (
56934
57999
  if (!Array.isArray(refs)) continue;
56935
58000
  for (const ref of refs) {
56936
58001
  if (typeof ref !== "string") continue;
56937
- const referencePath = isAbsolute15(ref) ? resolve30(ref) : resolve30(dirname20(block.loc.file), ref);
58002
+ const referencePath = isAbsolute16(ref) ? resolve31(ref) : resolve31(dirname20(block.loc.file), ref);
56938
58003
  const root = sortedRoots.find(
56939
58004
  (candidate) => isInsideCachePath(referencePath, candidate.cachePath)
56940
58005
  );
@@ -56942,7 +58007,7 @@ async function collectRegistryReferences(ast, roots, existing = {}, lockedAt = (
56942
58007
  if (!await isRealPathInside(referencePath, root.cachePath)) {
56943
58008
  throw new Error(`Registry reference is missing or escapes its repository: ${ref}`);
56944
58009
  }
56945
- const relativePath = relative17(resolve30(root.cachePath), referencePath).split(sep11).join("/");
58010
+ const relativePath = relative18(resolve31(root.cachePath), referencePath).split(sep12).join("/");
56946
58011
  const key = buildReferenceKey(root.repoUrl, relativePath, root.version);
56947
58012
  const hash = hashContent(await readFile24(referencePath));
56948
58013
  const previous = existing[key];
@@ -56962,9 +58027,9 @@ function normalizeRepository(value) {
56962
58027
  }
56963
58028
  async function generateLockfileReferences(config, entryPath, localPath, lockfile, imports) {
56964
58029
  const registry2 = await resolveRegistryPath(config, { lockfile });
56965
- const cacheDir = join22(homedir4(), ".promptscript", "cache");
58030
+ const cacheDir = join22(homedir6(), ".promptscript", "cache");
56966
58031
  const resolver = new Resolver({
56967
- registryPath: resolve31(registry2.path),
58032
+ registryPath: resolve32(registry2.path),
56968
58033
  localPath,
56969
58034
  lockfile,
56970
58035
  registries: config.registries,
@@ -57095,14 +58160,14 @@ async function lockCommand(options) {
57095
58160
  }
57096
58161
  }
57097
58162
  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");
58163
+ const localPath = resolve33(projectRoot, ".promptscript");
58164
+ const entryPath = config.input?.entry ? resolve33(projectRoot, config.input.entry) : resolve33(localPath, "project.prs");
57100
58165
  const registry2 = await resolveRegistryPath(config, {
57101
58166
  ...!options.update && Object.keys(existing.dependencies).length > 0 ? { lockfile: existing } : {}
57102
58167
  });
57103
58168
  const scannedImports = await collectRemoteImports(entryPath, {
57104
58169
  localPath,
57105
- registryPath: resolve32(registry2.path),
58170
+ registryPath: resolve33(registry2.path),
57106
58171
  registries: config.registries,
57107
58172
  strict: true
57108
58173
  });
@@ -57116,7 +58181,8 @@ async function lockCommand(options) {
57116
58181
  requestedDependencies.set(defaultGitRegistry.url, {
57117
58182
  versions: /* @__PURE__ */ new Set([defaultGitRegistry.ref ?? "main"]),
57118
58183
  ...defaultGitRegistry.fallbackUrl ? { fallbackUrl: defaultGitRegistry.fallbackUrl } : {},
57119
- ...defaultGitRegistry.auth ? { auth: defaultGitRegistry.auth } : {}
58184
+ ...defaultGitRegistry.auth ? { auth: defaultGitRegistry.auth } : {},
58185
+ ...defaultGitRegistry.timeout !== void 0 ? { timeout: defaultGitRegistry.timeout } : {}
57120
58186
  });
57121
58187
  }
57122
58188
  for (const [, entry] of aliasEntries) {
@@ -57128,6 +58194,9 @@ async function lockCommand(options) {
57128
58194
  if (typeof entry !== "string" && entry.fallbackUrl) {
57129
58195
  requested.fallbackUrl = entry.fallbackUrl;
57130
58196
  }
58197
+ if (typeof entry !== "string" && entry.timeout !== void 0) {
58198
+ requested.timeout = entry.timeout;
58199
+ }
57131
58200
  requestedDependencies.set(repoUrl, requested);
57132
58201
  }
57133
58202
  for (const imp of scannedImports) {
@@ -57152,7 +58221,8 @@ async function lockCommand(options) {
57152
58221
  previous,
57153
58222
  options.update === true && matchingRepos.includes(repoUrl),
57154
58223
  requested.fallbackUrl,
57155
- requested.auth
58224
+ requested.auth,
58225
+ { timeout: requested.timeout }
57156
58226
  );
57157
58227
  if (previous?.skills?.length && previous.commit !== resolved.commit) {
57158
58228
  throw new Error(
@@ -57322,7 +58392,7 @@ import {
57322
58392
  writeFile as writeFile8
57323
58393
  } from "fs/promises";
57324
58394
  import { existsSync as existsSync23 } from "fs";
57325
- import { basename as basename15, dirname as dirname21, join as join23, resolve as resolve33 } from "path";
58395
+ import { basename as basename15, dirname as dirname21, join as join23, resolve as resolve34 } from "path";
57326
58396
  import { parse as parseYaml13 } from "yaml";
57327
58397
  var VENDOR_DIR = ".promptscript/vendor";
57328
58398
  async function vendorSyncCommand(options) {
@@ -57343,7 +58413,7 @@ async function vendorSyncCommand(options) {
57343
58413
  spinner.succeed(`Dry run completed (${deps.length} dependencies ready)`);
57344
58414
  return;
57345
58415
  }
57346
- const vendorDir = resolve33(VENDOR_DIR);
58416
+ const vendorDir = resolve34(VENDOR_DIR);
57347
58417
  await validateVendorParentDirectory(vendorDir, true);
57348
58418
  const recoveredBackup = await recoverVendorDirectory(vendorDir);
57349
58419
  if (recoveredBackup) {
@@ -57416,7 +58486,7 @@ async function vendorCheckCommand(_options) {
57416
58486
  }
57417
58487
  const deps = getRepositoryDependencies(lockfile);
57418
58488
  validateDependencyPaths(deps);
57419
- const vendorDir = resolve33(VENDOR_DIR);
58489
+ const vendorDir = resolve34(VENDOR_DIR);
57420
58490
  await validateVendorParentDirectory(vendorDir, false);
57421
58491
  const recoveredBackup = await recoverVendorDirectory(vendorDir);
57422
58492
  if (recoveredBackup) {
@@ -57597,10 +58667,10 @@ init_loader2();
57597
58667
  init_user_config();
57598
58668
  init_console();
57599
58669
  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";
58670
+ import { join as join24, resolve as resolve35 } from "path";
58671
+ import { homedir as homedir7 } from "os";
57602
58672
  import { parse as parseYaml14 } from "yaml";
57603
- var CACHE_BASE = join24(homedir5(), ".promptscript", "cache");
58673
+ var CACHE_BASE = join24(homedir7(), ".promptscript", "cache");
57604
58674
  var LOCKFILE_PATH2 = "promptscript.lock";
57605
58675
  var VENDOR_DIR2 = ".promptscript/vendor";
57606
58676
  function loadLockfile2() {
@@ -57609,7 +58679,7 @@ function loadLockfile2() {
57609
58679
  }
57610
58680
  const parsed = parseYaml14(readFileSync5(LOCKFILE_PATH2, "utf-8"), { maxAliasCount: 100 });
57611
58681
  if (!isValidLockfile(parsed)) {
57612
- throw new Error(`Invalid lockfile: ${resolve34(LOCKFILE_PATH2)}`);
58682
+ throw new Error(`Invalid lockfile: ${resolve35(LOCKFILE_PATH2)}`);
57613
58683
  }
57614
58684
  return parsed;
57615
58685
  }
@@ -57710,14 +58780,14 @@ async function resolveCommand(importPath, options) {
57710
58780
  }
57711
58781
  const reference = parseImportReference(importPath);
57712
58782
  const loader = new FileLoader({
57713
- registryPath: resolve34(config.registry?.path ?? "./registry"),
57714
- localPath: resolve34("./.promptscript"),
58783
+ registryPath: resolve35(config.registry?.path ?? "./registry"),
58784
+ localPath: resolve35("./.promptscript"),
57715
58785
  registries
57716
58786
  });
57717
- const resolvedReference = loader.resolveRef(reference, resolve34(".promptscript/project.prs"));
58787
+ const resolvedReference = loader.resolveRef(reference, resolve35(".promptscript/project.prs"));
57718
58788
  const marker = parseRegistryMarker(resolvedReference);
57719
58789
  if (!marker) {
57720
- const localPath = resolve34(resolvedReference);
58790
+ const localPath = resolve35(resolvedReference);
57721
58791
  printResolution(
57722
58792
  {
57723
58793
  import: importPath,
@@ -57738,7 +58808,7 @@ async function resolveCommand(importPath, options) {
57738
58808
  const cacheVersion = marker.version || "latest";
57739
58809
  const registryCache = new RegistryCache(CACHE_BASE);
57740
58810
  const cachePath = registryCache.getCachePath(marker.repoUrl, cacheVersion);
57741
- const vendorDir = resolve34(VENDOR_DIR2);
58811
+ const vendorDir = resolve35(VENDOR_DIR2);
57742
58812
  const manifest = await loadVendorManifest(vendorDir);
57743
58813
  let repositoryPath = cachePath;
57744
58814
  let source = await registryCache.has(marker.repoUrl, cacheVersion) ? "cache" : "missing";
@@ -57811,28 +58881,28 @@ async function resolveCommand(importPath, options) {
57811
58881
 
57812
58882
  // packages/cli/src/commands/inspect.ts
57813
58883
  import { existsSync as existsSync25 } from "fs";
57814
- import { resolve as resolve35, basename as basename16 } from "path";
58884
+ import { resolve as resolve36, basename as basename16 } from "path";
57815
58885
  init_loader2();
57816
58886
  init_console();
57817
58887
  async function inspectCommand(skillName, options) {
57818
58888
  const isJson = options.format === "json";
57819
58889
  const spinner = isJson ? createSpinner("").stop() : createSpinner("Resolving...").start();
57820
58890
  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;
58891
+ const projectRoot = options.cwd ? resolve36(options.cwd) : process.cwd();
58892
+ const configPath = options.config ? resolve36(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve36(projectRoot, file)).find((file) => existsSync25(file)) : void 0;
57823
58893
  if (options.cwd && !configPath) {
57824
58894
  throw new Error(`No PromptScript configuration found in ${projectRoot}. Run: prs init`);
57825
58895
  }
57826
58896
  const config = await loadConfig(configPath);
57827
58897
  const registry2 = await resolveRegistryPath(config);
57828
- const localPath = resolve35(projectRoot, ".promptscript");
58898
+ const localPath = resolve36(projectRoot, ".promptscript");
57829
58899
  const resolver = new Resolver({
57830
- registryPath: registry2.isRemote ? registry2.path : resolve35(projectRoot, registry2.path),
58900
+ registryPath: registry2.isRemote ? registry2.path : resolve36(projectRoot, registry2.path),
57831
58901
  localPath,
57832
58902
  projectRoot,
57833
58903
  registries: config.registries
57834
58904
  });
57835
- const entryPath = resolve35(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
58905
+ const entryPath = resolve36(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
57836
58906
  if (!existsSync25(entryPath)) {
57837
58907
  spinner.stop();
57838
58908
  ConsoleOutput.error(`Entry file not found: ${entryPath}`);
@@ -58010,7 +59080,7 @@ function outputJson(skillName, skill, trace, sealed, composedFrom, baseSource) {
58010
59080
  // packages/cli/src/commands/explain.ts
58011
59081
  init_src();
58012
59082
  import { existsSync as existsSync26 } from "fs";
58013
- import { relative as relative18, resolve as resolve36 } from "path";
59083
+ import { relative as relative19, resolve as resolve37 } from "path";
58014
59084
  init_loader2();
58015
59085
  init_console();
58016
59086
  function normalizePath3(path2) {
@@ -58091,7 +59161,7 @@ function findMatches(ast, trace, requestedPath) {
58091
59161
  }
58092
59162
  function sanitizePath(path2, options) {
58093
59163
  if (options.absolutePaths || !path2.startsWith("/")) return path2;
58094
- const relativePath = relative18(options.projectRoot, path2);
59164
+ const relativePath = relative19(options.projectRoot, path2);
58095
59165
  return relativePath || ".";
58096
59166
  }
58097
59167
  function sanitizeText(value, options) {
@@ -58221,24 +59291,24 @@ async function explainCommand(targetPath, options) {
58221
59291
  const isJson = options.format === "json";
58222
59292
  const spinner = isJson ? createSpinner("").stop() : createSpinner("Resolving...").start();
58223
59293
  const outputOptions = {
58224
- projectRoot: resolve36(options.cwd ?? process.cwd()),
59294
+ projectRoot: resolve37(options.cwd ?? process.cwd()),
58225
59295
  absolutePaths: options.absolutePaths === true
58226
59296
  };
58227
59297
  try {
58228
59298
  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;
59299
+ const configPath = options.config ? resolve37(projectRoot, options.config) : options.cwd ? CONFIG_FILES.map((file) => resolve37(projectRoot, file)).find((file) => existsSync26(file)) : void 0;
58230
59300
  if (options.cwd && !configPath) {
58231
59301
  throw new Error(`No PromptScript configuration found in ${projectRoot}. Run: prs init`);
58232
59302
  }
58233
59303
  const config = await loadConfig(configPath);
58234
59304
  const registry2 = await resolveRegistryPath(config);
58235
59305
  const resolver = new Resolver({
58236
- registryPath: registry2.isRemote ? registry2.path : resolve36(projectRoot, registry2.path),
58237
- localPath: resolve36(projectRoot, ".promptscript"),
59306
+ registryPath: registry2.isRemote ? registry2.path : resolve37(projectRoot, registry2.path),
59307
+ localPath: resolve37(projectRoot, ".promptscript"),
58238
59308
  projectRoot,
58239
59309
  registries: config.registries
58240
59310
  });
58241
- const entryPath = resolve36(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
59311
+ const entryPath = resolve37(projectRoot, config.input?.entry ?? ".promptscript/project.prs");
58242
59312
  if (!existsSync26(entryPath)) {
58243
59313
  spinner.stop();
58244
59314
  ConsoleOutput.error(`Entry file not found: ${sanitizePath(entryPath, outputOptions)}`);
@@ -58300,7 +59370,7 @@ init_console();
58300
59370
  // packages/cli/src/commands/import.ts
58301
59371
  init_src3();
58302
59372
  init_console();
58303
- import { resolve as resolve37, join as join25, basename as basename17 } from "path";
59373
+ import { resolve as resolve38, join as join25, basename as basename17 } from "path";
58304
59374
  import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync27 } from "fs";
58305
59375
  import { stat as stat2, readdir as readdir8, readFile as readFile27, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
58306
59376
  function parseInstructionFrontmatter(content) {
@@ -58355,7 +59425,7 @@ async function importDirectory(dirPath) {
58355
59425
  return lines.join("\n");
58356
59426
  }
58357
59427
  async function importCommand(file, options) {
58358
- const filepath = resolve37(file);
59428
+ const filepath = resolve38(file);
58359
59429
  const inputStat = await stat2(filepath).catch(() => null);
58360
59430
  if (inputStat?.isDirectory()) {
58361
59431
  try {
@@ -58364,7 +59434,7 @@ async function importCommand(file, options) {
58364
59434
  console.log(prsContent);
58365
59435
  return;
58366
59436
  }
58367
- const outputDir = resolve37(options.output ?? ".promptscript");
59437
+ const outputDir = resolve38(options.output ?? ".promptscript");
58368
59438
  const outputPath = join25(outputDir, "imported.prs");
58369
59439
  if (existsSync27(outputPath) && !options.force) {
58370
59440
  reportOutputConflict(outputPath);
@@ -58398,11 +59468,11 @@ async function importCommand(file, options) {
58398
59468
  console.log(result.prsContent);
58399
59469
  return;
58400
59470
  }
58401
- const outputDir = resolve37(options.output ?? ".promptscript");
59471
+ const outputDir = resolve38(options.output ?? ".promptscript");
58402
59472
  if (!existsSync27(outputDir)) {
58403
59473
  mkdirSync(outputDir, { recursive: true });
58404
59474
  }
58405
- const outputPath = resolve37(outputDir, "imported.prs");
59475
+ const outputPath = resolve38(outputDir, "imported.prs");
58406
59476
  if (existsSync27(outputPath) && !options.force) {
58407
59477
  spinner.stop();
58408
59478
  reportOutputConflict(outputPath);
@@ -58772,11 +59842,11 @@ init_console();
58772
59842
  // packages/cli/src/telemetry/config.ts
58773
59843
  import { existsSync as existsSync30 } from "node:fs";
58774
59844
  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";
59845
+ import { dirname as dirname22, isAbsolute as isAbsolute17, join as join29, resolve as resolve39 } from "node:path";
58776
59846
  import { parseDocument as parseDocument2 } from "yaml";
58777
59847
 
58778
59848
  // packages/telemetry/src/config.ts
58779
- import { homedir as homedir6 } from "node:os";
59849
+ import { homedir as homedir8 } from "node:os";
58780
59850
  import { join as join27 } from "node:path";
58781
59851
  var DEFAULT_TELEMETRY_ENDPOINT = "https://telemetry.guziak.net/v1/events";
58782
59852
  function isDisabled(value) {
@@ -58825,7 +59895,7 @@ function resolveTelemetryConfig(input2 = {}) {
58825
59895
  return {
58826
59896
  enabled: vetoes.length === 0,
58827
59897
  endpoint,
58828
- cacheDirectory: input2.cacheDirectory ?? join27(homedir6(), ".promptscript", ".cache"),
59898
+ cacheDirectory: input2.cacheDirectory ?? join27(homedir8(), ".promptscript", ".cache"),
58829
59899
  vetoes
58830
59900
  };
58831
59901
  }
@@ -58911,7 +59981,8 @@ var promptscript_v1_default = {
58911
59981
  "mimo",
58912
59982
  "deep-agents",
58913
59983
  "forgecode",
58914
- "hermes"
59984
+ "hermes",
59985
+ "gitlab-duo"
58915
59986
  ],
58916
59987
  limits: {
58917
59988
  maxEvents: 25,
@@ -59433,7 +60504,7 @@ var FLUSH_BUDGET_MS = 5e3;
59433
60504
  var MAX_BATCHES_PER_FLUSH = 3;
59434
60505
  var COMPLETION_RETRIES = 20;
59435
60506
  function delay(milliseconds) {
59436
- return new Promise((resolve42) => setTimeout(resolve42, milliseconds));
60507
+ return new Promise((resolve43) => setTimeout(resolve43, milliseconds));
59437
60508
  }
59438
60509
  async function persistCompletion(cacheDirectory, remainingRecords, rejectedRecords) {
59439
60510
  for (let attempt = 0; attempt < COMPLETION_RETRIES; attempt += 1) {
@@ -59678,7 +60749,7 @@ function runtimeMetadata(appVersion, input2 = {}) {
59678
60749
  init_loader2();
59679
60750
  init_user_config();
59680
60751
  function resolveFrom(baseDirectory, value) {
59681
- return isAbsolute16(value) ? value : resolve38(baseDirectory, value);
60752
+ return isAbsolute17(value) ? value : resolve39(baseDirectory, value);
59682
60753
  }
59683
60754
  function findProjectConfig(cwd, customPath) {
59684
60755
  if (customPath !== void 0) {
@@ -59723,7 +60794,7 @@ async function readUserTelemetrySetting(configPath) {
59723
60794
  }
59724
60795
  }
59725
60796
  async function resolveCliTelemetryConfig(options = {}) {
59726
- const cwd = options.cwd === void 0 ? process.cwd() : resolve38(process.cwd(), options.cwd);
60797
+ const cwd = options.cwd === void 0 ? process.cwd() : resolve39(process.cwd(), options.cwd);
59727
60798
  const userSetting = await readUserTelemetrySetting(options.userConfigPath ?? USER_CONFIG_PATH);
59728
60799
  const projectPath = findProjectConfig(cwd, options.config);
59729
60800
  const environmentConfig = process.env["PROMPTSCRIPT_CONFIG"];
@@ -59937,13 +61008,41 @@ import {
59937
61008
  chmod,
59938
61009
  open as open4,
59939
61010
  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";
61011
+ stat as stat3,
61012
+ cp,
61013
+ mkdir as mkdir11
61014
+ } from "node:fs/promises";
61015
+ 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";
61016
+ import { existsSync as existsSync31 } from "node:fs";
61017
+ import { tmpdir as tmpdir3, homedir as homedir9 } from "node:os";
59945
61018
  import { parse as parseYaml15, stringify as stringifyYaml4 } from "yaml";
59946
61019
  var REMOTE_SOURCE_PATTERN = /^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}\/.+/;
61020
+ var MANAGED_SKILLS_DIR = join30(".promptscript", "skills");
61021
+ var USE_PATH_PATTERN = /^\.{1,2}\/[a-zA-Z0-9_/.-]+$/;
61022
+ function expandHomePath(input2) {
61023
+ if (input2 === "~") return homedir9();
61024
+ if (input2.startsWith("~/") || input2.startsWith("~\\")) {
61025
+ return join30(homedir9(), input2.slice(2));
61026
+ }
61027
+ return input2;
61028
+ }
61029
+ function isLocalSkillSource(source) {
61030
+ const trimmed = source.trim();
61031
+ if (trimmed.length === 0) return false;
61032
+ if (trimmed.startsWith("~")) {
61033
+ return trimmed === "~" || trimmed.slice(1, 2) === "/" || trimmed.slice(1, 2) === "\\";
61034
+ }
61035
+ if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return true;
61036
+ if (/^[a-zA-Z]:[\\/]/.test(trimmed)) return true;
61037
+ if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) return true;
61038
+ if (trimmed.startsWith("../") || trimmed.startsWith("..\\")) return true;
61039
+ if (REMOTE_SOURCE_PATTERN.test(trimmed)) return false;
61040
+ return existsSync31(resolve40(process.cwd(), expandHomePath(trimmed)));
61041
+ }
61042
+ function toUsePath(entryDir, skillDir) {
61043
+ const rel = relative20(entryDir, skillDir).split(sep13).join("/");
61044
+ return rel.startsWith("..") ? rel : `./${rel}`;
61045
+ }
59947
61046
  function parseSkillSource(source) {
59948
61047
  const versionSeparator = source.lastIndexOf("@");
59949
61048
  if (versionSeparator === -1) {
@@ -59962,7 +61061,7 @@ function toDependencyPin(dependency) {
59962
61061
  integrity: dependency.integrity
59963
61062
  };
59964
61063
  }
59965
- function resolveSkillDependency(repoUrl, requestedVersions, existing, forceUpdate, gitUrl, timeout = DEFAULT_GIT_TIMEOUT_MS) {
61064
+ function resolveSkillDependency(repoUrl, requestedVersions, existing, forceUpdate, gitUrl, timeout = void 0) {
59966
61065
  return gitUrl ? resolveRemoteDependency(
59967
61066
  repoUrl,
59968
61067
  requestedVersions,
@@ -60006,32 +61105,32 @@ async function collectProjectRemoteImports(entryFile) {
60006
61105
  const projectRoot = process.cwd();
60007
61106
  const entryFiles = /* @__PURE__ */ new Set();
60008
61107
  if (entryFile) {
60009
- entryFiles.add(resolve39(entryFile));
61108
+ entryFiles.add(resolve40(entryFile));
60010
61109
  }
60011
61110
  if (config) {
60012
61111
  if (config.input?.entry) {
60013
- entryFiles.add(resolve39(projectRoot, config.input.entry));
61112
+ entryFiles.add(resolve40(projectRoot, config.input.entry));
60014
61113
  }
60015
61114
  for (const build of Object.values(config.builds ?? {})) {
60016
61115
  if (build.entry) {
60017
- entryFiles.add(resolve39(projectRoot, build.entry));
61116
+ entryFiles.add(resolve40(projectRoot, build.entry));
60018
61117
  }
60019
61118
  }
60020
- const defaultEntry = resolve39(projectRoot, ".promptscript/project.prs");
61119
+ const defaultEntry = resolve40(projectRoot, ".promptscript/project.prs");
60021
61120
  if (!config.input?.entry && (existsSync31(defaultEntry) || entryFiles.size === 0)) {
60022
61121
  entryFiles.add(defaultEntry);
60023
61122
  }
60024
61123
  } else if (!entryFile) {
60025
- const defaultEntry = resolve39(projectRoot, ".promptscript/project.prs");
61124
+ const defaultEntry = resolve40(projectRoot, ".promptscript/project.prs");
60026
61125
  if (existsSync31(defaultEntry)) {
60027
61126
  entryFiles.add(defaultEntry);
60028
61127
  } else {
60029
- const promptscriptDir = resolve39(projectRoot, ".promptscript");
61128
+ const promptscriptDir = resolve40(projectRoot, ".promptscript");
60030
61129
  if (existsSync31(promptscriptDir)) {
60031
61130
  const entries = await readdir9(promptscriptDir);
60032
61131
  const prsFiles = entries.filter((entry) => entry.endsWith(".prs"));
60033
61132
  if (prsFiles.length === 1) {
60034
- entryFiles.add(resolve39(promptscriptDir, prsFiles[0]));
61133
+ entryFiles.add(resolve40(promptscriptDir, prsFiles[0]));
60035
61134
  }
60036
61135
  }
60037
61136
  }
@@ -60040,7 +61139,7 @@ async function collectProjectRemoteImports(entryFile) {
60040
61139
  for (const projectEntry of entryFiles) {
60041
61140
  imports.push(
60042
61141
  ...await collectRemoteImports(projectEntry, {
60043
- localPath: resolve39(projectRoot, ".promptscript"),
61142
+ localPath: resolve40(projectRoot, ".promptscript"),
60044
61143
  registries: config?.registries,
60045
61144
  strict: true,
60046
61145
  deduplicate: false,
@@ -60086,6 +61185,10 @@ function extractSkillFilePath(source) {
60086
61185
  if (!subPath) return "SKILL.md";
60087
61186
  return subPath.toLowerCase().endsWith(".md") ? subPath : `${subPath}/SKILL.md`;
60088
61187
  }
61188
+ function getRemoteSkillDiagnosticPath(source) {
61189
+ const sourcePath = parseSkillSource(source).path.replace(/^https?:\/\//i, "");
61190
+ return sourcePath.toLowerCase().endsWith(".md") ? sourcePath : `${sourcePath}/SKILL.md`;
61191
+ }
60089
61192
  function deriveSkillFolderName(source) {
60090
61193
  const subPath = extractSubPath(source);
60091
61194
  if (!subPath) return basename18(parseSkillSource(source).path);
@@ -60112,13 +61215,15 @@ async function fetchAndValidateRemoteSkill(source, options) {
60112
61215
  const cloneDir = !skillFilePath.includes("/") ? join30(tmp, repoName) : tmp;
60113
61216
  try {
60114
61217
  const gitRegistry = createGitRegistry({
60115
- url: repoUrl,
60116
- timeout: DEFAULT_GIT_TIMEOUT_MS
61218
+ url: repoUrl
60117
61219
  });
60118
61220
  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
61221
  const subPath = extractSubPath(source);
61222
+ const isMdSource = subPath.toLowerCase().endsWith(".md");
61223
+ const coneCandidate = isMdSource ? subPath.slice(0, subPath.lastIndexOf("/")) : subPath;
61224
+ const sparseCone = coneCandidate.length > 0 ? coneCandidate : void 0;
61225
+ await gitRegistry.cloneAtTag(repoUrl, cloneRef, cloneDir, void 0, sparseCone);
61226
+ await gitRegistry.checkoutCommit(cloneDir, options.commit);
60122
61227
  if (!subPath.toLowerCase().endsWith(".md") && existsSync31(join30(cloneDir, `${subPath}.prs`))) {
60123
61228
  return {
60124
61229
  content: "",
@@ -60159,7 +61264,16 @@ async function fetchAndValidateRemoteSkill(source, options) {
60159
61264
  filePath,
60160
61265
  existingNames: options.existingNames
60161
61266
  });
60162
- return { content, integrity, valid: result.valid, issues: result.issues };
61267
+ const diagnosticPath = getRemoteSkillDiagnosticPath(source);
61268
+ const remoteSkillDir = dirname23(diagnosticPath);
61269
+ const issues = result.issues.map((issue) => {
61270
+ const message = issue.message.replaceAll(dirname23(filePath), remoteSkillDir);
61271
+ if (issue.location) {
61272
+ return { ...issue, message, location: { ...issue.location, file: diagnosticPath } };
61273
+ }
61274
+ return { ...issue, message };
61275
+ });
61276
+ return { content, integrity, valid: result.valid, issues };
60163
61277
  } finally {
60164
61278
  await rm2(tmp, { recursive: true, force: true }).catch(() => {
60165
61279
  });
@@ -60225,7 +61339,7 @@ async function isStaleSkillsAddLock(lockPath) {
60225
61339
  }
60226
61340
  }
60227
61341
  async function acquireSkillsAddLock() {
60228
- const lockPath = resolve39(SKILLS_ADD_LOCK_PATH);
61342
+ const lockPath = resolve40(SKILLS_ADD_LOCK_PATH);
60229
61343
  for (let attempt = 0; attempt < 2; attempt++) {
60230
61344
  try {
60231
61345
  const file = await open4(lockPath, "wx", 384);
@@ -60266,7 +61380,7 @@ async function acquireSkillsAddLock() {
60266
61380
  await rm2(quarantinePath, { force: true });
60267
61381
  }
60268
61382
  }
60269
- throw new Error(`Cannot acquire skills add lock: ${resolve39(SKILLS_ADD_LOCK_PATH)}`);
61383
+ throw new Error(`Cannot acquire skills add lock: ${resolve40(SKILLS_ADD_LOCK_PATH)}`);
60270
61384
  }
60271
61385
  async function releaseSkillsAddLock(lock) {
60272
61386
  try {
@@ -60316,7 +61430,7 @@ function validateRemoteSource(source) {
60316
61430
  }
60317
61431
  async function resolveEntryFile(fileFlag) {
60318
61432
  if (fileFlag) {
60319
- const resolved = resolve39(fileFlag);
61433
+ const resolved = resolve40(fileFlag);
60320
61434
  if (!existsSync31(resolved)) {
60321
61435
  throw new Error(`File not found: ${resolved}`);
60322
61436
  }
@@ -60327,7 +61441,7 @@ async function resolveEntryFile(fileFlag) {
60327
61441
  try {
60328
61442
  const config = await loadConfig();
60329
61443
  if (config.input?.entry) {
60330
- const entryPath = resolve39(config.input.entry);
61444
+ const entryPath = resolve40(config.input.entry);
60331
61445
  if (existsSync31(entryPath)) {
60332
61446
  return entryPath;
60333
61447
  }
@@ -60335,16 +61449,16 @@ async function resolveEntryFile(fileFlag) {
60335
61449
  } catch {
60336
61450
  }
60337
61451
  }
60338
- const promptscriptDir = resolve39(".promptscript");
61452
+ const promptscriptDir = resolve40(".promptscript");
60339
61453
  if (existsSync31(promptscriptDir)) {
60340
61454
  const entries = await readdir9(promptscriptDir);
60341
61455
  const prsFiles = entries.filter((f) => f.endsWith(".prs"));
60342
61456
  if (prsFiles.length === 1) {
60343
- return resolve39(promptscriptDir, prsFiles[0]);
61457
+ return resolve40(promptscriptDir, prsFiles[0]);
60344
61458
  }
60345
61459
  if (prsFiles.length > 1) {
60346
61460
  if (prsFiles.includes("project.prs")) {
60347
- return resolve39(promptscriptDir, "project.prs");
61461
+ return resolve40(promptscriptDir, "project.prs");
60348
61462
  }
60349
61463
  throw new Error(
60350
61464
  `Multiple .prs files found in .promptscript/. Use --file to specify which one.`
@@ -60570,6 +61684,306 @@ async function rollbackSkillsAdd(state) {
60570
61684
  }
60571
61685
  return new Error(rollbackErrors.map((error) => error.message).join("; "));
60572
61686
  }
61687
+ function failLocalSkillStep(spinner, step) {
61688
+ spinner.fail(step.message);
61689
+ if (step.hint) {
61690
+ ConsoleOutput.error(step.hint);
61691
+ }
61692
+ process.exitCode = 1;
61693
+ }
61694
+ async function scanManagedSkillNames(managedRoot, skillDir) {
61695
+ if (!existsSync31(managedRoot)) return [];
61696
+ const entries = await readdir9(managedRoot, { withFileTypes: true });
61697
+ const names = [];
61698
+ for (const entry of entries) {
61699
+ if (!entry.isDirectory()) continue;
61700
+ if (resolve40(managedRoot, entry.name) === resolve40(skillDir)) continue;
61701
+ names.push(entry.name);
61702
+ }
61703
+ return names;
61704
+ }
61705
+ function collectLocalUseNames(lines, skillDir, entryDir) {
61706
+ const names = [];
61707
+ for (const line of lines) {
61708
+ const usePath = extractUsePath(line);
61709
+ if (!usePath || !(usePath.startsWith("./") || usePath.startsWith("../"))) continue;
61710
+ const absUsePath = resolve40(entryDir, usePath);
61711
+ const dir = usePath.endsWith(".md") ? dirname23(absUsePath) : absUsePath;
61712
+ if (dir === resolve40(skillDir)) continue;
61713
+ names.push(basename18(dir));
61714
+ }
61715
+ return names;
61716
+ }
61717
+ async function collectLocalExistingNames(lockfile, lines, skillDir, entryDir) {
61718
+ const names = new Set(collectExistingSkillNames(lockfile));
61719
+ const managedRoot = resolve40(process.cwd(), MANAGED_SKILLS_DIR);
61720
+ for (const name of await scanManagedSkillNames(managedRoot, skillDir)) {
61721
+ names.add(name);
61722
+ }
61723
+ for (const name of collectLocalUseNames(lines, skillDir, entryDir)) {
61724
+ names.add(name);
61725
+ }
61726
+ return names;
61727
+ }
61728
+ async function classifyLocalSkillSource(expanded, copyRequested) {
61729
+ const absSource = resolve40(process.cwd(), expanded);
61730
+ if (!existsSync31(absSource)) {
61731
+ return { ok: false, message: `Path not found: ${expanded}` };
61732
+ }
61733
+ const lstatDetails = await lstat11(absSource);
61734
+ if (lstatDetails.isSymbolicLink() && !copyRequested) {
61735
+ return {
61736
+ ok: false,
61737
+ message: "Symbolic-linked skill directories are not supported",
61738
+ hint: "The resolver skips symlinked directories at compile time. Use --copy to install the skill files."
61739
+ };
61740
+ }
61741
+ const details = lstatDetails.isSymbolicLink() ? await stat3(absSource) : lstatDetails;
61742
+ if (details.isFile()) {
61743
+ if (!absSource.toLowerCase().endsWith(".md")) {
61744
+ return {
61745
+ ok: false,
61746
+ message: `Not a SKILL.md: ${expanded}`,
61747
+ hint: "A local file source must be a SKILL.md, or the directory holding it."
61748
+ };
61749
+ }
61750
+ return { ok: true, value: { skillMdPath: absSource, skillDir: dirname23(absSource) } };
61751
+ }
61752
+ if (details.isDirectory()) {
61753
+ const skillMdPath = join30(absSource, "SKILL.md");
61754
+ if (!existsSync31(skillMdPath)) {
61755
+ return {
61756
+ ok: false,
61757
+ message: `No SKILL.md found in: ${expanded}`,
61758
+ hint: "A skill directory must contain a SKILL.md file."
61759
+ };
61760
+ }
61761
+ return { ok: true, value: { skillMdPath, skillDir: absSource } };
61762
+ }
61763
+ return { ok: false, message: `Unsupported file type: ${expanded}` };
61764
+ }
61765
+ function planLocalUsePath(skillDir, entryDir, copyRequested) {
61766
+ const skillName = basename18(skillDir);
61767
+ const managedDir = resolve40(process.cwd(), MANAGED_SKILLS_DIR, skillName);
61768
+ const sourceIsManaged = resolve40(skillDir) === resolve40(managedDir);
61769
+ const willCopy = copyRequested && !sourceIsManaged;
61770
+ if (willCopy) {
61771
+ return { ok: true, value: { usePath: toUsePath(entryDir, managedDir), managedDir, willCopy } };
61772
+ }
61773
+ const relToRoot = relative20(process.cwd(), skillDir);
61774
+ if (relToRoot.startsWith("..") || isAbsolute18(relToRoot)) {
61775
+ return {
61776
+ ok: false,
61777
+ message: "Skill is outside the project root",
61778
+ hint: `"${skillDir}" cannot be referenced in place because it lies outside the project root. Use --copy to install it into ${MANAGED_SKILLS_DIR}/.`
61779
+ };
61780
+ }
61781
+ const usePath = toUsePath(entryDir, skillDir);
61782
+ if (!USE_PATH_PATTERN.test(usePath)) {
61783
+ return {
61784
+ ok: false,
61785
+ message: `Cannot reference this path in a .prs file: ${usePath}`,
61786
+ 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."
61787
+ };
61788
+ }
61789
+ return { ok: true, value: { usePath, managedDir, willCopy } };
61790
+ }
61791
+ function validateLocalSkillFrontmatter(options, skillMdPath, skillContent, existingNames, usePath, spinner) {
61792
+ if (options.skipValidation) return "pass";
61793
+ const result = validateSkillFrontmatter(skillContent, { filePath: skillMdPath, existingNames });
61794
+ const hasWarnings = result.issues.some((issue) => issue.severity === "warning");
61795
+ if (!result.valid || options.strict && hasWarnings) {
61796
+ spinner.fail("SKILL.md failed validation");
61797
+ ConsoleOutput.error(usePath);
61798
+ printValidationIssues(result.issues);
61799
+ return "fail";
61800
+ }
61801
+ if (hasWarnings) {
61802
+ spinner.warn("SKILL.md has validation warnings");
61803
+ printValidationIssues(result.issues);
61804
+ spinner.start("Adding skill...");
61805
+ }
61806
+ return "pass";
61807
+ }
61808
+ async function commitLocalSkillAdd(plan, spinner) {
61809
+ spinner.text = plan.willCopy ? "Copying skill..." : "Adding skill...";
61810
+ let replacedBackupDir;
61811
+ let copyStarted = false;
61812
+ try {
61813
+ if (plan.willCopy) {
61814
+ await mkdir11(dirname23(plan.managedDir), { recursive: true });
61815
+ if (existsSync31(plan.managedDir)) {
61816
+ replacedBackupDir = `${plan.managedDir}.replaced-${process.pid}-${Date.now()}`;
61817
+ await rename3(plan.managedDir, replacedBackupDir);
61818
+ }
61819
+ copyStarted = true;
61820
+ await cp(plan.skillDir, plan.managedDir, { recursive: true, dereference: true });
61821
+ }
61822
+ await assertFileUnchanged(plan.entryFile, plan.originalEntryContent);
61823
+ await writeFileAtomically(plan.entryFile, plan.updatedEntryContent);
61824
+ } catch (error) {
61825
+ if (copyStarted) {
61826
+ await rm2(plan.managedDir, { recursive: true, force: true }).catch(() => {
61827
+ });
61828
+ }
61829
+ if (replacedBackupDir) {
61830
+ await rename3(replacedBackupDir, plan.managedDir).catch(() => {
61831
+ });
61832
+ }
61833
+ throw error;
61834
+ }
61835
+ if (replacedBackupDir) {
61836
+ await rm2(replacedBackupDir, { recursive: true, force: true }).catch(() => {
61837
+ });
61838
+ }
61839
+ }
61840
+ function checkSkillNameCollision(skillName, willCopy, force, existingNames) {
61841
+ if (existingNames.has(skillName) && willCopy && !force) {
61842
+ return `Skill "${skillName}" already exists in this project`;
61843
+ }
61844
+ if (force) {
61845
+ existingNames.delete(skillName);
61846
+ }
61847
+ return void 0;
61848
+ }
61849
+ async function prepareLocalSkillAdd(expanded, options) {
61850
+ const parsed = parseSkillSource(expanded);
61851
+ if (parsed.version !== "latest") {
61852
+ return {
61853
+ ok: false,
61854
+ message: "Versioned local paths are not supported",
61855
+ hint: "Local skills are referenced in place, so there is no version to pin."
61856
+ };
61857
+ }
61858
+ const classified = await classifyLocalSkillSource(expanded, options.copy === true);
61859
+ if (!classified.ok) {
61860
+ return classified;
61861
+ }
61862
+ const { skillDir, skillMdPath } = classified.value;
61863
+ const entryFile = await resolveEntryFile(options.file);
61864
+ const originalEntryContent = await readFile30(entryFile, "utf-8");
61865
+ const plan = planLocalUsePath(skillDir, dirname23(entryFile), options.copy === true);
61866
+ if (!plan.ok) {
61867
+ return plan;
61868
+ }
61869
+ return {
61870
+ ok: true,
61871
+ value: {
61872
+ skillMdPath,
61873
+ skillDir,
61874
+ skillName: basename18(skillDir),
61875
+ entryFile,
61876
+ entryDir: dirname23(entryFile),
61877
+ originalEntryContent,
61878
+ usePath: plan.value.usePath,
61879
+ managedDir: plan.value.managedDir,
61880
+ willCopy: plan.value.willCopy
61881
+ }
61882
+ };
61883
+ }
61884
+ function printLocalSkillDryRun(ctx, newLine, spinner) {
61885
+ spinner.succeed("Dry run \u2014 no files modified");
61886
+ ConsoleOutput.newline();
61887
+ ConsoleOutput.dryRun(`Would add to ${ctx.entryFile}:`);
61888
+ ConsoleOutput.dryRun(` ${newLine}`);
61889
+ if (ctx.willCopy) {
61890
+ ConsoleOutput.dryRun(`Would copy ${ctx.skillDir} \u2192 ${ctx.managedDir}`);
61891
+ }
61892
+ ConsoleOutput.dryRun("No lockfile entry (local skills are not pinned)");
61893
+ }
61894
+ function reportLocalSkillAdded(ctx, newLine, spinner) {
61895
+ spinner.succeed("Skill added");
61896
+ ConsoleOutput.newline();
61897
+ ConsoleOutput.success(`${newLine} \u2192 ${ctx.entryFile}`);
61898
+ if (ctx.willCopy) {
61899
+ ConsoleOutput.muted(`Copied into ${MANAGED_SKILLS_DIR}/${ctx.skillName}`);
61900
+ }
61901
+ ConsoleOutput.muted("Local skill - not recorded in the lockfile");
61902
+ }
61903
+ function reportLocalSkillError(error, spinner) {
61904
+ spinner.fail("Failed to add skill");
61905
+ ConsoleOutput.error(error instanceof Error ? error.message : String(error));
61906
+ process.exitCode = 1;
61907
+ }
61908
+ async function addLocalSkill(rawSource, options, spinner) {
61909
+ let transactionLock;
61910
+ try {
61911
+ if (!options.dryRun) {
61912
+ transactionLock = await acquireSkillsAddLock();
61913
+ }
61914
+ spinner.text = "Resolving skill source...";
61915
+ const prepared = await prepareLocalSkillAdd(expandHomePath(rawSource.trim()), options);
61916
+ if (!prepared.ok) {
61917
+ failLocalSkillStep(spinner, prepared);
61918
+ return;
61919
+ }
61920
+ const ctx = prepared.value;
61921
+ const lines = ctx.originalEntryContent.split("\n");
61922
+ const alreadyImported = lines.some((line) => extractUsePath(line) === ctx.usePath);
61923
+ if (alreadyImported) {
61924
+ spinner.warn(`Skill already imported: ${ctx.usePath}`);
61925
+ return;
61926
+ }
61927
+ const lockfile = await loadLockfile3();
61928
+ const existingNames = await collectLocalExistingNames(
61929
+ lockfile,
61930
+ lines,
61931
+ ctx.skillDir,
61932
+ ctx.entryDir
61933
+ );
61934
+ const refusal = checkSkillNameCollision(
61935
+ ctx.skillName,
61936
+ ctx.willCopy,
61937
+ options.force === true,
61938
+ existingNames
61939
+ );
61940
+ if (refusal) {
61941
+ spinner.fail(refusal);
61942
+ ConsoleOutput.error("Use --force to replace it.");
61943
+ process.exitCode = 1;
61944
+ return;
61945
+ }
61946
+ spinner.text = options.skipValidation ? "Reading SKILL.md..." : "Validating SKILL.md...";
61947
+ const skillContent = await readFile30(ctx.skillMdPath, "utf-8");
61948
+ const validationOutcome = validateLocalSkillFrontmatter(
61949
+ options,
61950
+ ctx.skillMdPath,
61951
+ skillContent,
61952
+ existingNames,
61953
+ ctx.usePath,
61954
+ spinner
61955
+ );
61956
+ if (validationOutcome === "fail") {
61957
+ process.exitCode = 1;
61958
+ return;
61959
+ }
61960
+ const insertionPoint = findInsertionPoint(lines);
61961
+ const newLine = `@use ${ctx.usePath}`;
61962
+ lines.splice(insertionPoint, 0, newLine);
61963
+ if (options.dryRun) {
61964
+ printLocalSkillDryRun(ctx, newLine, spinner);
61965
+ return;
61966
+ }
61967
+ await commitLocalSkillAdd(
61968
+ {
61969
+ entryFile: ctx.entryFile,
61970
+ originalEntryContent: ctx.originalEntryContent,
61971
+ updatedEntryContent: lines.join("\n"),
61972
+ willCopy: ctx.willCopy,
61973
+ skillDir: ctx.skillDir,
61974
+ managedDir: ctx.managedDir
61975
+ },
61976
+ spinner
61977
+ );
61978
+ reportLocalSkillAdded(ctx, newLine, spinner);
61979
+ } catch (error) {
61980
+ reportLocalSkillError(error, spinner);
61981
+ } finally {
61982
+ if (transactionLock) {
61983
+ await releaseSkillsAddLock(transactionLock);
61984
+ }
61985
+ }
61986
+ }
60573
61987
  async function skillsAddCommand(rawSource, options) {
60574
61988
  const spinner = createSpinner("Adding skill...").start();
60575
61989
  let rollbackState;
@@ -60583,6 +61997,10 @@ async function skillsAddCommand(rawSource, options) {
60583
61997
  process.exitCode = 1;
60584
61998
  return;
60585
61999
  }
62000
+ if (isLocalSkillSource(rawSource)) {
62001
+ await addLocalSkill(rawSource, options, spinner);
62002
+ return;
62003
+ }
60586
62004
  const sshCloneUrl = extractSshCloneUrl(rawSource);
60587
62005
  const source = normalizeSkillSource(rawSource);
60588
62006
  const sourceError = validateRemoteSource(source);
@@ -61052,6 +62470,8 @@ var __filename4 = fileURLToPath4(import.meta.url);
61052
62470
  var __dirname4 = dirname28(__filename4);
61053
62471
  var CLI_VERSION = getPackageVersion(__dirname3, "./package.json");
61054
62472
  var program = new Command();
62473
+ var RESOURCES_FLAG = "--resources <items>";
62474
+ var RESOURCES_DESCRIPTION = "Compile only these resources (agents, skills, commands, mcp, hooks, plugins, main)";
61055
62475
  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
62476
  if (actionCommand.name() === "__telemetry-flush" && process.env["PROMPTSCRIPT_TELEMETRY_FLUSH"] === "1") {
61057
62477
  return;
@@ -61088,11 +62508,11 @@ program.command("init").description("Initialize PromptScript in current director
61088
62508
  }
61089
62509
  return initCommand(opts);
61090
62510
  });
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(
62511
+ 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
62512
  "--no-migrate-factory-hooks",
61093
62513
  "Warn instead of migrating legacy .factory/settings.json hooks"
61094
62514
  ).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(
62515
+ 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
62516
  "--no-migrate-factory-hooks",
61097
62517
  "Warn instead of migrating legacy .factory/settings.json hooks"
61098
62518
  ).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 +62544,10 @@ program.command("resolve <import>").description("Show the lock pin and local sou
61124
62544
  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
62545
  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
62546
  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));
62547
+ 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(
62548
+ "--copy",
62549
+ "Local sources: copy into .promptscript/skills/ instead of referencing in place"
62550
+ ).option("--force", "Local sources: replace an existing skill with the same name").action((source, opts) => skillsAddCommand(source, opts));
61128
62551
  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
62552
  skills.command("list").description("List skills imported in the current project").action(() => skillsListCommand());
61130
62553
  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));