@vibecodeqa/cli 0.17.0 → 0.18.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/README.md +73 -63
- package/dist/check-meta.d.ts +1 -0
- package/dist/check-meta.js +34 -2
- package/dist/cli.js +35 -10
- package/dist/detect.js +24 -2
- package/dist/fs-utils.d.ts +4 -0
- package/dist/fs-utils.js +12 -6
- package/dist/report/html.d.ts +17 -10
- package/dist/report/html.js +106 -73
- package/dist/report/pages.d.ts +2 -1
- package/dist/report/pages.js +88 -82
- package/dist/report/sarif.d.ts +3 -0
- package/dist/report/sarif.js +67 -0
- package/dist/report/styles.d.ts +1 -1
- package/dist/report/styles.js +82 -36
- package/dist/runners/architecture.d.ts +2 -0
- package/dist/runners/architecture.js +232 -20
- package/dist/runners/code-coherence.d.ts +17 -0
- package/dist/runners/code-coherence.js +39 -0
- package/dist/runners/complexity.js +7 -37
- package/dist/runners/confusion.js +3 -31
- package/dist/runners/context.js +9 -40
- package/dist/runners/dependencies.js +28 -0
- package/dist/runners/doc-coherence.d.ts +14 -0
- package/dist/runners/doc-coherence.js +48 -0
- package/dist/runners/docs.js +7 -32
- package/dist/runners/duplication.js +9 -37
- package/dist/runners/lint.js +17 -0
- package/dist/runners/performance.d.ts +10 -0
- package/dist/runners/performance.js +174 -0
- package/dist/runners/react.js +15 -10
- package/dist/runners/secrets.js +8 -29
- package/dist/runners/security.js +15 -38
- package/dist/runners/standards.js +3 -36
- package/dist/runners/structure.js +35 -55
- package/dist/runners/testing.js +2 -36
- package/dist/runners/type-safety.d.ts +1 -1
- package/dist/runners/type-safety.js +19 -37
- package/dist/runners/types-check.d.ts +1 -1
- package/dist/runners/types-check.js +38 -20
- package/dist/types.d.ts +5 -5
- package/package.json +11 -10
package/dist/report/styles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** All CSS for the HTML report, extracted for maintainability. */
|
|
2
|
-
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* Top nav \u2014 split into dimensions (left) and views (right) */\n.top{position:sticky;top:0;z-index:20;background:#0c0c0fcc;backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 2rem;display:flex;align-items:center;gap:0}\n.logo{font-weight:800;font-size:1rem;margin-right:1.5rem;padding:0.7rem 0;flex-shrink:0}\n.logo span{color:var(--accent)}\n.nav-dims{display:flex;align-items:center;gap:0}\n.nav-views{margin-left:auto;display:flex;align-items:center;gap:0;border-left:1px solid var(--border);padding-left:0.3rem}\n.tn{padding:0.7rem 0.8rem;font-size:0.78rem;color:var(--muted);text-decoration:none;cursor:pointer;border-bottom:2px solid transparent;transition:all 0.15s}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.tn-view{font-size:0.72rem;opacity:0.8}\n.tn-view.active{opacity:1}\n\n/* Sidebar */\n.side{position:fixed;top:42px;left:0;bottom:0;width:200px;background:#0c0c0f;border-right:1px solid var(--border);overflow-y:auto;padding:0.8rem 0;font-size:0.7rem;z-index:10}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.3rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--text);font-weight:700;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:#14141a}\n.side-check{display:block;padding:0.2rem 0.8rem 0.2rem 1.2rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;width:1rem;font-weight:800;text-align:center}\n.side-views{padding-top:0.5rem}\n.side-views-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:#444;font-weight:600}\n.side-views .side-check{padding-left:0.8rem}\n\n/* Content */\n.content{max-width:900px;margin-left:200px;padding:2rem}\n.page{display:none;animation:fadeIn 0.15s}\n.page.active{display:block}\n@keyframes fadeIn{from{opacity:0}to{opacity:1}}\n\n/* Overview */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;cursor:pointer;transition:border-color 0.15s}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* Overview sections */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);cursor:pointer;text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* Timeline */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* Bar chart */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* Category pages */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.sub-nav{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1rem;flex-wrap:wrap}\n.sn{padding:0.5rem 0.8rem;font-size:0.75rem;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent}\n.sn:hover{color:var(--text)}\n.sn.active{color:var(--text);border-bottom-color:var(--accent)}\n.sp{display:none}.sp.active{display:block}\n\n/* Check detail */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:#0d0d12;border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* Issue list grouped by file */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:#555;font-size:0.55rem}\n\n/* All issues table */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:#555;font-size:0.58rem}\n\n/* File health (merged file map + heatmap) */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.fcs{font-size:0.6rem;color:#555}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:#555;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n@media(max-width:768px){.side{display:none}.content{margin-left:0;padding:1rem}.cats{grid-template-columns:1fr 1fr}.dash{flex-direction:column}.nav-views{display:none}}\n";
|
|
2
|
+
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:#0c0c0fdd;backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:1rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:#0c0c0f;border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:#444;font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--text);font-weight:700;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:#14141a}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.sub-nav{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1rem;flex-wrap:wrap}\n.sn{padding:0.5rem 0.8rem;font-size:0.75rem;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent}\n.sn:hover{color:var(--text)}\n.sn.active{color:var(--text);border-bottom-color:var(--accent)}\n.sp{display:none}.sp.active{display:block}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:#0d0d12;border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:#555;font-size:0.55rem}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:#555;font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:#555;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n";
|
package/dist/report/styles.js
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
/** All CSS for the HTML report, extracted for maintainability. */
|
|
2
2
|
export const CSS = `
|
|
3
|
-
:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8}
|
|
3
|
+
:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px}
|
|
4
4
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
5
5
|
body{font-family:"Inter",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}
|
|
6
6
|
code{font-family:"SF Mono",Menlo,monospace;font-size:0.85em}
|
|
7
7
|
|
|
8
|
-
/* Top nav
|
|
9
|
-
.top{position:sticky;top:0;z-index:
|
|
10
|
-
.logo{font-weight:800;font-size:1rem;margin-right:
|
|
8
|
+
/* ── Top nav ── */
|
|
9
|
+
.top{position:sticky;top:0;z-index:30;background:#0c0c0fdd;backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}
|
|
10
|
+
.logo{font-weight:800;font-size:1rem;margin-right:1rem;flex-shrink:0;text-decoration:none;color:var(--text)}
|
|
11
11
|
.logo span{color:var(--accent)}
|
|
12
|
-
.nav-
|
|
13
|
-
.nav-
|
|
14
|
-
.tn{padding:0
|
|
12
|
+
.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}
|
|
13
|
+
.nav-scroll::-webkit-scrollbar{display:none}
|
|
14
|
+
.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}
|
|
15
15
|
.tn:hover{color:var(--text)}
|
|
16
16
|
.tn.active{color:var(--text);border-bottom-color:var(--accent)}
|
|
17
|
-
.
|
|
18
|
-
.tn-view.active{opacity:1}
|
|
17
|
+
.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}
|
|
19
18
|
|
|
20
|
-
/* Sidebar */
|
|
21
|
-
.side{position:fixed;top:
|
|
19
|
+
/* ── Sidebar ── */
|
|
20
|
+
.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:#0c0c0f;border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}
|
|
22
21
|
.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}
|
|
23
22
|
.side-section:last-child{border-bottom:none}
|
|
24
|
-
.side-
|
|
23
|
+
.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:#444;font-weight:600}
|
|
24
|
+
.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}
|
|
25
25
|
.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--text);font-weight:700;cursor:pointer;text-decoration:none;font-size:0.72rem}
|
|
26
26
|
.side-cat:hover{background:#14141a}
|
|
27
|
-
.side-
|
|
27
|
+
.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}
|
|
28
|
+
.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}
|
|
28
29
|
.side-check:hover{color:var(--text);background:#14141a}
|
|
29
|
-
.side-check span{display:inline-block;width:
|
|
30
|
-
.side-
|
|
31
|
-
.side-
|
|
30
|
+
.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}
|
|
31
|
+
.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}
|
|
32
|
+
.side-stat span{font-weight:800;font-size:0.8rem}
|
|
33
|
+
.side-views{padding-top:0.3rem}
|
|
32
34
|
.side-views .side-check{padding-left:0.8rem}
|
|
33
35
|
|
|
34
|
-
/* Content */
|
|
35
|
-
.content{
|
|
36
|
-
.page{display:none;animation:fadeIn 0.15s}
|
|
37
|
-
.page.active{display:block}
|
|
38
|
-
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
36
|
+
/* ── Content ── */
|
|
37
|
+
.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}
|
|
39
38
|
|
|
40
|
-
/* Overview */
|
|
39
|
+
/* ── Overview ── */
|
|
41
40
|
.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}
|
|
42
41
|
.hero{display:flex;align-items:center;gap:1rem}
|
|
43
42
|
.hero svg{width:100px;height:100px}
|
|
@@ -47,8 +46,8 @@ code{font-family:"SF Mono",Menlo,monospace;font-size:0.85em}
|
|
|
47
46
|
.hd{font-size:0.68rem;color:var(--muted)}
|
|
48
47
|
.radar{flex:1;display:flex;justify-content:center}
|
|
49
48
|
.radar svg{max-width:240px;width:100%}
|
|
50
|
-
.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
|
51
|
-
.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;
|
|
49
|
+
.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}
|
|
50
|
+
.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}
|
|
52
51
|
.cc:hover{border-color:var(--accent)}
|
|
53
52
|
.cc-s{font-size:1.8rem;font-weight:900}
|
|
54
53
|
.cc-l{font-size:0.75rem;color:var(--muted)}
|
|
@@ -56,7 +55,7 @@ code{font-family:"SF Mono",Menlo,monospace;font-size:0.85em}
|
|
|
56
55
|
.mc{font-size:0.65rem;font-weight:800}
|
|
57
56
|
h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}
|
|
58
57
|
|
|
59
|
-
/* Overview sections */
|
|
58
|
+
/* ── Overview sections ── */
|
|
60
59
|
.ov-section{margin-bottom:1.5rem}
|
|
61
60
|
.ov-issue{font-size:0.68rem;font-family:"SF Mono",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}
|
|
62
61
|
.ov-issue .is{flex-shrink:0}
|
|
@@ -65,14 +64,14 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
65
64
|
.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}
|
|
66
65
|
.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
67
66
|
.ov-msg{flex:1;word-break:break-word}
|
|
68
|
-
.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);
|
|
67
|
+
.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}
|
|
69
68
|
.ov-link:hover{text-decoration:underline}
|
|
70
69
|
|
|
71
|
-
/* Timeline */
|
|
70
|
+
/* ── Timeline ── */
|
|
72
71
|
.timeline{margin:0.5rem 0;overflow-x:auto}
|
|
73
72
|
.timeline svg{max-width:100%}
|
|
74
73
|
|
|
75
|
-
/* Bar chart */
|
|
74
|
+
/* ── Bar chart ── */
|
|
76
75
|
.bars{margin-bottom:1.5rem}
|
|
77
76
|
.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}
|
|
78
77
|
.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}
|
|
@@ -82,7 +81,7 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
82
81
|
.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}
|
|
83
82
|
.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}
|
|
84
83
|
|
|
85
|
-
/* Category pages */
|
|
84
|
+
/* ── Category pages ── */
|
|
86
85
|
.cat-head{margin-bottom:0.3rem}
|
|
87
86
|
.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1rem;overflow:hidden}
|
|
88
87
|
.bf2{height:100%;border-radius:2px}
|
|
@@ -92,7 +91,7 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
92
91
|
.sn.active{color:var(--text);border-bottom-color:var(--accent)}
|
|
93
92
|
.sp{display:none}.sp.active{display:block}
|
|
94
93
|
|
|
95
|
-
/* Check detail */
|
|
94
|
+
/* ── Check detail ── */
|
|
96
95
|
.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}
|
|
97
96
|
.ch-g{font-size:2rem;font-weight:900}
|
|
98
97
|
.ch-s{display:block;font-size:0.7rem;color:var(--muted)}
|
|
@@ -107,7 +106,7 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
107
106
|
.k{color:var(--muted);margin-right:0.3rem}
|
|
108
107
|
.v{font-weight:600}
|
|
109
108
|
|
|
110
|
-
/* Issue list grouped by file */
|
|
109
|
+
/* ── Issue list grouped by file ── */
|
|
111
110
|
.iss-list{margin-top:1rem}
|
|
112
111
|
.fg{margin-bottom:0.8rem}
|
|
113
112
|
.fn{font-size:0.72rem;font-weight:600;font-family:"SF Mono",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}
|
|
@@ -116,11 +115,12 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
116
115
|
.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}
|
|
117
116
|
.ir.error .is{color:var(--fail);background:#ef444418}
|
|
118
117
|
.ir.warning .is{color:var(--warn);background:#eab30818}
|
|
118
|
+
.ir.info .is{color:var(--info);background:#6366f118}
|
|
119
119
|
.il{color:var(--accent);min-width:2rem;flex-shrink:0}
|
|
120
120
|
.im{flex:1;word-break:break-word}
|
|
121
121
|
.iru{color:#555;font-size:0.55rem}
|
|
122
122
|
|
|
123
|
-
/* All issues table */
|
|
123
|
+
/* ── All issues table ── */
|
|
124
124
|
.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}
|
|
125
125
|
.it{width:100%;border-collapse:collapse;font-size:0.68rem}
|
|
126
126
|
.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}
|
|
@@ -132,25 +132,71 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
132
132
|
.il2{color:var(--muted)}
|
|
133
133
|
.iru2{color:#555;font-size:0.58rem}
|
|
134
134
|
|
|
135
|
-
/* File health
|
|
135
|
+
/* ── File health ── */
|
|
136
136
|
.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}
|
|
137
137
|
.ff{width:200px;font-family:"SF Mono",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
138
138
|
.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}
|
|
139
139
|
.fbf{height:100%;border-radius:2px}
|
|
140
140
|
.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}
|
|
141
|
-
.fcs{font-size:0.6rem;color:#555}
|
|
142
141
|
.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}
|
|
143
142
|
.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:"SF Mono",monospace;font-size:0.65rem}
|
|
144
143
|
.hm-bar{height:14px;border-radius:3px;min-width:4px}
|
|
145
144
|
.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}
|
|
146
145
|
.hm-checks{font-size:0.58rem;color:#555;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
147
146
|
|
|
147
|
+
/* ── Premium cards ── */
|
|
148
|
+
.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}
|
|
149
|
+
.pro-card::before{content:"";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}
|
|
150
|
+
.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}
|
|
151
|
+
.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}
|
|
152
|
+
.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}
|
|
153
|
+
.sn-pro{opacity:0.7}
|
|
154
|
+
|
|
148
155
|
.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}
|
|
149
156
|
.footer a{color:var(--muted)}
|
|
150
157
|
.flink{color:var(--accent);text-decoration:none;font-family:"SF Mono",monospace}.flink:hover{text-decoration:underline}
|
|
151
|
-
.arch-svg{margin:1rem 0;overflow-x:auto}
|
|
158
|
+
.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
152
159
|
.arch-svg svg{border-radius:8px}
|
|
153
160
|
.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}
|
|
154
161
|
.ir:hover .cp-btn{opacity:0.6}
|
|
155
|
-
|
|
162
|
+
|
|
163
|
+
/* ── Mobile: hamburger collapses both navs ── */
|
|
164
|
+
@media(max-width:768px){
|
|
165
|
+
.hamburger{display:block}
|
|
166
|
+
.nav-scroll{display:none}
|
|
167
|
+
.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}
|
|
168
|
+
.side{display:none}
|
|
169
|
+
.side.open{display:block;z-index:25}
|
|
170
|
+
.top{padding:0 0.8rem}
|
|
171
|
+
.logo{font-size:0.85rem;margin-right:0.5rem}
|
|
172
|
+
.content{margin-left:0;padding:0.8rem}
|
|
173
|
+
.cats{grid-template-columns:1fr 1fr}
|
|
174
|
+
.dash{flex-direction:column;gap:1rem}
|
|
175
|
+
.hero svg{width:80px;height:80px}
|
|
176
|
+
.hg{font-size:2rem}
|
|
177
|
+
.radar svg{max-width:180px}
|
|
178
|
+
.bl{width:60px;font-size:0.62rem}
|
|
179
|
+
.bv{width:30px;font-size:0.6rem}
|
|
180
|
+
.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
181
|
+
.ff{width:120px;font-size:0.58rem}
|
|
182
|
+
.hm-name{width:120px;font-size:0.58rem}
|
|
183
|
+
.hm-checks{display:none}
|
|
184
|
+
.ov-check{width:50px}
|
|
185
|
+
.ov-loc{max-width:120px}
|
|
186
|
+
.ir{font-size:0.6rem}
|
|
187
|
+
.ch-head{flex-wrap:wrap}
|
|
188
|
+
.ch-g{font-size:1.5rem}
|
|
189
|
+
.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}
|
|
190
|
+
.ip-row{flex-direction:column;gap:0.1rem}
|
|
191
|
+
.kvs{gap:0.4rem}
|
|
192
|
+
.kv{font-size:0.62rem;padding:0.2rem 0.4rem}
|
|
193
|
+
.arch-svg svg{min-width:400px}
|
|
194
|
+
}
|
|
195
|
+
@media(max-width:480px){
|
|
196
|
+
.cats{grid-template-columns:1fr}
|
|
197
|
+
.tn{padding:0 0.4rem;font-size:0.65rem}
|
|
198
|
+
.ff{width:90px}
|
|
199
|
+
.hm-name{width:90px}
|
|
200
|
+
.ov-check{display:none}
|
|
201
|
+
}
|
|
156
202
|
`;
|
|
@@ -25,4 +25,6 @@ export interface ArchGraph {
|
|
|
25
25
|
}
|
|
26
26
|
export declare function runArchitecture(cwd: string): CheckResult;
|
|
27
27
|
export declare function generateArchSVG(details: Record<string, unknown>): string;
|
|
28
|
+
export declare function generateDSM(details: Record<string, unknown>): string;
|
|
29
|
+
export declare function generatePackageDiagram(details: Record<string, unknown>): string;
|
|
28
30
|
export {};
|
|
@@ -228,8 +228,22 @@ export function generateArchSVG(details) {
|
|
|
228
228
|
return "";
|
|
229
229
|
const nodes = Object.entries(graph);
|
|
230
230
|
const nodeCount = nodes.length;
|
|
231
|
-
if (nodeCount >
|
|
232
|
-
return ""
|
|
231
|
+
if (nodeCount > 50)
|
|
232
|
+
return `<div style="color:#6b7280;font-size:0.75rem">${nodeCount} modules — too many to render. Consider splitting into smaller packages.</div>`;
|
|
233
|
+
// Detect cycles for highlighting
|
|
234
|
+
const cycleEdges = new Set();
|
|
235
|
+
const cycles = details.circularDeps;
|
|
236
|
+
if (cycles > 0) {
|
|
237
|
+
// Mark edges that participate in cycles (simplified: mutual imports)
|
|
238
|
+
for (const [path, info] of nodes) {
|
|
239
|
+
for (const imp of info.imports) {
|
|
240
|
+
if (graph[imp]?.imports.includes(path)) {
|
|
241
|
+
cycleEdges.add(`${path}->${imp}`);
|
|
242
|
+
cycleEdges.add(`${imp}->${path}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
233
247
|
// Group by directory
|
|
234
248
|
const dirs = new Map();
|
|
235
249
|
for (const [path, info] of nodes) {
|
|
@@ -238,23 +252,33 @@ export function generateArchSVG(details) {
|
|
|
238
252
|
arr.push(path);
|
|
239
253
|
dirs.set(dir, arr);
|
|
240
254
|
}
|
|
241
|
-
const W = 800, padding =
|
|
255
|
+
const W = 800, padding = 50;
|
|
242
256
|
const dirEntries = [...dirs.entries()];
|
|
243
257
|
const dirWidth = (W - padding * 2) / Math.max(dirEntries.length, 1);
|
|
258
|
+
const nodeSpacing = 38;
|
|
244
259
|
// Position nodes
|
|
245
260
|
const positions = new Map();
|
|
246
261
|
let dirIdx = 0;
|
|
247
|
-
for (const [
|
|
262
|
+
for (const [, paths] of dirEntries) {
|
|
248
263
|
const x0 = padding + dirIdx * dirWidth + dirWidth / 2;
|
|
249
264
|
for (let i = 0; i < paths.length; i++) {
|
|
250
|
-
const y = padding +
|
|
265
|
+
const y = padding + 55 + i * nodeSpacing;
|
|
251
266
|
positions.set(paths[i], { x: x0, y });
|
|
252
267
|
}
|
|
253
268
|
dirIdx++;
|
|
254
269
|
}
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
270
|
+
const maxGroupLen = Math.max(...[...dirs.values()].map((p) => p.length));
|
|
271
|
+
const H = Math.max(320, padding * 2 + 55 + maxGroupLen * nodeSpacing + 50);
|
|
272
|
+
// ── Defs: arrowhead marker, glow filter ──
|
|
273
|
+
const defs = `<defs>
|
|
274
|
+
<marker id="ah" viewBox="0 0 10 7" refX="10" refY="3.5" markerWidth="8" markerHeight="6" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#818cf850"/></marker>
|
|
275
|
+
<marker id="ah-cross" viewBox="0 0 10 7" refX="10" refY="3.5" markerWidth="8" markerHeight="6" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#ef444460"/></marker>
|
|
276
|
+
<marker id="ah-cycle" viewBox="0 0 10 7" refX="10" refY="3.5" markerWidth="8" markerHeight="6" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#f97316"/></marker>
|
|
277
|
+
</defs>`;
|
|
278
|
+
// ── Background — transparent, inherits page dark bg ──
|
|
279
|
+
const bg = `<rect width="${W}" height="${H}" rx="8" fill="none"/>`;
|
|
280
|
+
// ── Draw edges (curved bezier paths with arrows) ──
|
|
281
|
+
let edgesSvg = "";
|
|
258
282
|
for (const [path, info] of nodes) {
|
|
259
283
|
const from = positions.get(path);
|
|
260
284
|
if (!from)
|
|
@@ -263,22 +287,36 @@ export function generateArchSVG(details) {
|
|
|
263
287
|
const to = positions.get(imp);
|
|
264
288
|
if (!to)
|
|
265
289
|
continue;
|
|
266
|
-
const
|
|
267
|
-
|
|
290
|
+
const isCycle = cycleEdges.has(`${path}->${imp}`);
|
|
291
|
+
const isCross = info.dir !== graph[imp]?.dir;
|
|
292
|
+
const color = isCycle ? "#f9731680" : isCross ? "#ef444435" : "#818cf820";
|
|
293
|
+
const marker = isCycle ? "url(#ah-cycle)" : isCross ? "url(#ah-cross)" : "url(#ah)";
|
|
294
|
+
const width = isCycle ? "2" : "1.2";
|
|
295
|
+
const dash = isCycle ? ' stroke-dasharray="5,3"' : "";
|
|
296
|
+
// Bezier curve: offset control point sideways to avoid straight-line overlap
|
|
297
|
+
const dx = to.x - from.x;
|
|
298
|
+
const dy = to.y - from.y;
|
|
299
|
+
const cx1 = from.x + dx * 0.3 + (dy === 0 ? 0 : Math.sign(dx) * 15);
|
|
300
|
+
const cy1 = from.y + dy * 0.3;
|
|
301
|
+
const cx2 = to.x - dx * 0.3 + (dy === 0 ? 0 : Math.sign(dx) * 15);
|
|
302
|
+
const cy2 = to.y - dy * 0.3;
|
|
303
|
+
edgesSvg += `<path d="M${from.x},${from.y} C${cx1},${cy1} ${cx2},${cy2} ${to.x},${to.y}" fill="none" stroke="${color}" stroke-width="${width}" marker-end="${marker}"${dash}/>`;
|
|
268
304
|
}
|
|
269
305
|
}
|
|
270
|
-
// Draw directory groups
|
|
271
|
-
let
|
|
306
|
+
// ── Draw directory groups ──
|
|
307
|
+
let groupsSvg = "";
|
|
272
308
|
dirIdx = 0;
|
|
273
309
|
for (const [dName, paths] of dirEntries) {
|
|
274
310
|
const x = padding + dirIdx * dirWidth;
|
|
275
|
-
const h = paths.length *
|
|
276
|
-
|
|
277
|
-
|
|
311
|
+
const h = paths.length * nodeSpacing + 24;
|
|
312
|
+
groupsSvg += `<rect x="${x + 5}" y="${padding + 32}" width="${dirWidth - 10}" height="${h}" rx="8" fill="#ffffff06" stroke="#ffffff10"/>`;
|
|
313
|
+
const label = dName === "." ? "root" : dName.split("/").pop();
|
|
314
|
+
groupsSvg += `<text x="${x + dirWidth / 2}" y="${padding + 24}" text-anchor="middle" fill="#6b7280" font-size="10" font-weight="700" letter-spacing="0.03em">${label}</text>`;
|
|
278
315
|
dirIdx++;
|
|
279
316
|
}
|
|
280
|
-
// Draw nodes
|
|
317
|
+
// ── Draw nodes ──
|
|
281
318
|
let nodesSvg = "";
|
|
319
|
+
const godThreshold = Math.max(3, Math.floor(nodeCount * 0.5));
|
|
282
320
|
for (const [path] of nodes) {
|
|
283
321
|
const pos = positions.get(path);
|
|
284
322
|
if (!pos)
|
|
@@ -286,9 +324,183 @@ export function generateArchSVG(details) {
|
|
|
286
324
|
const name = basename(path, extname(path));
|
|
287
325
|
const info = graph[path];
|
|
288
326
|
const fanIn = info.importedBy.length;
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
327
|
+
const fanOut = info.imports.length;
|
|
328
|
+
// Node color based on health
|
|
329
|
+
const isGod = fanIn >= godThreshold;
|
|
330
|
+
const isOrphan = fanIn === 0 && !["index", "main", "cli", "App"].includes(name);
|
|
331
|
+
const isHighFanOut = fanOut > 10;
|
|
332
|
+
const isInCycle = [...cycleEdges].some((e) => e.startsWith(path + "->") || e.endsWith("->" + path));
|
|
333
|
+
let nodeColor = "#6d78d0"; // default: softer accent
|
|
334
|
+
if (isInCycle)
|
|
335
|
+
nodeColor = "#d97706"; // amber for cycle participant
|
|
336
|
+
else if (isGod)
|
|
337
|
+
nodeColor = "#dc2626"; // red for god module
|
|
338
|
+
else if (isOrphan)
|
|
339
|
+
nodeColor = "#4b5563"; // dim for orphan
|
|
340
|
+
else if (isHighFanOut)
|
|
341
|
+
nodeColor = "#ca8a04"; // yellow for high fan-out
|
|
342
|
+
const size = Math.min(9, 3 + Math.floor(fanIn * 0.8));
|
|
343
|
+
// Node circle with subtle glow for important nodes
|
|
344
|
+
if (isGod || isInCycle) {
|
|
345
|
+
nodesSvg += `<circle cx="${pos.x}" cy="${pos.y}" r="${size + 4}" fill="${nodeColor}" opacity="0.15"/>`;
|
|
346
|
+
}
|
|
347
|
+
nodesSvg += `<circle cx="${pos.x}" cy="${pos.y}" r="${size}" fill="${nodeColor}"/>`;
|
|
348
|
+
// Label
|
|
349
|
+
const labelColor = isOrphan ? "#4b5563" : "#9ca3af";
|
|
350
|
+
nodesSvg += `<text x="${pos.x + size + 5}" y="${pos.y + 3}" fill="${labelColor}" font-size="9" font-weight="${isGod ? "700" : "400"}">${name}</text>`;
|
|
351
|
+
// Fan-in/fan-out badge (only for notable nodes)
|
|
352
|
+
if (fanIn > 2 || fanOut > 5) {
|
|
353
|
+
nodesSvg += `<text x="${pos.x + size + 5}" y="${pos.y + 13}" fill="#555" font-size="7">${fanIn}\u2190 ${fanOut}\u2192</text>`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// ── Legend ──
|
|
357
|
+
const legendY = H - 30;
|
|
358
|
+
const legend = `<g transform="translate(${padding}, ${legendY})" font-size="8" fill="#6b7280">
|
|
359
|
+
<circle cx="0" cy="0" r="4" fill="#6d78d0"/><text x="8" y="3">module</text>
|
|
360
|
+
<circle cx="60" cy="0" r="4" fill="#dc2626"/><text x="68" y="3">god module</text>
|
|
361
|
+
<circle cx="140" cy="0" r="4" fill="#d97706"/><text x="148" y="3">in cycle</text>
|
|
362
|
+
<circle cx="200" cy="0" r="4" fill="#ca8a04"/><text x="208" y="3">high fan-out</text>
|
|
363
|
+
<circle cx="280" cy="0" r="4" fill="#4b5563"/><text x="288" y="3">orphan</text>
|
|
364
|
+
<line x1="330" y1="0" x2="350" y2="0" stroke="#ef444440" stroke-width="1.2"/><text x="354" y="3">cross-dir</text>
|
|
365
|
+
<line x1="410" y1="0" x2="430" y2="0" stroke="#d97706" stroke-width="1.5" stroke-dasharray="5,3"/><text x="434" y="3">circular</text>
|
|
366
|
+
</g>`;
|
|
367
|
+
return `<svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${W}px">${defs}${bg}${groupsSvg}${edgesSvg}${nodesSvg}${legend}</svg>`;
|
|
368
|
+
}
|
|
369
|
+
// ── Dependency Matrix (DSM) ──────────────────────────────────────────
|
|
370
|
+
// Standard software architecture visualization. Rows and columns are modules,
|
|
371
|
+
// cells show import relationships. Clusters on the diagonal = well-structured packages.
|
|
372
|
+
export function generateDSM(details) {
|
|
373
|
+
const graph = details.graph;
|
|
374
|
+
if (!graph || Object.keys(graph).length === 0)
|
|
375
|
+
return "";
|
|
376
|
+
const entries = Object.entries(graph);
|
|
377
|
+
if (entries.length > 40)
|
|
378
|
+
return `<div style="color:#6b7280;font-size:0.75rem">${entries.length} modules — too many for matrix view.</div>`;
|
|
379
|
+
if (entries.length < 3)
|
|
380
|
+
return "";
|
|
381
|
+
// Sort by directory then name for clustering
|
|
382
|
+
entries.sort((a, b) => `${a[1].dir}/${a[0]}`.localeCompare(`${b[1].dir}/${b[0]}`));
|
|
383
|
+
const paths = entries.map(([p]) => p);
|
|
384
|
+
const idx = new Map(paths.map((p, i) => [p, i]));
|
|
385
|
+
const n = paths.length;
|
|
386
|
+
const cell = 14;
|
|
387
|
+
const labelW = 110;
|
|
388
|
+
const W = labelW + n * cell + 10;
|
|
389
|
+
const H = labelW + n * cell + 10;
|
|
390
|
+
// Build adjacency
|
|
391
|
+
const matrix = Array.from({ length: n }, () => Array(n).fill(false));
|
|
392
|
+
for (const [path, info] of entries) {
|
|
393
|
+
const from = idx.get(path);
|
|
394
|
+
for (const imp of info.imports) {
|
|
395
|
+
const to = idx.get(imp);
|
|
396
|
+
if (to !== undefined)
|
|
397
|
+
matrix[from][to] = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
let svg = "";
|
|
401
|
+
const ox = labelW, oy = labelW;
|
|
402
|
+
// Grid
|
|
403
|
+
for (let i = 0; i <= n; i++) {
|
|
404
|
+
svg += `<line x1="${ox}" y1="${oy + i * cell}" x2="${ox + n * cell}" y2="${oy + i * cell}" stroke="#1e1e24" stroke-width="0.5"/>`;
|
|
405
|
+
svg += `<line x1="${ox + i * cell}" y1="${oy}" x2="${ox + i * cell}" y2="${oy + n * cell}" stroke="#1e1e24" stroke-width="0.5"/>`;
|
|
406
|
+
}
|
|
407
|
+
// Cells — row imports col
|
|
408
|
+
for (let r = 0; r < n; r++) {
|
|
409
|
+
for (let c = 0; c < n; c++) {
|
|
410
|
+
if (r === c) {
|
|
411
|
+
// Diagonal — highlight
|
|
412
|
+
svg += `<rect x="${ox + c * cell}" y="${oy + r * cell}" width="${cell}" height="${cell}" fill="#818cf808"/>`;
|
|
413
|
+
}
|
|
414
|
+
else if (matrix[r][c]) {
|
|
415
|
+
const mutual = matrix[c][r]; // circular?
|
|
416
|
+
const color = mutual ? "#d97706" : "#6d78d0";
|
|
417
|
+
svg += `<rect x="${ox + c * cell + 2}" y="${oy + r * cell + 2}" width="${cell - 4}" height="${cell - 4}" rx="2" fill="${color}" opacity="0.7"/>`;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Directory bands (background stripe per dir group)
|
|
422
|
+
let prevDir = "";
|
|
423
|
+
let bandStart = 0;
|
|
424
|
+
const dirColors = ["#ffffff04", "#ffffff08"];
|
|
425
|
+
let dirIdx2 = 0;
|
|
426
|
+
for (let i = 0; i <= n; i++) {
|
|
427
|
+
const dir = i < n ? entries[i][1].dir : "__end__";
|
|
428
|
+
if (dir !== prevDir && i > 0) {
|
|
429
|
+
const fill = dirColors[dirIdx2 % 2];
|
|
430
|
+
svg += `<rect x="${ox}" y="${oy + bandStart * cell}" width="${n * cell}" height="${(i - bandStart) * cell}" fill="${fill}"/>`;
|
|
431
|
+
svg += `<rect x="${ox + bandStart * cell}" y="${oy}" width="${(i - bandStart) * cell}" height="${n * cell}" fill="${fill}"/>`;
|
|
432
|
+
dirIdx2++;
|
|
433
|
+
bandStart = i;
|
|
434
|
+
}
|
|
435
|
+
prevDir = dir;
|
|
436
|
+
}
|
|
437
|
+
// Row labels (left) and column labels (top, rotated)
|
|
438
|
+
for (let i = 0; i < n; i++) {
|
|
439
|
+
const name = basename(paths[i], extname(paths[i]));
|
|
440
|
+
svg += `<text x="${ox - 4}" y="${oy + i * cell + cell / 2 + 3}" text-anchor="end" fill="#9ca3af" font-size="7">${name}</text>`;
|
|
441
|
+
svg += `<text x="${ox + i * cell + cell / 2}" y="${oy - 4}" text-anchor="start" fill="#9ca3af" font-size="7" transform="rotate(-60 ${ox + i * cell + cell / 2} ${oy - 4})">${name}</text>`;
|
|
442
|
+
}
|
|
443
|
+
// Legend
|
|
444
|
+
svg += `<g transform="translate(${ox}, ${oy + n * cell + 16})" font-size="7" fill="#6b7280">`;
|
|
445
|
+
svg += `<rect x="0" y="-4" width="8" height="8" rx="2" fill="#6d78d0" opacity="0.7"/><text x="12" y="3">imports</text>`;
|
|
446
|
+
svg += `<rect x="60" y="-4" width="8" height="8" rx="2" fill="#d97706" opacity="0.7"/><text x="72" y="3">mutual (cycle)</text>`;
|
|
447
|
+
svg += `</g>`;
|
|
448
|
+
return `<svg viewBox="0 0 ${W} ${H + 30}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${W}px">${svg}</svg>`;
|
|
449
|
+
}
|
|
450
|
+
// ── Package Nesting Diagram ──────────────────────────────────────────
|
|
451
|
+
// UML-style Package diagram: directories as nested boxes, files as items inside.
|
|
452
|
+
export function generatePackageDiagram(details) {
|
|
453
|
+
const graph = details.graph;
|
|
454
|
+
if (!graph || Object.keys(graph).length === 0)
|
|
455
|
+
return "";
|
|
456
|
+
const entries = Object.entries(graph);
|
|
457
|
+
if (entries.length > 50)
|
|
458
|
+
return `<div style="color:#6b7280;font-size:0.75rem">${entries.length} modules — too many for package view.</div>`;
|
|
459
|
+
// Group by directory
|
|
460
|
+
const dirs = new Map();
|
|
461
|
+
for (const [path, info] of entries) {
|
|
462
|
+
const dir = info.dir || ".";
|
|
463
|
+
const arr = dirs.get(dir) || [];
|
|
464
|
+
arr.push({ path, fanIn: info.importedBy.length, fanOut: info.imports.length });
|
|
465
|
+
dirs.set(dir, arr);
|
|
466
|
+
}
|
|
467
|
+
const dirEntries = [...dirs.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
468
|
+
const boxW = 180;
|
|
469
|
+
const fileH = 18;
|
|
470
|
+
const headerH = 24;
|
|
471
|
+
const gap = 16;
|
|
472
|
+
const cols = Math.min(dirEntries.length, 4);
|
|
473
|
+
const colW = boxW + gap;
|
|
474
|
+
let svg = "";
|
|
475
|
+
let maxH = 0;
|
|
476
|
+
for (let i = 0; i < dirEntries.length; i++) {
|
|
477
|
+
const [dir, files] = dirEntries[i];
|
|
478
|
+
const col = i % cols;
|
|
479
|
+
const row = Math.floor(i / cols);
|
|
480
|
+
const prevRowsH = row * 300; // rough estimate, will adjust
|
|
481
|
+
const x = gap + col * colW;
|
|
482
|
+
let y = gap + prevRowsH;
|
|
483
|
+
const boxH = headerH + files.length * fileH + 8;
|
|
484
|
+
// Package box
|
|
485
|
+
svg += `<rect x="${x}" y="${y}" width="${boxW}" height="${boxH}" rx="6" fill="#ffffff04" stroke="#ffffff10"/>`;
|
|
486
|
+
// Package tab (UML-style)
|
|
487
|
+
svg += `<rect x="${x}" y="${y}" width="${Math.min(boxW * 0.6, 100)}" height="${headerH}" rx="4" fill="#ffffff08" stroke="#ffffff10"/>`;
|
|
488
|
+
const label = dir === "." ? "root" : dir.replace(/^src\//, "");
|
|
489
|
+
svg += `<text x="${x + 8}" y="${y + 16}" fill="#9ca3af" font-size="10" font-weight="700">${label}/</text>`;
|
|
490
|
+
svg += `<text x="${x + boxW - 8}" y="${y + 16}" text-anchor="end" fill="#4b5563" font-size="8">${files.length}</text>`;
|
|
491
|
+
y += headerH + 4;
|
|
492
|
+
// Files inside package
|
|
493
|
+
for (const f of files) {
|
|
494
|
+
const name = basename(f.path, extname(f.path));
|
|
495
|
+
const health = f.fanIn > 5 ? "#d97706" : f.fanOut > 8 ? "#ca8a04" : "#6d78d0";
|
|
496
|
+
svg += `<circle cx="${x + 12}" cy="${y + 7}" r="3" fill="${health}"/>`;
|
|
497
|
+
svg += `<text x="${x + 20}" y="${y + 10}" fill="#9ca3af" font-size="8">${name}</text>`;
|
|
498
|
+
svg += `<text x="${x + boxW - 8}" y="${y + 10}" text-anchor="end" fill="#4b5563" font-size="7">${f.fanIn}\u2190 ${f.fanOut}\u2192</text>`;
|
|
499
|
+
y += fileH;
|
|
500
|
+
}
|
|
501
|
+
maxH = Math.max(maxH, y + 8);
|
|
292
502
|
}
|
|
293
|
-
|
|
503
|
+
const W = gap + cols * colW;
|
|
504
|
+
const H = maxH + gap;
|
|
505
|
+
return `<svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${W}px">${svg}</svg>`;
|
|
294
506
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Code Coherence — detects internal contradictions and inconsistencies in the codebase.
|
|
2
|
+
*
|
|
3
|
+
* Premium feature (powered by LLM). Analyzes the codebase for patterns where
|
|
4
|
+
* different parts of the code contradict each other:
|
|
5
|
+
* - Function A validates input X, but function B that calls A re-validates X differently
|
|
6
|
+
* - Type says field is required, but all usages treat it as optional
|
|
7
|
+
* - Error handling is strict in module A but permissive in module B for the same operations
|
|
8
|
+
* - Naming conventions differ across modules (camelCase vs snake_case for same concepts)
|
|
9
|
+
* - Two implementations of the same algorithm with different behavior
|
|
10
|
+
* - Config declares a feature flag but no code reads it
|
|
11
|
+
* - Dead branches: conditions that can never be true given the types
|
|
12
|
+
* - Contradictory defaults (module A defaults timeout to 5s, module B to 30s)
|
|
13
|
+
*
|
|
14
|
+
* Currently returns a "coming soon" placeholder.
|
|
15
|
+
*/
|
|
16
|
+
import type { CheckResult } from "../types.js";
|
|
17
|
+
export declare function runCodeCoherence(cwd: string): CheckResult;
|