Skip to content

Commit 8653003

Browse files
authored
fix: v0.3.0 post-release — upgrade crash, install banner, upgrade detection (#161)
* fix: v0.3.0 post-release — upgrade crash, install banner, upgrade detection - Backfill NULL migration names in `__drizzle_migrations` before Drizzle `migrate()` runs. Drizzle beta.16 matches by `name` field; old DBs from v0.2.x have NULL names causing all migrations to re-run and crash on `CREATE TABLE project` (table already exists). - Move postinstall `printWelcome()` from `console.log` to `process.stderr.write` so the banner is visible under npm v7+ (which silences postinstall stdout). - Remove changelog dump from `showWelcomeBannerIfNeeded()` — the postinstall banner now handles the install notification. - Fix `method()` package detection: `opencode-ai` → `@altimateai/altimate-code`, brew formula `opencode` → `altimate-code`. - Fix `latest()` npm registry URL and brew formulae URL to use `@altimateai/altimate-code` and `altimate-code.json`. - Fix `getBrewFormula()` to reference `AltimateAI/tap/altimate-code`. - Add `altimate_change` markers to all modified upstream-shared files. - Add `db.ts` to required marker files in upstream-merge-guard test. - Add 5 branding guard tests for installation detection, brew formula, npm registry URL, and `getBrewFormula()`. - Add 8 E2E install tests covering new user (clean DB) and existing user (v0.2.x upgrade) scenarios, including backfill correctness, idempotency, and edge cases. - Add 5 unit tests for migration name backfill logic. Closes #160 * fix: use precise `altimate-code` match in `getBrewFormula()` Address code review finding: `includes("altimate")` could match unrelated packages. Narrowed to `includes("altimate-code")`. * fix: restore brief upgrade banner in `welcome.ts` The previous commit removed the entire banner display, leaving `showWelcomeBannerIfNeeded()` as dead code. Brew and curl install users don't run postinstall.mjs, so they'd never see any feedback. Restore a single-line "installed successfully!" message on stderr. The verbose changelog dump remains removed — only the postinstall box shows the full get-started hints. Addresses review feedback from GPT 5.2 Codex, GLM-5, MiniMax M2.5. * docs: fix stale @opencode-ai/plugin reference in CONTRIBUTING.md Update to @altimateai/altimate-code-plugin to match the rebranded npm package name. * docs: fix .opencode/memory/ references to .altimate-code/memory/ Update 7 occurrences in memory-tools.md to use the rebranded project config directory name. * ci: skip unaffected jobs using path-based change detection Add a `changes` job using dorny/paths-filter to detect which areas of the codebase were modified. Jobs now skip when their paths are unaffected: - TypeScript tests: only on packages/opencode/**, bun.lock, etc. - Python tests (3 matrix jobs): only on packages/altimate-engine/** - Lint: only on packages/altimate-engine/src/** - Marker Guard: always runs on PRs (unchanged) Push to main always runs all jobs (safety net). For a docs-only or CI-config-only change, this skips ~4 minutes of unnecessary TypeScript + Python test runs.
1 parent f17eb99 commit 8653003

File tree

12 files changed

+649
-55
lines changed

12 files changed

+649
-55
lines changed

.github/workflows/ci.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,43 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
jobs:
14+
# ---------------------------------------------------------------------------
15+
# Detect which areas of the codebase changed to skip unaffected jobs.
16+
# On push to main, all jobs run unconditionally (safety net).
17+
# ---------------------------------------------------------------------------
18+
changes:
19+
name: Detect Changes
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 2
22+
outputs:
23+
typescript: ${{ steps.filter.outputs.typescript }}
24+
python: ${{ steps.filter.outputs.python }}
25+
lint: ${{ steps.filter.outputs.lint }}
26+
steps:
27+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
28+
29+
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
30+
id: filter
31+
with:
32+
filters: |
33+
typescript:
34+
- 'packages/opencode/**'
35+
- 'packages/plugin/**'
36+
- 'packages/sdk/**'
37+
- 'packages/util/**'
38+
- 'packages/script/**'
39+
- 'bun.lock'
40+
- 'package.json'
41+
- 'tsconfig.json'
42+
python:
43+
- 'packages/altimate-engine/**'
44+
lint:
45+
- 'packages/altimate-engine/src/**'
46+
1447
typescript:
1548
name: TypeScript
49+
needs: changes
50+
if: needs.changes.outputs.typescript == 'true' || github.event_name == 'push'
1651
runs-on: ubuntu-latest
1752
timeout-minutes: 60
1853
steps:
@@ -77,6 +112,8 @@ jobs:
77112
78113
lint:
79114
name: Lint
115+
needs: changes
116+
if: needs.changes.outputs.lint == 'true' || github.event_name == 'push'
80117
runs-on: ubuntu-latest
81118
timeout-minutes: 60
82119
steps:
@@ -95,6 +132,8 @@ jobs:
95132

96133
python:
97134
name: Python ${{ matrix.python-version }}
135+
needs: changes
136+
if: needs.changes.outputs.python == 'true' || github.event_name == 'push'
98137
runs-on: ubuntu-latest
99138
timeout-minutes: 60
100139
strategy:
@@ -116,4 +155,3 @@ jobs:
116155
- name: Run tests
117156
run: pytest
118157
working-directory: packages/altimate-engine
119-

CONTRIBUTING.md

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

7979
### Understanding bun dev vs opencode
8080

bun.lock

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

docs/docs/data-engineering/tools/memory-tools.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ Memory blocks live in two scopes:
9292
| Scope | Storage location | Use case |
9393
|---|---|---|
9494
| **global** | `~/.local/share/altimate-code/memory/` | User-wide preferences: SQL style, preferred models, general conventions |
95-
| **project** | `.opencode/memory/` (in project root) | Project-specific: warehouse config, naming conventions, data model notes, past analyses |
95+
| **project** | `.altimate-code/memory/` (in project root) | Project-specific: warehouse config, naming conventions, data model notes, past analyses |
9696

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

9999
## File format
100100

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

186186
- After a session where the agent saved memory, review what was written:
187187
```bash
188-
ls .opencode/memory/ # project memory
189-
cat .opencode/memory/*.md # inspect all blocks
188+
ls .altimate-code/memory/ # project memory
189+
cat .altimate-code/memory/*.md # inspect all blocks
190190
```
191191
- 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
192192

193193
**How to fix:**
194194

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

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

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

224224
!!! warning

packages/opencode/script/postinstall.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ function printWelcome(version) {
106106
const empty = ` │ ${" ".repeat(contentWidth)} │`
107107
const row = (s) => ` │ ${pad(s)} │`
108108

109-
console.log(top)
110-
console.log(empty)
111-
console.log(row(` ${v}`))
112-
for (const line of lines) console.log(row(line))
113-
console.log(bot)
109+
// Use stderr — npm v7+ silences postinstall stdout
110+
const out = (s) => process.stderr.write(s + "\n")
111+
out(top)
112+
out(empty)
113+
out(row(` ${v}`))
114+
for (const line of lines) out(row(line))
115+
out(bot)
114116
}
115117

116118
/**

packages/opencode/src/cli/welcome.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from "fs"
22
import path from "path"
33
import os from "os"
44
import { Installation } from "../installation"
5-
import { extractChangelog } from "./changelog"
65
import { EOL } from "os"
76

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

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

4041
if (!isUpgrade) return
4142

42-
// Show welcome box
43+
// Show a brief confirmation — the full welcome box is in postinstall.mjs
4344
const tty = process.stderr.isTTY
4445
if (!tty) return
4546

@@ -50,28 +51,6 @@ export function showWelcomeBannerIfNeeded(): void {
5051
process.stderr.write(EOL)
5152
process.stderr.write(` ${orange}${bold}altimate-code v${currentVersion}${reset} installed successfully!${EOL}`)
5253
process.stderr.write(EOL)
53-
54-
// Try to show changelog for this version
55-
const changelog = extractChangelog("0.0.0", currentVersion)
56-
if (changelog) {
57-
// Extract only the latest version section
58-
const latestSection = changelog.split(/\n## \[/)[0]
59-
if (latestSection) {
60-
const dim = "\x1b[2m"
61-
const cyan = "\x1b[36m"
62-
const lines = latestSection.split("\n")
63-
for (const line of lines) {
64-
if (line.startsWith("## [")) {
65-
process.stderr.write(` ${cyan}${line}${reset}${EOL}`)
66-
} else if (line.startsWith("### ")) {
67-
process.stderr.write(` ${bold}${line}${reset}${EOL}`)
68-
} else if (line.trim()) {
69-
process.stderr.write(` ${dim}${line}${reset}${EOL}`)
70-
}
71-
}
72-
process.stderr.write(EOL)
73-
}
74-
}
7554
} catch {
7655
// Non-fatal — never let banner display break the CLI
7756
}

packages/opencode/src/installation/index.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,12 @@ export namespace Installation {
121121
name: "bun" as const,
122122
command: () => text(["bun", "pm", "ls", "-g"]),
123123
},
124+
// altimate_change start — brew formula name
124125
{
125126
name: "brew" as const,
126-
command: () => text(["brew", "list", "--formula", "opencode"]),
127+
command: () => text(["brew", "list", "--formula", "altimate-code"]),
127128
},
129+
// altimate_change end
128130
{
129131
name: "scoop" as const,
130132
command: () => text(["scoop", "list", "opencode"]),
@@ -145,8 +147,10 @@ export namespace Installation {
145147

146148
for (const check of checks) {
147149
const output = await check.command()
150+
// altimate_change start — package names for detection
148151
const installedName =
149-
check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai"
152+
check.name === "brew" ? "altimate-code" : check.name === "choco" || check.name === "scoop" ? "opencode" : "@altimateai/altimate-code"
153+
// altimate_change end
150154
if (output.includes(installedName)) {
151155
return check.name
152156
}
@@ -162,13 +166,15 @@ export namespace Installation {
162166
}),
163167
)
164168

169+
// altimate_change start — brew formula detection
165170
async function getBrewFormula() {
166171
const tapFormula = await text(["brew", "list", "--formula", "AltimateAI/tap/altimate-code"])
167-
if (tapFormula.includes("opencode")) return "AltimateAI/tap/altimate-code"
168-
const coreFormula = await text(["brew", "list", "--formula", "opencode"])
169-
if (coreFormula.includes("opencode")) return "opencode"
170-
return "opencode"
172+
if (tapFormula.includes("altimate-code")) return "AltimateAI/tap/altimate-code"
173+
const coreFormula = await text(["brew", "list", "--formula", "altimate-code"])
174+
if (coreFormula.includes("altimate-code")) return "altimate-code"
175+
return "AltimateAI/tap/altimate-code"
171176
}
177+
// altimate_change end
172178

173179
export async function upgrade(method: Method, target: string) {
174180
let result: Awaited<ReturnType<typeof upgradeCurl>> | undefined
@@ -280,7 +286,9 @@ export namespace Installation {
280286
if (!version) throw new Error(`Could not detect version for tap formula: ${formula}`)
281287
return version
282288
}
283-
return fetch("https://formulae.brew.sh/api/formula/opencode.json")
289+
// altimate_change start — brew formula URL
290+
return fetch("https://formulae.brew.sh/api/formula/altimate-code.json")
291+
// altimate_change end
284292
.then((res) => {
285293
if (!res.ok) throw new Error(res.statusText)
286294
return res.json()
@@ -295,7 +303,9 @@ export namespace Installation {
295303
return reg.endsWith("/") ? reg.slice(0, -1) : reg
296304
})
297305
const channel = CHANNEL
298-
return fetch(`${registry}/opencode-ai/${channel}`)
306+
// altimate_change start — npm package name for version check
307+
return fetch(`${registry}/@altimateai/altimate-code/${channel}`)
308+
// altimate_change end
299309
.then((res) => {
300310
if (!res.ok) throw new Error(res.statusText)
301311
return res.json()

packages/opencode/src/storage/db.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,44 @@ export namespace Database {
8080
return sql.sort((a, b) => a.timestamp - b.timestamp)
8181
}
8282

83+
// altimate_change start — backfill migration names for upgrade compatibility
84+
function backfillMigrationNames(sqlite: BunDatabase, entries: Journal) {
85+
try {
86+
// Check if the migrations table exists and has the name column
87+
const tableInfo = sqlite
88+
.prepare("SELECT name FROM pragma_table_info('__drizzle_migrations')")
89+
.all() as { name: string }[]
90+
if (!tableInfo.length || !tableInfo.some((c) => c.name === "name")) return
91+
92+
// Find entries with NULL or empty names
93+
const rows = sqlite
94+
.prepare("SELECT created_at FROM __drizzle_migrations WHERE name IS NULL OR name = ''")
95+
.all() as { created_at: number }[]
96+
if (!rows.length) return
97+
98+
log.info("backfilling migration names", { count: rows.length })
99+
100+
// Build timestamp → name lookup from local migrations
101+
const byTimestamp = new Map<number, string>()
102+
for (const entry of entries) {
103+
byTimestamp.set(entry.timestamp, entry.name)
104+
}
105+
106+
const stmt = sqlite.prepare("UPDATE __drizzle_migrations SET name = ? WHERE created_at = ?")
107+
for (const row of rows) {
108+
const name = byTimestamp.get(row.created_at)
109+
if (name) {
110+
stmt.run(name, row.created_at)
111+
}
112+
}
113+
} catch (e) {
114+
log.info("migration name backfill skipped", {
115+
error: e instanceof Error ? e.message : String(e),
116+
})
117+
}
118+
}
119+
// altimate_change end
120+
83121
export const Client = lazy(() => {
84122
log.info("opening database", { path: Path })
85123

@@ -110,6 +148,9 @@ export namespace Database {
110148
item.sql = "select 1;"
111149
}
112150
}
151+
// altimate_change start — backfill migration names before migrate
152+
backfillMigrationNames(sqlite, entries)
153+
// altimate_change end
113154
migrate(db, entries)
114155
}
115156

0 commit comments

Comments
 (0)