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
1 change: 0 additions & 1 deletion .husky/commit-msg

This file was deleted.

61 changes: 2 additions & 59 deletions bun.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/ingest-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"@bb/errors": "workspace:*",
"@bb/graph-core": "workspace:*",
"@bb/graph-db": "workspace:*",
"@bb/ladybug": "workspace:*",
"@bb/llm": "workspace:*",
"@bb/logger": "workspace:*",
"@bb/mongo": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/ingest-core/src/pipeline/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ async function repoLocationFor(knowledgeId: string, commitHash?: string): Promis
provider: "local",
orgId,
knowledgeId,
branch: kDoc.info.branch ?? "main",
commitHash: commitHash ?? kDoc.source.sourcePath, // best-effort fallback when no commit
};
}
Expand All @@ -142,6 +143,7 @@ async function repoLocationFor(knowledgeId: string, commitHash?: string): Promis
knowledgeId,
owner: parsed.owner,
repo: parsed.repo,
branch: kDoc.info.branch ?? "main",
commitHash: effectiveCommit,
};
}
Expand Down
1 change: 0 additions & 1 deletion packages/ingest-github/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@bb/mongo": "workspace:*",
"@bb/sqlite": "workspace:*",
"@bb/neo4j": "workspace:*",
"@bb/ladybug": "workspace:*",
"@bb/queue": "workspace:*",
"@bb/db-core": "workspace:*",
"@bb/graph-core": "workspace:*",
Expand Down
17 changes: 15 additions & 2 deletions packages/ingest-github/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ import "@bb/neo4j";
export interface BootstrapRuntimeOptions {
config: unknown;
loggerFactory: LoggerFactory;
/**
* Skip the OSS knowledge/concept-graph index bootstrap (`ensureKnowledgeIndexes`
* / `ensureConceptGraphIndexes`). A composition root sets this when it owns the
* Neo4j graph schema itself — e.g. the enterprise ingestion engine, whose
* `ensureIrGraphSchema()` is the sole (branch-scoped) authority for the `:File`
* / `:FileVersion` / … constraints; the OSS branchless `file_unique` here would
* otherwise reject a second branch's file. Connections are still established.
* Default `false` (OSS standalone keeps creating its own indexes).
*/
skipGraphIndexes?: boolean;
}

export async function bootstrapRuntime(opts: BootstrapRuntimeOptions): Promise<void> {
Expand All @@ -35,6 +45,9 @@ export async function bootstrapRuntime(opts: BootstrapRuntimeOptions): Promise<v

// Fulltext indexes the MCP smart_search / keyword_lookup tools query against.
// Idempotent (MERGE-based) so duplicate calls across composition roots are safe.
await indexesGraph.ensureKnowledgeIndexes();
await indexesGraph.ensureConceptGraphIndexes();
// Skipped when the composition root owns the graph schema (see `skipGraphIndexes`).
if (opts.skipGraphIndexes !== true) {
await indexesGraph.ensureKnowledgeIndexes();
await indexesGraph.ensureConceptGraphIndexes();
}
}
4 changes: 2 additions & 2 deletions packages/ingest-github/src/pipeline/github-index-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const resolveGithubIndexSource: IndexSourceResolver = async (input) => {
source = factoryResult.source;
commitHash = factoryResult.commitHash;
archiveSink = factoryResult.archiveSink;
location = { provider: "github", orgId, knowledgeId, owner, repo, commitHash };
location = { provider: "github", orgId, knowledgeId, owner, repo, branch, commitHash };
// The factory has already produced the source tree; meta-output dirs still
// need to exist before the strategy writes scan-manifest.json. Idempotent.
await ensureCommitDirs(location);
Expand All @@ -65,7 +65,7 @@ export const resolveGithubIndexSource: IndexSourceResolver = async (input) => {
`could not resolve HEAD commit hash for ${owner}/${repo}@${branch} before clone`,
);
}
location = { provider: "github", orgId, knowledgeId, owner, repo, commitHash: resolvedSha };
location = { provider: "github", orgId, knowledgeId, owner, repo, branch, commitHash: resolvedSha };
await ensureCommitDirs(location);
const repoDir = pathsFor(location).repositoryDir;
const cloneOpts: { repoUrl: string; branch: string; destinationDir: string; gitToken?: string } = {
Expand Down
2 changes: 2 additions & 0 deletions packages/ingest-github/src/pipeline/pull-source-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export async function resolvePullSource(input: ResolvePullSourceInput): Promise<
knowledgeId,
owner,
repo,
branch,
commitHash: factoryResult.targetCommit,
};
logger.info(
Expand Down Expand Up @@ -99,6 +100,7 @@ export async function resolvePullSource(input: ResolvePullSourceInput): Promise<
knowledgeId,
owner,
repo,
branch,
commitHash: resolvedTarget,
};
await ensureCommitDirs(location);
Expand Down
4 changes: 3 additions & 1 deletion packages/ingest-github/src/pipeline/run-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export async function runLocal(
// repository/ dir); only meta-output lives under the kube-v2 tree.
const commitHash = `local-${startedAt}`;
const orgId = resolveOrgId(payload);
const location: RepoLocation = { provider: "local", orgId, knowledgeId, commitHash };
// Local sources have no git branch; use a stable synthetic segment so the
// branch-scoped layout slot is populated.
const location: RepoLocation = { provider: "local", orgId, knowledgeId, branch: "main", commitHash };
await ensureCommitDirs(location);
const metaPaths = pathsFor(location);

Expand Down
1 change: 0 additions & 1 deletion packages/ladybug/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"@bb/graph-core": "workspace:*",
"@bb/graph-db": "workspace:*",
"@bb/types": "workspace:*",
"@ladybugdb/core": "^0.16.1",
"parquetjs": "^0.11.2"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ladybug/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Database, Connection, PreparedStatement, type LbugValue } from "@ladybugdb/core";
import { Database, Connection, PreparedStatement, type LbugValue } from "./coreStub.ts";
import { getConfigValue } from "@bb/config";
import { Config } from "@bb/types";

Expand Down
58 changes: 58 additions & 0 deletions packages/ladybug/src/coreStub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0-only WITH non-commercial-clause
//
// Local stand-in for the native `@ladybugdb/core` addon.
//
// The native LadybugDB graph provider is NOT part of the ByteBell deployables
// (admin-server / knowledge-server): those use the Neo4j graph provider plus the
// on-disk JSONL ladybug dumps read by `@bytebell/utils`, and never load this
// native binding. `@ladybugdb/core` ships a large per-platform prebuilt native
// tarball that `bun` cannot extract under QEMU during multi-arch
// `docker buildx` on an Apple-Silicon host — which broke `make ecr-publish`
// ("Fail extracting tarball for @ladybugdb/core-linux-x64"). The external dep
// was therefore removed from `@bb/ladybug`'s manifest.
//
// These stubs preserve exactly the type surface `client.ts` / `provider.ts`
// compile against. Constructing or calling any of them throws — the native
// provider is simply unavailable in this build variant.

export type LbugValue = unknown;

const UNAVAILABLE = "LadybugDB native provider (@ladybugdb/core) is not available in this build.";

export interface QueryResult {
getAll(): Promise<unknown[]>;
}

export class Database {
constructor(_path: string) {
throw new Error(UNAVAILABLE);
}
}

export class PreparedStatement {
isSuccess(): boolean {
throw new Error(UNAVAILABLE);
}

getErrorMessage(): string {
throw new Error(UNAVAILABLE);
}
}

export class Connection {
constructor(_db: Database) {
throw new Error(UNAVAILABLE);
}

query(_query: string): Promise<QueryResult> {
throw new Error(UNAVAILABLE);
}

prepare(_query: string): Promise<PreparedStatement> {
throw new Error(UNAVAILABLE);
}

execute(_prepared: PreparedStatement, _params: Record<string, LbugValue>): Promise<QueryResult | QueryResult[]> {
throw new Error(UNAVAILABLE);
}
}
2 changes: 1 addition & 1 deletion packages/ladybug/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as guidepostsRepo from "./guideposts.ts";

import { registerGraphProvider } from "@bb/graph-db";
import type { IGraphDatabaseProvider } from "@bb/graph-core";
import type { LbugValue } from "@ladybugdb/core";
import type { LbugValue } from "./coreStub.ts";

class LadybugGraphProvider implements IGraphDatabaseProvider {
knowledge = {
Expand Down
3 changes: 3 additions & 0 deletions packages/mcp/src/repoFs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export async function resolveCloneDir(knowledgeId: string): Promise<string> {
knowledgeId,
owner: parsed.owner,
repo: parsed.repo,
// Read-side resolves the branch the KB was indexed under (info.branch is set
// at index time). Multi-branch read selection is a separate MCP-side effort.
branch: kDoc.info.branch ?? "main",
commitHash: commitId,
};
return repositoryDirFor(getBytebellHome(), loc);
Expand Down
1 change: 1 addition & 0 deletions packages/mongo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
setKnowledgeCommit,
setKnowledgeState,
setKnowledgeBranch,
setBranchHead,
updateKnowledgeProgress,
upsertKnowledge,
listKnowledge,
Expand Down
70 changes: 69 additions & 1 deletion packages/mongo/src/knowledge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { KnowledgeDoc, KnowledgeState } from "@bb/types";
import { branchIdFor, type KnowledgeDoc, type KnowledgeState } from "@bb/types";
import { KnowledgeNotFoundError } from "@bb/errors";
import { _getDb } from "./client.ts";
import { Collections } from "./collections.ts";
Expand Down Expand Up @@ -89,6 +89,74 @@ export async function setKnowledgeBranch(knowledgeId: string, branch: string): P
}
}

/**
* Records that `branch` is indexed at `commitHash` on this knowledge, under the
* branch-per-KB model (`source.branches[]`). Upserts the branch element:
* advances its `headCommit`, appends `commitHash` to the branch's own deduped
* `commitHashes` history, and (optionally) sets the branch's processing
* `state`. Seeds `source.defaultBranch` on the first branch recorded. Idempotent
* per (branch, commit). Throws `KnowledgeNotFoundError` if the doc is missing.
*
* Callers: the branch-add trigger (state = PROCESSING at enqueue) and the
* worker on completion (state = PROCESSED + token usage).
*/
export async function setBranchHead(
knowledgeId: string,
branch: string,
commitHash: string,
opts: { state?: KnowledgeState; inputTokens?: string; outputTokens?: string; costUsd?: string } = {},
): Promise<void> {
const db = _getDb();
const commitEntry = {
hash: commitHash,
inputTokens: opts.inputTokens ?? "",
outputTokens: opts.outputTokens ?? "",
costUsd: opts.costUsd ?? "0",
};

// 1. Create the branch element if it doesn't exist yet (first commit seeds its history).
const inserted = await db.collection(Collections.Knowledge).updateOne(
{ knowledgeId, "source.branches.name": { $ne: branch } },
{
$addToSet: {
"source.branches": {
name: branch,
branchId: branchIdFor(branch),
headCommit: commitHash,
commitHashes: [commitEntry],
...(opts.state !== undefined ? { state: opts.state } : {}),
},
},
$set: { updatedAt: new Date() },
},
);

// 2. Otherwise advance the existing branch's head + append its commit.
if (inserted.modifiedCount === 0) {
const set: Record<string, unknown> = { "source.branches.$.headCommit": commitHash, updatedAt: new Date() };
if (opts.state !== undefined) {
set["source.branches.$.state"] = opts.state;
}
const updated = await db
.collection(Collections.Knowledge)
.updateOne(
{ knowledgeId, "source.branches.name": branch },
{ $set: set, $addToSet: { "source.branches.$.commitHashes": commitEntry } },
);
if (updated.matchedCount === 0) {
throw new KnowledgeNotFoundError(knowledgeId);
}
}

// 3. Seed the default branch on the first branch recorded.
await db
.collection(Collections.Knowledge)
.updateOne(
{ knowledgeId, "source.defaultBranch": { $exists: false } },
{ $set: { "source.defaultBranch": branch } },
);
}

export async function updateKnowledgeProgress(
knowledgeId: string,
processedFiles: number,
Expand Down
9 changes: 8 additions & 1 deletion packages/path-migration/src/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,13 @@ export async function migrateOne(input: MigrateOneInput): Promise<void> {

if (doc.source.kind === "local") {
const syntheticCommit = `migrated-${doc.updatedAt.getTime()}`;
const newLoc: RepoLocation = { provider: "local", orgId, knowledgeId, commitHash: syntheticCommit };
const newLoc: RepoLocation = {
provider: "local",
orgId,
knowledgeId,
branch: doc.info.branch ?? "main",
commitHash: syntheticCommit,
};
await moveMetaIfPresent(ctx, legacyMetaRoot, newLoc);
return;
}
Expand All @@ -149,6 +155,7 @@ export async function migrateOne(input: MigrateOneInput): Promise<void> {
knowledgeId,
owner: parsed.owner,
repo: parsed.repo,
branch: doc.info.branch ?? "main",
commitHash: commitId,
};
await moveCloneIfPresent(ctx, legacyCloneDir, newLoc);
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type {
LocalKnowledgeSource,
KnowledgeListEntry,
CommitHashRecord,
BranchRecord,
TokenUsage,
UsageGuard,
} from "./knowledge.ts";
Expand Down Expand Up @@ -54,6 +55,7 @@ export type { EnrichmentFailure, EnrichmentFailureReason } from "./knowledge.ts"
export { IngestionStrategyType } from "./config.ts";
export {
orgsRootFor,
branchIdFor,
commitBaseDirFor,
repositoryDirFor,
metaOutputRootFor,
Expand Down
24 changes: 23 additions & 1 deletion packages/types/src/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,34 @@ export interface CommitHashRecord {
costUsd: string;
}

/**
* One indexed branch of a knowledge base. Branch support lets a single
* `knowledgeId` hold many branches side by side (see
* `code_history/004-branch-support.md`). `branchId` is `branchIdFor(name)`
* (sha256) — the same value used as the on-disk `<branchId>` path segment and
* the branch-scoped graph key. `commitHashes` is this branch's own indexed
* commit history (per-branch, deduped, oldest → newest); `headCommit` is its
* current head; `state` is this branch's own processing state (a branch can be
* PROCESSING while a sibling is PROCESSED).
*/
export interface BranchRecord {
name: string;
branchId: string;
headCommit?: string;
state?: KnowledgeState;
commitHashes?: (string | CommitHashRecord)[];
}

export interface GithubKnowledgeSource {
kind: "github";
/** Current head pointer — the most recently indexed commit. */
/** Current head pointer — the most recently indexed commit (of the default branch). */
commitId?: string;
/** Every commit this knowledge has been indexed at, oldest → newest. Pull appends to this list. */
commitHashes?: (string | CommitHashRecord)[];
/** The branch treated as "current" for default reads. Seeded on first branch indexed. */
defaultBranch?: string;
/** Per-branch indexed state. One `knowledgeId` may hold many branches. */
branches?: BranchRecord[];
}

export interface LocalKnowledgeSource {
Expand Down
Loading
Loading