@promptscript/cli 1.0.0-alpha.4 → 1.0.0-alpha.6

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
@@ -296,6 +296,14 @@ function getPackageVersion(baseDir, relativePath = "../package.json") {
296
296
  return getPackageInfo(baseDir, relativePath).version;
297
297
  }
298
298
 
299
+ // packages/core/src/logger.ts
300
+ var noopLogger = {
301
+ verbose: () => {
302
+ },
303
+ debug: () => {
304
+ }
305
+ };
306
+
299
307
  // packages/cli/src/commands/init.ts
300
308
  import { fileURLToPath } from "url";
301
309
  import { dirname as dirname2 } from "path";
@@ -331,6 +339,7 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
331
339
  LogLevel2[LogLevel2["Quiet"] = 0] = "Quiet";
332
340
  LogLevel2[LogLevel2["Normal"] = 1] = "Normal";
333
341
  LogLevel2[LogLevel2["Verbose"] = 2] = "Verbose";
342
+ LogLevel2[LogLevel2["Debug"] = 3] = "Debug";
334
343
  return LogLevel2;
335
344
  })(LogLevel || {});
336
345
  var globalContext = {
@@ -349,6 +358,9 @@ function isVerbose() {
349
358
  function isQuiet() {
350
359
  return globalContext.logLevel <= 0 /* Quiet */;
351
360
  }
361
+ function isDebug() {
362
+ return globalContext.logLevel >= 3 /* Debug */;
363
+ }
352
364
  function createSpinner(text) {
353
365
  if (isQuiet()) {
354
366
  const noopSpinner = ora({ isSilent: true });
@@ -393,6 +405,13 @@ var ConsoleOutput = {
393
405
  if (isQuiet()) return;
394
406
  console.log(chalk.yellow(` \u2298 ${message}`));
395
407
  },
408
+ /**
409
+ * Print an unchanged file message.
410
+ */
411
+ unchanged(message) {
412
+ if (isQuiet()) return;
413
+ console.log(chalk.gray(` \u25CB ${message}`));
414
+ },
396
415
  /**
397
416
  * Print an info message.
398
417
  */
@@ -617,6 +636,16 @@ async function detectFrameworks(services) {
617
636
  }
618
637
 
619
638
  // packages/cli/src/utils/ai-tools-detector.ts
639
+ var PROMPTSCRIPT_MARKER = "PromptScript";
640
+ var INSTRUCTION_FILES = [
641
+ "CLAUDE.md",
642
+ "claude.md",
643
+ ".cursorrules",
644
+ ".github/copilot-instructions.md",
645
+ "AGENTS.md",
646
+ "AI_INSTRUCTIONS.md",
647
+ "AI.md"
648
+ ];
620
649
  var AI_TOOL_PATTERNS = [
621
650
  {
622
651
  target: "github",
@@ -648,6 +677,15 @@ async function directoryHasContent(dir, services) {
648
677
  return false;
649
678
  }
650
679
  }
680
+ async function isPromptScriptGenerated(filePath, services) {
681
+ try {
682
+ const content = await services.fs.readFile(filePath, "utf-8");
683
+ const header = content.slice(0, 500);
684
+ return header.includes(PROMPTSCRIPT_MARKER);
685
+ } catch {
686
+ return false;
687
+ }
688
+ }
651
689
  async function detectAITools(services = createDefaultServices()) {
652
690
  const detected = [];
653
691
  const details = {
@@ -656,6 +694,7 @@ async function detectAITools(services = createDefaultServices()) {
656
694
  cursor: [],
657
695
  antigravity: []
658
696
  };
697
+ const migrationCandidates = [];
659
698
  for (const pattern of AI_TOOL_PATTERNS) {
660
699
  const foundFiles = [];
661
700
  for (const file of pattern.files) {
@@ -673,7 +712,15 @@ async function detectAITools(services = createDefaultServices()) {
673
712
  details[pattern.target] = foundFiles;
674
713
  }
675
714
  }
676
- return { detected, details };
715
+ for (const file of INSTRUCTION_FILES) {
716
+ if (services.fs.existsSync(file)) {
717
+ const isGenerated = await isPromptScriptGenerated(file, services);
718
+ if (!isGenerated) {
719
+ migrationCandidates.push(file);
720
+ }
721
+ }
722
+ }
723
+ return { detected, details, migrationCandidates };
677
724
  }
678
725
  function getAllTargets() {
679
726
  return ["github", "claude", "cursor", "antigravity"];
@@ -694,6 +741,25 @@ function formatDetectionResults(detection) {
694
741
  }
695
742
  return lines;
696
743
  }
744
+ function hasMigrationCandidates(detection) {
745
+ return detection.migrationCandidates.length > 0;
746
+ }
747
+ function formatMigrationHint(detection) {
748
+ if (detection.migrationCandidates.length === 0) {
749
+ return [];
750
+ }
751
+ const lines = [];
752
+ lines.push("");
753
+ lines.push("\u{1F4CB} Existing instruction files detected:");
754
+ for (const file of detection.migrationCandidates) {
755
+ lines.push(` \u2022 ${file}`);
756
+ }
757
+ lines.push("");
758
+ lines.push(" These can be migrated to PromptScript for unified management.");
759
+ lines.push(" See: https://getpromptscript.dev/latest/guides/ai-migration-best-practices");
760
+ lines.push(" Or use the /migrate skill in Claude Code.");
761
+ return lines;
762
+ }
697
763
 
698
764
  // packages/cli/src/prettier/loader.ts
699
765
  import { existsSync as existsSync2 } from "fs";
@@ -800,6 +866,506 @@ async function resolvePrettierOptions(config, basePath = process.cwd()) {
800
866
  return result;
801
867
  }
802
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
+
803
1369
  // packages/cli/src/commands/init.ts
804
1370
  var __filename = fileURLToPath(import.meta.url);
805
1371
  var __dirname = dirname2(__filename);
@@ -827,11 +1393,45 @@ async function initCommand(options, services = createDefaultServices()) {
827
1393
  await fs4.writeFile("promptscript.yaml", configContent, "utf-8");
828
1394
  const projectPsContent = generateProjectPs(config, projectInfo);
829
1395
  await fs4.writeFile(".promptscript/project.prs", projectPsContent, "utf-8");
1396
+ const installedSkillPaths = [];
1397
+ if (options.migrate) {
1398
+ if (config.targets.includes("claude")) {
1399
+ await fs4.mkdir(".claude/skills/migrate-to-promptscript", { recursive: true });
1400
+ await fs4.writeFile(
1401
+ ".claude/skills/migrate-to-promptscript/SKILL.md",
1402
+ MIGRATE_SKILL_CLAUDE,
1403
+ "utf-8"
1404
+ );
1405
+ installedSkillPaths.push(".claude/skills/migrate-to-promptscript/SKILL.md");
1406
+ }
1407
+ if (config.targets.includes("github")) {
1408
+ await fs4.mkdir(".github/skills/migrate-to-promptscript", { recursive: true });
1409
+ await fs4.writeFile(
1410
+ ".github/skills/migrate-to-promptscript/SKILL.md",
1411
+ MIGRATE_SKILL_GITHUB,
1412
+ "utf-8"
1413
+ );
1414
+ installedSkillPaths.push(".github/skills/migrate-to-promptscript/SKILL.md");
1415
+ }
1416
+ if (config.targets.includes("cursor")) {
1417
+ await fs4.mkdir(".cursor/commands", { recursive: true });
1418
+ await fs4.writeFile(".cursor/commands/migrate.md", MIGRATE_SKILL_CURSOR, "utf-8");
1419
+ installedSkillPaths.push(".cursor/commands/migrate.md");
1420
+ }
1421
+ if (config.targets.includes("antigravity")) {
1422
+ await fs4.mkdir(".agent/rules", { recursive: true });
1423
+ await fs4.writeFile(".agent/rules/migrate.md", MIGRATE_SKILL_ANTIGRAVITY, "utf-8");
1424
+ installedSkillPaths.push(".agent/rules/migrate.md");
1425
+ }
1426
+ }
830
1427
  spinner.succeed("PromptScript initialized");
831
1428
  ConsoleOutput.newline();
832
1429
  console.log("Created:");
833
1430
  ConsoleOutput.success("promptscript.yaml");
834
1431
  ConsoleOutput.success(".promptscript/project.prs");
1432
+ for (const skillPath of installedSkillPaths) {
1433
+ ConsoleOutput.success(skillPath);
1434
+ }
835
1435
  ConsoleOutput.newline();
836
1436
  console.log("Configuration:");
837
1437
  ConsoleOutput.muted(` Project: ${config.projectId}`);
@@ -852,8 +1452,38 @@ async function initCommand(options, services = createDefaultServices()) {
852
1452
  }
853
1453
  ConsoleOutput.newline();
854
1454
  console.log("Next steps:");
855
- ConsoleOutput.muted("1. Edit .promptscript/project.prs to customize your AI instructions");
856
- ConsoleOutput.muted("2. Run: prs compile");
1455
+ if (options.migrate && installedSkillPaths.length > 0) {
1456
+ ConsoleOutput.muted("1. Use the migration skill to convert existing instructions:");
1457
+ if (config.targets.includes("claude")) {
1458
+ ConsoleOutput.muted(" Claude Code: /migrate");
1459
+ }
1460
+ if (config.targets.includes("github")) {
1461
+ ConsoleOutput.muted(" GitHub Copilot: @workspace /migrate");
1462
+ }
1463
+ if (config.targets.includes("cursor")) {
1464
+ ConsoleOutput.muted(" Cursor: /migrate");
1465
+ }
1466
+ if (config.targets.includes("antigravity")) {
1467
+ ConsoleOutput.muted(' Antigravity: Ask to "migrate to PromptScript"');
1468
+ }
1469
+ ConsoleOutput.muted("2. Review generated .promptscript/project.prs");
1470
+ ConsoleOutput.muted("3. Run: prs compile");
1471
+ } else {
1472
+ ConsoleOutput.muted("1. Edit .promptscript/project.prs to customize your AI instructions");
1473
+ ConsoleOutput.muted("2. Run: prs compile");
1474
+ if (hasMigrationCandidates(aiToolsDetection)) {
1475
+ const migrationHint = formatMigrationHint(aiToolsDetection);
1476
+ for (const line of migrationHint) {
1477
+ if (line.startsWith("\u{1F4CB}") || line.includes("migrated") || line.includes("See:") || line.includes("Or use")) {
1478
+ ConsoleOutput.info(line.replace(/^\s+/, ""));
1479
+ } else if (line.trim().startsWith("\u2022")) {
1480
+ ConsoleOutput.muted(line);
1481
+ } else if (line.trim()) {
1482
+ console.log(line);
1483
+ }
1484
+ }
1485
+ }
1486
+ }
857
1487
  } catch (error) {
858
1488
  if (error.name === "ExitPromptError") {
859
1489
  ConsoleOutput.newline();
@@ -1294,10 +1924,26 @@ ${this.convention.rootWrapper.end}`;
1294
1924
  /**
1295
1925
  * Escape markdown special characters for Prettier compatibility.
1296
1926
  * - Escapes __ to \_\_ (to avoid emphasis)
1297
- * - Escapes /* to /\* (to avoid glob patterns being interpreted)
1927
+ * - Escapes * in glob patterns (like packages/*) outside backticks
1298
1928
  */
1299
1929
  escapeMarkdownSpecialChars(content) {
1300
- return content.replace(/__/g, "\\_\\_").replace(/\/\*/g, "/\\*");
1930
+ return content.split("\n").map((line) => {
1931
+ let result = line.replace(/__/g, "\\_\\_");
1932
+ result = this.escapeGlobAsteriskOutsideBackticks(result);
1933
+ return result;
1934
+ }).join("\n");
1935
+ }
1936
+ /**
1937
+ * Escape glob asterisks (like packages/* or .cursor/rules/*.md) outside of backticks.
1938
+ */
1939
+ escapeGlobAsteriskOutsideBackticks(line) {
1940
+ const parts = line.split("`");
1941
+ return parts.map((part, index) => {
1942
+ if (index % 2 === 0) {
1943
+ return part.replace(/\/\*/g, "/\\*");
1944
+ }
1945
+ return part;
1946
+ }).join("`");
1301
1947
  }
1302
1948
  indentContent(content, renderer, level) {
1303
1949
  const indent = renderer.indent ?? "";
@@ -1469,6 +2115,7 @@ var BaseFormatter = class {
1469
2115
  * - Trims trailing whitespace from lines
1470
2116
  * - Normalizes markdown table formatting
1471
2117
  * - Adds blank lines before lists when preceded by text
2118
+ * - Adds blank lines before code blocks when preceded by text
1472
2119
  * - Escapes markdown special characters in paths
1473
2120
  */
1474
2121
  normalizeMarkdownForPrettier(content) {
@@ -1477,8 +2124,13 @@ var BaseFormatter = class {
1477
2124
  let inCodeBlock = false;
1478
2125
  for (const line of lines) {
1479
2126
  const trimmed = line.trimEnd();
1480
- if (trimmed.startsWith("```")) {
2127
+ if (trimmed.trimStart().startsWith("```")) {
1481
2128
  inCodeBlock = !inCodeBlock;
2129
+ if (trimmed.length > 0) {
2130
+ const match2 = line.match(/^(\s*)/);
2131
+ const leadingSpaces2 = match2?.[1]?.length ?? 0;
2132
+ minIndent = Math.min(minIndent, leadingSpaces2);
2133
+ }
1482
2134
  continue;
1483
2135
  }
1484
2136
  if (inCodeBlock) continue;
@@ -1494,17 +2146,30 @@ var BaseFormatter = class {
1494
2146
  inCodeBlock = false;
1495
2147
  for (const line of lines) {
1496
2148
  const trimmedLine = line.trimEnd();
1497
- if (trimmedLine.trimStart().startsWith("```")) {
2149
+ const unindentedLine = minIndent > 0 ? trimmedLine.slice(minIndent) : trimmedLine;
2150
+ const isCodeBlockMarker = trimmedLine.trimStart().startsWith("```");
2151
+ const isCodeBlockStart = isCodeBlockMarker && !inCodeBlock;
2152
+ if (isCodeBlockMarker) {
1498
2153
  inCodeBlock = !inCodeBlock;
1499
2154
  }
1500
- const unindentedLine = minIndent > 0 ? trimmedLine.slice(minIndent) : trimmedLine;
1501
2155
  if (inCodeBlock || unindentedLine.startsWith("```")) {
2156
+ if (isCodeBlockStart) {
2157
+ const prevLine2 = result.length > 0 ? result[result.length - 1] ?? "" : "";
2158
+ if (prevLine2.trim() && !prevLine2.startsWith("#")) {
2159
+ result.push("");
2160
+ }
2161
+ }
1502
2162
  result.push(unindentedLine);
1503
2163
  continue;
1504
2164
  }
1505
2165
  let processedLine = unindentedLine;
1506
2166
  processedLine = processedLine.replace(/__([^_]+)__/g, "\\_\\_$1\\_\\_");
1507
- processedLine = processedLine.replace(/\/\*/g, "/\\*");
2167
+ processedLine = this.escapeGlobAsteriskOutsideBackticks(processedLine);
2168
+ const prevLine = result.length > 0 ? result[result.length - 1] : "";
2169
+ const isHeader = prevLine?.trimStart().startsWith("#");
2170
+ if (isHeader && processedLine.trim()) {
2171
+ result.push("");
2172
+ }
1508
2173
  if (processedLine.trimStart().startsWith("|") && processedLine.trimEnd().endsWith("|")) {
1509
2174
  inTable = true;
1510
2175
  tableLines.push(processedLine.trim());
@@ -1514,9 +2179,14 @@ var BaseFormatter = class {
1514
2179
  tableLines = [];
1515
2180
  inTable = false;
1516
2181
  }
1517
- const prevLine = result.length > 0 ? result[result.length - 1] : "";
1518
2182
  const isListItem = processedLine.trimStart().startsWith("- ");
1519
- if (isListItem && prevLine && !prevLine.trimStart().startsWith("- ")) {
2183
+ const isNumberedItem = /^\d+\.\s/.test(processedLine.trimStart());
2184
+ const prevLineTrimmed = prevLine?.trimStart() ?? "";
2185
+ const isPrevListItem = prevLineTrimmed.startsWith("- ") || /^\d+\.\s/.test(prevLineTrimmed);
2186
+ const prevEndsWithColon = prevLine?.trimEnd().endsWith(":") ?? false;
2187
+ if ((isListItem || isNumberedItem) && prevLine && !isPrevListItem && !isHeader) {
2188
+ result.push("");
2189
+ } else if ((isListItem || isNumberedItem) && prevEndsWithColon) {
1520
2190
  result.push("");
1521
2191
  }
1522
2192
  result.push(processedLine);
@@ -1527,6 +2197,19 @@ var BaseFormatter = class {
1527
2197
  }
1528
2198
  return result.join("\n");
1529
2199
  }
2200
+ /**
2201
+ * Escape glob asterisks (like packages/* or .cursor/rules/*.md) outside of backticks.
2202
+ * Prettier escapes these to prevent them from being interpreted as emphasis markers.
2203
+ */
2204
+ escapeGlobAsteriskOutsideBackticks(line) {
2205
+ const parts = line.split("`");
2206
+ return parts.map((part, index) => {
2207
+ if (index % 2 === 0) {
2208
+ return part.replace(/\/\*/g, "/\\*");
2209
+ }
2210
+ return part;
2211
+ }).join("`");
2212
+ }
1530
2213
  /**
1531
2214
  * Strip all leading indentation from markdown content.
1532
2215
  * Used for AGENTS.md where content from multiple sources has inconsistent indentation.
@@ -1540,7 +2223,7 @@ var BaseFormatter = class {
1540
2223
  const trimmedEnd = line.trimEnd();
1541
2224
  if (trimmedEnd.trimStart().startsWith("```")) {
1542
2225
  if (!inCodeBlock) {
1543
- const prevLine2 = result.length > 0 ? result[result.length - 1] : "";
2226
+ const prevLine2 = result.length > 0 ? result[result.length - 1] ?? "" : "";
1544
2227
  if (prevLine2 && prevLine2.trim()) {
1545
2228
  result.push("");
1546
2229
  }
@@ -1555,9 +2238,14 @@ var BaseFormatter = class {
1555
2238
  }
1556
2239
  let stripped = trimmedEnd.trimStart();
1557
2240
  stripped = stripped.replace(/__/g, "\\_\\_");
1558
- stripped = stripped.replace(/\/\*/g, "/\\*");
1559
- const prevLine = result.length > 0 ? result[result.length - 1] : "";
1560
- if (stripped.startsWith("- ") && prevLine && !prevLine.startsWith("- ")) {
2241
+ stripped = this.escapeGlobAsteriskOutsideBackticks(stripped);
2242
+ const prevLine = result.length > 0 ? result[result.length - 1] ?? "" : "";
2243
+ const isListItem = stripped.startsWith("- ") || /^\d+\.\s/.test(stripped);
2244
+ const isPrevList = prevLine.startsWith("- ") || /^\d+\.\s/.test(prevLine);
2245
+ const prevEndsWithColon = prevLine.trimEnd().endsWith(":");
2246
+ if (isListItem && prevLine && !isPrevList) {
2247
+ result.push("");
2248
+ } else if (isListItem && prevEndsWithColon) {
1561
2249
  result.push("");
1562
2250
  }
1563
2251
  result.push(stripped);
@@ -1898,7 +2586,9 @@ var GitHubFormatter = class extends BaseFormatter {
1898
2586
  lines.push(`# ${config.description}`);
1899
2587
  lines.push("");
1900
2588
  if (config.content) {
1901
- lines.push(config.content);
2589
+ const dedentedContent = this.dedent(config.content);
2590
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2591
+ lines.push(normalizedContent);
1902
2592
  }
1903
2593
  return {
1904
2594
  path: `.github/instructions/${config.name}.instructions.md`,
@@ -1986,7 +2676,8 @@ var GitHubFormatter = class extends BaseFormatter {
1986
2676
  generatePromptFile(config) {
1987
2677
  const lines = [];
1988
2678
  lines.push("---");
1989
- lines.push(`description: "${config.description}"`);
2679
+ const descQuote = config.description.includes("'") ? '"' : "'";
2680
+ lines.push(`description: ${descQuote}${config.description}${descQuote}`);
1990
2681
  if (config.mode === "agent") {
1991
2682
  lines.push("mode: agent");
1992
2683
  if (config.tools && config.tools.length > 0) {
@@ -1998,11 +2689,13 @@ var GitHubFormatter = class extends BaseFormatter {
1998
2689
  lines.push("---");
1999
2690
  lines.push("");
2000
2691
  if (config.content) {
2001
- lines.push(config.content);
2692
+ const dedentedContent = this.dedent(config.content);
2693
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2694
+ lines.push(normalizedContent);
2002
2695
  }
2003
2696
  return {
2004
2697
  path: `.github/prompts/${config.name}.prompt.md`,
2005
- content: lines.join("\n")
2698
+ content: lines.join("\n") + "\n"
2006
2699
  };
2007
2700
  }
2008
2701
  // ============================================================
@@ -2044,7 +2737,8 @@ var GitHubFormatter = class extends BaseFormatter {
2044
2737
  lines.push("---");
2045
2738
  lines.push("");
2046
2739
  if (config.content) {
2047
- const normalizedContent = this.normalizeMarkdownForPrettier(config.content);
2740
+ const dedentedContent = this.dedent(config.content);
2741
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2048
2742
  lines.push(normalizedContent);
2049
2743
  }
2050
2744
  return {
@@ -2052,6 +2746,28 @@ var GitHubFormatter = class extends BaseFormatter {
2052
2746
  content: lines.join("\n") + "\n"
2053
2747
  };
2054
2748
  }
2749
+ /**
2750
+ * Remove common leading indentation from multiline text.
2751
+ * Calculates minimum indent from lines 2+ only, since line 1 may have been
2752
+ * trimmed (losing its indentation) while subsequent lines retain theirs.
2753
+ */
2754
+ dedent(text) {
2755
+ const lines = text.split("\n");
2756
+ if (lines.length <= 1) return text.trim();
2757
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
2758
+ const match = line.match(/^(\s*)/);
2759
+ const indent = match?.[1]?.length ?? 0;
2760
+ return Math.min(min, indent);
2761
+ }, Infinity);
2762
+ if (minIndent === 0 || minIndent === Infinity) {
2763
+ return text.trim();
2764
+ }
2765
+ const firstLine = lines[0] ?? "";
2766
+ return [
2767
+ firstLine.trim(),
2768
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
2769
+ ].join("\n").trim();
2770
+ }
2055
2771
  // ============================================================
2056
2772
  // AGENTS.md Generation
2057
2773
  // ============================================================
@@ -2187,7 +2903,9 @@ var GitHubFormatter = class extends BaseFormatter {
2187
2903
  lines.push("---");
2188
2904
  lines.push("");
2189
2905
  if (config.content) {
2190
- lines.push(config.content);
2906
+ const dedentedContent = this.dedent(config.content);
2907
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2908
+ lines.push(normalizedContent);
2191
2909
  }
2192
2910
  return {
2193
2911
  path: `.github/agents/${config.name}.md`,
@@ -2642,7 +3360,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2642
3360
  lines.push(`# ${config.description}`);
2643
3361
  lines.push("");
2644
3362
  if (config.content) {
2645
- lines.push(config.content);
3363
+ const dedentedContent = this.dedent(config.content);
3364
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3365
+ lines.push(normalizedContent);
2646
3366
  }
2647
3367
  return {
2648
3368
  path: `.claude/rules/${config.name}.md`,
@@ -2741,7 +3461,8 @@ var ClaudeFormatter = class extends BaseFormatter {
2741
3461
  lines.push("---");
2742
3462
  lines.push("");
2743
3463
  if (config.content) {
2744
- const normalizedContent = this.normalizeMarkdownForPrettier(config.content);
3464
+ const dedentedContent = this.dedent(config.content);
3465
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
2745
3466
  lines.push(normalizedContent);
2746
3467
  }
2747
3468
  return {
@@ -2749,6 +3470,28 @@ var ClaudeFormatter = class extends BaseFormatter {
2749
3470
  content: lines.join("\n") + "\n"
2750
3471
  };
2751
3472
  }
3473
+ /**
3474
+ * Remove common leading indentation from multiline text.
3475
+ * Calculates minimum indent from lines 2+ only, since line 1 may have been
3476
+ * trimmed (losing its indentation) while subsequent lines retain theirs.
3477
+ */
3478
+ dedent(text) {
3479
+ const lines = text.split("\n");
3480
+ if (lines.length <= 1) return text.trim();
3481
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
3482
+ const match = line.match(/^(\s*)/);
3483
+ const indent = match?.[1]?.length ?? 0;
3484
+ return Math.min(min, indent);
3485
+ }, Infinity);
3486
+ if (minIndent === 0 || minIndent === Infinity) {
3487
+ return text.trim();
3488
+ }
3489
+ const firstLine = lines[0] ?? "";
3490
+ return [
3491
+ firstLine.trim(),
3492
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
3493
+ ].join("\n").trim();
3494
+ }
2752
3495
  // ============================================================
2753
3496
  // Agent File Generation
2754
3497
  // ============================================================
@@ -2851,7 +3594,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2851
3594
  lines.push("---");
2852
3595
  lines.push("");
2853
3596
  if (config.content) {
2854
- lines.push(config.content);
3597
+ const dedentedContent = this.dedent(config.content);
3598
+ const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
3599
+ lines.push(normalizedContent);
2855
3600
  }
2856
3601
  return {
2857
3602
  path: `.claude/agents/${config.name}.md`,
@@ -2906,7 +3651,8 @@ var ClaudeFormatter = class extends BaseFormatter {
2906
3651
  const cleanText = text.split(/\n{2,}/).map(
2907
3652
  (para) => para.split("\n").map((line) => line.trim()).filter((line) => line).join("\n")
2908
3653
  ).filter((para) => para).join("\n\n");
2909
- return renderer.renderSection("Project", cleanText) + "\n";
3654
+ const normalizedText = this.normalizeMarkdownForPrettier(cleanText);
3655
+ return renderer.renderSection("Project", normalizedText) + "\n";
2910
3656
  }
2911
3657
  techStack(ast, renderer) {
2912
3658
  const context = this.findBlock(ast, "context");
@@ -2964,8 +3710,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2964
3710
  const text = this.extractText(context.content);
2965
3711
  const archMatch = this.extractSectionWithCodeBlock(text, "## Architecture");
2966
3712
  if (!archMatch) return null;
2967
- const content = archMatch.replace("## Architecture", "").trim();
2968
- return renderer.renderSection("Architecture", content) + "\n";
3713
+ const content = archMatch.replace("## Architecture", "");
3714
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
3715
+ return renderer.renderSection("Architecture", normalizedContent.trim()) + "\n";
2969
3716
  }
2970
3717
  codeStandards(ast, renderer) {
2971
3718
  const standards = this.findBlock(ast, "standards");
@@ -3046,7 +3793,7 @@ var ClaudeFormatter = class extends BaseFormatter {
3046
3793
  const props = this.getProps(shortcuts.content);
3047
3794
  for (const [cmd, desc] of Object.entries(props)) {
3048
3795
  const shortDesc = this.valueToString(desc).split("\n")[0] ?? "";
3049
- commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`);
3796
+ commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`.trimEnd());
3050
3797
  }
3051
3798
  }
3052
3799
  if (commandLines.length === 0) return null;
@@ -3055,8 +3802,9 @@ var ClaudeFormatter = class extends BaseFormatter {
3055
3802
  const text = this.extractText(knowledge.content);
3056
3803
  const match = this.extractSectionWithCodeBlock(text, "## Development Commands");
3057
3804
  if (match) {
3058
- const devCmds = match.replace("## Development Commands", "").trim();
3059
- content += "\n\n" + devCmds;
3805
+ const devCmds = match.replace("## Development Commands", "");
3806
+ const normalizedDevCmds = this.normalizeMarkdownForPrettier(devCmds);
3807
+ content += "\n\n" + normalizedDevCmds.trim();
3060
3808
  }
3061
3809
  }
3062
3810
  return renderer.renderSection("Commands", content) + "\n";
@@ -3067,8 +3815,9 @@ var ClaudeFormatter = class extends BaseFormatter {
3067
3815
  const text = this.extractText(knowledge.content);
3068
3816
  const match = this.extractSectionWithCodeBlock(text, "## Post-Work Verification");
3069
3817
  if (!match) return null;
3070
- const content = match.replace("## Post-Work Verification", "").trim();
3071
- return renderer.renderSection("Post-Work Verification", content) + "\n";
3818
+ const content = match.replace("## Post-Work Verification", "");
3819
+ const normalizedContent = this.normalizeMarkdownForPrettier(content);
3820
+ return renderer.renderSection("Post-Work Verification", normalizedContent.trim()) + "\n";
3072
3821
  }
3073
3822
  documentation(ast, renderer) {
3074
3823
  const standards = this.findBlock(ast, "standards");
@@ -3446,13 +4195,39 @@ var CursorFormatter = class extends BaseFormatter {
3446
4195
  if (never) sections.push(never);
3447
4196
  }
3448
4197
  intro(ast) {
3449
- const projectInfo = this.extractProjectInfo(ast);
3450
- if (projectInfo.text.toLowerCase().startsWith("you are")) {
3451
- return projectInfo.text;
4198
+ const identity2 = this.findBlock(ast, "identity");
4199
+ if (identity2) {
4200
+ const fullText = this.dedent(this.extractText(identity2.content));
4201
+ if (fullText.toLowerCase().startsWith("you are")) {
4202
+ return fullText;
4203
+ }
3452
4204
  }
4205
+ const projectInfo = this.extractProjectInfo(ast);
3453
4206
  const orgSuffix = projectInfo.org ? ` at ${projectInfo.org}` : "";
3454
4207
  return `You are working on ${projectInfo.text}${orgSuffix}.`;
3455
4208
  }
4209
+ /**
4210
+ * Remove common leading whitespace from all lines (dedent).
4211
+ * Handles the case where trim() was already called, causing the first line
4212
+ * to lose its indentation while subsequent lines retain theirs.
4213
+ */
4214
+ dedent(text) {
4215
+ const lines = text.split("\n");
4216
+ if (lines.length <= 1) return text.trim();
4217
+ const minIndent = lines.slice(1).filter((line) => line.trim().length > 0).reduce((min, line) => {
4218
+ const match = line.match(/^(\s*)/);
4219
+ const indent = match?.[1]?.length ?? 0;
4220
+ return Math.min(min, indent);
4221
+ }, Infinity);
4222
+ if (minIndent === 0 || minIndent === Infinity) {
4223
+ return text.trim();
4224
+ }
4225
+ const firstLine = lines[0] ?? "";
4226
+ return [
4227
+ firstLine.trim(),
4228
+ ...lines.slice(1).map((line) => line.trim().length > 0 ? line.slice(minIndent) : "")
4229
+ ].join("\n").trim();
4230
+ }
3456
4231
  /**
3457
4232
  * Generate YAML frontmatter for Cursor MDC format.
3458
4233
  * @see https://cursor.com/docs/context/rules
@@ -15378,11 +16153,13 @@ var Resolver = class {
15378
16153
  cache;
15379
16154
  resolving;
15380
16155
  cacheEnabled;
16156
+ logger;
15381
16157
  constructor(options) {
15382
16158
  this.loader = new FileLoader(options);
15383
16159
  this.cache = /* @__PURE__ */ new Map();
15384
16160
  this.resolving = /* @__PURE__ */ new Set();
15385
16161
  this.cacheEnabled = options.cache !== false;
16162
+ this.logger = options.logger ?? noopLogger;
15386
16163
  }
15387
16164
  /**
15388
16165
  * Resolve a PromptScript file and all its dependencies.
@@ -15394,15 +16171,19 @@ var Resolver = class {
15394
16171
  async resolve(entryPath) {
15395
16172
  const absPath = this.loader.toAbsolutePath(entryPath);
15396
16173
  if (this.resolving.has(absPath)) {
16174
+ this.logger.debug(`Circular dependency detected: ${absPath}`);
15397
16175
  throw new CircularDependencyError([...this.resolving, absPath]);
15398
16176
  }
15399
16177
  if (this.cacheEnabled && this.cache.has(absPath)) {
16178
+ this.logger.debug(`Cache hit: ${absPath}`);
15400
16179
  return this.cache.get(absPath);
15401
16180
  }
15402
16181
  this.resolving.add(absPath);
16182
+ this.logger.verbose(`Parsing ${absPath}`);
15403
16183
  try {
15404
16184
  const result = await this.doResolve(absPath);
15405
16185
  if (this.cacheEnabled) {
16186
+ this.logger.debug(`Cache store: ${absPath}`);
15406
16187
  this.cache.set(absPath, result);
15407
16188
  }
15408
16189
  return result;
@@ -15421,16 +16202,33 @@ var Resolver = class {
15421
16202
  return { ast: null, sources, errors };
15422
16203
  }
15423
16204
  let ast = parseData.ast;
16205
+ this.logger.debug(`AST node count: ${this.countNodes(ast)}`);
15424
16206
  ast = await this.resolveInherit(ast, absPath, sources, errors);
15425
16207
  ast = await this.resolveImports(ast, absPath, sources, errors);
16208
+ if (ast.extends.length > 0) {
16209
+ this.logger.debug(`Applying ${ast.extends.length} extension(s)`);
16210
+ }
15426
16211
  ast = applyExtends(ast);
15427
16212
  ast = await resolveNativeSkills(ast, this.loader.getRegistryPath(), absPath);
16213
+ this.logger.debug(`Resolved ${sources.length} source file(s)`);
15428
16214
  return {
15429
16215
  ast,
15430
16216
  sources: [...new Set(sources)],
15431
16217
  errors
15432
16218
  };
15433
16219
  }
16220
+ /**
16221
+ * Count nodes in AST for debug output.
16222
+ */
16223
+ countNodes(ast) {
16224
+ let count = 1;
16225
+ if (ast.meta) count++;
16226
+ if (ast.inherit) count++;
16227
+ count += ast.uses.length;
16228
+ count += ast.blocks.length;
16229
+ count += ast.extends.length;
16230
+ return count;
16231
+ }
15434
16232
  /**
15435
16233
  * Load and parse a file.
15436
16234
  */
@@ -15465,11 +16263,14 @@ var Resolver = class {
15465
16263
  return ast;
15466
16264
  }
15467
16265
  const parentPath = this.loader.resolveRef(ast.inherit.path, absPath);
16266
+ this.logger.verbose(`Resolving inherit: ${ast.inherit.path.raw}`);
16267
+ this.logger.verbose(` \u2192 ${parentPath}`);
15468
16268
  try {
15469
16269
  const parent = await this.resolve(parentPath);
15470
16270
  sources.push(...parent.sources);
15471
16271
  errors.push(...parent.errors);
15472
16272
  if (parent.ast) {
16273
+ this.logger.debug(`Merging with parent AST`);
15473
16274
  return resolveInheritance(parent.ast, ast);
15474
16275
  }
15475
16276
  } catch (err) {
@@ -15487,11 +16288,14 @@ var Resolver = class {
15487
16288
  let result = ast;
15488
16289
  for (const use of ast.uses) {
15489
16290
  const importPath = this.loader.resolveRef(use.path, absPath);
16291
+ this.logger.verbose(`Resolving import: ${use.path.raw}`);
16292
+ this.logger.verbose(` \u2192 ${importPath}`);
15490
16293
  try {
15491
16294
  const imported = await this.resolve(importPath);
15492
16295
  sources.push(...imported.sources);
15493
16296
  errors.push(...imported.errors);
15494
16297
  if (imported.ast) {
16298
+ this.logger.debug(`Merging import${use.alias ? ` as "${use.alias}"` : ""}`);
15495
16299
  result = resolveUses(result, use, imported.ast);
15496
16300
  }
15497
16301
  } catch (err) {
@@ -16765,6 +17569,7 @@ var Validator = class {
16765
17569
  rules;
16766
17570
  config;
16767
17571
  disabledRules;
17572
+ logger;
16768
17573
  /**
16769
17574
  * Create a new validator instance.
16770
17575
  *
@@ -16774,11 +17579,13 @@ var Validator = class {
16774
17579
  this.config = config;
16775
17580
  this.rules = [...allRules];
16776
17581
  this.disabledRules = new Set(config.disableRules ?? []);
17582
+ this.logger = config.logger ?? noopLogger;
16777
17583
  if (config.customRules) {
16778
17584
  for (const rule of config.customRules) {
16779
17585
  this.rules.push(rule);
16780
17586
  }
16781
17587
  }
17588
+ this.logger.debug(`Validator initialized with ${this.rules.length} rules`);
16782
17589
  }
16783
17590
  /**
16784
17591
  * Validate an AST.
@@ -16788,15 +17595,22 @@ var Validator = class {
16788
17595
  */
16789
17596
  validate(ast) {
16790
17597
  const messages = [];
17598
+ const activeRules = this.rules.filter(
17599
+ (r) => !this.disabledRules.has(r.name) && !this.disabledRules.has(r.id)
17600
+ );
17601
+ this.logger.verbose(`Running ${activeRules.length} validation rules`);
16791
17602
  for (const rule of this.rules) {
16792
17603
  if (this.disabledRules.has(rule.name) || this.disabledRules.has(rule.id)) {
17604
+ this.logger.debug(`Skipping disabled rule: ${rule.name}`);
16793
17605
  continue;
16794
17606
  }
16795
17607
  const configuredSeverity = this.config.rules?.[rule.name];
16796
17608
  const severity = configuredSeverity ?? rule.defaultSeverity;
16797
17609
  if (severity === "off") {
17610
+ this.logger.debug(`Skipping off rule: ${rule.name}`);
16798
17611
  continue;
16799
17612
  }
17613
+ this.logger.debug(`Running rule: ${rule.name} (${severity})`);
16800
17614
  const ctx = {
16801
17615
  ast,
16802
17616
  config: this.config,
@@ -16814,6 +17628,9 @@ var Validator = class {
16814
17628
  const errors = messages.filter((m) => m.severity === "error");
16815
17629
  const warnings = messages.filter((m) => m.severity === "warning");
16816
17630
  const infos = messages.filter((m) => m.severity === "info");
17631
+ if (errors.length > 0 || warnings.length > 0) {
17632
+ this.logger.verbose(`Validation: ${errors.length} error(s), ${warnings.length} warning(s)`);
17633
+ }
16817
17634
  return {
16818
17635
  valid: errors.length === 0,
16819
17636
  errors,
@@ -16895,13 +17712,16 @@ ${afterMarker.replace(/^\n+/, "\n")}`;
16895
17712
  var Compiler = class _Compiler {
16896
17713
  constructor(options) {
16897
17714
  this.options = options;
16898
- this.resolver = new Resolver(options.resolver);
16899
- this.validator = new Validator(options.validator);
17715
+ this.logger = options.logger ?? noopLogger;
17716
+ this.resolver = new Resolver({ ...options.resolver, logger: this.logger });
17717
+ this.validator = new Validator({ ...options.validator, logger: this.logger });
16900
17718
  this.loadedFormatters = this.loadFormatters(options.formatters);
17719
+ this.logger.debug(`Compiler initialized with ${this.loadedFormatters.length} formatters`);
16901
17720
  }
16902
17721
  resolver;
16903
17722
  validator;
16904
17723
  loadedFormatters;
17724
+ logger;
16905
17725
  /**
16906
17726
  * Compile a PromptScript file through the full pipeline.
16907
17727
  *
@@ -16909,6 +17729,10 @@ var Compiler = class _Compiler {
16909
17729
  * @returns Compilation result with outputs, errors, and stats
16910
17730
  */
16911
17731
  async compile(entryPath) {
17732
+ this.logger.verbose(`Entry: ${entryPath}`);
17733
+ this.logger.verbose(
17734
+ `Targets: ${this.loadedFormatters.map((f) => f.formatter.name).join(", ")}`
17735
+ );
16912
17736
  const startTotal = Date.now();
16913
17737
  const stats = {
16914
17738
  resolveTime: 0,
@@ -16916,6 +17740,7 @@ var Compiler = class _Compiler {
16916
17740
  formatTime: 0,
16917
17741
  totalTime: 0
16918
17742
  };
17743
+ this.logger.verbose("=== Stage 1: Resolve ===");
16919
17744
  const startResolve = Date.now();
16920
17745
  let resolved;
16921
17746
  try {
@@ -16923,6 +17748,7 @@ var Compiler = class _Compiler {
16923
17748
  } catch (err) {
16924
17749
  stats.resolveTime = Date.now() - startResolve;
16925
17750
  stats.totalTime = Date.now() - startTotal;
17751
+ this.logger.verbose(`Resolve failed (${stats.resolveTime}ms)`);
16926
17752
  return {
16927
17753
  success: false,
16928
17754
  outputs: /* @__PURE__ */ new Map(),
@@ -16932,6 +17758,7 @@ var Compiler = class _Compiler {
16932
17758
  };
16933
17759
  }
16934
17760
  stats.resolveTime = Date.now() - startResolve;
17761
+ this.logger.verbose(`Resolve completed (${stats.resolveTime}ms)`);
16935
17762
  if (resolved.errors.length > 0 || !resolved.ast) {
16936
17763
  stats.totalTime = Date.now() - startTotal;
16937
17764
  return {
@@ -16942,9 +17769,11 @@ var Compiler = class _Compiler {
16942
17769
  stats
16943
17770
  };
16944
17771
  }
17772
+ this.logger.verbose("=== Stage 2: Validate ===");
16945
17773
  const startValidate = Date.now();
16946
17774
  const validation = this.validator.validate(resolved.ast);
16947
17775
  stats.validateTime = Date.now() - startValidate;
17776
+ this.logger.verbose(`Validate completed (${stats.validateTime}ms)`);
16948
17777
  if (!validation.valid) {
16949
17778
  stats.totalTime = Date.now() - startTotal;
16950
17779
  return {
@@ -16955,16 +17784,23 @@ var Compiler = class _Compiler {
16955
17784
  stats
16956
17785
  };
16957
17786
  }
17787
+ this.logger.verbose("=== Stage 3: Format ===");
16958
17788
  const startFormat = Date.now();
16959
17789
  const outputs = /* @__PURE__ */ new Map();
16960
17790
  const formatErrors = [];
16961
17791
  for (const { formatter, config } of this.loadedFormatters) {
17792
+ const formatterStart = Date.now();
17793
+ this.logger.verbose(`Formatting for ${formatter.name}`);
16962
17794
  try {
16963
17795
  const formatOptions = this.getFormatOptionsForTarget(formatter.name, config);
17796
+ this.logger.debug(` Convention: ${formatOptions.convention ?? "default"}`);
16964
17797
  const output = formatter.format(resolved.ast, formatOptions);
17798
+ const formatterTime = Date.now() - formatterStart;
17799
+ this.logger.verbose(` \u2192 ${output.path} (${formatterTime}ms)`);
16965
17800
  outputs.set(output.path, addMarkerToOutput(output));
16966
17801
  if (output.additionalFiles) {
16967
17802
  for (const additionalFile of output.additionalFiles) {
17803
+ this.logger.verbose(` \u2192 ${additionalFile.path} (additional)`);
16968
17804
  outputs.set(additionalFile.path, addMarkerToOutput(additionalFile));
16969
17805
  }
16970
17806
  }
@@ -16978,6 +17814,7 @@ var Compiler = class _Compiler {
16978
17814
  }
16979
17815
  stats.formatTime = Date.now() - startFormat;
16980
17816
  stats.totalTime = Date.now() - startTotal;
17817
+ this.logger.verbose(`Format completed (${stats.formatTime}ms)`);
16981
17818
  if (formatErrors.length > 0) {
16982
17819
  return {
16983
17820
  success: false,
@@ -17277,6 +18114,24 @@ var PROMPTSCRIPT_MARKERS = [
17277
18114
  "> Auto-generated by PromptScript"
17278
18115
  // Legacy - for backwards compatibility
17279
18116
  ];
18117
+ var MARKER_REGEX = /<!-- PromptScript [^>]+ -->\n*/g;
18118
+ function stripMarkers(content) {
18119
+ return content.replace(MARKER_REGEX, "");
18120
+ }
18121
+ function createCliLogger() {
18122
+ return {
18123
+ verbose: (message) => {
18124
+ if (isVerbose() || isDebug()) {
18125
+ ConsoleOutput.verbose(message);
18126
+ }
18127
+ },
18128
+ debug: (message) => {
18129
+ if (isDebug()) {
18130
+ ConsoleOutput.debug(message);
18131
+ }
18132
+ }
18133
+ };
18134
+ }
17280
18135
  function parseTargets(targets) {
17281
18136
  return targets.map((entry) => {
17282
18137
  if (typeof entry === "string") {
@@ -17290,10 +18145,10 @@ function parseTargets(targets) {
17290
18145
  return { name, config };
17291
18146
  }).filter((target) => target.config?.enabled !== false);
17292
18147
  }
17293
- async function isPromptScriptGenerated(filePath) {
18148
+ async function isPromptScriptGenerated2(filePath) {
17294
18149
  try {
17295
18150
  const content = await readFile6(filePath, "utf-8");
17296
- const lines = content.split("\n").slice(0, 10);
18151
+ const lines = content.split("\n").slice(0, 20);
17297
18152
  return lines.some((line) => PROMPTSCRIPT_MARKERS.some((marker) => line.includes(marker)));
17298
18153
  } catch {
17299
18154
  return false;
@@ -17321,7 +18176,7 @@ function printErrors(errors) {
17321
18176
  }
17322
18177
  }
17323
18178
  async function writeOutputs(outputs, options, _config, services) {
17324
- const result = { written: [], skipped: [] };
18179
+ const result = { written: [], skipped: [], unchanged: [] };
17325
18180
  let overwriteAll = false;
17326
18181
  const conflicts = [];
17327
18182
  for (const output of outputs.values()) {
@@ -17329,16 +18184,26 @@ async function writeOutputs(outputs, options, _config, services) {
17329
18184
  const fileExists2 = existsSync7(outputPath);
17330
18185
  if (options.dryRun) {
17331
18186
  if (fileExists2) {
17332
- const isGenerated2 = await isPromptScriptGenerated(outputPath);
18187
+ const isGenerated2 = await isPromptScriptGenerated2(outputPath);
17333
18188
  if (isGenerated2) {
17334
- ConsoleOutput.dryRun(`Would overwrite: ${outputPath}`);
18189
+ const existingContent = await readFile6(outputPath, "utf-8");
18190
+ const existingWithoutMarker = stripMarkers(existingContent);
18191
+ const newWithoutMarker = stripMarkers(output.content);
18192
+ if (existingWithoutMarker === newWithoutMarker) {
18193
+ ConsoleOutput.dryRun(`Unchanged: ${outputPath}`);
18194
+ result.unchanged.push(outputPath);
18195
+ } else {
18196
+ ConsoleOutput.dryRun(`Would overwrite: ${outputPath}`);
18197
+ result.written.push(outputPath);
18198
+ }
17335
18199
  } else {
17336
18200
  ConsoleOutput.warning(`Would conflict: ${outputPath} (not generated by PromptScript)`);
18201
+ result.written.push(outputPath);
17337
18202
  }
17338
18203
  } else {
17339
18204
  ConsoleOutput.dryRun(`Would write: ${outputPath}`);
18205
+ result.written.push(outputPath);
17340
18206
  }
17341
- result.written.push(outputPath);
17342
18207
  continue;
17343
18208
  }
17344
18209
  if (!fileExists2) {
@@ -17348,8 +18213,16 @@ async function writeOutputs(outputs, options, _config, services) {
17348
18213
  result.written.push(outputPath);
17349
18214
  continue;
17350
18215
  }
17351
- const isGenerated = await isPromptScriptGenerated(outputPath);
18216
+ const isGenerated = await isPromptScriptGenerated2(outputPath);
17352
18217
  if (isGenerated) {
18218
+ const existingContent = await readFile6(outputPath, "utf-8");
18219
+ const existingWithoutMarker = stripMarkers(existingContent);
18220
+ const newWithoutMarker = stripMarkers(output.content);
18221
+ if (existingWithoutMarker === newWithoutMarker) {
18222
+ ConsoleOutput.unchanged(outputPath);
18223
+ result.unchanged.push(outputPath);
18224
+ continue;
18225
+ }
17353
18226
  await mkdir2(dirname5(outputPath), { recursive: true });
17354
18227
  await writeFile2(outputPath, output.content, "utf-8");
17355
18228
  ConsoleOutput.success(outputPath);
@@ -17416,12 +18289,16 @@ function printWarnings(warnings) {
17416
18289
  }
17417
18290
  async function compileCommand(options, services = createDefaultServices()) {
17418
18291
  const spinner = createSpinner("Loading configuration...").start();
18292
+ const logger = createCliLogger();
17419
18293
  try {
18294
+ logger.verbose("Loading configuration...");
17420
18295
  const config = await loadConfig(options.config);
17421
18296
  spinner.text = "Compiling...";
17422
18297
  const selectedTarget = options.target ?? options.format;
17423
18298
  const targets = selectedTarget ? [{ name: selectedTarget }] : parseTargets(config.targets);
17424
18299
  const registryPath = options.registry ?? config.registry?.path ?? "./registry";
18300
+ logger.verbose(`Registry: ${registryPath}`);
18301
+ logger.debug(`Config: ${JSON.stringify(config, null, 2)}`);
17425
18302
  const prettierOptions = await resolvePrettierOptions(config, process.cwd());
17426
18303
  const compiler = new Compiler({
17427
18304
  resolver: {
@@ -17431,7 +18308,8 @@ async function compileCommand(options, services = createDefaultServices()) {
17431
18308
  validator: config.validation,
17432
18309
  formatters: targets,
17433
18310
  customConventions: config.customConventions,
17434
- prettier: prettierOptions
18311
+ prettier: prettierOptions,
18312
+ logger
17435
18313
  });
17436
18314
  const entryPath = resolve4("./.promptscript/project.prs");
17437
18315
  if (!existsSync7(entryPath)) {
@@ -17450,6 +18328,9 @@ async function compileCommand(options, services = createDefaultServices()) {
17450
18328
  spinner.succeed("Compilation successful");
17451
18329
  ConsoleOutput.newline();
17452
18330
  const writeResult = await writeOutputs(result.outputs, options, config, services);
18331
+ if (writeResult.unchanged.length > 0) {
18332
+ ConsoleOutput.muted(`Unchanged ${writeResult.unchanged.length} file(s)`);
18333
+ }
17453
18334
  if (writeResult.skipped.length > 0) {
17454
18335
  ConsoleOutput.muted(`Skipped ${writeResult.skipped.length} file(s)`);
17455
18336
  }
@@ -18080,18 +18961,22 @@ function printResults(results) {
18080
18961
  var __filename2 = fileURLToPath2(import.meta.url);
18081
18962
  var __dirname2 = dirname7(__filename2);
18082
18963
  var program = new Command();
18083
- program.name("prs").description("PromptScript CLI - Standardize AI instructions").version(getPackageVersion(__dirname2, "./package.json")).option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-error output").hook("preAction", (thisCommand) => {
18964
+ 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) => {
18084
18965
  const opts = thisCommand.opts();
18085
18966
  if (opts["quiet"]) {
18086
18967
  setContext({ logLevel: 0 /* Quiet */ });
18968
+ } else if (opts["debug"]) {
18969
+ setContext({ logLevel: 3 /* Debug */ });
18087
18970
  } else if (opts["verbose"]) {
18088
18971
  setContext({ logLevel: 2 /* Verbose */ });
18089
18972
  }
18090
- if (process.env["PROMPTSCRIPT_VERBOSE"] === "1" || process.env["PROMPTSCRIPT_VERBOSE"] === "true") {
18973
+ if (process.env["PROMPTSCRIPT_DEBUG"] === "1" || process.env["PROMPTSCRIPT_DEBUG"] === "true") {
18974
+ setContext({ logLevel: 3 /* Debug */ });
18975
+ } else if (process.env["PROMPTSCRIPT_VERBOSE"] === "1" || process.env["PROMPTSCRIPT_VERBOSE"] === "true") {
18091
18976
  setContext({ logLevel: 2 /* Verbose */ });
18092
18977
  }
18093
18978
  });
18094
- 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));
18979
+ 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));
18095
18980
  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));
18096
18981
  program.command("validate").description("Validate PromptScript files").option("--strict", "Treat warnings as errors").option("--format <format>", "Output format (text, json)", "text").action(validateCommand);
18097
18982
  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);