Skip to content

Commit 4b7cb45

Browse files
aksOpsclaude
andauthored
release: v0.2.0 — frontend rewrite, ActiveMQ detector, mode=none default, drill-down treemap (#112)
* perf(detectors): quick-reject pre-screen on auth detectors (-31% detector CPU) Profiling on a 30K-file polyglot fixture (kept at ~/projects/polyglot-bench: spring-petclinic-microservices, airflow, istio, eShop, angular/components, nuxt, actix/examples, ktor-samples, nlohmann/json, play-samples, PSScriptAnalyzer, terraform-aws-eks; 14 distinct languages) showed the three cross-cutting auth detectors burning 55% of all detector CPU because they ran the lines × patterns double loop on every supported-language file — even files with zero auth keywords. Fix: per-detector PRE_SCREEN Pattern with all distinctive literal substrings of the underlying patterns. One regex pass over file content; if no keyword present, the file cannot match — short-circuit before the line loop. Measured impact (JFR ExecutionSample, JDK 25, polyglot fixture): CertificateAuthDetector: 244 → 147 samples (-39.8%, -0.97s CPU) SessionHeaderAuthDetector: 206 → 43 samples (-79.1%, -1.63s CPU) LdapAuthDetector: 47 → 25 samples (-46.8%, -0.22s CPU) Auth subtotal: 497 → 215 samples (-56.7%, -2.82s) All detectors total: 902 → 624 samples (-30.8%, -2.78s) Detection semantics unchanged — pre-screen rejects only files where no underlying pattern can match (keyword absent). Tests covering keyword-bearing fixtures pass through pre-screen and run the existing logic byte-for-byte. Tests: 3689 / 0 failures / 0 errors / 32 skipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ui): migrate frontend from AntD to @ossrandom/design-system Full UI rewrite onto @ossrandom/design-system v0.3.0 (npm). Replaces Ant Design + ECharts + @ant-design/icons with the design-system primitives + charts subpath + an inline-SVG icon set. Surface changes: - AppShell-based header with a single IconButton theme toggle and a monospace MCP-URL pill (copy-to-clipboard). - Stats grid replaced with a CSS auto-fit grid (mobile-responsive). - File-tree treemap renders via @ossrandom/design-system/charts (d3-hierarchy, canvas engine, maxDepth=1) with an explicit drill-down / drill-up affordance via a breadcrumb row; treemap layout is deterministic (children sorted alphabetically, fixed height, single remount via key when focusPath changes). - File viewer drawer kept; MCP Tools sidebar removed (the design-system Menu's nested-inline expansion didn't match the prior UX). - Three orphan pages deleted (Explorer.tsx, McpConsole.tsx, CodebaseMap.tsx) — none were routed in App.tsx. - index.css trimmed to body reset + layout-only classes using design-system CSS tokens; `.rcs-treemap-engine-badge` hidden via a single rule. - vite.config.ts: manualChunks reorganised (vendor-ds / vendor-ds-charts replace vendor-antd / vendor-echarts). Bundled fonts and the deck.gl tesselator chunk live alongside the hashed JS in src/main/resources/static/assets/ — air-gapped requirement per build.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(auth): allow_unauthenticated bypass actually grants /api access When `mcp.auth.mode=none` AND `allow_unauthenticated=true`, the filter used to call chain.doFilter(...) without setting an Authentication on the SecurityContext. The SecurityFilterChain has .requestMatchers("/api/**", "/mcp/**", "/actuator/**").authenticated() .anonymous().disable() so requests reaching the controllers without a Principal were rejected with 403 — the "unauthenticated" escape hatch was inert. Bypass branch now installs a fake PreAuthenticatedAuthenticationToken with role ROLE_MCP_CLIENT (mirroring the bearer-success path) and clears the context in finally, so the chain's authenticated() rule passes. Behavioural impact only when allow_unauthenticated=true; bearer mode unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(auth)!: default mcp.auth.mode=none with allow_unauthenticated=true Built-in defaults now produce a server that starts unauthenticated out of the box. The fail-fast safety valve still works: explicitly setting mcp.auth.allow_unauthenticated=false in codeiq.yml (or env) makes mode=none refuse to start under the serving profile. ConfigDefaults previously emitted new McpAuthConfig("none", "CODEIQ_MCP_TOKEN", null, null) which evaluated to allowUnauthenticated=false at runtime; combined with TokenResolver's serving-profile gate, this hard-failed every fresh install on `codeiq serve`. Operators wanting auth in production opt in explicitly via mode=bearer (and CODEIQ_MCP_TOKEN env var). McpAuthConfig javadoc updated to document the new default semantics: allow_unauthenticated is now an opt-in *harden* flag (set false to reject mode=none) rather than an opt-in *escape hatch*. BREAKING (defaults): a fresh install no longer prompts for or enforces a token. Upgraded deployments that already set mode=bearer in codeiq.yml are unaffected. To preserve the prior fail-fast behaviour, set mcp.auth.allow_unauthenticated: false explicitly. Tests: TokenResolverTest, ConfigDefaultsTest, ServingChainIntegrationTest, UnifiedConfigAdapterTest — 27/27 pass. Full suite: 3706 / 0 / 32. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(messaging): Apache ActiveMQ Classic + Artemis detector Adds io.github.randomcodespace.iq.detector.jvm.java.ActiveMqDetector covering both ActiveMQ Classic (org.apache.activemq.*) and ActiveMQ Artemis (org.apache.activemq.artemis.*). Both products ship a class literally named ActiveMQConnectionFactory, so the broker flavour is disambiguated by import / FQN before emission. Coverage: - Connection factories: ActiveMQConnectionFactory, ActiveMQQueueConnectionFactory, ActiveMQTopicConnectionFactory, ActiveMQJMSConnectionFactory, ActiveMQXAConnectionFactory, PooledConnectionFactory. - Transport URLs: tcp/ssl/nio/udp/vm/amqp/stomp/mqtt/ws/wss with the optional +nio / +ssl modifiers, plus failover:(...) / failover:tcp:... (the failover transport uses `:(` rather than `://`, handled as a separate alternation in BROKER_URL_RE). - Direct destination instantiation: new ActiveMQQueue("..."), new ActiveMQTopic("..."). - session.createQueue("...") / session.createTopic("...") only attributed to ActiveMQ when the file already mentions an AMQ import or class ref — avoids double-counting against JmsDetector. - Spring Boot config keys: spring.activemq.broker-url and spring.artemis.broker-url in application.properties / application.yml (no class context — emits the broker MESSAGE_QUEUE node only). Emits NodeKind {MESSAGE_QUEUE, QUEUE, TOPIC} with broker properties ("activemq" or "activemq_artemis"), plus EdgeKind {CONNECTS_TO, SENDS_TO, RECEIVES_FROM} — same shape as IbmMqDetector and TibcoEmsDetector so the topology view composes cleanly. Tests: 17 cases covering metadata, early-exit, Classic + Artemis, named queue/topic producer/consumer, transport URL variants (failover, pooled), Spring Boot config keys, negative double-counting against JmsDetector, and a determinism fixture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(health): make `codeiq serve` start cleanly under Spring Boot 4 Two coupled changes that together let `serve` boot from defaults without -D / env / config overrides. 1. GraphHealthIndicator: @ConditionalOnBean(GraphStore.class) → @Profile("serving"). Spring documents @ConditionalOnBean as fragile on user @component classes (its evaluation depends on bean-definition ordering during scan). The serving profile already guarantees GraphStore is present; @Profile activates earlier and deterministic- ally so the readiness-group reference resolves on a clean install. 2. application.yml: management.endpoint.health.validate-group-membership set to false. Spring Boot 4 enabled strict group-membership validation by default — startup fails when a `health.group.*.include` references a profile-conditional bean that hasn't yet been added to the registry when the management endpoint config loads. The serving profile's readiness include of `graphHealthIndicator` consistently tripped this. Disabled per the error message's own remediation hint; missing health contributors are silently skipped at probe time, which is the desired runtime behaviour. Verified: `java -jar code-iq-cli.jar serve <path> -p 37779` with no flags / env / codeiq.yml — port binds, /api/stats returns data without auth, /actuator/health/readiness returns {"status":"UP"}. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(build): regenerate package-lock.json against npm registry The previous lockfile pointed @ossrandom/design-system at a local filesystem path (`file:../../../../design-system`, link:true) — a holdover from the local-checkout development of the design-system. On CI that path doesn't exist, so `npm install` left the dep unresolved and `vite build` failed with TS2307 "Cannot find module '@ossrandom/design-system'". Regenerated lockfile resolves to the published 0.3.0 tarball: https://registry.npmjs.org/@ossrandom/design-system/-/design-system-0.3.0.tgz Verified locally: `rm -rf node_modules && npm install` clean, `npm run build` produces the same bundle modulo content-hash filenames. The two new design-system-*.js chunks reflect the registry-resolved artifact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3bc3ebf commit 4b7cb45

24 files changed

Lines changed: 3269 additions & 2669 deletions

src/main/frontend/package-lock.json

Lines changed: 232 additions & 1156 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/frontend/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
"test:e2e:report": "playwright show-report"
1313
},
1414
"dependencies": {
15-
"@ant-design/icons": "^6.2.1",
16-
"antd": "^6.3.7",
17-
"echarts": "^6.0.0",
18-
"echarts-for-react": "^3.0.2",
15+
"@ossrandom/design-system": "^0.3.0",
16+
"d3-hierarchy": "^3.1.2",
1917
"react": "^19.2.5",
2018
"react-dom": "^19.2.5",
2119
"react-router-dom": "^7.1.5"
Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
11
import { Outlet } from 'react-router-dom';
2-
import { Layout, Switch, Typography, Space } from 'antd';
3-
import { SunOutlined, MoonOutlined } from '@ant-design/icons';
2+
import { useState } from 'react';
3+
import { AppShell, IconButton } from '@ossrandom/design-system';
44
import { useTheme } from '@/context/ThemeContext';
55

6-
const { Header, Content } = Layout;
6+
function SunIcon() {
7+
return (
8+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
9+
<circle cx="12" cy="12" r="4" />
10+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
11+
</svg>
12+
);
13+
}
714

8-
export default function AppLayout() {
15+
function MoonIcon() {
16+
return (
17+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
18+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
19+
</svg>
20+
);
21+
}
22+
23+
function CopyIcon({ ok }: { ok: boolean }) {
24+
if (ok) {
25+
return (
26+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
27+
<path d="M20 6L9 17l-5-5" />
28+
</svg>
29+
);
30+
}
31+
return (
32+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
33+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
34+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
35+
</svg>
36+
);
37+
}
38+
39+
function McpUrlPill() {
40+
const [copied, setCopied] = useState(false);
41+
const url = (typeof window !== 'undefined' ? window.location.origin : '') + '/mcp';
42+
const onCopy = async () => {
43+
try {
44+
await navigator.clipboard.writeText(url);
45+
setCopied(true);
46+
setTimeout(() => setCopied(false), 1200);
47+
} catch {
48+
/* clipboard blocked — silent */
49+
}
50+
};
51+
return (
52+
<div className="codeiq-mcp-url" title={url}>
53+
<span>MCP&nbsp;·&nbsp;{url}</span>
54+
<button onClick={onCopy} aria-label="Copy MCP URL" type="button">
55+
<CopyIcon ok={copied} />
56+
</button>
57+
</div>
58+
);
59+
}
60+
61+
function Header() {
962
const { isDark, toggle } = useTheme();
63+
return (
64+
<div className="codeiq-header">
65+
<div className="codeiq-brand">Code IQ</div>
66+
<div className="codeiq-header-actions">
67+
<McpUrlPill />
68+
<IconButton
69+
icon={isDark ? <SunIcon /> : <MoonIcon />}
70+
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
71+
variant="ghost"
72+
size="sm"
73+
onClick={toggle}
74+
/>
75+
</div>
76+
</div>
77+
);
78+
}
1079

80+
export default function AppLayout() {
1181
return (
12-
<Layout style={{ minHeight: '100vh' }}>
13-
<Header
14-
style={{
15-
padding: '0 24px',
16-
display: 'flex',
17-
alignItems: 'center',
18-
justifyContent: 'space-between',
19-
position: 'sticky',
20-
top: 0,
21-
zIndex: 100,
22-
borderBottom: isDark ? '1px solid #303030' : '1px solid #e8e8e8',
23-
}}
24-
>
25-
<Typography.Title
26-
level={4}
27-
style={{ color: '#2563eb', margin: 0, whiteSpace: 'nowrap', lineHeight: '64px' }}
28-
>
29-
Code IQ
30-
</Typography.Title>
31-
<Space>
32-
<Switch
33-
checked={isDark}
34-
onChange={toggle}
35-
checkedChildren={<MoonOutlined />}
36-
unCheckedChildren={<SunOutlined />}
37-
/>
38-
</Space>
39-
</Header>
40-
<Content style={{ padding: '16px 24px', overflow: 'auto' }}>
82+
<AppShell header={<Header />}>
83+
<div className="codeiq-content">
4184
<Outlet />
42-
</Content>
43-
</Layout>
85+
</div>
86+
</AppShell>
4487
);
4588
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { CSSProperties } from 'react';
2+
3+
const base: CSSProperties = { display: 'inline-block', verticalAlign: '-2px' };
4+
5+
function Svg({ d, size = 14 }: { d: string; size?: number }) {
6+
return (
7+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
8+
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={base}>
9+
<path d={d} />
10+
</svg>
11+
);
12+
}
13+
14+
export const Icon = {
15+
Nodes: () => <Svg d="M5 12h14M12 5v14M6 6l12 12M6 18l12-12" />,
16+
Branches: () => <Svg d="M6 3v12a3 3 0 0 0 3 3h6M6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />,
17+
File: () => <Svg d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6" />,
18+
Code: () => <Svg d="M16 18l6-6-6-6M8 6l-6 6 6 6" />,
19+
Api: () => <Svg d="M2 12h6M16 12h6M12 2v6M12 16v6M5 5l4.5 4.5M14.5 14.5L19 19M5 19l4.5-4.5M14.5 9.5L19 5" />,
20+
Safety: () => <Svg d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />,
21+
Appstore: () => <Svg d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z" />,
22+
Build: () => <Svg d="M14.7 6.3a4 4 0 0 0-5.4 5.4l-7 7 2 2 7-7a4 4 0 0 0 5.4-5.4l-3 3-1.5-1.5z" />,
23+
Play: () => <Svg d="M5 3l14 9-14 9V3z" />,
24+
Clock: () => <Svg d="M12 6v6l4 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />,
25+
Search: () => <Svg d="M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.35-4.35" />,
26+
History: () => <Svg d="M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5M12 7v5l3 2" />,
27+
};

src/main/frontend/src/index.css

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,133 @@ body {
22
margin: 0;
33
padding: 0;
44
}
5-
/* Ant Design handles all theming */
6-
7-
/* MCP tool list: allow multiline descriptions */
8-
.mcp-tool-menu .ant-menu-item {
9-
height: auto !important;
10-
line-height: normal !important;
11-
padding-top: 4px !important;
12-
padding-bottom: 4px !important;
13-
white-space: normal !important;
5+
6+
/* Layout-only — visuals come from design-system tokens. */
7+
.codeiq-header {
8+
display: flex;
9+
align-items: center;
10+
justify-content: space-between;
11+
gap: 12px;
12+
padding: 0 16px;
13+
height: 56px;
14+
}
15+
16+
.codeiq-brand {
17+
color: var(--accent);
18+
font-size: var(--fs-h4);
19+
font-weight: var(--fw-semibold);
20+
letter-spacing: -0.01em;
21+
white-space: nowrap;
22+
}
23+
24+
.codeiq-content {
25+
padding: 16px;
26+
}
27+
28+
.codeiq-header-actions {
29+
display: flex;
30+
align-items: center;
31+
gap: 8px;
32+
min-width: 0;
33+
}
34+
35+
.codeiq-mcp-url {
36+
display: inline-flex;
37+
align-items: center;
38+
gap: 6px;
39+
padding: 4px 8px;
40+
border: 1px solid var(--border-1);
41+
border-radius: 6px;
42+
background: var(--bg-2);
43+
color: var(--fg-2);
44+
font-family: var(--font-mono);
45+
font-size: 12px;
46+
max-width: 360px;
47+
overflow: hidden;
48+
}
49+
50+
.codeiq-mcp-url > span {
51+
overflow: hidden;
52+
text-overflow: ellipsis;
53+
white-space: nowrap;
54+
}
55+
56+
.codeiq-mcp-url > button {
57+
flex: none;
58+
background: transparent;
59+
border: 0;
60+
padding: 2px;
61+
color: var(--fg-3);
62+
cursor: pointer;
63+
border-radius: 4px;
64+
}
65+
66+
.codeiq-mcp-url > button:hover {
67+
color: var(--fg-1);
68+
background: var(--bg-3);
69+
}
70+
71+
/* Stats grid — auto-fit so cards flow on mobile. */
72+
.codeiq-stats-grid {
73+
display: grid;
74+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
75+
gap: 8px;
76+
}
77+
78+
/* Hide the design-system Treemap's engine badge (renders "canvas"/"webgl"
79+
in a corner — debug affordance, not desired in app chrome). */
80+
.rcs-treemap-engine-badge {
81+
display: none !important;
82+
}
83+
84+
.codeiq-breadcrumb {
85+
display: flex;
86+
align-items: center;
87+
flex-wrap: wrap;
88+
gap: 4px;
89+
padding: 6px 10px;
90+
border: 1px solid var(--border-1);
91+
border-radius: 6px;
92+
background: var(--bg-1);
93+
font-size: 12px;
94+
color: var(--fg-2);
95+
font-family: var(--font-mono);
96+
}
97+
98+
.codeiq-breadcrumb button {
99+
background: transparent;
100+
border: 0;
101+
padding: 2px 6px;
102+
border-radius: 4px;
103+
color: inherit;
104+
font-family: inherit;
105+
font-size: inherit;
106+
cursor: pointer;
107+
}
108+
109+
.codeiq-breadcrumb button:hover:not(:disabled) {
110+
background: var(--bg-3);
111+
color: var(--fg-1);
112+
}
113+
114+
.codeiq-breadcrumb button:disabled {
115+
cursor: default;
116+
opacity: 0.7;
117+
}
118+
119+
.codeiq-breadcrumb-sep {
120+
opacity: 0.4;
121+
user-select: none;
122+
}
123+
124+
@media (max-width: 600px) {
125+
.codeiq-header {
126+
padding: 0 12px;
127+
}
128+
.codeiq-content {
129+
padding: 12px;
130+
}
131+
.codeiq-mcp-url {
132+
display: none;
133+
}
14134
}

0 commit comments

Comments
 (0)