speckeeper 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { z } from 'zod';
3
- import { Command } from 'commander';
4
3
  import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
5
- import { dirname, join, resolve, isAbsolute } from 'path';
4
+ import { dirname, join, resolve, basename, isAbsolute } from 'path';
6
5
  import { fileURLToPath } from 'url';
6
+ import { Command } from 'commander';
7
7
  import chalk4 from 'chalk';
8
- import { parse } from 'yaml';
8
+ import { stringify, parse } from 'yaml';
9
9
  import 'crypto';
10
10
  import { glob } from 'glob';
11
11
  import nodeSqlParser from 'node-sql-parser';
@@ -535,6 +535,1298 @@ var init_dsl = __esm({
535
535
  init_workflows();
536
536
  }
537
537
  });
538
+
539
+ // src/generated/policy-runtime.ts
540
+ var RISK_ORDER = {
541
+ low: 0,
542
+ medium: 1,
543
+ high: 2,
544
+ critical: 3
545
+ };
546
+ function maxRiskLevel(...levels) {
547
+ let max = "low";
548
+ for (const level of levels) {
549
+ if (RISK_ORDER[level] > RISK_ORDER[max]) {
550
+ max = level;
551
+ }
552
+ }
553
+ return max;
554
+ }
555
+ function isOptionActive(definition, value, specified) {
556
+ const schemaType = definition.schema?.type;
557
+ if (schemaType === "boolean") {
558
+ return value === true;
559
+ }
560
+ if (definition.repeatable) {
561
+ return specified && Array.isArray(value) && value.length > 0;
562
+ }
563
+ return specified && value != null;
564
+ }
565
+ function derivePolicy(input) {
566
+ const sideEffects = /* @__PURE__ */ new Set();
567
+ const reads = [];
568
+ const writes = [];
569
+ const networkEffects = [];
570
+ const riskLevels = [];
571
+ let executionMode;
572
+ let explicitConfirmation;
573
+ if (input.commandEffects) {
574
+ const ce = input.commandEffects;
575
+ riskLevels.push(ce.riskLevel ?? "low");
576
+ if (ce.writes) {
577
+ sideEffects.add("file_write");
578
+ for (const w of ce.writes) {
579
+ writes.push({
580
+ kind: "semantic",
581
+ target: w.target,
582
+ description: w.description,
583
+ overwrite: w.overwrite,
584
+ destructive: w.destructive,
585
+ ...w.idempotent !== void 0 ? { idempotent: w.idempotent } : {},
586
+ ...w.idempotencyKey ? { idempotencyKey: w.idempotencyKey } : {},
587
+ ...w.idempotentNote ? { idempotentNote: w.idempotentNote } : {},
588
+ source: `command:${input.commandId}`
589
+ });
590
+ }
591
+ }
592
+ if (ce.reads) {
593
+ for (const r of ce.reads) {
594
+ reads.push({
595
+ kind: "semantic",
596
+ target: r.target,
597
+ description: r.description,
598
+ source: `command:${input.commandId}`
599
+ });
600
+ }
601
+ }
602
+ if (ce.network) {
603
+ sideEffects.add("network");
604
+ if (typeof ce.network === "object") {
605
+ networkEffects.push({
606
+ ...ce.network.description ? { description: ce.network.description } : {},
607
+ ...ce.network.domains ? { domains: ce.network.domains } : {},
608
+ ...ce.network.idempotent !== void 0 ? { idempotent: ce.network.idempotent } : {},
609
+ ...ce.network.idempotencyKey ? { idempotencyKey: ce.network.idempotencyKey } : {},
610
+ ...ce.network.idempotentNote ? { idempotentNote: ce.network.idempotentNote } : {},
611
+ source: `command:${input.commandId}`
612
+ });
613
+ }
614
+ }
615
+ if (ce.executionMode) {
616
+ executionMode = ce.executionMode;
617
+ }
618
+ if (ce.requiresConfirmation !== void 0) {
619
+ explicitConfirmation = ce.requiresConfirmation;
620
+ }
621
+ }
622
+ for (const [optName, optInput] of Object.entries(input.options)) {
623
+ const { definition, value, specified } = optInput;
624
+ const active = isOptionActive(definition, value, specified);
625
+ if (!active) continue;
626
+ if (definition.file) {
627
+ const filePath = typeof value === "string" ? value : void 0;
628
+ const mode = definition.file.mode;
629
+ if (mode === "read" || mode === "readWrite") {
630
+ reads.push({
631
+ kind: "option-file",
632
+ option: optName,
633
+ path: filePath,
634
+ source: `option:${optName}`
635
+ });
636
+ }
637
+ if (mode === "write" || mode === "append" || mode === "readWrite") {
638
+ sideEffects.add("file_write");
639
+ writes.push({
640
+ kind: "option-file",
641
+ option: optName,
642
+ path: filePath,
643
+ mode,
644
+ source: `option:${optName}`
645
+ });
646
+ }
647
+ }
648
+ if (definition.effects) {
649
+ const eff = definition.effects;
650
+ riskLevels.push(eff.riskLevel ?? "low");
651
+ if (eff.writes) {
652
+ sideEffects.add("file_write");
653
+ for (const w of eff.writes) {
654
+ writes.push({
655
+ kind: "semantic",
656
+ target: w.target,
657
+ description: w.description,
658
+ overwrite: w.overwrite,
659
+ destructive: w.destructive,
660
+ ...w.idempotent !== void 0 ? { idempotent: w.idempotent } : {},
661
+ ...w.idempotencyKey ? { idempotencyKey: w.idempotencyKey } : {},
662
+ ...w.idempotentNote ? { idempotentNote: w.idempotentNote } : {},
663
+ source: `option:${optName}`
664
+ });
665
+ }
666
+ }
667
+ if (eff.reads) {
668
+ for (const r of eff.reads) {
669
+ reads.push({
670
+ kind: "semantic",
671
+ target: r.target,
672
+ description: r.description,
673
+ source: `option:${optName}`
674
+ });
675
+ }
676
+ }
677
+ if (eff.network) {
678
+ sideEffects.add("network");
679
+ if (typeof eff.network === "object") {
680
+ networkEffects.push({
681
+ ...eff.network.description ? { description: eff.network.description } : {},
682
+ ...eff.network.domains ? { domains: eff.network.domains } : {},
683
+ ...eff.network.idempotent !== void 0 ? { idempotent: eff.network.idempotent } : {},
684
+ ...eff.network.idempotencyKey ? { idempotencyKey: eff.network.idempotencyKey } : {},
685
+ ...eff.network.idempotentNote ? { idempotentNote: eff.network.idempotentNote } : {},
686
+ source: `option:${optName}`
687
+ });
688
+ }
689
+ }
690
+ if (eff.executionMode && !executionMode) {
691
+ executionMode = eff.executionMode;
692
+ }
693
+ if (eff.requiresConfirmation !== void 0 && explicitConfirmation === void 0) {
694
+ explicitConfirmation = eff.requiresConfirmation;
695
+ }
696
+ }
697
+ }
698
+ const finalRiskLevel = riskLevels.length > 0 ? maxRiskLevel(...riskLevels) : "low";
699
+ const requiresConfirmation = explicitConfirmation ?? (finalRiskLevel === "high" || finalRiskLevel === "critical");
700
+ let requiresSecrets;
701
+ if (input.env) {
702
+ const secrets = [];
703
+ for (const [envName, envVar] of Object.entries(input.env)) {
704
+ if (envVar.sensitive) {
705
+ secrets.push(envName);
706
+ }
707
+ }
708
+ if (secrets.length > 0) {
709
+ requiresSecrets = secrets;
710
+ }
711
+ }
712
+ const semanticWrites = writes.filter((w) => w.kind === "semantic");
713
+ const idempotent = semanticWrites.every((w) => w.idempotent === true) && networkEffects.every((n) => n.idempotent === true);
714
+ return {
715
+ riskLevel: finalRiskLevel,
716
+ requiresConfirmation,
717
+ idempotent,
718
+ sideEffects: [...sideEffects],
719
+ reads,
720
+ writes,
721
+ ...networkEffects.length > 0 ? { network: networkEffects } : {},
722
+ ...executionMode ? { executionMode } : {},
723
+ ...requiresSecrets ? { requiresSecrets } : {}
724
+ };
725
+ }
726
+
727
+ // src/generated/policy.ts
728
+ var commandDefinitions = {
729
+ "init": {
730
+ "effects": {
731
+ "riskLevel": "medium",
732
+ "writes": [
733
+ {
734
+ "target": "speckeeper.config.ts, design/ directory",
735
+ "description": "generates config and starter template files",
736
+ "idempotent": false
737
+ }
738
+ ]
739
+ },
740
+ "options": [
741
+ {
742
+ "name": "force",
743
+ "schema": {
744
+ "type": "boolean",
745
+ "default": false
746
+ },
747
+ "effects": {
748
+ "riskLevel": "high",
749
+ "writes": [
750
+ {
751
+ "target": "existing project files",
752
+ "description": "overwrites existing config and design files",
753
+ "overwrite": true,
754
+ "destructive": true
755
+ }
756
+ ]
757
+ }
758
+ },
759
+ {
760
+ "name": "format",
761
+ "schema": {
762
+ "type": "string",
763
+ "default": "ts",
764
+ "enum": [
765
+ "ts",
766
+ "yaml"
767
+ ]
768
+ }
769
+ }
770
+ ],
771
+ "env": {
772
+ "CURSOR_API_KEY": {
773
+ "sensitive": true
774
+ },
775
+ "GEMINI_API_KEY": {
776
+ "sensitive": true
777
+ },
778
+ "OPENAI_API_KEY": {
779
+ "sensitive": true
780
+ },
781
+ "ANTHROPIC_API_KEY": {
782
+ "sensitive": true
783
+ }
784
+ }
785
+ },
786
+ "build": {
787
+ "effects": {
788
+ "riskLevel": "low",
789
+ "writes": [
790
+ {
791
+ "target": "docs/ directory",
792
+ "description": "generated Markdown documentation files",
793
+ "overwrite": true,
794
+ "idempotent": true
795
+ }
796
+ ]
797
+ },
798
+ "options": [
799
+ {
800
+ "name": "config",
801
+ "schema": {
802
+ "type": "string"
803
+ },
804
+ "file": {
805
+ "mode": "read",
806
+ "exists": true,
807
+ "encoding": "utf-8"
808
+ }
809
+ },
810
+ {
811
+ "name": "output",
812
+ "schema": {
813
+ "type": "string",
814
+ "default": "."
815
+ }
816
+ },
817
+ {
818
+ "name": "format",
819
+ "schema": {
820
+ "type": "string",
821
+ "default": "both",
822
+ "enum": [
823
+ "markdown",
824
+ "json",
825
+ "both"
826
+ ]
827
+ }
828
+ },
829
+ {
830
+ "name": "watch",
831
+ "schema": {
832
+ "type": "boolean",
833
+ "default": false
834
+ },
835
+ "effects": {
836
+ "executionMode": "watch"
837
+ }
838
+ },
839
+ {
840
+ "name": "verbose",
841
+ "schema": {
842
+ "type": "boolean",
843
+ "default": false
844
+ }
845
+ }
846
+ ],
847
+ "env": {
848
+ "CURSOR_API_KEY": {
849
+ "sensitive": true
850
+ },
851
+ "GEMINI_API_KEY": {
852
+ "sensitive": true
853
+ },
854
+ "OPENAI_API_KEY": {
855
+ "sensitive": true
856
+ },
857
+ "ANTHROPIC_API_KEY": {
858
+ "sensitive": true
859
+ }
860
+ }
861
+ },
862
+ "lint": {
863
+ "options": [
864
+ {
865
+ "name": "config",
866
+ "schema": {
867
+ "type": "string"
868
+ },
869
+ "file": {
870
+ "mode": "read",
871
+ "exists": true,
872
+ "encoding": "utf-8"
873
+ }
874
+ },
875
+ {
876
+ "name": "phase",
877
+ "schema": {
878
+ "type": "string",
879
+ "enum": [
880
+ "REQ",
881
+ "HLD",
882
+ "LLD",
883
+ "OPS"
884
+ ]
885
+ }
886
+ },
887
+ {
888
+ "name": "strict",
889
+ "schema": {
890
+ "type": "boolean",
891
+ "default": false
892
+ }
893
+ },
894
+ {
895
+ "name": "fix",
896
+ "schema": {
897
+ "type": "boolean",
898
+ "default": false
899
+ }
900
+ },
901
+ {
902
+ "name": "format",
903
+ "schema": {
904
+ "type": "string",
905
+ "default": "text",
906
+ "enum": [
907
+ "text",
908
+ "json",
909
+ "github"
910
+ ]
911
+ }
912
+ }
913
+ ],
914
+ "env": {
915
+ "CURSOR_API_KEY": {
916
+ "sensitive": true
917
+ },
918
+ "GEMINI_API_KEY": {
919
+ "sensitive": true
920
+ },
921
+ "OPENAI_API_KEY": {
922
+ "sensitive": true
923
+ },
924
+ "ANTHROPIC_API_KEY": {
925
+ "sensitive": true
926
+ }
927
+ }
928
+ },
929
+ "drift": {
930
+ "options": [
931
+ {
932
+ "name": "config",
933
+ "schema": {
934
+ "type": "string"
935
+ },
936
+ "file": {
937
+ "mode": "read",
938
+ "exists": true,
939
+ "encoding": "utf-8"
940
+ }
941
+ },
942
+ {
943
+ "name": "update",
944
+ "schema": {
945
+ "type": "boolean",
946
+ "default": false
947
+ }
948
+ },
949
+ {
950
+ "name": "format",
951
+ "schema": {
952
+ "type": "string",
953
+ "default": "text",
954
+ "enum": [
955
+ "text",
956
+ "json",
957
+ "diff"
958
+ ]
959
+ }
960
+ },
961
+ {
962
+ "name": "fail-on-drift",
963
+ "schema": {
964
+ "type": "boolean",
965
+ "default": false
966
+ }
967
+ }
968
+ ],
969
+ "env": {
970
+ "CURSOR_API_KEY": {
971
+ "sensitive": true
972
+ },
973
+ "GEMINI_API_KEY": {
974
+ "sensitive": true
975
+ },
976
+ "OPENAI_API_KEY": {
977
+ "sensitive": true
978
+ },
979
+ "ANTHROPIC_API_KEY": {
980
+ "sensitive": true
981
+ }
982
+ }
983
+ },
984
+ "check": {
985
+ "options": [
986
+ {
987
+ "name": "config",
988
+ "schema": {
989
+ "type": "string"
990
+ },
991
+ "file": {
992
+ "mode": "read",
993
+ "exists": true,
994
+ "encoding": "utf-8"
995
+ }
996
+ },
997
+ {
998
+ "name": "strict",
999
+ "schema": {
1000
+ "type": "boolean",
1001
+ "default": false
1002
+ }
1003
+ },
1004
+ {
1005
+ "name": "verbose",
1006
+ "schema": {
1007
+ "type": "boolean",
1008
+ "default": false
1009
+ }
1010
+ },
1011
+ {
1012
+ "name": "coverage",
1013
+ "schema": {
1014
+ "type": "boolean",
1015
+ "default": false
1016
+ }
1017
+ }
1018
+ ],
1019
+ "env": {
1020
+ "CURSOR_API_KEY": {
1021
+ "sensitive": true
1022
+ },
1023
+ "GEMINI_API_KEY": {
1024
+ "sensitive": true
1025
+ },
1026
+ "OPENAI_API_KEY": {
1027
+ "sensitive": true
1028
+ },
1029
+ "ANTHROPIC_API_KEY": {
1030
+ "sensitive": true
1031
+ }
1032
+ }
1033
+ },
1034
+ "new": {
1035
+ "effects": {
1036
+ "riskLevel": "low",
1037
+ "writes": [
1038
+ {
1039
+ "target": "design/ directory",
1040
+ "description": "new TypeScript spec file with auto-generated ID",
1041
+ "idempotent": false
1042
+ }
1043
+ ]
1044
+ },
1045
+ "options": [
1046
+ {
1047
+ "name": "kind",
1048
+ "schema": {
1049
+ "type": "string"
1050
+ }
1051
+ },
1052
+ {
1053
+ "name": "name",
1054
+ "schema": {
1055
+ "type": "string"
1056
+ }
1057
+ },
1058
+ {
1059
+ "name": "output",
1060
+ "schema": {
1061
+ "type": "string"
1062
+ }
1063
+ },
1064
+ {
1065
+ "name": "template",
1066
+ "schema": {
1067
+ "type": "string"
1068
+ },
1069
+ "file": {
1070
+ "mode": "read",
1071
+ "exists": true,
1072
+ "encoding": "utf-8"
1073
+ }
1074
+ },
1075
+ {
1076
+ "name": "dry-run",
1077
+ "schema": {
1078
+ "type": "boolean",
1079
+ "default": false
1080
+ }
1081
+ }
1082
+ ],
1083
+ "env": {
1084
+ "CURSOR_API_KEY": {
1085
+ "sensitive": true
1086
+ },
1087
+ "GEMINI_API_KEY": {
1088
+ "sensitive": true
1089
+ },
1090
+ "OPENAI_API_KEY": {
1091
+ "sensitive": true
1092
+ },
1093
+ "ANTHROPIC_API_KEY": {
1094
+ "sensitive": true
1095
+ }
1096
+ }
1097
+ },
1098
+ "impact": {
1099
+ "options": [
1100
+ {
1101
+ "name": "config",
1102
+ "schema": {
1103
+ "type": "string"
1104
+ },
1105
+ "file": {
1106
+ "mode": "read",
1107
+ "exists": true,
1108
+ "encoding": "utf-8"
1109
+ }
1110
+ },
1111
+ {
1112
+ "name": "depth",
1113
+ "schema": {
1114
+ "type": "integer",
1115
+ "minimum": 1,
1116
+ "default": 3
1117
+ }
1118
+ },
1119
+ {
1120
+ "name": "direction",
1121
+ "schema": {
1122
+ "type": "string",
1123
+ "default": "both",
1124
+ "enum": [
1125
+ "upstream",
1126
+ "downstream",
1127
+ "both"
1128
+ ]
1129
+ }
1130
+ },
1131
+ {
1132
+ "name": "format",
1133
+ "schema": {
1134
+ "type": "string",
1135
+ "default": "text",
1136
+ "enum": [
1137
+ "text",
1138
+ "json",
1139
+ "mermaid"
1140
+ ]
1141
+ }
1142
+ }
1143
+ ],
1144
+ "env": {
1145
+ "CURSOR_API_KEY": {
1146
+ "sensitive": true
1147
+ },
1148
+ "GEMINI_API_KEY": {
1149
+ "sensitive": true
1150
+ },
1151
+ "OPENAI_API_KEY": {
1152
+ "sensitive": true
1153
+ },
1154
+ "ANTHROPIC_API_KEY": {
1155
+ "sensitive": true
1156
+ }
1157
+ }
1158
+ },
1159
+ "scaffold": {
1160
+ "effects": {
1161
+ "riskLevel": "low",
1162
+ "writes": [
1163
+ {
1164
+ "target": "design/ directory",
1165
+ "description": "generated _models/ and spec data files",
1166
+ "idempotent": false
1167
+ }
1168
+ ]
1169
+ },
1170
+ "options": [
1171
+ {
1172
+ "name": "source",
1173
+ "schema": {
1174
+ "type": "string"
1175
+ },
1176
+ "file": {
1177
+ "mode": "read",
1178
+ "exists": true,
1179
+ "encoding": "utf-8"
1180
+ }
1181
+ },
1182
+ {
1183
+ "name": "output",
1184
+ "schema": {
1185
+ "type": "string",
1186
+ "default": "design/"
1187
+ }
1188
+ },
1189
+ {
1190
+ "name": "force",
1191
+ "schema": {
1192
+ "type": "boolean",
1193
+ "default": false
1194
+ },
1195
+ "effects": {
1196
+ "riskLevel": "medium",
1197
+ "writes": [
1198
+ {
1199
+ "target": "existing model and spec files",
1200
+ "description": "overwrites existing TypeScript model source files",
1201
+ "overwrite": true
1202
+ }
1203
+ ]
1204
+ }
1205
+ },
1206
+ {
1207
+ "name": "dry-run",
1208
+ "schema": {
1209
+ "type": "boolean",
1210
+ "default": false
1211
+ }
1212
+ },
1213
+ {
1214
+ "name": "format",
1215
+ "schema": {
1216
+ "type": "string",
1217
+ "default": "ts",
1218
+ "enum": [
1219
+ "ts",
1220
+ "yaml"
1221
+ ]
1222
+ }
1223
+ }
1224
+ ],
1225
+ "env": {
1226
+ "CURSOR_API_KEY": {
1227
+ "sensitive": true
1228
+ },
1229
+ "GEMINI_API_KEY": {
1230
+ "sensitive": true
1231
+ },
1232
+ "OPENAI_API_KEY": {
1233
+ "sensitive": true
1234
+ },
1235
+ "ANTHROPIC_API_KEY": {
1236
+ "sensitive": true
1237
+ }
1238
+ }
1239
+ },
1240
+ "convert": {
1241
+ "effects": {
1242
+ "riskLevel": "low",
1243
+ "writes": [
1244
+ {
1245
+ "target": ".yaml output file",
1246
+ "description": "YAML conversion of the TS spec data",
1247
+ "idempotent": true
1248
+ }
1249
+ ]
1250
+ },
1251
+ "options": [
1252
+ {
1253
+ "name": "output",
1254
+ "schema": {
1255
+ "type": "string"
1256
+ },
1257
+ "file": {
1258
+ "mode": "write",
1259
+ "encoding": "utf-8"
1260
+ }
1261
+ },
1262
+ {
1263
+ "name": "dry-run",
1264
+ "schema": {
1265
+ "type": "boolean",
1266
+ "default": false
1267
+ }
1268
+ }
1269
+ ],
1270
+ "env": {
1271
+ "CURSOR_API_KEY": {
1272
+ "sensitive": true
1273
+ },
1274
+ "GEMINI_API_KEY": {
1275
+ "sensitive": true
1276
+ },
1277
+ "OPENAI_API_KEY": {
1278
+ "sensitive": true
1279
+ },
1280
+ "ANTHROPIC_API_KEY": {
1281
+ "sensitive": true
1282
+ }
1283
+ }
1284
+ },
1285
+ "audit-requirements": {
1286
+ "effects": {
1287
+ "network": {
1288
+ "description": "LLM API calls to configured provider",
1289
+ "idempotent": true
1290
+ }
1291
+ },
1292
+ "options": [
1293
+ {
1294
+ "name": "config",
1295
+ "schema": {
1296
+ "type": "string"
1297
+ },
1298
+ "file": {
1299
+ "mode": "read",
1300
+ "exists": true,
1301
+ "encoding": "utf-8"
1302
+ }
1303
+ },
1304
+ {
1305
+ "name": "adapter",
1306
+ "schema": {
1307
+ "type": "string",
1308
+ "enum": [
1309
+ "cursor",
1310
+ "claude",
1311
+ "openai",
1312
+ "gemini",
1313
+ "mock"
1314
+ ]
1315
+ }
1316
+ },
1317
+ {
1318
+ "name": "model",
1319
+ "schema": {
1320
+ "type": "string"
1321
+ }
1322
+ },
1323
+ {
1324
+ "name": "dry-run",
1325
+ "schema": {
1326
+ "type": "boolean",
1327
+ "default": false
1328
+ }
1329
+ },
1330
+ {
1331
+ "name": "show-prompt",
1332
+ "schema": {
1333
+ "type": "boolean",
1334
+ "default": false
1335
+ }
1336
+ },
1337
+ {
1338
+ "name": "fail-on",
1339
+ "schema": {
1340
+ "type": "string",
1341
+ "enum": [
1342
+ "warning",
1343
+ "error",
1344
+ "critical"
1345
+ ],
1346
+ "default": "error"
1347
+ }
1348
+ },
1349
+ {
1350
+ "name": "output",
1351
+ "schema": {
1352
+ "type": "string"
1353
+ },
1354
+ "file": {
1355
+ "mode": "write",
1356
+ "encoding": "utf-8"
1357
+ }
1358
+ },
1359
+ {
1360
+ "name": "report-format",
1361
+ "schema": {
1362
+ "type": "string",
1363
+ "enum": [
1364
+ "json",
1365
+ "text",
1366
+ "yaml"
1367
+ ],
1368
+ "default": "json"
1369
+ }
1370
+ }
1371
+ ],
1372
+ "env": {
1373
+ "CURSOR_API_KEY": {
1374
+ "sensitive": true
1375
+ },
1376
+ "GEMINI_API_KEY": {
1377
+ "sensitive": true
1378
+ },
1379
+ "OPENAI_API_KEY": {
1380
+ "sensitive": true
1381
+ },
1382
+ "ANTHROPIC_API_KEY": {
1383
+ "sensitive": true
1384
+ }
1385
+ }
1386
+ },
1387
+ "propose-trace-links": {
1388
+ "effects": {
1389
+ "network": {
1390
+ "description": "LLM API calls to configured provider",
1391
+ "idempotent": true
1392
+ }
1393
+ },
1394
+ "options": [
1395
+ {
1396
+ "name": "config",
1397
+ "schema": {
1398
+ "type": "string"
1399
+ },
1400
+ "file": {
1401
+ "mode": "read",
1402
+ "exists": true,
1403
+ "encoding": "utf-8"
1404
+ }
1405
+ },
1406
+ {
1407
+ "name": "adapter",
1408
+ "schema": {
1409
+ "type": "string",
1410
+ "enum": [
1411
+ "cursor",
1412
+ "claude",
1413
+ "openai",
1414
+ "gemini",
1415
+ "mock"
1416
+ ]
1417
+ }
1418
+ },
1419
+ {
1420
+ "name": "model",
1421
+ "schema": {
1422
+ "type": "string"
1423
+ }
1424
+ },
1425
+ {
1426
+ "name": "dry-run",
1427
+ "schema": {
1428
+ "type": "boolean",
1429
+ "default": false
1430
+ }
1431
+ },
1432
+ {
1433
+ "name": "show-prompt",
1434
+ "schema": {
1435
+ "type": "boolean",
1436
+ "default": false
1437
+ }
1438
+ },
1439
+ {
1440
+ "name": "fail-on",
1441
+ "schema": {
1442
+ "type": "string",
1443
+ "enum": [
1444
+ "warning",
1445
+ "error",
1446
+ "critical"
1447
+ ],
1448
+ "default": "error"
1449
+ }
1450
+ },
1451
+ {
1452
+ "name": "output",
1453
+ "schema": {
1454
+ "type": "string"
1455
+ },
1456
+ "file": {
1457
+ "mode": "write",
1458
+ "encoding": "utf-8"
1459
+ }
1460
+ },
1461
+ {
1462
+ "name": "report-format",
1463
+ "schema": {
1464
+ "type": "string",
1465
+ "enum": [
1466
+ "json",
1467
+ "text",
1468
+ "yaml"
1469
+ ],
1470
+ "default": "json"
1471
+ }
1472
+ }
1473
+ ],
1474
+ "env": {
1475
+ "CURSOR_API_KEY": {
1476
+ "sensitive": true
1477
+ },
1478
+ "GEMINI_API_KEY": {
1479
+ "sensitive": true
1480
+ },
1481
+ "OPENAI_API_KEY": {
1482
+ "sensitive": true
1483
+ },
1484
+ "ANTHROPIC_API_KEY": {
1485
+ "sensitive": true
1486
+ }
1487
+ }
1488
+ },
1489
+ "explain-impact": {
1490
+ "effects": {
1491
+ "network": {
1492
+ "description": "LLM API calls to configured provider",
1493
+ "idempotent": true
1494
+ }
1495
+ },
1496
+ "options": [
1497
+ {
1498
+ "name": "adapter",
1499
+ "schema": {
1500
+ "type": "string",
1501
+ "enum": [
1502
+ "cursor",
1503
+ "claude",
1504
+ "openai",
1505
+ "gemini",
1506
+ "mock"
1507
+ ]
1508
+ }
1509
+ },
1510
+ {
1511
+ "name": "model",
1512
+ "schema": {
1513
+ "type": "string"
1514
+ }
1515
+ },
1516
+ {
1517
+ "name": "dry-run",
1518
+ "schema": {
1519
+ "type": "boolean",
1520
+ "default": false
1521
+ }
1522
+ },
1523
+ {
1524
+ "name": "show-prompt",
1525
+ "schema": {
1526
+ "type": "boolean",
1527
+ "default": false
1528
+ }
1529
+ },
1530
+ {
1531
+ "name": "fail-on",
1532
+ "schema": {
1533
+ "type": "string",
1534
+ "enum": [
1535
+ "warning",
1536
+ "error",
1537
+ "critical"
1538
+ ],
1539
+ "default": "error"
1540
+ }
1541
+ },
1542
+ {
1543
+ "name": "output",
1544
+ "schema": {
1545
+ "type": "string"
1546
+ },
1547
+ "file": {
1548
+ "mode": "write",
1549
+ "encoding": "utf-8"
1550
+ }
1551
+ },
1552
+ {
1553
+ "name": "report-format",
1554
+ "schema": {
1555
+ "type": "string",
1556
+ "enum": [
1557
+ "json",
1558
+ "text",
1559
+ "yaml"
1560
+ ],
1561
+ "default": "json"
1562
+ }
1563
+ }
1564
+ ],
1565
+ "env": {
1566
+ "CURSOR_API_KEY": {
1567
+ "sensitive": true
1568
+ },
1569
+ "GEMINI_API_KEY": {
1570
+ "sensitive": true
1571
+ },
1572
+ "OPENAI_API_KEY": {
1573
+ "sensitive": true
1574
+ },
1575
+ "ANTHROPIC_API_KEY": {
1576
+ "sensitive": true
1577
+ }
1578
+ }
1579
+ },
1580
+ "propose-acceptance-criteria": {
1581
+ "effects": {
1582
+ "network": {
1583
+ "description": "LLM API calls to configured provider",
1584
+ "idempotent": true
1585
+ }
1586
+ },
1587
+ "options": [
1588
+ {
1589
+ "name": "config",
1590
+ "schema": {
1591
+ "type": "string"
1592
+ },
1593
+ "file": {
1594
+ "mode": "read",
1595
+ "exists": true,
1596
+ "encoding": "utf-8"
1597
+ }
1598
+ },
1599
+ {
1600
+ "name": "adapter",
1601
+ "schema": {
1602
+ "type": "string",
1603
+ "enum": [
1604
+ "cursor",
1605
+ "claude",
1606
+ "openai",
1607
+ "gemini",
1608
+ "mock"
1609
+ ]
1610
+ }
1611
+ },
1612
+ {
1613
+ "name": "model",
1614
+ "schema": {
1615
+ "type": "string"
1616
+ }
1617
+ },
1618
+ {
1619
+ "name": "dry-run",
1620
+ "schema": {
1621
+ "type": "boolean",
1622
+ "default": false
1623
+ }
1624
+ },
1625
+ {
1626
+ "name": "show-prompt",
1627
+ "schema": {
1628
+ "type": "boolean",
1629
+ "default": false
1630
+ }
1631
+ },
1632
+ {
1633
+ "name": "fail-on",
1634
+ "schema": {
1635
+ "type": "string",
1636
+ "enum": [
1637
+ "warning",
1638
+ "error",
1639
+ "critical"
1640
+ ],
1641
+ "default": "error"
1642
+ }
1643
+ },
1644
+ {
1645
+ "name": "output",
1646
+ "schema": {
1647
+ "type": "string"
1648
+ },
1649
+ "file": {
1650
+ "mode": "write",
1651
+ "encoding": "utf-8"
1652
+ }
1653
+ },
1654
+ {
1655
+ "name": "report-format",
1656
+ "schema": {
1657
+ "type": "string",
1658
+ "enum": [
1659
+ "json",
1660
+ "text",
1661
+ "yaml"
1662
+ ],
1663
+ "default": "json"
1664
+ }
1665
+ }
1666
+ ],
1667
+ "env": {
1668
+ "CURSOR_API_KEY": {
1669
+ "sensitive": true
1670
+ },
1671
+ "GEMINI_API_KEY": {
1672
+ "sensitive": true
1673
+ },
1674
+ "OPENAI_API_KEY": {
1675
+ "sensitive": true
1676
+ },
1677
+ "ANTHROPIC_API_KEY": {
1678
+ "sensitive": true
1679
+ }
1680
+ }
1681
+ }
1682
+ };
1683
+ function deriveCommandPolicy(commandId, optionValues) {
1684
+ const def = commandDefinitions[commandId];
1685
+ if (!def) throw new Error(`Unknown command: ${commandId}`);
1686
+ const cmdDef = def;
1687
+ const options = {};
1688
+ const activeOptions = [];
1689
+ for (const optDef of cmdDef.options ?? []) {
1690
+ const value = optionValues[optDef.name];
1691
+ const specified = optDef.name in optionValues && value !== void 0;
1692
+ options[optDef.name] = { value, specified, definition: optDef };
1693
+ if (isOptionActive(optDef, value, specified)) {
1694
+ activeOptions.push(optDef.name);
1695
+ }
1696
+ }
1697
+ const policy = derivePolicy({
1698
+ commandId,
1699
+ commandEffects: cmdDef.effects,
1700
+ options,
1701
+ env: cmdDef.env
1702
+ });
1703
+ return { command: commandId, activeOptions, policy };
1704
+ }
1705
+
1706
+ // src/generated/program.ts
1707
+ function createProgram(handlers2, version) {
1708
+ const program = new Command();
1709
+ program.name("speckeeper").version(version, "-V, --version").description("Requirements and design management framework with TypeScript DSL.");
1710
+ program.option("--introspect", "Output derived policy as JSON without executing the command");
1711
+ program.command("init").description("Initialize a new speckeeper project with starter templates.").option("-F, --force", "Overwrite existing files.", false).option("--format <format>", "Spec data format: ts (default) or yaml.", "ts").action(async (opts, cmd) => {
1712
+ const globalOpts = cmd.optsWithGlobals();
1713
+ if (globalOpts.introspect) {
1714
+ const policy = deriveCommandPolicy("init", opts);
1715
+ console.log(JSON.stringify(policy, null, 2));
1716
+ return;
1717
+ }
1718
+ await handlers2.init(opts, globalOpts);
1719
+ });
1720
+ program.command("build").description("Generate docs/ and specs/ from TypeScript models.").option("-c, --config <path>", "Path to config file.").option("-o, --output <path>", "Base output directory path.", ".").option("-f, --format <format>", "Output format: markdown, json, both.", "both").option("-w, --watch", "Watch for changes and auto-regenerate.", false).option("-v, --verbose", "Show detailed output.", false).action(async (opts, cmd) => {
1721
+ const globalOpts = cmd.optsWithGlobals();
1722
+ if (globalOpts.introspect) {
1723
+ const policy = deriveCommandPolicy("build", opts);
1724
+ console.log(JSON.stringify(policy, null, 2));
1725
+ return;
1726
+ }
1727
+ await handlers2.build(opts, globalOpts);
1728
+ });
1729
+ program.command("lint").description("Check design integrity (ID duplicates, references, layer violations, etc.).").option("-c, --config <path>", "Path to config file.").option("-p, --phase <phase>", "Phase gate to check against: REQ, HLD, LLD, OPS.").option("-s, --strict", "Treat warnings as errors.", false).option("--fix", "Attempt to fix auto-fixable issues (not yet implemented).", false).option("-f, --format <format>", "Output format: text, json, github.", "text").action(async (opts, cmd) => {
1730
+ const globalOpts = cmd.optsWithGlobals();
1731
+ if (globalOpts.introspect) {
1732
+ const policy = deriveCommandPolicy("lint", opts);
1733
+ console.log(JSON.stringify(policy, null, 2));
1734
+ return;
1735
+ }
1736
+ await handlers2.lint(opts, globalOpts);
1737
+ });
1738
+ program.command("drift").description("Check if generated files have been manually edited.").option("-c, --config <path>", "Path to config file.").option("-u, --update", "Auto-update if differences are found (not yet implemented).", false).option("-f, --format <format>", "Output format: text, json, diff.", "text").option("--fail-on-drift", "Exit with code 1 if drift is detected (for CI).", false).action(async (opts, cmd) => {
1739
+ const globalOpts = cmd.optsWithGlobals();
1740
+ if (globalOpts.introspect) {
1741
+ const policy = deriveCommandPolicy("drift", opts);
1742
+ console.log(JSON.stringify(policy, null, 2));
1743
+ return;
1744
+ }
1745
+ await handlers2.drift(opts, globalOpts);
1746
+ });
1747
+ program.command("check").description("Check external SSOT conformance (including custom models).").argument("[type]", "Type of check to run. Filters sources by type.").option("-c, --config <path>", "Path to config file.").option("--strict", "Treat warnings as errors.", false).option("-v, --verbose", "Show detailed output.", false).option("--coverage", "Check if all testable acceptance criteria are covered by TestRefs.", false).action(async (type, opts, cmd) => {
1748
+ const globalOpts = cmd.optsWithGlobals();
1749
+ if (globalOpts.introspect) {
1750
+ const policy = deriveCommandPolicy("check", opts);
1751
+ console.log(JSON.stringify(policy, null, 2));
1752
+ return;
1753
+ }
1754
+ await handlers2.check(type, opts, globalOpts);
1755
+ });
1756
+ program.command("new").description("Create a new element with auto-generated ID.").argument("<type>", "Element type to create.").option("-k, --kind <kind>", "Sub-kind (e.g. functional, non-functional for requirements).").option("-n, --name <name>", "Name of the element.").option("-o, --output <path>", "Output directory path.").option("-t, --template <path>", "Path to template file.").option("--dry-run", "Preview generated file content without writing.", false).action(async (type, opts, cmd) => {
1757
+ const globalOpts = cmd.optsWithGlobals();
1758
+ if (globalOpts.introspect) {
1759
+ const policy = deriveCommandPolicy("new", opts);
1760
+ console.log(JSON.stringify(policy, null, 2));
1761
+ return;
1762
+ }
1763
+ await handlers2.new(type, opts, globalOpts);
1764
+ });
1765
+ program.command("impact").description("Analyze impact of changes to an ID.").argument("<id>", "Spec ID to analyze (e.g. REQ-001, ENT-ORDER).").option("-c, --config <path>", "Path to config file.").option("-d, --depth <n>", "Analysis depth (reference tracking level).", "3").option("--direction <direction>", "Analysis direction: upstream, downstream, both.", "both").option("-f, --format <format>", "Output format: text, json, mermaid.", "text").action(async (id, opts, cmd) => {
1766
+ const globalOpts = cmd.optsWithGlobals();
1767
+ if (globalOpts.introspect) {
1768
+ const policy = deriveCommandPolicy("impact", opts);
1769
+ console.log(JSON.stringify(policy, null, 2));
1770
+ return;
1771
+ }
1772
+ await handlers2.impact(id, opts, globalOpts);
1773
+ });
1774
+ program.command("scaffold").description("Generate _models/ from a Mermaid flowchart definition.").option("-s, --source <path>", "Path to Markdown file containing Mermaid flowchart.").option("-o, --output <path>", "Output directory.", "design/").option("-F, --force", "Overwrite existing files.", false).option("--dry-run", "Preview generated files without writing.", false).option("--format <format>", "Spec data format: ts (default) or yaml.", "ts").action(async (opts, cmd) => {
1775
+ const globalOpts = cmd.optsWithGlobals();
1776
+ if (globalOpts.introspect) {
1777
+ const policy = deriveCommandPolicy("scaffold", opts);
1778
+ console.log(JSON.stringify(policy, null, 2));
1779
+ return;
1780
+ }
1781
+ await handlers2.scaffold(opts, globalOpts);
1782
+ });
1783
+ program.command("convert").description("Convert a TS spec data file to YAML format.").argument("<file>", "Path to TS spec data file.").option("-o, --output <path>", "Output file path (default: same name with .yaml extension).").option("-n, --dry-run", "Preview conversion without writing.", false).action(async (file, opts, cmd) => {
1784
+ const globalOpts = cmd.optsWithGlobals();
1785
+ if (globalOpts.introspect) {
1786
+ const policy = deriveCommandPolicy("convert", opts);
1787
+ console.log(JSON.stringify(policy, null, 2));
1788
+ return;
1789
+ }
1790
+ await handlers2.convert(file, opts, globalOpts);
1791
+ });
1792
+ program.command("audit-requirements").description("Run LLM-based requirement quality audit.").option("-c, --config <path>", "Path to config file.").option("-a, --adapter <name>", "SDK adapter to use for LLM execution.").option("--model <name>", "LLM model override.").option("-n, --dry-run", "Output the constructed prompt without calling LLM.", false).option("--show-prompt", "Display the constructed LLM prompt on stderr.", false).option("--fail-on <level>", "Minimum severity that causes a non-zero exit.", "error").option("-o, --output <file>", "Write result to a file instead of stdout.").option("--report-format <fmt>", "Output format for the audit report.", "json").action(async (opts, cmd) => {
1793
+ const globalOpts = cmd.optsWithGlobals();
1794
+ if (globalOpts.introspect) {
1795
+ const policy = deriveCommandPolicy("audit-requirements", opts);
1796
+ console.log(JSON.stringify(policy, null, 2));
1797
+ return;
1798
+ }
1799
+ await handlers2.auditRequirements(opts, globalOpts);
1800
+ });
1801
+ program.command("propose-trace-links").description("LLM-based traceability link proposal.").option("-c, --config <path>", "Path to config file.").option("-a, --adapter <name>", "SDK adapter to use for LLM execution.").option("--model <name>", "LLM model override.").option("-n, --dry-run", "Output the constructed prompt without calling LLM.", false).option("--show-prompt", "Display the constructed LLM prompt on stderr.", false).option("--fail-on <level>", "Minimum severity that causes a non-zero exit.", "error").option("-o, --output <file>", "Write result to a file instead of stdout.").option("--report-format <fmt>", "Output format for the report.", "json").action(async (opts, cmd) => {
1802
+ const globalOpts = cmd.optsWithGlobals();
1803
+ if (globalOpts.introspect) {
1804
+ const policy = deriveCommandPolicy("propose-trace-links", opts);
1805
+ console.log(JSON.stringify(policy, null, 2));
1806
+ return;
1807
+ }
1808
+ await handlers2.proposeTraceLinks(opts, globalOpts);
1809
+ });
1810
+ program.command("explain-impact").description("LLM-based explanation of impact analysis output.").option("-a, --adapter <name>", "SDK adapter to use for LLM execution.").option("--model <name>", "LLM model override.").option("-n, --dry-run", "Output the constructed prompt without calling LLM.", false).option("--show-prompt", "Display the constructed LLM prompt on stderr.", false).option("--fail-on <level>", "Minimum severity that causes a non-zero exit.", "error").option("-o, --output <file>", "Write result to a file instead of stdout.").option("--report-format <fmt>", "Output format for the report.", "json").action(async (opts, cmd) => {
1811
+ const globalOpts = cmd.optsWithGlobals();
1812
+ if (globalOpts.introspect) {
1813
+ const policy = deriveCommandPolicy("explain-impact", opts);
1814
+ console.log(JSON.stringify(policy, null, 2));
1815
+ return;
1816
+ }
1817
+ await handlers2.explainImpact(opts, globalOpts);
1818
+ });
1819
+ program.command("propose-acceptance-criteria").description("LLM-based acceptance criteria proposal.").argument("[specIds...]", "Spec IDs to propose criteria for. Defaults to all specs if omitted.").option("-c, --config <path>", "Path to config file.").option("-a, --adapter <name>", "SDK adapter to use for LLM execution.").option("--model <name>", "LLM model override.").option("-n, --dry-run", "Output the constructed prompt without calling LLM.", false).option("--show-prompt", "Display the constructed LLM prompt on stderr.", false).option("--fail-on <level>", "Minimum severity that causes a non-zero exit.", "error").option("-o, --output <file>", "Write result to a file instead of stdout.").option("--report-format <fmt>", "Output format for the report.", "json").action(async (specIds, opts, cmd) => {
1820
+ const globalOpts = cmd.optsWithGlobals();
1821
+ if (globalOpts.introspect) {
1822
+ const policy = deriveCommandPolicy("propose-acceptance-criteria", opts);
1823
+ console.log(JSON.stringify(policy, null, 2));
1824
+ return;
1825
+ }
1826
+ await handlers2.proposeAcceptanceCriteria(specIds, opts, globalOpts);
1827
+ });
1828
+ return program;
1829
+ }
538
1830
  var defaultConfig = {
539
1831
  srcDir: "src",
540
1832
  designDir: "design",
@@ -1961,6 +3253,8 @@ async function runInit(options = {}) {
1961
3253
  console.log(chalk4.gray(" Please ensure the package is properly installed."));
1962
3254
  return;
1963
3255
  }
3256
+ const isYaml = options.format === "yaml";
3257
+ const skipForYaml = /* @__PURE__ */ new Set(["design/requirements.ts"]);
1964
3258
  let packageJsonCreated = false;
1965
3259
  copyTemplateDir(templatesDir, cwd, projectName, options.force || false, (relativePath, created) => {
1966
3260
  if (created) {
@@ -1971,25 +3265,29 @@ async function runInit(options = {}) {
1971
3265
  } else {
1972
3266
  console.log(chalk4.yellow(` Skipped: ${relativePath} (already exists)`));
1973
3267
  }
1974
- });
3268
+ }, "", isYaml ? skipForYaml : void 0);
3269
+ if (isYaml) {
3270
+ writeYamlInitFiles(cwd, options.force || false);
3271
+ }
1975
3272
  console.log("");
1976
3273
  console.log(chalk4.cyan(" Project initialized successfully!"));
1977
3274
  console.log("");
3275
+ const dataFormat = isYaml ? "design/*.yaml" : "design/*.ts using defineSpecs()";
1978
3276
  console.log(" Next steps:");
1979
3277
  if (packageJsonCreated) {
1980
3278
  console.log(chalk4.gray(" 1. Run `npm install` to install dependencies"));
1981
- console.log(chalk4.gray(" 2. Add spec data in design/*.ts using defineSpecs()"));
3279
+ console.log(chalk4.gray(` 2. Add spec data in ${dataFormat}`));
1982
3280
  console.log(chalk4.gray(" 3. Import new spec files in design/index.ts"));
1983
3281
  console.log(chalk4.gray(" 4. Run `npx speckeeper lint` to validate"));
1984
3282
  } else {
1985
3283
  console.log(chalk4.gray(" 1. Add speckeeper and zod to your package.json dependencies"));
1986
3284
  console.log(chalk4.gray(' 2. Ensure "type": "module" is set in package.json'));
1987
- console.log(chalk4.gray(" 3. Add spec data in design/*.ts using defineSpecs()"));
3285
+ console.log(chalk4.gray(` 3. Add spec data in ${dataFormat}`));
1988
3286
  console.log(chalk4.gray(" 4. Import new spec files in design/index.ts"));
1989
3287
  console.log(chalk4.gray(" 5. Run `npx speckeeper lint` to validate"));
1990
3288
  }
1991
3289
  }
1992
- function copyTemplateDir(srcDir, destDir, projectName, force, onFile, relativePath = "") {
3290
+ function copyTemplateDir(srcDir, destDir, projectName, force, onFile, relativePath = "", skipRelPaths) {
1993
3291
  const entries = readdirSync(srcDir);
1994
3292
  for (const entry of entries) {
1995
3293
  const srcPath = join(srcDir, entry);
@@ -2002,8 +3300,9 @@ function copyTemplateDir(srcDir, destDir, projectName, force, onFile, relativePa
2002
3300
  mkdirSync(destPath, { recursive: true });
2003
3301
  onFile(relPath + "/", true);
2004
3302
  }
2005
- copyTemplateDir(srcPath, destPath, projectName, force, onFile, relPath);
3303
+ copyTemplateDir(srcPath, destPath, projectName, force, onFile, relPath, skipRelPaths);
2006
3304
  } else {
3305
+ if (skipRelPaths?.has(relPath)) continue;
2007
3306
  if (!force && existsSync(destPath)) {
2008
3307
  onFile(relPath, false);
2009
3308
  continue;
@@ -2019,6 +3318,91 @@ function copyTemplateDir(srcDir, destDir, projectName, force, onFile, relativePa
2019
3318
  }
2020
3319
  }
2021
3320
  }
3321
+ function writeYamlInitFiles(cwd, force) {
3322
+ const designDir = join(cwd, "design");
3323
+ if (!existsSync(designDir)) {
3324
+ mkdirSync(designDir, { recursive: true });
3325
+ }
3326
+ const reqYaml = join(designDir, "requirements.yaml");
3327
+ if (force || !existsSync(reqYaml)) {
3328
+ writeFileSync(reqYaml, `# Requirements
3329
+ model: requirement
3330
+ specs:
3331
+ - id: REQ-001
3332
+ name: User Authentication
3333
+ type: functional
3334
+ priority: must
3335
+ description: Users can authenticate using email and password
3336
+ acceptanceCriteria:
3337
+ - id: REQ-001-01
3338
+ description: Valid credentials grant access
3339
+ verificationMethod: test
3340
+ - id: REQ-001-02
3341
+ description: Invalid credentials show error message
3342
+ verificationMethod: test
3343
+ `);
3344
+ console.log(chalk4.green(" Created: design/requirements.yaml"));
3345
+ }
3346
+ const indexTs = join(designDir, "index.ts");
3347
+ if (force || !existsSync(indexTs)) {
3348
+ writeFileSync(indexTs, `/**
3349
+ * Design entry point
3350
+ */
3351
+ import { mergeSpecs, loadYamlDir } from 'speckeeper';
3352
+ import { allModels } from './_models/index.ts';
3353
+
3354
+ const yamlSpecs = loadYamlDir(import.meta.dirname, allModels);
3355
+
3356
+ export default mergeSpecs(...yamlSpecs);
3357
+ `);
3358
+ console.log(chalk4.green(" Created: design/index.ts (yaml mode)"));
3359
+ }
3360
+ }
3361
+ async function runConvert(file, options = {}) {
3362
+ const filePath = resolve(file);
3363
+ if (!existsSync(filePath)) {
3364
+ console.error(chalk4.red(` Error: File not found: ${filePath}`));
3365
+ process.exit(1);
3366
+ }
3367
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".js") && !filePath.endsWith(".mjs")) {
3368
+ console.error(chalk4.red(" Error: Source file must be a .ts, .js, or .mjs file"));
3369
+ process.exit(1);
3370
+ }
3371
+ console.log(chalk4.cyan("speckeeper convert"));
3372
+ console.log(chalk4.gray(` Source: ${filePath}`));
3373
+ console.log("");
3374
+ let specModule;
3375
+ try {
3376
+ const imported = await import(filePath);
3377
+ specModule = imported.default ?? imported;
3378
+ } catch (err) {
3379
+ console.error(chalk4.red(` Error: Failed to import ${filePath}`));
3380
+ console.error(chalk4.gray(` ${err instanceof Error ? err.message : String(err)}`));
3381
+ process.exit(1);
3382
+ }
3383
+ if (!specModule?.entries || !Array.isArray(specModule.entries)) {
3384
+ console.error(chalk4.red(" Error: File does not export a valid SpecModule (missing entries array)"));
3385
+ console.error(chalk4.gray(" The file must export a SpecModule via defineSpecs()"));
3386
+ process.exit(1);
3387
+ }
3388
+ const yamlData = specModule.entries.length === 1 ? { model: specModule.entries[0].model.id, specs: specModule.entries[0].data } : { entries: specModule.entries.map((e) => ({ model: e.model.id, specs: e.data })) };
3389
+ const yamlString = stringify(yamlData, {
3390
+ lineWidth: 120,
3391
+ defaultStringType: "PLAIN",
3392
+ defaultKeyType: "PLAIN"
3393
+ });
3394
+ if (options.dryRun) {
3395
+ console.log(chalk4.yellow(" Dry run \u2014 showing conversion result:"));
3396
+ console.log("");
3397
+ console.log(yamlString);
3398
+ return;
3399
+ }
3400
+ const outputPath = options.output ? resolve(options.output) : join(dirname(filePath), basename(filePath).replace(/\.(ts|js|mjs)$/, ".yaml"));
3401
+ writeFileSync(outputPath, yamlString);
3402
+ console.log(chalk4.green(` Created: ${outputPath}`));
3403
+ console.log("");
3404
+ console.log(chalk4.gray(" Remember to update design/index.ts to use loadYamlDir() for YAML files."));
3405
+ }
2022
3406
 
2023
3407
  // src/scaffold/mermaid-parser.ts
2024
3408
  function extractMermaidBlocks(markdown) {
@@ -2549,12 +3933,27 @@ function generateModelFile(node, _incomingEdges, _outgoingEdges, _allNodes) {
2549
3933
  content
2550
3934
  };
2551
3935
  }
2552
- function generateSpecDataFile(node) {
3936
+ function generateSpecDataFile(node, format = "ts") {
2553
3937
  const templateInfo = resolveModelTemplate(node.id, node.classes, node.subgraph);
2554
3938
  const className = `${templateInfo.modelName}Model`;
2555
3939
  const typeName = templateInfo.modelName;
2556
3940
  const varName = toCamelCase(templateInfo.modelName) + "s";
2557
3941
  const fileName = templateInfo.fileName;
3942
+ if (format === "yaml") {
3943
+ const content2 = `# ${templateInfo.modelName} Specifications
3944
+ #
3945
+ # Generated by speckeeper scaffold \u2014 add your specs below.
3946
+ model: ${templateInfo.fileName}
3947
+ specs:
3948
+ # - id: ${templateInfo.defaultIdPrefix}-001
3949
+ # name: "..."
3950
+ # description: "..."
3951
+ `;
3952
+ return {
3953
+ relativePath: `${fileName}.yaml`,
3954
+ content: content2
3955
+ };
3956
+ }
2558
3957
  const content = `/**
2559
3958
  * ${templateInfo.modelName} Specifications
2560
3959
  *
@@ -2580,7 +3979,7 @@ export default defineSpecs(
2580
3979
  function toCamelCase(pascalCase) {
2581
3980
  return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
2582
3981
  }
2583
- function generateDesignIndex(speckeeperNodes) {
3982
+ function generateDesignIndex(speckeeperNodes, format = "ts") {
2584
3983
  const generated = /* @__PURE__ */ new Set();
2585
3984
  const specFiles = [];
2586
3985
  for (const node of speckeeperNodes) {
@@ -2594,6 +3993,21 @@ function generateDesignIndex(speckeeperNodes) {
2594
3993
  fileName: templateInfo.fileName
2595
3994
  });
2596
3995
  }
3996
+ if (format === "yaml") {
3997
+ const content2 = `/**
3998
+ * Design entry point \u2014 generated by speckeeper scaffold
3999
+ *
4000
+ * YAML spec files in this directory are auto-discovered by loadYamlDir().
4001
+ */
4002
+ import { mergeSpecs, loadYamlDir } from 'speckeeper';
4003
+ import { allModels } from './_models';
4004
+
4005
+ const yamlSpecs = loadYamlDir(import.meta.dirname, allModels);
4006
+
4007
+ export default mergeSpecs(...yamlSpecs);
4008
+ `;
4009
+ return { relativePath: "index.ts", content: content2 };
4010
+ }
2597
4011
  const imports = specFiles.map((f) => `import ${f.varName} from './${f.fileName}';`).join("\n");
2598
4012
  const args = specFiles.map((f) => f.varName).join(", ");
2599
4013
  const content = `/**
@@ -2609,7 +4023,7 @@ export default mergeSpecs(${args});
2609
4023
  content
2610
4024
  };
2611
4025
  }
2612
- function generateAllModelFiles(speckeeperNodes, resolvedEdges, allNodes) {
4026
+ function generateAllModelFiles(speckeeperNodes, resolvedEdges, allNodes, format = "ts") {
2613
4027
  const files = [];
2614
4028
  const generated = /* @__PURE__ */ new Set();
2615
4029
  const templateNodeGroups = /* @__PURE__ */ new Map();
@@ -2632,9 +4046,9 @@ function generateAllModelFiles(speckeeperNodes, resolvedEdges, allNodes) {
2632
4046
  resolvedEdges.filter((e) => groupNodeIds.has(e.targetId));
2633
4047
  resolvedEdges.filter((e) => groupNodeIds.has(e.sourceId));
2634
4048
  files.push(generateModelFile(node));
2635
- files.push(generateSpecDataFile(node));
4049
+ files.push(generateSpecDataFile(node, format));
2636
4050
  }
2637
- files.push(generateDesignIndex(speckeeperNodes));
4051
+ files.push(generateDesignIndex(speckeeperNodes, format));
2638
4052
  return files;
2639
4053
  }
2640
4054
 
@@ -2727,7 +4141,8 @@ async function scaffoldCommand(options) {
2727
4141
  );
2728
4142
  printDiagnostics(diagnostics);
2729
4143
  const outputDir = options.output ? resolve(options.output) : join(process.cwd(), "design");
2730
- const modelFiles = generateAllModelFiles(speckeeperNodes, resolved, flowchart.nodes);
4144
+ const specFormat = options.format ?? "ts";
4145
+ const modelFiles = generateAllModelFiles(speckeeperNodes, resolved, flowchart.nodes, specFormat);
2731
4146
  const indexFile = generateModelsIndex(modelFiles);
2732
4147
  const allFiles = [...modelFiles, indexFile];
2733
4148
  console.log("");
@@ -3330,13 +4745,13 @@ async function commandExplainImpact(opts) {
3330
4745
  }
3331
4746
  }
3332
4747
  function readStdin() {
3333
- return new Promise((resolve3, reject) => {
4748
+ return new Promise((resolve4, reject) => {
3334
4749
  const chunks = [];
3335
4750
  process.stdin.on("data", (chunk) => chunks.push(chunk));
3336
- process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
4751
+ process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
3337
4752
  process.stdin.on("error", reject);
3338
4753
  if (process.stdin.isTTY) {
3339
- resolve3("");
4754
+ resolve4("");
3340
4755
  }
3341
4756
  });
3342
4757
  }
@@ -3386,68 +4801,78 @@ function getVersion() {
3386
4801
  return "0.0.0";
3387
4802
  }
3388
4803
  }
3389
- var program = new Command();
3390
- program.name("speckeeper").description("Requirements and design management framework with TypeScript DSL").version(getVersion());
3391
- program.command("build").description("Generate docs/ and specs/ from TypeScript models").option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Base output directory path", ".").option("-f, --format <format>", "Output format: markdown, json, both", "both").option("-w, --watch", "Watch for changes and auto-regenerate").option("-v, --verbose", "Show detailed output").action(buildCommand);
3392
- program.command("lint").description("Check design integrity (ID duplicates, references, layer violations, etc.)").option("-c, --config <path>", "Path to config file").option("-p, --phase <phase>", "Phase gate to check against (REQ, HLD, LLD, OPS)").option("-s, --strict", "Treat warnings as errors").option("--fix", "Attempt to fix auto-fixable issues").option("-f, --format <format>", "Output format: text, json, github", "text").action(lintCommand);
3393
- program.command("drift").description("Check if generated files have been manually edited").option("-c, --config <path>", "Path to config file").option("-u, --update", "Auto-update if differences are found").option("-f, --format <format>", "Output format: text, json, diff", "text").option("--fail-on-drift", "Exit with code 1 if drift is detected (for CI)").action(driftCommand);
3394
- program.command("check").description("Check external SSOT conformance (including custom models)").argument("[type]", "Type of check: external-ssot, openapi, ddl, iac, custom, all, test").option("-c, --config <path>", "Path to config file").option("--strict", "Treat warnings as errors").option("-v, --verbose", "Show detailed output").option("--coverage", "Check if all testable acceptance criteria are covered by TestRefs").action(checkCommand);
3395
- program.command("new").description("Create a new element with auto-generated ID").argument("<type>", "Type: requirement, usecase, entity, component, screen, flow, error-case, term").option("-k, --kind <kind>", "Sub-kind (e.g., functional, non-functional for requirements)").option("-n, --name <name>", "Name of the element").option("-o, --output <path>", "Output directory path").option("-t, --template <path>", "Path to template file").action(newCommand);
3396
- program.command("impact").description("Analyze impact of changes to an ID").argument("<id>", "ID to analyze (e.g., REQ-001, ENT-ORDER)").option("-c, --config <path>", "Path to config file").option("-d, --depth <depth>", "Analysis depth (reference tracking level)", "3").option("--direction <direction>", "Analysis direction: upstream, downstream, both", "both").option("-f, --format <format>", "Output format: text, json, mermaid", "text").action(impactCommand);
3397
- program.command("init").description("Initialize a new speckeeper project with starter templates").option("-f, --force", "Overwrite existing files").action(runInit);
3398
- program.command("scaffold").description("Generate _models/ from a mermaid flowchart definition").requiredOption("-s, --source <path>", "Path to Markdown file containing mermaid flowchart").option("-o, --output <path>", "Output directory (default: design/)").option("-f, --force", "Overwrite existing files").option("--dry-run", "Preview generated files without writing").action(scaffoldCommand);
3399
- var llmOptions = (cmd) => cmd.option("-a, --adapter <name>", "SDK adapter (cursor, claude, openai, gemini, mock)").option("--model <name>", "LLM model override").option("-n, --dry-run", "Output prompt without calling LLM").option("--fail-on <level>", "Minimum severity for non-zero exit (warning, error, critical)", "error").option("-o, --output <file>", "Write result to a file instead of stdout").option("--report-format <fmt>", "Output format (json, text, yaml)", "text");
3400
- llmOptions(
3401
- program.command("audit-requirements").description("LLM-based requirement quality audit").option("-c, --config <path>", "Path to config file")
3402
- ).action((opts) => {
3403
- commandAuditRequirements({
3404
- config: opts.config,
3405
- adapter: opts.adapter,
3406
- model: opts.model,
3407
- dryRun: opts.dryRun,
3408
- failOn: opts.failOn,
3409
- output: opts.output,
3410
- reportFormat: opts.reportFormat
3411
- });
3412
- });
3413
- llmOptions(
3414
- program.command("propose-trace-links").description("LLM-based traceability link proposal").option("-c, --config <path>", "Path to config file")
3415
- ).action((opts) => {
3416
- commandProposeTraceLinks({
3417
- config: opts.config,
3418
- adapter: opts.adapter,
3419
- model: opts.model,
3420
- dryRun: opts.dryRun,
3421
- failOn: opts.failOn,
3422
- output: opts.output,
3423
- reportFormat: opts.reportFormat
3424
- });
3425
- });
3426
- llmOptions(
3427
- program.command("explain-impact").description("LLM-based explanation of impact analysis output")
3428
- ).action((opts) => {
3429
- commandExplainImpact({
3430
- adapter: opts.adapter,
3431
- model: opts.model,
3432
- dryRun: opts.dryRun,
3433
- failOn: opts.failOn,
3434
- output: opts.output,
3435
- reportFormat: opts.reportFormat
3436
- });
3437
- });
3438
- llmOptions(
3439
- program.command("propose-acceptance-criteria [specIds...]").description("LLM-based acceptance criteria proposal").option("-c, --config <path>", "Path to config file")
3440
- ).action((specIds, opts) => {
3441
- commandProposeAcceptanceCriteria(specIds, {
3442
- config: opts.config,
3443
- adapter: opts.adapter,
3444
- model: opts.model,
3445
- dryRun: opts.dryRun,
3446
- failOn: opts.failOn,
3447
- output: opts.output,
3448
- reportFormat: opts.reportFormat
3449
- });
3450
- });
3451
- program.parse();
4804
+ var handlers = {
4805
+ init: async (opts) => {
4806
+ await runInit(opts);
4807
+ },
4808
+ build: async (opts) => {
4809
+ await buildCommand(opts);
4810
+ },
4811
+ lint: async (opts) => {
4812
+ await lintCommand(opts);
4813
+ },
4814
+ drift: async (opts) => {
4815
+ await driftCommand(opts);
4816
+ },
4817
+ check: async (type, opts) => {
4818
+ await checkCommand(type, opts);
4819
+ },
4820
+ new: async (type, opts) => {
4821
+ await newCommand({ type, output: opts.output, dryRun: opts.dryRun });
4822
+ },
4823
+ impact: async (id, opts) => {
4824
+ await impactCommand(id, opts);
4825
+ },
4826
+ scaffold: async (opts) => {
4827
+ await scaffoldCommand(opts);
4828
+ },
4829
+ convert: async (file, opts) => {
4830
+ await runConvert(file, opts);
4831
+ },
4832
+ auditRequirements: async (opts) => {
4833
+ await commandAuditRequirements({
4834
+ config: opts.config,
4835
+ adapter: opts.adapter,
4836
+ model: opts.model,
4837
+ dryRun: opts.dryRun,
4838
+ failOn: opts.failOn,
4839
+ output: opts.output,
4840
+ reportFormat: opts.reportFormat
4841
+ });
4842
+ },
4843
+ proposeTraceLinks: async (opts) => {
4844
+ await commandProposeTraceLinks({
4845
+ config: opts.config,
4846
+ adapter: opts.adapter,
4847
+ model: opts.model,
4848
+ dryRun: opts.dryRun,
4849
+ failOn: opts.failOn,
4850
+ output: opts.output,
4851
+ reportFormat: opts.reportFormat
4852
+ });
4853
+ },
4854
+ explainImpact: async (opts) => {
4855
+ await commandExplainImpact({
4856
+ adapter: opts.adapter,
4857
+ model: opts.model,
4858
+ dryRun: opts.dryRun,
4859
+ failOn: opts.failOn,
4860
+ output: opts.output,
4861
+ reportFormat: opts.reportFormat
4862
+ });
4863
+ },
4864
+ proposeAcceptanceCriteria: async (specIds, opts) => {
4865
+ await commandProposeAcceptanceCriteria(specIds, {
4866
+ config: opts.config,
4867
+ adapter: opts.adapter,
4868
+ model: opts.model,
4869
+ dryRun: opts.dryRun,
4870
+ failOn: opts.failOn,
4871
+ output: opts.output,
4872
+ reportFormat: opts.reportFormat
4873
+ });
4874
+ }
4875
+ };
4876
+ createProgram(handlers, getVersion()).parse();
3452
4877
  //# sourceMappingURL=cli.js.map
3453
4878
  //# sourceMappingURL=cli.js.map