@promptscript/cli 1.0.0-alpha.5 → 1.0.0-alpha.7

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
@@ -1,7 +1,7 @@
1
1
  // packages/cli/src/cli.ts
2
2
  import { Command } from "commander";
3
- import { fileURLToPath as fileURLToPath2 } from "url";
4
- import { dirname as dirname7 } from "path";
3
+ import { fileURLToPath as fileURLToPath3 } from "url";
4
+ import { dirname as dirname9 } from "path";
5
5
 
6
6
  // packages/core/src/types/constants.ts
7
7
  var BLOCK_TYPES = [
@@ -306,7 +306,7 @@ var noopLogger = {
306
306
 
307
307
  // packages/cli/src/commands/init.ts
308
308
  import { fileURLToPath } from "url";
309
- import { dirname as dirname2 } from "path";
309
+ import { dirname as dirname3 } from "path";
310
310
 
311
311
  // packages/cli/src/services.ts
312
312
  import { writeFile, mkdir, readFile, readdir } from "fs/promises";
@@ -405,6 +405,13 @@ var ConsoleOutput = {
405
405
  if (isQuiet()) return;
406
406
  console.log(chalk.yellow(` \u2298 ${message}`));
407
407
  },
408
+ /**
409
+ * Print an unchanged file message.
410
+ */
411
+ unchanged(message) {
412
+ if (isQuiet()) return;
413
+ console.log(chalk.gray(` \u25CB ${message}`));
414
+ },
408
415
  /**
409
416
  * Print an info message.
410
417
  */
@@ -859,9 +866,910 @@ async function resolvePrettierOptions(config, basePath = process.cwd()) {
859
866
  return result;
860
867
  }
861
868
 
869
+ // packages/cli/src/templates/migrate-skill.ts
870
+ var MIGRATE_SKILL_CLAUDE = `---
871
+ name: 'migrate-to-promptscript'
872
+ description: 'Migrate existing AI instruction files to PromptScript format'
873
+ allowed-tools:
874
+ - Read
875
+ - Write
876
+ - Glob
877
+ - Grep
878
+ - Bash
879
+ user-invocable: true
880
+ ---
881
+
882
+ # Migrate to PromptScript
883
+
884
+ ## Overview
885
+
886
+ This skill guides you through migrating existing AI instruction files
887
+ to PromptScript format, creating a unified source of truth for all
888
+ AI coding assistants.
889
+
890
+ ## Step 1: Discovery
891
+
892
+ Search for existing instruction files using these patterns:
893
+
894
+ Claude Code:
895
+
896
+ - CLAUDE.md, claude.md, CLAUDE.local.md
897
+
898
+ Cursor:
899
+
900
+ - .cursorrules
901
+ - .cursor/rules/\\*.md
902
+ - .cursor/rules/\\*.mdc
903
+
904
+ GitHub Copilot:
905
+
906
+ - .github/copilot-instructions.md
907
+ - .github/instructions/\\*.md
908
+
909
+ Other:
910
+
911
+ - AGENTS.md
912
+ - AI_INSTRUCTIONS.md
913
+ - AI.md
914
+ - .ai/instructions.md
915
+
916
+ Use Glob tool to find these files.
917
+
918
+ ## Step 2: Read and Analyze
919
+
920
+ For each discovered file:
921
+
922
+ 1. Read the full content using the Read tool
923
+ 2. Identify sections by headers (##, ###) and patterns
924
+ 3. Classify content type using the mapping table below
925
+ 4. Note any tool-specific content that may need special handling
926
+
927
+ ## Step 3: Content Mapping
928
+
929
+ Map source content to PromptScript blocks:
930
+
931
+ | Source Pattern | PromptScript Block |
932
+ | ------------------------------------ | ------------------ |
933
+ | You are, persona, identity, role | @identity |
934
+ | Tech stack, languages, frameworks | @context |
935
+ | Coding standards, conventions, rules | @standards |
936
+ | Don't, Never, restrictions | @restrictions |
937
+ | Commands, shortcuts | @shortcuts |
938
+ | API docs, references, knowledge base | @knowledge |
939
+ | Parameters, config values | @params |
940
+ | File patterns, globs, applyTo | @guards |
941
+ | Skills, capabilities | @skills |
942
+ | Agents, subagents | @agents |
943
+ | Local-only settings | @local |
944
+
945
+ ## Step 4: Generate PromptScript
946
+
947
+ ### Required: @meta block
948
+
949
+ Every PromptScript file needs metadata with id and syntax fields.
950
+
951
+ ### Identity (persona)
952
+
953
+ Convert persona descriptions to @identity block with triple-quote string.
954
+
955
+ ### Context (project info)
956
+
957
+ Convert tech stack to @context block with structured properties
958
+ like project, languages, frameworks.
959
+
960
+ ### Standards (conventions)
961
+
962
+ Convert coding standards to @standards block organized by category:
963
+ code, naming, commits, etc.
964
+
965
+ ### Restrictions (don'ts)
966
+
967
+ Convert restrictions to @restrictions block using dash prefix for each item.
968
+
969
+ ### Shortcuts (commands)
970
+
971
+ Convert custom commands to @shortcuts block. Simple shortcuts use
972
+ key-value format. Complex shortcuts use object format with
973
+ prompt, description, and content fields.
974
+
975
+ ### Knowledge (references)
976
+
977
+ Convert API docs and reference material to @knowledge block
978
+ using triple-quote string for rich content.
979
+
980
+ ### Guards (file patterns)
981
+
982
+ Convert file-specific rules to @guards block with globs array
983
+ specifying file patterns.
984
+
985
+ ### Params (configuration)
986
+
987
+ Convert configuration parameters to @params block with type annotations:
988
+ range(), enum(), boolean.
989
+
990
+ ### Skills (capabilities)
991
+
992
+ Convert skill definitions to @skills block with description,
993
+ trigger, and content fields.
994
+
995
+ ### Agents (subagents)
996
+
997
+ Convert agent definitions to @agents block with description,
998
+ tools, model, and content fields.
999
+
1000
+ ## Step 5: File Organization
1001
+
1002
+ Simple Projects - single file structure:
1003
+
1004
+ - .promptscript/project.prs
1005
+ - promptscript.yaml
1006
+
1007
+ Complex Projects - modular file structure:
1008
+
1009
+ - .promptscript/project.prs (main with @use imports)
1010
+ - .promptscript/context.prs
1011
+ - .promptscript/standards.prs
1012
+ - .promptscript/restrictions.prs
1013
+ - .promptscript/commands.prs
1014
+ - promptscript.yaml
1015
+
1016
+ ## Step 6: Configuration
1017
+
1018
+ Create promptscript.yaml with:
1019
+
1020
+ - version: '1'
1021
+ - project.id
1022
+ - input.entry pointing to main .prs file
1023
+ - targets for github, claude, cursor
1024
+
1025
+ ## Step 7: Validation
1026
+
1027
+ After generating PromptScript files:
1028
+
1029
+ 1. Validate syntax: prs validate
1030
+ 2. Test compilation: prs compile --dry-run
1031
+ 3. Compare output with original files
1032
+ 4. Iterate if content is missing or incorrect
1033
+
1034
+ ## Common Patterns
1035
+
1036
+ ### Merging Multiple Sources
1037
+
1038
+ When instructions exist in multiple files:
1039
+
1040
+ 1. Identity: Take from most detailed source
1041
+ 2. Standards: Merge all, deduplicate
1042
+ 3. Restrictions: Combine all (union)
1043
+ 4. Commands: Merge, resolve conflicts
1044
+
1045
+ ### Tool-Specific Content
1046
+
1047
+ Handle tool-specific content:
1048
+
1049
+ - GitHub prompts: Use @shortcuts with prompt: true
1050
+ - Claude agents: Use @agents block
1051
+ - Cursor rules: Map to @standards
1052
+ - Local content: Use @local block
1053
+
1054
+ ### Preserving Formatting
1055
+
1056
+ Use triple-quote multiline strings for:
1057
+
1058
+ - Rich markdown content
1059
+ - Code examples
1060
+ - Complex instructions
1061
+
1062
+ ## Syntax Rules
1063
+
1064
+ Quick reference for PromptScript syntax:
1065
+
1066
+ - Strings: quoted or identifier
1067
+ - Multi-line: triple quotes
1068
+ - Arrays: [item1, item2] or - item prefix
1069
+ - Objects: { key: value }
1070
+ - Comments: # comment
1071
+ - Required @meta fields: id, syntax
1072
+
1073
+ ## Quality Checklist
1074
+
1075
+ Before completing migration:
1076
+
1077
+ - @meta block has id and syntax
1078
+ - Identity is clear and specific
1079
+ - Standards are organized by category
1080
+ - Restrictions use dash prefix (-)
1081
+ - Shortcuts work in target tools
1082
+ - prs validate passes
1083
+ - prs compile produces correct output
1084
+ - No duplicate content across blocks
1085
+
1086
+ ## Troubleshooting
1087
+
1088
+ ### Missing @meta Error
1089
+ Add required metadata block at the start.
1090
+
1091
+ ### Multiline String in Object Error
1092
+ Assign multiline strings to named keys, don't leave them loose
1093
+ inside objects.
1094
+
1095
+ ### Content Not Appearing in Output
1096
+ Check block names match expected patterns and
1097
+ verify syntax with prs validate --verbose.
1098
+ `;
1099
+ var MIGRATE_SKILL_CURSOR = `# Migrate to PromptScript
1100
+
1101
+ Use this command to migrate existing AI instruction files to PromptScript format.
1102
+
1103
+ ## Step 1: Discovery
1104
+
1105
+ Search for existing instruction files:
1106
+
1107
+ - CLAUDE.md, claude.md, CLAUDE.local.md
1108
+ - .cursorrules, .cursor/rules/*.mdc
1109
+ - .github/copilot-instructions.md
1110
+ - AGENTS.md, AI_INSTRUCTIONS.md
1111
+
1112
+ ## Step 2: Content Mapping
1113
+
1114
+ Map source content to PromptScript blocks:
1115
+
1116
+ | Source Pattern | PromptScript Block |
1117
+ | ------------------------------------ | ------------------ |
1118
+ | You are, persona, identity, role | @identity |
1119
+ | Tech stack, languages, frameworks | @context |
1120
+ | Coding standards, conventions, rules | @standards |
1121
+ | Don't, Never, restrictions | @restrictions |
1122
+ | Commands, shortcuts | @shortcuts |
1123
+ | API docs, references | @knowledge |
1124
+
1125
+ ## Step 3: Generate PromptScript
1126
+
1127
+ Create \`.promptscript/project.prs\` with:
1128
+
1129
+ 1. **@meta** block with id and syntax fields (required)
1130
+ 2. **@identity** for persona/role descriptions
1131
+ 3. **@context** for tech stack info
1132
+ 4. **@standards** organized by category
1133
+ 5. **@restrictions** with dash prefix for each item
1134
+ 6. **@shortcuts** for commands
1135
+
1136
+ ## Step 4: Validation
1137
+
1138
+ After generating:
1139
+
1140
+ 1. Run \`prs validate\`
1141
+ 2. Run \`prs compile --dry-run\`
1142
+ 3. Compare output with original files
1143
+
1144
+ ## Syntax Quick Reference
1145
+
1146
+ - Strings: quoted or identifier
1147
+ - Multi-line: triple quotes (\`"""\`)
1148
+ - Arrays: \`[item1, item2]\`
1149
+ - Objects: \`{ key: value }\`
1150
+ - Comments: \`# comment\`
1151
+
1152
+ ## Quality Checklist
1153
+
1154
+ - @meta block has id and syntax
1155
+ - Standards organized by category
1156
+ - Restrictions use dash prefix (-)
1157
+ - prs validate passes
1158
+ `;
1159
+ var MIGRATE_SKILL_ANTIGRAVITY = `# Migrate to PromptScript
1160
+
1161
+ This rule provides guidance for migrating existing AI instruction files to PromptScript format.
1162
+
1163
+ ## Overview
1164
+
1165
+ PromptScript creates a unified source of truth for all AI coding assistants,
1166
+ compiling to native formats for Claude, GitHub Copilot, Cursor, and Antigravity.
1167
+
1168
+ ## Step 1: Discovery
1169
+
1170
+ Search for existing instruction files:
1171
+
1172
+ - CLAUDE.md, claude.md, CLAUDE.local.md
1173
+ - .cursorrules, .cursor/rules/*.mdc
1174
+ - .github/copilot-instructions.md
1175
+ - .agent/rules/*.md
1176
+ - AGENTS.md, AI_INSTRUCTIONS.md
1177
+
1178
+ ## Step 2: Content Mapping
1179
+
1180
+ Map source content to PromptScript blocks:
1181
+
1182
+ | Source Pattern | PromptScript Block |
1183
+ | ------------------------------------ | ------------------ |
1184
+ | You are, persona, identity, role | @identity |
1185
+ | Tech stack, languages, frameworks | @context |
1186
+ | Coding standards, conventions, rules | @standards |
1187
+ | Don't, Never, restrictions | @restrictions |
1188
+ | Commands, shortcuts | @shortcuts |
1189
+ | API docs, references | @knowledge |
1190
+
1191
+ ## Step 3: Generate PromptScript
1192
+
1193
+ Create \`.promptscript/project.prs\` with required blocks:
1194
+
1195
+ 1. **@meta** - id and syntax fields (required)
1196
+ 2. **@identity** - persona/role descriptions
1197
+ 3. **@context** - tech stack info
1198
+ 4. **@standards** - coding rules organized by category
1199
+ 5. **@restrictions** - things to avoid (dash prefix)
1200
+ 6. **@shortcuts** - commands
1201
+
1202
+ ## Step 4: Validation
1203
+
1204
+ After generating PromptScript files:
1205
+
1206
+ 1. Run \`prs validate\` to check syntax
1207
+ 2. Run \`prs compile --dry-run\` to preview output
1208
+ 3. Compare compiled output with original files
1209
+
1210
+ ## Syntax Reference
1211
+
1212
+ - Strings: quoted or identifier
1213
+ - Multi-line: triple quotes
1214
+ - Arrays: [item1, item2]
1215
+ - Objects: { key: value }
1216
+ - Comments: # comment
1217
+ `;
1218
+ var MIGRATE_SKILL_GITHUB = `---
1219
+ name: 'migrate-to-promptscript'
1220
+ description: 'Migrate existing AI instruction files to PromptScript format'
1221
+ allowed-tools:
1222
+ - read
1223
+ - write
1224
+ - glob
1225
+ - grep
1226
+ - execute
1227
+ user-invocable: true
1228
+ ---
1229
+
1230
+ # Migrate to PromptScript
1231
+
1232
+ ## Overview
1233
+
1234
+ This skill guides you through migrating existing AI instruction files
1235
+ to PromptScript format, creating a unified source of truth for all
1236
+ AI coding assistants.
1237
+
1238
+ ## Step 1: Discovery
1239
+
1240
+ Search for existing instruction files using these patterns:
1241
+
1242
+ Claude Code:
1243
+
1244
+ - CLAUDE.md, claude.md, CLAUDE.local.md
1245
+
1246
+ Cursor:
1247
+
1248
+ - .cursorrules
1249
+ - .cursor/rules/\\*.md
1250
+ - .cursor/rules/\\*.mdc
1251
+
1252
+ GitHub Copilot:
1253
+
1254
+ - .github/copilot-instructions.md
1255
+ - .github/instructions/\\*.md
1256
+
1257
+ Other:
1258
+
1259
+ - AGENTS.md
1260
+ - AI_INSTRUCTIONS.md
1261
+ - AI.md
1262
+ - .ai/instructions.md
1263
+
1264
+ ## Step 2: Read and Analyze
1265
+
1266
+ For each discovered file:
1267
+
1268
+ 1. Read the full content
1269
+ 2. Identify sections by headers (##, ###) and patterns
1270
+ 3. Classify content type using the mapping table below
1271
+ 4. Note any tool-specific content that may need special handling
1272
+
1273
+ ## Step 3: Content Mapping
1274
+
1275
+ Map source content to PromptScript blocks:
1276
+
1277
+ | Source Pattern | PromptScript Block |
1278
+ | ------------------------------------ | ------------------ |
1279
+ | You are, persona, identity, role | @identity |
1280
+ | Tech stack, languages, frameworks | @context |
1281
+ | Coding standards, conventions, rules | @standards |
1282
+ | Don't, Never, restrictions | @restrictions |
1283
+ | Commands, shortcuts | @shortcuts |
1284
+ | API docs, references, knowledge base | @knowledge |
1285
+ | Parameters, config values | @params |
1286
+ | File patterns, globs, applyTo | @guards |
1287
+ | Skills, capabilities | @skills |
1288
+ | Agents, subagents | @agents |
1289
+ | Local-only settings | @local |
1290
+
1291
+ ## Step 4: Generate PromptScript
1292
+
1293
+ ### Required: @meta block
1294
+
1295
+ Every PromptScript file needs metadata with id and syntax fields.
1296
+
1297
+ ### Identity (persona)
1298
+
1299
+ Convert persona descriptions to @identity block with triple-quote string.
1300
+
1301
+ ### Context (project info)
1302
+
1303
+ Convert tech stack to @context block with structured properties
1304
+ like project, languages, frameworks.
1305
+
1306
+ ### Standards (conventions)
1307
+
1308
+ Convert coding standards to @standards block organized by category:
1309
+ code, naming, commits, etc.
1310
+
1311
+ ### Restrictions (don'ts)
1312
+
1313
+ Convert restrictions to @restrictions block using dash prefix for each item.
1314
+
1315
+ ### Shortcuts (commands)
1316
+
1317
+ Convert custom commands to @shortcuts block. Simple shortcuts use
1318
+ key-value format. Complex shortcuts use object format with
1319
+ prompt, description, and content fields.
1320
+
1321
+ ## Step 5: File Organization
1322
+
1323
+ Simple Projects - single file structure:
1324
+
1325
+ - .promptscript/project.prs
1326
+ - promptscript.yaml
1327
+
1328
+ Complex Projects - modular file structure:
1329
+
1330
+ - .promptscript/project.prs (main with @use imports)
1331
+ - .promptscript/context.prs
1332
+ - .promptscript/standards.prs
1333
+ - .promptscript/restrictions.prs
1334
+ - .promptscript/commands.prs
1335
+ - promptscript.yaml
1336
+
1337
+ ## Step 6: Configuration
1338
+
1339
+ Create promptscript.yaml with:
1340
+
1341
+ - version: '1'
1342
+ - project.id
1343
+ - input.entry pointing to main .prs file
1344
+ - targets for github, claude, cursor
1345
+
1346
+ ## Step 7: Validation
1347
+
1348
+ After generating PromptScript files:
1349
+
1350
+ 1. Validate syntax: prs validate
1351
+ 2. Test compilation: prs compile --dry-run
1352
+ 3. Compare output with original files
1353
+ 4. Iterate if content is missing or incorrect
1354
+
1355
+ ## Quality Checklist
1356
+
1357
+ Before completing migration:
1358
+
1359
+ - @meta block has id and syntax
1360
+ - Identity is clear and specific
1361
+ - Standards are organized by category
1362
+ - Restrictions use dash prefix (-)
1363
+ - Shortcuts work in target tools
1364
+ - prs validate passes
1365
+ - prs compile produces correct output
1366
+ - No duplicate content across blocks
1367
+ `;
1368
+
1369
+ // packages/cli/src/utils/manifest-loader.ts
1370
+ import { join as join3, dirname as dirname2 } from "path";
1371
+ import { parse as parseYaml2 } from "yaml";
1372
+ var OFFICIAL_REGISTRY = {
1373
+ name: "PromptScript Official Registry",
1374
+ url: "https://github.com/mrwogu/promptscript-registry.git",
1375
+ branch: "main",
1376
+ manifestUrl: "https://raw.githubusercontent.com/mrwogu/promptscript-registry/main/registry-manifest.yaml"
1377
+ };
1378
+ var ManifestLoadError = class extends Error {
1379
+ originalCause;
1380
+ constructor(message, cause) {
1381
+ super(message);
1382
+ this.name = "ManifestLoadError";
1383
+ this.originalCause = cause;
1384
+ }
1385
+ };
1386
+ var manifestCache = /* @__PURE__ */ new Map();
1387
+ var MANIFEST_FILENAME = "registry-manifest.yaml";
1388
+ async function loadManifest(options = {}, services = createDefaultServices()) {
1389
+ const { registryPath = findDefaultRegistryPath(services), useCache = true } = options;
1390
+ const manifestPath = resolveManifestPath(registryPath, services);
1391
+ if (useCache && manifestCache.has(manifestPath)) {
1392
+ return {
1393
+ manifest: manifestCache.get(manifestPath),
1394
+ path: manifestPath,
1395
+ cached: true
1396
+ };
1397
+ }
1398
+ const manifest = await loadManifestFromPath(manifestPath, services);
1399
+ validateManifest(manifest);
1400
+ if (useCache) {
1401
+ manifestCache.set(manifestPath, manifest);
1402
+ }
1403
+ return {
1404
+ manifest,
1405
+ path: manifestPath,
1406
+ cached: false
1407
+ };
1408
+ }
1409
+ function findDefaultRegistryPath(services) {
1410
+ const candidates = ["./registry", "../promptscript-registry", "./.promptscript-registry"];
1411
+ for (const candidate of candidates) {
1412
+ const manifestPath = join3(services.cwd, candidate, MANIFEST_FILENAME);
1413
+ if (services.fs.existsSync(manifestPath)) {
1414
+ return candidate;
1415
+ }
1416
+ }
1417
+ return "./registry";
1418
+ }
1419
+ function resolveManifestPath(registryPath, services) {
1420
+ if (registryPath.endsWith(".yaml") || registryPath.endsWith(".yml")) {
1421
+ return join3(services.cwd, registryPath);
1422
+ }
1423
+ return join3(services.cwd, registryPath, MANIFEST_FILENAME);
1424
+ }
1425
+ async function loadManifestFromPath(manifestPath, services) {
1426
+ if (!services.fs.existsSync(manifestPath)) {
1427
+ throw new ManifestLoadError(
1428
+ `Registry manifest not found at: ${manifestPath}
1429
+ Run \`prs pull\` to fetch the registry or check your registry configuration.`
1430
+ );
1431
+ }
1432
+ try {
1433
+ const content = await services.fs.readFile(manifestPath, "utf-8");
1434
+ const manifest = parseYaml2(content);
1435
+ return manifest;
1436
+ } catch (error) {
1437
+ throw new ManifestLoadError(
1438
+ `Failed to parse manifest: ${manifestPath}`,
1439
+ error instanceof Error ? error : void 0
1440
+ );
1441
+ }
1442
+ }
1443
+ function validateManifest(manifest) {
1444
+ if (!manifest.version) {
1445
+ throw new ManifestLoadError("Manifest missing required field: version");
1446
+ }
1447
+ if (manifest.version !== "1") {
1448
+ throw new ManifestLoadError(`Unsupported manifest version: ${manifest.version}`);
1449
+ }
1450
+ if (!manifest.meta) {
1451
+ throw new ManifestLoadError("Manifest missing required field: meta");
1452
+ }
1453
+ if (!manifest.catalog) {
1454
+ throw new ManifestLoadError("Manifest missing required field: catalog");
1455
+ }
1456
+ if (!Array.isArray(manifest.catalog)) {
1457
+ throw new ManifestLoadError("Manifest catalog must be an array");
1458
+ }
1459
+ }
1460
+ async function loadManifestFromUrl(url = OFFICIAL_REGISTRY.manifestUrl, useCache = true) {
1461
+ if (useCache && manifestCache.has(url)) {
1462
+ return {
1463
+ manifest: manifestCache.get(url),
1464
+ url,
1465
+ cached: true
1466
+ };
1467
+ }
1468
+ try {
1469
+ const response = await fetch(url, {
1470
+ headers: {
1471
+ Accept: "application/x-yaml, text/yaml, text/plain",
1472
+ "User-Agent": "PromptScript-CLI"
1473
+ }
1474
+ });
1475
+ if (!response.ok) {
1476
+ throw new ManifestLoadError(
1477
+ `Failed to fetch manifest from ${url}: ${response.status} ${response.statusText}`
1478
+ );
1479
+ }
1480
+ const content = await response.text();
1481
+ const manifest = parseYaml2(content);
1482
+ validateManifest(manifest);
1483
+ if (useCache) {
1484
+ manifestCache.set(url, manifest);
1485
+ }
1486
+ return {
1487
+ manifest,
1488
+ url,
1489
+ cached: false
1490
+ };
1491
+ } catch (error) {
1492
+ if (error instanceof ManifestLoadError) {
1493
+ throw error;
1494
+ }
1495
+ throw new ManifestLoadError(
1496
+ `Failed to load manifest from ${url}`,
1497
+ error instanceof Error ? error : void 0
1498
+ );
1499
+ }
1500
+ }
1501
+ function isValidGitUrl(url, allowedHosts) {
1502
+ try {
1503
+ if (url.startsWith("git@")) {
1504
+ const hostMatch = url.match(/^git@([^:]+):/);
1505
+ if (hostMatch && hostMatch[1]) {
1506
+ const host = hostMatch[1];
1507
+ return allowedHosts ? allowedHosts.includes(host) : true;
1508
+ }
1509
+ return false;
1510
+ }
1511
+ if (!url.startsWith("https://") && !url.startsWith("http://")) {
1512
+ return false;
1513
+ }
1514
+ const parsed = new URL(url);
1515
+ if (!parsed.hostname || parsed.hostname.length === 0) {
1516
+ return false;
1517
+ }
1518
+ if (allowedHosts) {
1519
+ return allowedHosts.includes(parsed.hostname);
1520
+ }
1521
+ return true;
1522
+ } catch {
1523
+ return false;
1524
+ }
1525
+ }
1526
+
1527
+ // packages/cli/src/utils/suggestion-engine.ts
1528
+ async function buildProjectContext(projectInfo, services = createDefaultServices()) {
1529
+ const files = await detectProjectFiles(services);
1530
+ const dependencies = await detectDependencies(services);
1531
+ return {
1532
+ files,
1533
+ dependencies,
1534
+ languages: projectInfo.languages,
1535
+ frameworks: projectInfo.frameworks
1536
+ };
1537
+ }
1538
+ async function detectProjectFiles(services) {
1539
+ const relevantFiles = [
1540
+ "package.json",
1541
+ "tsconfig.json",
1542
+ "pyproject.toml",
1543
+ "requirements.txt",
1544
+ "Cargo.toml",
1545
+ "go.mod",
1546
+ "pom.xml",
1547
+ "build.gradle",
1548
+ ".git",
1549
+ ".env",
1550
+ ".env.example",
1551
+ "jest.config.js",
1552
+ "vitest.config.ts",
1553
+ "vitest.config.js",
1554
+ "pytest.ini",
1555
+ "next.config.js",
1556
+ "next.config.mjs",
1557
+ "next.config.ts",
1558
+ "nuxt.config.js",
1559
+ "nuxt.config.ts",
1560
+ "vite.config.js",
1561
+ "vite.config.ts",
1562
+ "Dockerfile",
1563
+ "docker-compose.yml"
1564
+ ];
1565
+ const existingFiles = [];
1566
+ for (const file of relevantFiles) {
1567
+ if (services.fs.existsSync(file)) {
1568
+ existingFiles.push(file);
1569
+ }
1570
+ }
1571
+ return existingFiles;
1572
+ }
1573
+ async function detectDependencies(services) {
1574
+ if (!services.fs.existsSync("package.json")) {
1575
+ return [];
1576
+ }
1577
+ try {
1578
+ const content = await services.fs.readFile("package.json", "utf-8");
1579
+ const pkg = JSON.parse(content);
1580
+ return [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
1581
+ } catch {
1582
+ return [];
1583
+ }
1584
+ }
1585
+ function calculateSuggestions(manifest, context) {
1586
+ const result = {
1587
+ inherit: void 0,
1588
+ use: [],
1589
+ skills: [],
1590
+ reasoning: []
1591
+ };
1592
+ const suggestedUse = /* @__PURE__ */ new Set();
1593
+ const suggestedSkills = /* @__PURE__ */ new Set();
1594
+ for (const rule of manifest.suggestionRules) {
1595
+ const match = matchCondition(rule.condition, context);
1596
+ if (match.matches) {
1597
+ if (rule.suggest.inherit) {
1598
+ if (!result.inherit) {
1599
+ result.inherit = rule.suggest.inherit;
1600
+ result.reasoning.push({
1601
+ suggestion: `inherit: ${rule.suggest.inherit}`,
1602
+ reason: match.reason,
1603
+ trigger: match.trigger,
1604
+ matchedValue: match.matchedValue
1605
+ });
1606
+ }
1607
+ }
1608
+ if (rule.suggest.use) {
1609
+ for (const useItem of rule.suggest.use) {
1610
+ if (!suggestedUse.has(useItem)) {
1611
+ suggestedUse.add(useItem);
1612
+ result.use.push(useItem);
1613
+ result.reasoning.push({
1614
+ suggestion: `use: ${useItem}`,
1615
+ reason: match.reason,
1616
+ trigger: match.trigger,
1617
+ matchedValue: match.matchedValue
1618
+ });
1619
+ }
1620
+ }
1621
+ }
1622
+ if (rule.suggest.skills) {
1623
+ for (const skill of rule.suggest.skills) {
1624
+ if (!suggestedSkills.has(skill)) {
1625
+ suggestedSkills.add(skill);
1626
+ result.skills.push(skill);
1627
+ result.reasoning.push({
1628
+ suggestion: `skill: ${skill}`,
1629
+ reason: match.reason,
1630
+ trigger: match.trigger,
1631
+ matchedValue: match.matchedValue
1632
+ });
1633
+ }
1634
+ }
1635
+ }
1636
+ }
1637
+ }
1638
+ return result;
1639
+ }
1640
+ function matchCondition(condition, context) {
1641
+ if (condition.always) {
1642
+ return {
1643
+ matches: true,
1644
+ reason: "Default recommendation",
1645
+ trigger: "always"
1646
+ };
1647
+ }
1648
+ if (condition.files) {
1649
+ for (const file of condition.files) {
1650
+ if (context.files.includes(file)) {
1651
+ return {
1652
+ matches: true,
1653
+ reason: `Detected file: ${file}`,
1654
+ trigger: "file",
1655
+ matchedValue: file
1656
+ };
1657
+ }
1658
+ }
1659
+ }
1660
+ if (condition.dependencies) {
1661
+ for (const dep of condition.dependencies) {
1662
+ if (context.dependencies.includes(dep)) {
1663
+ return {
1664
+ matches: true,
1665
+ reason: `Detected dependency: ${dep}`,
1666
+ trigger: "dependency",
1667
+ matchedValue: dep
1668
+ };
1669
+ }
1670
+ }
1671
+ }
1672
+ if (condition.languages) {
1673
+ for (const lang of condition.languages) {
1674
+ if (context.languages.includes(lang)) {
1675
+ return {
1676
+ matches: true,
1677
+ reason: `Detected language: ${lang}`,
1678
+ trigger: "language",
1679
+ matchedValue: lang
1680
+ };
1681
+ }
1682
+ }
1683
+ }
1684
+ if (condition.frameworks) {
1685
+ for (const framework of condition.frameworks) {
1686
+ if (context.frameworks.includes(framework)) {
1687
+ return {
1688
+ matches: true,
1689
+ reason: `Detected framework: ${framework}`,
1690
+ trigger: "framework",
1691
+ matchedValue: framework
1692
+ };
1693
+ }
1694
+ }
1695
+ }
1696
+ return {
1697
+ matches: false,
1698
+ reason: "",
1699
+ trigger: "always"
1700
+ };
1701
+ }
1702
+ function formatSuggestionResult(result) {
1703
+ const lines = [];
1704
+ if (result.inherit) {
1705
+ lines.push(`\u{1F4E6} Inherit: ${result.inherit}`);
1706
+ }
1707
+ if (result.use.length > 0) {
1708
+ lines.push(`\u{1F527} Use: ${result.use.join(", ")}`);
1709
+ }
1710
+ if (result.skills.length > 0) {
1711
+ lines.push(`\u26A1 Skills: ${result.skills.join(", ")}`);
1712
+ }
1713
+ if (result.reasoning.length > 0) {
1714
+ lines.push("");
1715
+ lines.push("Reasoning:");
1716
+ for (const r of result.reasoning) {
1717
+ lines.push(` \u2022 ${r.suggestion} (${r.reason})`);
1718
+ }
1719
+ }
1720
+ return lines;
1721
+ }
1722
+ function createSuggestionChoices(manifest, result) {
1723
+ const choices = [];
1724
+ if (result.inherit) {
1725
+ const entry = manifest.catalog.find((e) => e.id === result.inherit);
1726
+ choices.push({
1727
+ name: `${result.inherit} (inherit)`,
1728
+ value: `inherit:${result.inherit}`,
1729
+ checked: true,
1730
+ description: entry?.description
1731
+ });
1732
+ }
1733
+ for (const use of result.use) {
1734
+ const entry = manifest.catalog.find((e) => e.id === use);
1735
+ choices.push({
1736
+ name: `${use} (use)`,
1737
+ value: `use:${use}`,
1738
+ checked: true,
1739
+ description: entry?.description
1740
+ });
1741
+ }
1742
+ for (const skill of result.skills) {
1743
+ const entry = manifest.catalog.find((e) => e.id === skill);
1744
+ choices.push({
1745
+ name: `${skill} (skill)`,
1746
+ value: `skill:${skill}`,
1747
+ checked: true,
1748
+ description: entry?.description
1749
+ });
1750
+ }
1751
+ return choices;
1752
+ }
1753
+ function parseSelectedChoices(selected) {
1754
+ const result = {
1755
+ use: [],
1756
+ skills: []
1757
+ };
1758
+ for (const choice of selected) {
1759
+ if (choice.startsWith("inherit:")) {
1760
+ result.inherit = choice.slice("inherit:".length);
1761
+ } else if (choice.startsWith("use:")) {
1762
+ result.use.push(choice.slice("use:".length));
1763
+ } else if (choice.startsWith("skill:")) {
1764
+ result.skills.push(choice.slice("skill:".length));
1765
+ }
1766
+ }
1767
+ return result;
1768
+ }
1769
+
862
1770
  // packages/cli/src/commands/init.ts
863
1771
  var __filename = fileURLToPath(import.meta.url);
864
- var __dirname = dirname2(__filename);
1772
+ var __dirname = dirname3(__filename);
865
1773
  async function initCommand(options, services = createDefaultServices()) {
866
1774
  const { fs: fs4 } = services;
867
1775
  if (fs4.existsSync("promptscript.yaml") && !options.force) {
@@ -873,11 +1781,22 @@ async function initCommand(options, services = createDefaultServices()) {
873
1781
  const projectInfo = await detectProject(services);
874
1782
  const aiToolsDetection = await detectAITools(services);
875
1783
  const prettierConfigPath = findPrettierConfig(process.cwd());
1784
+ let manifest;
1785
+ try {
1786
+ const registryPath = options.registry ?? "./registry";
1787
+ const { manifest: loadedManifest } = await loadManifest({ registryPath }, services);
1788
+ manifest = loadedManifest;
1789
+ } catch (error) {
1790
+ if (!(error instanceof ManifestLoadError)) {
1791
+ throw error;
1792
+ }
1793
+ }
876
1794
  const config = await resolveConfig(
877
1795
  options,
878
1796
  projectInfo,
879
1797
  aiToolsDetection,
880
1798
  prettierConfigPath,
1799
+ manifest,
881
1800
  services
882
1801
  );
883
1802
  const spinner = createSpinner("Creating PromptScript configuration...").start();
@@ -886,11 +1805,45 @@ async function initCommand(options, services = createDefaultServices()) {
886
1805
  await fs4.writeFile("promptscript.yaml", configContent, "utf-8");
887
1806
  const projectPsContent = generateProjectPs(config, projectInfo);
888
1807
  await fs4.writeFile(".promptscript/project.prs", projectPsContent, "utf-8");
1808
+ const installedSkillPaths = [];
1809
+ if (options.migrate) {
1810
+ if (config.targets.includes("claude")) {
1811
+ await fs4.mkdir(".claude/skills/migrate-to-promptscript", { recursive: true });
1812
+ await fs4.writeFile(
1813
+ ".claude/skills/migrate-to-promptscript/SKILL.md",
1814
+ MIGRATE_SKILL_CLAUDE,
1815
+ "utf-8"
1816
+ );
1817
+ installedSkillPaths.push(".claude/skills/migrate-to-promptscript/SKILL.md");
1818
+ }
1819
+ if (config.targets.includes("github")) {
1820
+ await fs4.mkdir(".github/skills/migrate-to-promptscript", { recursive: true });
1821
+ await fs4.writeFile(
1822
+ ".github/skills/migrate-to-promptscript/SKILL.md",
1823
+ MIGRATE_SKILL_GITHUB,
1824
+ "utf-8"
1825
+ );
1826
+ installedSkillPaths.push(".github/skills/migrate-to-promptscript/SKILL.md");
1827
+ }
1828
+ if (config.targets.includes("cursor")) {
1829
+ await fs4.mkdir(".cursor/commands", { recursive: true });
1830
+ await fs4.writeFile(".cursor/commands/migrate.md", MIGRATE_SKILL_CURSOR, "utf-8");
1831
+ installedSkillPaths.push(".cursor/commands/migrate.md");
1832
+ }
1833
+ if (config.targets.includes("antigravity")) {
1834
+ await fs4.mkdir(".agent/rules", { recursive: true });
1835
+ await fs4.writeFile(".agent/rules/migrate.md", MIGRATE_SKILL_ANTIGRAVITY, "utf-8");
1836
+ installedSkillPaths.push(".agent/rules/migrate.md");
1837
+ }
1838
+ }
889
1839
  spinner.succeed("PromptScript initialized");
890
1840
  ConsoleOutput.newline();
891
1841
  console.log("Created:");
892
1842
  ConsoleOutput.success("promptscript.yaml");
893
1843
  ConsoleOutput.success(".promptscript/project.prs");
1844
+ for (const skillPath of installedSkillPaths) {
1845
+ ConsoleOutput.success(skillPath);
1846
+ }
894
1847
  ConsoleOutput.newline();
895
1848
  console.log("Configuration:");
896
1849
  ConsoleOutput.muted(` Project: ${config.projectId}`);
@@ -899,7 +1852,14 @@ async function initCommand(options, services = createDefaultServices()) {
899
1852
  ConsoleOutput.muted(` Inherit: ${config.inherit}`);
900
1853
  }
901
1854
  if (config.registry) {
902
- ConsoleOutput.muted(` Registry: ${config.registry}`);
1855
+ if (config.registry.type === "git") {
1856
+ ConsoleOutput.muted(` Registry: ${config.registry.url} (${config.registry.ref})`);
1857
+ } else {
1858
+ ConsoleOutput.muted(` Registry: ${config.registry.path} (local)`);
1859
+ }
1860
+ }
1861
+ if (config.use && config.use.length > 0) {
1862
+ ConsoleOutput.muted(` Use: ${config.use.join(", ")}`);
903
1863
  }
904
1864
  ConsoleOutput.newline();
905
1865
  if (config.prettierConfigPath) {
@@ -911,17 +1871,35 @@ async function initCommand(options, services = createDefaultServices()) {
911
1871
  }
912
1872
  ConsoleOutput.newline();
913
1873
  console.log("Next steps:");
914
- ConsoleOutput.muted("1. Edit .promptscript/project.prs to customize your AI instructions");
915
- ConsoleOutput.muted("2. Run: prs compile");
916
- if (hasMigrationCandidates(aiToolsDetection)) {
917
- const migrationHint = formatMigrationHint(aiToolsDetection);
918
- for (const line of migrationHint) {
919
- if (line.startsWith("\u{1F4CB}") || line.includes("migrated") || line.includes("See:") || line.includes("Or use")) {
920
- ConsoleOutput.info(line.replace(/^\s+/, ""));
921
- } else if (line.trim().startsWith("\u2022")) {
922
- ConsoleOutput.muted(line);
923
- } else if (line.trim()) {
924
- console.log(line);
1874
+ if (options.migrate && installedSkillPaths.length > 0) {
1875
+ ConsoleOutput.muted("1. Use the migration skill to convert existing instructions:");
1876
+ if (config.targets.includes("claude")) {
1877
+ ConsoleOutput.muted(" Claude Code: /migrate");
1878
+ }
1879
+ if (config.targets.includes("github")) {
1880
+ ConsoleOutput.muted(" GitHub Copilot: @workspace /migrate");
1881
+ }
1882
+ if (config.targets.includes("cursor")) {
1883
+ ConsoleOutput.muted(" Cursor: /migrate");
1884
+ }
1885
+ if (config.targets.includes("antigravity")) {
1886
+ ConsoleOutput.muted(' Antigravity: Ask to "migrate to PromptScript"');
1887
+ }
1888
+ ConsoleOutput.muted("2. Review generated .promptscript/project.prs");
1889
+ ConsoleOutput.muted("3. Run: prs compile");
1890
+ } else {
1891
+ ConsoleOutput.muted("1. Edit .promptscript/project.prs to customize your AI instructions");
1892
+ ConsoleOutput.muted("2. Run: prs compile");
1893
+ if (hasMigrationCandidates(aiToolsDetection)) {
1894
+ const migrationHint = formatMigrationHint(aiToolsDetection);
1895
+ for (const line of migrationHint) {
1896
+ if (line.startsWith("\u{1F4CB}") || line.includes("migrated") || line.includes("See:") || line.includes("Or use")) {
1897
+ ConsoleOutput.info(line.replace(/^\s+/, ""));
1898
+ } else if (line.trim().startsWith("\u2022")) {
1899
+ ConsoleOutput.muted(line);
1900
+ } else if (line.trim()) {
1901
+ console.log(line);
1902
+ }
925
1903
  }
926
1904
  }
927
1905
  }
@@ -935,23 +1913,46 @@ async function initCommand(options, services = createDefaultServices()) {
935
1913
  process.exit(1);
936
1914
  }
937
1915
  }
938
- async function resolveConfig(options, projectInfo, aiToolsDetection, prettierConfigPath, services) {
1916
+ async function resolveConfig(options, projectInfo, aiToolsDetection, prettierConfigPath, manifest, services) {
939
1917
  if (options.yes) {
1918
+ let inherit = options.inherit;
1919
+ let use;
1920
+ let activeManifest = manifest;
1921
+ if (!activeManifest) {
1922
+ try {
1923
+ const { manifest: remoteManifest } = await loadManifestFromUrl();
1924
+ activeManifest = remoteManifest;
1925
+ } catch {
1926
+ }
1927
+ }
1928
+ if (activeManifest) {
1929
+ const context = await buildProjectContext(projectInfo, services);
1930
+ const suggestions = calculateSuggestions(activeManifest, context);
1931
+ if (!inherit && suggestions.inherit) {
1932
+ inherit = suggestions.inherit;
1933
+ }
1934
+ if (suggestions.use.length > 0) {
1935
+ use = suggestions.use;
1936
+ }
1937
+ }
1938
+ const registry = options.registry ? { type: "local", path: options.registry } : { type: "git", url: OFFICIAL_REGISTRY.url, ref: OFFICIAL_REGISTRY.branch };
940
1939
  return {
941
1940
  projectId: options.name ?? projectInfo.name,
942
1941
  team: options.team,
943
- inherit: options.inherit,
944
- registry: options.registry ?? "./registry",
1942
+ inherit,
1943
+ use,
1944
+ registry,
945
1945
  targets: options.targets ?? getSuggestedTargets(aiToolsDetection),
946
1946
  prettierConfigPath
947
1947
  };
948
1948
  }
949
1949
  if (!options.interactive && options.name && options.targets) {
1950
+ const registry = options.registry ? { type: "local", path: options.registry } : void 0;
950
1951
  return {
951
1952
  projectId: options.name,
952
1953
  team: options.team,
953
1954
  inherit: options.inherit,
954
- registry: options.registry,
1955
+ registry,
955
1956
  targets: options.targets,
956
1957
  prettierConfigPath
957
1958
  };
@@ -961,10 +1962,11 @@ async function resolveConfig(options, projectInfo, aiToolsDetection, prettierCon
961
1962
  projectInfo,
962
1963
  aiToolsDetection,
963
1964
  prettierConfigPath,
1965
+ manifest,
964
1966
  services
965
1967
  );
966
1968
  }
967
- async function runInteractivePrompts(options, projectInfo, aiToolsDetection, prettierConfigPath, services) {
1969
+ async function runInteractivePrompts(options, projectInfo, aiToolsDetection, prettierConfigPath, manifest, services) {
968
1970
  const { prompts: prompts2 } = services;
969
1971
  ConsoleOutput.newline();
970
1972
  console.log("\u{1F680} PromptScript Setup");
@@ -990,33 +1992,124 @@ async function runInteractivePrompts(options, projectInfo, aiToolsDetection, pre
990
1992
  message: "Project name:",
991
1993
  default: options.name ?? projectInfo.name
992
1994
  });
993
- const wantsInherit = await prompts2.confirm({
994
- message: "Do you want to inherit from a parent configuration?",
995
- default: false
1995
+ let registry;
1996
+ let activeManifest = manifest;
1997
+ const registryChoice = await prompts2.select({
1998
+ message: "Registry configuration:",
1999
+ choices: [
2000
+ {
2001
+ name: `\u{1F4E6} Use official PromptScript Registry (${OFFICIAL_REGISTRY.url})`,
2002
+ value: "official"
2003
+ },
2004
+ {
2005
+ name: "\u{1F517} Connect to a custom Git registry",
2006
+ value: "custom-git"
2007
+ },
2008
+ {
2009
+ name: "\u{1F4C1} Use a local registry directory",
2010
+ value: "local"
2011
+ },
2012
+ {
2013
+ name: "\u23ED\uFE0F Skip registry (configure later)",
2014
+ value: "skip"
2015
+ }
2016
+ ],
2017
+ default: manifest ? "official" : "skip"
996
2018
  });
997
- let inherit;
998
- if (wantsInherit) {
999
- inherit = await prompts2.input({
1000
- message: "Inheritance path (e.g., @company/team):",
1001
- default: options.inherit ?? "@company/team",
2019
+ if (registryChoice === "official") {
2020
+ registry = {
2021
+ type: "git",
2022
+ url: OFFICIAL_REGISTRY.url,
2023
+ ref: OFFICIAL_REGISTRY.branch
2024
+ };
2025
+ if (!activeManifest) {
2026
+ try {
2027
+ ConsoleOutput.muted("Fetching registry manifest...");
2028
+ const { manifest: remoteManifest } = await loadManifestFromUrl();
2029
+ activeManifest = remoteManifest;
2030
+ ConsoleOutput.success(
2031
+ `Loaded ${remoteManifest.catalog.length} configurations from official registry`
2032
+ );
2033
+ } catch {
2034
+ ConsoleOutput.warn("Could not fetch registry manifest - suggestions will be limited");
2035
+ }
2036
+ }
2037
+ } else if (registryChoice === "custom-git") {
2038
+ const gitUrl = await prompts2.input({
2039
+ message: "Git repository URL:",
2040
+ default: "https://github.com/your-org/your-registry.git",
1002
2041
  validate: (value) => {
1003
- if (!value.startsWith("@")) {
1004
- return "Inheritance path should start with @";
2042
+ if (!isValidGitUrl(value)) {
2043
+ return "Please enter a valid Git repository URL (https:// or git@)";
1005
2044
  }
1006
2045
  return true;
1007
2046
  }
1008
2047
  });
1009
- }
1010
- const wantsRegistry = await prompts2.confirm({
1011
- message: "Do you want to configure a registry?",
1012
- default: false
1013
- });
1014
- let registry;
1015
- if (wantsRegistry) {
1016
- registry = await prompts2.input({
1017
- message: "Registry path:",
2048
+ const gitRef = await prompts2.input({
2049
+ message: "Branch or tag:",
2050
+ default: "main"
2051
+ });
2052
+ registry = {
2053
+ type: "git",
2054
+ url: gitUrl,
2055
+ ref: gitRef
2056
+ };
2057
+ } else if (registryChoice === "local") {
2058
+ const localPath = await prompts2.input({
2059
+ message: "Local registry path:",
1018
2060
  default: options.registry ?? "./registry"
1019
2061
  });
2062
+ registry = {
2063
+ type: "local",
2064
+ path: localPath
2065
+ };
2066
+ }
2067
+ let inherit = options.inherit;
2068
+ let use;
2069
+ if (activeManifest) {
2070
+ const context = await buildProjectContext(projectInfo, services);
2071
+ const suggestions = calculateSuggestions(activeManifest, context);
2072
+ if (suggestions.inherit || suggestions.use.length > 0) {
2073
+ ConsoleOutput.newline();
2074
+ console.log("\u{1F4E6} Suggested configurations based on your project:");
2075
+ const suggestionLines = formatSuggestionResult(suggestions);
2076
+ for (const line of suggestionLines) {
2077
+ ConsoleOutput.muted(` ${line}`);
2078
+ }
2079
+ ConsoleOutput.newline();
2080
+ const choices = createSuggestionChoices(activeManifest, suggestions);
2081
+ if (choices.length > 0) {
2082
+ const selected = await prompts2.checkbox({
2083
+ message: "Select configurations to use:",
2084
+ choices: choices.map((c) => ({
2085
+ name: c.description ? `${c.name} - ${c.description}` : c.name,
2086
+ value: c.value,
2087
+ checked: c.checked
2088
+ }))
2089
+ });
2090
+ const parsed = parseSelectedChoices(selected);
2091
+ inherit = parsed.inherit;
2092
+ use = parsed.use.length > 0 ? parsed.use : void 0;
2093
+ }
2094
+ }
2095
+ }
2096
+ if (!inherit) {
2097
+ const wantsInherit = await prompts2.confirm({
2098
+ message: "Do you want to inherit from a parent configuration?",
2099
+ default: false
2100
+ });
2101
+ if (wantsInherit) {
2102
+ inherit = await prompts2.input({
2103
+ message: "Inheritance path (e.g., @stacks/react):",
2104
+ default: options.inherit ?? "@stacks/react",
2105
+ validate: (value) => {
2106
+ if (!value.startsWith("@")) {
2107
+ return "Inheritance path should start with @";
2108
+ }
2109
+ return true;
2110
+ }
2111
+ });
2112
+ }
1020
2113
  }
1021
2114
  const suggestedTargets = getSuggestedTargets(aiToolsDetection);
1022
2115
  const allTargets = getAllTargets();
@@ -1045,6 +2138,7 @@ async function runInteractivePrompts(options, projectInfo, aiToolsDetection, pre
1045
2138
  projectId,
1046
2139
  team,
1047
2140
  inherit,
2141
+ use,
1048
2142
  registry,
1049
2143
  targets,
1050
2144
  prettierConfigPath
@@ -1068,13 +2162,34 @@ function generateConfig(config) {
1068
2162
  if (config.inherit) {
1069
2163
  lines.push(`inherit: '${config.inherit}'`);
1070
2164
  } else {
1071
- lines.push("# inherit: '@company/team'");
2165
+ lines.push("# inherit: '@stacks/react'");
2166
+ }
2167
+ lines.push("");
2168
+ if (config.use && config.use.length > 0) {
2169
+ lines.push("use:");
2170
+ for (const useItem of config.use) {
2171
+ lines.push(` - '${useItem}'`);
2172
+ }
2173
+ } else {
2174
+ lines.push("# use:", "# - '@fragments/testing'", "# - '@fragments/typescript'");
1072
2175
  }
1073
2176
  lines.push("");
1074
2177
  if (config.registry) {
1075
- lines.push("registry:", ` path: '${config.registry}'`);
2178
+ if (config.registry.type === "git") {
2179
+ lines.push("registry:", " git:", ` url: '${config.registry.url}'`);
2180
+ if (config.registry.ref) {
2181
+ lines.push(` ref: '${config.registry.ref}'`);
2182
+ }
2183
+ } else {
2184
+ lines.push("registry:", ` path: '${config.registry.path}'`);
2185
+ }
1076
2186
  } else {
1077
- lines.push("# registry:", "# path: './registry'");
2187
+ lines.push(
2188
+ "# registry:",
2189
+ "# git:",
2190
+ "# url: 'https://github.com/mrwogu/promptscript-registry.git'",
2191
+ "# ref: 'main'"
2192
+ );
1078
2193
  }
1079
2194
  lines.push("", "targets:");
1080
2195
  for (const target of config.targets) {
@@ -1095,7 +2210,13 @@ function generateConfig(config) {
1095
2210
  return lines.join("\n");
1096
2211
  }
1097
2212
  function generateProjectPs(config, projectInfo) {
1098
- const inheritLine = config.inherit ? `@inherit ${config.inherit}` : "# @inherit @company/team";
2213
+ const inheritLine = config.inherit ? `@inherit ${config.inherit}` : "# @inherit @stacks/react";
2214
+ let useLines = "";
2215
+ if (config.use && config.use.length > 0) {
2216
+ useLines = config.use.map((u) => `@use ${u}`).join("\n");
2217
+ } else {
2218
+ useLines = "# @use @fragments/testing\n# @use @fragments/typescript";
2219
+ }
1099
2220
  const languagesLine = projectInfo.languages.length > 0 ? ` languages: [${projectInfo.languages.join(", ")}]` : " # languages: [typescript]";
1100
2221
  const frameworksLine = projectInfo.frameworks.length > 0 ? ` frameworks: [${projectInfo.frameworks.join(", ")}]` : " # frameworks: []";
1101
2222
  const syntaxVersion = getPackageVersion(__dirname, "./package.json");
@@ -1108,11 +2229,12 @@ function generateProjectPs(config, projectInfo) {
1108
2229
  }
1109
2230
 
1110
2231
  ${inheritLine}
2232
+ ${useLines}
1111
2233
 
1112
2234
  @identity {
1113
2235
  """
1114
2236
  You are working on the ${config.projectId} project.
1115
-
2237
+
1116
2238
  [Describe your project here]
1117
2239
  """
1118
2240
  }
@@ -1141,7 +2263,7 @@ ${frameworksLine}
1141
2263
  }
1142
2264
 
1143
2265
  // packages/cli/src/commands/compile.ts
1144
- import { resolve as resolve4, dirname as dirname5 } from "path";
2266
+ import { resolve as resolve4, dirname as dirname6 } from "path";
1145
2267
  import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile6 } from "fs/promises";
1146
2268
  import { existsSync as existsSync7 } from "fs";
1147
2269
  import chokidar from "chokidar";
@@ -1149,7 +2271,7 @@ import chokidar from "chokidar";
1149
2271
  // packages/cli/src/config/loader.ts
1150
2272
  import { readFile as readFile3 } from "fs/promises";
1151
2273
  import { existsSync as existsSync3 } from "fs";
1152
- import { parse as parseYaml2 } from "yaml";
2274
+ import { parse as parseYaml3 } from "yaml";
1153
2275
  var CONFIG_FILES = [
1154
2276
  "promptscript.yaml",
1155
2277
  "promptscript.yml",
@@ -1196,7 +2318,7 @@ async function loadConfig(customPath) {
1196
2318
  let content = await readFile3(configFile, "utf-8");
1197
2319
  content = interpolateEnvVars(content);
1198
2320
  try {
1199
- return parseYaml2(content);
2321
+ return parseYaml3(content);
1200
2322
  } catch (error) {
1201
2323
  const message = error instanceof Error ? error.message : "Unknown parse error";
1202
2324
  throw new Error(`Failed to parse ${configFile}: ${message}`);
@@ -1365,10 +2487,26 @@ ${this.convention.rootWrapper.end}`;
1365
2487
  /**
1366
2488
  * Escape markdown special characters for Prettier compatibility.
1367
2489
  * - Escapes __ to \_\_ (to avoid emphasis)
1368
- * - Escapes /* to /\* (to avoid glob patterns being interpreted)
2490
+ * - Escapes * in glob patterns (like packages/*) outside backticks
1369
2491
  */
1370
2492
  escapeMarkdownSpecialChars(content) {
1371
- return content.replace(/__/g, "\\_\\_").replace(/\/\*/g, "/\\*");
2493
+ return content.split("\n").map((line) => {
2494
+ let result = line.replace(/__/g, "\\_\\_");
2495
+ result = this.escapeGlobAsteriskOutsideBackticks(result);
2496
+ return result;
2497
+ }).join("\n");
2498
+ }
2499
+ /**
2500
+ * Escape glob asterisks (like packages/* or .cursor/rules/*.md) outside of backticks.
2501
+ */
2502
+ escapeGlobAsteriskOutsideBackticks(line) {
2503
+ const parts = line.split("`");
2504
+ return parts.map((part, index) => {
2505
+ if (index % 2 === 0) {
2506
+ return part.replace(/\/\*/g, "/\\*");
2507
+ }
2508
+ return part;
2509
+ }).join("`");
1372
2510
  }
1373
2511
  indentContent(content, renderer, level) {
1374
2512
  const indent = renderer.indent ?? "";
@@ -1540,6 +2678,7 @@ var BaseFormatter = class {
1540
2678
  * - Trims trailing whitespace from lines
1541
2679
  * - Normalizes markdown table formatting
1542
2680
  * - Adds blank lines before lists when preceded by text
2681
+ * - Adds blank lines before code blocks when preceded by text
1543
2682
  * - Escapes markdown special characters in paths
1544
2683
  */
1545
2684
  normalizeMarkdownForPrettier(content) {
@@ -1548,8 +2687,13 @@ var BaseFormatter = class {
1548
2687
  let inCodeBlock = false;
1549
2688
  for (const line of lines) {
1550
2689
  const trimmed = line.trimEnd();
1551
- if (trimmed.startsWith("```")) {
2690
+ if (trimmed.trimStart().startsWith("```")) {
1552
2691
  inCodeBlock = !inCodeBlock;
2692
+ if (trimmed.length > 0) {
2693
+ const match2 = line.match(/^(\s*)/);
2694
+ const leadingSpaces2 = match2?.[1]?.length ?? 0;
2695
+ minIndent = Math.min(minIndent, leadingSpaces2);
2696
+ }
1553
2697
  continue;
1554
2698
  }
1555
2699
  if (inCodeBlock) continue;
@@ -1565,17 +2709,30 @@ var BaseFormatter = class {
1565
2709
  inCodeBlock = false;
1566
2710
  for (const line of lines) {
1567
2711
  const trimmedLine = line.trimEnd();
1568
- if (trimmedLine.trimStart().startsWith("```")) {
2712
+ const unindentedLine = minIndent > 0 ? trimmedLine.slice(minIndent) : trimmedLine;
2713
+ const isCodeBlockMarker = trimmedLine.trimStart().startsWith("```");
2714
+ const isCodeBlockStart = isCodeBlockMarker && !inCodeBlock;
2715
+ if (isCodeBlockMarker) {
1569
2716
  inCodeBlock = !inCodeBlock;
1570
2717
  }
1571
- const unindentedLine = minIndent > 0 ? trimmedLine.slice(minIndent) : trimmedLine;
1572
2718
  if (inCodeBlock || unindentedLine.startsWith("```")) {
2719
+ if (isCodeBlockStart) {
2720
+ const prevLine2 = result.length > 0 ? result[result.length - 1] ?? "" : "";
2721
+ if (prevLine2.trim() && !prevLine2.startsWith("#")) {
2722
+ result.push("");
2723
+ }
2724
+ }
1573
2725
  result.push(unindentedLine);
1574
2726
  continue;
1575
2727
  }
1576
2728
  let processedLine = unindentedLine;
1577
2729
  processedLine = processedLine.replace(/__([^_]+)__/g, "\\_\\_$1\\_\\_");
1578
- processedLine = processedLine.replace(/\/\*/g, "/\\*");
2730
+ processedLine = this.escapeGlobAsteriskOutsideBackticks(processedLine);
2731
+ const prevLine = result.length > 0 ? result[result.length - 1] : "";
2732
+ const isHeader = prevLine?.trimStart().startsWith("#");
2733
+ if (isHeader && processedLine.trim()) {
2734
+ result.push("");
2735
+ }
1579
2736
  if (processedLine.trimStart().startsWith("|") && processedLine.trimEnd().endsWith("|")) {
1580
2737
  inTable = true;
1581
2738
  tableLines.push(processedLine.trim());
@@ -1585,9 +2742,14 @@ var BaseFormatter = class {
1585
2742
  tableLines = [];
1586
2743
  inTable = false;
1587
2744
  }
1588
- const prevLine = result.length > 0 ? result[result.length - 1] : "";
1589
2745
  const isListItem = processedLine.trimStart().startsWith("- ");
1590
- if (isListItem && prevLine && !prevLine.trimStart().startsWith("- ")) {
2746
+ const isNumberedItem = /^\d+\.\s/.test(processedLine.trimStart());
2747
+ const prevLineTrimmed = prevLine?.trimStart() ?? "";
2748
+ const isPrevListItem = prevLineTrimmed.startsWith("- ") || /^\d+\.\s/.test(prevLineTrimmed);
2749
+ const prevEndsWithColon = prevLine?.trimEnd().endsWith(":") ?? false;
2750
+ if ((isListItem || isNumberedItem) && prevLine && !isPrevListItem && !isHeader) {
2751
+ result.push("");
2752
+ } else if ((isListItem || isNumberedItem) && prevEndsWithColon) {
1591
2753
  result.push("");
1592
2754
  }
1593
2755
  result.push(processedLine);
@@ -1598,6 +2760,19 @@ var BaseFormatter = class {
1598
2760
  }
1599
2761
  return result.join("\n");
1600
2762
  }
2763
+ /**
2764
+ * Escape glob asterisks (like packages/* or .cursor/rules/*.md) outside of backticks.
2765
+ * Prettier escapes these to prevent them from being interpreted as emphasis markers.
2766
+ */
2767
+ escapeGlobAsteriskOutsideBackticks(line) {
2768
+ const parts = line.split("`");
2769
+ return parts.map((part, index) => {
2770
+ if (index % 2 === 0) {
2771
+ return part.replace(/\/\*/g, "/\\*");
2772
+ }
2773
+ return part;
2774
+ }).join("`");
2775
+ }
1601
2776
  /**
1602
2777
  * Strip all leading indentation from markdown content.
1603
2778
  * Used for AGENTS.md where content from multiple sources has inconsistent indentation.
@@ -1611,7 +2786,7 @@ var BaseFormatter = class {
1611
2786
  const trimmedEnd = line.trimEnd();
1612
2787
  if (trimmedEnd.trimStart().startsWith("```")) {
1613
2788
  if (!inCodeBlock) {
1614
- const prevLine2 = result.length > 0 ? result[result.length - 1] : "";
2789
+ const prevLine2 = result.length > 0 ? result[result.length - 1] ?? "" : "";
1615
2790
  if (prevLine2 && prevLine2.trim()) {
1616
2791
  result.push("");
1617
2792
  }
@@ -1626,9 +2801,14 @@ var BaseFormatter = class {
1626
2801
  }
1627
2802
  let stripped = trimmedEnd.trimStart();
1628
2803
  stripped = stripped.replace(/__/g, "\\_\\_");
1629
- stripped = stripped.replace(/\/\*/g, "/\\*");
1630
- const prevLine = result.length > 0 ? result[result.length - 1] : "";
1631
- if (stripped.startsWith("- ") && prevLine && !prevLine.startsWith("- ")) {
2804
+ stripped = this.escapeGlobAsteriskOutsideBackticks(stripped);
2805
+ const prevLine = result.length > 0 ? result[result.length - 1] ?? "" : "";
2806
+ const isListItem = stripped.startsWith("- ") || /^\d+\.\s/.test(stripped);
2807
+ const isPrevList = prevLine.startsWith("- ") || /^\d+\.\s/.test(prevLine);
2808
+ const prevEndsWithColon = prevLine.trimEnd().endsWith(":");
2809
+ if (isListItem && prevLine && !isPrevList) {
2810
+ result.push("");
2811
+ } else if (isListItem && prevEndsWithColon) {
1632
2812
  result.push("");
1633
2813
  }
1634
2814
  result.push(stripped);
@@ -1969,7 +3149,9 @@ var GitHubFormatter = class extends BaseFormatter {
1969
3149
  lines.push(`# ${config.description}`);
1970
3150
  lines.push("");
1971
3151
  if (config.content) {
1972
- lines.push(config.content);
3152
+ const dedentedContent = this.dedent(config.content);
3153
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3154
+ lines.push(normalizedContent);
1973
3155
  }
1974
3156
  return {
1975
3157
  path: `.github/instructions/${config.name}.instructions.md`,
@@ -2057,7 +3239,8 @@ var GitHubFormatter = class extends BaseFormatter {
2057
3239
  generatePromptFile(config) {
2058
3240
  const lines = [];
2059
3241
  lines.push("---");
2060
- lines.push(`description: "${config.description}"`);
3242
+ const descQuote = config.description.includes("'") ? '"' : "'";
3243
+ lines.push(`description: ${descQuote}${config.description}${descQuote}`);
2061
3244
  if (config.mode === "agent") {
2062
3245
  lines.push("mode: agent");
2063
3246
  if (config.tools && config.tools.length > 0) {
@@ -2069,11 +3252,13 @@ var GitHubFormatter = class extends BaseFormatter {
2069
3252
  lines.push("---");
2070
3253
  lines.push("");
2071
3254
  if (config.content) {
2072
- lines.push(config.content);
3255
+ const dedentedContent = this.dedent(config.content);
3256
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3257
+ lines.push(normalizedContent);
2073
3258
  }
2074
3259
  return {
2075
3260
  path: `.github/prompts/${config.name}.prompt.md`,
2076
- content: lines.join("\n")
3261
+ content: lines.join("\n") + "\n"
2077
3262
  };
2078
3263
  }
2079
3264
  // ============================================================
@@ -2115,7 +3300,8 @@ var GitHubFormatter = class extends BaseFormatter {
2115
3300
  lines.push("---");
2116
3301
  lines.push("");
2117
3302
  if (config.content) {
2118
- const normalizedContent = this.normalizeMarkdownForPrettier(config.content);
3303
+ const dedentedContent = this.dedent(config.content);
3304
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2119
3305
  lines.push(normalizedContent);
2120
3306
  }
2121
3307
  return {
@@ -2123,6 +3309,28 @@ var GitHubFormatter = class extends BaseFormatter {
2123
3309
  content: lines.join("\n") + "\n"
2124
3310
  };
2125
3311
  }
3312
+ /**
3313
+ * Remove common leading indentation from multiline text.
3314
+ * Calculates minimum indent from lines 2+ only, since line 1 may have been
3315
+ * trimmed (losing its indentation) while subsequent lines retain theirs.
3316
+ */
3317
+ dedent(text) {
3318
+ const lines = text.split("\n");
3319
+ if (lines.length <= 1) return text.trim();
3320
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
3321
+ const match = line.match(/^(\s*)/);
3322
+ const indent = match?.[1]?.length ?? 0;
3323
+ return Math.min(min, indent);
3324
+ }, Infinity);
3325
+ if (minIndent === 0 || minIndent === Infinity) {
3326
+ return text.trim();
3327
+ }
3328
+ const firstLine = lines[0] ?? "";
3329
+ return [
3330
+ firstLine.trim(),
3331
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
3332
+ ].join("\n").trim();
3333
+ }
2126
3334
  // ============================================================
2127
3335
  // AGENTS.md Generation
2128
3336
  // ============================================================
@@ -2258,7 +3466,9 @@ var GitHubFormatter = class extends BaseFormatter {
2258
3466
  lines.push("---");
2259
3467
  lines.push("");
2260
3468
  if (config.content) {
2261
- lines.push(config.content);
3469
+ const dedentedContent = this.dedent(config.content);
3470
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3471
+ lines.push(normalizedContent);
2262
3472
  }
2263
3473
  return {
2264
3474
  path: `.github/agents/${config.name}.md`,
@@ -2713,7 +3923,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2713
3923
  lines.push(`# ${config.description}`);
2714
3924
  lines.push("");
2715
3925
  if (config.content) {
2716
- lines.push(config.content);
3926
+ const dedentedContent = this.dedent(config.content);
3927
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3928
+ lines.push(normalizedContent);
2717
3929
  }
2718
3930
  return {
2719
3931
  path: `.claude/rules/${config.name}.md`,
@@ -2812,7 +4024,8 @@ var ClaudeFormatter = class extends BaseFormatter {
2812
4024
  lines.push("---");
2813
4025
  lines.push("");
2814
4026
  if (config.content) {
2815
- const normalizedContent = this.normalizeMarkdownForPrettier(config.content);
4027
+ const dedentedContent = this.dedent(config.content);
4028
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2816
4029
  lines.push(normalizedContent);
2817
4030
  }
2818
4031
  return {
@@ -2820,6 +4033,28 @@ var ClaudeFormatter = class extends BaseFormatter {
2820
4033
  content: lines.join("\n") + "\n"
2821
4034
  };
2822
4035
  }
4036
+ /**
4037
+ * Remove common leading indentation from multiline text.
4038
+ * Calculates minimum indent from lines 2+ only, since line 1 may have been
4039
+ * trimmed (losing its indentation) while subsequent lines retain theirs.
4040
+ */
4041
+ dedent(text) {
4042
+ const lines = text.split("\n");
4043
+ if (lines.length <= 1) return text.trim();
4044
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
4045
+ const match = line.match(/^(\s*)/);
4046
+ const indent = match?.[1]?.length ?? 0;
4047
+ return Math.min(min, indent);
4048
+ }, Infinity);
4049
+ if (minIndent === 0 || minIndent === Infinity) {
4050
+ return text.trim();
4051
+ }
4052
+ const firstLine = lines[0] ?? "";
4053
+ return [
4054
+ firstLine.trim(),
4055
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
4056
+ ].join("\n").trim();
4057
+ }
2823
4058
  // ============================================================
2824
4059
  // Agent File Generation
2825
4060
  // ============================================================
@@ -2922,7 +4157,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2922
4157
  lines.push("---");
2923
4158
  lines.push("");
2924
4159
  if (config.content) {
2925
- lines.push(config.content);
4160
+ const dedentedContent = this.dedent(config.content);
4161
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
4162
+ lines.push(normalizedContent);
2926
4163
  }
2927
4164
  return {
2928
4165
  path: `.claude/agents/${config.name}.md`,
@@ -2977,7 +4214,8 @@ var ClaudeFormatter = class extends BaseFormatter {
2977
4214
  const cleanText = text.split(/\n{2,}/).map(
2978
4215
  (para) => para.split("\n").map((line) => line.trim()).filter((line) => line).join("\n")
2979
4216
  ).filter((para) => para).join("\n\n");
2980
- return renderer.renderSection("Project", cleanText) + "\n";
4217
+ const normalizedText = this.normalizeMarkdownForPrettier(cleanText);
4218
+ return renderer.renderSection("Project", normalizedText) + "\n";
2981
4219
  }
2982
4220
  techStack(ast, renderer) {
2983
4221
  const context = this.findBlock(ast, "context");
@@ -3035,8 +4273,9 @@ var ClaudeFormatter = class extends BaseFormatter {
3035
4273
  const text = this.extractText(context.content);
3036
4274
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
3037
4275
  if (!archMatch) return null;
3038
- const content = archMatch.replace("## Architecture", "").trim();
3039
- return renderer.renderSection("Architecture", content) + "\n";
4276
+ const content = archMatch.replace("## Architecture", "");
4277
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
4278
+ return renderer.renderSection("Architecture", normalizedContent.trim()) + "\n";
3040
4279
  }
3041
4280
  codeStandards(ast, renderer) {
3042
4281
  const standards = this.findBlock(ast, "standards");
@@ -3117,7 +4356,7 @@ var ClaudeFormatter = class extends BaseFormatter {
3117
4356
  const props = this.getProps(shortcuts.content);
3118
4357
  for (const [cmd, desc] of Object.entries(props)) {
3119
4358
  const shortDesc = this.valueToString(desc).split("\n")[0] ?? "";
3120
- commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`);
4359
+ commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`.trimEnd());
3121
4360
  }
3122
4361
  }
3123
4362
  if (commandLines.length === 0) return null;
@@ -3126,8 +4365,9 @@ var ClaudeFormatter = class extends BaseFormatter {
3126
4365
  const text = this.extractText(knowledge.content);
3127
4366
  const match = this.extractSectionWithCodeBlock(text, "## Development Commands");
3128
4367
  if (match) {
3129
- const devCmds = match.replace("## Development Commands", "").trim();
3130
- content += "\n\n" + devCmds;
4368
+ const devCmds = match.replace("## Development Commands", "");
4369
+ const normalizedDevCmds = this.normalizeMarkdownForPrettier(devCmds);
4370
+ content += "\n\n" + normalizedDevCmds.trim();
3131
4371
  }
3132
4372
  }
3133
4373
  return renderer.renderSection("Commands", content) + "\n";
@@ -3138,8 +4378,9 @@ var ClaudeFormatter = class extends BaseFormatter {
3138
4378
  const text = this.extractText(knowledge.content);
3139
4379
  const match = this.extractSectionWithCodeBlock(text, "## Post-Work Verification");
3140
4380
  if (!match) return null;
3141
- const content = match.replace("## Post-Work Verification", "").trim();
3142
- return renderer.renderSection("Post-Work Verification", content) + "\n";
4381
+ const content = match.replace("## Post-Work Verification", "");
4382
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
4383
+ return renderer.renderSection("Post-Work Verification", normalizedContent.trim()) + "\n";
3143
4384
  }
3144
4385
  documentation(ast, renderer) {
3145
4386
  const standards = this.findBlock(ast, "standards");
@@ -3517,13 +4758,39 @@ var CursorFormatter = class extends BaseFormatter {
3517
4758
  if (never) sections.push(never);
3518
4759
  }
3519
4760
  intro(ast) {
3520
- const projectInfo = this.extractProjectInfo(ast);
3521
- if (projectInfo.text.toLowerCase().startsWith("you are")) {
3522
- return projectInfo.text;
4761
+ const identity2 = this.findBlock(ast, "identity");
4762
+ if (identity2) {
4763
+ const fullText = this.dedent(this.extractText(identity2.content));
4764
+ if (fullText.toLowerCase().startsWith("you are")) {
4765
+ return fullText;
4766
+ }
3523
4767
  }
4768
+ const projectInfo = this.extractProjectInfo(ast);
3524
4769
  const orgSuffix = projectInfo.org ? ` at ${projectInfo.org}` : "";
3525
4770
  return `You are working on ${projectInfo.text}${orgSuffix}.`;
3526
4771
  }
4772
+ /**
4773
+ * Remove common leading whitespace from all lines (dedent).
4774
+ * Handles the case where trim() was already called, causing the first line
4775
+ * to lose its indentation while subsequent lines retain theirs.
4776
+ */
4777
+ dedent(text) {
4778
+ const lines = text.split("\n");
4779
+ if (lines.length <= 1) return text.trim();
4780
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
4781
+ const match = line.match(/^(\s*)/);
4782
+ const indent = match?.[1]?.length ?? 0;
4783
+ return Math.min(min, indent);
4784
+ }, Infinity);
4785
+ if (minIndent === 0 || minIndent === Infinity) {
4786
+ return text.trim();
4787
+ }
4788
+ const firstLine = lines[0] ?? "";
4789
+ return [
4790
+ firstLine.trim(),
4791
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
4792
+ ].join("\n").trim();
4793
+ }
3527
4794
  /**
3528
4795
  * Generate YAML frontmatter for Cursor MDC format.
3529
4796
  * @see https://cursor.com/docs/context/rules
@@ -14613,7 +15880,7 @@ function parse(source, options = {}) {
14613
15880
 
14614
15881
  // packages/resolver/src/loader.ts
14615
15882
  import { readFile as readFile4 } from "fs/promises";
14616
- import { resolve as resolve2, dirname as dirname3, isAbsolute } from "path";
15883
+ import { resolve as resolve2, dirname as dirname4, isAbsolute } from "path";
14617
15884
  var FileLoader = class {
14618
15885
  registryPath;
14619
15886
  localPath;
@@ -14674,7 +15941,7 @@ var FileLoader = class {
14674
15941
  */
14675
15942
  resolveRef(ref, fromFile) {
14676
15943
  if (ref.isRelative) {
14677
- const dir = dirname3(fromFile);
15944
+ const dir = dirname4(fromFile);
14678
15945
  const rawPath = ref.raw.endsWith(".prs") ? ref.raw : `${ref.raw}.prs`;
14679
15946
  return resolve2(dir, rawPath);
14680
15947
  }
@@ -15336,7 +16603,7 @@ function uniqueConcat3(parent, child) {
15336
16603
 
15337
16604
  // packages/resolver/src/skills.ts
15338
16605
  import { readFile as readFile5, access } from "fs/promises";
15339
- import { resolve as resolve3, dirname as dirname4 } from "path";
16606
+ import { resolve as resolve3, dirname as dirname5 } from "path";
15340
16607
  function parseSkillMd(content) {
15341
16608
  const lines = content.split("\n");
15342
16609
  let inFrontmatter = false;
@@ -15391,7 +16658,7 @@ async function resolveNativeSkills(ast, registryPath, sourceFile) {
15391
16658
  const skillsContent = skillsBlock.content;
15392
16659
  const updatedProperties = { ...skillsContent.properties };
15393
16660
  let hasUpdates = false;
15394
- const sourceDir = dirname4(sourceFile);
16661
+ const sourceDir = dirname5(sourceFile);
15395
16662
  const isSkillsDir = sourceDir.includes("@skills");
15396
16663
  for (const [skillName, skillValue] of Object.entries(skillsContent.properties)) {
15397
16664
  if (typeof skillValue !== "object" || skillValue === null || Array.isArray(skillValue)) {
@@ -15619,7 +16886,7 @@ var Resolver = class {
15619
16886
 
15620
16887
  // packages/resolver/src/registry.ts
15621
16888
  import { existsSync as existsSync4, promises as fs } from "fs";
15622
- import { join as join3 } from "path";
16889
+ import { join as join4 } from "path";
15623
16890
  var FileSystemRegistry = class {
15624
16891
  rootPath;
15625
16892
  constructor(options) {
@@ -15629,7 +16896,7 @@ var FileSystemRegistry = class {
15629
16896
  * Resolve a path relative to the registry root.
15630
16897
  */
15631
16898
  resolvePath(path) {
15632
- return join3(this.rootPath, path);
16899
+ return join4(this.rootPath, path);
15633
16900
  }
15634
16901
  async fetch(path) {
15635
16902
  const fullPath = this.resolvePath(path);
@@ -15844,12 +17111,12 @@ function createHttpRegistry(options) {
15844
17111
 
15845
17112
  // packages/resolver/src/git-registry.ts
15846
17113
  import { existsSync as existsSync6, promises as fs3 } from "fs";
15847
- import { join as join5 } from "path";
17114
+ import { join as join6 } from "path";
15848
17115
  import { simpleGit } from "simple-git";
15849
17116
 
15850
17117
  // packages/resolver/src/git-cache-manager.ts
15851
17118
  import { existsSync as existsSync5, promises as fs2 } from "fs";
15852
- import { join as join4 } from "path";
17119
+ import { join as join5 } from "path";
15853
17120
  import { homedir } from "os";
15854
17121
 
15855
17122
  // packages/resolver/src/git-url-utils.ts
@@ -15957,7 +17224,7 @@ function parseVersionedPath(path) {
15957
17224
  }
15958
17225
 
15959
17226
  // packages/resolver/src/git-cache-manager.ts
15960
- var DEFAULT_CACHE_DIR = join4(homedir(), ".promptscript", ".cache", "git");
17227
+ var DEFAULT_CACHE_DIR = join5(homedir(), ".promptscript", ".cache", "git");
15961
17228
  var DEFAULT_TTL = 36e5;
15962
17229
  var METADATA_FILE = ".prs-cache-meta.json";
15963
17230
  var GitCacheManager = class {
@@ -15976,7 +17243,7 @@ var GitCacheManager = class {
15976
17243
  */
15977
17244
  getCachePath(url, ref) {
15978
17245
  const cacheKey = getCacheKey(url, ref);
15979
- return join4(this.cacheDir, cacheKey);
17246
+ return join5(this.cacheDir, cacheKey);
15980
17247
  }
15981
17248
  /**
15982
17249
  * Check if a cache entry exists and is not stale.
@@ -16082,7 +17349,7 @@ var GitCacheManager = class {
16082
17349
  if (!dir.isDirectory()) {
16083
17350
  continue;
16084
17351
  }
16085
- const cachePath = join4(this.cacheDir, dir.name);
17352
+ const cachePath = join5(this.cacheDir, dir.name);
16086
17353
  const metadata = await this.readMetadata(cachePath);
16087
17354
  if (metadata) {
16088
17355
  const isStale = Date.now() - metadata.lastUpdated > this.ttl;
@@ -16130,7 +17397,7 @@ var GitCacheManager = class {
16130
17397
  let size = 0;
16131
17398
  const entries = await fs2.readdir(dirPath, { withFileTypes: true });
16132
17399
  for (const entry of entries) {
16133
- const fullPath = join4(dirPath, entry.name);
17400
+ const fullPath = join5(dirPath, entry.name);
16134
17401
  if (entry.isDirectory()) {
16135
17402
  size += await this.calculateDirSize(fullPath);
16136
17403
  } else {
@@ -16144,7 +17411,7 @@ var GitCacheManager = class {
16144
17411
  * Read metadata from a cache directory.
16145
17412
  */
16146
17413
  async readMetadata(cachePath) {
16147
- const metadataPath = join4(cachePath, METADATA_FILE);
17414
+ const metadataPath = join5(cachePath, METADATA_FILE);
16148
17415
  if (!existsSync5(metadataPath)) {
16149
17416
  return null;
16150
17417
  }
@@ -16159,7 +17426,7 @@ var GitCacheManager = class {
16159
17426
  * Write metadata to a cache directory.
16160
17427
  */
16161
17428
  async writeMetadata(cachePath, metadata) {
16162
- const metadataPath = join4(cachePath, METADATA_FILE);
17429
+ const metadataPath = join5(cachePath, METADATA_FILE);
16163
17430
  await fs2.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
16164
17431
  }
16165
17432
  };
@@ -16450,9 +17717,9 @@ var GitRegistry = class {
16450
17717
  cleanPath += ".prs";
16451
17718
  }
16452
17719
  if (this.subPath) {
16453
- return join5(repoPath, this.subPath, cleanPath);
17720
+ return join6(repoPath, this.subPath, cleanPath);
16454
17721
  }
16455
- return join5(repoPath, cleanPath);
17722
+ return join6(repoPath, cleanPath);
16456
17723
  }
16457
17724
  /**
16458
17725
  * Resolve a directory path within the repository.
@@ -16462,9 +17729,9 @@ var GitRegistry = class {
16462
17729
  */
16463
17730
  resolveDirectoryPath(repoPath, relativePath) {
16464
17731
  if (this.subPath) {
16465
- return join5(repoPath, this.subPath, relativePath);
17732
+ return join6(repoPath, this.subPath, relativePath);
16466
17733
  }
16467
- return join5(repoPath, relativePath);
17734
+ return join6(repoPath, relativePath);
16468
17735
  }
16469
17736
  /**
16470
17737
  * Check if an error is an authentication error.
@@ -17182,8 +18449,8 @@ var Compiler = class _Compiler {
17182
18449
  */
17183
18450
  async watch(entryPath, options = {}) {
17184
18451
  const { default: chokidar2 } = await import("chokidar");
17185
- const { dirname: dirname8, resolve: resolve9 } = await import("path");
17186
- const baseDir = dirname8(resolve9(entryPath));
18452
+ const { dirname: dirname10, resolve: resolve9 } = await import("path");
18453
+ const baseDir = dirname10(resolve9(entryPath));
17187
18454
  const includePatterns = options.include ?? ["**/*.prs"];
17188
18455
  const excludePatterns = options.exclude ?? ["**/node_modules/**"];
17189
18456
  const debounceMs = options.debounce ?? 300;
@@ -17403,6 +18670,69 @@ function createPager(enabled = true) {
17403
18670
  return new Pager(enabled);
17404
18671
  }
17405
18672
 
18673
+ // packages/cli/src/utils/registry-resolver.ts
18674
+ import { join as join7 } from "path";
18675
+ async function resolveRegistryPath(config) {
18676
+ if (config.registry?.git) {
18677
+ const gitConfig = config.registry.git;
18678
+ const ref = gitConfig.ref ?? "main";
18679
+ const cacheManager = new GitCacheManager({
18680
+ ttl: config.registry.cache?.ttl
18681
+ });
18682
+ const normalizedUrl = normalizeGitUrl(gitConfig.url);
18683
+ const cachePath = cacheManager.getCachePath(normalizedUrl, ref);
18684
+ const isValid = await cacheManager.isValid(normalizedUrl, ref);
18685
+ if (isValid) {
18686
+ const subPath2 = gitConfig.path ?? "";
18687
+ return {
18688
+ path: subPath2 ? join7(cachePath, subPath2) : cachePath,
18689
+ isRemote: true,
18690
+ source: "git"
18691
+ };
18692
+ }
18693
+ const registry = createGitRegistry({
18694
+ url: gitConfig.url,
18695
+ ref,
18696
+ path: gitConfig.path,
18697
+ auth: gitConfig.auth,
18698
+ cache: {
18699
+ enabled: config.registry.cache?.enabled ?? true,
18700
+ ttl: config.registry.cache?.ttl
18701
+ }
18702
+ });
18703
+ try {
18704
+ await registry.fetch("registry-manifest.yaml");
18705
+ } catch (error) {
18706
+ const message = error instanceof Error ? error.message : String(error);
18707
+ if (!message.includes("not found") && !message.includes("FileNotFoundError")) {
18708
+ throw new Error(
18709
+ `Failed to clone registry from ${gitConfig.url}: ${message}. Please check your network connection and registry configuration.`
18710
+ );
18711
+ }
18712
+ }
18713
+ const subPath = gitConfig.path ?? "";
18714
+ return {
18715
+ path: subPath ? join7(cachePath, subPath) : cachePath,
18716
+ isRemote: true,
18717
+ source: "git"
18718
+ };
18719
+ }
18720
+ if (config.registry?.url) {
18721
+ const localPath = config.registry?.path ?? "./registry";
18722
+ return {
18723
+ path: localPath,
18724
+ isRemote: true,
18725
+ source: "http"
18726
+ };
18727
+ }
18728
+ const registryPath = config.registry?.path ?? "./registry";
18729
+ return {
18730
+ path: registryPath,
18731
+ isRemote: false,
18732
+ source: "local"
18733
+ };
18734
+ }
18735
+
17406
18736
  // packages/cli/src/commands/compile.ts
17407
18737
  var PROMPTSCRIPT_MARKERS = [
17408
18738
  "<!-- PromptScript",
@@ -17410,6 +18740,10 @@ var PROMPTSCRIPT_MARKERS = [
17410
18740
  "> Auto-generated by PromptScript"
17411
18741
  // Legacy - for backwards compatibility
17412
18742
  ];
18743
+ var MARKER_REGEX = /<!-- PromptScript [^>]+ -->\n*/g;
18744
+ function stripMarkers(content) {
18745
+ return content.replace(MARKER_REGEX, "");
18746
+ }
17413
18747
  function createCliLogger() {
17414
18748
  return {
17415
18749
  verbose: (message) => {
@@ -17468,7 +18802,7 @@ function printErrors(errors) {
17468
18802
  }
17469
18803
  }
17470
18804
  async function writeOutputs(outputs, options, _config, services) {
17471
- const result = { written: [], skipped: [] };
18805
+ const result = { written: [], skipped: [], unchanged: [] };
17472
18806
  let overwriteAll = false;
17473
18807
  const conflicts = [];
17474
18808
  for (const output of outputs.values()) {
@@ -17478,18 +18812,28 @@ async function writeOutputs(outputs, options, _config, services) {
17478
18812
  if (fileExists2) {
17479
18813
  const isGenerated2 = await isPromptScriptGenerated2(outputPath);
17480
18814
  if (isGenerated2) {
17481
- ConsoleOutput.dryRun(`Would overwrite: ${outputPath}`);
18815
+ const existingContent = await readFile6(outputPath, "utf-8");
18816
+ const existingWithoutMarker = stripMarkers(existingContent);
18817
+ const newWithoutMarker = stripMarkers(output.content);
18818
+ if (existingWithoutMarker === newWithoutMarker) {
18819
+ ConsoleOutput.dryRun(`Unchanged: ${outputPath}`);
18820
+ result.unchanged.push(outputPath);
18821
+ } else {
18822
+ ConsoleOutput.dryRun(`Would overwrite: ${outputPath}`);
18823
+ result.written.push(outputPath);
18824
+ }
17482
18825
  } else {
17483
18826
  ConsoleOutput.warning(`Would conflict: ${outputPath} (not generated by PromptScript)`);
18827
+ result.written.push(outputPath);
17484
18828
  }
17485
18829
  } else {
17486
18830
  ConsoleOutput.dryRun(`Would write: ${outputPath}`);
18831
+ result.written.push(outputPath);
17487
18832
  }
17488
- result.written.push(outputPath);
17489
18833
  continue;
17490
18834
  }
17491
18835
  if (!fileExists2) {
17492
- await mkdir2(dirname5(outputPath), { recursive: true });
18836
+ await mkdir2(dirname6(outputPath), { recursive: true });
17493
18837
  await writeFile2(outputPath, output.content, "utf-8");
17494
18838
  ConsoleOutput.success(outputPath);
17495
18839
  result.written.push(outputPath);
@@ -17497,14 +18841,22 @@ async function writeOutputs(outputs, options, _config, services) {
17497
18841
  }
17498
18842
  const isGenerated = await isPromptScriptGenerated2(outputPath);
17499
18843
  if (isGenerated) {
17500
- await mkdir2(dirname5(outputPath), { recursive: true });
18844
+ const existingContent = await readFile6(outputPath, "utf-8");
18845
+ const existingWithoutMarker = stripMarkers(existingContent);
18846
+ const newWithoutMarker = stripMarkers(output.content);
18847
+ if (existingWithoutMarker === newWithoutMarker) {
18848
+ ConsoleOutput.unchanged(outputPath);
18849
+ result.unchanged.push(outputPath);
18850
+ continue;
18851
+ }
18852
+ await mkdir2(dirname6(outputPath), { recursive: true });
17501
18853
  await writeFile2(outputPath, output.content, "utf-8");
17502
18854
  ConsoleOutput.success(outputPath);
17503
18855
  result.written.push(outputPath);
17504
18856
  continue;
17505
18857
  }
17506
18858
  if (options.force || overwriteAll) {
17507
- await mkdir2(dirname5(outputPath), { recursive: true });
18859
+ await mkdir2(dirname6(outputPath), { recursive: true });
17508
18860
  await writeFile2(outputPath, output.content, "utf-8");
17509
18861
  ConsoleOutput.success(outputPath);
17510
18862
  result.written.push(outputPath);
@@ -17517,7 +18869,7 @@ async function writeOutputs(outputs, options, _config, services) {
17517
18869
  const response = await promptForOverwrite(outputPath, services);
17518
18870
  switch (response) {
17519
18871
  case "yes":
17520
- await mkdir2(dirname5(outputPath), { recursive: true });
18872
+ await mkdir2(dirname6(outputPath), { recursive: true });
17521
18873
  await writeFile2(outputPath, output.content, "utf-8");
17522
18874
  ConsoleOutput.success(outputPath);
17523
18875
  result.written.push(outputPath);
@@ -17528,7 +18880,7 @@ async function writeOutputs(outputs, options, _config, services) {
17528
18880
  break;
17529
18881
  case "all":
17530
18882
  overwriteAll = true;
17531
- await mkdir2(dirname5(outputPath), { recursive: true });
18883
+ await mkdir2(dirname6(outputPath), { recursive: true });
17532
18884
  await writeFile2(outputPath, output.content, "utf-8");
17533
18885
  ConsoleOutput.success(outputPath);
17534
18886
  result.written.push(outputPath);
@@ -17567,10 +18919,20 @@ async function compileCommand(options, services = createDefaultServices()) {
17567
18919
  try {
17568
18920
  logger.verbose("Loading configuration...");
17569
18921
  const config = await loadConfig(options.config);
17570
- spinner.text = "Compiling...";
17571
18922
  const selectedTarget = options.target ?? options.format;
17572
18923
  const targets = selectedTarget ? [{ name: selectedTarget }] : parseTargets(config.targets);
17573
- const registryPath = options.registry ?? config.registry?.path ?? "./registry";
18924
+ let registryPath;
18925
+ if (options.registry) {
18926
+ registryPath = options.registry;
18927
+ } else {
18928
+ spinner.text = "Resolving registry...";
18929
+ const registry = await resolveRegistryPath(config);
18930
+ registryPath = registry.path;
18931
+ if (registry.isRemote) {
18932
+ logger.verbose(`Using cached git registry: ${registryPath}`);
18933
+ }
18934
+ }
18935
+ spinner.text = "Compiling...";
17574
18936
  logger.verbose(`Registry: ${registryPath}`);
17575
18937
  logger.debug(`Config: ${JSON.stringify(config, null, 2)}`);
17576
18938
  const prettierOptions = await resolvePrettierOptions(config, process.cwd());
@@ -17602,6 +18964,9 @@ async function compileCommand(options, services = createDefaultServices()) {
17602
18964
  spinner.succeed("Compilation successful");
17603
18965
  ConsoleOutput.newline();
17604
18966
  const writeResult = await writeOutputs(result.outputs, options, config, services);
18967
+ if (writeResult.unchanged.length > 0) {
18968
+ ConsoleOutput.muted(`Unchanged ${writeResult.unchanged.length} file(s)`);
18969
+ }
17605
18970
  if (writeResult.skipped.length > 0) {
17606
18971
  ConsoleOutput.muted(`Skipped ${writeResult.skipped.length} file(s)`);
17607
18972
  }
@@ -17743,10 +19108,12 @@ async function validateCommand(options) {
17743
19108
  const spinner = isJsonFormat ? createSpinner("").stop() : createSpinner("Loading configuration...").start();
17744
19109
  try {
17745
19110
  const config = await loadConfig();
19111
+ if (!isJsonFormat) spinner.text = "Resolving registry...";
19112
+ const registry = await resolveRegistryPath(config);
17746
19113
  if (!isJsonFormat) spinner.text = "Validating...";
17747
19114
  const compiler = new Compiler({
17748
19115
  resolver: {
17749
- registryPath: config.registry?.path ?? "./registry",
19116
+ registryPath: registry.path,
17750
19117
  localPath: "./.promptscript"
17751
19118
  },
17752
19119
  validator: config.validation,
@@ -17795,7 +19162,7 @@ function outputJsonResult(result) {
17795
19162
  // packages/cli/src/commands/pull.ts
17796
19163
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
17797
19164
  import { existsSync as existsSync9 } from "fs";
17798
- import { resolve as resolve6, dirname as dirname6 } from "path";
19165
+ import { resolve as resolve6, dirname as dirname7 } from "path";
17799
19166
  async function pullCommand(options) {
17800
19167
  const spinner = createSpinner("Loading configuration...").start();
17801
19168
  try {
@@ -17834,7 +19201,7 @@ async function pullCommand(options) {
17834
19201
  }
17835
19202
  return;
17836
19203
  }
17837
- await mkdir3(dirname6(destPath), { recursive: true });
19204
+ await mkdir3(dirname7(destPath), { recursive: true });
17838
19205
  await writeFile3(destPath, content, "utf-8");
17839
19206
  spinner.succeed("Pulled from registry");
17840
19207
  ConsoleOutput.success(destPath);
@@ -17990,11 +19357,13 @@ async function diffCommand(options) {
17990
19357
  const spinner = createSpinner("Loading configuration...").start();
17991
19358
  try {
17992
19359
  const config = await loadConfig();
19360
+ spinner.text = "Resolving registry...";
19361
+ const registry = await resolveRegistryPath(config);
17993
19362
  spinner.text = "Compiling...";
17994
19363
  const targets = options.target ? [{ name: options.target }] : parseTargets2(config.targets);
17995
19364
  const compiler = new Compiler({
17996
19365
  resolver: {
17997
- registryPath: config.registry?.path ?? "./registry",
19366
+ registryPath: registry.path,
17998
19367
  localPath: "./.promptscript"
17999
19368
  },
18000
19369
  validator: config.validation,
@@ -18228,11 +19597,189 @@ function printResults(results) {
18228
19597
  }
18229
19598
  }
18230
19599
 
18231
- // packages/cli/src/cli.ts
19600
+ // packages/cli/src/commands/update-check.ts
19601
+ import { fileURLToPath as fileURLToPath2 } from "url";
19602
+ import { dirname as dirname8 } from "path";
19603
+
19604
+ // packages/cli/src/utils/version-check.ts
19605
+ import { homedir as homedir2 } from "os";
19606
+ import { join as join8 } from "path";
19607
+ import { existsSync as existsSync12, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
19608
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@promptscript/cli/latest";
19609
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
19610
+ var FETCH_TIMEOUT_MS = 3e3;
19611
+ function getCacheDir() {
19612
+ return join8(homedir2(), ".promptscript", ".cache");
19613
+ }
19614
+ function getCachePath() {
19615
+ return join8(getCacheDir(), "version.json");
19616
+ }
19617
+ function readCache() {
19618
+ try {
19619
+ const cachePath = getCachePath();
19620
+ if (!existsSync12(cachePath)) {
19621
+ return null;
19622
+ }
19623
+ const content = readFileSync3(cachePath, "utf-8");
19624
+ return JSON.parse(content);
19625
+ } catch {
19626
+ return null;
19627
+ }
19628
+ }
19629
+ function writeCache(cache) {
19630
+ try {
19631
+ const cacheDir = getCacheDir();
19632
+ if (!existsSync12(cacheDir)) {
19633
+ mkdirSync(cacheDir, { recursive: true });
19634
+ }
19635
+ const cachePath = getCachePath();
19636
+ writeFileSync(cachePath, JSON.stringify(cache, null, 2), "utf-8");
19637
+ } catch {
19638
+ }
19639
+ }
19640
+ function isCacheValid(cache) {
19641
+ try {
19642
+ const lastCheck = new Date(cache.lastCheck).getTime();
19643
+ const now = Date.now();
19644
+ return now - lastCheck < CACHE_TTL_MS;
19645
+ } catch {
19646
+ return false;
19647
+ }
19648
+ }
19649
+ async function fetchLatestVersion() {
19650
+ try {
19651
+ const controller = new AbortController();
19652
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
19653
+ const response = await fetch(NPM_REGISTRY_URL, {
19654
+ signal: controller.signal,
19655
+ headers: {
19656
+ Accept: "application/json"
19657
+ }
19658
+ });
19659
+ clearTimeout(timeout);
19660
+ if (!response.ok) {
19661
+ if (isVerbose()) {
19662
+ ConsoleOutput.verbose(`Could not check for updates: HTTP ${response.status}`);
19663
+ }
19664
+ return null;
19665
+ }
19666
+ const data = await response.json();
19667
+ return data.version ?? null;
19668
+ } catch (error) {
19669
+ if (isVerbose()) {
19670
+ const code = error.code;
19671
+ const message = error instanceof Error ? error.message : "Unknown error";
19672
+ ConsoleOutput.verbose(`Could not check for updates: ${code ?? message}`);
19673
+ }
19674
+ return null;
19675
+ }
19676
+ }
19677
+ function isNewerVersion(currentVersion, latestVersion) {
19678
+ const cleanCurrent = currentVersion.replace(/^v/, "");
19679
+ const cleanLatest = latestVersion.replace(/^v/, "");
19680
+ const [currentBase, currentPrerelease] = cleanCurrent.split("-");
19681
+ const [latestBase, latestPrerelease] = cleanLatest.split("-");
19682
+ const currentParts = (currentBase ?? "").split(".").map((p) => parseInt(p, 10) || 0);
19683
+ const latestParts = (latestBase ?? "").split(".").map((p) => parseInt(p, 10) || 0);
19684
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
19685
+ const c = currentParts[i] ?? 0;
19686
+ const l = latestParts[i] ?? 0;
19687
+ if (l > c) return true;
19688
+ if (l < c) return false;
19689
+ }
19690
+ if (currentPrerelease && !latestPrerelease) {
19691
+ return true;
19692
+ }
19693
+ return false;
19694
+ }
19695
+ async function checkForUpdates(currentVersion) {
19696
+ if (process.env["PROMPTSCRIPT_NO_UPDATE_CHECK"]) {
19697
+ return null;
19698
+ }
19699
+ if (isQuiet()) {
19700
+ return null;
19701
+ }
19702
+ const cache = readCache();
19703
+ if (cache && isCacheValid(cache) && cache.currentVersion === currentVersion) {
19704
+ if (isNewerVersion(currentVersion, cache.latestVersion)) {
19705
+ return {
19706
+ currentVersion,
19707
+ latestVersion: cache.latestVersion,
19708
+ updateAvailable: true
19709
+ };
19710
+ }
19711
+ return null;
19712
+ }
19713
+ const latestVersion = await fetchLatestVersion();
19714
+ if (!latestVersion) {
19715
+ return null;
19716
+ }
19717
+ writeCache({
19718
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString(),
19719
+ latestVersion,
19720
+ currentVersion
19721
+ });
19722
+ if (isNewerVersion(currentVersion, latestVersion)) {
19723
+ return {
19724
+ currentVersion,
19725
+ latestVersion,
19726
+ updateAvailable: true
19727
+ };
19728
+ }
19729
+ return null;
19730
+ }
19731
+ async function forceCheckForUpdates(currentVersion) {
19732
+ const latestVersion = await fetchLatestVersion();
19733
+ if (!latestVersion) {
19734
+ return { info: null, error: true };
19735
+ }
19736
+ writeCache({
19737
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString(),
19738
+ latestVersion,
19739
+ currentVersion
19740
+ });
19741
+ const updateAvailable = isNewerVersion(currentVersion, latestVersion);
19742
+ return {
19743
+ info: {
19744
+ currentVersion,
19745
+ latestVersion,
19746
+ updateAvailable
19747
+ },
19748
+ error: false
19749
+ };
19750
+ }
19751
+ function printUpdateNotification(info) {
19752
+ console.log(
19753
+ `Update available: ${info.currentVersion} \u2192 ${info.latestVersion} (npm i -g @promptscript/cli)`
19754
+ );
19755
+ }
19756
+
19757
+ // packages/cli/src/commands/update-check.ts
18232
19758
  var __filename2 = fileURLToPath2(import.meta.url);
18233
- var __dirname2 = dirname7(__filename2);
19759
+ var __dirname2 = dirname8(__filename2);
19760
+ async function updateCheckCommand() {
19761
+ const currentVersion = getPackageVersion(__dirname2, "../../package.json");
19762
+ console.log(`@promptscript/cli v${currentVersion}`);
19763
+ const { info, error } = await forceCheckForUpdates(currentVersion);
19764
+ if (error) {
19765
+ ConsoleOutput.error("Could not check for updates");
19766
+ process.exitCode = 1;
19767
+ return;
19768
+ }
19769
+ if (info?.updateAvailable) {
19770
+ console.log(
19771
+ `Update available: ${info.currentVersion} \u2192 ${info.latestVersion} (npm i -g @promptscript/cli)`
19772
+ );
19773
+ } else {
19774
+ ConsoleOutput.success("Up to date");
19775
+ }
19776
+ }
19777
+
19778
+ // packages/cli/src/cli.ts
19779
+ var __filename3 = fileURLToPath3(import.meta.url);
19780
+ var __dirname3 = dirname9(__filename3);
18234
19781
  var program = new Command();
18235
- program.name("prs").description("PromptScript CLI - Standardize AI instructions").version(getPackageVersion(__dirname2, "./package.json")).option("--verbose", "Enable verbose output").option("--debug", "Enable debug output (includes verbose)").option("--quiet", "Suppress non-error output").hook("preAction", (thisCommand) => {
19782
+ program.name("prs").description("PromptScript CLI - Standardize AI instructions").version(getPackageVersion(__dirname3, "../package.json")).option("--verbose", "Enable verbose output").option("--debug", "Enable debug output (includes verbose)").option("--quiet", "Suppress non-error output").hook("preAction", async (thisCommand, actionCommand) => {
18236
19783
  const opts = thisCommand.opts();
18237
19784
  if (opts["quiet"]) {
18238
19785
  setContext({ logLevel: 0 /* Quiet */ });
@@ -18246,13 +19793,22 @@ program.name("prs").description("PromptScript CLI - Standardize AI instructions"
18246
19793
  } else if (process.env["PROMPTSCRIPT_VERBOSE"] === "1" || process.env["PROMPTSCRIPT_VERBOSE"] === "true") {
18247
19794
  setContext({ logLevel: 2 /* Verbose */ });
18248
19795
  }
19796
+ if (actionCommand.name() === "update-check") {
19797
+ return;
19798
+ }
19799
+ const currentVersion = getPackageVersion(__dirname3, "../package.json");
19800
+ const updateInfo = await checkForUpdates(currentVersion);
19801
+ if (updateInfo) {
19802
+ printUpdateNotification(updateInfo);
19803
+ }
18249
19804
  });
18250
- program.command("init").description("Initialize PromptScript in current directory").option("-n, --name <name>", "Project name (auto-detected from package.json, etc.)").option("-t, --team <team>", "Team namespace").option("--inherit <path>", "Inheritance path (e.g., @company/team)").option("--registry <path>", "Registry path").option("--targets <targets...>", "Target AI tools (github, claude, cursor)").option("-i, --interactive", "Force interactive mode").option("-y, --yes", "Skip prompts, use defaults").option("-f, --force", "Force reinitialize even if already initialized").action((opts) => initCommand(opts));
19805
+ program.command("init").description("Initialize PromptScript in current directory").option("-n, --name <name>", "Project name (auto-detected from package.json, etc.)").option("-t, --team <team>", "Team namespace").option("--inherit <path>", "Inheritance path (e.g., @company/team)").option("--registry <path>", "Registry path").option("--targets <targets...>", "Target AI tools (github, claude, cursor)").option("-i, --interactive", "Force interactive mode").option("-y, --yes", "Skip prompts, use defaults").option("-f, --force", "Force reinitialize even if already initialized").option("-m, --migrate", "Install migration skill for AI-assisted migration").action((opts) => initCommand(opts));
18251
19806
  program.command("compile").description("Compile PromptScript to target formats").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-a, --all", "All configured targets", true).option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option("--dry-run", "Preview changes").option("--registry <path>", "Registry path (overrides config)").option("-c, --config <path>", "Path to custom config file").option("--force", "Force overwrite existing files without prompts").action((opts) => compileCommand(opts));
18252
19807
  program.command("validate").description("Validate PromptScript files").option("--strict", "Treat warnings as errors").option("--format <format>", "Output format (text, json)", "text").action(validateCommand);
18253
19808
  program.command("pull").description("Pull updates from registry").option("-f, --force", "Force overwrite").option("--dry-run", "Preview changes without pulling").option("-b, --branch <name>", "Git branch to pull from").option("--tag <name>", "Git tag to pull from").option("--commit <hash>", "Git commit to pull from").option("--refresh", "Force re-fetch from remote (ignore cache)").action(pullCommand);
18254
19809
  program.command("diff").description("Show diff for compiled output").option("-t, --target <target>", "Specific target").option("-a, --all", "Show diff for all targets at once").option("--full", "Show full diff without truncation").option("--no-pager", "Disable pager output").option("--color", "Force colored output").option("--no-color", "Disable colored output").action(diffCommand);
18255
- program.command("check").description("Check configuration and dependencies health").option("--fix", "Attempt to fix issues").action(checkCommand);
19810
+ program.command("check").description("Check configuration and dependencies health").action(checkCommand);
19811
+ program.command("update-check").description("Check for CLI updates").action(updateCheckCommand);
18256
19812
  function run(args = process.argv) {
18257
19813
  program.parse(args);
18258
19814
  }
@@ -18262,18 +19818,25 @@ export {
18262
19818
  ConsoleOutput,
18263
19819
  LogLevel,
18264
19820
  checkCommand,
19821
+ checkForUpdates,
18265
19822
  compileCommand,
18266
19823
  createSpinner,
18267
19824
  diffCommand,
19825
+ fetchLatestVersion,
18268
19826
  findConfigFile,
19827
+ forceCheckForUpdates,
19828
+ getCacheDir,
19829
+ getCachePath,
18269
19830
  getContext,
18270
19831
  initCommand,
18271
19832
  isQuiet,
18272
19833
  isVerbose,
18273
19834
  loadConfig,
19835
+ printUpdateNotification,
18274
19836
  pullCommand,
18275
19837
  run,
18276
19838
  setContext,
19839
+ updateCheckCommand,
18277
19840
  validateCommand
18278
19841
  };
18279
19842
  /*! Bundled license information: