@hegemonart/get-design-done 1.52.0 → 1.54.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +90 -0
- package/README.md +4 -0
- package/SKILL.md +2 -1
- package/agents/component-taxonomy-mapper.md +3 -0
- package/agents/design-context-reviewer-gate.md +102 -0
- package/agents/design-context-reviewer.md +186 -0
- package/agents/motion-mapper.md +1 -0
- package/agents/token-mapper.md +3 -0
- package/dist/claude-code/.claude/skills/discover/SKILL.md +7 -1
- package/dist/claude-code/.claude/skills/explore/SKILL.md +3 -1
- package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +81 -0
- package/package.json +1 -1
- package/reference/frameworks/astro.md +43 -0
- package/reference/frameworks/nextjs.md +44 -0
- package/reference/frameworks/remix.md +44 -0
- package/reference/frameworks/storybook.md +44 -0
- package/reference/frameworks/sveltekit.md +43 -0
- package/reference/frameworks/vite-react.md +43 -0
- package/reference/interaction.md +1 -0
- package/reference/motion/framer-motion.md +45 -0
- package/reference/motion/gsap.md +45 -0
- package/reference/motion/motion-one.md +44 -0
- package/reference/motion/react-spring.md +44 -0
- package/reference/motion.md +1 -0
- package/reference/registry.json +163 -1
- package/reference/registry.schema.json +18 -1
- package/reference/skill-graph.md +2 -1
- package/reference/systems/chakra.md +44 -0
- package/reference/systems/css-modules.md +44 -0
- package/reference/systems/mui.md +44 -0
- package/reference/systems/radix-themes.md +43 -0
- package/reference/systems/shadcn.md +45 -0
- package/reference/systems/styled-components.md +44 -0
- package/reference/systems/tailwind.md +44 -0
- package/reference/systems/vanilla-extract.md +44 -0
- package/scripts/lib/detect/stack.cjs +455 -0
- package/scripts/lib/detect/stack.d.cts +44 -0
- package/scripts/lib/explore-parallel-runner/index.ts +196 -1
- package/scripts/lib/explore-parallel-runner/types.ts +85 -0
- package/scripts/lib/health-mirror/index.cjs +73 -1
- package/scripts/lib/manifest/skills.json +10 -2
- package/scripts/lib/mapper-spawn.cjs +257 -0
- package/scripts/lib/mapper-spawn.d.cts +60 -0
- package/scripts/lib/mappers/compute-batches.mjs +625 -0
- package/scripts/lib/mappers/graph-adjacency.mjs +129 -0
- package/scripts/lib/mappers/incremental-discover.cjs +617 -0
- package/scripts/lib/mappers/incremental-discover.d.cts +133 -0
- package/scripts/lib/mappers/neighbor-map.mjs +0 -0
- package/scripts/lib/new-addendum.cjs +204 -0
- package/sdk/cli/index.js +1504 -3
- package/sdk/fingerprint/classify.cjs +406 -0
- package/sdk/fingerprint/index.ts +405 -0
- package/sdk/fingerprint/store.cjs +523 -0
- package/sdk/index.ts +1 -0
- package/sdk/mcp/gdd-mcp/server.js +1047 -0
- package/skills/discover/SKILL.md +7 -1
- package/skills/explore/SKILL.md +3 -1
- package/skills/new-addendum/SKILL.md +81 -0
package/sdk/cli/index.js
CHANGED
|
@@ -527,6 +527,1405 @@ var require_concurrency_tuner = __commonJS({
|
|
|
527
527
|
}
|
|
528
528
|
});
|
|
529
529
|
|
|
530
|
+
// scripts/lib/mappers/incremental-discover.cjs
|
|
531
|
+
var require_incremental_discover = __commonJS({
|
|
532
|
+
"scripts/lib/mappers/incremental-discover.cjs"(exports2, module2) {
|
|
533
|
+
"use strict";
|
|
534
|
+
var path = require("node:path");
|
|
535
|
+
var fs = require("node:fs");
|
|
536
|
+
var { pathToFileURL } = require("node:url");
|
|
537
|
+
function pkgRoot() {
|
|
538
|
+
let dir = __dirname;
|
|
539
|
+
for (let i = 0; i < 10; i += 1) {
|
|
540
|
+
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
|
|
541
|
+
const parent = path.dirname(dir);
|
|
542
|
+
if (parent === dir) break;
|
|
543
|
+
dir = parent;
|
|
544
|
+
}
|
|
545
|
+
return process.cwd();
|
|
546
|
+
}
|
|
547
|
+
var ROOT = pkgRoot();
|
|
548
|
+
var MAPPERS_DIR = path.join(ROOT, "scripts", "lib", "mappers");
|
|
549
|
+
var SDK_FP_DIR = path.join(ROOT, "sdk", "fingerprint");
|
|
550
|
+
var _classify = null;
|
|
551
|
+
function classify(...args) {
|
|
552
|
+
if (!_classify) _classify = require(path.join(SDK_FP_DIR, "classify.cjs")).classify;
|
|
553
|
+
return _classify(...args);
|
|
554
|
+
}
|
|
555
|
+
var _batchMod = null;
|
|
556
|
+
var _neighborMod = null;
|
|
557
|
+
var _fpMod = null;
|
|
558
|
+
async function loadBatchMod() {
|
|
559
|
+
if (!_batchMod) {
|
|
560
|
+
_batchMod = await import(pathToFileURL(path.join(MAPPERS_DIR, "compute-batches.mjs")).href);
|
|
561
|
+
}
|
|
562
|
+
return _batchMod;
|
|
563
|
+
}
|
|
564
|
+
async function loadNeighborMod() {
|
|
565
|
+
if (!_neighborMod) {
|
|
566
|
+
_neighborMod = await import(pathToFileURL(path.join(MAPPERS_DIR, "neighbor-map.mjs")).href);
|
|
567
|
+
}
|
|
568
|
+
return _neighborMod;
|
|
569
|
+
}
|
|
570
|
+
async function loadFingerprintMod() {
|
|
571
|
+
if (!_fpMod) {
|
|
572
|
+
_fpMod = await import(pathToFileURL(path.join(SDK_FP_DIR, "index.ts")).href);
|
|
573
|
+
}
|
|
574
|
+
return _fpMod;
|
|
575
|
+
}
|
|
576
|
+
function nodeList(graph) {
|
|
577
|
+
return Array.isArray(graph && graph.nodes) ? graph.nodes : [];
|
|
578
|
+
}
|
|
579
|
+
function edgeList(graph) {
|
|
580
|
+
return Array.isArray(graph && graph.edges) ? graph.edges : [];
|
|
581
|
+
}
|
|
582
|
+
var STRUCTURAL_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
583
|
+
"composes",
|
|
584
|
+
"extends",
|
|
585
|
+
"depends-on",
|
|
586
|
+
"consumes-context",
|
|
587
|
+
"provides-context"
|
|
588
|
+
]);
|
|
589
|
+
var FINGERPRINTABLE = /* @__PURE__ */ new Map([
|
|
590
|
+
["component", "component"],
|
|
591
|
+
["token", "token"],
|
|
592
|
+
["motion-fragment", "motion"]
|
|
593
|
+
]);
|
|
594
|
+
function indexForProjection(graph) {
|
|
595
|
+
const byId = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const n of nodeList(graph)) {
|
|
597
|
+
if (n && typeof n.id === "string") byId.set(n.id, n);
|
|
598
|
+
}
|
|
599
|
+
const tokensOf = /* @__PURE__ */ new Map();
|
|
600
|
+
const variantsOf = /* @__PURE__ */ new Map();
|
|
601
|
+
const ensure = (map, id) => {
|
|
602
|
+
let s = map.get(id);
|
|
603
|
+
if (!s) {
|
|
604
|
+
s = /* @__PURE__ */ new Set();
|
|
605
|
+
map.set(id, s);
|
|
606
|
+
}
|
|
607
|
+
return s;
|
|
608
|
+
};
|
|
609
|
+
const typeOf = (id) => byId.get(id) && byId.get(id).type;
|
|
610
|
+
const nameOf = (id) => {
|
|
611
|
+
const node = byId.get(id);
|
|
612
|
+
return node && typeof node.name === "string" ? node.name : id;
|
|
613
|
+
};
|
|
614
|
+
for (const e of edgeList(graph)) {
|
|
615
|
+
if (!e || typeof e.source !== "string" || typeof e.target !== "string") continue;
|
|
616
|
+
const sType = typeOf(e.source);
|
|
617
|
+
const tType = typeOf(e.target);
|
|
618
|
+
if (e.type === "uses-token") {
|
|
619
|
+
if (sType === "component" && tType === "token") ensure(tokensOf, e.source).add(e.target);
|
|
620
|
+
else if (tType === "component" && sType === "token") ensure(tokensOf, e.target).add(e.source);
|
|
621
|
+
} else if (e.type === "extends") {
|
|
622
|
+
if (tType === "component" && (sType === "variant" || sType === "state")) {
|
|
623
|
+
ensure(variantsOf, e.target).add(nameOf(e.source));
|
|
624
|
+
} else if (sType === "component" && (tType === "variant" || tType === "state")) {
|
|
625
|
+
ensure(variantsOf, e.source).add(nameOf(e.target));
|
|
626
|
+
}
|
|
627
|
+
} else if (STRUCTURAL_EDGE_TYPES.has(e.type)) {
|
|
628
|
+
if (sType === "component" && tType === "state") ensure(variantsOf, e.source).add(nameOf(e.target));
|
|
629
|
+
else if (tType === "component" && sType === "state") ensure(variantsOf, e.target).add(nameOf(e.source));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { byId, tokensOf, variantsOf };
|
|
633
|
+
}
|
|
634
|
+
function strArray(v) {
|
|
635
|
+
if (!Array.isArray(v)) return [];
|
|
636
|
+
const out = [];
|
|
637
|
+
const seen = /* @__PURE__ */ new Set();
|
|
638
|
+
for (const x of v) {
|
|
639
|
+
const s = typeof x === "string" ? x : x && typeof x.name === "string" ? x.name : null;
|
|
640
|
+
if (s != null && !seen.has(s)) {
|
|
641
|
+
seen.add(s);
|
|
642
|
+
out.push(s);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
out.sort();
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
function propShape(props) {
|
|
649
|
+
if (!Array.isArray(props)) return [];
|
|
650
|
+
const out = [];
|
|
651
|
+
for (const p of props) {
|
|
652
|
+
if (typeof p === "string") {
|
|
653
|
+
out.push({ name: p, type: "" });
|
|
654
|
+
} else if (p && typeof p === "object" && typeof p.name === "string") {
|
|
655
|
+
out.push({
|
|
656
|
+
name: p.name,
|
|
657
|
+
type: typeof p.type === "string" ? p.type : "",
|
|
658
|
+
...typeof p.optional === "boolean" ? { optional: p.optional } : {}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
function projectNode(node, fpType, idx) {
|
|
665
|
+
if (fpType === "component") {
|
|
666
|
+
const usedTokens = idx.tokensOf.has(node.id) ? [...idx.tokensOf.get(node.id)].sort() : [];
|
|
667
|
+
const variantsFromGraph = idx.variantsOf.has(node.id) ? [...idx.variantsOf.get(node.id)] : [];
|
|
668
|
+
const variantsFromNode = strArray(node.exported_variants || node.variants);
|
|
669
|
+
const exportedVariants = [.../* @__PURE__ */ new Set([...variantsFromGraph, ...variantsFromNode])].sort();
|
|
670
|
+
return {
|
|
671
|
+
component_signature: {
|
|
672
|
+
name: typeof node.name === "string" ? node.name : node.id,
|
|
673
|
+
members: strArray(node.members)
|
|
674
|
+
},
|
|
675
|
+
props_shape: propShape(node.props),
|
|
676
|
+
used_tokens: usedTokens,
|
|
677
|
+
exported_variants: exportedVariants
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
if (fpType === "token") {
|
|
681
|
+
return {
|
|
682
|
+
token_name: typeof node.name === "string" ? node.name : node.id,
|
|
683
|
+
token_value: node.value === void 0 ? null : typeof node.value === "object" ? JSON.stringify(node.value) : node.value,
|
|
684
|
+
token_type: typeof node.subtype === "string" ? node.subtype : typeof node.token_type === "string" ? node.token_type : "",
|
|
685
|
+
...typeof node.subtype === "string" ? { subtype: node.subtype } : {},
|
|
686
|
+
...typeof node.theme_scope === "string" ? { theme_scope: node.theme_scope } : {}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
animation_target: typeof node.name === "string" ? node.name : node.id,
|
|
691
|
+
...Number.isFinite(node.duration_ms) ? { duration_ms: node.duration_ms } : {},
|
|
692
|
+
...typeof node.easing === "string" ? { easing: node.easing } : {}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function idNamespace(id) {
|
|
696
|
+
if (typeof id !== "string") return "unknown";
|
|
697
|
+
const i = id.indexOf(":");
|
|
698
|
+
return i > 0 ? id.slice(0, i) : id;
|
|
699
|
+
}
|
|
700
|
+
function isFileNode(node) {
|
|
701
|
+
return !!node && FINGERPRINTABLE.has(node.type);
|
|
702
|
+
}
|
|
703
|
+
function deriveDirShape(graph) {
|
|
704
|
+
const counts = {};
|
|
705
|
+
const layerHist = {};
|
|
706
|
+
let totalFiles = 0;
|
|
707
|
+
for (const n of nodeList(graph)) {
|
|
708
|
+
if (!isFileNode(n)) continue;
|
|
709
|
+
totalFiles += 1;
|
|
710
|
+
const ns = idNamespace(n.id);
|
|
711
|
+
let dir = ns;
|
|
712
|
+
if (n.type === "token" && typeof n.subtype === "string" && n.subtype.length) {
|
|
713
|
+
dir = `token:${n.subtype}`;
|
|
714
|
+
}
|
|
715
|
+
counts[dir] = (counts[dir] || 0) + 1;
|
|
716
|
+
layerHist[ns] = (layerHist[ns] || 0) + 1;
|
|
717
|
+
}
|
|
718
|
+
const dirs = Object.keys(counts).sort();
|
|
719
|
+
return { dirs, counts, layerHist, totalFiles };
|
|
720
|
+
}
|
|
721
|
+
function derivePrevDirShape(prevFingerprints) {
|
|
722
|
+
const fps = prevFingerprints && typeof prevFingerprints === "object" ? prevFingerprints : {};
|
|
723
|
+
const ids = Object.keys(fps);
|
|
724
|
+
if (ids.length === 0) return null;
|
|
725
|
+
const counts = {};
|
|
726
|
+
const layerHist = {};
|
|
727
|
+
for (const id of ids) {
|
|
728
|
+
const ns = idNamespace(id);
|
|
729
|
+
let dir = ns;
|
|
730
|
+
if (ns === "token") {
|
|
731
|
+
const parts = id.split(":");
|
|
732
|
+
if (parts.length >= 3 && parts[1]) dir = `token:${parts[1]}`;
|
|
733
|
+
}
|
|
734
|
+
counts[dir] = (counts[dir] || 0) + 1;
|
|
735
|
+
layerHist[ns] = (layerHist[ns] || 0) + 1;
|
|
736
|
+
}
|
|
737
|
+
return { dirs: Object.keys(counts).sort(), counts, layerHist };
|
|
738
|
+
}
|
|
739
|
+
function asFingerprint(v) {
|
|
740
|
+
if (v == null) return null;
|
|
741
|
+
if (typeof v === "string") return { full: v, structural: v };
|
|
742
|
+
if (typeof v === "object" && typeof v.full === "string") {
|
|
743
|
+
return { full: v.full, structural: typeof v.structural === "string" ? v.structural : v.full };
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
function buildCompareResults(graph, prevFingerprints, fingerprint, compareFingerprints) {
|
|
748
|
+
const idx = indexForProjection(graph);
|
|
749
|
+
const prev = prevFingerprints && typeof prevFingerprints === "object" ? prevFingerprints : {};
|
|
750
|
+
const fingerprints = {};
|
|
751
|
+
const compareResults = [];
|
|
752
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
753
|
+
for (const node of nodeList(graph)) {
|
|
754
|
+
if (!node || typeof node.id !== "string") continue;
|
|
755
|
+
const fpType = FINGERPRINTABLE.get(node.type);
|
|
756
|
+
if (!fpType) continue;
|
|
757
|
+
currentIds.add(node.id);
|
|
758
|
+
let fp;
|
|
759
|
+
try {
|
|
760
|
+
fp = fingerprint(projectNode(node, fpType, idx), fpType);
|
|
761
|
+
} catch {
|
|
762
|
+
compareResults.push({ id: node.id, type: node.type, change: "STRUCTURAL" });
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
fingerprints[node.id] = { full: fp.full, structural: fp.structural, type: node.type };
|
|
766
|
+
const before = asFingerprint(prev[node.id]);
|
|
767
|
+
const change = compareFingerprints(before, { full: fp.full, structural: fp.structural });
|
|
768
|
+
compareResults.push({ id: node.id, type: node.type, change });
|
|
769
|
+
}
|
|
770
|
+
for (const id of Object.keys(prev)) {
|
|
771
|
+
if (currentIds.has(id)) continue;
|
|
772
|
+
const before = asFingerprint(prev[id]);
|
|
773
|
+
const change = compareFingerprints(before, null);
|
|
774
|
+
const t = prev[id] && typeof prev[id] === "object" && typeof prev[id].type === "string" ? prev[id].type : idNamespace(id);
|
|
775
|
+
compareResults.push({ id, type: t, change });
|
|
776
|
+
}
|
|
777
|
+
return { fingerprints, compareResults };
|
|
778
|
+
}
|
|
779
|
+
function selectBatches(batches, action, affectedBatchHints) {
|
|
780
|
+
const all = Array.isArray(batches) ? batches : [];
|
|
781
|
+
if (action === "SKIP") return [];
|
|
782
|
+
if (action === "FULL_UPDATE") return all.slice();
|
|
783
|
+
const hints = new Set(Array.isArray(affectedBatchHints) ? affectedBatchHints : []);
|
|
784
|
+
if (hints.size === 0) return [];
|
|
785
|
+
const out = [];
|
|
786
|
+
for (const b of all) {
|
|
787
|
+
const members = Array.isArray(b && b.members) ? b.members : [];
|
|
788
|
+
if (members.some((m) => hints.has(m))) out.push(b);
|
|
789
|
+
}
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
async function planIncremental2(args) {
|
|
793
|
+
const { graph, prevFingerprints, opts } = args && typeof args === "object" ? args : {};
|
|
794
|
+
const o = opts && typeof opts === "object" ? opts : {};
|
|
795
|
+
const prev = prevFingerprints && typeof prevFingerprints === "object" ? prevFingerprints : {};
|
|
796
|
+
const { computeBatches } = await loadBatchMod();
|
|
797
|
+
const { batches, modularity, method } = computeBatches(graph, o.computeBatchesOpts);
|
|
798
|
+
const { fingerprint, compareFingerprints } = await loadFingerprintMod();
|
|
799
|
+
const { fingerprints, compareResults } = buildCompareResults(
|
|
800
|
+
graph,
|
|
801
|
+
prev,
|
|
802
|
+
fingerprint,
|
|
803
|
+
compareFingerprints
|
|
804
|
+
);
|
|
805
|
+
const currDirShape = deriveDirShape(graph);
|
|
806
|
+
let prevDirShape = derivePrevDirShape(prev);
|
|
807
|
+
if (o.hadPriorBaseline === false) prevDirShape = null;
|
|
808
|
+
const projectStats = {
|
|
809
|
+
totalFiles: currDirShape.totalFiles,
|
|
810
|
+
prevDirShape,
|
|
811
|
+
currDirShape: { dirs: currDirShape.dirs, counts: currDirShape.counts, layerHist: currDirShape.layerHist },
|
|
812
|
+
...o.thresholds && typeof o.thresholds === "object" ? { thresholds: o.thresholds } : {}
|
|
813
|
+
};
|
|
814
|
+
const classification = classify(compareResults, projectStats);
|
|
815
|
+
const effectiveAction = o.forceFull ? "FULL_UPDATE" : classification.action;
|
|
816
|
+
const batchesToMap = selectBatches(batches, effectiveAction, classification.affectedBatchHints);
|
|
817
|
+
const neighborMaps = {};
|
|
818
|
+
if (batchesToMap.length > 0) {
|
|
819
|
+
const { buildNeighborMap } = await loadNeighborMod();
|
|
820
|
+
const cap = Number.isInteger(o.neighborCap) && o.neighborCap >= 0 ? o.neighborCap : 50;
|
|
821
|
+
for (const b of batchesToMap) {
|
|
822
|
+
if (b && typeof b.id === "string") {
|
|
823
|
+
neighborMaps[b.id] = buildNeighborMap(b, graph, { cap });
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
action: effectiveAction,
|
|
829
|
+
batches,
|
|
830
|
+
batchesToMap,
|
|
831
|
+
neighborMaps,
|
|
832
|
+
fingerprints,
|
|
833
|
+
compareResults,
|
|
834
|
+
classification,
|
|
835
|
+
method,
|
|
836
|
+
modularity
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
module2.exports = {
|
|
840
|
+
planIncremental: planIncremental2,
|
|
841
|
+
// exported for the wiring layer + tests (pure helpers, no side effects).
|
|
842
|
+
selectBatches,
|
|
843
|
+
deriveDirShape,
|
|
844
|
+
derivePrevDirShape,
|
|
845
|
+
buildCompareResults,
|
|
846
|
+
projectNode,
|
|
847
|
+
indexForProjection,
|
|
848
|
+
FINGERPRINTABLE
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
});
|
|
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
|
+
|
|
530
1929
|
// sdk/cli/index.ts
|
|
531
1930
|
var index_exports = {};
|
|
532
1931
|
__export(index_exports, {
|
|
@@ -5560,6 +6959,9 @@ var import_node_path14 = require("node:path");
|
|
|
5560
6959
|
// scripts/lib/explore-parallel-runner/index.ts
|
|
5561
6960
|
var import_node_path12 = require("node:path");
|
|
5562
6961
|
var import_concurrency_tuner = __toESM(require_concurrency_tuner());
|
|
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());
|
|
5563
6965
|
|
|
5564
6966
|
// scripts/lib/explore-parallel-runner/mappers.ts
|
|
5565
6967
|
var import_node_fs12 = require("node:fs");
|
|
@@ -5897,12 +7299,109 @@ var DEFAULT_MAPPERS = Object.freeze([
|
|
|
5897
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."
|
|
5898
7300
|
})
|
|
5899
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
|
+
}
|
|
5900
7357
|
async function run3(opts) {
|
|
5901
|
-
const
|
|
7358
|
+
const baseSpecs = opts.mappers ?? DEFAULT_MAPPERS;
|
|
5902
7359
|
const cwd = opts.cwd ?? process.cwd();
|
|
5903
7360
|
const concurrency = opts.concurrency ?? (0, import_concurrency_tuner.resolveConcurrency)();
|
|
5904
7361
|
const logger = getLogger().child("explore.runner");
|
|
7362
|
+
const { specs } = composeMapperSpecs(baseSpecs, cwd, opts.addendums, logger);
|
|
5905
7363
|
const outputPath = (0, import_node_path12.resolve)(cwd, ".design/DESIGN-PATTERNS.md");
|
|
7364
|
+
let batching = void 0;
|
|
7365
|
+
if (opts.incremental && opts.incremental.graph !== void 0 && opts.incremental.graph !== null) {
|
|
7366
|
+
try {
|
|
7367
|
+
const plan = await (0, import_incremental_discover.planIncremental)({
|
|
7368
|
+
graph: opts.incremental.graph,
|
|
7369
|
+
prevFingerprints: opts.incremental.prevFingerprints,
|
|
7370
|
+
opts: {
|
|
7371
|
+
...opts.incremental.forceFull !== void 0 ? { forceFull: opts.incremental.forceFull } : {},
|
|
7372
|
+
...opts.incremental.computeBatchesOpts !== void 0 ? { computeBatchesOpts: opts.incremental.computeBatchesOpts } : {},
|
|
7373
|
+
...opts.incremental.neighborCap !== void 0 ? { neighborCap: opts.incremental.neighborCap } : {},
|
|
7374
|
+
...opts.incremental.thresholds !== void 0 ? { thresholds: opts.incremental.thresholds } : {}
|
|
7375
|
+
}
|
|
7376
|
+
});
|
|
7377
|
+
batching = Object.freeze({
|
|
7378
|
+
action: plan.action,
|
|
7379
|
+
method: plan.method,
|
|
7380
|
+
modularity: plan.modularity,
|
|
7381
|
+
batches: Object.freeze(plan.batches.map((b) => Object.freeze({
|
|
7382
|
+
id: b.id,
|
|
7383
|
+
members: Object.freeze([...b.members]),
|
|
7384
|
+
mergeable: b.mergeable,
|
|
7385
|
+
kind: b.kind,
|
|
7386
|
+
source: b.source
|
|
7387
|
+
}))),
|
|
7388
|
+
batchesToMap: Object.freeze(plan.batchesToMap.map((b) => b.id)),
|
|
7389
|
+
neighborMaps: Object.freeze({ ...plan.neighborMaps }),
|
|
7390
|
+
classification: Object.freeze({ ...plan.classification })
|
|
7391
|
+
});
|
|
7392
|
+
logger.info("explore.runner.batching", {
|
|
7393
|
+
action: plan.action,
|
|
7394
|
+
method: plan.method,
|
|
7395
|
+
batch_count: plan.batches.length,
|
|
7396
|
+
batches_to_map: plan.batchesToMap.length,
|
|
7397
|
+
structural_count: plan.classification.structuralCount
|
|
7398
|
+
});
|
|
7399
|
+
} catch (err) {
|
|
7400
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7401
|
+
logger.warn("explore.runner.batching_failed", { message });
|
|
7402
|
+
batching = void 0;
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
5906
7405
|
logger.info("explore.runner.started", {
|
|
5907
7406
|
mapper_count: specs.length,
|
|
5908
7407
|
concurrency
|
|
@@ -5924,7 +7423,8 @@ async function run3(opts) {
|
|
|
5924
7423
|
}),
|
|
5925
7424
|
parallel_count: 0,
|
|
5926
7425
|
serial_count: 0,
|
|
5927
|
-
total_usage: { input_tokens: 0, output_tokens: 0, usd_cost: 0 }
|
|
7426
|
+
total_usage: { input_tokens: 0, output_tokens: 0, usd_cost: 0 },
|
|
7427
|
+
...batching !== void 0 ? { batching } : {}
|
|
5928
7428
|
});
|
|
5929
7429
|
}
|
|
5930
7430
|
const safeSpecs = [];
|
|
@@ -6040,7 +7540,8 @@ async function run3(opts) {
|
|
|
6040
7540
|
input_tokens: totalInput,
|
|
6041
7541
|
output_tokens: totalOutput,
|
|
6042
7542
|
usd_cost: totalCost
|
|
6043
|
-
}
|
|
7543
|
+
},
|
|
7544
|
+
...batching !== void 0 ? { batching } : {}
|
|
6044
7545
|
});
|
|
6045
7546
|
}
|
|
6046
7547
|
|