<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aqiron Security</title>
    <description>The latest articles on DEV Community by Aqiron Security (aqiron-security).</description>
    <link>https://dev.to/aqiron-security</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14893%2F0428b2d0-36a7-450e-a419-b394189a3ab8.png</url>
      <title>DEV Community: Aqiron Security</title>
      <link>https://dev.to/aqiron-security</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aqiron-security"/>
    <language>en</language>
    <item>
      <title>Separating a VS Code Extension from a TypeScript Core: Architecture Lessons from Aqiron Security</title>
      <dc:creator>Developer Open</dc:creator>
      <pubDate>Wed, 23 Sep 2026 14:51:53 +0000</pubDate>
      <link>https://dev.to/aqiron-security/separating-a-vs-code-extension-from-a-typescript-core-architecture-lessons-from-aqiron-security-4mc5</link>
      <guid>https://dev.to/aqiron-security/separating-a-vs-code-extension-from-a-typescript-core-architecture-lessons-from-aqiron-security-4mc5</guid>
      <description>&lt;p&gt;When I started building Aqiron Security, the natural approach was to keep the security logic inside the VS Code extension.&lt;/p&gt;

&lt;p&gt;That works.&lt;/p&gt;

&lt;p&gt;At least initially.&lt;/p&gt;

&lt;p&gt;But as the project started accumulating scanner orchestration, finding normalization, correlation, project intelligence, RAG, reporting, and AI operations, I started running into a deeper architectural question:&lt;/p&gt;

&lt;p&gt;Should the VS Code extension actually own the application's core runtime?&lt;/p&gt;

&lt;p&gt;I decided the answer should be no.&lt;/p&gt;

&lt;p&gt;Aqiron Security is an open-source application security project. The current product is a VS Code extension, but I wanted the security/runtime layer to have a much cleaner boundary from the client.&lt;/p&gt;

&lt;p&gt;The result is a design where the extension acts as the client and starts a separate Node.js process containing the TypeScript core.&lt;/p&gt;

&lt;p&gt;This article explains why I made that separation, how the current architecture works, and some of the trade-offs I have encountered.&lt;/p&gt;

&lt;p&gt;The original problem&lt;/p&gt;

&lt;p&gt;A VS Code extension has a lot of responsibilities already.&lt;/p&gt;

&lt;p&gt;It deals with things like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;extension activation&lt;br&gt;
commands&lt;br&gt;
diagnostics&lt;br&gt;
the VS Code API&lt;br&gt;
webview communication&lt;br&gt;
editor state&lt;br&gt;
user settings&lt;br&gt;
UI lifecycle&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Security scanning introduces another class of responsibilities:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;starting external tools&lt;br&gt;
parsing scanner output&lt;br&gt;
normalizing findings&lt;br&gt;
correlating duplicate results&lt;br&gt;
generating reports&lt;br&gt;
managing long-running operations&lt;br&gt;
cancellation&lt;br&gt;
AI operations&lt;br&gt;
workspace analysis&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Putting all of that into one runtime makes the boundary blurry.&lt;/p&gt;

&lt;p&gt;The code may still work, but over time the architecture starts answering questions like:&lt;/p&gt;

&lt;p&gt;"Does this security service need VS Code?"&lt;/p&gt;

&lt;p&gt;That is a dangerous dependency to create if the answer does not actually need to be yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The separation
&lt;/h2&gt;

&lt;p&gt;The current Aqiron architecture looks roughly like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;┌──────────────────────────────────────────────┐&lt;br&gt;
│              VS Code Extension                │&lt;br&gt;
│                                              │&lt;br&gt;
│ activation / commands / diagnostics          │&lt;br&gt;
│ React webview / settings / workspace UI      │&lt;br&gt;
│ ScanController / client-side services        │&lt;br&gt;
└──────────────────────┬───────────────────────┘&lt;br&gt;
                       │&lt;br&gt;
                       │ CoreClient&lt;br&gt;
                       │ CoreProcessManager&lt;br&gt;
                       │&lt;br&gt;
                       │ newline-delimited JSON&lt;br&gt;
                       ▼&lt;br&gt;
┌──────────────────────────────────────────────┐&lt;br&gt;
│            Aqiron Core Runtime               │&lt;br&gt;
│            Node.js + TypeScript              │&lt;br&gt;
│                                              │&lt;br&gt;
│ protocol / cancellation / adapters           │&lt;br&gt;
│ scanning / analysis / correlation            │&lt;br&gt;
│ reports / RAG / AI operations                │&lt;br&gt;
└──────────────────────┬───────────────────────┘&lt;br&gt;
                       │&lt;br&gt;
          ┌────────────┴────────────┐&lt;br&gt;
          ▼                         ▼&lt;br&gt;
   Native Aqiron rules       External scanners&lt;br&gt;
                             Trivy / Semgrep&lt;br&gt;
                             OSV-Scanner /&lt;br&gt;
                             Betterleaks / MobSF&lt;br&gt;
          │                         │&lt;br&gt;
          └────────────┬────────────┘&lt;br&gt;
                       ▼&lt;br&gt;
                Unified findings&lt;br&gt;
                       ▼&lt;br&gt;
             correlation + graph&lt;br&gt;
                       ▼&lt;br&gt;
                  reports&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is not a cloud architecture.&lt;/p&gt;

&lt;p&gt;The current project is local-first. The extension starts the core process locally and communicates with it over standard input/output. The repository currently keeps &lt;code&gt;packages/core&lt;/code&gt; private and bundles it into the extension rather than publishing it as a separate npm package.&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;The boundary exists today, but the eventual product packaging can evolve later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a separate process?
&lt;/h2&gt;

&lt;p&gt;There were several reasons.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Keep the core independent from VS Code
&lt;/h2&gt;

&lt;p&gt;The first reason is architectural independence.&lt;/p&gt;

&lt;p&gt;The core should not need to know that VS Code exists.&lt;/p&gt;

&lt;p&gt;Ideally, security logic should be able to operate on concepts like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;workspace&lt;br&gt;
scan&lt;br&gt;
finding&lt;br&gt;
project&lt;br&gt;
report&lt;br&gt;
AI request&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;rather than:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vscode.workspace&lt;br&gt;
vscode.window&lt;br&gt;
WebviewPanel&lt;br&gt;
TextDocument&lt;br&gt;
DiagnosticCollection&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The extension is responsible for translating between the developer environment and the core.&lt;/p&gt;

&lt;p&gt;That gives me a much cleaner dependency direction:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VS Code client&lt;br&gt;
      ↓&lt;br&gt;
    Core&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;rather than:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VS Code ↔ Security logic ↔ VS Code&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Process isolation gives us a real boundary
&lt;/h2&gt;

&lt;p&gt;Using a separate process also creates a runtime boundary.&lt;/p&gt;

&lt;p&gt;The extension host and the security core no longer execute as one giant logical process.&lt;/p&gt;

&lt;p&gt;That matters for long-running operations.&lt;/p&gt;

&lt;p&gt;A scan might involve:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;discover files&lt;br&gt;
      ↓&lt;br&gt;
run multiple tools&lt;br&gt;
      ↓&lt;br&gt;
parse results&lt;br&gt;
      ↓&lt;br&gt;
normalize findings&lt;br&gt;
      ↓&lt;br&gt;
correlate findings&lt;br&gt;
      ↓&lt;br&gt;
build relationships&lt;br&gt;
      ↓&lt;br&gt;
generate reports&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's a very different workload from handling an editor command or updating a sidebar.&lt;/p&gt;

&lt;p&gt;With a separate core process, the extension can treat the security engine more like a service.&lt;/p&gt;

&lt;p&gt;That makes lifecycle handling, restart behavior, and failure boundaries easier to reason about.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. IPC forces us to define a contract
&lt;/h2&gt;

&lt;p&gt;This was probably the most valuable part of the architecture.&lt;/p&gt;

&lt;p&gt;Once the extension and core became separate processes, they couldn't casually call each other's internal functions anymore.&lt;/p&gt;

&lt;p&gt;They needed a protocol.&lt;/p&gt;

&lt;p&gt;The current protocol is intentionally simple:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stdin/stdout&lt;br&gt;
+&lt;br&gt;
newline-delimited JSON&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A request looks conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CoreRequestMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CoreResponseMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;CoreProtocolError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also event messages for asynchronous pipeline updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CoreEventMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual protocol also has a versioned handshake.&lt;/p&gt;

&lt;p&gt;For example, the current runtime exposes a protocol version and core version and can report compatibility states such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;compatible&lt;br&gt;
protocol-mismatch&lt;br&gt;
extension-too-old&lt;br&gt;
core-too-old&lt;br&gt;
unsupported&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That gives us an explicit compatibility boundary instead of relying on both sides silently assuming they agree.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why newline-delimited JSON?
&lt;/h2&gt;

&lt;p&gt;I deliberately didn't start with something complicated.&lt;/p&gt;

&lt;p&gt;The current transport is essentially:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;message 1\n&lt;br&gt;
message 2\n&lt;br&gt;
message 3\n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;where each line contains one JSON message.&lt;/p&gt;

&lt;p&gt;That gives us a few useful properties:&lt;/p&gt;
&lt;h2&gt;
  
  
  Easy to inspect
&lt;/h2&gt;

&lt;p&gt;You can literally look at the communication stream.&lt;/p&gt;
&lt;h2&gt;
  
  
  Easy to debug
&lt;/h2&gt;

&lt;p&gt;Malformed input can be identified and rejected.&lt;/p&gt;
&lt;h2&gt;
  
  
  No additional server required
&lt;/h2&gt;

&lt;p&gt;The extension starts the process locally and communicates through stdio.&lt;/p&gt;
&lt;h2&gt;
  
  
  Language-neutral at the protocol level
&lt;/h2&gt;

&lt;p&gt;The wire format is JSON rather than TypeScript-specific objects.&lt;/p&gt;

&lt;p&gt;That last point matters.&lt;/p&gt;

&lt;p&gt;The implementation is TypeScript, but the protocol doesn't fundamentally need to be.&lt;/p&gt;
&lt;h2&gt;
  
  
  Correlation IDs become important
&lt;/h2&gt;

&lt;p&gt;Once requests and responses cross a process boundary, we need a way to know which response belongs to which request.&lt;/p&gt;

&lt;p&gt;That's why requests carry an ID.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;request id: abc123&lt;br&gt;
method: scan.start&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The response can return:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;id: abc123&lt;br&gt;
success: true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Without this, concurrent operations become painful to reason about.&lt;/p&gt;

&lt;p&gt;The ID becomes the connection between:&lt;/p&gt;

&lt;p&gt;request&lt;br&gt;
   ↓&lt;br&gt;
core operation&lt;br&gt;
   ↓&lt;br&gt;
response&lt;/p&gt;

&lt;p&gt;and also gives us something useful for cancellation and pipeline events.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cancellation is part of the architecture
&lt;/h2&gt;

&lt;p&gt;Security operations shouldn't be treated as unstoppable functions.&lt;/p&gt;

&lt;p&gt;Imagine starting a deep scan and then closing the workspace.&lt;/p&gt;

&lt;p&gt;Or starting an AI analysis and then deciding you don't need it anymore.&lt;/p&gt;

&lt;p&gt;The architecture therefore includes explicit cancellation operations.&lt;/p&gt;

&lt;p&gt;The current core protocol exposes operations such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;core.cancel&lt;br&gt;
scan.cancel&lt;br&gt;
ai.cancel&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and the runtime propagates cancellation through the relevant cancellation sources and scanner context.&lt;/p&gt;

&lt;p&gt;This is one of those details that seems unnecessary until you have a real long-running operation.&lt;/p&gt;

&lt;p&gt;Then it becomes essential.&lt;/p&gt;
&lt;h2&gt;
  
  
  The extension should not know how scanning works
&lt;/h2&gt;

&lt;p&gt;One of the goals of the boundary is to let the client ask for a scan without knowing the implementation details.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;coreClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startScan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;workspaceRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deep&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;trusted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension doesn't need to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;which scanners are installed&lt;br&gt;
how scanner output is parsed&lt;br&gt;
how findings are normalized&lt;br&gt;
how correlation works&lt;br&gt;
how reports are generated&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those concerns belong to the core pipeline.&lt;/p&gt;

&lt;p&gt;The current core pipeline can combine native Aqiron rules with optional external scanners, normalize results into &lt;code&gt;UnifiedFinding[]&lt;/code&gt;, then pass them through correlation/graph processing and report generation.&lt;/p&gt;

&lt;p&gt;That separation is the main reason I like this architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding model becomes the shared language
&lt;/h2&gt;

&lt;p&gt;A scanner may produce one format.&lt;/p&gt;

&lt;p&gt;Another scanner produces something completely different.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Scanner A&lt;br&gt;
severity = HIGH&lt;br&gt;
file = foo.dart&lt;br&gt;
line = 41&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;while another might report:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Scanner B&lt;br&gt;
level = error&lt;br&gt;
path = foo.dart&lt;br&gt;
startLine = 41&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The core shouldn't force the rest of the system to understand every scanner's native format.&lt;/p&gt;

&lt;p&gt;Instead, scanner-specific parsers convert the output into a common model.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;external scanner&lt;br&gt;
       ↓&lt;br&gt;
scanner-specific parser&lt;br&gt;
       ↓&lt;br&gt;
UnifiedFinding&lt;br&gt;
       ↓&lt;br&gt;
correlation&lt;br&gt;
       ↓&lt;br&gt;
report&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is one of the biggest advantages of having an application-level core rather than scattering scanner logic throughout the VS Code extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture isn't completely finished
&lt;/h2&gt;

&lt;p&gt;This is important because architecture diagrams can easily make an early project look more mature than it actually is.&lt;/p&gt;

&lt;p&gt;Aqiron is currently version 0.0.1 and under active development.&lt;/p&gt;

&lt;p&gt;There are still intentional limitations.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;workspace operations currently require a Flutter workspace&lt;br&gt;
external scanners are optional&lt;br&gt;
the core is still bundled into the extension&lt;br&gt;
quick file scans use a separate direct extension path&lt;br&gt;
the project does not yet have independently published Core, CLI, or Desktop packages&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the current architecture is not:&lt;/p&gt;

&lt;p&gt;`Aqiron Core npm package&lt;br&gt;
        ↓&lt;br&gt;
VS Code&lt;br&gt;
CLI&lt;br&gt;
Desktop&lt;/p&gt;

&lt;p&gt;Not yet.&lt;/p&gt;

&lt;p&gt;It's closer to:&lt;/p&gt;

&lt;p&gt;VS Code&lt;br&gt;
   ↓&lt;br&gt;
internal Core process&lt;br&gt;
   ↓&lt;br&gt;
bundled runtime`&lt;/p&gt;

&lt;p&gt;That is an important distinction.&lt;/p&gt;

&lt;p&gt;The architecture is being prepared for broader reuse without prematurely creating a bunch of packages that don't yet need to exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I didn't immediately split everything into repositories&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was another deliberate decision.&lt;/p&gt;

&lt;p&gt;It would be easy to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;aqiron-core&lt;br&gt;
aqiron-security-vscode&lt;br&gt;
aqiron-security-cli&lt;br&gt;
aqiron-security-desktop&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and create four repositories immediately.&lt;/p&gt;

&lt;p&gt;But that would add operational complexity before the products existed.&lt;/p&gt;

&lt;p&gt;You would now have to manage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;package publishing&lt;br&gt;
version coordination&lt;br&gt;
cross-repository changes&lt;br&gt;
release synchronization&lt;br&gt;
dependency management&lt;br&gt;
contributor workflow across multiple repositories&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current repository gives me a cleaner intermediate step:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/&lt;br&gt;
packages/core/&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;with an explicit runtime boundary.&lt;/p&gt;

&lt;p&gt;When multiple clients become real products, the repository structure can change.&lt;/p&gt;

&lt;p&gt;Until then, the architecture can evolve without forcing the project to pay the cost of premature distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about this architecture
&lt;/h2&gt;

&lt;p&gt;The biggest win isn't actually "using IPC."&lt;/p&gt;

&lt;p&gt;The bigger win is making &lt;strong&gt;the boundary explicit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The VS Code extension owns the developer environment.&lt;/p&gt;

&lt;p&gt;The core owns security operations.&lt;/p&gt;

&lt;p&gt;The protocol connects them.&lt;/p&gt;

&lt;p&gt;That gives us a mental model like:&lt;/p&gt;

&lt;p&gt;`Client responsibilities&lt;br&gt;
    ↓&lt;br&gt;
UI&lt;br&gt;
VS Code&lt;br&gt;
commands&lt;br&gt;
diagnostics&lt;br&gt;
workspace interaction&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      │
      │ protocol
      ▼
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Core responsibilities&lt;br&gt;
    ↓&lt;br&gt;
scanning&lt;br&gt;
normalization&lt;br&gt;
correlation&lt;br&gt;
RAG&lt;br&gt;
AI operations&lt;br&gt;
reports&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;That's much easier to reason about than a single giant extension runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still need to watch
&lt;/h2&gt;

&lt;p&gt;The architecture also introduces new problems.&lt;/p&gt;

&lt;p&gt;A process boundary is not free.&lt;/p&gt;

&lt;p&gt;Now we have to care about:&lt;/p&gt;

&lt;p&gt;process startup time&lt;/p&gt;

&lt;p&gt;restart behavior&lt;/p&gt;

&lt;p&gt;malformed messages&lt;/p&gt;

&lt;p&gt;protocol compatibility&lt;/p&gt;

&lt;p&gt;stderr/stdout handling&lt;/p&gt;

&lt;p&gt;partial failures&lt;/p&gt;

&lt;p&gt;cancellation&lt;/p&gt;

&lt;p&gt;shutdown&lt;/p&gt;

&lt;p&gt;concurrent requests&lt;/p&gt;

&lt;p&gt;serialization overhead&lt;/p&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We traded code coupling for process-boundary complexity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think that's a reasonable trade for Aqiron, but it isn't automatically the right choice for every VS Code extension.&lt;/p&gt;

&lt;p&gt;If your project is a small command-based extension with a few hundred lines of logic, this architecture would probably be overkill.&lt;/p&gt;

&lt;p&gt;For a growing security platform with multiple subsystems and long-running operations, the boundary becomes much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger goal
&lt;/h2&gt;

&lt;p&gt;The long-term idea is not "make a complicated VS Code extension."&lt;/p&gt;

&lt;p&gt;It's to make the security core reusable.&lt;/p&gt;

&lt;p&gt;The future might eventually look like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Aqiron Core
               /     |      \
              /      |       \
             ↓       ↓        ↓

         VS Code    CLI     Desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But I don't need to build all three clients today.&lt;/p&gt;

&lt;p&gt;Right now, I'm using the VS Code extension as the first real client and using the Core boundary to keep the architecture ready for future evolution.&lt;/p&gt;

&lt;p&gt;That's the part I'm most interested in getting right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The biggest lesson I've taken from this project is that architecture isn't about drawing the biggest possible diagram.&lt;/p&gt;

&lt;p&gt;It's about deciding where responsibilities should stop.&lt;/p&gt;

&lt;p&gt;For Aqiron, the important boundary became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VS Code is the client.&lt;br&gt;
The TypeScript runtime is the security engine.&lt;br&gt;
IPC is the contract between them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That doesn't mean the architecture is finished.&lt;/p&gt;

&lt;p&gt;It means there is now a clear place to evolve it.&lt;/p&gt;

&lt;p&gt;I'm still working through the trade-offs, so I'd be interested in hearing from people who have built:&lt;/p&gt;

&lt;p&gt;VS Code extensions with external processes&lt;/p&gt;

&lt;p&gt;TypeScript/Node developer tools&lt;/p&gt;

&lt;p&gt;language-server-style architectures&lt;/p&gt;

&lt;p&gt;security scanners&lt;/p&gt;

&lt;p&gt;CLI + GUI products sharing a common runtime&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How would you design this boundary differently?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aqiron Security is open source and currently under active development.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>vscode</category>
      <category>opensource</category>
      <category>node</category>
    </item>
  </channel>
</rss>
