npm package: @kairosinternational/stackforge
Description: Agentic dev kit — drop-in and bolt-on. MCP auto-config, memory infrastructure, hardware/IoT bridges, CI/CD templates.
Latest version: 0.2.0

# @kairosinternational/stackforge

Agentic dev kit — drop-in and bolt-on. CLI scaffold, persistent memory via Mem0, MQTT + serial hardware bridges, MCP auto-config, and a five-stage promotion system. Every KAIR product is built and maintained through Stackforge.

```
npm install @kairosinternational/stackforge
```

---

## Quickstart

### As a CLI (scaffold new projects)

```bash
npm install -g @kairosinternational/stackforge
stackforge init
# Follow the prompts — creates project directory, SESSION_HANDOFF.md,
# CLAUDE.md, Start Bubby launcher, close-session script, and git repo.

cd C:\Dev\YourProject
# Double-click "Start Bubby - YourProject.bat" to start a session
```

### As an npm package (add memory to any app)

```bash
npm install @kairosinternational/stackforge
```

```js
import { AgentRunner } from '@kairosinternational/stackforge/runner';

const runner = new AgentRunner({
  claudeApiKey: process.env.CLAUDE_API_KEY,
  mem0ApiKey: process.env.MEM0_API_KEY,
  userId: 'my-tenant',
});

const { text, memories } = await runner.run({
  systemPrompt: 'You are a helpful assistant.',
  userPrompt: 'What do you know about this customer?',
  recallQuery: 'customer history',
  storeSummary: 'Customer asked about order status',
});
```

---

## Subpath Exports

```js
import { MemoryManager }  from '@kairosinternational/stackforge/memory';
import { AgentRunner }    from '@kairosinternational/stackforge/runner';
import { memoryMiddleware, getMemory } from '@kairosinternational/stackforge/middleware';
import { MqttBridge }     from '@kairosinternational/stackforge/bridges/mqtt';
import { SerialBridge }   from '@kairosinternational/stackforge/bridges/serial';
```

---

## CLI Commands

### `stackforge init`

Create a new project scaffold with all KAIR conventions.

```bash
stackforge init
```

Creates: `SESSION_HANDOFF.md`, `CLAUDE.md`, `.gitignore`, `prompt.txt`, `.env.example`, `close-session.ps1`, `Start Bubby` launchers (.bat + .ps1), `transcripts/`, and Claude Code hooks in `settings.json`.

### `stackforge setup`

First-run setup for Stackforge itself. Copies `.env.example` to `.env`.

```bash
stackforge setup
```

### `stackforge health`

Check connectivity to all configured MCP servers and service endpoints.

```bash
stackforge health
```

### `stackforge promote`

Advance a project through five maturity stages. Detects current stage from SESSION_HANDOFF.md.

```bash
cd C:\Dev\YourProject
stackforge promote
```

| Stage | Name | Milestone |
|-------|------|-----------|
| 1 | Local | Scaffold exists, localhost only |
| 2 | Backed | Supabase connected, schema migrated |
| 3 | Versioned | GitHub repo linked, code pushed |
| 4 | Deployed | Vercel live with preview URL |
| 5 | Live | Custom domain, SSL, production |

### `stackforge session:close`

Close the current Bubby session. Copies the Claude Code transcript to `transcripts/` and updates SESSION_HANDOFF.md with the session ID and resume command.

```bash
stackforge session:close
```

Runs automatically when using the `Start Bubby` launcher. Run manually if you launched `claude` directly.

### `stackforge mcp:configure`

Read `.stackforge.json` and auto-configure MCP servers into Claude Desktop's config file. Supports Windows (`AppData`) and WSL (`/mnt/c/Users/`) paths.

```bash
stackforge mcp:configure
```

### `stackforge hardware enable`

Enable hardware bridges in `.stackforge.json`.

```bash
# Enable MQTT
stackforge hardware enable --mqtt --broker localhost --port 1883

# Enable serial with auto-detect
stackforge hardware enable --serial

# Enable serial with specific port and baud rate
stackforge hardware enable --serial --serial-port /dev/ttyUSB0 --baud-rate 115200

# Enable both
stackforge hardware enable --mqtt --serial
```

### `stackforge hardware disable`

```bash
stackforge hardware disable --mqtt
stackforge hardware disable --serial
```

### `stackforge hardware up / down / status`

```bash
stackforge hardware up       # Start MQTT broker + bridge via Docker Compose
stackforge hardware down     # Stop hardware Docker services
stackforge hardware status   # Show config and container status
```

---

## Memory Layer

Stackforge wraps [Mem0](https://mem0.ai) to give any Claude-powered app persistent, searchable memory across sessions.

**Required env var:** `MEM0_API_KEY`

### MemoryManager

Low-level Mem0 wrapper. Store and recall memories with metadata filtering.

```js
import { MemoryManager } from '@kairosinternational/stackforge/memory';

const memory = new MemoryManager({
  apiKey: process.env.MEM0_API_KEY,
  userId: 'tenant-123',
});

// Store
await memory.store('Customer prefers email communication', {
  type: 'preference',
  customerId: 'cust-456',
});

// Recall
const results = await memory.recall('customer communication preferences', {
  limit: 5,
  metadata: { type: 'preference' },
});

// List all
const all = await memory.list();

// Delete
await memory.delete('memory-id-here');
```

### AgentRunner

Wraps any Claude API call with automatic memory recall before the call and memory store after it. Recalled memories are injected into the system prompt.

```js
import { AgentRunner } from '@kairosinternational/stackforge/runner';

const runner = new AgentRunner({
  claudeApiKey: process.env.CLAUDE_API_KEY,
  mem0ApiKey: process.env.MEM0_API_KEY,
  userId: 'my-tenant',
  model: 'claude-sonnet-4-20250514', // default
});

const { text, memories } = await runner.run({
  systemPrompt: 'You are a sales assistant.',
  userPrompt: 'Draft a follow-up email for Acme Corp.',
  maxTokens: 300,

  // Memory recall (before Claude call)
  recallQuery: 'Acme Corp interactions',     // defaults to userPrompt
  recallMetadata: { type: 'interaction' },   // optional filter

  // Memory store (after Claude call)
  storeSummary: 'Drafted follow-up email for Acme Corp',
  storeMetadata: { type: 'interaction', company: 'acme' },

  // Skip either side if not needed
  skipRecall: false,
  skipStore: false,
});

console.log(text);                    // Claude's response
console.log(memories);                // Memories that were recalled
```

### Express Middleware

Attaches a `req.memory` instance to every request.

```js
import express from 'express';
import { memoryMiddleware } from '@kairosinternational/stackforge/middleware';

const app = express();

app.use(memoryMiddleware({
  apiKey: process.env.MEM0_API_KEY,
  userId: (req) => req.user?.id || 'anonymous',
}));

app.get('/api/recall', async (req, res) => {
  const memories = await req.memory.recall('recent activity');
  res.json({ memories });
});
```

### Next.js App Router

```js
import { getMemory } from '@kairosinternational/stackforge/middleware';

export async function POST(request) {
  const { userId, query } = await request.json();
  const memory = getMemory({ userId });
  const results = await memory.recall(query);
  return Response.json({ results });
}
```

---

## Hardware/IoT Bridges

Stackforge provides two bridges for communicating with physical hardware: MQTT for networked IoT devices and serial for direct USB connections.

### MQTT Bridge

Connects to an MQTT broker (Eclipse Mosquitto by default) with pub/sub, wildcard subscriptions, and auto-reconnect.

**Peer dependency:** `npm install mqtt`

```js
import { MqttBridge } from '@kairosinternational/stackforge/bridges/mqtt';

const mqtt = new MqttBridge({
  broker: 'mqtt://localhost:1883',
  logLevel: 'info', // error | warn | info | debug
});

await mqtt.connect();

// Subscribe with wildcards
mqtt.subscribe('sensors/+/temperature', (topic, message) => {
  console.log(`${topic}: ${message}`);
});

// Publish
mqtt.publish('actuators/valve/1', { action: 'open' }, { qos: 1 });

// Status
console.log(mqtt.status());
// { connected: true, broker: 'mqtt://localhost:1883', subscriptions: 1 }

await mqtt.disconnect();
```

### Serial Bridge

Connects to serial devices (Arduino, ESP32, USB sensors) with auto-detection, configurable baud rate, and event-based I/O.

**Peer dependency:** `npm install serialport`

```js
import { SerialBridge } from '@kairosinternational/stackforge/bridges/serial';

// List available ports
const ports = await SerialBridge.listPorts();
console.log(ports);
// [{ path: 'COM3', manufacturer: 'Arduino', vendorId: '2341', ... }]

const serial = new SerialBridge({
  path: 'COM3',           // null = auto-detect first available port
  baudRate: 9600,          // default
  delimiter: '\n',         // line delimiter, default
  logLevel: 'info',
});

await serial.connect();

// Receive data (line-delimited)
serial.on('data', (line) => {
  console.log('Raw:', line);
});

// Auto-parsed JSON payloads
serial.on('json', (obj) => {
  console.log('Parsed:', obj);
});

// Connection events
serial.on('connected', ({ path, baudRate }) => { /* ... */ });
serial.on('disconnected', ({ path }) => { /* ... */ });
serial.on('error', (err) => { /* ... */ });

// Send data
serial.send('LED:ON');

// Status (same shape as MqttBridge)
console.log(serial.status());
// { connected: true, port: 'COM3', baudRate: 9600 }

await serial.disconnect();
```

### Docker Compose (MQTT + Serial)

The included `docker-compose.hardware.yml` runs a Mosquitto broker and a bridge service that optionally connects both MQTT and serial:

```bash
# Enable hardware in config
stackforge hardware enable --mqtt --serial

# Start services
stackforge hardware up

# Health check
curl http://localhost:3100/health
# { "status": "ok", "mqtt": { "connected": true, ... }, "serial": { "connected": true, ... } }

# List serial ports
curl http://localhost:3100/serial/ports

# Send data over serial
curl -X POST http://localhost:3100/serial/send \
  -H 'Content-Type: application/json' \
  -d '{"data": "LED:ON"}'

# Publish to MQTT
curl -X POST http://localhost:3100/publish \
  -H 'Content-Type: application/json' \
  -d '{"topic": "sensors/room1/temp", "payload": "22.5"}'

# Stop services
stackforge hardware down
```

**Environment variables for Docker:**

| Variable | Default | Purpose |
|----------|---------|---------|
| `MQTT_PORT` | `1883` | MQTT broker port |
| `MQTT_WS_PORT` | `9001` | MQTT WebSocket port |
| `BRIDGE_PORT` | `3100` | Bridge HTTP API port |
| `BRIDGE_LOG_LEVEL` | `info` | Log level (error/warn/info/debug) |
| `SERIAL_ENABLED` | `false` | Enable serial bridge in container |
| `SERIAL_PORT` | (auto) | Serial port path (auto-detect if empty) |
| `SERIAL_BAUD` | `9600` | Serial baud rate |

When serial is enabled, the bridge automatically relays serial data to MQTT topics:
- `stackforge/serial/raw` — every line received
- `stackforge/serial/json` — lines that parse as valid JSON

---

## The Bolt-On Pattern

Stackforge is designed to bolt onto existing apps without rewriting anything. The pattern:

1. **Wrap existing Claude calls** with `AgentRunner` to add memory
2. **Keep the original function signatures** so callers don't change
3. **Fallback gracefully** when `MEM0_API_KEY` isn't set

### Example: Adding Memory to an Existing App

**Before** (direct Claude calls):

```js
// lib/claude.js
export async function classifyLead(name, description) {
  const resp = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: { 'x-api-key': process.env.CLAUDE_API_KEY, /* ... */ },
    body: JSON.stringify({
      model: 'claude-sonnet-4-20250514',
      system: 'Classify this sales lead...',
      messages: [{ role: 'user', content: `${name}: ${description}` }],
    }),
  });
  const data = await resp.json();
  return JSON.parse(data.content[0].text);
}
```

**After** (memory-enriched, same signature):

```js
// lib/claude.js
import { AgentRunner } from '@kairosinternational/stackforge/runner';

const runner = new AgentRunner({
  claudeApiKey: process.env.CLAUDE_API_KEY,
  mem0ApiKey: process.env.MEM0_API_KEY,
  userId: 'my-app',
});

export async function classifyLead(name, description) {
  const { text } = await runner.run({
    systemPrompt: 'Classify this sales lead...',
    userPrompt: `${name}: ${description}`,
    recallQuery: `${name} ${description}`,
    recallMetadata: { type: 'lead' },
    storeSummary: `Classified lead: ${name}`,
    storeMetadata: { type: 'lead', name },
  });
  return JSON.parse(text);
}
```

Callers of `classifyLead()` don't change at all. The function now recalls prior knowledge about the lead before classifying and stores the result for future recall.

### Graceful Fallback

If you want the memory layer to be optional:

```js
let runner;
try {
  const { AgentRunner } = await import('@kairosinternational/stackforge/runner');
  runner = new AgentRunner({
    claudeApiKey: process.env.CLAUDE_API_KEY,
    mem0ApiKey: process.env.MEM0_API_KEY,
    userId: 'my-app',
  });
} catch {
  runner = null; // MEM0_API_KEY not set — fall back to direct calls
}

export async function classifyLead(name, description) {
  if (runner) {
    const { text } = await runner.run({ /* ... */ });
    return JSON.parse(text);
  }
  // Original direct Claude call as fallback
  const resp = await fetch('https://api.anthropic.com/v1/messages', { /* ... */ });
  const data = await resp.json();
  return JSON.parse(data.content[0].text);
}
```

---

## Bubby Sessions

"Bubby" is the team name for Claude Code. Every KAIR project uses structured sessions:

1. **Start** — Double-click `Start Bubby - Project.bat` (or run `claude` directly)
2. **First message** — "Read SESSION_HANDOFF.md and tell me where we left off"
3. **Work** — One deliverable per session (a migration, an API route, a UI page)
4. **End** — Type `/exit`. The launcher runs `session:close` automatically
5. **Resume** — `claude --resume <session-id>` (printed by session:close)

SESSION_HANDOFF.md carries all context between sessions so Bubby always knows what's done and what's next.

---

## Configuration

`.stackforge.json` in the project root:

```json
{
  "mcp": {
    "servers": [
      { "name": "github", "type": "docker", "image": "ghcr.io/github/github-mcp-server" },
      { "name": "vercel", "type": "url", "url": "https://mcp.vercel.com" }
    ]
  },
  "hardware": {
    "mqtt": { "enabled": true, "broker": "localhost", "port": 1883 },
    "serial": { "enabled": false, "port": null, "baudRate": 9600, "autoDetect": true }
  },
  "ci": {
    "provider": "github-actions",
    "deployTarget": "vercel"
  }
}
```

---

## Environment Variables

| Variable | Required | Used By |
|----------|----------|---------|
| `MEM0_API_KEY` | For memory features | MemoryManager, AgentRunner |
| `CLAUDE_API_KEY` | For AgentRunner | AgentRunner |
| `MQTT_BROKER` | No (default: `mqtt://localhost:1883`) | MqttBridge, bridge service |

---

## Requirements

- Node.js >= 20
- Git
- Claude Code CLI (for session features)
- Docker Desktop (for hardware bridges)
- Windows 10/11 (PowerShell + optional WSL2)

**Optional peer dependencies:**
- `mqtt` ^5.0.0 — for MQTT bridge
- `serialport` ^12.0.0 — for serial bridge

---

## License

MIT — KAIR / Kai'Ros International Technologies