Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,43 @@ concurrency:
cancel-in-progress: true

jobs:
# ---------------------------------------------------------------------------
# Detect which areas of the codebase changed to skip unaffected jobs.
# On push to main, all jobs run unconditionally (safety net).
# ---------------------------------------------------------------------------
changes:
name: Detect Changes
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
typescript: ${{ steps.filter.outputs.typescript }}
python: ${{ steps.filter.outputs.python }}
lint: ${{ steps.filter.outputs.lint }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
id: filter
with:
filters: |
typescript:
- 'packages/opencode/**'
- 'packages/plugin/**'
- 'packages/sdk/**'
- 'packages/util/**'
- 'packages/script/**'
- 'bun.lock'
- 'package.json'
- 'tsconfig.json'
python:
- 'packages/altimate-engine/**'
lint:
- 'packages/altimate-engine/src/**'

typescript:
name: TypeScript
needs: changes
if: needs.changes.outputs.typescript == 'true' || github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
Expand Down Expand Up @@ -77,6 +112,8 @@ jobs:

lint:
name: Lint
needs: changes
if: needs.changes.outputs.lint == 'true' || github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
Expand All @@ -95,6 +132,8 @@ jobs:

python:
name: Python ${{ matrix.python-version }}
needs: changes
if: needs.changes.outputs.python == 'true' || github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
Expand All @@ -116,4 +155,3 @@ jobs:
- name: Run tests
run: pytest
working-directory: packages/altimate-engine

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
- `packages/app`: The shared web UI components, written in SolidJS
- `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`)
- `packages/plugin`: Source for `@opencode-ai/plugin`
- `packages/plugin`: Source for `@altimateai/altimate-code-plugin`

### Understanding bun dev vs opencode

Expand Down
13 changes: 8 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions docs/docs/data-engineering/tools/memory-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ Memory blocks live in two scopes:
| Scope | Storage location | Use case |
|---|---|---|
| **global** | `~/.local/share/altimate-code/memory/` | User-wide preferences: SQL style, preferred models, general conventions |
| **project** | `.opencode/memory/` (in project root) | Project-specific: warehouse config, naming conventions, data model notes, past analyses |
| **project** | `.altimate-code/memory/` (in project root) | Project-specific: warehouse config, naming conventions, data model notes, past analyses |

Project memory travels with your repo. Add `.opencode/memory/` to `.gitignore` if it contains sensitive information, or commit it to share team conventions.
Project memory travels with your repo. Add `.altimate-code/memory/` to `.gitignore` if it contains sensitive information, or commit it to share team conventions.

## File format

Expand Down Expand Up @@ -185,14 +185,14 @@ The agent decides what to save based on conversation context. It may occasionall

- After a session where the agent saved memory, review what was written:
```bash
ls .opencode/memory/ # project memory
cat .opencode/memory/*.md # inspect all blocks
ls .altimate-code/memory/ # project memory
cat .altimate-code/memory/*.md # inspect all blocks
```
- The agent always reports when it creates or updates a memory block, so watch for `Memory: Created "..."` or `Memory: Updated "..."` messages in the session output

**How to fix:**

- Delete the bad block: ask the agent or run `rm .opencode/memory/bad-block.md`
- Delete the bad block: ask the agent or run `rm .altimate-code/memory/bad-block.md`
- Edit the file directly — it's just Markdown
- Ask the agent to rewrite it: "Update the warehouse-config memory with the correct warehouse name"

Expand All @@ -218,7 +218,7 @@ Memory blocks are stored as plaintext files on disk. Be mindful of what gets sav

- **Do not** save credentials, API keys, or connection strings in memory blocks
- **Do** save structural information (warehouse names, naming conventions, schema patterns)
- If using project-scoped memory in a shared repo, add `.opencode/memory/` to `.gitignore` to avoid committing sensitive context
- If using project-scoped memory in a shared repo, add `.altimate-code/memory/` to `.gitignore` to avoid committing sensitive context
- Memory blocks are scoped per-user (global) and per-project — there is no cross-user or cross-project leakage

!!! warning
Expand Down
12 changes: 7 additions & 5 deletions packages/opencode/script/postinstall.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,13 @@ function printWelcome(version) {
const empty = ` │ ${" ".repeat(contentWidth)} │`
const row = (s) => ` │ ${pad(s)} │`

console.log(top)
console.log(empty)
console.log(row(` ${v}`))
for (const line of lines) console.log(row(line))
console.log(bot)
// Use stderr — npm v7+ silences postinstall stdout
const out = (s) => process.stderr.write(s + "\n")
out(top)
out(empty)
out(row(` ${v}`))
for (const line of lines) out(row(line))
out(bot)
}

/**
Expand Down
31 changes: 5 additions & 26 deletions packages/opencode/src/cli/welcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import fs from "fs"
import path from "path"
import os from "os"
import { Installation } from "../installation"
import { extractChangelog } from "./changelog"
import { EOL } from "os"

const APP_NAME = "altimate-code"
Expand All @@ -16,9 +15,11 @@ function getDataDir(): string {

/**
* Check for a post-install/upgrade marker written by postinstall.mjs.
* If found, display a welcome banner (and changelog on upgrade), then remove the marker.
* If found, display a brief upgrade confirmation on stderr, then remove the marker.
*
* npm v7+ silences postinstall stdout, so this is the reliable way to show the banner.
* The postinstall script shows the full welcome box (with get-started hints).
* This function handles the case where postinstall output was silenced (npm v7+)
* or the install method didn't run postinstall at all (brew, curl).
*/
export function showWelcomeBannerIfNeeded(): void {
try {
Expand All @@ -39,7 +40,7 @@ export function showWelcomeBannerIfNeeded(): void {

if (!isUpgrade) return

// Show welcome box
// Show a brief confirmation — the full welcome box is in postinstall.mjs
const tty = process.stderr.isTTY
if (!tty) return

Expand All @@ -50,28 +51,6 @@ export function showWelcomeBannerIfNeeded(): void {
process.stderr.write(EOL)
process.stderr.write(` ${orange}${bold}altimate-code v${currentVersion}${reset} installed successfully!${EOL}`)
process.stderr.write(EOL)

// Try to show changelog for this version
const changelog = extractChangelog("0.0.0", currentVersion)
if (changelog) {
// Extract only the latest version section
const latestSection = changelog.split(/\n## \[/)[0]
if (latestSection) {
const dim = "\x1b[2m"
const cyan = "\x1b[36m"
const lines = latestSection.split("\n")
for (const line of lines) {
if (line.startsWith("## [")) {
process.stderr.write(` ${cyan}${line}${reset}${EOL}`)
} else if (line.startsWith("### ")) {
process.stderr.write(` ${bold}${line}${reset}${EOL}`)
} else if (line.trim()) {
process.stderr.write(` ${dim}${line}${reset}${EOL}`)
}
}
process.stderr.write(EOL)
}
}
} catch {
// Non-fatal — never let banner display break the CLI
}
Expand Down
26 changes: 18 additions & 8 deletions packages/opencode/src/installation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@ export namespace Installation {
name: "bun" as const,
command: () => text(["bun", "pm", "ls", "-g"]),
},
// altimate_change start — brew formula name
{
name: "brew" as const,
command: () => text(["brew", "list", "--formula", "opencode"]),
command: () => text(["brew", "list", "--formula", "altimate-code"]),
},
// altimate_change end
{
name: "scoop" as const,
command: () => text(["scoop", "list", "opencode"]),
Expand All @@ -145,8 +147,10 @@ export namespace Installation {

for (const check of checks) {
const output = await check.command()
// altimate_change start — package names for detection
const installedName =
check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai"
check.name === "brew" ? "altimate-code" : check.name === "choco" || check.name === "scoop" ? "opencode" : "@altimateai/altimate-code"
// altimate_change end
if (output.includes(installedName)) {
return check.name
}
Expand All @@ -162,13 +166,15 @@ export namespace Installation {
}),
)

// altimate_change start — brew formula detection
async function getBrewFormula() {
const tapFormula = await text(["brew", "list", "--formula", "AltimateAI/tap/altimate-code"])
if (tapFormula.includes("opencode")) return "AltimateAI/tap/altimate-code"
const coreFormula = await text(["brew", "list", "--formula", "opencode"])
if (coreFormula.includes("opencode")) return "opencode"
return "opencode"
if (tapFormula.includes("altimate-code")) return "AltimateAI/tap/altimate-code"
const coreFormula = await text(["brew", "list", "--formula", "altimate-code"])
if (coreFormula.includes("altimate-code")) return "altimate-code"
return "AltimateAI/tap/altimate-code"
}
// altimate_change end

export async function upgrade(method: Method, target: string) {
let result: Awaited<ReturnType<typeof upgradeCurl>> | undefined
Expand Down Expand Up @@ -280,7 +286,9 @@ export namespace Installation {
if (!version) throw new Error(`Could not detect version for tap formula: ${formula}`)
return version
}
return fetch("https://formulae.brew.sh/api/formula/opencode.json")
// altimate_change start — brew formula URL
return fetch("https://formulae.brew.sh/api/formula/altimate-code.json")
// altimate_change end
.then((res) => {
if (!res.ok) throw new Error(res.statusText)
return res.json()
Expand All @@ -295,7 +303,9 @@ export namespace Installation {
return reg.endsWith("/") ? reg.slice(0, -1) : reg
})
const channel = CHANNEL
return fetch(`${registry}/opencode-ai/${channel}`)
// altimate_change start — npm package name for version check
return fetch(`${registry}/@altimateai/altimate-code/${channel}`)
// altimate_change end
.then((res) => {
if (!res.ok) throw new Error(res.statusText)
return res.json()
Expand Down
41 changes: 41 additions & 0 deletions packages/opencode/src/storage/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,44 @@ export namespace Database {
return sql.sort((a, b) => a.timestamp - b.timestamp)
}

// altimate_change start — backfill migration names for upgrade compatibility
function backfillMigrationNames(sqlite: BunDatabase, entries: Journal) {
try {
// Check if the migrations table exists and has the name column
const tableInfo = sqlite
.prepare("SELECT name FROM pragma_table_info('__drizzle_migrations')")
.all() as { name: string }[]
if (!tableInfo.length || !tableInfo.some((c) => c.name === "name")) return

// Find entries with NULL or empty names
const rows = sqlite
.prepare("SELECT created_at FROM __drizzle_migrations WHERE name IS NULL OR name = ''")
.all() as { created_at: number }[]
if (!rows.length) return

log.info("backfilling migration names", { count: rows.length })

// Build timestamp → name lookup from local migrations
const byTimestamp = new Map<number, string>()
for (const entry of entries) {
byTimestamp.set(entry.timestamp, entry.name)
}

const stmt = sqlite.prepare("UPDATE __drizzle_migrations SET name = ? WHERE created_at = ?")
for (const row of rows) {
const name = byTimestamp.get(row.created_at)
if (name) {
stmt.run(name, row.created_at)
}
}
} catch (e) {
log.info("migration name backfill skipped", {
error: e instanceof Error ? e.message : String(e),
})
}
}
// altimate_change end

export const Client = lazy(() => {
log.info("opening database", { path: Path })

Expand Down Expand Up @@ -110,6 +148,9 @@ export namespace Database {
item.sql = "select 1;"
}
}
// altimate_change start — backfill migration names before migrate
backfillMigrationNames(sqlite, entries)
// altimate_change end
migrate(db, entries)
}

Expand Down
Loading
Loading