@hegemonart/get-design-done 1.53.0 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -0
  4. package/README.md +4 -0
  5. package/SKILL.md +2 -1
  6. package/agents/component-taxonomy-mapper.md +3 -0
  7. package/agents/motion-mapper.md +1 -0
  8. package/agents/token-mapper.md +3 -0
  9. package/bin/gdd-dashboard +91 -0
  10. package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +81 -0
  11. package/package.json +2 -1
  12. package/reference/frameworks/astro.md +43 -0
  13. package/reference/frameworks/nextjs.md +44 -0
  14. package/reference/frameworks/remix.md +44 -0
  15. package/reference/frameworks/storybook.md +44 -0
  16. package/reference/frameworks/sveltekit.md +43 -0
  17. package/reference/frameworks/vite-react.md +43 -0
  18. package/reference/interaction.md +1 -0
  19. package/reference/motion/framer-motion.md +45 -0
  20. package/reference/motion/gsap.md +45 -0
  21. package/reference/motion/motion-one.md +44 -0
  22. package/reference/motion/react-spring.md +44 -0
  23. package/reference/motion.md +1 -0
  24. package/reference/registry.json +163 -1
  25. package/reference/registry.schema.json +18 -1
  26. package/reference/skill-graph.md +2 -1
  27. package/reference/systems/chakra.md +44 -0
  28. package/reference/systems/css-modules.md +44 -0
  29. package/reference/systems/mui.md +44 -0
  30. package/reference/systems/radix-themes.md +43 -0
  31. package/reference/systems/shadcn.md +45 -0
  32. package/reference/systems/styled-components.md +44 -0
  33. package/reference/systems/tailwind.md +44 -0
  34. package/reference/systems/vanilla-extract.md +44 -0
  35. package/scripts/lib/dashboard/graph-html.cjs +0 -0
  36. package/scripts/lib/detect/stack.cjs +455 -0
  37. package/scripts/lib/detect/stack.d.cts +44 -0
  38. package/scripts/lib/explore-parallel-runner/index.ts +138 -1
  39. package/scripts/lib/explore-parallel-runner/types.ts +27 -0
  40. package/scripts/lib/health-mirror/index.cjs +218 -1
  41. package/scripts/lib/manifest/skills.json +8 -0
  42. package/scripts/lib/mapper-spawn.cjs +257 -0
  43. package/scripts/lib/mapper-spawn.d.cts +60 -0
  44. package/scripts/lib/new-addendum.cjs +204 -0
  45. package/sdk/cli/commands/dashboard.ts +419 -0
  46. package/sdk/cli/index.js +1388 -3
  47. package/sdk/cli/index.ts +7 -0
  48. package/sdk/dashboard/data/_pkg-root.cjs +92 -0
  49. package/sdk/dashboard/data/cost-aggregator.cjs +187 -0
  50. package/sdk/dashboard/data/discovery.cjs +297 -0
  51. package/sdk/dashboard/data/risk-surface.cjs +136 -0
  52. package/sdk/dashboard/data/source.cjs +576 -0
  53. package/sdk/dashboard/tui/ansi.cjs +355 -0
  54. package/sdk/dashboard/tui/index.cjs +778 -0
  55. package/sdk/mcp/gdd-mcp/server.js +1117 -0
  56. package/skills/new-addendum/SKILL.md +81 -0
package/sdk/cli/index.js CHANGED
@@ -850,6 +850,1082 @@ var require_incremental_discover = __commonJS({
850
850
  }
851
851
  });
852
852
 
853
+ // scripts/lib/detect/rules/ban-01.cjs
854
+ var require_ban_01 = __commonJS({
855
+ "scripts/lib/detect/rules/ban-01.cjs"(exports2, module2) {
856
+ "use strict";
857
+ var PATTERN = "border-left:\\s*[2-9][0-9]*px|border-right:\\s*[2-9][0-9]*px";
858
+ function matcher(ctx) {
859
+ const out = [];
860
+ const re = new RegExp(PATTERN, "gi");
861
+ const text = String(ctx && ctx.content || "");
862
+ let m;
863
+ while ((m = re.exec(text)) !== null) {
864
+ const upto = text.slice(0, m.index);
865
+ const line = upto.split("\n").length;
866
+ const lastNl = upto.lastIndexOf("\n");
867
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
868
+ out.push({ line, column, match: m[0] });
869
+ if (m.index === re.lastIndex) re.lastIndex++;
870
+ }
871
+ return out;
872
+ }
873
+ module2.exports = {
874
+ id: "BAN-01",
875
+ category: "decoration",
876
+ name: "Side-Stripe Borders",
877
+ description: "A thick (>=2px) left/right accent border \u2014 a dated, decorative side-stripe.",
878
+ references: ["reference/anti-patterns.md#BAN-01"],
879
+ severity: "warn",
880
+ pattern: PATTERN,
881
+ matcher
882
+ };
883
+ }
884
+ });
885
+
886
+ // scripts/lib/detect/rules/ban-02.cjs
887
+ var require_ban_02 = __commonJS({
888
+ "scripts/lib/detect/rules/ban-02.cjs"(exports2, module2) {
889
+ "use strict";
890
+ var PATTERN = "background-clip:\\s*text|text-fill-color:\\s*transparent";
891
+ function matcher(ctx) {
892
+ const out = [];
893
+ const re = new RegExp(PATTERN, "gi");
894
+ const text = String(ctx && ctx.content || "");
895
+ let m;
896
+ while ((m = re.exec(text)) !== null) {
897
+ const upto = text.slice(0, m.index);
898
+ const line = upto.split("\n").length;
899
+ const lastNl = upto.lastIndexOf("\n");
900
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
901
+ out.push({ line, column, match: m[0] });
902
+ if (m.index === re.lastIndex) re.lastIndex++;
903
+ }
904
+ return out;
905
+ }
906
+ module2.exports = {
907
+ id: "BAN-02",
908
+ category: "decoration",
909
+ name: "Gradient Text",
910
+ description: "Gradient-filled text via background-clip:text \u2014 low legibility, an AI-era cliche.",
911
+ references: ["reference/anti-patterns.md#BAN-02"],
912
+ severity: "warn",
913
+ pattern: PATTERN,
914
+ matcher
915
+ };
916
+ }
917
+ });
918
+
919
+ // scripts/lib/detect/rules/ban-03.cjs
920
+ var require_ban_03 = __commonJS({
921
+ "scripts/lib/detect/rules/ban-03.cjs"(exports2, module2) {
922
+ "use strict";
923
+ var PATTERN = "cubic-bezier\\(.*-[0-9]|bounce|elastic|spring\\(";
924
+ function matcher(ctx) {
925
+ const out = [];
926
+ const re = new RegExp(PATTERN, "gi");
927
+ const text = String(ctx && ctx.content || "");
928
+ let m;
929
+ while ((m = re.exec(text)) !== null) {
930
+ const upto = text.slice(0, m.index);
931
+ const line = upto.split("\n").length;
932
+ const lastNl = upto.lastIndexOf("\n");
933
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
934
+ out.push({ line, column, match: m[0] });
935
+ if (m.index === re.lastIndex) re.lastIndex++;
936
+ }
937
+ return out;
938
+ }
939
+ module2.exports = {
940
+ id: "BAN-03",
941
+ category: "motion",
942
+ name: "Bounce/Elastic Easing",
943
+ description: "Bounce/elastic/spring easing \u2014 playful overshoot that reads as unserious for product UI.",
944
+ references: ["reference/anti-patterns.md#BAN-03"],
945
+ severity: "warn",
946
+ pattern: PATTERN,
947
+ matcher
948
+ };
949
+ }
950
+ });
951
+
952
+ // scripts/lib/detect/rules/ban-05.cjs
953
+ var require_ban_05 = __commonJS({
954
+ "scripts/lib/detect/rules/ban-05.cjs"(exports2, module2) {
955
+ "use strict";
956
+ var PATTERN = "background.*#000000|background.*rgb\\(0,\\s*0,\\s*0\\)";
957
+ function matcher(ctx) {
958
+ const out = [];
959
+ const re = new RegExp(PATTERN, "gi");
960
+ const text = String(ctx && ctx.content || "");
961
+ let m;
962
+ while ((m = re.exec(text)) !== null) {
963
+ const upto = text.slice(0, m.index);
964
+ const line = upto.split("\n").length;
965
+ const lastNl = upto.lastIndexOf("\n");
966
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
967
+ out.push({ line, column, match: m[0] });
968
+ if (m.index === re.lastIndex) re.lastIndex++;
969
+ }
970
+ return out;
971
+ }
972
+ module2.exports = {
973
+ id: "BAN-05",
974
+ category: "color",
975
+ name: "Pure Black Dark Mode",
976
+ description: "Pure #000 dark-mode background \u2014 harsh contrast + halation; use a near-black surface.",
977
+ references: ["reference/anti-patterns.md#BAN-05"],
978
+ severity: "warn",
979
+ pattern: PATTERN,
980
+ matcher
981
+ };
982
+ }
983
+ });
984
+
985
+ // scripts/lib/detect/rules/ban-06.cjs
986
+ var require_ban_06 = __commonJS({
987
+ "scripts/lib/detect/rules/ban-06.cjs"(exports2, module2) {
988
+ "use strict";
989
+ var PATTERN = "user-scalable=no|maximum-scale=1";
990
+ function matcher(ctx) {
991
+ const out = [];
992
+ const re = new RegExp(PATTERN, "gi");
993
+ const text = String(ctx && ctx.content || "");
994
+ let m;
995
+ while ((m = re.exec(text)) !== null) {
996
+ const upto = text.slice(0, m.index);
997
+ const line = upto.split("\n").length;
998
+ const lastNl = upto.lastIndexOf("\n");
999
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1000
+ out.push({ line, column, match: m[0] });
1001
+ if (m.index === re.lastIndex) re.lastIndex++;
1002
+ }
1003
+ return out;
1004
+ }
1005
+ module2.exports = {
1006
+ id: "BAN-06",
1007
+ category: "accessibility",
1008
+ name: "Disabling Zoom",
1009
+ description: "Viewport meta that disables pinch-zoom \u2014 a WCAG 1.4.4 failure.",
1010
+ references: ["reference/anti-patterns.md#BAN-06"],
1011
+ severity: "error",
1012
+ pattern: PATTERN,
1013
+ matcher
1014
+ };
1015
+ }
1016
+ });
1017
+
1018
+ // scripts/lib/detect/rules/ban-07.cjs
1019
+ var require_ban_07 = __commonJS({
1020
+ "scripts/lib/detect/rules/ban-07.cjs"(exports2, module2) {
1021
+ "use strict";
1022
+ var PATTERN = ":focus\\s*\\{[^}]*outline:\\s*(none|0)";
1023
+ function matcher(ctx) {
1024
+ const out = [];
1025
+ const re = new RegExp(PATTERN, "gi");
1026
+ const text = String(ctx && ctx.content || "");
1027
+ let m;
1028
+ while ((m = re.exec(text)) !== null) {
1029
+ const upto = text.slice(0, m.index);
1030
+ const line = upto.split("\n").length;
1031
+ const lastNl = upto.lastIndexOf("\n");
1032
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1033
+ out.push({ line, column, match: m[0] });
1034
+ if (m.index === re.lastIndex) re.lastIndex++;
1035
+ }
1036
+ return out;
1037
+ }
1038
+ module2.exports = {
1039
+ id: "BAN-07",
1040
+ category: "accessibility",
1041
+ name: "Naked outline: none",
1042
+ description: "Removing the focus outline without a replacement \u2014 a keyboard-a11y failure.",
1043
+ references: ["reference/anti-patterns.md#BAN-07"],
1044
+ severity: "error",
1045
+ pattern: PATTERN,
1046
+ matcher
1047
+ };
1048
+ }
1049
+ });
1050
+
1051
+ // scripts/lib/detect/rules/ban-08.cjs
1052
+ var require_ban_08 = __commonJS({
1053
+ "scripts/lib/detect/rules/ban-08.cjs"(exports2, module2) {
1054
+ "use strict";
1055
+ var PATTERN = "transition:\\s*all\\s";
1056
+ function matcher(ctx) {
1057
+ const out = [];
1058
+ const re = new RegExp(PATTERN, "gi");
1059
+ const text = String(ctx && ctx.content || "");
1060
+ let m;
1061
+ while ((m = re.exec(text)) !== null) {
1062
+ const upto = text.slice(0, m.index);
1063
+ const line = upto.split("\n").length;
1064
+ const lastNl = upto.lastIndexOf("\n");
1065
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1066
+ out.push({ line, column, match: m[0] });
1067
+ if (m.index === re.lastIndex) re.lastIndex++;
1068
+ }
1069
+ return out;
1070
+ }
1071
+ module2.exports = {
1072
+ id: "BAN-08",
1073
+ category: "motion",
1074
+ name: "transition: all",
1075
+ description: "transition: all animates layout-triggering properties \u2014 jank; name the exact properties.",
1076
+ references: ["reference/anti-patterns.md#BAN-08"],
1077
+ severity: "warn",
1078
+ pattern: PATTERN,
1079
+ matcher
1080
+ };
1081
+ }
1082
+ });
1083
+
1084
+ // scripts/lib/detect/rules/ban-09.cjs
1085
+ var require_ban_09 = __commonJS({
1086
+ "scripts/lib/detect/rules/ban-09.cjs"(exports2, module2) {
1087
+ "use strict";
1088
+ var PATTERN = "transform:\\s*scale\\(\\s*0\\s*\\)|scale\\(\\s*0\\s*\\)";
1089
+ function matcher(ctx) {
1090
+ const out = [];
1091
+ const re = new RegExp(PATTERN, "gi");
1092
+ const text = String(ctx && ctx.content || "");
1093
+ let m;
1094
+ while ((m = re.exec(text)) !== null) {
1095
+ const upto = text.slice(0, m.index);
1096
+ const line = upto.split("\n").length;
1097
+ const lastNl = upto.lastIndexOf("\n");
1098
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1099
+ out.push({ line, column, match: m[0] });
1100
+ if (m.index === re.lastIndex) re.lastIndex++;
1101
+ }
1102
+ return out;
1103
+ }
1104
+ module2.exports = {
1105
+ id: "BAN-09",
1106
+ category: "motion",
1107
+ name: "scale(0) Animation Entry",
1108
+ description: "Entering from scale(0) \u2014 nothing materializes from nothing; start at scale(0.95)+opacity.",
1109
+ references: ["reference/anti-patterns.md#BAN-09"],
1110
+ severity: "warn",
1111
+ pattern: PATTERN,
1112
+ matcher
1113
+ };
1114
+ }
1115
+ });
1116
+
1117
+ // scripts/lib/detect/rules/ban-11.cjs
1118
+ var require_ban_11 = __commonJS({
1119
+ "scripts/lib/detect/rules/ban-11.cjs"(exports2, module2) {
1120
+ "use strict";
1121
+ var PATTERN = "outline-(slate|zinc|neutral|gray|stone|blue|red|green|yellow|purple)-\\d+|img\\s*\\{[^}]*outline:\\s*[^}]*#[0-9a-fA-F]{3,8}";
1122
+ function matcher(ctx) {
1123
+ const out = [];
1124
+ const re = new RegExp(PATTERN, "gi");
1125
+ const text = String(ctx && ctx.content || "");
1126
+ let m;
1127
+ while ((m = re.exec(text)) !== null) {
1128
+ const upto = text.slice(0, m.index);
1129
+ const line = upto.split("\n").length;
1130
+ const lastNl = upto.lastIndexOf("\n");
1131
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1132
+ out.push({ line, column, match: m[0] });
1133
+ if (m.index === re.lastIndex) re.lastIndex++;
1134
+ }
1135
+ return out;
1136
+ }
1137
+ module2.exports = {
1138
+ id: "BAN-11",
1139
+ category: "decoration",
1140
+ name: "Tinted Image Outline",
1141
+ description: "A colored outline on an image \u2014 color contamination; use low-opacity black/white.",
1142
+ references: ["reference/anti-patterns.md#BAN-11"],
1143
+ severity: "warn",
1144
+ pattern: PATTERN,
1145
+ matcher
1146
+ };
1147
+ }
1148
+ });
1149
+
1150
+ // scripts/lib/detect/rules/ban-12.cjs
1151
+ var require_ban_12 = __commonJS({
1152
+ "scripts/lib/detect/rules/ban-12.cjs"(exports2, module2) {
1153
+ "use strict";
1154
+ var PATTERN = "transition:\\s*all|transition-property:\\s*all";
1155
+ function matcher(ctx) {
1156
+ const out = [];
1157
+ const re = new RegExp(PATTERN, "gi");
1158
+ const text = String(ctx && ctx.content || "");
1159
+ let m;
1160
+ while ((m = re.exec(text)) !== null) {
1161
+ const upto = text.slice(0, m.index);
1162
+ const line = upto.split("\n").length;
1163
+ const lastNl = upto.lastIndexOf("\n");
1164
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1165
+ out.push({ line, column, match: m[0] });
1166
+ if (m.index === re.lastIndex) re.lastIndex++;
1167
+ }
1168
+ return out;
1169
+ }
1170
+ module2.exports = {
1171
+ id: "BAN-12",
1172
+ category: "motion",
1173
+ name: "transition: all (property)",
1174
+ description: "transition: all / transition-property: all \u2014 recalculates layout every transition.",
1175
+ references: ["reference/anti-patterns.md#BAN-12"],
1176
+ severity: "warn",
1177
+ pattern: PATTERN,
1178
+ matcher
1179
+ };
1180
+ }
1181
+ });
1182
+
1183
+ // scripts/lib/detect/rules/ban-13.cjs
1184
+ var require_ban_13 = __commonJS({
1185
+ "scripts/lib/detect/rules/ban-13.cjs"(exports2, module2) {
1186
+ "use strict";
1187
+ var PATTERN = "will-change:\\s*all";
1188
+ function matcher(ctx) {
1189
+ const out = [];
1190
+ const re = new RegExp(PATTERN, "gi");
1191
+ const text = String(ctx && ctx.content || "");
1192
+ let m;
1193
+ while ((m = re.exec(text)) !== null) {
1194
+ const upto = text.slice(0, m.index);
1195
+ const line = upto.split("\n").length;
1196
+ const lastNl = upto.lastIndexOf("\n");
1197
+ const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
1198
+ out.push({ line, column, match: m[0] });
1199
+ if (m.index === re.lastIndex) re.lastIndex++;
1200
+ }
1201
+ return out;
1202
+ }
1203
+ module2.exports = {
1204
+ id: "BAN-13",
1205
+ category: "performance",
1206
+ name: "will-change: all",
1207
+ description: "will-change: all promotes every property to its own GPU layer \u2014 huge texture memory.",
1208
+ references: ["reference/anti-patterns.md#BAN-13"],
1209
+ severity: "warn",
1210
+ pattern: PATTERN,
1211
+ matcher
1212
+ };
1213
+ }
1214
+ });
1215
+
1216
+ // scripts/lib/detect/rules/index.cjs
1217
+ var require_rules = __commonJS({
1218
+ "scripts/lib/detect/rules/index.cjs"(exports2, module2) {
1219
+ "use strict";
1220
+ var r0 = require_ban_01();
1221
+ var r1 = require_ban_02();
1222
+ var r2 = require_ban_03();
1223
+ var r3 = require_ban_05();
1224
+ var r4 = require_ban_06();
1225
+ var r5 = require_ban_07();
1226
+ var r6 = require_ban_08();
1227
+ var r7 = require_ban_09();
1228
+ var r8 = require_ban_11();
1229
+ var r9 = require_ban_12();
1230
+ var r10 = require_ban_13();
1231
+ var RULES = [r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10];
1232
+ var EXEMPT = Object.freeze(["BAN-04", "BAN-10"]);
1233
+ module2.exports = { RULES, EXEMPT };
1234
+ }
1235
+ });
1236
+
1237
+ // scripts/lib/detect/engine.cjs
1238
+ var require_engine = __commonJS({
1239
+ "scripts/lib/detect/engine.cjs"(exports2, module2) {
1240
+ "use strict";
1241
+ var fs = require("node:fs");
1242
+ var path = require("node:path");
1243
+ var { RULES, EXEMPT } = require_rules();
1244
+ var SCANNABLE_EXT = /* @__PURE__ */ new Set([".html", ".htm", ".css", ".scss", ".jsx", ".tsx", ".js", ".ts", ".vue", ".svelte"]);
1245
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".design", ".planning"]);
1246
+ function walk(root) {
1247
+ const out = [];
1248
+ if (!fs.existsSync(root)) return out;
1249
+ const st = fs.statSync(root);
1250
+ if (st.isFile()) {
1251
+ if (SCANNABLE_EXT.has(path.extname(root).toLowerCase())) out.push(root);
1252
+ return out;
1253
+ }
1254
+ const stack = [root];
1255
+ while (stack.length) {
1256
+ const dir = stack.pop();
1257
+ let entries;
1258
+ try {
1259
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1260
+ } catch {
1261
+ continue;
1262
+ }
1263
+ for (const e of entries) {
1264
+ const full = path.join(dir, e.name);
1265
+ if (e.isDirectory()) {
1266
+ if (!SKIP_DIRS.has(e.name)) stack.push(full);
1267
+ } else if (e.isFile() && SCANNABLE_EXT.has(path.extname(e.name).toLowerCase())) out.push(full);
1268
+ }
1269
+ }
1270
+ return out;
1271
+ }
1272
+ function selectRules(ruleId) {
1273
+ if (!ruleId) return RULES;
1274
+ const id = String(ruleId).toUpperCase();
1275
+ return RULES.filter((r) => r.id === id);
1276
+ }
1277
+ function scanContent(content, ctx, rules) {
1278
+ const findings = [];
1279
+ for (const rule of rules) {
1280
+ let hits = [];
1281
+ try {
1282
+ hits = rule.matcher({ content, ext: ctx.ext, path: ctx.path }) || [];
1283
+ } catch {
1284
+ hits = [];
1285
+ }
1286
+ for (const h of hits) {
1287
+ findings.push({
1288
+ ruleId: rule.id,
1289
+ category: rule.category,
1290
+ name: rule.name,
1291
+ severity: rule.severity,
1292
+ file: ctx.path,
1293
+ line: h.line,
1294
+ column: h.column,
1295
+ match: h.match,
1296
+ references: rule.references
1297
+ });
1298
+ }
1299
+ }
1300
+ return findings;
1301
+ }
1302
+ function run6(root, opts) {
1303
+ const o = opts || {};
1304
+ const rules = selectRules(o.ruleId);
1305
+ const cwd = o.cwd || process.cwd();
1306
+ const files = walk(root);
1307
+ const findings = [];
1308
+ let errors = 0;
1309
+ for (const abs of files) {
1310
+ let content;
1311
+ try {
1312
+ content = fs.readFileSync(abs, "utf8");
1313
+ } catch {
1314
+ errors++;
1315
+ continue;
1316
+ }
1317
+ const rel = path.relative(cwd, abs).split(path.sep).join("/");
1318
+ findings.push(...scanContent(content, { path: rel || abs, ext: path.extname(abs).toLowerCase() }, rules));
1319
+ }
1320
+ findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column || a.ruleId.localeCompare(b.ruleId));
1321
+ return { findings, filesScanned: files.length, errors, rules: rules.length };
1322
+ }
1323
+ module2.exports = { run: run6, walk, scanContent, selectRules, RULES, EXEMPT, SCANNABLE_EXT, SKIP_DIRS };
1324
+ }
1325
+ });
1326
+
1327
+ // scripts/lib/detect/stack.cjs
1328
+ var require_stack = __commonJS({
1329
+ "scripts/lib/detect/stack.cjs"(exports2, module2) {
1330
+ "use strict";
1331
+ var fs = require("node:fs");
1332
+ var path = require("node:path");
1333
+ var { walk, SKIP_DIRS } = require_engine();
1334
+ function readDeps(root) {
1335
+ const pkgPath = path.basename(String(root || "")) === "package.json" ? root : path.join(root || ".", "package.json");
1336
+ let raw;
1337
+ try {
1338
+ raw = fs.readFileSync(pkgPath, "utf8");
1339
+ } catch {
1340
+ return { deps: {}, present: false, error: null };
1341
+ }
1342
+ let pkg;
1343
+ try {
1344
+ pkg = JSON.parse(raw);
1345
+ } catch (e) {
1346
+ return { deps: {}, present: true, error: "package.json is not valid JSON" + (e && e.message ? `: ${e.message}` : "") };
1347
+ }
1348
+ if (!pkg || typeof pkg !== "object") {
1349
+ return { deps: {}, present: true, error: "package.json did not parse to an object" };
1350
+ }
1351
+ const deps = {};
1352
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
1353
+ const m = pkg[field];
1354
+ if (m && typeof m === "object" && !Array.isArray(m)) {
1355
+ for (const k of Object.keys(m)) deps[k] = m[k];
1356
+ }
1357
+ }
1358
+ return { deps, present: true, error: null };
1359
+ }
1360
+ function hasDep(deps, name) {
1361
+ return Object.prototype.hasOwnProperty.call(deps, name);
1362
+ }
1363
+ function hasDepPrefix(deps, prefix) {
1364
+ for (const k of Object.keys(deps)) {
1365
+ if (k === prefix || k.startsWith(prefix)) return true;
1366
+ }
1367
+ return false;
1368
+ }
1369
+ function hasTopLevelFile(root, names) {
1370
+ let entries;
1371
+ try {
1372
+ entries = fs.readdirSync(root, { withFileTypes: true });
1373
+ } catch {
1374
+ return false;
1375
+ }
1376
+ const set = new Set(entries.filter((e) => e.isFile()).map((e) => e.name));
1377
+ for (const n of names) if (set.has(n)) return true;
1378
+ return false;
1379
+ }
1380
+ function hasTopLevelConfig(root, stem) {
1381
+ let entries;
1382
+ try {
1383
+ entries = fs.readdirSync(root, { withFileTypes: true });
1384
+ } catch {
1385
+ return false;
1386
+ }
1387
+ const prefix = stem + ".";
1388
+ for (const e of entries) {
1389
+ if (e.isFile() && e.name.startsWith(prefix)) return true;
1390
+ }
1391
+ return false;
1392
+ }
1393
+ function hasTopLevelDir(root, name) {
1394
+ try {
1395
+ const st = fs.statSync(path.join(root, name));
1396
+ return st.isDirectory();
1397
+ } catch {
1398
+ return false;
1399
+ }
1400
+ }
1401
+ function findFileMatching(root, re) {
1402
+ let files;
1403
+ try {
1404
+ files = walk(root);
1405
+ } catch {
1406
+ return null;
1407
+ }
1408
+ for (const abs of files) {
1409
+ if (re.test(path.basename(abs))) return relish(root, abs);
1410
+ }
1411
+ return null;
1412
+ }
1413
+ function findContentMatching(root, re, fileFilter) {
1414
+ let files;
1415
+ try {
1416
+ files = walk(root);
1417
+ } catch {
1418
+ return null;
1419
+ }
1420
+ for (const abs of files) {
1421
+ if (fileFilter && !fileFilter(abs)) continue;
1422
+ let text;
1423
+ try {
1424
+ text = fs.readFileSync(abs, "utf8");
1425
+ } catch {
1426
+ continue;
1427
+ }
1428
+ const m = re.exec(text);
1429
+ if (m) return { file: relish(root, abs), match: m[0] };
1430
+ }
1431
+ return null;
1432
+ }
1433
+ function relish(root, abs) {
1434
+ const rel = path.relative(root, abs);
1435
+ return (rel || abs).split(path.sep).join("/");
1436
+ }
1437
+ var DS_PROBES = [
1438
+ {
1439
+ id: "shadcn",
1440
+ // shadcn is a tailwind super-set: detect it FIRST so a shadcn project (which also
1441
+ // ships tailwind) is labeled shadcn rather than the more generic tailwind.
1442
+ detect(root, deps) {
1443
+ const hasComponentsJson = hasTopLevelFile(root, ["components.json"]);
1444
+ const cnHit = hasComponentsJson ? null : findContentMatching(root, /\bcn\s*\(/, (abs) => /utils\.(t|j)sx?$/.test(abs.split(path.sep).join("/")));
1445
+ if (hasComponentsJson) return { ev: "components.json present (shadcn/ui)" };
1446
+ if (cnHit) return { ev: `cn() helper in ${cnHit.file} (shadcn/ui)` };
1447
+ return null;
1448
+ }
1449
+ },
1450
+ {
1451
+ id: "tailwind",
1452
+ detect(root, deps) {
1453
+ if (hasDep(deps, "tailwindcss")) return { ev: "tailwindcss in dependencies" };
1454
+ if (hasTopLevelConfig(root, "tailwind.config")) return { ev: "tailwind.config.* present" };
1455
+ const themeHit = findContentMatching(root, /@theme\b/, (abs) => /\.css$/.test(abs));
1456
+ if (themeHit) return { ev: `@theme directive in ${themeHit.file} (tailwind v4)` };
1457
+ return null;
1458
+ }
1459
+ },
1460
+ {
1461
+ id: "radix-themes",
1462
+ detect(root, deps) {
1463
+ if (hasDep(deps, "@radix-ui/themes")) return { ev: "@radix-ui/themes in dependencies" };
1464
+ return null;
1465
+ }
1466
+ },
1467
+ {
1468
+ id: "mui",
1469
+ detect(root, deps) {
1470
+ if (hasDep(deps, "@mui/material")) return { ev: "@mui/material in dependencies" };
1471
+ return null;
1472
+ }
1473
+ },
1474
+ {
1475
+ id: "chakra",
1476
+ detect(root, deps) {
1477
+ if (hasDep(deps, "@chakra-ui/react")) return { ev: "@chakra-ui/react in dependencies" };
1478
+ return null;
1479
+ }
1480
+ },
1481
+ {
1482
+ id: "vanilla-extract",
1483
+ detect(root, deps) {
1484
+ if (hasDep(deps, "@vanilla-extract/css")) return { ev: "@vanilla-extract/css in dependencies" };
1485
+ const cssTs = findFileMatching(root, /\.css\.ts$/);
1486
+ if (cssTs) return { ev: `*.css.ts file ${cssTs} (vanilla-extract)` };
1487
+ return null;
1488
+ }
1489
+ },
1490
+ {
1491
+ id: "styled-components",
1492
+ detect(root, deps) {
1493
+ if (hasDep(deps, "styled-components")) return { ev: "styled-components in dependencies" };
1494
+ return null;
1495
+ }
1496
+ },
1497
+ {
1498
+ id: "css-modules",
1499
+ // Weakest signal (a plain file pattern, no dep). Last so any explicit DS wins over it.
1500
+ detect(root, deps) {
1501
+ const mod = findFileMatching(root, /\.module\.css$/);
1502
+ if (mod) return { ev: `*.module.css file ${mod} (CSS Modules)` };
1503
+ return null;
1504
+ }
1505
+ }
1506
+ ];
1507
+ function detectDs(root, deps) {
1508
+ for (const probe of DS_PROBES) {
1509
+ let res = null;
1510
+ try {
1511
+ res = probe.detect(root, deps);
1512
+ } catch {
1513
+ res = null;
1514
+ }
1515
+ if (res) return { ds: probe.id, evidence: res.ev };
1516
+ }
1517
+ return { ds: null, evidence: "no design-system signal (no known DS dep, config file, or file pattern)" };
1518
+ }
1519
+ function detectFramework(root, deps) {
1520
+ if (hasDep(deps, "next")) {
1521
+ const router = hasTopLevelDir(root, "app") ? "app-router" : hasTopLevelDir(root, "pages") ? "pages-router" : hasTopLevelDir(root, "src") && hasTopLevelDir(path.join(root, "src"), "app") ? "app-router (src/)" : "router undetermined";
1522
+ return { framework: "nextjs", evidence: `next in dependencies (${router})` };
1523
+ }
1524
+ if (hasDepPrefix(deps, "@remix-run/")) {
1525
+ return { framework: "remix", evidence: "@remix-run/* in dependencies" };
1526
+ }
1527
+ if (hasDep(deps, "astro") || hasTopLevelConfig(root, "astro.config")) {
1528
+ return { framework: "astro", evidence: hasDep(deps, "astro") ? "astro in dependencies" : "astro.config.* present" };
1529
+ }
1530
+ if (hasDep(deps, "@sveltejs/kit") || hasTopLevelConfig(root, "svelte.config")) {
1531
+ return {
1532
+ framework: "sveltekit",
1533
+ evidence: hasDep(deps, "@sveltejs/kit") ? "@sveltejs/kit in dependencies" : "svelte.config.* present"
1534
+ };
1535
+ }
1536
+ if (hasDep(deps, "storybook") || hasDepPrefix(deps, "@storybook/")) {
1537
+ return { framework: "storybook", evidence: "storybook / @storybook/* in dependencies" };
1538
+ }
1539
+ if ((hasDep(deps, "vite") || hasTopLevelConfig(root, "vite.config")) && (hasDep(deps, "react") || hasDep(deps, "react-dom"))) {
1540
+ return { framework: "vite-react", evidence: "vite + react in dependencies (no next/remix/astro/sveltekit)" };
1541
+ }
1542
+ return { framework: null, evidence: "no framework signal (no next/remix/vite-react/astro/sveltekit/storybook)" };
1543
+ }
1544
+ var MOTION_PROBES = [
1545
+ {
1546
+ id: "framer-motion",
1547
+ detect(deps) {
1548
+ if (hasDep(deps, "framer-motion")) return "framer-motion in dependencies";
1549
+ if (hasDep(deps, "motion")) return "motion in dependencies (framer-motion v11+)";
1550
+ return null;
1551
+ }
1552
+ },
1553
+ {
1554
+ id: "gsap",
1555
+ detect(deps) {
1556
+ if (hasDep(deps, "gsap")) return "gsap in dependencies";
1557
+ return null;
1558
+ }
1559
+ },
1560
+ {
1561
+ id: "motion-one",
1562
+ detect(deps) {
1563
+ if (hasDepPrefix(deps, "@motionone/")) return "@motionone/* in dependencies";
1564
+ return null;
1565
+ }
1566
+ },
1567
+ {
1568
+ id: "react-spring",
1569
+ detect(deps) {
1570
+ if (hasDep(deps, "react-spring") || hasDepPrefix(deps, "@react-spring/")) return "react-spring / @react-spring/* in dependencies";
1571
+ return null;
1572
+ }
1573
+ }
1574
+ ];
1575
+ function detectMotion(deps) {
1576
+ const libs = [];
1577
+ const evidence = [];
1578
+ for (const probe of MOTION_PROBES) {
1579
+ let ev = null;
1580
+ try {
1581
+ ev = probe.detect(deps);
1582
+ } catch {
1583
+ ev = null;
1584
+ }
1585
+ if (ev) {
1586
+ libs.push(probe.id);
1587
+ evidence.push(`${probe.id}: ${ev}`);
1588
+ }
1589
+ }
1590
+ return { motion_libs: libs, evidence };
1591
+ }
1592
+ function detectStack2(root) {
1593
+ const dir = root || process.cwd();
1594
+ const evidence = {};
1595
+ let exists = false;
1596
+ try {
1597
+ exists = fs.existsSync(dir);
1598
+ } catch {
1599
+ exists = false;
1600
+ }
1601
+ if (!exists) {
1602
+ return {
1603
+ ds: null,
1604
+ framework: null,
1605
+ motion_libs: [],
1606
+ evidence: { note: `root path does not exist: ${dir}` }
1607
+ };
1608
+ }
1609
+ const { deps, present, error } = readDeps(dir);
1610
+ if (!present) evidence.note = "no package.json at root \u2014 relying on config-file + file-pattern probes only";
1611
+ else if (error) evidence.note = `${error} \u2014 relying on config-file + file-pattern probes only`;
1612
+ let ds = null;
1613
+ let framework = null;
1614
+ let motion_libs = [];
1615
+ try {
1616
+ const dsr = detectDs(dir, deps);
1617
+ ds = dsr.ds;
1618
+ evidence.ds = dsr.evidence;
1619
+ } catch (e) {
1620
+ evidence.ds = "ds detection error: " + (e && e.message ? e.message : String(e));
1621
+ }
1622
+ try {
1623
+ const fwr = detectFramework(dir, deps);
1624
+ framework = fwr.framework;
1625
+ evidence.framework = fwr.evidence;
1626
+ } catch (e) {
1627
+ evidence.framework = "framework detection error: " + (e && e.message ? e.message : String(e));
1628
+ }
1629
+ try {
1630
+ const mr = detectMotion(deps);
1631
+ motion_libs = mr.motion_libs;
1632
+ evidence.motion = mr.evidence;
1633
+ } catch (e) {
1634
+ evidence.motion = ["motion detection error: " + (e && e.message ? e.message : String(e))];
1635
+ }
1636
+ return { ds, framework, motion_libs, evidence };
1637
+ }
1638
+ var HELP = `gdd stack detection \u2014 fingerprint a project's design-system / framework / motion stack.
1639
+
1640
+ Usage:
1641
+ detect-stack [root] [options]
1642
+
1643
+ Arguments:
1644
+ [root] Project directory to scan (defaults to the current directory).
1645
+
1646
+ Options:
1647
+ --json Machine-readable JSON (default).
1648
+ --pretty Pretty-printed human summary.
1649
+ -h, --help This help.
1650
+
1651
+ Always exits 0 \u2014 an undetected stack is reported, not an error.`;
1652
+ function parseArgs2(argv) {
1653
+ const opts = { root: null, json: true, pretty: false, help: false };
1654
+ for (let i = 0; i < argv.length; i++) {
1655
+ const a = argv[i];
1656
+ if (a === "--json") opts.json = true;
1657
+ else if (a === "--pretty") {
1658
+ opts.pretty = true;
1659
+ opts.json = false;
1660
+ } else if (a === "-h" || a === "--help") opts.help = true;
1661
+ else if (!a.startsWith("-") && opts.root === null) opts.root = a;
1662
+ }
1663
+ return opts;
1664
+ }
1665
+ function renderPretty(res) {
1666
+ const lines = [];
1667
+ lines.push("gdd stack:");
1668
+ lines.push(` design-system : ${res.ds || "(none detected)"}`);
1669
+ lines.push(` framework : ${res.framework || "(none detected)"}`);
1670
+ lines.push(` motion : ${res.motion_libs.length ? res.motion_libs.join(", ") : "(none detected)"}`);
1671
+ if (res.evidence && res.evidence.note) lines.push(` note : ${res.evidence.note}`);
1672
+ return lines.join("\n");
1673
+ }
1674
+ function main2(argv, io) {
1675
+ const o = io || {};
1676
+ const log = o.log || ((s) => process.stdout.write(s + "\n"));
1677
+ const opts = parseArgs2(argv);
1678
+ if (opts.help) {
1679
+ log(HELP);
1680
+ return 0;
1681
+ }
1682
+ const root = opts.root || o.cwd || process.cwd();
1683
+ const res = detectStack2(root);
1684
+ if (opts.pretty) log(renderPretty(res));
1685
+ else log(JSON.stringify(res, null, 2));
1686
+ return 0;
1687
+ }
1688
+ module2.exports = {
1689
+ detectStack: detectStack2,
1690
+ main: main2,
1691
+ // internals exported for unit reuse / introspection (kept stable for executors B & F).
1692
+ readDeps,
1693
+ hasDep,
1694
+ hasDepPrefix,
1695
+ detectDs,
1696
+ detectFramework,
1697
+ detectMotion,
1698
+ parseArgs: parseArgs2,
1699
+ HELP,
1700
+ SKIP_DIRS
1701
+ };
1702
+ if (require.main === module2) process.exit(main2(process.argv.slice(2)));
1703
+ }
1704
+ });
1705
+
1706
+ // scripts/lib/mapper-spawn.cjs
1707
+ var require_mapper_spawn = __commonJS({
1708
+ "scripts/lib/mapper-spawn.cjs"(exports2, module2) {
1709
+ "use strict";
1710
+ var fs = require("node:fs");
1711
+ var path = require("node:path");
1712
+ var BLOCK_HEADER = "## Stack-specific guidance";
1713
+ var ADDENDUM_SEPARATOR = "\n\n---\n\n";
1714
+ var CATEGORY_ORDER = ["system", "framework", "motion"];
1715
+ function normKey(value) {
1716
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
1717
+ }
1718
+ function baseNameNoExt(p) {
1719
+ if (typeof p !== "string" || p.length === 0) return "";
1720
+ const tail2 = p.replace(/\\/g, "/").split("/").pop() || "";
1721
+ return tail2.replace(/\.md$/i, "");
1722
+ }
1723
+ function classifyEntry(entry) {
1724
+ let category = null;
1725
+ const explicitKind = normKey(entry.kind || entry.category);
1726
+ if (explicitKind === "system" || explicitKind === "ds" || explicitKind === "design-system") {
1727
+ category = "system";
1728
+ } else if (explicitKind === "framework") {
1729
+ category = "framework";
1730
+ } else if (explicitKind === "motion") {
1731
+ category = "motion";
1732
+ } else if (typeof entry.path === "string") {
1733
+ const p = entry.path.replace(/\\/g, "/");
1734
+ if (/(^|\/)reference\/systems\//i.test(p) || /(^|\/)systems\//i.test(p)) category = "system";
1735
+ else if (/(^|\/)reference\/frameworks\//i.test(p) || /(^|\/)frameworks\//i.test(p)) category = "framework";
1736
+ else if (/(^|\/)reference\/motion\//i.test(p) || /(^|\/)motion\//i.test(p)) category = "motion";
1737
+ }
1738
+ let key = normKey(entry.stack);
1739
+ if (key === "") key = normKey(baseNameNoExt(entry.path));
1740
+ if (key === "") {
1741
+ const nameParts = normKey(entry.name).split("-").filter(Boolean);
1742
+ key = nameParts.length > 0 ? nameParts[nameParts.length - 1] : "";
1743
+ }
1744
+ return { category, key };
1745
+ }
1746
+ function composesInto(entry, mapperName) {
1747
+ if (!entry || entry.type !== "stack-addendum") return false;
1748
+ const list = entry.composes_into;
1749
+ if (!Array.isArray(list)) return false;
1750
+ return list.some((m) => normKey(m) === normKey(mapperName));
1751
+ }
1752
+ function readAddendumBody(entry, refDir) {
1753
+ if (typeof entry.path !== "string" || entry.path.length === 0) return null;
1754
+ const rel = entry.path.replace(/\\/g, "/");
1755
+ const candidates = [];
1756
+ if (path.isAbsolute(rel)) {
1757
+ candidates.push(rel);
1758
+ } else {
1759
+ candidates.push(path.resolve(refDir, rel));
1760
+ const stripped = rel.replace(/^reference\//i, "");
1761
+ if (stripped !== rel) candidates.push(path.resolve(refDir, stripped));
1762
+ }
1763
+ for (const abs of candidates) {
1764
+ let body;
1765
+ try {
1766
+ body = fs.readFileSync(abs, "utf8");
1767
+ } catch {
1768
+ continue;
1769
+ }
1770
+ const trimmed = body.replace(/\s+$/, "").replace(/^/, "");
1771
+ if (trimmed.trim().length > 0) return trimmed;
1772
+ }
1773
+ return null;
1774
+ }
1775
+ function composeAddendums(mapperName, stack, opts) {
1776
+ const used = [];
1777
+ const missing = [];
1778
+ const empty = () => ({ block: "", used, missing });
1779
+ const o = opts || {};
1780
+ const cap = Number.isInteger(o.cap) && o.cap >= 0 ? o.cap : 3;
1781
+ const refDir = typeof o.refDir === "string" && o.refDir.length > 0 ? o.refDir : process.cwd();
1782
+ if (!stack || typeof stack !== "object" || cap === 0) return empty();
1783
+ const registry = o.registry;
1784
+ const entries = registry && Array.isArray(registry.entries) ? registry.entries : [];
1785
+ const detected = {
1786
+ system: normKey(stack.ds),
1787
+ framework: normKey(stack.framework),
1788
+ // motion is a list; take the first non-empty entry (cap allows only one
1789
+ // motion addendum, so the leading detected lib wins).
1790
+ motion: Array.isArray(stack.motion_libs) ? normKey(stack.motion_libs.find((m) => normKey(m) !== "")) : ""
1791
+ };
1792
+ const candidates = [];
1793
+ for (const entry of entries) {
1794
+ if (!composesInto(entry, mapperName)) continue;
1795
+ const { category, key } = classifyEntry(entry);
1796
+ if (category === null || key === "") continue;
1797
+ candidates.push({ entry, category, key });
1798
+ }
1799
+ const bodies = [];
1800
+ for (const category of CATEGORY_ORDER) {
1801
+ if (used.length >= cap) break;
1802
+ const want = detected[category];
1803
+ if (want === "") continue;
1804
+ const hit = candidates.find((c) => c.category === category && c.key === want);
1805
+ if (!hit) {
1806
+ missing.push(want);
1807
+ continue;
1808
+ }
1809
+ const body = readAddendumBody(hit.entry, refDir);
1810
+ if (body === null) {
1811
+ missing.push(want);
1812
+ continue;
1813
+ }
1814
+ bodies.push(body);
1815
+ used.push(typeof hit.entry.name === "string" && hit.entry.name.length > 0 ? hit.entry.name : hit.key);
1816
+ }
1817
+ if (bodies.length === 0) return empty();
1818
+ const block = `${BLOCK_HEADER}
1819
+
1820
+ ${bodies.join(ADDENDUM_SEPARATOR)}`;
1821
+ return { block, used, missing };
1822
+ }
1823
+ function applyAddendums2(spec, stack, opts) {
1824
+ if (!spec || typeof spec !== "object") {
1825
+ return { spec, block: "", used: [], missing: [] };
1826
+ }
1827
+ const mapperName = typeof spec.name === "string" ? spec.name : "";
1828
+ const { block, used, missing } = composeAddendums(mapperName, stack, opts);
1829
+ if (block !== "") {
1830
+ const base = typeof spec.prompt === "string" ? spec.prompt : "";
1831
+ spec.prompt = base === "" ? block : `${base}
1832
+
1833
+ ${block}`;
1834
+ }
1835
+ return { spec, block, used, missing };
1836
+ }
1837
+ module2.exports = {
1838
+ composeAddendums,
1839
+ applyAddendums: applyAddendums2,
1840
+ // Exported for unit-level coverage + reuse by the runner wiring (executor F).
1841
+ classifyEntry,
1842
+ composesInto,
1843
+ BLOCK_HEADER
1844
+ };
1845
+ }
1846
+ });
1847
+
1848
+ // scripts/lib/reference-registry.cjs
1849
+ var require_reference_registry = __commonJS({
1850
+ "scripts/lib/reference-registry.cjs"(exports2, module2) {
1851
+ "use strict";
1852
+ var fs = require("fs");
1853
+ var path = require("path");
1854
+ var REPO_ROOT2 = path.resolve(__dirname, "..", "..");
1855
+ var DEFAULT_REGISTRY_PATH = path.join(REPO_ROOT2, "reference", "registry.json");
1856
+ var _cache = null;
1857
+ var _cachePath = null;
1858
+ function loadRegistry({ cwd } = {}) {
1859
+ const p = cwd ? path.join(cwd, "reference", "registry.json") : DEFAULT_REGISTRY_PATH;
1860
+ if (_cache && _cachePath === p) return _cache;
1861
+ _cachePath = p;
1862
+ _cache = JSON.parse(fs.readFileSync(p, "utf8"));
1863
+ return _cache;
1864
+ }
1865
+ function list({ type, cwd } = {}) {
1866
+ const reg = loadRegistry({ cwd });
1867
+ if (!type) return reg.entries.slice();
1868
+ return reg.entries.filter((e) => e.type === type);
1869
+ }
1870
+ function find(name, { cwd } = {}) {
1871
+ const reg = loadRegistry({ cwd });
1872
+ return reg.entries.find((e) => e.name === name) || null;
1873
+ }
1874
+ function validateRegistry({ cwd } = {}) {
1875
+ const root = cwd || REPO_ROOT2;
1876
+ const refDir = path.join(root, "reference");
1877
+ const reg = (() => {
1878
+ try {
1879
+ return JSON.parse(fs.readFileSync(path.join(refDir, "registry.json"), "utf8"));
1880
+ } catch {
1881
+ return { entries: [] };
1882
+ }
1883
+ })();
1884
+ const onDisk = /* @__PURE__ */ new Set();
1885
+ for (const leaf of walk(refDir)) {
1886
+ const rel = path.relative(root, leaf).replace(/\\/g, "/");
1887
+ if (rel === "reference/registry.json") continue;
1888
+ if (rel.endsWith(".schema.json")) continue;
1889
+ if (rel.startsWith("reference/schemas/")) continue;
1890
+ if (rel.startsWith("reference/data/")) continue;
1891
+ if (!/\.(md|json)$/.test(rel)) continue;
1892
+ onDisk.add(rel);
1893
+ }
1894
+ const registryPaths = new Set(reg.entries.map((e) => e.path));
1895
+ const missingInRegistry = [...onDisk].filter((p) => !registryPaths.has(p)).sort();
1896
+ const danglingInRegistry = reg.entries.filter((e) => !fs.existsSync(path.join(root, e.path))).map((e) => ({ name: e.name, path: e.path }));
1897
+ const nameCount = {}, pathCount = {};
1898
+ for (const e of reg.entries) {
1899
+ nameCount[e.name] = (nameCount[e.name] || 0) + 1;
1900
+ pathCount[e.path] = (pathCount[e.path] || 0) + 1;
1901
+ }
1902
+ const duplicates = [];
1903
+ for (const [k, v] of Object.entries(nameCount)) if (v > 1) duplicates.push({ kind: "name", key: k, count: v });
1904
+ for (const [k, v] of Object.entries(pathCount)) if (v > 1) duplicates.push({ kind: "path", key: k, count: v });
1905
+ return {
1906
+ ok: missingInRegistry.length === 0 && danglingInRegistry.length === 0 && duplicates.length === 0,
1907
+ missingInRegistry,
1908
+ danglingInRegistry,
1909
+ duplicates
1910
+ };
1911
+ }
1912
+ function* walk(dir) {
1913
+ let entries;
1914
+ try {
1915
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1916
+ } catch {
1917
+ return;
1918
+ }
1919
+ for (const e of entries) {
1920
+ const full = path.join(dir, e.name);
1921
+ if (e.isDirectory()) yield* walk(full);
1922
+ else if (e.isFile()) yield full;
1923
+ }
1924
+ }
1925
+ module2.exports = { list, find, validateRegistry, loadRegistry };
1926
+ }
1927
+ });
1928
+
853
1929
  // sdk/cli/index.ts
854
1930
  var index_exports = {};
855
1931
  __export(index_exports, {
@@ -5884,6 +6960,8 @@ var import_node_path14 = require("node:path");
5884
6960
  var import_node_path12 = require("node:path");
5885
6961
  var import_concurrency_tuner = __toESM(require_concurrency_tuner());
5886
6962
  var import_incremental_discover = __toESM(require_incremental_discover());
6963
+ var import_stack = __toESM(require_stack());
6964
+ var import_mapper_spawn = __toESM(require_mapper_spawn());
5887
6965
 
5888
6966
  // scripts/lib/explore-parallel-runner/mappers.ts
5889
6967
  var import_node_fs12 = require("node:fs");
@@ -6221,11 +7299,67 @@ var DEFAULT_MAPPERS = Object.freeze([
6221
7299
  prompt: "Describe z-order, focal points, and attention grammar. Output to .design/map/visual-hierarchy.md \u2014 one section per surface describing layering, emphasis, and scan path."
6222
7300
  })
6223
7301
  ]);
7302
+ function agentNameOf(spec) {
7303
+ const base = spec.agentPath.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "");
7304
+ return base && base.length > 0 ? base : spec.name;
7305
+ }
7306
+ function composeMapperSpecs(specs, cwd, addendumOpts, logger) {
7307
+ const missingByMapper = {};
7308
+ const opt = addendumOpts ?? {};
7309
+ if (opt.enabled === false) return { specs, missingByMapper };
7310
+ try {
7311
+ const root = typeof opt.root === "string" ? opt.root : cwd;
7312
+ const detect = typeof opt.detectStack === "function" ? opt.detectStack : import_stack.detectStack;
7313
+ const stack = detect(root);
7314
+ let registry = opt.registry;
7315
+ const refDir = typeof opt.refDir === "string" ? opt.refDir : (0, import_node_path12.resolve)(cwd, "reference");
7316
+ if (registry === void 0) {
7317
+ try {
7318
+ const { loadRegistry } = require_reference_registry();
7319
+ registry = loadRegistry({ cwd });
7320
+ } catch {
7321
+ registry = void 0;
7322
+ }
7323
+ }
7324
+ let anyChanged = false;
7325
+ const recomposed = specs.map((spec) => {
7326
+ const agentName = agentNameOf(spec);
7327
+ const carrier = { name: agentName, prompt: spec.prompt };
7328
+ const { block, missing } = (0, import_mapper_spawn.applyAddendums)(carrier, stack, {
7329
+ registry,
7330
+ refDir
7331
+ });
7332
+ if (Array.isArray(missing) && missing.length > 0) {
7333
+ missingByMapper[agentName] = missing;
7334
+ }
7335
+ if (block && block.length > 0 && carrier.prompt !== spec.prompt) {
7336
+ anyChanged = true;
7337
+ return Object.freeze({ ...spec, prompt: carrier.prompt });
7338
+ }
7339
+ return spec;
7340
+ });
7341
+ if (anyChanged) {
7342
+ logger.info("explore.runner.addendums_composed", {
7343
+ mappers_augmented: recomposed.filter((s, i) => s !== specs[i]).length,
7344
+ ds: stack && stack.ds ? stack.ds : null,
7345
+ framework: stack && stack.framework ? stack.framework : null,
7346
+ motion_libs: stack && Array.isArray(stack.motion_libs) ? stack.motion_libs.length : 0
7347
+ });
7348
+ return { specs: Object.freeze(recomposed), missingByMapper };
7349
+ }
7350
+ return { specs, missingByMapper };
7351
+ } catch (err) {
7352
+ const message = err instanceof Error ? err.message : String(err);
7353
+ logger.warn("explore.runner.addendums_failed", { message });
7354
+ return { specs, missingByMapper };
7355
+ }
7356
+ }
6224
7357
  async function run3(opts) {
6225
- const specs = opts.mappers ?? DEFAULT_MAPPERS;
7358
+ const baseSpecs = opts.mappers ?? DEFAULT_MAPPERS;
6226
7359
  const cwd = opts.cwd ?? process.cwd();
6227
7360
  const concurrency = opts.concurrency ?? (0, import_concurrency_tuner.resolveConcurrency)();
6228
7361
  const logger = getLogger().child("explore.runner");
7362
+ const { specs } = composeMapperSpecs(baseSpecs, cwd, opts.addendums, logger);
6229
7363
  const outputPath = (0, import_node_path12.resolve)(cwd, ".design/DESIGN-PATTERNS.md");
6230
7364
  let batching = void 0;
6231
7365
  if (opts.incremental && opts.incremental.graph !== void 0 && opts.incremental.graph !== null) {
@@ -8448,6 +9582,252 @@ ${BUILD_USAGE}`);
8448
9582
  return typeof res.status === "number" ? res.status : 3;
8449
9583
  }
8450
9584
 
9585
+ // sdk/cli/commands/dashboard.ts
9586
+ var import_node_child_process2 = require("node:child_process");
9587
+ var import_node_module4 = require("node:module");
9588
+ var import_node_http = require("node:http");
9589
+ var import_node_fs23 = require("node:fs");
9590
+ var import_node_path21 = require("node:path");
9591
+ var DASHBOARD_FLAGS = [
9592
+ ...COMMON_FLAGS,
9593
+ { name: "web", type: "boolean", default: false },
9594
+ { name: "once", type: "boolean", default: false },
9595
+ { name: "no-open", type: "boolean", default: false },
9596
+ { name: "root", type: "string" }
9597
+ ];
9598
+ var DASHBOARD_USAGE = `gdd-sdk dashboard [flags]
9599
+
9600
+ Open the GDD dashboard. Read-only. Dep-free (Node builtins only).
9601
+
9602
+ Default (no --web) launches the terminal UI (bin/gdd-dashboard). With --web it
9603
+ emits a self-contained HTML graph of the design-context, serves it on an ephemeral
9604
+ local port, and opens your browser.
9605
+
9606
+ Flags:
9607
+ --web Web mode: build + serve the design-context graph as HTML.
9608
+ --once Write the HTML to .design/dashboard.html and exit (no server). Implies --web.
9609
+ --no-open Web mode: serve + print the URL but do NOT open a browser (headless/CI).
9610
+ --root <dir> Project root to read .design/ from (default: GDD_PROJECT_ROOT or walk-up).
9611
+ -h, --help Show this help.
9612
+
9613
+ Exit codes: 0 ok \xB7 3 arg error / TUI not found \xB7 (TUI exit code forwarded otherwise)
9614
+ `;
9615
+ function anchorDirs() {
9616
+ const out = [];
9617
+ const entry = process.argv[1];
9618
+ if (typeof entry === "string" && entry.length > 0) out.push((0, import_node_path21.dirname)(entry));
9619
+ out.push(process.cwd());
9620
+ return out;
9621
+ }
9622
+ function climbToMarker(startDir) {
9623
+ const req = (0, import_node_module4.createRequire)((0, import_node_path21.join)(startDir, "noop.js"));
9624
+ let dir = startDir;
9625
+ let firstWithPkg = null;
9626
+ for (let i = 0; i < 12; i++) {
9627
+ const pkgPath = (0, import_node_path21.join)(dir, "package.json");
9628
+ if ((0, import_node_fs23.existsSync)(pkgPath)) {
9629
+ if (firstWithPkg === null) firstWithPkg = dir;
9630
+ try {
9631
+ const pkg = req(pkgPath);
9632
+ if (pkg && pkg.name === "get-design-done") return { root: dir, firstWithPkg };
9633
+ } catch {
9634
+ }
9635
+ }
9636
+ const parent = (0, import_node_path21.dirname)(dir);
9637
+ if (parent === dir) break;
9638
+ dir = parent;
9639
+ }
9640
+ return { root: null, firstWithPkg };
9641
+ }
9642
+ var _cachedPkgRoot = null;
9643
+ function findPackageRoot() {
9644
+ if (_cachedPkgRoot !== null) return _cachedPkgRoot;
9645
+ let fallback = null;
9646
+ for (const anchor of anchorDirs()) {
9647
+ const { root, firstWithPkg } = climbToMarker(anchor);
9648
+ if (root) {
9649
+ _cachedPkgRoot = root;
9650
+ return root;
9651
+ }
9652
+ if (fallback === null && firstWithPkg !== null) fallback = firstWithPkg;
9653
+ }
9654
+ _cachedPkgRoot = fallback ?? process.cwd();
9655
+ return _cachedPkgRoot;
9656
+ }
9657
+ function requireFromRoot(relPath) {
9658
+ const root = findPackageRoot();
9659
+ const req = (0, import_node_module4.createRequire)((0, import_node_path21.join)(root, "noop.js"));
9660
+ return req((0, import_node_path21.join)(root, relPath));
9661
+ }
9662
+ function resolveRoot(deps, flags) {
9663
+ if (typeof deps.root === "string" && deps.root.length > 0) return deps.root;
9664
+ const flagRoot = flags["root"];
9665
+ if (typeof flagRoot === "string" && flagRoot.length > 0) return flagRoot;
9666
+ if (process.env["GDD_PROJECT_ROOT"]) return process.env["GDD_PROJECT_ROOT"];
9667
+ return findPackageRoot();
9668
+ }
9669
+ function loadGraphGraceful(root, stderr) {
9670
+ const graphPath = (0, import_node_path21.join)(root, ".design", "context-graph.json");
9671
+ try {
9672
+ const query = requireFromRoot("scripts/lib/design-context-query.cjs");
9673
+ if (typeof query.load === "function") return query.load(graphPath);
9674
+ } catch (err) {
9675
+ stderr.write(
9676
+ `gdd-sdk dashboard: no design-context graph at ${graphPath} (${errMsg(err)}); rendering an empty graph.
9677
+ `
9678
+ );
9679
+ }
9680
+ return { nodes: [], edges: [] };
9681
+ }
9682
+ function buildDashboardHtml(root, stderr) {
9683
+ const graph = loadGraphGraceful(root, stderr);
9684
+ const htmlLib = requireFromRoot("scripts/lib/dashboard/graph-html.cjs");
9685
+ return htmlLib.buildGraphHtml(graph, { title: "GDD Design Context Graph" });
9686
+ }
9687
+ function isHeadless(deps, flags) {
9688
+ if (typeof deps.headless === "boolean") return deps.headless;
9689
+ if (flags["no-open"] === true) return true;
9690
+ if (process.env["CI"]) return true;
9691
+ if (process.platform === "linux") {
9692
+ return !process.env["DISPLAY"] && !process.env["WAYLAND_DISPLAY"];
9693
+ }
9694
+ return false;
9695
+ }
9696
+ function defaultOpenBrowser(url) {
9697
+ try {
9698
+ if (process.platform === "darwin") {
9699
+ (0, import_node_child_process2.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
9700
+ } else if (process.platform === "win32") {
9701
+ (0, import_node_child_process2.spawn)("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
9702
+ } else {
9703
+ (0, import_node_child_process2.spawn)("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
9704
+ }
9705
+ return true;
9706
+ } catch {
9707
+ return false;
9708
+ }
9709
+ }
9710
+ function serveHtml(html) {
9711
+ return new Promise((resolve11, reject) => {
9712
+ const server = (0, import_node_http.createServer)((_req, res) => {
9713
+ res.writeHead(200, {
9714
+ "content-type": "text/html; charset=utf-8",
9715
+ "cache-control": "no-store"
9716
+ });
9717
+ res.end(html);
9718
+ });
9719
+ server.on("error", reject);
9720
+ server.listen(0, "127.0.0.1", () => {
9721
+ const addr = server.address();
9722
+ if (addr === null || typeof addr === "string") {
9723
+ server.close();
9724
+ reject(new Error("could not determine the ephemeral server port"));
9725
+ return;
9726
+ }
9727
+ const port = addr.port;
9728
+ resolve11({ server, port, url: `http://127.0.0.1:${port}/` });
9729
+ });
9730
+ });
9731
+ }
9732
+ function errMsg(err) {
9733
+ if (err instanceof Error) return err.message;
9734
+ return String(err);
9735
+ }
9736
+ async function dashboardCommand(parsed, deps = {}) {
9737
+ const stdout = deps.stdout ?? process.stdout;
9738
+ const stderr = deps.stderr ?? process.stderr;
9739
+ if (parsed.flags["help"] === true || parsed.flags["h"] === true) {
9740
+ stdout.write(DASHBOARD_USAGE);
9741
+ return 0;
9742
+ }
9743
+ let flags;
9744
+ try {
9745
+ flags = coerceFlags(parsed, DASHBOARD_FLAGS);
9746
+ } catch {
9747
+ stderr.write(`gdd-sdk dashboard: invalid flags
9748
+ ${DASHBOARD_USAGE}`);
9749
+ return 3;
9750
+ }
9751
+ const once = flags["once"] === true;
9752
+ const web = flags["web"] === true || once;
9753
+ if (!web) {
9754
+ return runTui(deps, stdout, stderr);
9755
+ }
9756
+ const root = resolveRoot(deps, flags);
9757
+ const html = buildDashboardHtml(root, stderr);
9758
+ if (once) {
9759
+ const designDir = (0, import_node_path21.join)(root, ".design");
9760
+ try {
9761
+ (0, import_node_fs23.mkdirSync)(designDir, { recursive: true });
9762
+ } catch {
9763
+ }
9764
+ const outFile = (0, import_node_path21.join)(designDir, "dashboard.html");
9765
+ try {
9766
+ (0, import_node_fs23.writeFileSync)(outFile, html, "utf8");
9767
+ } catch (err) {
9768
+ stderr.write(`gdd-sdk dashboard: could not write ${outFile}: ${errMsg(err)}
9769
+ `);
9770
+ return 3;
9771
+ }
9772
+ stdout.write(`Wrote dashboard HTML to ${outFile}
9773
+ `);
9774
+ return 0;
9775
+ }
9776
+ let served;
9777
+ try {
9778
+ served = await serveHtml(html);
9779
+ } catch (err) {
9780
+ stderr.write(`gdd-sdk dashboard: could not start the web server: ${errMsg(err)}
9781
+ `);
9782
+ return 3;
9783
+ }
9784
+ const headless = isHeadless(deps, flags);
9785
+ const opener = deps.openBrowser ?? defaultOpenBrowser;
9786
+ stdout.write(`GDD dashboard serving at ${served.url}
9787
+ `);
9788
+ if (headless) {
9789
+ stdout.write("Headless environment detected \u2014 open the URL above in a browser.\n");
9790
+ stdout.write("Press Ctrl+C to stop the server.\n");
9791
+ } else {
9792
+ const launched = opener(served.url);
9793
+ if (!launched) {
9794
+ stdout.write("Could not auto-open a browser \u2014 open the URL above manually.\n");
9795
+ }
9796
+ stdout.write("Press Ctrl+C to stop the server.\n");
9797
+ }
9798
+ await new Promise((resolve11) => {
9799
+ const shutdown = () => {
9800
+ served.server.close(() => resolve11());
9801
+ };
9802
+ process.once("SIGINT", shutdown);
9803
+ process.once("SIGTERM", shutdown);
9804
+ served.server.on("close", () => resolve11());
9805
+ });
9806
+ return 0;
9807
+ }
9808
+ function runTui(deps, _stdout, stderr) {
9809
+ let bin = deps.tuiBin;
9810
+ if (!bin) {
9811
+ const root = findPackageRoot();
9812
+ const candidate = (0, import_node_path21.join)(root, "bin", "gdd-dashboard");
9813
+ bin = (0, import_node_fs23.existsSync)(candidate) ? candidate : void 0;
9814
+ }
9815
+ if (!bin || !(0, import_node_fs23.existsSync)(bin)) {
9816
+ stderr.write(
9817
+ "gdd-sdk dashboard: could not locate bin/gdd-dashboard (the terminal UI).\nTry `gdd dashboard --web` for the browser graph instead.\n"
9818
+ );
9819
+ return 3;
9820
+ }
9821
+ const stdio = deps.tuiStdio ?? "inherit";
9822
+ const res = (0, import_node_child_process2.spawnSync)(process.execPath, [bin], { stdio });
9823
+ if (res.error) {
9824
+ stderr.write(`gdd-sdk dashboard: failed to launch the TUI: ${res.error.message}
9825
+ `);
9826
+ return 3;
9827
+ }
9828
+ return typeof res.status === "number" ? res.status : 0;
9829
+ }
9830
+
8451
9831
  // sdk/cli/index.ts
8452
9832
  var USAGE6 = `gdd-sdk <command> [flags]
8453
9833
 
@@ -8458,6 +9838,7 @@ Commands:
8458
9838
  audit Probe connections + dry-run verify.
8459
9839
  init Bootstrap a new project.
8460
9840
  build skills Compile per-harness skill bundles from source/skills/.
9841
+ dashboard Open the GDD dashboard (TUI; --web for the browser graph).
8461
9842
 
8462
9843
  Use 'gdd-sdk <command> -h' for command-specific flags.
8463
9844
 
@@ -8476,7 +9857,8 @@ async function dispatch(parsed, deps = {}) {
8476
9857
  query: deps.commands?.query ?? queryCommand,
8477
9858
  audit: deps.commands?.audit ?? auditCommand,
8478
9859
  init: deps.commands?.init ?? initCommand,
8479
- build: deps.commands?.build ?? buildCommand
9860
+ build: deps.commands?.build ?? buildCommand,
9861
+ dashboard: deps.commands?.dashboard ?? dashboardCommand
8480
9862
  };
8481
9863
  if (parsed.subcommand === null) {
8482
9864
  stdout.write(USAGE6);
@@ -8502,6 +9884,8 @@ async function dispatch(parsed, deps = {}) {
8502
9884
  return await commands.init(parsed, { stdout, stderr });
8503
9885
  case "build":
8504
9886
  return await commands.build(parsed, { stdout, stderr });
9887
+ case "dashboard":
9888
+ return await commands.dashboard(parsed, { stdout, stderr });
8505
9889
  default:
8506
9890
  stderr.write(
8507
9891
  `gdd-sdk: unknown subcommand "${parsed.subcommand}"
@@ -8516,7 +9900,8 @@ var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set([
8516
9900
  "query",
8517
9901
  "audit",
8518
9902
  "init",
8519
- "build"
9903
+ "build",
9904
+ "dashboard"
8520
9905
  ]);
8521
9906
  async function main(argv = process.argv.slice(2), deps = {}) {
8522
9907
  const parsed = parseArgs(argv);