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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to Context are documented here.

## Unreleased

## 0.6.10 - 2026-08-21

- Made repository recovery identity transport-independent by matching the full namespace/repository path while preserving pinned-commit and subpath validation.
- Added Gate-owned managed fast paths that preserve ordinary inspection and resolution capabilities while skipping redundant HTML review surfaces and dialogue resources under current-conversation authority.
- Added ordinary-mode guidance after workspace creation and source capture that explains the review/fully-managed trade-off before later approval rounds.

Expand Down
18 changes: 9 additions & 9 deletions bun.lock

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "context",
"version": "0.6.9",
"version": "0.6.10",
"packageManager": "bun@1.3.9",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/context-workflow/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema: agent-graph.provider.v1
id: c4a/context
version: 0.6.9
version: 0.6.10
name: Context workflow
description: Internal work contract for Context knowledge workspaces.
graphs:
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/context-cli",
"description": "Local runtime and Agent integration for traceable knowledge production",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,38 @@ async function fixture(): Promise<{
}

describe("repository source recovery", () => {
test("groups transport aliases by repository path and pinned commit", async () => {
const input = await fixture();
await writeRepoRegistry(input.project, {
repos: [
{
name: "20260813/alpha",
namespace: "20260813",
module: "alpha",
subpath: "packages/alpha",
git: {
remote: "git@source.example.com:example/product-monorepo.git",
ref: input.commit,
},
},
{
name: "20260813/beta",
namespace: "20260813",
module: "beta",
subpath: "packages/beta",
git: {
remote: "https://mirror.example.net/example/product-monorepo/",
ref: input.commit,
},
},
],
});

const plan = await repositoryRecoveryPlan({ projectRoot: input.project });
expect(plan.groups).toHaveLength(1);
expect(plan.groups[0]?.sources).toEqual(["20260813/alpha", "20260813/beta"]);
});

test("groups logical modules and restores them from one explicit local checkout", async () => {
const input = await fixture();
const plan = await repositoryRecoveryPlan({ projectRoot: input.project });
Expand Down Expand Up @@ -100,6 +132,39 @@ describe("repository source recovery", () => {
expect(readyPlan.next_action).toBeNull();
});

test("accepts a local checkout whose origin uses another transport host", async () => {
const input = await fixture();
git(input.checkout, [
"remote",
"set-url",
"origin",
"ssh://mirror-user@mirror.example.net:29418/example/product-monorepo.git",
]);

const result = await restoreRepositorySources({
projectRoot: input.project,
payload: {
schema: "context.repository-source-recovery.v1",
repositories: [{ source: "20260813/alpha", mode: "local", path: input.checkout }],
},
});

expect(result.restored[0]).toEqual(expect.objectContaining({ mode: "local", ready: true }));
});

test("rejects a local checkout with a different repository path", async () => {
const input = await fixture();
git(input.checkout, ["remote", "set-url", "origin", "git@mirror.example.net:other/product-monorepo.git"]);

await expect(restoreRepositorySources({
projectRoot: input.project,
payload: {
schema: "context.repository-source-recovery.v1",
repositories: [{ source: "20260813/alpha", mode: "local", path: input.checkout }],
},
})).rejects.toThrow("local repository origin does not match the registered source");
});

test("clones the pinned commit into the disposable repository cache", async () => {
const input = await fixture();
const bare = join(input.root, "origin.git");
Expand Down
34 changes: 24 additions & 10 deletions packages/context-cli/src/project/repoSourceRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,26 @@ export function parseRepositoryRecoveryPayload(value: unknown): RepositoryRecove
return { schema: RECOVERY_SCHEMA, repositories };
}

function canonicalRemote(value: string): string {
function trimRepositorySuffix(value: string): string {
return value.trim().replace(/^\/+|\/+$/gu, "").replace(/\.git$/iu, "");
}

function repositoryPathIdentity(value: string): string {
const trimmed = value.trim().replace(/\/+$/u, "").replace(/\.git$/iu, "");
const scp = /^(?:[^@]+@)?([^:]+):(.+)$/u.exec(trimmed);
if (scp !== null) return `${scp[1]?.toLowerCase()}/${scp[2]}`;
try {
const url = new URL(trimmed);
return `${url.hostname.toLowerCase()}${url.pathname}`.replace(/\/+$/u, "").replace(/\.git$/iu, "");
} catch {
return trimmed;
const windowsPath = /^[a-z]:[\\/]/iu.test(trimmed);
const urlSyntax = /^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed);
if (!windowsPath && urlSyntax) {
try {
const url = new URL(trimmed);
if (url.hostname.length > 0) return trimRepositorySuffix(url.pathname);
return url.pathname.replace(/\/+$/u, "").replace(/\.git$/iu, "");
} catch {
return trimmed;
}
}
const scp = windowsPath ? null : /^(?:[^@/:]+@)?[^/:]+:(.+)$/u.exec(trimmed);
if (scp?.[1] !== undefined) return trimRepositorySuffix(scp[1]);
return trimmed;
}

function repositorySlug(remote: string): string {
Expand All @@ -111,7 +121,7 @@ function groupId(source: Pick<RepoSourceRecord, "git">): string {
}

function groupKey(source: Pick<RepoSourceRecord, "git">): string {
return `${canonicalRemote(source.git.remote)}\u0000${source.git.ref.toLowerCase()}`;
return `${repositoryPathIdentity(source.git.remote)}\u0000${source.git.ref.toLowerCase()}`;
}

function suggestedCloneTarget(source: Pick<RepoSourceRecord, "git">): string {
Expand Down Expand Up @@ -217,10 +227,14 @@ async function verifyCheckout(input: {
subpaths: readonly string[];
}): Promise<void> {
const actualRemote = await git(input.checkout, ["remote", "get-url", "origin"]);
if (canonicalRemote(actualRemote) !== canonicalRemote(input.remote)) {
const expectedRepository = repositoryPathIdentity(input.remote);
const actualRepository = repositoryPathIdentity(actualRemote);
if (actualRepository !== expectedRepository) {
throw userInputError("local repository origin does not match the registered source", {
expected_remote: input.remote,
actual_remote: actualRemote,
expected_repository: expectedRepository,
actual_repository: actualRepository,
checkout: input.checkout,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/context/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/context",
"description": "Declarative SDK for Context knowledge sources, workflows, review, and package outputs",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/core",
"description": "Shared extraction types, schemas, and utilities for Context",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/dev-cli",
"description": "Developer menu for Context build, verification, link, and release preparation",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-go/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-go",
"description": "Go extraction plugin and structural index for Context",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-rush/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-rush",
"description": "Rush workspace structural index for Context",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-ts",
"description": "TypeScript extraction plugin for the Context ExtractionResult v2 contract",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract",
"description": "Language-plugin framework and repository runner for Context code evidence",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/tui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/tui",
"description": "Shared terminal UI components (Ink + React) for Context development tools",
"version": "0.6.9",
"version": "0.6.10",
"type": "module",
"license": "MIT",
"repository": {
Expand Down