Skip to content

Commit 77b3847

Browse files
anandgupta42claude
andcommitted
fix: improve merge tooling and add upstream merge guard tests
Merge script improvements: - Add per-file error handling in `autoResolveConflicts` so one failure doesn't abort all resolution - Handle delete/add conflicts in skipFiles with `git rm` fallback - Add `cleanupSkipFiles` step to `postMergeTransforms` — automatically removes skipFiles packages after merge - Change `stageAll()` from `git add -A` to `git add -u` to avoid ENOBUFS from untracked experiment directories - Add `--force` to `fetchRemote` tag fetches to resolve local/upstream tag conflicts - Handle already-committed merges in `--continue` flow gracefully New tests (upstream-merge-guard.test.ts): - Installation branding: USER_AGENT, brew tap, npm package name - Root package.json integrity: no globs in workspaces, no sst/electron - Deleted packages stay deleted (14 upstream-only dirs/files) - OAuth/MCP branding: client_name, HTML titles - opencode.ai domain leak scanner for src/*.ts and src/*.tsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4bc6d54 commit 77b3847

4 files changed

Lines changed: 364 additions & 78 deletions

File tree

.upstream-merge-state.json

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { readFileSync, existsSync } from "fs"
3+
import { join, resolve } from "path"
4+
import { Glob } from "bun"
5+
6+
const repoRoot = resolve(import.meta.dir, "..", "..", "..", "..")
7+
const pkgDir = resolve(import.meta.dir, "..", "..")
8+
const srcDir = join(pkgDir, "src")
9+
10+
function readText(filePath: string): string {
11+
return readFileSync(filePath, "utf-8")
12+
}
13+
14+
function readJSON(filePath: string): any {
15+
return JSON.parse(readFileSync(filePath, "utf-8"))
16+
}
17+
18+
// ---------------------------------------------------------------------------
19+
// 1. Installation Script Branding
20+
// ---------------------------------------------------------------------------
21+
describe("Installation script branding", () => {
22+
const installSrc = readText(join(srcDir, "installation", "index.ts"))
23+
24+
test("USER_AGENT starts with `altimate-code/` not `opencode/`", () => {
25+
expect(installSrc).toContain("USER_AGENT = `altimate-code/")
26+
expect(installSrc).not.toMatch(/USER_AGENT\s*=\s*`opencode\//)
27+
})
28+
29+
test("brew tap references AltimateAI/tap not anomalyco/tap", () => {
30+
expect(installSrc).toContain("AltimateAI/tap")
31+
expect(installSrc).not.toContain("anomalyco/tap")
32+
})
33+
34+
test("npm package install uses @altimateai/altimate-code not opencode-ai", () => {
35+
// npm/pnpm/bun install commands should reference our package
36+
expect(installSrc).toContain("@altimateai/altimate-code")
37+
38+
// Should not contain the upstream npm package name in install commands
39+
// (note: @opencode-ai/ as internal scope is allowed, but `opencode-ai@` as
40+
// an npm install target is not)
41+
const installLines = installSrc.split("\n").filter(
42+
(line) =>
43+
(line.includes("npm") || line.includes("pnpm") || line.includes("bun")) &&
44+
line.includes("install"),
45+
)
46+
for (const line of installLines) {
47+
expect(line).not.toMatch(/["'`]opencode-ai["'`@]/)
48+
}
49+
})
50+
})
51+
52+
// ---------------------------------------------------------------------------
53+
// 2. Root package.json Integrity
54+
// ---------------------------------------------------------------------------
55+
describe("Root package.json integrity", () => {
56+
const rootPkg = readJSON(join(repoRoot, "package.json"))
57+
58+
test("workspaces list only explicit paths (no globs)", () => {
59+
const packages: string[] = rootPkg.workspaces?.packages ?? []
60+
expect(packages.length).toBeGreaterThan(0)
61+
for (const entry of packages) {
62+
expect(entry).not.toContain("*")
63+
expect(entry).not.toContain("?")
64+
expect(entry).not.toContain("{")
65+
}
66+
})
67+
68+
test("no `sst` in devDependencies", () => {
69+
const devDeps = rootPkg.devDependencies ?? {}
70+
expect(devDeps).not.toHaveProperty("sst")
71+
})
72+
73+
test("no `electron` in trustedDependencies", () => {
74+
const trusted: string[] = rootPkg.trustedDependencies ?? []
75+
expect(trusted).not.toContain("electron")
76+
})
77+
78+
test("no `@aws-sdk/client-s3` in dependencies", () => {
79+
const deps = rootPkg.dependencies ?? {}
80+
expect(deps).not.toHaveProperty("@aws-sdk/client-s3")
81+
})
82+
})
83+
84+
// ---------------------------------------------------------------------------
85+
// 3. Deleted Packages Stay Deleted
86+
// ---------------------------------------------------------------------------
87+
describe("Deleted packages stay deleted", () => {
88+
const forbiddenDirs = [
89+
"packages/app",
90+
"packages/console",
91+
"packages/desktop",
92+
"packages/desktop-electron",
93+
"packages/enterprise",
94+
"packages/extensions",
95+
"packages/function",
96+
"packages/identity",
97+
"packages/slack",
98+
"packages/storybook",
99+
"packages/ui",
100+
"packages/web",
101+
"infra",
102+
"nix",
103+
]
104+
105+
for (const dir of forbiddenDirs) {
106+
test(`${dir}/ should not exist`, () => {
107+
expect(existsSync(join(repoRoot, dir))).toBe(false)
108+
})
109+
}
110+
111+
const forbiddenFiles = ["sst.config.ts", "sst-env.d.ts"]
112+
113+
for (const file of forbiddenFiles) {
114+
test(`${file} should not exist at repo root`, () => {
115+
expect(existsSync(join(repoRoot, file))).toBe(false)
116+
})
117+
}
118+
119+
test("no translated README.*.md files exist at repo root", () => {
120+
const translatedPatterns = [
121+
"README.zh-CN.md",
122+
"README.ja.md",
123+
"README.ko.md",
124+
"README.es.md",
125+
"README.fr.md",
126+
"README.de.md",
127+
"README.pt.md",
128+
"README.ru.md",
129+
"README.ar.md",
130+
"README.hi.md",
131+
]
132+
for (const readme of translatedPatterns) {
133+
expect(existsSync(join(repoRoot, readme))).toBe(false)
134+
}
135+
})
136+
})
137+
138+
// ---------------------------------------------------------------------------
139+
// 4. OAuth/MCP Branding
140+
// ---------------------------------------------------------------------------
141+
describe("OAuth/MCP branding", () => {
142+
const oauthProviderPath = join(srcDir, "mcp", "oauth-provider.ts")
143+
const oauthCallbackPath = join(srcDir, "mcp", "oauth-callback.ts")
144+
145+
test("oauth-provider.ts has client_name: \"Altimate Code\" not \"OpenCode\"", () => {
146+
const content = readText(oauthProviderPath)
147+
expect(content).toContain('client_name: "Altimate Code"')
148+
expect(content).not.toMatch(/client_name:\s*"OpenCode"/)
149+
})
150+
151+
test("oauth-callback.ts HTML titles contain \"Altimate Code\" not \"OpenCode\"", () => {
152+
const content = readText(oauthCallbackPath)
153+
// All <title> tags should reference Altimate Code
154+
const titleMatches = content.match(/<title>[^<]+<\/title>/g) ?? []
155+
expect(titleMatches.length).toBeGreaterThan(0)
156+
for (const title of titleMatches) {
157+
expect(title).toContain("Altimate Code")
158+
expect(title).not.toContain("OpenCode")
159+
}
160+
})
161+
162+
test("oauth-callback.ts body text references Altimate Code not OpenCode", () => {
163+
const content = readText(oauthCallbackPath)
164+
// User-facing strings mentioning the product
165+
expect(content).toContain("Altimate Code")
166+
// No user-facing "OpenCode" references (excluding internal identifiers)
167+
const lines = content.split("\n")
168+
for (const line of lines) {
169+
// Skip import lines and internal identifiers
170+
if (line.trim().startsWith("import ")) continue
171+
if (line.includes("@opencode-ai/")) continue
172+
if (line.includes("OPENCODE_")) continue
173+
if (line.includes(".opencode")) continue
174+
// Check user-facing HTML content for leaked branding
175+
if (line.includes("<title>") || line.includes("<p>") || line.includes("<h")) {
176+
expect(line).not.toMatch(/\bOpenCode\b/)
177+
}
178+
}
179+
})
180+
})
181+
182+
// ---------------------------------------------------------------------------
183+
// 5. No opencode.ai Domain Leaks in src/
184+
// ---------------------------------------------------------------------------
185+
describe("No opencode.ai domain leaks in src/", () => {
186+
function isExcludedLine(line: string, filePath: string): boolean {
187+
const trimmed = line.trim()
188+
if (trimmed.includes("@opencode-ai/")) return true
189+
if (/OPENCODE_/.test(trimmed)) return true
190+
if (trimmed.includes(".opencode/") || trimmed.includes('.opencode"') || trimmed.includes(".opencode\\")) return true
191+
if (trimmed.includes("opencode.json") || trimmed.includes("opencode.jsonc")) return true
192+
if (trimmed.includes("packages/opencode")) return true
193+
if (trimmed.includes("window.__OPENCODE__")) return true
194+
if (trimmed.startsWith("import ")) return true
195+
if (trimmed.startsWith("//")) return true
196+
if (/['"]\.opencode['"]/.test(trimmed)) return true
197+
if (/\.opencode/.test(trimmed) && !/opencode\.ai/i.test(trimmed)) return true
198+
if (filePath.includes("/test/")) return true
199+
return false
200+
}
201+
202+
test("no opencode.ai domain references in any src/ .ts files", async () => {
203+
const violations: string[] = []
204+
const glob = new Glob("**/*.ts")
205+
for await (const file of glob.scan({ cwd: srcDir })) {
206+
const filePath = join(srcDir, file)
207+
const content = readText(filePath)
208+
const lines = content.split("\n")
209+
for (let i = 0; i < lines.length; i++) {
210+
const line = lines[i]
211+
if (isExcludedLine(line, filePath)) continue
212+
if (/opencode\.ai/i.test(line)) {
213+
violations.push(`${file}:${i + 1}: ${line.trim()}`)
214+
}
215+
}
216+
}
217+
expect(violations).toEqual([])
218+
})
219+
220+
test("no opencode.ai domain references in any src/ .tsx files", async () => {
221+
const violations: string[] = []
222+
const glob = new Glob("**/*.tsx")
223+
for await (const file of glob.scan({ cwd: srcDir })) {
224+
const filePath = join(srcDir, file)
225+
const content = readText(filePath)
226+
const lines = content.split("\n")
227+
for (let i = 0; i < lines.length; i++) {
228+
const line = lines[i]
229+
if (isExcludedLine(line, filePath)) continue
230+
if (/opencode\.ai/i.test(line)) {
231+
violations.push(`${file}:${i + 1}: ${line.trim()}`)
232+
}
233+
}
234+
}
235+
expect(violations).toEqual([])
236+
})
237+
})

0 commit comments

Comments
 (0)