diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b65ec986..73064207 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -89,9 +89,9 @@ jobs:
echo "artifact_url=$artifact_url"
} >> "$GITHUB_OUTPUT"
- # Mirror block/sessh: mint a scoped token for the OSS Homebrew tap and
- # trigger its bump-formula workflow so `brew install block/tap/ghost`
- # tracks releases automatically. block -> block, no Square refs. Gated on
+ # Mint a scoped token for the OSS Homebrew tap and trigger its
+ # bump-formula workflow so `brew install block/tap/ghost` tracks releases
+ # automatically. Gated on
# an actual publish and on the BLOCK_HOMEBREW_TAP_* secrets (GitHub App
# installed on this repo), so forks and unconfigured environments skip it
# cleanly instead of failing the release.
diff --git a/.gitignore b/.gitignore
index dbacff6b..73787dbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ docs/superpowers/plans/
docs/superpowers/specs/
notes/
/demo/
+# Local Ghost selection event tapes
+**/.ghost/.events
+**/fingerprint/.events
diff --git a/CODEOWNERS b/CODEOWNERS
index 05435e95..b7aadb8c 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,24 +1,2 @@
-# This CODEOWNERS file denotes the project leads
-# and encodes their responsibilities for code review.
-
-# Instructions: At a minimum, replace the '@GITHUB_USER_NAME_GOES_HERE'
-# here with at least one project lead.
-
-# Lines starting with '#' are comments.
-# Each line is a file pattern followed by one or more owners.
-# The format is described: https://github.blog/2017-07-06-introducing-code-owners/
-
-# These owners will be the default owners for everything in the repo.
-* @nahiyankhan
-
-
-# -----------------------------------------------
-# BELOW THIS LINE ARE TEMPLATES, UNUSED
-# -----------------------------------------------
-# Order is important. The last matching pattern has the most precedence.
-# So if a pull request only touches javascript files, only these owners
-# will be requested to review.
-# *.js @octocat @github/js
-
-# You can also use email addresses if you prefer.
-# docs/* docs@example.com
\ No newline at end of file
+# Default owner for all files.
+* @nahiyankhan
\ No newline at end of file
diff --git a/apps/docs/src/components/docs/icons.tsx b/apps/docs/src/components/docs/icons.tsx
index 6517c562..be85559c 100644
--- a/apps/docs/src/components/docs/icons.tsx
+++ b/apps/docs/src/components/docs/icons.tsx
@@ -113,31 +113,6 @@ export const Icons = {
/>
),
- cashapp: (props: IconProps) => (
-
- Cash App Logo
-
-
-
- ),
paypal: (props: IconProps) => (
`. Trials run concurrently and sample at the
- endpoint's default temperature — trial-to-trial variance is the signal
- being measured, so it is not pinned to 0.
+- `openai-compatible` — a real LLM behind any OpenAI-compatible chat API.
+ Configure `CONTEXT_CONTROL_BASE_URL`, `CONTEXT_CONTROL_API_KEY`, and
+ `CONTEXT_CONTROL_MODEL` in an untracked `.env` or `.env.local` at the
+ working directory. The CLI loads it on startup and makes this adapter the
+ default when all three values are present. Trials run concurrently and
+ sample at the endpoint's default temperature because trial-to-trial variance
+ is the signal being measured.
Add providers to `MODEL_ADAPTERS` in `lib/model.mjs`.
diff --git a/packages/context-control/cli.mjs b/packages/context-control/cli.mjs
index 1af5b26f..39c0f1fd 100644
--- a/packages/context-control/cli.mjs
+++ b/packages/context-control/cli.mjs
@@ -6,8 +6,8 @@ import { resolve } from "node:path";
import { defaultGhostBin } from "./lib/ghost.mjs";
import { startServer } from "./lib/server.mjs";
-// Load .env from the working directory if present (DATABRICKS_HOST lives
-// in an untracked .env / .env.local, same convention as the ghost CLI).
+// Load optional model-adapter configuration from untracked env files, using
+// the same convention as the ghost CLI.
for (const envFile of [".env", ".env.local"]) {
const envPath = resolve(process.cwd(), envFile);
if (existsSync(envPath)) {
diff --git a/packages/context-control/lib/model.mjs b/packages/context-control/lib/model.mjs
index 2b03f47d..1c498313 100644
--- a/packages/context-control/lib/model.mjs
+++ b/packages/context-control/lib/model.mjs
@@ -8,14 +8,9 @@
// deliberately imperfect — it produces the speckle pattern the heatmap
// exists to expose. Use it to exercise the UI loop.
//
-// - databricks: a real LLM behind a Databricks serving endpoint with an
-// OpenAI-compatible chat API. This is the model actually under test:
-// it sees the ask plus the menu (id, kind, description — exactly the
+// - openai-compatible: a real LLM behind an OpenAI-compatible chat API.
+// It sees the ask plus the menu (id, kind, description — exactly the
// selection surface `ghost gather` emits) and returns node ids.
-import { execFile } from "node:child_process";
-import { promisify } from "node:util";
-
-const execFileAsync = promisify(execFile);
const STOPWORDS = new Set([
"a",
@@ -127,52 +122,47 @@ export function parseIdReply(text) {
}
}
-/**
- * Databricks serving-endpoint adapter (OpenAI-compatible chat API).
- * Auth comes from the `databricks` CLI's cached OAuth token; the host
- * comes from DATABRICKS_HOST. Nothing is stored.
- */
-export function databricksModel({
- host = process.env.DATABRICKS_HOST,
- endpoint = process.env.CONTEXT_CONTROL_ENDPOINT ?? "goose",
+/** OpenAI-compatible chat adapter configured entirely through public env vars. */
+export function openAICompatibleModel({
+ baseUrl = process.env.CONTEXT_CONTROL_BASE_URL,
+ apiKey = process.env.CONTEXT_CONTROL_API_KEY,
+ model = process.env.CONTEXT_CONTROL_MODEL,
} = {}) {
- if (!host) throw new Error("databricks model needs DATABRICKS_HOST");
- let tokenPromise = null;
- const getToken = () => {
- tokenPromise ??= execFileAsync("databricks", [
- "auth",
- "token",
- "--host",
- host,
- ]).then(({ stdout }) => JSON.parse(stdout).access_token);
- return tokenPromise;
- };
+ if (!baseUrl) {
+ throw new Error("openai-compatible model needs CONTEXT_CONTROL_BASE_URL");
+ }
+ if (!apiKey) {
+ throw new Error("openai-compatible model needs CONTEXT_CONTROL_API_KEY");
+ }
+ if (!model) {
+ throw new Error("openai-compatible model needs CONTEXT_CONTROL_MODEL");
+ }
return {
- name: `databricks:${endpoint}`,
+ name: "openai-compatible",
async select({ ask, menu, cover }) {
- const token = await getToken();
const res = await fetch(
- `${host}/serving-endpoints/${endpoint}/invocations`,
+ `${baseUrl.replace(/\/$/, "")}/chat/completions`,
{
method: "POST",
headers: {
- authorization: `Bearer ${token}`,
+ authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify({
+ model,
messages: [
{ role: "system", content: SELECT_SYSTEM },
{ role: "user", content: selectUser(ask, menu, cover) },
],
- // Trial-to-trial variance is the signal being measured, so
- // sample at the endpoint's default temperature; do not pin 0.
+ // Trial-to-trial variance is the signal being measured, so sample at
+ // the endpoint's default temperature rather than pinning it to zero.
max_tokens: 1024,
}),
},
);
if (!res.ok) {
throw new Error(
- `databricks ${endpoint}: ${res.status} ${await res.text()}`,
+ `openai-compatible model: ${res.status} ${await res.text()}`,
);
}
const data = await res.json();
@@ -182,14 +172,14 @@ export function databricksModel({
}
const MODEL_ADAPTERS = {
- databricks: {
- available: () => Boolean(process.env.DATABRICKS_HOST),
- create: (name) => {
- const endpoint = name.includes(":")
- ? name.slice(name.indexOf(":") + 1)
- : null;
- return databricksModel(endpoint ? { endpoint } : {});
- },
+ "openai-compatible": {
+ available: () =>
+ Boolean(
+ process.env.CONTEXT_CONTROL_BASE_URL &&
+ process.env.CONTEXT_CONTROL_API_KEY &&
+ process.env.CONTEXT_CONTROL_MODEL,
+ ),
+ create: () => openAICompatibleModel(),
},
"fake-lexical": {
available: () => true,
@@ -199,10 +189,9 @@ const MODEL_ADAPTERS = {
/** Resolve a model name exposed by availableModels. */
export function resolveModel(name = availableModels()[0]) {
- const key = name?.startsWith("databricks:") ? "databricks" : name;
- const adapter = MODEL_ADAPTERS[key];
+ const adapter = MODEL_ADAPTERS[name];
if (!adapter) throw new Error(`unknown model: ${name}`);
- return adapter.create(name);
+ return adapter.create();
}
/** Available adapter names in default order. */
diff --git a/packages/context-control/test/context-control.test.ts b/packages/context-control/test/context-control.test.ts
index 7a13bbcd..81e3710e 100644
--- a/packages/context-control/test/context-control.test.ts
+++ b/packages/context-control/test/context-control.test.ts
@@ -4,7 +4,7 @@ import { join, resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { initFingerprintPackage } from "../../ghost/src/scan/fingerprint-package.js";
import { parseAsks } from "../lib/bench.mjs";
-import { parseIdReply } from "../lib/model.mjs";
+import { openAICompatibleModel, parseIdReply } from "../lib/model.mjs";
import {
consistency,
jaccard,
@@ -145,6 +145,21 @@ describe("demo asks", () => {
});
});
+describe("openAICompatibleModel", () => {
+ it("requires portable endpoint configuration", () => {
+ expect(() => openAICompatibleModel({})).toThrow("CONTEXT_CONTROL_BASE_URL");
+ expect(() =>
+ openAICompatibleModel({ baseUrl: "https://models.example.com/v1" }),
+ ).toThrow("CONTEXT_CONTROL_API_KEY");
+ expect(() =>
+ openAICompatibleModel({
+ baseUrl: "https://models.example.com/v1",
+ apiKey: "test-key",
+ }),
+ ).toThrow("CONTEXT_CONTROL_MODEL");
+ });
+});
+
describe("parseIdReply", () => {
it("parses a bare JSON array", () => {
expect(parseIdReply('["a", "b.c"]')).toEqual(["a", "b.c"]);
diff --git a/packages/ghost/test/cli.test.ts b/packages/ghost/test/cli.test.ts
index 064d1e52..4d090be5 100644
--- a/packages/ghost/test/cli.test.ts
+++ b/packages/ghost/test/cli.test.ts
@@ -1519,7 +1519,7 @@ describe("ghost CLI", () => {
[
"relay",
"gather",
- "Code/Features/Lending/LendingUI",
+ "Sources/Features/Transfers/TransfersUI",
"--package",
".ghost",
"--format",
@@ -1538,14 +1538,16 @@ describe("ghost CLI", () => {
expect(json).toHaveProperty("stackDirs");
expect(json).toHaveProperty("brief");
expect(json.source.kind).toBe("package");
- expect(json.targetPaths).toEqual(["Code/Features/Lending/LendingUI"]);
+ expect(json.targetPaths).toEqual([
+ "Sources/Features/Transfers/TransfersUI",
+ ]);
expect(json.stackDirs).toHaveLength(1);
expect(typeof json.brief).toBe("string");
expect(json.context.schema).toBe("ghost.relay-context/v1");
expect(json).not.toHaveProperty("context_packet");
expect(json.context.target).toMatchObject({
mode: "generation",
- paths: ["Code/Features/Lending/LendingUI"],
+ paths: ["Sources/Features/Transfers/TransfersUI"],
});
expect(json.context.sections.intent).toEqual(
expect.arrayContaining([
@@ -1572,7 +1574,7 @@ describe("ghost CLI", () => {
why_selected: expect.arrayContaining([
{
kind: "linked_ref",
- value: "inventory.exemplar:lending-tokenized-screen",
+ value: "inventory.exemplar:transfers-tokenized-screen",
},
]),
}),
@@ -1581,12 +1583,12 @@ describe("ghost CLI", () => {
kind: "composition",
}),
expect.objectContaining({
- ref: "inventory.exemplar:lending-tokenized-screen",
+ ref: "inventory.exemplar:transfers-tokenized-screen",
kind: "inventory",
- path: "Code/Features/Lending/LendingUI",
+ path: "Sources/Features/Transfers/TransfersUI",
why_selected: expect.arrayContaining([
- { kind: "path", value: "Code/Features/Lending/LendingUI" },
- { kind: "scope", value: "lending" },
+ { kind: "path", value: "Sources/Features/Transfers/TransfersUI" },
+ { kind: "scope", value: "transfers" },
{ kind: "surface_type", value: "native-feature" },
]),
}),
@@ -1602,9 +1604,9 @@ describe("ghost CLI", () => {
expect(json.selected_context.suggested_reads).toEqual(
expect.arrayContaining([
expect.objectContaining({
- path: "Code/Features/Lending/LendingUI",
+ path: "Sources/Features/Transfers/TransfersUI",
reason:
- "source surface for inventory.exemplar:lending-tokenized-screen",
+ "source surface for inventory.exemplar:transfers-tokenized-screen",
}),
]),
);
@@ -2227,7 +2229,7 @@ async function writeCheckPackage(
`schema: ghost.fingerprint/v1
intent:
summary:
- product: Cash iOS
+ product: Harbor iOS
situations: []
principles:
- id: tokenized-ui-color
@@ -2236,16 +2238,16 @@ intent:
experience_contracts: []
inventory:
exemplars:
- - id: lending-tokenized-screen
- path: Code/Features/Lending/LendingUI
- title: Lending tokenized UI
- surface: lending
- why: Shows semantic CashTheme color usage for native lending UI.
+ - id: transfers-tokenized-screen
+ path: Sources/Features/Transfers/TransfersUI
+ title: Transfers tokenized UI
+ surface: transfers
+ why: Shows semantic HarborTheme color usage for native transfers UI.
refs:
- intent.principle:tokenized-ui-color
- composition.pattern:tokenized-ui-color
building_blocks:
- tokens: [CashTheme.primary]
+ tokens: [HarborTheme.primary]
components: []
composition:
patterns:
@@ -2257,7 +2259,7 @@ composition:
options.checks === false
? undefined
: `schema: ghost.validate/v1
-id: cash-ios
+id: harbor-ios
checks:
- id: no-hardcoded-ui-color
title: Use design tokens for UI color
@@ -2266,9 +2268,9 @@ checks:
derivation:
intent: [intent.principle:tokenized-ui-color]
composition: [composition.pattern:tokenized-ui-color]
- inventory: [inventory.exemplar:lending-tokenized-screen]
+ inventory: [inventory.exemplar:transfers-tokenized-screen]
applies_to:
- paths: [Code/Features/Lending]
+ paths: [Sources/Features/Transfers]
detector:
type: forbidden-regex
pattern: '${detectorPattern}'
@@ -2277,8 +2279,8 @@ checks:
support: 0.94
observed_count: 47
examples:
- - Code/Features/Lending/LendingUI
- repair: Replace literals with Arcade/Cash semantic tokens.
+ - Sources/Features/Transfers/TransfersUI
+ repair: Replace literals with Harbor semantic tokens.
- id: candidate-density-check
title: Candidate density check
status: proposed
@@ -2286,21 +2288,21 @@ checks:
derivation:
intent: [intent.principle:tokenized-ui-color]
applies_to:
- paths: [Code/Features/Lending]
+ paths: [Sources/Features/Transfers]
detector:
type: required-regex
- pattern: 'CashTheme'
+ pattern: 'HarborTheme'
evidence:
support: 0.5
observed_count: 1
examples:
- - Code/Features/Lending/LendingUI
+ - Sources/Features/Transfers/TransfersUI
`,
);
await writeFile(
join(pkg, "resources.yml"),
`schema: ghost.resources/v1
-id: cash-ios
+id: harbor-ios
primary:
target: .
`,
@@ -2320,7 +2322,7 @@ primary:
await writeFile(
join(pkg, "patterns.yml"),
`schema: ghost.patterns/v1
-id: cash-ios
+id: harbor-ios
surface_types: []
composition_patterns: []
`,
@@ -2473,7 +2475,7 @@ checks:
derivation:
intent: [${intentRef}]
applies_to:
- paths: [Code/Features/Lending]
+ paths: [Sources/Features/Transfers]
detector:
type: forbidden-regex
pattern: '#[0-9a-fA-F]{3,8}'
@@ -2482,15 +2484,15 @@ checks:
support: 0.94
observed_count: 47
examples:
- - Code/Features/Lending/LendingUI
+ - Sources/Features/Transfers/TransfersUI
`;
}
function mapWithScopes(): string {
return `---
schema: ghost.map/v1
-id: cash-ios
-repo: squareup/cash-ios
+id: harbor-ios
+repo: example/harbor-ios
mapped_at: 2026-05-06T00:00:00.000Z
platform: ios
languages:
@@ -2506,31 +2508,31 @@ composition:
- design-tokens
design_system:
paths:
- - Code/DesignSystem
+ - Sources/DesignSystem
status: active
surface_sources:
render_strategy: static-source
include:
- - Code/Features/**
+ - Sources/Features/**
exclude:
- "**/Tests/**"
feature_areas:
- - name: lending
+ - name: transfers
paths:
- - Code/Features/Lending
+ - Sources/Features/Transfers
scopes:
- - id: lending
- name: Lending
+ - id: transfers
+ name: Transfers
kind: product-surface
paths:
- - Code/Features/Lending
+ - Sources/Features/Transfers
orientation_files:
- README.md
---
## Identity
-Cash iOS.
+Harbor iOS.
## Topology
diff --git a/packages/vessel-react/fingerprint/.events b/packages/vessel-react/fingerprint/.events
deleted file mode 100644
index 14a7fa82..00000000
--- a/packages/vessel-react/fingerprint/.events
+++ /dev/null
@@ -1,2 +0,0 @@
-{"ts":"2026-07-08T11:24:21.294Z","event":"gather","menu":["anti-goal.median","contract.theming","contract.tokens","index","pattern.conversation","primitive.composition","primitive.controls"],"wild":false,"wildIds":[]}
-{"ts":"2026-07-08T11:24:37.764Z","event":"pull","ids":["primitive.controls"],"inlinedMaterials":11,"omittedMaterials":0}
diff --git a/packages/vessel-react/scripts/resolve-dts-paths.mjs b/packages/vessel-react/scripts/resolve-dts-paths.mjs
index f1945b37..d2b0255e 100644
--- a/packages/vessel-react/scripts/resolve-dts-paths.mjs
+++ b/packages/vessel-react/scripts/resolve-dts-paths.mjs
@@ -4,8 +4,8 @@
* real relative paths. tsc leaves the aliases verbatim; consumers can't
* resolve them because they don't have the same tsconfig `paths`.
*
- * Tiny in-tree alternative to the `tsc-alias` npm package (which we can't
- * reliably install through the Block artifactory right now).
+ * Tiny in-tree alternative to the `tsc-alias` npm package, avoiding an
+ * extra runtime dependency.
*/
import { readdir, readFile, writeFile } from "node:fs/promises";