@promptscript/cli 1.19.0 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,9 +20,19 @@ native configuration for 50 AI coding platforms.
20
20
 
21
21
  [![Watch the PromptScript introduction](https://img.youtube.com/vi/7sHMn-DbZig/maxresdefault.jpg)](https://youtu.be/7sHMn-DbZig)
22
22
 
23
- AI coding platforms use different files for instructions, skills, agents, commands, MCP
24
- integrations, hooks, and settings. PromptScript replaces hand-maintained copies with one validated,
25
- composable, Git-native source and target-specific compilers.
23
+ ## What is PromptScript?
24
+
25
+ PromptScript is an open-source language and compiler for AI agent configuration. `.prs` sources
26
+ define instructions, project standards, restrictions, skills, agents, MCP servers, hooks, and
27
+ workflows, while `promptscript.yaml` configures targets and extension-compliance policies.
28
+ PromptScript compiles them into the native files each AI tool already reads, including `CLAUDE.md`,
29
+ `.github/copilot-instructions.md`, `.cursor/rules`, and output for 47 more targets.
30
+
31
+ Every AI coding tool uses its own file names, formats, and paths. Teams using several tools end up
32
+ maintaining several copies of the same rules. Those copies drift, and a change made for one tool may
33
+ never reach the others.
34
+
35
+ PromptScript fixes the source instead of patching each generated file:
26
36
 
27
37
  ```text
28
38
  .promptscript/project.prs
@@ -33,6 +43,16 @@ composable, Git-native source and target-specific compilers.
33
43
 
34
44
  No runtime proxy. Each selected platform keeps consuming its own native configuration.
35
45
 
46
+ ## Why maintain native files by hand?
47
+
48
+ Even a few tools create copies that need the same manual updates. Errors stay hidden until an agent
49
+ reads the broken file. Policy changes become separate edits across every repository and tool.
50
+
51
+ With PromptScript, you change one source, review the generated diff, and validate it in CI. Git
52
+ records each change. Remote dependencies stay pinned in `promptscript.lock`, so builds remain
53
+ reproducible. Adding or switching tools does not require rewriting the source. See the
54
+ [full comparison](https://getpromptscript.dev/guides/vs-manual/).
55
+
36
56
  ## Install
37
57
 
38
58
  Requires Node.js 20 or later.
package/index.js CHANGED
@@ -17249,12 +17249,35 @@ var init_visitor2 = __esm({
17249
17249
  return expression ? expression.loc : fallbackLoc;
17250
17250
  }
17251
17251
  parseTextBlock(token) {
17252
- let value = token.image.slice(3, -3).trim();
17252
+ let value = this.normalizeTextBlockIndent(token.image.slice(3, -3));
17253
17253
  if (this.interpolateEnv) {
17254
17254
  value = this.interpolateEnvVars(value);
17255
17255
  }
17256
17256
  return value;
17257
17257
  }
17258
+ /**
17259
+ * Strip the common leading whitespace shared by the non-empty lines of a
17260
+ * text block, then trim. A plain trim() would leave the first line at
17261
+ * column 0 while every following line keeps its source indentation, so
17262
+ * markdown consumers read the block body indent as list nesting. Stripping
17263
+ * the common indent keeps relative (nested) indentation intact.
17264
+ */
17265
+ normalizeTextBlockIndent(raw) {
17266
+ let minIndent = Infinity;
17267
+ for (const line of raw.split("\n")) {
17268
+ if (line.trim().length === 0) continue;
17269
+ const match2 = /^([ \t]*)/.exec(line);
17270
+ const indent = match2?.[1]?.length ?? 0;
17271
+ if (indent < minIndent) {
17272
+ minIndent = indent;
17273
+ if (minIndent === 0) break;
17274
+ }
17275
+ }
17276
+ if (minIndent === Infinity || minIndent === 0) {
17277
+ return raw.trim();
17278
+ }
17279
+ return raw.split("\n").map((line) => line.slice(minIndent)).join("\n").trim();
17280
+ }
17258
17281
  /**
17259
17282
  * Parse a string literal, handling escape sequences.
17260
17283
  */
@@ -18810,6 +18833,52 @@ var init_extractors = __esm({
18810
18833
  }
18811
18834
  });
18812
18835
 
18836
+ // packages/formatters/src/section-title-resolver.ts
18837
+ function presentationSectionId(blockName, entry) {
18838
+ if (entry.sectionId) return getSectionContract(entry.sectionId)?.id ?? entry.sectionId;
18839
+ return getPrimarySectionForBlock(blockName)?.id;
18840
+ }
18841
+ function sourceTitle(ast, section) {
18842
+ const owners = [section.primaryOwner, ...section.fallbackOwners];
18843
+ for (const source of ["explicit", "legacy"]) {
18844
+ for (const owner of owners) {
18845
+ const candidates = ast.blocks.filter((block) => block.name === owner).flatMap(
18846
+ (block) => (block.canonicalBody?.entries ?? []).filter(
18847
+ (entry) => entry.type === "PresentationEntry" && entry.source === source && presentationSectionId(block.name, entry) === section.id
18848
+ )
18849
+ );
18850
+ const selected = candidates.at(-1);
18851
+ if (selected) return selected.title;
18852
+ }
18853
+ }
18854
+ return void 0;
18855
+ }
18856
+ function resolveSourceSectionTitle(ast, sectionIdOrAlias) {
18857
+ const section = getSectionContract(sectionIdOrAlias);
18858
+ return section ? sourceTitle(ast, section) : void 0;
18859
+ }
18860
+ function configuredTitle(section, titles) {
18861
+ if (!titles) return void 0;
18862
+ for (const key of [section.id, ...section.formatterAliases]) {
18863
+ const title = titles[key];
18864
+ if (title !== void 0) return title;
18865
+ }
18866
+ return void 0;
18867
+ }
18868
+ function resolveSectionTitle(ast, sectionIdOrAlias, options = {}) {
18869
+ const section = getSectionContract(sectionIdOrAlias);
18870
+ if (!section) {
18871
+ return options.defaultTitle ?? sectionIdOrAlias;
18872
+ }
18873
+ return (options.sourceOverrides === false ? void 0 : sourceTitle(ast, section)) ?? configuredTitle(section, options.formatterTitles) ?? options.defaultTitle ?? section.defaultTitle;
18874
+ }
18875
+ var init_section_title_resolver = __esm({
18876
+ "packages/formatters/src/section-title-resolver.ts"() {
18877
+ "use strict";
18878
+ init_src();
18879
+ }
18880
+ });
18881
+
18813
18882
  // packages/formatters/src/base-formatter.ts
18814
18883
  import { posix } from "path";
18815
18884
  var BaseFormatter;
@@ -18819,6 +18888,7 @@ var init_base_formatter = __esm({
18819
18888
  init_src();
18820
18889
  init_convention_renderer();
18821
18890
  init_extractors();
18891
+ init_section_title_resolver();
18822
18892
  BaseFormatter = class _BaseFormatter {
18823
18893
  /**
18824
18894
  * Canonical entry point for legacy implementations.
@@ -19003,6 +19073,21 @@ var init_base_formatter = __esm({
19003
19073
  }
19004
19074
  return this.valueToString(value).split("\n")[0] ?? fallback;
19005
19075
  }
19076
+ /**
19077
+ * Whether the `@context` block's text is consumed by the project/intro
19078
+ * fallback. Must mirror the project() and intro() consumption conditions
19079
+ * exactly, otherwise the context section would either duplicate the text
19080
+ * (predicate too narrow) or drop it entirely (predicate too wide).
19081
+ */
19082
+ contextTextConsumedByProject(ast) {
19083
+ const identity = this.findBlock(ast, "identity");
19084
+ if (identity) return false;
19085
+ const context = this.findBlock(ast, "context");
19086
+ if (!context) return false;
19087
+ if (resolveSourceSectionTitle(ast, "context")) return false;
19088
+ const projectTitle = resolveSourceSectionTitle(ast, "project");
19089
+ return context.content.type === "MixedContent" || projectTitle !== void 0;
19090
+ }
19006
19091
  /**
19007
19092
  * `@context` keys that already have a dedicated rendering path. Anything
19008
19093
  * outside this set is generic and must be surfaced by the context section.
@@ -20890,52 +20975,6 @@ var init_mcp_helpers = __esm({
20890
20975
  }
20891
20976
  });
20892
20977
 
20893
- // packages/formatters/src/section-title-resolver.ts
20894
- function presentationSectionId(blockName, entry) {
20895
- if (entry.sectionId) return getSectionContract(entry.sectionId)?.id ?? entry.sectionId;
20896
- return getPrimarySectionForBlock(blockName)?.id;
20897
- }
20898
- function sourceTitle(ast, section) {
20899
- const owners = [section.primaryOwner, ...section.fallbackOwners];
20900
- for (const source of ["explicit", "legacy"]) {
20901
- for (const owner of owners) {
20902
- const candidates = ast.blocks.filter((block) => block.name === owner).flatMap(
20903
- (block) => (block.canonicalBody?.entries ?? []).filter(
20904
- (entry) => entry.type === "PresentationEntry" && entry.source === source && presentationSectionId(block.name, entry) === section.id
20905
- )
20906
- );
20907
- const selected = candidates.at(-1);
20908
- if (selected) return selected.title;
20909
- }
20910
- }
20911
- return void 0;
20912
- }
20913
- function resolveSourceSectionTitle(ast, sectionIdOrAlias) {
20914
- const section = getSectionContract(sectionIdOrAlias);
20915
- return section ? sourceTitle(ast, section) : void 0;
20916
- }
20917
- function configuredTitle(section, titles) {
20918
- if (!titles) return void 0;
20919
- for (const key of [section.id, ...section.formatterAliases]) {
20920
- const title = titles[key];
20921
- if (title !== void 0) return title;
20922
- }
20923
- return void 0;
20924
- }
20925
- function resolveSectionTitle(ast, sectionIdOrAlias, options = {}) {
20926
- const section = getSectionContract(sectionIdOrAlias);
20927
- if (!section) {
20928
- return options.defaultTitle ?? sectionIdOrAlias;
20929
- }
20930
- return (options.sourceOverrides === false ? void 0 : sourceTitle(ast, section)) ?? configuredTitle(section, options.formatterTitles) ?? options.defaultTitle ?? section.defaultTitle;
20931
- }
20932
- var init_section_title_resolver = __esm({
20933
- "packages/formatters/src/section-title-resolver.ts"() {
20934
- "use strict";
20935
- init_src();
20936
- }
20937
- });
20938
-
20939
20978
  // packages/formatters/src/markdown-instruction-formatter.ts
20940
20979
  var MarkdownInstructionFormatter;
20941
20980
  var init_markdown_instruction_formatter = __esm({
@@ -21427,17 +21466,16 @@ var init_markdown_instruction_formatter = __esm({
21427
21466
  context(ast, renderer) {
21428
21467
  const contextBlock = this.findBlock(ast, "context");
21429
21468
  if (!contextBlock) return null;
21430
- const identity = this.findBlock(ast, "identity");
21431
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
21469
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
21432
21470
  const propertyItems = this.contextPropertyItems(ast);
21433
21471
  let body = "";
21434
21472
  if (!textIsConsumedByProject) {
21435
21473
  const text = this.extractText(contextBlock.content);
21436
21474
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
21437
21475
  const strippedText = archMatch ? text.replace(archMatch, "") : text;
21438
- const remainingText = this.dedent(strippedText);
21476
+ const remainingText = strippedText.trim();
21439
21477
  if (remainingText) {
21440
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
21478
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
21441
21479
  body = this.normalizeMarkdownForPrettier(downgradedText).trim();
21442
21480
  }
21443
21481
  }
@@ -23123,8 +23161,7 @@ ${f.content}`
23123
23161
  contextSection(ast, renderer) {
23124
23162
  const contextBlock = this.findBlock(ast, "context");
23125
23163
  if (!contextBlock) return null;
23126
- const identity = this.findBlock(ast, "identity");
23127
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
23164
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
23128
23165
  const propertyItems = this.contextPropertyItems(ast);
23129
23166
  let body = "";
23130
23167
  if (!textIsConsumedByProject) {
@@ -23132,7 +23169,7 @@ ${f.content}`
23132
23169
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
23133
23170
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
23134
23171
  if (remainingText) {
23135
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
23172
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
23136
23173
  body = this.stripAllIndent(downgradedText);
23137
23174
  }
23138
23175
  }
@@ -24238,8 +24275,7 @@ ${f.content}`
24238
24275
  contextSection(ast, renderer) {
24239
24276
  const contextBlock = this.findBlock(ast, "context");
24240
24277
  if (!contextBlock) return null;
24241
- const identity = this.findBlock(ast, "identity");
24242
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
24278
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
24243
24279
  const propertyItems = this.contextPropertyItems(ast);
24244
24280
  let body = "";
24245
24281
  if (!textIsConsumedByProject) {
@@ -24247,7 +24283,7 @@ ${f.content}`
24247
24283
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
24248
24284
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
24249
24285
  if (remainingText) {
24250
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
24286
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
24251
24287
  body = this.normalizeMarkdownForPrettier(downgradedText).trim();
24252
24288
  }
24253
24289
  }
@@ -25146,10 +25182,14 @@ ${text2.trim()}`;
25146
25182
  }
25147
25183
  }
25148
25184
  const text = this.extractText(context.content);
25149
- if (text?.includes("Architecture")) {
25150
- return text.trim();
25151
- }
25152
- return null;
25185
+ const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
25186
+ if (!archMatch) return null;
25187
+ const content = archMatch.replace("## Architecture", "");
25188
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
25189
+ const title = resolveSectionTitle(ast, "architecture", { defaultTitle: "Architecture" });
25190
+ return `## ${title}
25191
+
25192
+ ${normalizedContent.trim()}`;
25153
25193
  }
25154
25194
  contextSection(ast) {
25155
25195
  const context = this.findBlock(ast, "context");
@@ -25161,13 +25201,13 @@ ${text2.trim()}`;
25161
25201
  consumedByIntro ? ["tech-stack", "project"] : ["tech-stack"]
25162
25202
  );
25163
25203
  let body = "";
25164
- if (resolveSourceSectionTitle(ast, "context")) {
25204
+ if (!this.contextTextConsumedByProject(ast)) {
25165
25205
  const text = this.extractText(context.content);
25166
25206
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
25167
25207
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
25168
25208
  if (remainingText) {
25169
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
25170
- body = this.stripAllIndent(downgradedText);
25209
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
25210
+ body = downgradedText.trim();
25171
25211
  }
25172
25212
  }
25173
25213
  if (propertyItems.length > 0) {
@@ -26035,8 +26075,7 @@ ${this.stripAllIndent(content)}`;
26035
26075
  contextSection(ast, _renderer) {
26036
26076
  const contextBlock = this.findBlock(ast, "context");
26037
26077
  if (!contextBlock) return null;
26038
- const identity = this.findBlock(ast, "identity");
26039
- const textIsConsumedByProject = !identity && !resolveSourceSectionTitle(ast, "context");
26078
+ const textIsConsumedByProject = this.contextTextConsumedByProject(ast);
26040
26079
  const propertyItems = this.contextPropertyItems(ast, ["frameworks"]);
26041
26080
  let body = "";
26042
26081
  if (!textIsConsumedByProject) {
@@ -26044,7 +26083,7 @@ ${this.stripAllIndent(content)}`;
26044
26083
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
26045
26084
  const remainingText = archMatch ? text.replace(archMatch, "").trim() : text.trim();
26046
26085
  if (remainingText) {
26047
- const downgradedText = remainingText.replace(/^(\s*)## /gm, "$1### ");
26086
+ const downgradedText = remainingText.replace(/^([ \t]*)## /gm, "$1### ");
26048
26087
  body = this.stripAllIndent(downgradedText);
26049
26088
  }
26050
26089
  }
@@ -43978,6 +44017,37 @@ async function readBoundedRegularFile(filePath, maxBytes, description) {
43978
44017
  await handle.close();
43979
44018
  }
43980
44019
  }
44020
+ async function readOptionalBoundedRegularFile(filePath, description) {
44021
+ try {
44022
+ return await readBoundedRegularFile(filePath, MAX_GIT_CONFIG_BYTES, description);
44023
+ } catch (error) {
44024
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
44025
+ return Buffer.alloc(0);
44026
+ }
44027
+ throw error;
44028
+ }
44029
+ }
44030
+ async function readGitBooleanConfig(filePath, key, description) {
44031
+ try {
44032
+ const result = await execFileAsync(
44033
+ "git",
44034
+ ["config", "--no-includes", "--file", filePath, "--bool", "--get", key],
44035
+ {
44036
+ env: {
44037
+ PATH: process.env["PATH"],
44038
+ GIT_CONFIG_GLOBAL: NULL_DEVICE,
44039
+ GIT_CONFIG_NOSYSTEM: "1"
44040
+ }
44041
+ }
44042
+ );
44043
+ return result.stdout.trim() === "true";
44044
+ } catch (error) {
44045
+ if (typeof error === "object" && error !== null && "code" in error && error.code === 1) {
44046
+ return false;
44047
+ }
44048
+ throw new Error(`Invalid ${description}: ${filePath}`, { cause: error });
44049
+ }
44050
+ }
43981
44051
  async function resolveSafeRepositorySymlink(symlinkPath, repositoryRoot, gitMetadataRoot) {
43982
44052
  let targetPath;
43983
44053
  try {
@@ -44109,8 +44179,23 @@ async function verifyGitRepositoryCheckout(directory, gitDirectoryName, expected
44109
44179
  "Vendor Git config"
44110
44180
  );
44111
44181
  const localConfig = localConfigContent.toString("utf-8");
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))) {
44113
- throw new Error(`External or partial Git object sources are not allowed: ${gitDir}`);
44182
+ const externalGitSourcesError = () => `External or partial Git object sources are not allowed: ${gitDir}. ` + (options.allowPartial ? `Delete the checkout directory ${directory} and re-run this command to re-clone it.` : `Run 'prs vendor sync' to re-vendor the dependency, then re-run this command.`);
44183
+ if (/^[ \t]*\[(?:include|includeif)\b/im.test(localConfig)) {
44184
+ throw new Error(externalGitSourcesError());
44185
+ }
44186
+ const usesWorktreeConfig = await readGitBooleanConfig(
44187
+ join9(gitDir, "config"),
44188
+ "extensions.worktreeConfig",
44189
+ "Git worktree config extension"
44190
+ );
44191
+ if (!options.allowPartial && (usesWorktreeConfig || /\bpromisor\s*=\s*true\b/i.test(localConfig) || /\bpartialclone/i.test(localConfig))) {
44192
+ throw new Error(externalGitSourcesError());
44193
+ }
44194
+ if (options.allowPartial && usesWorktreeConfig) {
44195
+ const worktreeConfig = (await readOptionalBoundedRegularFile(join9(gitDir, "config.worktree"), "Git worktree config")).toString("utf-8");
44196
+ if (/^[ \t]*\[(?:include|includeif)\b/im.test(worktreeConfig)) {
44197
+ throw new Error(externalGitSourcesError());
44198
+ }
44114
44199
  }
44115
44200
  await rejectMetadataPath("commondir", "Git common directories are not allowed");
44116
44201
  await rejectMetadataPath(
@@ -45555,7 +45640,9 @@ var Resolver = class {
45555
45640
  const markerCommit = version && /^[0-9a-f]{40}$/i.test(version) && !/^0{40}$/.test(version) ? version : null;
45556
45641
  const lockedCommit = lockfileCommit ?? markerCommit;
45557
45642
  if (this.options.lockfile && !lockfileCommit) {
45558
- throw new Error(`Remote dependency is not pinned by the lockfile: ${repoUrl}`);
45643
+ throw new Error(
45644
+ `Remote dependency is not pinned by the lockfile: ${repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
45645
+ );
45559
45646
  }
45560
45647
  const requestedRef = lockEntry?.version ?? (version || void 0);
45561
45648
  const tag = requestedRef && requestedRef !== "latest" && !/^[0-9a-f]{40}$/i.test(requestedRef) ? requestedRef : void 0;
@@ -45569,7 +45656,9 @@ var Resolver = class {
45569
45656
  }
45570
45657
  if (this.options.vendorDir && vendorManifest) {
45571
45658
  if (!lockEntry || !lockedCommit) {
45572
- throw new Error(`Vendored dependency is not pinned by the lockfile: ${repoUrl}`);
45659
+ throw new Error(
45660
+ `Vendored dependency is not pinned by the lockfile: ${repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
45661
+ );
45573
45662
  }
45574
45663
  vendoredPath = await resolveVendoredRepository(
45575
45664
  this.options.vendorDir,
@@ -52644,12 +52733,16 @@ async function resolveRegistryPath(config, options = {}) {
52644
52733
  if (options.vendorDir && existsSync12(options.vendorDir) && !vendorManifest) {
52645
52734
  throw new Error(`Vendor manifest is missing: ${options.vendorDir}`);
52646
52735
  }
52647
- if (options.lockfile && !lockedRepository) {
52648
- throw new Error(`Git registry is not pinned by the lockfile: ${gitConfig.url}`);
52736
+ if (options.lockfile && !lockedRepository && !options.allowMissingLockEntry) {
52737
+ throw new Error(
52738
+ `Git registry is not pinned by the lockfile: ${gitConfig.url}. Run 'prs lock' to pin it, then re-run this command.`
52739
+ );
52649
52740
  }
52650
52741
  if (options.vendorDir && vendorManifest) {
52651
52742
  if (!lockedRepository) {
52652
- throw new Error(`Vendored registry is not pinned by the lockfile: ${gitConfig.url}`);
52743
+ throw new Error(
52744
+ `Vendored registry is not pinned by the lockfile: ${gitConfig.url}. Run 'prs lock' to pin it, then re-run this command.`
52745
+ );
52653
52746
  }
52654
52747
  const [repoUrl, dependency] = lockedRepository;
52655
52748
  const repositoryPath = await resolveVendoredRepository(
@@ -52689,7 +52782,8 @@ async function resolveRegistryPath(config, options = {}) {
52689
52782
  cachePath,
52690
52783
  ".git",
52691
52784
  lockedRepository[1].commit,
52692
- /* @__PURE__ */ new Set([".prs-cache-meta.json"])
52785
+ /* @__PURE__ */ new Set([".prs-cache-meta.json"]),
52786
+ { allowPartial: true }
52693
52787
  );
52694
52788
  return {
52695
52789
  path: await resolveRegistrySubPath(cachePath, gitConfig.path),
@@ -52742,7 +52836,8 @@ async function resolveRegistryPath(config, options = {}) {
52742
52836
  cachePath,
52743
52837
  ".git",
52744
52838
  lockedRepository[1].commit,
52745
- /* @__PURE__ */ new Set([".prs-cache-meta.json"])
52839
+ /* @__PURE__ */ new Set([".prs-cache-meta.json"]),
52840
+ { allowPartial: true }
52746
52841
  );
52747
52842
  }
52748
52843
  return {
@@ -57901,6 +57996,63 @@ async function collectRemoteImports(entryPath, options) {
57901
57996
  const visited = /* @__PURE__ */ new Set();
57902
57997
  const seen = /* @__PURE__ */ new Set();
57903
57998
  const results = [];
57999
+ function recordRemoteImport(path2, sourceFile, resolved) {
58000
+ const marker = parseRegistryMarker(resolved);
58001
+ if (!marker) {
58002
+ return false;
58003
+ }
58004
+ const key = `${marker.repoUrl}\0${marker.path}\0${marker.version}`;
58005
+ if (options.deduplicate === false || !seen.has(key)) {
58006
+ seen.add(key);
58007
+ results.push({
58008
+ ...marker,
58009
+ ...options.includeLocations ? {
58010
+ sourceFile,
58011
+ sourceLine: path2.loc.line,
58012
+ rawSource: path2.raw
58013
+ } : {}
58014
+ });
58015
+ }
58016
+ return true;
58017
+ }
58018
+ async function scanLocalReference(resolved) {
58019
+ if (!resolved.endsWith(".prs") && !resolved.endsWith(".md")) {
58020
+ return;
58021
+ }
58022
+ if (existsSync21(resolved)) {
58023
+ await scan(resolved);
58024
+ return;
58025
+ }
58026
+ if (resolved.endsWith(".prs")) {
58027
+ const skillDirectory = resolved.slice(0, -".prs".length);
58028
+ if (existsSync21(skillDirectory)) {
58029
+ const skillFile = join21(skillDirectory, "SKILL.md");
58030
+ if (existsSync21(skillFile)) {
58031
+ await scan(skillFile);
58032
+ }
58033
+ return;
58034
+ }
58035
+ }
58036
+ if (options.strict) {
58037
+ throw new Error(`Imported PromptScript file not found: ${resolved}`);
58038
+ }
58039
+ }
58040
+ async function scanReference(path2, sourceFile) {
58041
+ let resolved;
58042
+ try {
58043
+ resolved = loader.resolveRef(path2, sourceFile);
58044
+ } catch (error) {
58045
+ if (options.strict) {
58046
+ throw new Error(`Cannot resolve import '${path2.raw}' in ${sourceFile}`, {
58047
+ cause: error
58048
+ });
58049
+ }
58050
+ return;
58051
+ }
58052
+ if (!recordRemoteImport(path2, sourceFile, resolved)) {
58053
+ await scanLocalReference(resolved);
58054
+ }
58055
+ }
57904
58056
  async function scan(filePath) {
57905
58057
  if (visited.has(filePath)) return;
57906
58058
  visited.add(filePath);
@@ -57921,54 +58073,16 @@ async function collectRemoteImports(entryPath, options) {
57921
58073
  throw new Error(`Cannot scan invalid PromptScript file: ${filePath}: ${errors[0].message}`);
57922
58074
  }
57923
58075
  if (!ast) return;
57924
- for (const use of ast.uses) {
57925
- let resolved;
57926
- try {
57927
- resolved = loader.resolveRef(use.path, filePath);
57928
- } catch (error) {
57929
- if (options.strict) {
57930
- throw new Error(`Cannot resolve import '${use.path.raw}' in ${filePath}`, {
57931
- cause: error
57932
- });
57933
- }
57934
- continue;
57935
- }
57936
- const marker = parseRegistryMarker(resolved);
57937
- if (marker) {
57938
- const key = `${marker.repoUrl}\0${marker.path}\0${marker.version}`;
57939
- if (options.deduplicate === false || !seen.has(key)) {
57940
- seen.add(key);
57941
- results.push({
57942
- ...marker,
57943
- ...options.includeLocations ? {
57944
- sourceFile: filePath,
57945
- sourceLine: use.path.loc.line,
57946
- rawSource: use.path.raw
57947
- } : {}
57948
- });
57949
- }
57950
- } else {
57951
- if (!resolved.endsWith(".prs") && !resolved.endsWith(".md")) {
57952
- continue;
57953
- }
57954
- if (existsSync21(resolved)) {
57955
- await scan(resolved);
57956
- continue;
57957
- }
57958
- if (resolved.endsWith(".prs")) {
57959
- const skillDirectory = resolved.slice(0, -".prs".length);
57960
- if (existsSync21(skillDirectory)) {
57961
- const skillFile = join21(skillDirectory, "SKILL.md");
57962
- if (existsSync21(skillFile)) {
57963
- await scan(skillFile);
57964
- }
57965
- continue;
57966
- }
57967
- }
57968
- if (options.strict) {
57969
- throw new Error(`Imported PromptScript file not found: ${resolved}`);
57970
- }
57971
- }
58076
+ const inlineUses = ast.blocks.flatMap(
58077
+ (block) => block.name === "skills" && (block.content.type === "ObjectContent" || block.content.type === "MixedContent") ? block.content.inlineUses ?? [] : []
58078
+ );
58079
+ const paths = [
58080
+ ...ast.inherit ? [ast.inherit.path] : [],
58081
+ ...ast.uses.map((use) => use.path),
58082
+ ...inlineUses.map((use) => use.path)
58083
+ ];
58084
+ for (const path2 of paths) {
58085
+ await scanReference(path2, filePath);
57972
58086
  }
57973
58087
  }
57974
58088
  await scan(entryPath);
@@ -58163,6 +58277,7 @@ async function lockCommand(options) {
58163
58277
  const localPath = resolve33(projectRoot, ".promptscript");
58164
58278
  const entryPath = config.input?.entry ? resolve33(projectRoot, config.input.entry) : resolve33(localPath, "project.prs");
58165
58279
  const registry2 = await resolveRegistryPath(config, {
58280
+ allowMissingLockEntry: true,
58166
58281
  ...!options.update && Object.keys(existing.dependencies).length > 0 ? { lockfile: existing } : {}
58167
58282
  });
58168
58283
  const scannedImports = await collectRemoteImports(entryPath, {
@@ -58817,7 +58932,9 @@ async function resolveCommand(importPath, options) {
58817
58932
  }
58818
58933
  if (manifest) {
58819
58934
  if (!lockKey || !dependency) {
58820
- throw new Error(`Vendored dependency is not pinned by the lockfile: ${marker.repoUrl}`);
58935
+ throw new Error(
58936
+ `Vendored dependency is not pinned by the lockfile: ${marker.repoUrl}. Run 'prs lock' to pin it, then re-run this command.`
58937
+ );
58821
58938
  }
58822
58939
  const vendoredPath = await resolveVendoredRepository(
58823
58940
  vendorDir,
@@ -58836,7 +58953,10 @@ async function resolveCommand(importPath, options) {
58836
58953
  cachePath,
58837
58954
  ".git",
58838
58955
  dependency.commit,
58839
- /* @__PURE__ */ new Set([".prs-registry-meta.json"])
58956
+ /* @__PURE__ */ new Set([".prs-registry-meta.json"]),
58957
+ {
58958
+ allowPartial: true
58959
+ }
58840
58960
  );
58841
58961
  } catch {
58842
58962
  source = "stale cache";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptscript/cli",
3
- "version": "1.19.0",
3
+ "version": "1.19.1",
4
4
  "description": "CLI for PromptScript - standardize AI instructions across GitHub Copilot, Claude, Cursor and other AI tools",
5
5
  "keywords": [
6
6
  "cli",
@@ -47,10 +47,10 @@
47
47
  "commander": "^15.0.0",
48
48
  "minimatch": "^10.2.6",
49
49
  "ora": "^9.4.1",
50
- "prettier": "^3.9.6",
51
- "yaml": "^2.9.0",
50
+ "prettier": "^3.9.8",
51
+ "yaml": "^2.9.1",
52
52
  "simple-git": "3.36.0",
53
- "fastify": "^5.12.3",
53
+ "fastify": "^5.12.5",
54
54
  "@fastify/cors": "^11.3.0",
55
55
  "@fastify/websocket": "^11.3.0",
56
56
  "ws": "^8.21.3",
@@ -13,7 +13,7 @@ export interface RemoteImport {
13
13
  sourceFile?: string;
14
14
  /** Source line containing this import when location collection is enabled */
15
15
  sourceLine?: number;
16
- /** Original @use source text when location collection is enabled */
16
+ /** Original reference text when location collection is enabled */
17
17
  rawSource?: string;
18
18
  }
19
19
  /**
@@ -35,7 +35,7 @@ export interface ScanOptions {
35
35
  }
36
36
  /**
37
37
  * Scan `.prs` files starting from an entry point, recursively follow local
38
- * `@use` imports, and collect all remote `@use` references.
38
+ * references, and collect remote `@use`, `@inherit`, and inline skill imports.
39
39
  *
40
40
  * Remote references are those that resolve to registry markers — either direct
41
41
  * URL-style imports (e.g. `github.com/org/repo/path`) or registry alias
@@ -1 +1 @@
1
- {"version":3,"file":"lock-scanner.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/lock-scanner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAmGzB"}
1
+ {"version":3,"file":"lock-scanner.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/lock-scanner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CA2HzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/lock.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,KAAK,EAAY,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGvE,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAQhC,4CAA4C;AAC5C,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAUjD,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAyLrE;AAkBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EAAE,EAC3B,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,WAAW,EAAE,OAAO,EACpB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,cAAc,EACrB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAmE7B"}
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/lock.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,KAAK,EAAY,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGvE,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAQhC,4CAA4C;AAC5C,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAUjD,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA0LrE;AAkBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EAAE,EAC3B,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,WAAW,EAAE,OAAO,EACpB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,cAAc,EACrB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAmE7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-cmd.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/resolve-cmd.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAoIzD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiKf"}
1
+ {"version":3,"file":"resolve-cmd.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/resolve-cmd.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAoIzD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuKf"}
@@ -23,6 +23,8 @@ export interface ResolvedRegistry {
23
23
  export interface ResolveRegistryOptions {
24
24
  vendorDir?: string;
25
25
  lockfile?: Lockfile;
26
+ /** Allow lock generation to resolve a registry missing from an existing lockfile. */
27
+ allowMissingLockEntry?: boolean;
26
28
  /** Refuse cache repair, network access, and all registry writes. */
27
29
  readOnly?: boolean;
28
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry-resolver.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/utils/registry-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAcvE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACjC,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoBD;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAiL3B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAe,GAAG,MAAM,CAKzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,OAAO,CAEhG"}
1
+ {"version":3,"file":"registry-resolver.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/utils/registry-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAcvE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACjC,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,qFAAqF;IACrF,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAoBD;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAyL3B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAe,GAAG,MAAM,CAKzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,OAAO,CAEhG"}